Седов Иван Алексеевич
Рязанский политехнический колледж
ASSEMBLER 8-bit SIMULATOR
Занятие #4: создание процедур
остаток от деления, вывод числа из регистра


https://e1m7.github.io/work/
Процедура вывода числа из регистра очень сложная. Реализовывать ее "сама в себе" не правильно, для удобства сначала надо создать процедуру, которая будет присылать нам остаток от деления числа на число.
Процедура остаток от деления двух чисел. Она должна получать на вход два числа (допустим, 13 и 5), а возвращать остаток от деления второго числа на первое (3).
- Берем 13
- Делим 13 на 5 (получаем 2)
- Умножаем 2 на 5 (получаем 10)
- Берем 13 и вычитаем 10 (получаем 3)
- Кладем 3 в регистр А (ответ)
remainder_division:
; input
; C = number1
; D = number2
; output
; A = number
MOV A, C ; A=13
DIV D ; A=A/D=13/5=2
MUL D ; A=A*D=2*5=10
SUB C, A ; C=C-A=13-10=3
MOV A, C ; A=C
RETПроцедура remainder_division занимает 14 байт(!) и не использует стек при работе(!), если передать в нее числа 13 и 5, то она вернет 3 в регистре A.
JMP start
start:
MOV C, 13
MOV D, 5
CALL remainder_division
HLTПроцедура работает хорошо, но вылетает при делении на 0. Эмулятор сам останавливает дальнейшее выполнение программы. Это цена минимализма:) Для a > 0, b > 0 процедура работает хорошо, как для a > b, так и для a < b и a = b.
Проведем рассуждения о процедуре вывода числа из регистра. Допустим, что в регистре А находится число 125. Как вывести его на экран?
JMP start
start:
MOV A, 125
MOV [232], A
HLT


Чтобы вывести число 125 (из регистра А) необходимо положить в три ячейки памяти (232, 233, 234) три числа = ASCII-кодам трех цифр, из которых состоит число 125, а именно:
- 1 = 49 (положить в [232])
- 2 = 50 (положить в [233])
- 5 = 53 (положить в [234])
Сделать это надо автоматически, то есть если число было, например, 14, то положить надо два кода (49 и 52) в ячейки памяти 232 и 233.
Основная идея процедуры надо разбить число в регистре на цифры, из которых оно состоит (шаг 1), найти коды этих цифр (шаг 2) и положить их (в строгом порядке) в ячейки начиная с 232 (шаг 3).
Основная идея процедуры (более подробно) будет такова:
- Берем число 125
- Ищем остаток от деления на 10 (это будет 5)
- Кладем код цифры 5 в стек, а 125 делим на 10 (получаем 12)
- Берем число 12
- Ищем остаток от деления на 10 (это будет 2)
- Кладем код цифры 2 в стек, а 12 делим на 10 (получаем 1)
- Берем число 1
- Ищем остаток от деления на 10 (это будет 1)
- Кладем код цифры 1 в стек, а 1 делим на 10 (получаем 0)
Деление продолжает до тех пор, пока мы не получим 0.

В таблице ASCII цифры 0-9 идут под номерами от 48(0) до 57(9). Для вывода цифры на дисплей мы должны положить по адресу 232 именно код символа, а не его значение.
Если мы хотим увидеть на экране 1, то надо положить в 232-ой байт памяти код 1 = 49. Как быстро перевести любую цифру (0-9) в ее код? Если регистр A=1, то есть два способа:
ADD A, 48 ; A=A+48=1+48=49
ADD A, '0' ; A=A+'0'=1+48=49print_number:
; A = number
PUSH B
PUSH C
PUSH D
PUSH 0 ; 0 в стеке, чтобы ловить конец числа
; Разделение числа на цифры и положение в стек их кодов
.loop41: ; Начало цикла
CMP A, 0 ; Сравнение A=0?
JE .print41 ; (да) переход .print41
; (нет) ищем остаток от деления и кладем его в стек
MOV B, A ; B=A(125) (временно сохраним)
MOV C, A ; C=A=125 (для нахождения остатка)
MOV D, 10 ; D=10 (для нахождения остатка)
CALL remainder_division
ADD A, '0' ; A=A+'0' (превратили 5 в '5')
PUSH A ; Положили '5'(0x35=53d) в стек
MOV A, B ; A=B=125 (вернули A ее значение)
DIV 10 ; A=A/10=12 (отбросили одну цифру справа)
JMP .loop41 ; Переход на начало цикла
; Вытаскивание кодов цифр из стека и вывод их на экран
.print41:
MOV D, 232 ; D=232 (настройка на выходные байты)
.loop42: ; Начало цикла
POP A ; A=первое число из стека ('1'=0x31=49)
CMP A, 0 ; Сравнение A=0?
JE .exit41 ; (да) переход .exit
; (нет) выводим число в текущую позицию D
MOV [D], A ; [232]=A(0x31=49='1')
INC D ; D=D+1=232+=233
JMP .loop42 ; Переход на начало цикла
.exit41:
POP D
POP C
POP B
RET JMP start
start:
MOV A, 125
CALL print_number
CALL haltТак написал бы процедуру программист 1-го года изучения ассемблера. В данной программе есть две ошибки и два недочета (4 "косяка" на программу в 84 байта это довольно много, "джуну" такое простят, но уже для "мидла" это будет стыдно). Кто сможет найти их?
Ошибки и недочеты
Ошибка №1 если записать в регистр А ноль и вызвать процедуру, то не будет никакого результата.
Ошибка №2 в процедуре поиска остатка от деления содержимое регистров C и D не сохраняется в стек в начале и не восстанавливается в конце.
Недочет №1 процедура remainder_division используется только для того, чтобы делить на 10 (это не обязательно передавать, это можно жестко прописать в процедуре).
Недочет №2 в процедуре print_number регистр B используется только один раз (сохраняет значение А и потом восстанавливает его), это глупо.
JMP start
start:
MOV A, 250
CALL print_number
HLT
print_number:
; input
; A = number
; output
; number => display
PUSH C
CMP A, 0
JE print0
PUSH 0
loop41: CMP A, 0
JE print40
MOV C, A
CALL remdiv10
ADD A, '0'
PUSH A
MOV A, C
DIV 10
JMP loop41print40: MOV C, 232
loop42: POP A
CMP A, 0
JE end40
MOV [C], A
INC C
JMP loop42
print0: MOV [232], 48
end40: POP C
RET
remdiv10:
; input
; C = number
; output
; A = rem number/10
PUSH C
MOV A, C
DIV 10
MUL 10
SUB C, A
MOV A, C
POP C
RET Сколько байт в этой программе?
Замечание
В нашем распоряжении есть процедуры:
- print_string
-
print_number
-
print_char
- remainder_division
-
remdiv10
- halt
assembler8_04
By Ivan Sedov
assembler8_04
- 354