Один раз мы делали нечто подобное, но на верилоге. Там очень эффективным оказался метод Double Dabble ("сдвинуть и прибавить 3"). Тогда специальный модуль осуществлял преобразование 16-битного числа в 20-битное двоично-десятичное и занимал 45 ЛЭ.
Но интересно было бы сделать ту же задачу "чисто программно", причём не шибко заботясь о скорости, ставя приоритет компактному коду.

Ситуация осложняется тем, что наш процессор почти что не умеет в логику - его АЛУ максимум умеет сдвигать влево-вправо, все остальные операции арифметические. Так что возиться по каждым 4 битам, как-то к ним прибавляя значения, регистрируя переносы где надо, и всё в таком духе - не для нас :)
Начнём с беззнаковых целых чисел...
Я нашёл 5 "магических констант", которые позволяют решить задачу на удивление легко.
BCDtable dw 0x1, 0xA, 0x64, 0x3E8, 0x2710
[Ну очень магические!]
BCDtable dw 1, 10, 100, 1000, 10000
Мы начинаем выдавать разряд за разрядом, начиная с самого старшего. А именно, взявшись за k-й разряд, мы вычитаем из исходного числа BCDtable[k] до тех пор, пока не получим отрицательное значение. Количество вычитаний, не приведших к смене знака мы подсчитываем - это и будет очередной десятичный разряд. Далее уменьшаем k на единичку - и всё хорошо.
Вот как это выглядит на нашем укуренном ассемблере:
;в аккумуляторе лежит беззнаковое число, которое мы хотим выдать в UART0 в десятичном виде. ;для начала, всегда 5 разрядов, пусть даже старшие - нули ;мы используем регистры i, k, Y, Acc Bin2Bcd proc k 4 ;номер обрабатываемого разряда @@start: i -1 ;получившийся десятичный разряд Y BCDtable @@sub: SUB [Y+k] i++ 0 JGE @@sub ADD [Y+k] [SP] Acc ;сейчас аккумулятор пригодится... Acc '0' ADD i UART0 Acc Acc [SP] kloop @@start JMP [--SP] Bin2Bcd endp
Всего 14 слов, или 28 байт в памяти - не так уж плохо.
Для проверки функционирования напишем программку, которая выведет нам степени двойки.
;простейший Bin2Bcd %include "Win1251.inc" .rodata OurString Int16 'С','т','е','п','е','н','и',' ','д','в','о','й','к','и',':' CRLF Int16 13,10,-32768 BCDtable dw 1,10,100,1000,10000 .data Stack dw ?,? .code main proc SP Stack X OurString CALL print X CRLF j 15 ACC 1 @@start: C Acc CALL Bin2Bcd ;очередное число CALL print ;перенос строки Acc C ADD Acc jLOOP @@start @@endless: JMP @@endless main endp ;X указывает на начало строки текста ;конец обозначается числом -32768 ;пока что процедура категорически неряшливая: ;меняет значение регистра i и Acc print proc i 0 @@start: ABS [X+i] ;хитрая проверка на -32768 JO @@finish UART0 Acc i++ 0 JMP @@start @@finish: JMP [--SP] print endp
Повторно приводить код процедуры Bin2Bcd мы не стали. При объявлении строк видим типично ассемблерный приём - строки OurString и CRLF имеют общий кусочек. То есть, когда мы начнём выводить OurString, то не остановимся на ней, и ещё выведем содержание CRLF, и только там остановимся на числе -32768.
Глянем, как оно работает на эмуляторе:

Хорошо работает! Однако нули в начале числа - как-то неаккуратно!
Можно модернизировать процедуру, чтобы их убрать. Для этого нужно хранить информацию, была ли у нас хоть одна значащая цифра? Пока её не было, мы должны пропускать нули (вместо 00001 написать просто 1), а вот нули вслед за значащей цифрой мы пропускать не должны: заменить 100 на 1 - это катастрофа. Хранить эту информацию будем в [SP+1] - не хочется засорять лишние регистры, и так мы слишком много заняли, наверняка придётся в дальнейшем их в начале процедуры сохранять в стеке, а потом восстанавливать.
Но затем нужно ещё рассмотреть "специальный случай", когда наше число - НОЛЬ. По описанной логике, программа так и возвратила бы пустую строку, а нужно - чтобы этот последний ноль всё-таки отобразился.
Все эти тонкости удлиняют нам программу на 6 строк. Любители языков высокого уровня плачут кровавыми слезами от происходящего в этом коде :)
BetterBin2Bcd proc k 4 ;номер обрабатываемого разряда [SP+1] -1 ;-1 означает, что значащих цифр ещё не было, 0-что появились @@start: i [SP+1] Y BCDtable @@sub: SUB [Y+k] i++ 0 JGE @@sub ADD [Y+k] iLOOP @@proceed ;прыжка не будет, если [SP+1]=-1 и текущий разряд нулевой kloop @@start [SP+1] 0 @@proceed: [SP] Acc ;сейчас аккумулятор пригодится... Acc '0' ADD i SUB [SP+1] UART0 Acc [SP+1] 0 Acc [SP] kloop @@start JMP [--SP] BetterBin2Bcd endp
Раньше у нас всегда i начинал работать с -1, и по окончании цикла с вычитанием "магической константы" i показывал текущую цифру. Дальше мы прибавляли ASCII-значение "нуля" - и получали правильный символ, чтобы отправить его в UART.
Теперь i начинает работать со значения из [SP+1], либо -1, либо 0. По окончании цикла с вычитанием, у нас стоит команда
iLOOP @@proceed
Если i не равно нулю, то будет вычитаться единичка и выполняться переход на метку @@proceed. В противном случае - перехода не будет.
Так бывает в одном-единственном случае: если [SP+1]=-1, а текущая цифра - ноль. Тогда мы выполняем следующую команду:
kloop @@start
если у нас ещё остались цифры, мы перейдём к следующей из них, "начисто проигнорировав" текущую, т.е ничего не выводя наружу. Так и надо. А вот если это уже была последняя цифра, то и этого перехода не будет совершено - мы положим в [SP+1] нолик - и выйдем всё на ту же метку @@proceed.
Там, как не сложно догадаться, мы всё-таки отображаем нашу цифру. Поскольку у нас i могла начинаться либо с -1, либо с 0, то дополнительная команда
SUB [SP+1]
подправляет это значение. Тут ещё учитывается, что если был совершён прыжок по iLOOP @@proceed, то из i была вычтена единичка.
И наконец, команда
[SP+1] 0
указывает, что значащая цифра у нас появилась - и больше нули пропускать нельзя!
Вот новая программа для проверки всего этого безобразия (процедуры не приводим, print не изменилась, а BetterBin2Bcd мы видели только что):
%include "Win1251.inc" .rodata OurString Int16 'С','т','е','п','е','н','и',' ','д','в','о','й','к','и',':',13,10,-32768 ZeroStr Int16 'Н','о','л','и','к',':' CRLF Int16 13,10,-32768 BCDtable dw 1,10,100,1000,10000 .data Stack dw ?,?,? .code main proc SP Stack X OurString CALL print X CRLF j 15 ACC 1 @@start: C Acc CALL BetterBin2Bcd ;очередное число CALL print ;перенос строки Acc C ADD Acc jLOOP @@start X ZeroStr CALL print Acc 0 CALL BetterBin2Bcd ;хотим проверить, "голый" ноль вообще отобразится? @@endless: JMP @@endless main endp
Вот что получается на эмуляторе:

Фурычит, однако, и нолик правильно отображает.
Ну и остаётся прошить это дело на ПЛИС. В этот раз задал скорость UART: 230 400 бод. При тактовой частоте в 4 МГц, это деление в 17 раз, погрешность 2%. Как показывает "картинка для привлечения внимания" - работает. В этот раз как-то вообще с пол-пинка зафурычило, меня это пугает...