Из журнала ZX Format №№1-2, Санкт-Петербург, 11.1995
(C) Михаил Спицын.
Курс прерван по просьбам читателей.
Мы начинаем цикл статей по ассемблеру для начинающих, с целью побудить людей к изучению данного языка.
В этих статьях мы раскажем о строении компьютера, графике, звуке (48 и 128) и, естественно, о самом ассемблере.
Итак...
1. ПРОЦЕССОР КОМПЬЮТЕРА
Как человек, так и любой компьютер имеет мозг; этим мозгом у компьютера является процессор.
А что же за "мозг" у нашего Speccy?
"Мозг" Spectrum'а - это процессор Z80 фирмы ZILOG. Процессор Z80 является псевдошестнадцатиразрядным. Как это понять?
Дело в том, что каждый процессор может понимать числа определенной разрядности. К примеру восьмиразрядное число записывается как комбинация из восьми двоичных нулей и единиц и может обозначать любое целое число от 0 до 255.
Каждый разряд такого числа, это степень двойки, например двоичное число 01100110 соответствует десятичному 102, а перевести из десятичной в двоичную систему можно так:
2^7*0+2^6*1+2^5*1+2^4*0+2^3*0+2^2*1+2^1*1+2^0*0=102
Продолжим. Z80 имеет двадцать восьмиразрядных регистров (регистр это ячейка внутри процессора, куда можно записывать числа) и все регистры объединяются в пары, каждая из которых так и называется: "регистровая пара".
Вот мы и добрались до ответа на наш вопрос, - 8+8=16, следовательно регистровая пара дает шестнадцатиразрядное число.
Теперь о том, почему Speccy не просто шестнадцатиразрядный, а "псевдо" такой.
Итак регистры нам нужны для того, чтобы выполнять над ними арифметические операции, а именно сложение и вычетание. К сожалению наш маленький Speccy не умеет делить и умножать, а уж ни о каких более "страшных" действиях и говорить не приходится.
Так вот, процесор может складывать/вычитать как регистры, так и регистровые пары, т.е. выполнять арифметические операции над восьми и шестнадцатиразрядными числами, а так как регистры имеют лишь восемь разрядов (это вам не IBM), то и процессор "псевдошестнадцатиразрядный".
Вот таблица всех регистров с их назначением:
- Код: Выделить всё
+-------+----------------------+
|Регистр|Основное назначение |
+-------+----------------------+
| A | 8-ый аккумулятор |
| F | Флаговый регистр |
| B | 8-ый счетчик |
| BC | 16-ый счетчик |
| DE | Регистр приемник |
| HL | 16-ый аккумулятор |
| IX | Индексный регистр |
| IY | Системный индекс |
| I | Вектор прерывания |
| R | Регистр регенерации |
| SP | Вершина стека |
| PC | Счетчик команд |
+-------+----------------------+
| Примечание: |
| 8-ый - восьмиразрядный |
+------------------------------+
2. ПРОСТЕЙШИЕ КОМАНДЫ
Теперь рассмотрим простейшие операции ассемблера, используя в качестве примера бейсик, с которым, я надеюсь, Вы знакомы.
Первое, что нужно сделать для изучения ассемблера - это выбрать себе "оболочку-ассемблер", в которой Вы будете работать.
Могу Вам посоветовать TASM128 если у Вас ZX128 или, для ZX48, ZEUS ASSEMBLER.
Итак приступим :
- Код: Выделить всё
BASIC ASSEMBLER
10 LET A=1 LD A,1
20 STOP RET
Команда "LET" эквивалентна команде "LD" , если Вы наберете и запустите ее, то в регистр "A" будет занесена единица. По команде "RET" произойдет выход в вызывающую программу (если программа была запущена из BASIC'а, то по команде "RET" она вернет управление BASIC'у).
Во все регистры, за исключением "F" и "PC", можно заносить числа, например:
- Код: Выделить всё
LD A,1 ; 1-->A
LD B,15 ; 15-->B
LD HL,16384 ; 16384-->HL
LD IX,50000 ; 50000-->IX
Но обычно программисту не хватает регистров, тогда приходится использовать память компьютера, например:
- Код: Выделить всё
BASIC ASSEMBLER
LET A=PEEK 23556 LD A,(23556)
После выполнения этой команды в "A" будет находиться число, записаное в указанной ячейке памяти, а учитывая то, что в этой ячейке BASIC хранит код последней нажатой клавиши, данная команда не лишена практического смысла.
Также используются и обратные команды :
- Код: Выделить всё
BASIC ASSEMBLER
POKE 23607,A LD (23607),A
Но часто приходиться записывать/считывать значение ячейки, адрес которой рассчитывается или берется из таблицы. В таком случае используются следующие команды:
- Код: Выделить всё
ЗАПИСЬ ЧТЕНИЕ
LD (BC),A LD A,(BC)
LD (DE),A LD A,(DE)
LD (HL),0 LD E,(HL)
Примечание: Хочу заметить, что не сушествует команды прямой записи числа в ячейку памяти, как и записи из ячейки в ячейку.
А теперь я хочу привести программу, которая печатает любое сообщение (она аналогична оператору "PRINT").
В программе используется команда "CALL", для вызова процедуры ПЗУ, эту команду можно сравнить с командой BASIC'а "GO SUB", например:
- Код: Выделить всё
BASIC ASSEMBLER
10 GO SUB 999 CALL M1
.... ....
999 LET A=1 M1 LD A,1
1000 RETURN RET
Здесь "М1"-это метка, вместо которой при ассемблировании программы будет поставлен соответствующий адрес.
ПРИМЕР ПРОГРАММЫ
После точки с запятой в ассемблере, также как после REM в бейсике пишутся комментарии.
- Код: Выделить всё
;Программа печати текстовых
;сообщений. -= PRINT TEXT =-
ORG 30000
ENT
START LD A,1 ;Открываем 1
CALL #1601;канал для
;печати.
LD DE,TEXT;Адрес текста
LD BC,LEN ;Длина текста
CALL #203C;Печать текста
RET
TEXT DEFB 22 ;Код "AT"
DEFB Y,X ;Позиция печати
DEFM "HELLO, EVEREBODY!"
LEN EQU 20 ;LEN=20
Y EQU 10 ;Y=10
X EQU 5 ;X=5
Эта программа аналогична строке BASIC'a:
- Код: Выделить всё
10 PRINT AT 10,5;"HELLO, EVEREBODY!"
ФЛАГИ
Регистр "F" процессора называется флаговым. Что это такое ?
Флаг - это переменная, которая может иметь два состояния, - установлено (равно единице) и сброшено (равно нулю). Поэтому регистр "F" можно рассматривать как набор восьми флаговых битов. Мы можем использовать только четыре из них: флаг нуля, флаг переноса, флаг знака и флаг четности-переполнения.
АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ
Арифметика - наука весьма и весьма полезная, мы постоянно что-нибудь считаем: складываем, отнимаем, делим, умножаем. О том, как сделать это на ассемблере, мы сейчас и поговорим.
Начнем мы с самого простого, прибавим единицу к чему-нибудь, например к регистру "А":
- Код: Выделить всё
LD A,NUBER
INC A
RET
Как видите, это очень просто, для этого существует команда "INC" - инкремент (увеличение на единицу), после нее идет операнд, т.е. какой-нибудь регистр или регистровая пара:
- Код: Выделить всё
INC A INC HL
INC H INC DE
INC E INC IY
INC E INC (HL)
INC (IX+N) INC (IY+N)
Если Вам нужно увеличить на единицу какую-либо ячейку памяти, то следует поступить так:
- Код: Выделить всё
LD HL,ADDRES LD IX,ADDRES
INC (HL) INC (IX+0)
RET RET
Первый вариант работает быстрее и удобен, если работа идет с одной ячейкой памяти, но если вы работаете в таблице, то это не экономично и некрасиво. Сравните: нам нужно увеличить на единицу первый, пятый и десятый байты в таблице:
- Код: Выделить всё
LD HL,TABL+1 LD IX,TABL
INC (HL) INC (IX+1)
LD HL,TABL+5 INC (IX+5)
INC (HL) INC (IX+10)
LD HL,TABL+10 RET
INC (HL)
RET
Все, что сказано выше о увеличении на единицу, справедливо и для декремента, т.е. для уменьшения на единицу:
- Код: Выделить всё
DEC A DEC HL
DEC L DEC IX
DEC H DEC DE
DEC E DEC BC
DEC D DEC IY
DEC C DEC IX
DEC B DEC (HL)
DEC (IX+N) DEC (IX+N)
А теперь предположим, что нам надо увеличить регистр "A" не на единицу, а скажем, на десять:
- Код: Выделить всё
LD A,NUMBER
ADD A,10
RET
Складывать можно регистр "A" с числом и другими регистрами и с ячейкой памяти, адресованной регистровыми парами "HL", "IX" и "IY". Так же можно складывать регистровые пары с "HL", "IX" и "IY".
- Код: Выделить всё
ADD A,N ADD A,(HL)
ADD A,A ADD A,(IX+N)
ADD A,B ADD A,(IY+N)
ADD A,C ADD HL,HL
ADD A,D ADD HL,BC
ADD A,E ADD HL,DE
ADD A,H ADD HL,SP
ADD IX,IX ADD IX,BC
ADD IX,DE ADD IX,SP
Как видите, набор команд достаточно велик. При выполнении этой команды может возникнуть ошибка:
- Код: Выделить всё
LD A,45
LD B,230
ADD A,B
RET
Сумма "А" и "В" превысило 255 и поэтому в "А" окажется не 275, а 20 (регистр "А" не резиновый); чтобы мы знали, что произошло переполнение, процессор устанавливает флаг переноса в единицу. Остается только проверить его.
Подобно тому, как у "INC" есть "DEC", у "АDD" тоже есть "парочка", это "SUB", и у нее имеются свои особенности. Команда "SUB" работает только с регистром "А", поэтому при написании мнемоники этой команды "А" опускается:
- Код: Выделить всё
SUB N SUB C
SUB A SUB H
SUB B SUB D
SUB E SUB (HL)
SUB (IX+N) SUB (IY+N)
Команда влияет на флаг переноса так же, как и "АDD".
Кроме пары команд "ADD" и "SUB" существуют еще одна пара. Команды "ADC" и "SBC" работают с учетом флага переноса, т.е. при сложении или вычитании к результату добавляется (отнимается) значение флага переноса. Для установки флага переноса есть две спецкоманды - это "SCF" и "CCF". "SCF" - установить флаг переноса в единицу. "CCF" - установить флаг переноса в ноль.
- Код: Выделить всё
ADC A,N SBC A,N
ADC A,A SBC A,A
ADC A,H SBC A,H
ADC A,L SBC A,L
ADC A,D SBC A,D
ADC A,E SBC A,E
ADC A,B SBC A,B
ADC A,C SBC A,C
ADC A,(HL) SBC A,(HL)
ADC A,(IX+N) SBC A,(IX+N)
ADC A,(IY+N) SBC A,)IY+N)
ADC HL,HL SBC HL,HL
ADC HL,BC SBC HL,BC
ADC HL,DE SBC HL,DE
ADC HL,SP SBC HL,SP
А теперь примеры работы команд "ADC" и "SBC" :
- Код: Выделить всё
LD A,10 LD A,10
LD B,5 LD B,5
CCF CCF
SBC A,B ADC A,B
RET RET
A=5 B=5 A=15 B=5
Вместо двух команд "CCF" и "SBC A,B" можно поставить просто "SUB B", результат будет тот же.
- Код: Выделить всё
LD A,10 LD A,10
LD B,5 LD B,5
SCF SCF
SBC A,B ADC A,B
RET RET
A=4 B=5 A=16 B=5
Как видно из результатов, флаг переноса значительно влияет на результат операции. При вычитании он отнимается от результата, а при сложении прибавляется к результату.
По операциям сложения и вычитания рассказано почти все, теперь мы поговорим о делении и умножении. К сожалению SPECCY не имеет команд деления и умножения, но эти команды можно составить из нескольких других. Например, нам нужно перемножить содержимое двух регистров - "А" и "C" :
- Код: Выделить всё
LD A,10
LD C,5
LD B,A
XOR A
LOOP ADD A,C
DJNZ LOOP
RET
В примере встречаются две новые команды - "XOR A" и "DJNZ LOOP". "XOR A" обнуляет регистр "А", а команда "DJNZ LOOP" повторяет все команды, с команды, помеченной меткой (например "LOOP"), до команды "DJNZ" (после нее должна стоять та же метка, что и в начале цикла); число повторений задается в регистре "В". Используя то, что умножение M на N - это сложение числа М само с собой N раз, Вы можете разобратся с примером, приведенным выше.
Можно использовать это свойство и для деления. Попробуйте сделать это сами.

