В апноуте AVR202 [1] раскрываются вопросы реализации 16-разрядной арифметики на языке ассемблера 8-разрядного микроконтроллера AVR:
• Простые подпрограммы расширяемые до 32 бит или любой длины слова данных. • Плотность кода и скорость обработки сопоставимы с аналогичными показателям 16-разрядных микроконтроллеров. • Есть запускаемый пример кода.
Прим. переводчика: описанный в этом даташите код имеет смысл использовать только в тех редких случаях, когда Вы программируете на языке ассемблера, и хотите добиться от своей программы экстремального быстродействия. При программировании на языке C сложение чисел любой разрядности реализуется довольно просто средствами самого языка.
Список всех реализаций с их ключевыми параметрами приведен в таблице 1.
Таблица 1. Арифметические действия и их параметры эффективности.
Операция
Размер кода (в словах)
Время выполнения (в циклах ядра)
Сложение двух 16-битных регистровых переменных
2
2
Сложение 16-битной непосредственной константы и 16-битной регистровой переменной
2
2
Вычитание 16-битных регистровых переменных
2
2
Вычитание 16-битной непосредственной константы из 16-битной регистровой переменной
2
2
Сравнение двух 16-битных регистровых переменных
2
2
Сравнение 16-битной непосредственной константы и 16-битной регистровой переменной
3
3
Инверсия в дополнительном коде 16-битной регистровой переменной
4
4
Примечание: размер кода для AVR отсчитывается иногда по словам, что создает некоторую путаницу. Одна инструкция ассемблера AVR занимает как минимум 1 слово (2 байта).
[16-битное сложение регистров]
Операция выполняется по следующему алгоритму:
1. Складываются младшие байты слов. 2. Затем складываются старшие байты слов с учетом переноса, если он возник на шаге 1.
Можно таким же образом складывать дополнительное количество n байт, если учитывать перенос, возникающий от сложения предыдущего байта.
[Сложение 16-битного регистра и 16-битной непосредственной константы]
Под термином "непосредственная" (immediate) понимается операнд, входящий в состав команды языка ассемблера. Поскольку у AVR нет инструкции прибавления непосредственной константы или прибавления непосредственной константы с учетом переноса, то вместо этого используется вычитание непосредственной константы и вычитание непосредственной константы с учетом переноса. Звучит немного дико, но работает:
1. Сначала выполняется операция непосредственного вычитания (subtract immediate) проинвертированного числа из младшего регистра. 2. Затем делается то же самое для старшего байта с учетом переноса: выполняется subtract immediate with carry старшего байта проинвертированного числа из старшего регистра.
Таким же образом можно добавлять другие n-байт с учетом переноса.
[16-битное вычитание регистров]
Операция выполняется по следующему алгоритму:
1. Вычитаются младшие байты слов. 2. Затем вычитаются старшие байты слов с учетом переноса, если он возник на шаге 1.
Можно таким же образом вычитать дополнительное количество n байт, если учитывать перенос, возникающий от вычитания предыдущего байта.
[Вычитание из 16-битного регистра 16-битной непосредственной константы]
Операция выполняется по следующему алгоритму:
1. Вычитается из младшего байта регистра младший байт непосредственной константы. 2. Затем вычитается из старшего байта регистра старший байт непосредственной константы с учетом переноса, если он возник на шаге 1.
Можно таким же образом вычитать дополнительное количество n байт, если учитывать перенос, возникающий от вычитания предыдущего байта.
[Сравнение двух регистровых 16-битных переменных]
Операция выполняется по следующему алгоритму:
1. Сначала сравниваются младшие байты. 2. Затем сравниваются старшие байты с учетом переноса, если он возник на шаге 1.
Обратите внимание, что инструкция ассемблера "сравнение с учетом переноса" (Compare with Carry) поддерживает распространение нуля (zero-propagation). Это означает, что все инструкции ветвления по условию могут использовать двухшаговые операции сравнения. Можно сравнивать дополнительно n-количество байт, если сравнивать их с учетом переноса от сравнения на предыдущем шаге.
[Сравнение регистровой 16-битной переменной и 16-битной непосредственной константы]
Операция выполняется по следующему алгоритму:
1. Сравнивается младший регистр с младшим байтом непосредственной константы. 2. Старший байт непосредственной константы сохраняется в третий регистр. 3. Сравниваются старший регистр переменной с третьим регистром с учетом переноса, если он возник на шаге 1.
[Инверсия в дополнительном коде 16-битной регистровой переменной]
Здесь применяется арифметика в дополнительном коде, которая нужна для операции сложения непосредственной константы с регистром. Операция выполняется по следующему алгоритму:
1. Инвертируется младший байт. 2. Инвертируется старший байт. 3. Вычитается $FF из младшего байта. 4. Вычитается с учетом переноса $FF из старшего байта.
Примечание: шаги 3 и 4 эквивалентны добавлению $0001 к 16-разрядному числу.
;**** AVR202 ***************************************************************;*;* Title: 16-bit Arithmetics;* Version: 1.1;* Last updated: 97.07.04;* Target: AT90Sxxxx (подойдет для всех AVR);*;* Support E-mail: avr@atmel.com;*;* Описание: этот апноут перечисляет реализации следующих операций;* сложения/вычитания/сравнения:;*;* "add16" ADD 16+16;* "adddi16" ADD 16+Immediate(16);* "sub16" SUB 16-16;* "subi16" SUB 16-Immediate(16);* "cp16" COMPARE 16/16 ;* "cpi16" COMPARE 16/Immediate ;* "neg16" NEGATION 16;***************************************************************************.csegldir16,0x12;Инициализация нескольких регистров для демонстрацииldir17,0x34; подпрограмм ниже. Все ожидаемые результаты представленыldir18,0x56; в комментариях к коду.ldir19,0x78; ;***************************************************************************;* "add16" - сложение 16-битных регистров;*;* Этот пример складывает 2 регистровых переменных - 2 пары регистров ;* (add1l,add1h) и (add2l,add2h). Результат будет помещен в (add1l, add1h).;*;* Количество слов программы: 2;* Количество циклов: 2;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 4;*;* Примечание: сумма и слагаемое используют те же самые регистры. Это;* означает, что одно слагаемое будет перезаписано суммой.;***************************************************************************;**** Регистровые переменные.defadd1l = r16.defadd1h = r17.defadd2l = r18.defadd2h = r19 ;***** Кодadd16:addadd1l, add2l;складываются младшие байтыadcadd1h, add2h;складываются старшие байты с учетом переноса;Ожидаемый результат: 0xAC68 ;***************************************************************************;* "addi16" - сложение 16-разрядной регистровой переменной ;* с immediate-константой.;*;* Этот пример складывает регистровую переменную (addi1l,addi1h);* с непосредственной 16-битной константой, определенной оператором .equ.;* Результат помещается в (addi1l, addi1h).;*;* Количество слов программы: 2;* Количество циклов: 2;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 2;*;* Примечание: сумма и слагаемое используют те же самые регистры. Это;* означает, что одно слагаемое будет перезаписано суммой.;***************************************************************************;***** Регистровые переменные.defaddi1l = r16.defaddi1h = r17 ;***** 16-битная immediate-константа.equaddi2 = 0x1234 ;***** Кодaddi16:subiadd1l, low(-addi2) ;Добавить младший байт ( x -(-y)) = x + ysbciadd1h, high(-addi2) ;Добавить старший байт с учетом переноса;Ожидаемый результат 0xBE9C ;***************************************************************************;* "sub16" - вычитание 16-битных регистровых переменных;*;* В этом примере вычитается пара регистровых переменных (sub1l,sub1h);* из другой пары (sub2l, sub2h). Результат помещается в регистры;* sub1l, sub1h.;*;* Количество слов программы: 2;* Количество циклов: 2;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 4;*;* Примечание: результат и "sub1" используют одни и те же регистры, поэтому;* "sub1" будет перезаписан результатом.;***************************************************************************;***** Регистровые переменные.defsub1l = r16.defsub1h = r17.defsub2l = r18.defsub2h = r19 ;***** Кодsub16:subsub1l,sub2l;Вычитание младших байтовsbcsub1h,sub2h;Вычитание старших с учетом переноса;Ожидаемый результат 0x4646 ;***************************************************************************;* "subi16" - вычитание 16-битной immediate-константы из 16-битного;* регистра.;*;* В этом примере вычитается непосредственное 16-разрядное число subi2;* из 16-разрядного регистра (subi1l,subi1h). Результат помещается в ;* регистры subi1l, subi1h.;*;* Количество слов программы: 2;* Количество циклов: 2;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 2;*;* Примечание: результат и "subi1" используют одни и те же регистры, поэтому;* "subi1" будет перезаписан результатом.;***************************************************************************;***** Регистровые переменные.defsubi1l = r16.defsubi1h = r17 ;***** 16-битная immediate-константа.equsubi2 = 0x1234 ;***** Кодsubi16:subisubi1l,low(subi2) ;Вычесть младшие байтыsbcisubi1h,high(subi2) ;Вычесть старшие с учетом переноса;Ожидаемый результат 0x3412 ;***************************************************************************;* "cp16" - сравнение двух 16-битных чисел;*;* В этом примере сравниваются 2 регистровые пары (cp1l,cp1h) и;* (cp2l,cp2h). Если они равны, то устанавливается флаг нуля (zero flag),;* иначе он будет очищен.;*;* Количество слов программы: 2;* Количество циклов: 2;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 4;***************************************************************************;***** Регистровые переменные.defcp1l = r16.defcp1h = r17.defcp2l = r18.defcp2h = r19 ;***** Кодcp16:cpcp1l,cp2l;Сравнить младшие байтыcpccp1h,cp2h;Сравнить старшие байты с учетом переноса; от предыдущей операцииncp16:;Ожидаемый результат Z=0;***************************************************************************;* "cpi16" - сравнение 16-разрядного регистра и 16-разрядной;* immediate-константы;*;* В этом примере сравнивается регистровая пара (cpi1l,cpi1h) со значением;* cpi2. Если они равны, то установится zero flag, иначе он будет очищен.;* Это работает благодаря распространению нуля в системе команд AVR.;* Также установится перенос, если результат будет отрицательным. Это;* означает, что после сравнения могут использоваться все инструкции;* ветвления по условию.;*;* Количество слов программы: 3;* Количество циклов: 3;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 3;***************************************************************************;***** Регистровые переменные.defcp1l =r16.defcp1h =r17.defc_tmp=r18 .equcp2 = 0x3412;immediate-величина для сравнения ;***** Кодcpi16:cpicp1l,low(cp2) ;Сравнение младших байтldic_tmp,high(cp2) ;cpccp1h,c_tmp;Сравнение старшего байта;Ожидаемый результат Z=1, C= ;***************************************************************************;* "neg16" - инверсия в дополнительном коде 16-разрядного регистра;*;* Этот пример делает инверсию регистровой пары (ng1l,ng1h). Результат;* операции перезапишет содержимое этой регистровой пары.;*;* Количество слов программы: 4;* Количество циклов: 4;* Используемые младшие регистры: не используются;* Используемые старшие регистры: 2;***************************************************************************;***** Регистровые переменные.defng1l = r16.defng1h = r17 ;***** Кодng16:comng1l;Инверсия младшего байтаcomng1h;Инверсия старшего байтаsubing1l,low(-1) ;Прибавление 0x0001, сначала младший,sbcing1h,high(-1) ; затем старший байт.;Ожидаемый результат 0xCBEE ;***************************************************************************;* Конец примеров.;***************************************************************************forever:rjmpforever