Стек Z80: назначение регистра SP и установка в ассемблере

ZX Land #05

Ты говоришь на ассемблере?

(часть третья)

Приветствую всех, кто хочет чего-то большего, чем просто играть, и ищет другие способы самореализации. Знать, как выполнить ту или иную задачу, действительно приятно. А если нас за это ещё и похвалят…

Но перейдём к делу!

Постараюсь объяснить, что такое «стек», как он работает и как им пользоваться.

В словаре польского языка под этим словом можно прочитать: «много предметов, сложенных один на другой; большое количество чего-либо…». В компьютере стек устроен похожим образом. Он служит для сохранения значений регистров.

Стек, конечно же, находится в оперативной памяти RAM, а конкретное место в ней задаёт специальный 16-битный регистр SP. Инструкция BASIC CLEAR адрес устанавливает основание стека. На ассемблере это может выглядеть так:

1  LD SP,адрес     ; установка стека с указанного адреса
2  LD SP,HL        ; установка основания стека
3  LD SP,IX        ; по значению
4  LD SP,IY        ; соответствующего регистра
5  LD SP,(адрес)   ; установка стека по значению двух
                   ; ячеек памяти

Но не спешите! Появилось немало новых понятий. Для начала, пожалуй, объясню, что такое «основание стека». Это его самая глубокая точка. Этот адрес означает, что регистры будут сохраняться в стеке, начиная с указанного адреса и двигаясь назад — к началу памяти.

Команда LD SP,HL — это загрузка, то есть копирование значения регистров HL в SP.

То же самое делают команды LD SP,IX и LD SP,IY. Стоит знать, что существуют такие регистры, как IX и IY. В разных умных книгах их называют индексными регистрами. Но ими мы займёмся в другой раз.

Пятая команда в нашем списке, LD SP,(адрес), означает установку основания стека по значению двух ячеек памяти. Первая ячейка — так называемая младшая, вторая — старшая. Старшая задаёт область памяти с точностью до 256 байт и называется страницей, а младшая указывает, о каком из этих 256 байтов идёт речь.

Например, адрес 16384 в записи «младший — старший» будет выглядеть так:

адрес   = 0
адрес+1 = 64, то есть:
256*(адрес+1)+(адрес) = 256*64+0 = 16384

А теперь, наконец, о том, как сохранить регистр с помощью стека. К сожалению, сохранить один регистр нельзя. Это обязательно должна быть пара регистров.

Основная инструкция:

PUSH пара регистров

То есть, если процессор, например, встретит команду

PUSH HL

он выполнит следующие действия:

DEC SP
LD  (SP),H  ; старший
DEC SP
LD  (SP),L  ; младший

Разумеется, извлечение регистров происходит в обратном порядке. Основная инструкция для этого:

POP пара регистров

Например:

POP HL

это приведёт к следующим действиям процессора:

LD  L,(SP)  ; младший
INC SP
LD  H,(SP)  ; старший
INC SP

Выполнение инструкции PUSH HL не обязательно должно сопровождаться инструкцией POP HL. Значение регистров можно извлечь из стека в любую пару регистров, например DE.

PUSH HL
POP  DE

Процессор не знает, сохраняли мы значение HL или DE. Поэтому, если мы сохранили больше одной пары и хотим после извлечения получить в них те же значения, инструкции POP должны идти в обратном порядке.

PUSH HL
PUSH DE
  .
  .     ; программа
  .
POP  DE
POP  HL

Но это ещё не все инструкции, использующие стек. Существует, среди прочего, инструкция

CALL адрес

для вызова подпрограмм. Это аналог команды BASIC GO SUB: встретив RETURN, программа возвращается в основную часть. Если говорить проще: CALL помещает в стек адрес следующей за ней инструкции, которую предстоит выполнить, а RET извлекает этот адрес и переходит по нему.

  40000 CALL 40100 ; DEC SP
                LD  (SP),40003
  40003  .
         .
         .
  40100 RET ; JP (SP) INC SP

Стек очень полезен, однако ошибки при написании процедур часто возникают из-за неправильного его использования. Так случается даже у самых опытных программистов. Мой первый совет: следите за тем, чтобы количество команд PUSH и POP совпадало. Иначе, например, если забыть POP, а затем выполнить RET, процессор перейдёт по адресу, который не был извлечён из стека вместе с парой регистров. Чаще всего это приводит к «зависанию» программы или перезагрузке системы.

Теперь я покажу короткую программку, которая должна проиллюстрировать сказанное выше. Но для полного понимания нам ещё нужно узнать о команде ADD HL,DE, которая здесь встречается. Она прибавляет значение DE к HL. Подробнее об арифметике Z-80 мы поговорим в следующей части цикла.

 10        ORG  40000 ; показ экрана
 20 TLO    EQU  127   ; цвет фона
 30 KOLOR  EQU  7     ; цвет экрана
 40 OPOZN  EQU  2     ; задержка

Постоянные значения задаются с помощью директивы EQU.

 50        LD   HL,22528 ; закраска
 60        LD   DE,22529 ; экрана
 70        LD   BC,767   ; цветом
 80        LD   (HL),TLO ; фона
 90        LDIR
100 START  LD   DE,32
110        LD   HL,22528
120        LD   B,8
130 PETLA  PUSH BC
140        PUSH HL
150        LD   B,OPOZN
160 SLOW   HALT
170        DJNZ SLOW
180        LD   B,3
190 SUPEL  CALL LOOK ; переход к
                     подпрограмме
200        INC  H    ; увеличение
   пары регистров HL на 256 за счёт
   увеличения только старшего байта
   пары HL — переход к следующей трети
210        DJNZ SUPEL
220        POP  HL
230        POP  BC
240        ADD  HL,DE ; переход
                       на строку ниже
250        DJNZ PETLA
260        RET         ; выход из процедуры
270 LOOK   PUSH HL     ; подпрограмма
280        PUSH BC     ; рисования полосы
290        LD   B,32
300 LOOP   LD   (HL),KOLOR
310        INC  HL
320        DJNZ LOOP
330        POP  BC
340        POP  HL
350        RET         ; выход из подпрограммы

P. S. Если мои рассуждения и наблюдения изложены слишком сложно, прошу написать мне об этом и связаться со мной.

SCALMA

Поделитесь вашим мнением о статье