Подпрограммы – это вспомогательные алгоритмы, которые можно вызывать по имени. В языке ассемблера имя подпрограммы – это метка. Для вызова подпрограммы используется команда
call метка
Подпрограмма должна заканчиваться командой возврата из подпрограммы
ret
Подпрограммы располагаются в программе ниже основной программы, после команды stop.
Пример программы, которая использует подпрограмму divMod для деления с остатком:
|
ассемблер |
псевдокод |
|
mov 1234, R0 mov 10, R1 call divMod stop divMod: mov R0, R2 div R1, R0 mul R0, R1 sub R1, R2 mov R2, R1 ret |
R0:= 123416 R1:= 1016 вызвать divMod стоп
R2:= R0 R0:= R0 div R1 R1:= R1 * R0 R2:= R2 – R1 R1:= R2 возврат |
Чтобы при отладке выполнять по шагам не только основную программу, но и подпрограмму, при выполнении команды call нужно нажать не F8, а F7.
Стек – это структура типа LIFO (англ. Last In – First Out, последним пришел – первым ушел). В современных компьютерах стек размещается в памяти, специальный регистр SP (англ. stack pointer) указывает на начало стека. Для работы со стеком используются всего две команды:
|
ассемблер |
псевдокод |
|
push R0 pop R0 |
сохранить R0 в стеке «снять» данные с вершины стека в R0 |
Конечно, сохранять в стеке можно не только R0, но и другие регистры.
Стек используется:
для временного хранения данных
для хранения адресов возврата из подпрограмм
для размещения локальных переменных подпрограмм
Пример программы (обмен значений регистров R0 и R1):
|
ассемблер |
псевдокод |
|
push R0 push R1 pop R0 pop R1 |
R0 – в стек R1 – в стек со стека – в R0 (старое значение R1) со стека – в R1 (старое значение R0) |
Если подпрограмма использует какой-то регистр, которые не содержит исходные данные и не предназначен для записи результата, она должна сохранить его стеке при входе и восстановить старое значение из стека при выходе. Например:
|
ассемблер |
псевдокод |
|
proc: push R0 ... pop R0 ret |
начало подпрограммы R0 – в стек основное тело подпрограммы со стека – в R0 (старое значение R0) возврат из подпрограммы |
Заметьте, что подпрограмма, приведенная в предыдущем пункте, не совсем грамотно написана – она не сохраняет значение регистра R2, хотя «портит» его во время работы.
ПЗУ в данной модели компьютера – это набор подпрограмм, каждая из которых заканчивается командой ret. Всего в ПЗУ может быть до 256 подпрограмм.
ПЗУ загружается при запуске тренажёра «ЛамПанель» из файла lampanel.rom, который должен находиться в том же каталоге, что и сама программа. Это обычный текстовый файл, который можно редактировать в редакторах типа Блокнота (если, конечно, вы понимаете, что вы делаете). В настоящей версии в ПЗУ включены следующие подпрограммы:
|
Номер |
Описание |
|
0 |
Очистить все порты панели (выключить все лампочки). |
|
1 |
Установить в FF16 все порты панели (включить все лампочки). |
|
2 |
Записать значение R0 во все порты панели. |
|
3 |
Прокрутить изображение на панели вниз. |
|
4 |
Прокрутить изображение на панели вверх. |
|
5 |
Вывести на панель массив данных, адрес которого находится в R0. |
|
6 |
Выполнить инверсию экрана (применить NOT). |
|
7 |
Операция «И» со всеми портами (R1 – маска). |
|
8 |
Операция «ИЛИ» со всеми портами (R1 – маска). |
|
9 |
Операция «исключающее ИЛИ» со всеми портами (R1 – маска). |
|
A16 |
Логический сдвиг влево всех портов (R1 – величина сдвига). |
|
B16 |
Логический сдвиг вправо всех портов (R1 – величина сдвига). |
|
C16 |
Циклический сдвиг влево всех портов (R1 – величина сдвига). |
|
D16 |
Циклический сдвиг вправо всех портов (R1 – величина сдвига). |
|
E16 |
Арифметический сдвиг вправо всех портов (R1 – величина сдвига). |
|
F16 |
Деление с остатком (R0:=R0 div R1, R1:=R0 mod R1). |
|
1016 |
Вывод цифры на экран (R0 – цифра, R1 – позиция, от 0 до 2) |
|
1116 |
Вывод числа из R0 на экран (R1 – система счисления, от 2 до 16). |
|
1216 |
Вывод числа из R0 на экран в десятичной системе счисления. |
|
1316 |
Вывод числа из R0 на экран в шестнадцатеричной системе счисления. |
Просмотреть содержимое ПЗУ можно с
помощью пункта меню «Программа-Просмотр
ПЗУ» или кнопки
на панели инструментов. Выделив
какую-нибудь строчку в левой части окна,
мы увидим справа текст выбранной
подпрограммы и ее коды:

Для вызова подпрограмм из ПЗУ нужно использовать команду
system номер_подпрограммы
Пример программы:
|
ассемблер |
псевдокод |
|
system 0 system 1 mov 123, R0 system 12 system 6 mov 1, R1 system A system B system 13 stop |
выключить панель включить все лампочки R0:= 12316 вывести R0 в десятичной системе инверсия R1:= 1 ; величина сдвига сдвиг экрана влево сдвиг экрана вправо вывести R0 в шестнадцатеричной системе стоп |
Все рассмотренные выше команды работают с 16-битными данными (словами). Часто, например, при обработке текстов, нужно использовать однобайтные данные. Для этого предназначены следующие команды, которые полностью аналогичны соответствующим командам без буквы «b» (от англ. byte) на конце:
|
команда |
значение |
|
movb cmpb shlb shrb sarb rolb rorb rclb rcrb |
копирование байта сравнение двух байтов логический сдвиг влево логический сдвиг вправо арифметический сдвиг вправо циклический сдвиг влево циклический сдвиг вправо циклический сдвиг влево через бит переноса циклический сдвиг вправо через бит переноса |
Команда movb очищает старший байт регистра, в который копируются данные. Например,
|
ассемблер |
псевдокод |
|
mov 1234, R0 movb 12, R0 |
R0:= 123416 R0:= 1216 |
Остальные команды никак не изменяют старший байт регистра-приемника.
Существует специальная команда для обмена старшего и младшего байтов слова:
swapb регистр
Пример программы:
|
ассемблер |
псевдокод |
|
mov 1234, R0 swapb R0 |
R0:= 123416 R0:= 341216 |
Согласно принципу однородности памяти фон Неймана, данные размещаются в той же области памяти, что и программа (обычно сразу после команды stop).
В тренажере «ЛамПанель» данные – это 16-битные слова (вводятся как числа в шестнадцатеричной системе счисления) или символьные строки, заключенные в двойные кавычки. Для размещения данных в памяти применяется команда data. Например:
... ; основная программа
stop
ddd: ; метка начала блока данных
data 1234 ; слово 123416
data 5678 ; слово 567816
data "Ехал Грека через реку" ; строка
Для того, чтобы работать с этими данными, нужно как-то к ним обратиться. Для этого используется косвенная адресация – в регистре находятся не сами данные, а их адрес в памяти. Рассмотрим пример:
|
ассемблер |
псевдокод |
|
mov @ddd, R0 swapb (R0) add 2, R0 swapb (R0) stop ddd: data 1234 data 5678 |
R0:= адрес метки ddd переставить байты слова под адресу ddd увеличить адрес на 2 (байта) переставить байты слова под адресу ddd+2 стоп начало блока данных здесь будет 341216 здесь будет 785616 |
Запись @метка означает «адрес метки». Запись (R0) означает «данные, адрес которых находится в R0» – это и есть косвенная адресация.
Косвенную адресацию можно использовать и в других командах, работающих с регистрами.
Пусть в блоке данных, который начинается на метке ddd, записан массив, который нужно обработать в цикле. В этом случае удобно использовать косвенную адресацию с автоматическим увеличением адреса. Запись «(R0)+» означает «работать с данными, адрес которых находится в R0, и после выполнения операции увеличить R0». Если команда работает со словом, R0 увеличится на 2, а если с байтом – на 1.
Пример программы:
|
ассемблер |
псевдокод |
|
mov @ddd, R0 mov 3, R1 loop: swapb (R0)+ sub 1, R1 jnz loop stop ddd: data 1234 data 5678 data 9ABC |
R0:= адрес метки ddd записать в R1 количество шагов цикла начало цикла переставить байты слова под адресу из R0 уменьшить счетчик оставшихся шагов если счетчик не ноль, перейти в начало цикла стоп начало блока данных здесь будет 341216 здесь будет 785616 здесь будет BC9A16 |
Пример программы обработки байтов:
|
ассемблер |
псевдокод |
|
mov @ddd, R0 loop: movb (r0),r1 or 20,r1 movb r1,(r0)+ cmpb 0,(r0) jnz loop stop ddd: data "ABCD" |
R0:= адрес метки ddd начало цикла R1:= байт слова под адресу из R0 из заглавной буквы сделать строчную записать результат в память сравнить код следующего байта с 0 если не ноль, перейти в начало цикла стоп начало блока данных здесь будет "abcd" |