Седов Иван Алексеевич

Рязанский политехнический колледж

ASSEMBLER 8-bit SIMULATOR

Занятие #4: создание процедур

остаток от деления, вывод числа из регистра

https://e1m7.github.io/work/

Процедура вывода числа из регистра очень сложная. Реализовывать ее "сама в себе" не правильно, для удобства сначала надо создать процедуру, которая будет присылать нам остаток от деления числа на число. 

 

Процедура остаток от деления двух чисел. Она должна получать на вход два числа (допустим, 13 и 5), а возвращать остаток от деления второго числа на первое (3).

 

  1. Берем 13
  2. Делим 13 на 5 (получаем 2)
  3. Умножаем 2 на 5 (получаем 10)
  4. Берем 13 и вычитаем 10 (получаем 3)
  5. Кладем 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. 1 = 49 (положить в [232])
  2. 2 = 50 (положить в [233]) 
  3. 5 = 53 (положить в [234])

Сделать это надо автоматически, то есть если число было, например, 14, то положить надо два кода (49 и 52) в ячейки памяти 232 и 233.

 

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

Основная идея процедуры (более подробно) будет такова:

  1. Берем число 125
  2. Ищем остаток от деления на 10 (это будет 5)
  3. Кладем код цифры 5 в стек, а 125 делим на 10 (получаем 12)

 

  1. Берем число 12
  2. Ищем остаток от деления на 10 (это будет 2)
  3. Кладем код цифры 2 в стек, а 12 делим на 10 (получаем 1)

 

  1. Берем число 1
  2. Ищем остаток от деления на 10 (это будет 1)
  3. Кладем код цифры 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=49
print_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 loop41
print40: 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