2

2. Команды вычислительных операций микрокомпьютера

Программа, вводимая в микрокомпьютер, обычно представляет собой детальный перечень выполняемых ею операций и состоит из комбинаций машинных слов. Последовательность таких машинных слов может формироваться и в компьютере на основе программы, написанной человеком на языке высокого уровня, но в случае микрокомпьютера (в частности, когда требуется понимание принципов работы микрокомпьютера, непосредственное управление его отдельными узлами или высокоскоростная обработка информации) программа должна быть написана непосредственно на машинном языке. При этом необходимо правильное понимание отдельных команд микрокомпьютера.

Из множества команд микрокомпьютера в первую очередь рассмотрим те, которые относятся к пересылке данных и к арифметическим операциям, в частности функции команд и их структура. К интересующим нас командам относятся команды обмена данными между регистрами и основной памятью; команды сложения, вычитания, умножения и деления целых чисел и других вычислительных операций над ними; логические команды и команды сдвига и др.

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

2.1 • Структура машинных команд

Для того чтобы микрокомпьютер выполнил определенные действия, необходимо задать программу, в которой подробно описан порядок этих действий, Часто такую программу пишут на искусственном языке, называемом языком высокого уровня. Эта программа окончательно реализуется после преобразования ее в последовательность машинных слов, или команд, иначе говоря, после трансляции так называемой программой-транслятором также представляющей собой последовательность машинных слов. Таким образом, можно сказать, что основной программой, непосредственно выполняемой на ЭВМ, будет последовательность машинных слов.

Рассмотрим структуру машинных .команд, являющихся основой программного обеспечения. Машинные команды могут быть различными в зависимости от типа компьютера. Однако, поскольку основные принципы проектирования всех типов компьютеров практически одинаковы, понимая машинные команды (машинный язык) компьютера одного типа легко можно перейти к машинным командам компьютера другого типа (если будет написана соответствующая программа). Рассмотрим в качестве типичной системы команд машинный язык процессора 8086.

В машинном языке процессора 8086, как показано на рис. 2.1, содержатся команды длиной от 1 до 6 байт. Эти команды кроме кода операции и поля данных или адресов содержат специальные разряды, такие как указатель w, показывающий, какова длина данных, над которыми производится операция (1 или 2 байт). Код операции сообщает, что именно должно быть сделано в соответствии с этой командой, и конкретно может быть представлен, например, следующими двоичными числами.

00000001 сложить (ADD) 00101001 вычесть (SUB) 11110011 умножить (MULtiply) 11110111 разделить (DIVide) 10001001 переслать (MOVe)

Программирование на машинном языке — весьма кропотливый и трудоемкий процесс. Есть другой путь — программирование на языке ассемблера, т .е. на языке машинных команд, но записанных не в кодах, а в мнемонических обозначениях и не с числовыми адресами, а с символическими. Обозначением кода операции в этом случае будет сокращение полного английского названия операций, например ADD — add (сложить), SUB — subtract (вычесть), MUL — multiply (умножить), DIV — divide (разделить), MOV — move (переслать). Специальная программа — ассемблер преобразует каждую команду символической программы внутри компьютера в двоичные числа типа 00000001, 00101001 и т. д. В этом случае нет особой нужды знать, каким именно двоичным числом будет представлен код каждой операции. Однако, когда эти числа все же необходимо знать, можно посмотреть таблицу, формируемую ассемблером и в которой исходной символической программе ставится в соответствие программа, в которой коды представлены двоичными числами (в тексте программы шестнадцатеричными). Для наглядности в табл. 2.1 приведены значения шестнадцатеричных чисел и соответствующие им значения двоичных и десятичных чисел.

Таблица 2.1. Запись шестнадцатеричных, двоичных и десятичных чисел

Шестнадцатеричные числа | Двоичные числа | Десятичные числа
0 | 0000 | 0
1 | 0001 | 1
2 | 0010 | 2
3 | 0011 | 3
4 | 0100 | 4
5 | 0101 | 5
6 | 0110 | 6
7 | 0111 | 7
8 | 1000 | 8
9 | 1001 | 9
A | 1010 | 10
B | 1011 | 11
C | 1100 | 12
D | 1101 | 13
E | 1110 | 14
F | 1111 | 15

Шестнадцатеричные числа используются для компактного .представления двоичных чисел. С этой целью двоичное число разбивается на группы по 4 бит, каждая из которых заменяется соответствующей ей шестнадцатеричной цифрой

Из рис. 2.1 видно, что существуют различные типы команд: команды, содержащие данные длиной 8 бит (1 байт), имеют длину 2 или 3 байт; команды, содержащие данные или адрес (его смещаемую часть) длиной 16 бит (2 байт), имеют длину 3, 4, 5, 6 байт; команды, содержащие адрес длиной 4 байт (сегмент + смещение), имеют длину 5 байт. При использовании такой системы команд особое внимание необходимо уделять следующим моментам:

1) Длина команды точно не устанавливается и составляет от 1 до 6 байт. Это сделано затем, чтобы можно было коротко представлять простые команды.

2) Данные, над которыми производят операции с помощью команд, имеют длину 1 или 2 байт. Если эти данные представляют собой номер регистра или адрес, то при w = 0 обрабатываются 1-байтные данные, а при w = 1 — 2-байтные данные.

3) Адрес представляется номером сегмента н смешением, однако такое представление адреса полной длиной 4 байт осуществляется только в некоторых командах CALL/JMP (команда перехода), обычно же используется такая запись адреса, когда в самом начале программы указывается номер сегмента, а затем в командах указываются только смещения длиной 2 байт. Поэтому в большинстве команд процессора 8086 присутствуют короткие адреса — всего лишь 2 байт. Однако это неудобно в тех случаях, когда необходимо переключение сегментов.

Рис. 2.1. Форматы различных типов машинных команд (процессор 8086. reg означает номер регистра, seg — номер сегмента, значения х и w равны 0 или 1, Значения а и r/m см. в табл. 2.2; ххх — то же, что и reg в команде mov. а — команды длиной 1 байт (при w = I операции производятся над данными длиной 2 байт); б — команды длиной 2 байт (при w= 1 операции производятся над данными длиной 2 байт); в—• команды длиной 3 байт (при w= 1 операции производятся над данными длиной 2 байт); г — команды длиной 4, 5, 6 байт.

2.2. Пересыпка данных

К наиболее простым командам процессора 8086 относится команда MOV (переслать). Она используется для того, чтобы

1) данные длиной 1 байт или 2 байт переслать:

а) из регистра в регистр;

б) из регистра в память;

в) из памяти в регистр;

2) данные, непосредственно записанные в команде (их называют непосредственно заданными операндами, литералами), переслать в регистр или по заданному адресу в память.

Ниже для простоты при записи команд в программе используют строчные буквы латинского алфавита, и запись производят в соответствии с формой, принятой в языке ассемблера.

Комментарий помещают после точки с запятой.

Можно привести следующие примеры команды MOV:

mov al,bl ;переслать содержимое регистра BL

;в регистр AL

mov ds,ax ;переслать содержимое регистра АХ

;в регистр DS

mov ah, 3 ;переслать число 3 в регистр АН

mov cx, 256 ;переслать число 256 в регистр СХ

Рис. 2.2. Пересылка данных между регистрами и памятью.

Как ясно из этих примеров, при пересылке данных источником данных является второй операнд команды mov, а приемником данных — первый операнд, т. е. пересылка производится справа налево. В двух последних примерах числа 3 и 256 представляют собой непосредственно заданные значения oneрандов (литералы). Такой способ задания операндов называется непосредственной адресацией.

На рис. 2.2 показано, каким образом можно пересылать данные в соответствии с этими командами mov. «Прямой адрес» на рисунке является переменной, представляющей собой адрес в основной памяти, обозначаемый в командах как х или у.

mov bh,x ;переслать содержимое адреса х в регистр ;ВН

mov у,es ;переслать содержимое регистра ES по адресу у

Переменные х и у называют также прямыми адресами. Соответственно, такой способ обращения к памяти называется прямой адресацией. Во избежание путаницы с непосредственной адресацией, указание прямого адреса в виде числа не допускается.

«Косвенный адрес» на рисунке обозначается как [bх], способ его употребления ясен из следующих примеров команд:

mov [si], 256 ;переслать 256 в [SI]

mov al, [bx + si] ;переслать [BX + SI] в регистр AL

mov ex, [bx + si + 21] ;переслать [ВХ + SI + 21] в; регистр СХ

mov [bp+si+1026],dx ;переслать содержимое регистра DX в [BP + SI + 1026]

В то время как в этом примере команды (программы) al и сх обозначают содержимое регистров AL и СХ соответственно, обозначение, заключенное в квадратные скобки, такое как [Ьх], показывает, что данные расположены по адресу, указанному содержимым регистра ВХ.

Пусть, например, в регистр SI помещено число 1234, тогда si выражает число 1234 в вышеуказанном примере, в противоположность этому [si] выражает содержимое ячейки с адресом 1234. Таким образом, если бы перед вышеприведенной программой была записана последовательность команд

mov si, 1234

mov bx, 2345

mov bp, 3456

то смысл четырех команд предыдущего примера оказался бы следующим:

поместить число 256 в ячейку с адресом 1234;

переслать содержимое ячейки с адресом 3579 в регистр AL;

переслать 2 последовательных байта, начиная с адреса 3600, в регистр СХ;

переслать содержимое регистра DX в первые 2 байт памяти, начиная с адреса 3456+1234+1026=5716.

Таким образом, содержимое регистров с АХ по DI непосредственно не используется, а рассматривается в качестве адресов; используются же данные в ячейках с указанными адресами. Такой способ использования данных называется косвенной адресацией.

Данные, определяемые с помощью косвенной адресации, можно рассматривать в следующем представлении:

Здесь v — косвенный адрес; в предыдущем примере v был равен 1234. Таким образом, если используется регистр SI, то, изменяя его содержимое, можно задавать различные адреса памяти.

С другой стороны, поскольку, как уже было сказано, команда, подобная

mov al, x

имеет смысл «переслать содержимое адреса х в регистр AL», х представляет собой прямой адрес, т.е. адрес ячейки памяти, содержащей значение операнда.

Итак, выше были приведены примеры различных модификаций команды MOV. Посмотрим еще раз на рис. 2.2 и попробуем более четко сформулировать правила формирования команд MOV.

1. Длина данных, которые могут пересылаться, составляет либо 1 байт, либо 2 байт. Если регистры разделены напары АН, AL; BH, BL; CH, CL; DH, DL, то длина данных составляет 1 байт.

2. Пересылка данных возможна между любыми регистрами.

3. Непосредственные данные можно формировать только в регистрах АХ, СХ, DX, регистрах базы (ВХ, ВР), индексных регистрах (SI, DI). Формирование литералов в регистрах CS, DS, ES, SS невозможно.

4. Пересылка данных между ячейками памяти возможна путем назначения адресов в регистрах АХ, СХ, DX, регистре базы, индексных регистрах. Регистр SP (указатель стека) для этой цели использовать нельзя.

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

6. Пересылка данных из сегментных регистров возможна в одном направлении (из них). Наличие такого ограничения связано с тем, что изменение содержимого регистров сегментов производится довольно редко.

С учетом вышеприведенных правил команды пересылки можно записывать следующим образом:

mov bl,5 ;переслать число 5 в регистр BL

mov [bp+di+6],dl ;переслать содержимое DL
;по ;адресу, сформированному как ВР+DI+6

Не обязательно знать, как эти команды представляются внутри ЭВМ. Однако необходимо знать, в каком виде — в виде шестнадцатеричного или двоичного кода — представляются эти команды при записи их для отладки программы или при внесении в оперативную память. Команды MOV, приведенные на рис. 2.1, можно классифицировать следующим образом, исходя из их длины и длины данных, над которыми производятся операции (символ г/m означает регистр или память).

Пересылка 1-байтных данных: | Длина команды
Между г/гm и регистрами | Длина 2 байт (например, б4, г2)
Непосредственные данные в r/m | Длина от 3 до 5 байт (например, в5, гЗ, г6)
Непосредственные данные в регистры | Длина 2 байт (например, б2)
Из прямоадресуемой памяти в акумулятор | Длина 3 байт (например, вЗ)
Из аккумулятора в прямоадресуемую память | Длина 3 байт (например, вЗ)
Пересылка 2-байтных данных (w == 1) |
Между г/m и регистрами | Длина 2 байт (например, б4, г2)
Непосредственные данные в г/m | Длина от 4 до 6 байт (например, г1, г5, г7)
Непосредственные данные в регистры | Длина 3 байта (например, в2)
Из прямоадресуемой памяти в аккумулятор | Длина 3 байта (например, вЗ)
Из аккумулятора в прямоадресуемую память | Длина 3 байта (например, вЗ)
Из r/m в регистры сегментов | Длина 2 байт (например, б5)
Из регистров сегментов в r/m | Длина 2 байт (например, б5)

Примеры даны в соответствии с рисунком 2.1, а, б, в, г.

Естественно, что в командах, в которых указан прямой адрес памяти (основной памяти), и в командах, которые содержат 2-байтные данные (непосредственные данные длиной 2 байт), длина адреса и данных составляет 2 байт, и поэтому вся команда имеет именно указанную выше длину. Однако в командах, в которых данные представлены через посредство поля г/m, смысл адресной части команды определяется с учетом значения а, как показано в табл. 2.2.

Таблица 2.2. Сочетания г/m и а

\ а 11
\ 00 01 10 --------
г/m \ w=I w=0

-------------------------------------------------------------------------------------------------------------------------------------

000 [BX + SI] [BX + SI + a'] [BX + SI + a] AX AL

001 [BX + DI] [BX + DI + a'] [BX + DI + a] CX CL

010 [BP + SI] [BP + SI + a'] [BP + SI + a] DX DL

001 [BP + DI] [BP + DI + a'] [BP + DI + a] BX BL

100 [SI] [SI + a'] [SI+a] SP AH

101 [DI] [DI + a'] [Dl + a] BP CH

110 a (как исключение) [BP + a'] [BP + a] SI DH

111 [BX] [BX + a'] [BX + a] DI BH

1) а' представляет собой адрес длиной 16 бит, полученный за счет удаленна старшего байта из адреса а и добавления 8 бит нулей слева младшему байту.

2) Команда (2) а xxx r/m имеет длину 1 байт. Часть ххх представляет собой двоичное число длиной 3 бит; она может также выражать номер регистра.

3) Запись (3) 00 ххх 110 представляет собой команду прямой адресации.

4) Косвенная адресация в виде [АХ], [CX], [DX], [SP] не допускается.

Сопоставим сказанное с рис. 2.1. Случай, когда а в табл. 2.2 составляет 11 (в двоичном исчислении), прост; так при r/m=011 команды будут иметь вид

mov al, bl ;переслать содержимое BL в

; AL

mov bx, ax ;переслать содержимое АХ в

;ВХ

Но при а = 00 или а = 01 или а =10 осуществляется уже косвенная адресация.

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

системной программой, называемой ассемблером, в двоичные (шестнадцатеричные) числа. Поэтому программа, записанная в виде последовательности командных слов, имеет смысл программы, предназначенной для ассемблера, и называется также программой на языке ассемблера. Название «язык ассемблера» указывает на запись команд (т. е. командных слов) в мнемонических обозначениях, например mov.

Функцией ассемблера является трансляция командных слов, записанных так, как показано в правой части рис. 2.3, в шестнадцатеричные числа, показанные в левой части рисунка. Содержимое этого рисунка получено при введении в ЭВМ исходной программы (правая часть, рис. 2.3) и называется распечаткой программы на входном и выходном языках ассемблера. Для того чтобы понять, насколько сложно преобразование командных слов в шестнадцатеричный код, осуществляемое ассемблером, выберем несколько примеров преобразования из программы, представленной на рис. 2.4.

Рис 2 3. Распечатка программы на входном и выходном языках ассемблера (числа в левой части представляют собой адрес, выраженный в байтах).

Рис. 2 4 Различные модификации команды mov (R в левой части рисунка представляет собой относительный адрес)

Рассмотрим вначале преобразование команд

mov ah, bh -> 8A E7

mov ah, x -> 8A 26xxxx

На вид они похожи, однако первая команда имеет длину 2 байт, а вторая — длину 4 байт. Из следующих двух команд:

mov ch, 3 -> B5 03

mov ax, 3 -> B8 0003

первая имеет длину 2 байт, а вторая — длину 3 байт.

Далее, команды

mov ax, у -> А1 хххх

mov bx, y -> 8B 1E xxxx

также очень похожи, но, благодаря тому что в процессоре 8086 аккумулятор (регистр АХ) располагает специальными возможностями, длина первой из этих команд оказывается равной 3 байт, а второй — 4 байт. (Здесь хххх — относительный адрес, о чем свидетельствует буква R справа. Он определяется после распределения памяти системной программой, называемой редактором связей.) Заметим, что команда mov ах, у после преобразования могла бы превратиться в шест-надцатеричное число 8В 06 хххх, однако ассемблер представляет команду mov ax,у не в таком виде, а в виде A1 xxxx. Это связано с особенностями функций регистра ах. В приведенных примерах из-за этих особенностей команды удлиняются. Можно привести и обратные примеры, т. е. когда из-за наличия особенностей команда укорачивается.

Например,

mov [si],cx -> 89 ОС

mov [bp],cx -> 89 4E 00

Вторая команда по существу должна принять вид 890Е, но, из-за того что второй байт в двоичной системе счисления записывается как 00001110, это соответствует исключению в табл. 2.2, и составляющая а' в [ВР+а'] при этом равна нулю, поэтому длина команды равна 1 байту. При этом осуществляется прямая адресация по адресу 4Е00.

Из примеров, следующих ниже, видно, что команды MOV могут иметь длину от 2 до 6 байт,

mov ds, ax -> 8E D8

mov ax, 3 -> В8 0003

mov [si], 258 -> С7 04 0102

mov [bp + 516], 1027 -> С7 86 0204 0403

Здесь запись в последней команде [bp+516] обозначает адрес, в котором к адресу, сформированному в ВР, прибавляется число 516. Приведенная на рис. 2.4 команда

mov ax, [si + bx + 258]

имеет следующий смысл: «переслать 2-байтные данные, находящиеся по адресу, представляющему собой сумму адреса, сформированного в SI, адреса, сформированного в ВХ, и 258, в регистр АХ». Заслуживают внимания также следующие команды:

mov byte ptr x, 255

mov word ptr [di + 515], 4

Эти команды по существу должны иметь вид

mov х, 255

mov [di + 515], 4

Однако при таком представлении неясно, можно ли помещать по адресам х и [di+515] данные длиной 1 байт, или обязательно, чтобы данные имели длину 2 байт. Для случая, когда длина данных составляет 1 байт, мы записываем команду с указанием byte ptr (ptr имеет смысл pointed by — указываемый (адресом)...), а для случая, когда длина данных составляет 2 байт (т. е. слово), — с указанием word ptr.

Команды, требующие такого дополнительного указания, можно найти в нижней части программы на рис. 2.4. Значение х помещается в область памяти, размер которой в соответствии с указанием db (define byte — определитель байтов) Составляет 1 байт, а значение у — в область памяти, размер которой в соответствии с указанием dw (define word — определитель слов) составляет 2 байт. Но в качестве значения у сюда могут быть помещены и данные длиной 1 байт.

Рис. 2.5. Программа для определения размера данных.

Приведенная на рис. 2.5 программа иллюстрирует, как шестнадцатеричное представление чисел в регистре АХ (АН) зависит от их величины и знака. В языке ассемблера используются следующие диапазоны значений данных:

| Операции над положительными целыми числами | Операции над положительными и отрицательными целыми числами
1 байт | От 0 до 255 | -128 до 127
2 байта | от 0 до 65 535 | от -32 768 до 32 767

Рис 2 6. Команды обмена между регистрами.

На рис. 2.6 приведены команды xchg (exchange — обмен), очень похожие на команды mov. Это команды, осуществляющие обмен содержимым регистров. Например, команда

xchg ax, bx

представляет собой команду, в соответствии с которой осуществляется обмен содержимым между регистрами АХ и ВХ; она используется для выполнения тех же действий, которые выполняются последовательностью команд

mov ex, ах

mov ax, bx

mov bx, ex

Только при этом теряется содержимое регистра СХ, используемого в качестве промежуточного, в то время как в случае применения команды xchg этого не происходит.

Если посмотреть на распечатку программы на входном и выходном языках ассемблера, то легко видеть, что и команды xchg могут быть представлены различными модификациями. В соответствии с указателем dw, используемым для определения размера необходимой области памяти, для данных отводится область длиной 2 байт, но из этой области длиной 2 байт допускается также извлечение данных длиной только 1 байт (в соответствии с byte ptr x). В этом примере в соответствии с указателем dw имеются две области длиной по 2 байта, и в качестве начальных значений туда помещаются шестнадцатеричные числа 1234h и 5678h (h имеет смысл hex — шестнадцатеричный) соответственно. Адресами, указывающими эти участки (называемыми также указателями),
являются х и х+2.

2.3. Правила записи команд ассемблера

В предыдущем разделе на рис. 2 3—2.6 приведены программы, записанные на языке ассемблера. В данном разделе рассмотрим минимум синтаксических правил языка ассемблера, необходимых для написания такой простой программы.

1. Первой строкой программы на языке ассемблера является предложение, состоящее из слова title (заголовок) и собственно имени программы. Сама программа может быть написана как заглавными, так и строчными буквами.

2. Программа пишется в виде одного сегмента кодов. Чтобы отразить этот факт, второй строкой программы должно быть предложение, состоящее из имени программы (move) и слова segment (сегмент). Число пробелов между словами может быть любым. После слова segment должно быть записано слово assume, которое показывает, что названием CS (сегмента кодов), т.е. сегмента самой программы, является (в данном случае) move. При использовании данных, обозначаемых обычно латинскими буквами, например х, у, может быть точно так же указан и DS (сегмент данных), о чем будет сказано позднее. Заканчивается сегмент словом ends

move segment

assume cs: move

move ends

3. Сегмент кодов обычно содержит основную программу и несколько подпрограмм, и для каждой из них записывается название (например, movl); после названия в начале подпрограммы пишется слово ргос, а в конце — endp.

mov1 proc

mov1 endp

4) Квадратные скобки, свидетельствующие о косвенной адресации, могут использоваться по-разному.

mov ax, [bp + di + 258] ;адрес (смещение)

mov ax, [bp] + [di] + 258 ;258 не относится к данным

mov ax, [bp + di] + 258

Далее принята форма записи, использованная в первом примере,

5) Для данных используются имена (имена переменных}, обозначаемые латинскими буквами. Отведение памяти для данных различной длины обеспечивается с помощью зарезервированных слов:
db (define byte)—для 1-байтных данных,
dw (define word) — для 2-байтных данных и
dd (define double word — определитель двойного слова) — для 4-байтных данных.

Ниже показано, как осуществляется описание данных:

х db

у dw

6) Поскольку ассемблер непосредственно не указывает адресов, по которым размещаются данные, адреса вышеуказанных переменных х и у обозначены, например, через 0000R и 0001R, как показано на распечатке, изображенной на рис. 2.4. R показывает, что адреса являются перемещаемыми или относительными.

7) При размещении данных в регистры длиной 1 байт помещаются 1-байтные данные, а в регистры длиной 2 байт помещаются 2-байтные данные. Например, в случае команды

mov ax, 3

число 3 представляет собой 2-байтное данное, в шестнадцатеричном счислении имеющее вид 0003.

Однако необходимо обратить внимание на то, что в случае, когда производится обмен данными с ячейками памяти через базовый или индексные регистры, размер области памяти, в которую помещаются данные, неизвестен. В этом случае ясно, что, если значение числа (в качестве данных используются только целые числа) больше 255, для размещения в памяти потребуется по крайней мере 2 байт, но если значение данных находится в диапазоне 0 — 255, то нельзя точно сказать, какой объем памяти нужен — 1 байт или 2 байт. Если данные занимают 1 байт, ставят указатель byte ptr, если 2 байт — указатель word ptr. Например,

mov byte ptr x, 4

mov word ptr y, 4

8) Данные, записанные числовым выражением, например 255, интерпретируются как десятичные числа. При желании оперировать шестнадцатеричными числами записывают число в шестнадцатеричной системе счисления и в конце ставят h: 1234h, 0abch. При этом если шестнадцатеричное число начинается с какой-либо буквы латинского алфавита от а до f, то перед ней необходимо ставить 0. Делается это для того, чтобы можно было отличать это число от имени переменной abch. Допускается также использование двоичных чисел типа 000011l1b (Последний символ b указывает на то, что данное число является двоичным)

9) Запись bх означает, что данные размещены в регистре ВХ; запись [bх] означает, что данные расположены по адресу, находящемуся в регистре ВХ. Косвенная адресация реализуется только с помощью регистров ВХ, ВР, SI, DI. Другие регистры для этой цели использовать нельзя.

10) Начало комментария к программе отмечается точкой с запятой. Действие комментария прекращается в конце строки. Поскольку комментарии в ассемблере в процессе трансляции игнорируются, после точки с запятой можно записывать любые замечания.

11) В отличие от программы, представленной на рис. 2.4, данные х и у обычно записываются не в сегменте программы, а в специальном сегменте данных, пример которого дан ниже.

Mydata segment

х db

у dw

mydata ends

Подробнее этот вопрос рассмотрим позднее.

Правила, приведенные в пп. 1 — 11, — это только часть синтаксиса ассемблера, поэтому написать реальную программу с их помощью нельзя. Однако составить распечатку программы на входном и выходном языках ассемблера, подобную показанной на рис. 2.3—2.6, вполне возможно. Все действия над программой, начиная с ее ввода и кончая выполнением, описаны ниже и осуществляются с помощью операционной системы типа MS-DOS (рис. 2.7).

1) Вводят с клавиатуры программу пользователя, используя программу, называемую редактором текста (например EDIT имя_файла.ASM). После необходимой корректировки программу размещают на магнитном диске. Полученный файл называют файлом исходной программы (SOURCE файл).

2) Транслируют исходную программу (файл), используя программу-ассемблер, и одновременно с получением распечатки программы на языке ассемблера (файл «листинг» - LST файл) помещают в файл полученную программу на машинном языке. Полученный таким образом файл представляет собой файл объектной программы (OBJ файл). Например можно использовать следующую командную процедуру
c:\tasm\bin\tasm.exe /c /r /la /ml /os /w2 /x /z /zi %1.asm,%1.obj,%1.lst

3) Связывают (или преобразовывают внутренний формат объектного файла) файл объектной программы с библиотечными программами и другими программами с помощью редактора связей, и таким образом формируют файл загрузочного модуля ( EXE файл) в такой форме, прн которой сразу возможна его загрузка в оперативную память и выполнение. Например можно использовать следующую командную процедуру
C:\TASM\BIN\TLINK /m /M /s /l /i /Tde /v %1.obj,%1.exe,%1.map;

4) Выполняют загрузочный модуль. Вызывают системный загрузчик, который копирует содержимое исполняемого файла в оперативную память ЭВМ и настраивает центральный процессор на выполнение машинных команд, содержащихся в загрузочном модуле. (см. рис. 2.7)

Рис 2 7 Схема работы редактора текста, ассемблера, редактора связей.

2.4. Арифметические команды

В число команд, используемых для реализации арифметических операций процессора модели 8086, входят команды сложения, вычитания, умножения, деления и сравнения, представленные в табл. 2.3, и некоторые другие. Арифметические операции с помощью таких команд осуществляются аналогично операциям, выполняемым с помощью команды mov, между регистрами, содержимым памяти, непосредственными данными.

Подавляющее большинство команд относится к типу

X + Y -> X

т. е. выполняется операция заполнения области памяти, занимаемой исходными данными X, другими данными X+Y, т. е. полученный результат заменяет начальные данные. Таким образом, необходимо помнить, что исходное значение X теряется.

Как видно из табл. 2.3, адреса расположения данных, над которыми производятся арифметические операции, и длина данных зависят от команды, и нельзя сказать, чтобы все команды были симметричны.

Таблица 2.3. Команды арифметических операций процессора модели 8086

Команда | Смысл на англ. языке | Функции | r | d | m | w
add | add | Сложение | o | o | o | o
adc | add with carry | Сложение с переносом | о | о | о | о
sub | subtract | Вычитание | о | о | о | о
sbb | subtract with borrow | Вычитание с заемом | о | о | о | о
cmp | compare | Сравнение | о | о | о | о
mul | multiply (unsigned) | Умножение целых чисел (без знака) | о | х | о | о
imul | integer multiply (signed) | Умножение целых чисел (со знаком) | о | х | о | о
div | divide (unsigned) | Деление целых чисел (без знака) | о | х | о | о
idiv | integer divide (signed) | Деление целых чисел (со знаком) | о | х | о | о
cbw | convert byte to word | Преобразование байта в слово | х | х | х | х
cwd | convert word to double word | Преобразование слова двойное слово | х | х | х | х
inc | increment | Увеличение на 1 | о | х | о | о
dec | decrement | Уменьшение на 1 | о | х | о | о
neg | change sign | Изменение знака | о | х | о | о
ааа | ASCII adjust for add | Коррекция сложения | х | х | х | х
aas | ASCII adjust for subtract | Коррекция вычитания | х | х | х | х
aam | ASCII adjust for multiply | Коррекция умножения | x | x | x | x
aad | ASCII adjust for divide | Коррекция деления | x | x | x | x
daa | decimal adjust for add | Коррекция сложения в десятичной системе счисления | x | x | x | x
das | decimal adjust for sub tract | Коррекция вычитания в десятичной системе счисления | х | х | х | х

Примечание: г — регистры; d — непосредственные данные; m — адрес прямоадресуемой памяти, w — возможность оперирования 2-байтными данными – словами.

Знак о — означает, что использование данного типа операндов возможно, знак х — не разрешено.

Рис. 2.9. Различные модификации команды add.

На рис. 2.9 представлена программа, в которой показаны различные модификации команды add, являющейся типичной арифметической операцией. Как видно из шестнадцатеричного кода, записанного в левой части каждой из команд, длина команды add составляет от 2 до 6 байт. Вторая половина каждой команды (после второго байта) аналогична команде mov. Команда

add ah, 3

например, означает, что к содержимому регистра АН длиной 1 байт прибавляют число 3, а команда

add ax, 258

— что к содержимому регистра АХ длиной 2 байт прибавляют число 258 (2-байтное данное).

Команда

add bx, 258

также означает, что к содержимому регистра ВХ прибавляют 258. Эта команда имеет смысл, аналогичный смыслу предыдущей команды, только предыдущая команда, операндом которой было содержимое регистра АХ, имела длину 3 байт (6 шестнадцатеричных разрядов), а команда, операндом которой является содержимое регистра ВХ, имеет длину 4 байт (8 шестнадцатеричных разрядов). Это означает, что при проектировании процессора 8086 было предусмотрено использование регистра АХ в качестве аккумулятора. При операциях сложения использование регистра АХ (или одного из регистров АН или AL) позволяет сократить длину команды, а значит, и уменьшить время ее считывания из памяти, что в конечном счете уменьшает время выполнения команды. Таким образом, при написании реальной программы аккумулятор (регистры АХ, АН, AL) должен быть максимально задействован.

Далее, команда

add ah, bh

представляет собой команду, в соответствии с которой к содержимому регистра АН прибавляется содержимое регистра ВН; операндами являются 8-битовые данные. В то же время команда

add ax, bx

означает арифметическую операцию, операндами которой являются 16-битовые данные. Таким образом, в зависимости от регистра, использованного в команде, ассемблер автоматически делает заключение о длине данных (8 бит или 16 бит). В соответствии с этим заключением ассемблер осуществляет преобразование в команду, записанную шестнадцатеричным кодом.

Команда

add byte ptr [di + 4], 3

представляет собой команду, в соответствии с которой 1-байтное данное 3 прибавляют к данным, расположенным в памяти по адресу [di + 4], а

add [di + bx + 260], 258

— команду, в соответствии с которой 2-байтное данное 258 прибавляют к данным, расположенным по адресу [di+bx + 260]. В первом случае используется дополнительный указатель длины данных (byte ptr), без которого не ясно, чему равна длина данных — 1 байт или 2 байт.

Указатели длины данных бывают двух видов: если длина данных равна 1 байт, в команде ставится указатель byte ptr, а если 2 байт — word Ptr.

Команда sub в табл. 2.3 является простой командой вычитания, ее применение аналогично применению команды add, поэтому не требует особых пояснений. Однако необходимо внимательно отнестись к командам сложения и вычитания с использованием переноса (carry) и заема (borrow). Указанные команды имеют код adc и sbb соответственно. Рассмотрим в связи с этим рис. 2.10.

Рис. 2.10. Различие между командами add и adc: a — перенос в соответствии с командой add; б — сложение с использованием переноса в соответствии с командой adc.

На рисунке показаны примеры способов нахождения суммы

add al, bl add ax, bx

adc ah, bh adc ax, bx

Если при выполнении этих команд возникает перенос, то для флажка состояния С, представляющего собой один бит в регистре флажков (регистре состояния), показанном на рис. 1.2, формируется значение «1».

Разница между этими двумя командами заключается в том, что в случае команды add значение флажка С (признака переноса) при сложении не учитывается (прибавляется).

Как видно из рисунка, последовательное действие двух команд

add al, bl ;сложить младшие байты

adc ah, bh ;сложить старшие байты с переносом

аналогично действию одной команды add ax, bx. В первом случае дважды производится сложение данных длиной по 8 бит (т. е. сложение данных длиной по 16 бит), а во втором случае — один раз сложение данных длиной по 16 бит. Если аналогично рассматривать две команды

add bx, dx ;сложить 2 младших байта

adc ax, сх ;сложить старшие байты с переносом

то их можно было бы заменить одной командой, если считать, что в регистры СХ и DX помещаются данные общей длиной 32 бит (4 байт), которые могут быть сложены с общим содержимым регистров АХ и ВХ.

Рис. 2.11. Команда sbb, в соответствии с которой производится вычитание с займом.

В отличие от рассмотренных команд sbb представляет собой команду вычитания с заемом, как показано на рис. 2.11. В этом случае результат, оставшийся в регистре AL, составляет 0 - 1 = -1, и поскольку при прибавлении к -1 числа, равного 1, получается 0, то в двоичной системе счисления (в представлении с дополнением до 2) получится число 11111111. При этом заем, который приходится делать, формируется как значение флажка С (аналогично случаю команды add).

Действие двух последовательных команд

sub ai, bl ;вычесть 1 младший байт

sbb ah, bh ;вычесть старший байт с заемом

аналогично действию одной команды sub ax, bx.

В наборе команд ассемблера микропроцессора 8086 имеется команда cmp (сравнения). Она записывается как

cmp ax, bx

и в результате ее выполнения сравнивается содержимое, например, регистра АХ с содержимым регистра ВХ. При этом в результате операции сравнения формируется значение соответствующих флажков регистра флажков, результат операции нигде не фиксируется (рис. 2.12).

В процессоре 8086, как можно видеть на рисунке, регистр флажков содержит 9 одно битовых признаков, пять из которых:

О (признак переполнения);

S (признак знака плюс или минус);

Z (знак нуля);

С, А (признаки переноса).

P (значение бита четности)

— формируются при выполнении команды сmр.

Если при выполнении команды

сmр а, р

оказывается, что а = р, признак Z становится равным 1.

При выполнении команды сmр в действительности осуществляется арифметическое действие (а — p), но при этом достаточно, если в результате всего лишь будет сформирован признак. Эта команда в комбинации с командой перехода, о которой будет сказано ниже, используется для решения вопроса: «а больше, чем р?».

Рис 2 12. Регистр флажков состояния в процессоре 8086.

В отличие от рассмотренных команд сложения, вычитания и сравнения команды умножения и деления (рис. 2.13) составляются таким образом, чтобы исходные данные обязательно находились в аккумуляторе (регистры АХ, АН, AL).

Эти команды имеют простой вид (рис. 2.14) и называются одноадресными командами. Непосредственные данные в этих командах не допускаются. В случае, когда в команде умножения оперируют данными длиной 8 бит, результат имеет длину 16 бит и помещается в регистр АХ, а в случае, когда оперируют данными длиной 16 бит, результат имеет длину 32 бит и помещается в регистры DX и АХ.

При делении, наоборот, делимое представляет собой 16-битовое или 32-битовое число (рис. 2.13,6), а остаток помещается в регистр АН или регистр DX. Поскольку при делении 32-битовых данных нет возможности заранее поместить верхнюю половину кода данных в регистр DX, программа будет иметь, например, следующий вид:

mov dx, [si]

div у

Рис 2 13 Размещение данных в командах умножения и деления:

а - команда умножения; б — команда деления.

Рис. 2 14. Различные модификации команды mul (умножения), здесь данные помещаются в специальный сегмент.

Команды умножения и деления в зависимости от того, используется ли старший (левый) бит данных в качестве признака знака (0 в случае, когда знак «+» и 1 в случае, когда знак «—»), делятся на команды imul, idiv и mul, div.

При умножении и делении следует обратить особое внимание на то, что в командах типа mul [di] точно неизвестно, 1-байтную или 2-байтную длину имеют данные, адрес которых указан в регистре DI. Поэтому в таких случаях используют указатели:

при 1-байтной длине данных - byte ptr [di];

при 2-байтной длине данных - word ptr [di].

Здесь ptr, как уже говорилось, означает pointed by, т. е. имеет смысл указателя, относящегося к [di].

Если говорить о длине данных, то в командах деления возможны не только варианты, показанные на рис. 2.13:

(16 бит)/(8 бит),

(32 бит)/(16 бит),

но и такие варианты, как

(8 бит)/(8 бит),

(16 бит)/(16 бит).

При этом необходимо увеличить длину делимого от 8 бит (регистр AL) до 16 бит (регистр АХ), или длину делимого 16 бит (регистр АХ) до длины 32 бит, присоединяя регистр DX. В этом случае, если исходное число в регистрах AL или АХ положительно (признак знака равен нулю), содержимое регистров АН или DX можно просто установить в нуль, а если это отрицательное число, то признак знака исходного числа должен быть распространен на регистр АН или регистр DX:

10110011 -> 1111111110110011

Для этого служат следующие две команды из табл. 2.3:

cbw (convert byte to word) .. преобразовать байт в слово

cwd (convert word to double . преобразовать слово в двойное слово word)

Здесь «слово» означает содержимое регистра АХ размером 2 байт, а «двойное слово» — содержимое объединенных регистров DX и АХ размером 4 байта. Команды cbw и cwd, представленные шестнадцатеричным кодом, показаны на рис. 2.15.

Поскольку выше было упомянуто о знаке данных, следует сказать о том, что существует команда neg (negate — инвертировать), которая служит для изменения знака данных (т.е. формирование дополнения до 2). В этой команде, аналогично рассмотренной выше команде mul (умножения), в случае, когда операндом является содержимое ячеек памяти, при 1-байтной длине данных также ставится указатель byte ptr, а при 2-байтной длине данных — указатель word ptr.

В примерах, приведенных на рис. 2.15, данные х определяются как 1-байтное, а у— как 2-байтное слово, однако при необходимости можно также использовать указатели byte ptr у (указывает, что длина у равна одному байту) и word ptr х (указывает, что длина х равна слову).

Рис 2 15. Команды inc и dec.

В табл. 2.3 и на рис. 2.15 показаны команды inc и dec, которые соответственно служат для увеличения значения данных на 1, и для уменьшения значения данных на 1. Эти команды, как видно из рис. 2.15, оперируют с содержимым 16-битовых регистров, в результате чего команда имеет длину всего в 1 байт.

Среди арифметических команд кроме рассмотренных выше существуют также команды для операций над десятичными числами. Они будут рассмотрены далее.

2.5. Логические команды и команды сдвига

К логическим командам процессора 8086 относятся три команды с использованием двух операндов AND (И), OR (ИЛИ) и XOR (ИСКЛЮЧАЮЩЕЕ ИЛИ).

Эти команды осуществляются для каждого бита данных, как показано в следующем примере:

В операции XOR в случае, когда в обоих операндах биты равны 1, результат равен 0. Если в операции AND использовать в качестве исходных данных маску (приведенный выше операнд 00111100), то она оказывается очень удобной, так как операция просто сводится к считыванию только битов, соответствующих битам маски, равным 1 (в приведенном выше примере находящиеся в середине биты 0101).

Рис. 2 16. Примеры логических команд.

На рис. 2.16 приведены примеры команд and и других логических команд. К логическим командам относится еще одна команда, имеющая вид, аналогичный виду команды and, и выполняющая подобные действия. Это — команда test. Команда test, так же как и команда сравнения сmр, результат выполнения логической операции and никуда не засылает, а только формирует значение битов регистра флажков. Она используется в сочетании с командой условного перехода, о которой будет сказано позднее.

Рис 2 17. Команды сдвига и циклического сдвига.

Рис. 2 18. Различные модификации команды sal

Команда not, также представленная на рис 2.16, служит для изменения каждого бита данных: 0 на 1, 1 на 0.

В соответствии с этой командой данные 01110010, например, изменяются на данные 10001101.

Существуют также команды сдвига и циклического сдвига, показанные на рис. 2.17, которые могут изменять все биты данных подобно команде not.

Операндами этой команды являются 1-байтные или 2-байтные данные, размещенные в регистрах или в косвенно адресуемых ячейках памяти. На рис. 2.18 показаны эти команды, представленные шестнадцатеричным кодом. Число разрядов, на которое может быть произведен сдвиг вправо или влево, различно для разных команд: если стоит указатель 1, то сдвиг производится на 1 разряд, а если указатель cl, то сдвиг производится на такое число разрядов, какое содержится в регистре CL. Команда типа shr ax,2 не допускается (для этой марки процессора). Непосредственная запись числа, на которое осуществляется сдвиг, возможна только для числа 1. При желании произвести, сдвиг содержимого регистра АХ вправо, например, на 5 разрядов необходимо использовать следующие команды:

mov cl,5

shr ax,сl