Assembler: div & mul. Приветствуем всех! С вами команда IT… | by ITRoot Corp
3 min read·
Apr 5, 2019Приветствуем всех! С вами команда IT Root, и сегодня в планах у нас команды div и mul
- Для умножения в Assembler используют команду MUL
- Для деления в Assembler используют команду DIV
Умножение в Assembler
Для умножения чисел без знака предназначена команда MUL. У этой команды только один операнд — второй множитель, который должен находиться в регистре или в памяти. Местоположение первого множителя и результата задаётся неявно и зависит от размера операнда:
Отличие умножения от сложения и вычитания в том, что разрядность результата получается в 2 раза больше, чем разрядность сомножителей. Также и в десятичной системе — например, умножая двухзначное число на двухзначное, мы можем получить в результате максимум четырёхзначное. Запись «DX:AX» означает, что старшее слово результата будет находиться в DX, а младшее — в AX.
Если старшая часть результата равна нулю, то флаги CF и ОF будут иметь нулевое значение. В этом случае старшую часть результата можно отбросить. Это свойство можно использовать в программе, если результат должен быть такого же размера, как множители.
Если аргументом команды MUL является 1-байтовый регистр (например MUL bl), то значение этого регистра bl умножится на значение регистра al, а результат запишется в регистр ax, и так будет всегда, независимо от того, какой 1-байтовый регистр мы возьмем.
al * bl = ax;
Если аргументом является регистр из 2 байт(например MUL bx), то значение в регистре bx умножится на значение, хранящееся в регистре ax, а результат умножения запишется в регистр eax.
bx * ax = dx:ax
Если аргументом является регистр из 4 байт(например MUL ebx), то значение в регистре ebx умножится на значение, хранящееся в регистре eax, а результат умножения запишется в 2 регистра: edx и eax.
ebx * eax = edx:eax
Деление в Assembler
Для умножения чисел без знака предназначена команда DIV, которая относится к группе команд целочисленной арифметики и производит целочисленное деление с остатком беззнаковых целочисленных операндов.
Делимое, частное и остаток задаются неявно. Делимое является переменной в регистре (или регистровой паре) AX, DX:AX или EDX:EAX в зависимости от кода команды и размера операнда (что также определяет и разрядность делителя). Единственный явный операнд команды — операнд-источник (SRC), задающий делитель — может быть переменной в регистре или в памяти.
Целая часть частного помещается в регистр AL, AX или EAX в зависимости от заданного размера делителя (8, 16 или 32 бита). При этом остаток от целочисленного деления помещается в регистр AH, DX или EDX соответственно.
Действие команды DIV зависит от размера операнда-источника следующим образом:
Если частное, получаемое в результате деления, оказывается слишком велико, чтобы поместиться в целевом регистре-назначении (то есть имеет место переполнение), или если делитель равен нулю, то генерируется особая ситуация #DE.
Если аргументом команды div является 1-байтовый регистр (например DIV bl), то значение регистра ax поделится на значение регистра bl, результат от деления запишется в регистр al, а остаток запишется в регистр ah.
ax / bl = al:ah
Если аргументом является регистр из 2 байт(например DIV bx), то процессор поделит число, старшие биты которого хранит регистр dx, а младшие ax на значение, хранящееся в регистре bx. Результат от деления запишется в регистр ax, а остаток запишется в регистр dx.
(dx:ax) / bx = ax:dx
Если же аргументом является регистр из 4 байт(например DIV ebx), то процессор аналогично предыдущему варианту поделит число, старшие биты которого хранит регистр edx, а младшие eax на значение, хранящееся в регистре ebx. Результат от деления запишется в регистр eax, а остаток запишется в регистр edx.
(edx:eax) / ebx = eax:edx
AfterWord
На этой ноте пост подходит к концу. Теперь вы знаете про команды умножения и деления в Assembler. В одном из следующих постов мы разберём умножение и деление чисел со знаком. Спасибо за внимание и до скорых встреч!)
Не забывайте, что множество интересных статей можно найти на нашем Telegram канале и в Telegram боте, также все статьи мы публикуем в Twitter и Facebook
Урок 11. Умножение и деление
Поход на рынок, как правило, заканчивается покупкой какого-либо товара. Если мы хотим купить несколько единиц товара одного наименования, то продавец посчитает нам итоговую сумму с использованием простейшего компьютера – микрокалькулятора. В аналогии с ассемблером при умножении товаров программа микрокалькулятора будет оперировать с числами без знака, поскольку нельзя купить минус три пури или минус пять круассанов. Чтобы есть и худеть, даже если очень бы хотелось
Умножение чисел без знака
Допустим, нам требуется сделать для нашего микрокалькулятора программу умножения количества товаров и цен. Применим команду ассемблера MUL для умножения беззнаковых чисел. Работать с этой командой просто, первый множитель по умолчанию хранится в регистре AL или AX, а второй множитель команде передаётся как единственный операнд – хранимый в регистре или в памяти.
Размер операнда | Множитель | Результат |
Байт | AL | AX |
Слово | AX | DX:AX |
Как видно из таблицы, длина множителя (байт или слово) определяются по размеру переданного операнда. Если команда MUL перемножает два байтовых операнда, результатом будет слово, а если перемножаются двухбайтовые операнды, результатом будет двойное слово. При умножении в ассемблере разрядность результата будет ровно в 2 раза больше каждого из исходных значений.
mul bl ;AX = AL * BL mul ax ;DX:AX = AX * AX
mul bl ;AX = AL * BL mul ax ;DX:AX = AX * AX |
Если умножаются два операнда размеров в байт, то результат умножения помещается в регистр AX, а при умножении двух слов, результат комбинируется в регистрах DX:AX, где старшее слово результата будет в DX, а младшее в AX. Не всегда результатом умножения двух байт будет слово или при умножением двух слов – двойное слово. Если старшая часть результата содержит ноль, то флаги CF и OF будут также равны нулю. Соответственно старшую часть результата умножения можно игнорировать, что может быть полезно в определенных случаях.
Умножение чисел со знаком
Теперь переходим к миру математики, в котором знаковые числа перемножаются не реже беззнаковых. В ассемблере для таких операций есть команда IMUL. Инструкция имеет три формы, разнящиеся числом операндов:Вызов с одним операндом – аналогично команде MUL. Передаваемым операндом может быть регистр или значение в памяти. Операнд умножается на значение в AL (операнд – байт) или AX (операнд – слово). Результат хранится в AX или комбинируется в DX:AX соответственно.
Вызов с двумя операндами – в этой форме перезаписываемый операнд назначения (первый множитель) умножается на передаваемый операнд — источник (второй множитель). В качестве перезаписываемого операнда должен указываться регистр общего назначения, а вторым операндом может быть непосредственное значение, регистр общего назначения или область памяти. Младшая часть результата помещается в перезаписываемый операнд, старшая часть (дважды от размера операнда – источника) отсекается.
Вызов с тремя операндами – в этой форме используется операнд назначения и два операнда -источника, содержащие первый и второй множители. Первый множитель, которым может быть регистр общего назначение или область памяти, умножается на второй множитель (непосредственное значение). Итоговое произведение операндов (дважды от размера первого операнда – источника) усекается и хранится в операнде назначения (регистр общего назначения).
Примеры использования команды IMUL:
imul bl ;AX = AL * BL imul cx ;DX:AX = AX * CX imul si,-13 ;SI = SI * -13 imul bx,si,123h ;BX = SI * 123h
imul bl ;AX = AL * BL imul cx ;DX:AX = AX * CX imul si,-13 ;SI = SI * -13 imul bx,si,123h ;BX = SI * 123h |
Для первой формы флаги CF = 0 и OF = 0 означают, что результат умножения поместился в младшей части, в то время как CF = OF = 1 для команды IMUL в форме с двумя или тремя операндами сигнализируют переполнение, то есть потерю старшей части результата.
Деление чисел без знака
Команда DIV используется для выполнения деления беззнаковых чисел с остатком. Если при умножении разрядность произведения всегда в два раза больше множителей, то при делении действует обратный принцип – большим разрядом является делимое, а частное всегда в два раза меньше делимого. Инструкция принимает единственный операнд – делитель, помещаемый в регистр общего назначения или в память. Размер делителя распределяет делимое, частное и остаток согласно таблице:
Размер операнда | Делимое | Частное | Остаток |
Байт | AX | AL | AH |
Слово | DX:AX | AX | DX |
Операция деления в ассемблере может вызывать прерывание (подробнее о прерываниях будет написано в одном из следующих уроков) в ряде случаев:
- При делении на ноль;
- Когда происходит переполнение частного, то есть результат не помещается в отведенную разрядность (например, если делимое – слово (1000), а делитель – байт (2), то результат (500) не поместится в байт).
Примеры:
div cl ;AL = AX / CL, остаток в AH div di ;AX = DX:AX / DI, остаток в DX
div cl ;AL = AX / CL, остаток в AH div di ;AX = DX:AX / DI, остаток в DX |
Деление чисел со знаком
Команда IDIV используется для деления чисел со знаком. Вызов аналогичен команде DIV – передаётся единственный аргумент – делитель, который неявно определяет размеры делимого, частного и остатка. Прерывание генерируется, если выполняется деление на ноль или частное превышает отведенную разрядность.
Программа к уроку
Применим полученные знания о командах деления и умножения для написания простой программы вычисления общего пройденного пути, имея исходные скорость, ускорение, время и расстояние:
s= s0+ v0t + at2/2
use16 org 100h ;===== Часть формулы: v0*t ===== mov al,[v] ;AL = v0 mov bl,[t] ;BL = t mul bl ;AX = AL * BL mov si,ax ;Произведение в SI ;===== Часть формулы: (a*t^2)/2 ===== mov al,[t] ;AL = t mul bl ;AX = AL * BL = a*t mov bl,[a] ;BX = a mov bh,0 ;BX — второй множитель, поэтому прибираемся в BH mul bx ;DX:AX = AX * BX = a*t^2 mov bx,2 ;BX = 2 div bx ;DX:AX / BX = a*t^2 / 2 ;===== Складываем все вместе ===== add ax,si ;v0*t + (a*t^2)/2 add al,[s] ;| adc ah,0 ;|. 2)/2
add al,[s] ;|
adc ah,0 ;|… + s0
mov [r],ax ;Храним результат в [r]
mov ax,4C00h ;|
int 21h ;|Заверешение программы
;—————————————
s db 180
v db 7
a db 4
t db 41
r dw ?
В 8-й строке результат умножения сохраняется в регистре SI. В 14-й строке старшая часть регистра BX – BH выставляется в ноль, что корректно только для беззнаковых чисел. Попытка присвоить ноль старшей части регистра, хранящего число со знаком может привести к ошибке, если младшая часть регистра хранит отрицательное число. Обратите внимание на строках 21-22 прибавление байта из переменной [s0] к слову AX происходит в два этапа: прибавление числа к AL, и корректировкой на перенос прибавлением нуля к AH.
Упражнение к уроку (1):
z = a2 + 2ab + b2
Числа в формуле используйте 16 битные целые без знака.
Упражнение к уроку (2) (продвинутое):
Используя школьный курс сложения столбиком и команды MUL и ADD, напишите программу для умножения 32-битных чисел без знака. Результатом будет 64-битное число без знака.
Учебники поAVR — 8-битное деление
В этом руководстве мы рассмотрим процедуры 8-битного деления в AVR Assembly. AVR не имеет возможности выполнять аппаратное деление (т. е. нет инструкций по делению), поэтому для выполнения этой задачи подпрограммы должны быть написаны с использованием других инструкций.
Руководство по применению Atmel AVR200 содержит полный список подпрограмм разделения — вместо того, чтобы изобретать велосипед, мы рассмотрим, как они работают.
8-битный беззнаковый раздел
8-битная подпрограмма беззнакового деления от Atmel показана ниже
;*************************************** ********************************************** ;* ;* "div8u" - 8/8-битное беззнаковое деление ;* ;* Эта подпрограмма разделяет две регистровые переменные "dd8u" (дивиденд) и ;* "dv8u" (делитель). Результат помещается в "dres8u", а остаток в ;* "дрем8у". ;* ;* Количество слов: 14 ;* Количество циклов :97 ;* Используются нижние регистры :1 (drem8u) ;* Использованы верхние регистры: 3 (dres8u/dd8u,dv8u,dcnt8u) ;* ;****************************************************** **************************** ;***** Переменные регистра подпрограммы .def drem8u =r15 ;остаток .def dres8u =r16 ;результат .def dd8u =r16 ;дивиденд .def dv8u =r17 ;делитель .def dcnt8u =r18 ;счетчик циклов ;***** Код div8u: sub drem8u,drem8u ;очистить остаток и перенести лди dcnt8u, 9;инициализировать счетчик циклов d8u_1: rol dd8u ;сдвиг делимого влево dec dcnt8u ;счетчик уменьшения brne d8u_2 ;если сделано рет ; возвращаться d8u_2: rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_3 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_1 ;иначе d8u_3: сек ; установить перенос в результат rjmp d8u_1
В этой подпрограмме делитель вводится в регистр dd8u (r16), а делитель помещается в регистр dv8u (r17). По завершении результат будет сохранен в dd8u (r16), а остаток — в drem8u (r15).
Подпрограмма начинается с очистки регистра, в котором будет храниться остаток от деления, drem8u, с помощью инструкции sub.
div8u: sub drem8u,drem8u ;очистить остаток и перенести
Вы можете удивиться, а почему бы просто не использовать вместо этого инструкцию clr? Ну, этой подпрограмме нужно Флаг переноса сбрасывается перед выполнением, и clr оставит его без изменений. Если бы вместо этого использовался clr, clc должен был бы следовать за ним, например.
div8u: clr drem8u ;очистить остаток клк ; ясно нести
Вместо этого использование sub очищает регистр и флаг переноса в одной инструкции. Довольно хитро!
Затем dcnt8u инициализируется как счетчик циклов со значением 9.
ldi dcnt8u,9 ;init loop counter
A For Loop устанавливается на метке d8u_1
d8u_1: rol dd8u ;сдвиг влево делимого dec dcnt8u ;счетчик уменьшения brne d8u_2 ;если сделано рет ; return
В каждом цикле dcnt8u будет уменьшаться, что дает 8 итераций, по одной для каждого бита в делимом. Подпрограмма вернется, когда достигнет нуля.
Затем старший бит делителя сдвигается в младший бит остатка. Мы проверяем, входит ли делитель в остаток, используя инструкцию sub. Вычитаем из этого значения делитель, и если он отрицательный (проверяется инструкцией brcc), то знаем, что делитель входит в остаток менее одного раза. В противном случае мы знаем, что он входит по крайней мере один раз.
d8u_2: rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_3 ;если результат отрицательный
Если делитель , а не идет в остаток, нам нужно восстановить его (поскольку он был изменен с помощью инструкции sub) и сдвинуть ноль в наш результат.
drem8u восстанавливается путем добавления к нему обратно dv8u, флаг переноса сбрасывается с помощью инструкции clc, а rol используется для сдвига нуля (значения флага переноса) в результат. Этот поток показан ниже с удаленными альтернативными ответвлениями.
d8u_2: rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель . .. добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат ↓ d8u_1: rol dd8u ;сдвиг нуля в делимое
Обратите внимание, что dd8u является как входным делимым , так и результатом , поэтому флаг переноса сдвигается в него, а не в другой регистр.
В качестве альтернативы, если делитель идет в остаток, нам не нужно его восстанавливать. Вместо этого нам нужно сместить единицу в результат, установив флаг переноса.
d8u_2: rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_3 ;если результат отрицательный ... d8u_3: сек ; установить перенос в результат ↓ d8u_1: rol dd8u ;сдвинуть единицу в делимое
Для любого результата используется rjmp для возврата к началу цикла, где из делимого будет смещен еще один бит, и цикл будет повторяться для всех 8 битов.
d8u_1: rol dd8u ;сдвиг влево делимого ... rjmp d8u_1
Использовать эту подпрограмму просто. Делимое просто нужно загрузить в r16, а делитель в r17, прежде чем он будет вызван.
ldi r16,101 ; загрузить 101 в r16 лди r17,10 ; загрузить 10 в r17 вызов div8u ; вычислить 101/10
В приведенном выше примере r16 будет содержать значение 10, когда подпрограмма закончит работу — результат деления, а r15 будет содержать 1, остаток.
Время выполнения и размер кода
В ассемблере вы часто будете сталкиваться с компромиссом между временем выполнения и размером кода. Приведенный выше пример довольно компактен из-за использования ветвей , но это приводит к нескольким дополнительным циклам накладных расходов на каждой итерации цикла. Вместо этого ветвление можно было бы уменьшить, если бы для каждого цикла цикла был отдельный блок кода, что ускорило бы процесс, но увеличило бы размер кода.
Вышеприведенное «оптимизировано по размеру», но вместо этого рассмотрим эту «оптимизированную по скорости» версию подпрограммы.
;***************************************************** ******************************* ;* ;* "div8u" - 8/8-битное беззнаковое деление ;* ;* Эта подпрограмма разделяет две регистровые переменные "dd8u" (дивиденд) и ;* "dv8u" (делитель). Результат помещается в "dres8u", а остаток в ;* "дрем8у". ;* ;* Количество слов: 66 + возврат ;* Количество циклов: 50/58/66 (мин./средн./макс.) + возврат ;* Используются нижние регистры :1 (drem8u) ;* Использованы верхние регистры :2 (dres8u/dd8u,dv8u) ;* ;****************************************************** **************************** ;***** Переменные регистра подпрограммы .def drem8u =r15 ;остаток .def dres8u =r16 ;результат .def dd8u =r16 ;дивиденд .def dv8u =r17 ;делитель ;***** Код div8u: sub drem8u,drem8u ;очистить остаток и перенести rol dd8u ;сдвиг дивиденда влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_1 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_2 ;иначе d8u_1: сек ; установить перенос в результат d8u_2: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_3 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_4 ;еще d8u_3: сек ; установить перенос в результат d8u_4: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_5 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_6 ;иначе d8u_5: сек ; установить перенос в результат d8u_6: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_7 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_8 ;иначе d8u_7: сек ; установить перенос в результат d8u_8: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель бркк d8u_9;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_10 ;иначе d8u_9: сек ; установить перенос в результат d8u_10: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_11 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_12 ;еще d8u_11: сек ; установить перенос в результат d8u_12: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_13 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_14 ;еще d8u_13: сек ; установить перенос в результат d8u_14: rol dd8u ;сдвиг делимого влево rol drem8u ;сдвинуть делимое в остаток sub drem8u,dv8u ;остаток = остаток - делитель brcc d8u_15 ;если результат отрицательный добавить дрем8у,дв8у ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8u_16 ;иначе d8u_15: сек ; установить перенос в результат d8u_16: rol dd8u ;сдвиг делимого влево рет
«Оптимизированная по скорости» версия значительно длиннее — 66 слов вместо 14. Однако она может выполняться за 58 циклов по сравнению с 97 для «оптимизированной по размеру» версии.
Вам придется принять решение на основе компромисса между скоростью и размером кода для вашего конкретного приложения. Но самое удивительное в сборке то, что теперь у вас есть выбор!
8-битное деление со знаком
8-битное деление со знаком на самом деле довольно просто, если вы знаете, как выполнять беззнаковое деление. Вам просто нужно проверить, являются ли делитель и делимое отрицательными, преобразовать их Дополнение до значения до величины, если оно есть, и продолжить беззнаковое деление, как и раньше. В конце концов, если один или другой был отрицательным (но не оба!), вам нужно преобразовать результат обратно в значение со знаком. Это показано ниже
;************************************************ ********************************** ;* ;* "div8s" - 8/8-битное деление со знаком ;* ;* Эта подпрограмма разделяет две регистровые переменные "dd8s" (дивиденд) и ;* "dv8s" (делитель). Результат помещается в "dres8s", а остаток в ;* "дрем8с". ;* ;* Количество слов: 22 ;* Количество циклов: 103 ;* Используются нижние регистры: 2 (d8s, drem8s) ;* Используются старшие регистры: 3 (dres8s/dd8s,dv8s,dcnt8s) ;* ;****************************************************** **************************** ;***** Переменные регистра подпрограммы .def d8s =r14 ;регистр знака .def drem8s =r15 ;остаток .def dres8s =r16 ;результат .def dd8s =r16 ;дивиденд .def dv8s =r17 ;делитель .def dcnt8s =r18 ;счетчик циклов ;***** Код div8s: mov d8s,dd8s ;переместить делимое в регистр подписи eor d8s,dv8s ;xor знак с делителем sbrc dv8s,7 ;если установлен старший бит делителя отрицательный dv8s ; поменять знак делителя sbrc dd8s,7 ;если установлен старший бит дивиденда отрицательный dd8s ; поменять знак делителя sub drem8s, drem8s ;очистить остаток и перенести лди dcnt8s, 9;инициализировать счетчик циклов d8s_1: rol dd8s ;сдвинуть делимое влево dec dcnt8s ;счетчик уменьшения brne d8s_2 ;если сделано sbrc d8s,7 ; если MSB знакового регистра установлен отрицательные платья ; изменить знак результата рет ; возвращаться d8s_2: rol drem8s ;сдвинуть делимое в остаток sub drem8s,dv8s ;остаток = остаток - делитель brcc d8s_3 ;если результат отрицательный добавить дрем8с,дв8с ; восстановить остаток клк ; очистить перенос, чтобы перейти в результат rjmp d8s_1 ;иначе d8s_3: сек ; установить перенос в результат rjmp d8s_1
Эта процедура деления со знаком требует на один регистр больше, чем раньше — d8s (r14). d8s используется для отслеживания знаков операндов и результата.
Подпрограмма начинается с копирования делимого dd8s в регистр знаков d8s и выполнения исключающего ИЛИ между ним и делителем dv8s. Если делимое или делитель отрицательный, будет установлен бит 7 регистра знака, иначе он будет очищен.
div8s: mov d8s,dd8s ;переместить делимое в регистр подписи eor d8s,dv8s ;исключающий знак с делителем
Далее по отдельности проверяются знак делимого и делителя. Если они отрицательны, они преобразуются в величину без знака с помощью инструкции neg.
sbrc dv8s,7 ;если установлен старший бит делителя отрицательный dv8s ; поменять знак делителя sbrc dd8s,7 ;если установлен старший бит дивиденда отрицательный dd8s ; изменить знак делителя
После этого процедура деления может продолжаться точно так же, как и раньше, поскольку мы знаем, что используем два значения без знака. Разница лишь в том, что перед возвратом мы проверяем знаковый регистр d8s. Если установлен его самый старший бит, один из операндов был отрицательным, поэтому мы знаем, что результат должен быть отрицательным и преобразуется обратно в значение со знаком, используя neg 9.0003
раздел 8: ... sbrc d8s,7 ; если MSB знакового регистра установлен отрицательные платья ; изменить знак результата рет ; возвращаться ...
Конечно, если вы предпочитаете скорость размеру кода, эту подпрограмму можно модифицировать точно так же, как приведенную выше, чтобы уменьшить количество ветвлений. Посмотрите, сможете ли вы написать его самостоятельно!
Заключение
Вот и все. 8-битное деление в AVR Assembly. Не так уж и плохо, верно? Надеюсь, вы видите, насколько это дороже, чем аппаратные подпрограммы. Избегайте этого, если можете, но если необходимо… теперь вы знаете, как это сделать.
<< Предыдущая
Следующая >>
DIV — Беззнаковое деление
DIV — Беззнаковое делениеКод операции | Инструкция | Оп/Ан | 64-битный режим | Режим совместимости/этапа | Описание |
---|---|---|---|---|---|
F6/6 | ДЕЛ р/м8 | М | Действительно | Действительно | Беззнаковое деление AX на r/m8 с сохранением результата в AL := Частное, AH := Остаток. |
РЕКС + F6/6 | ДЕЛ об/м8 1 | М | Действительно | Н.В. | Беззнаковое деление AX на r/m8 с сохранением результата в AL := Частное, AH := Остаток. |
Ф7/6 | РАЗДЕЛ р/м16 | М | Действительно | Действительно | Беззнаковое деление DX:AX на r/m16 с сохранением результата в AX := Частное, DX := Остаток. |
Ф7/6 | РАЗДЕЛ об/м32 | М | Действительно | Действительно | Беззнаковое деление EDX:EAX на r/m32 с сохранением результата в EAX := Частное, EDX := Остаток. |
REX.W + F7/6 | РАЗДЕЛ р/м64 | М | Действительно | Н.В. | Беззнаковое деление RDX:RAX на r/m64 с сохранением результата в RAX := Частное, RDX := Остаток. |
1. В 64-битном режиме r/m8 нельзя закодировать для доступа к следующим байтовым регистрам, если используется префикс REX: AH, BH, CH, DH.
Кодирование операнда инструкции ¶
Оп/Ан | Операнд 1 | Операнд 2 | Операнд 3 | Операнд 4 |
---|---|---|---|---|
М | ModRM:об/м (ш) | н/д | н/д | Н/Д |
Описание ¶
Делит беззнаковое значение в регистрах AX, DX:AX, EDX:EAX или RDX:RAX (делимое) на исходный операнд (делитель) и сохраняет результат в регистрах AX (AH:AL), DX:AX, EDX :EAX или RDX:RAX. Исходным операндом может быть регистр общего назначения или ячейка памяти. Действие этой инструкции зависит от размера операнда (делимое/делитель). Деление с использованием 64-битного операнда доступно только в 64-битном режиме.
Нецелочисленные результаты усекаются (отрезаются) в сторону 0. Остаток всегда меньше делителя по величине. Переполнение обозначается исключением #DE (ошибка деления), а не флагом CF.
В 64-битном режиме размер операции инструкции по умолчанию составляет 32 бита. Использование префикса REX.R разрешает доступ к дополнительным регистрам (R8-R15). Использование префикса REX.W способствует работе до 64 бит. В 64-битном режиме, когда применяется REX.W, инструкция делит значение без знака в RDX:RAX на исходный операнд и сохраняет частное в RAX, а остаток в RDX.
См. сводную таблицу в начале этого раздела для кодирования данных и ограничений. См. Таблицу 3-15.
Размер операнда | Дивиденд | Делитель | Частное | Остаток | Максимальное частное |
---|---|---|---|---|---|
Слово/байт | ТОПОР | об/м8 | АЛ | АХ | 255 |
Двойное слово/слово | DX:AX | об/м16 | ТОПОР | ДС | 65 535 |
Квадратное/двойное слово | EDX:EAX | об/м32 | ЭАКС | ЭДС | 2 32 − 1 |
Двойное четверное слово/четверное слово | ГДРС:РАКС | об/м64 | РАКС | гексоген | 2 64 − 1 |
Операция ¶
МСФО = 0 ТОГДА #DE; ФИ; (* Ошибка деления *) IF OperandSize = 8 (* Операция Word/Byte *) ЗАТЕМ темп := AX/SRC; ЕСЛИ температура > FFH ТОГДА #DE; (*Ошибка деления*) ЕЩЕ АЛ := темп; AH := AX MOD SRC; ФИ; ELSE IF OperandSize = 16 (* операция двойное слово/слово *) ЗАТЕМ темп:= DX:AX/SRC; ЕСЛИ температура > FFFFH ТОГДА #DE; (*Ошибка деления*) ЕЩЕ AX := темп; DX := DX:AX MOD SRC; ФИ; ФИ; ELSE IF Размер операнда = 32 (* Операция с квадрословом/двойным словом *) ЗАТЕМ темп:= EDX:EAX/SRC; ЕСЛИ температура > FFFFFFFFH ТОГДА #DE; (*Ошибка деления*) ЕЩЕ EAX := темп; EDX := EDX:EAX MOD SRC; ФИ; ФИ; ИНАЧЕ, ЕСЛИ 64-битный режим и размер операнда = 64 (* операция двойного четверного слова/четверенного слова *) ЗАТЕМ темп:= RDX:RAX/SRC; ЕСЛИ температура > FFFFFFFFFFFFFFFFH ТОГДА #DE; (*Ошибка деления*) ЕЩЕ РАКС := темп; RDX := RDX:RAX MOD SRC; ФИ; ФИ; ФИ;
Затронутые флаги ¶
Флаги CF, OF, SF, ZF, AF и PF не определены.
Исключения защищенного режима ¶
#DE | Если исходный операнд (делитель) равен 0 |
Если частное слишком велико для указанного регистра. | |
#GP(0) | Если эффективный адрес операнда памяти выходит за пределы сегмента CS, DS, ES, FS или GS. |
Если регистр DS, ES, FS или GS содержит селектор сегмента NULL. | |
#SS(0) | Если эффективный адрес операнда памяти выходит за пределы сегмента SS. |
#PF(код неисправности) | Если происходит ошибка страницы. |
#AC(0) | Если включена проверка выравнивания и делается невыровненная ссылка на память, когда текущий уровень привилегий равен 3. |
#UD | Если используется префикс LOCK. |
Исключения режима реального адреса ¶
#DE | Если исходный операнд (делитель) равен 0. |
Если частное слишком велико для указанного регистра. | |
#GP | Если эффективный адрес операнда памяти выходит за пределы сегмента CS, DS, ES, FS или GS. |
Если регистр DS, ES, FS или GS содержит селектор сегмента NULL. | |
#SS(0) | Если эффективный адрес операнда памяти выходит за пределы сегмента SS. |
#УД | Если используется префикс LOCK. |
Исключения режима Virtual-8086 ¶
#DE | Если исходный операнд (делитель) равен 0. |
Если частное слишком велико для указанного регистра. | |
#GP(0) | Если эффективный адрес операнда памяти выходит за пределы сегмента CS, DS, ES, FS или GS. |
#SS | Если эффективный адрес операнда памяти выходит за пределы сегмента SS. |
#PF(код неисправности) | Если происходит ошибка страницы. Оставить комментарий
|