我正在研究摩托罗拉 68000 CPU 的组装。我使用的书是:
68000 Assembly Language Programming, Second Edition, Leventhal, Hawkins, Kane, Cramer
和EASy68k模拟器。
我有几个关于将二进制数转换为 BCD(二进制编码十进制)的问题。
书中原题说:“将内存位置 6000 的变量 NUMBER 的内容转换为位置 6002 的变量 STRING 中的四个 BCD 数字(6002 中的最高有效位)。NUMBER 中的 16 位数字是无符号的且小于超过一万。”
例子:
input: NUMBER - (6000) = 1C52
output: STRING - (6002) = 07
(6003) = 02
(6004) = 05
(6005) = 00
因为 1C52(十六进制)= 7250(十进制)(MC68k 是大端 CPU)
由于 MC68k 是一款出色的 CISC CPU,具有丰富的指令库,因此编写解决方案并不难:
DATA: EQU $6000
PROGRAM: EQU $4000
ORG DATA
NUMBER: DS.W 1
STRING: DS.L 1
ORG PROGRAM
MAIN:
CLR.L D0 ; Clear D0
MOVE.W NUMBER, D0 ; Store our number (2 bytes) to D0
MOVEA.L #STRING+4, A0 ; We'll go backwards -> so we store the address of the last byte + 1 of the variable STRING to A0 (+1 because we use pre-decrement addressing)
MOVEQ #1, D2 ; A counter which will cause (DBRA) two iterations of the LOOP part of the program
MOVE.L #$FFFF,D3 ; D3 is a mask used to clear the 2 most significant bytes of D0 in each iteration of LOOP
LOOP: DIVU.W #10, D0 ; Divide D0 by 10 (the result will be saved in the first 2 bytes od D0, and the remainder (our BCD digit) in the second two (more significant) two bytes of D0
MOVE.L D0, D1 ; Make a copy of D0
SWAP D1 ; swap the first 16 bits of D0 with the second 16 bits of D0
MOVE.B D1,-(A0) ; Now the first 16 bits of D1 contain the remainder (our BCD digit) which we will save to address -(A0)
AND.L D3, D0 ; Use the mask to clear the second half (16 bits) of D0 so that the next DIVU instruction doesn't by mistake take the remainder as a part of the number which needs to be divided
DBRA D2, LOOP ; Decrement our counter D2 by 1 and go back to LOOP if D2 is not equal to -1
DIVU #10, D0 ; This (last) division by 10 will cause our most significant BCD decimal to be at the lower 16 bits of D0 while the second most significant BCD decimal will be the remainder of the DIVU instruction and therefore stored at the higher 16 bits of D0
MOVE.B D0, -2(A0) ; Save the most significant BCD digit
SWAP D0 ; swap lower and higher 16 bits of D0
MOVE.B D0, -(A0) ; Save second most significant BCD digit
MOVE.B #9, D0
TRAP #15
END MAIN
DIVU = DIVision 无符号
我对这个解决方案很满意,但我想知道/了解 MC68k 如何更详细地执行这种除法(计算结果和余数),让我解释一下。例如,如果我们想做相反的事情,即将 BCD 数转换为二进制数,我们可以使用以下算法:让我们取序列:'7'、'2'、'5'、'0' BCD 数字,其中“7”是最高有效数字,“0”是最低有效数字。如果我们想对这些数字进行十进制数,我们可以这样做(伪代码):
number = 0;
number = number * 10 + 7 = 0 * 10 + 7 = 0 + 7 = 7
number = number * 10 + 2 = 7 * 10 + 2 = 70 + 2 = 72
number = number * 10 + 5 = 72 * 10 + 5 = 720 + 5 = 725
number = number * 10 + 0 = 725 * 10 + 0 = 7250 + 0 = 7250
但当然,我们需要调整以 2 为底的数字的乘法。MC68k 或多或少提供了 2 种方法:
- 像“MULU #10, D1”这样的乘法助记符,它只会产生一个乘以 10 的数字
或者由简单指令组成的集合:
ADD.W D1, D1 ; D1 = D1 + D1 = 2x MOVE.W D1, D3 LSL.W #2, D3 ; D3 = 8x = (2x) * 4 ADD.W D3, D1 ; D1 = 10x = 2x + 8x
这会产生相同的结果(原始数字 x -> 10x)。ADD 指令的工作方式如下:
ADD D1, D2 = pseudo-code = D2 = D2 + D1
LSL指令是逻辑左移指令。而且我们知道,将一个数逻辑左移 1 位与将其乘以 2 的结果相同,将其左移 2 位与将数乘以 4 的结果相同。
所以,对于 BCD 到二进制的转换,我可以在我的算法中使用像 MULU 这样的乘法指令,而对于二进制到 BCD,我可以在我的算法中使用像 DIVU 这样的除法指令。
而且,对于 BCD 到二进制,我可以使用 ADD 和逻辑移位指令来模拟乘法,但是二进制到 BCD 的类似方法是什么?如何使用比 DIV 更简单的指令(如减法、加法、逻辑移位……)来模拟除法并计算商/余数?
我还在这里找到了一个有趣的二进制到 BCD 转换算法:
http ://www.eng.utah.edu/~nmcdonal/Tutorials/BCDTutorial/BCDConversion.html
但我不知道为什么会这样。为什么我们需要将 3(= 11 二进制)添加到包含大于或等于 5 的数字的每一列(= 4 位)?
我考虑过编写一个使用该算法的解决方案,但是:
- 在 3 个班次之后,我必须在每次班次后检查个数列是否包含大于 4 的数字 - 在 7 个班次之后,我必须检查个数和十个数列在每班后包含大于 4 的数字
- 在 11 班之后,我必须检查在每班之后,十和百列是否包含大于 4 的数字
- 在 15 班之后,我必须检查是否是, 十、百、千列在每班后包含大于 4 的数字
看起来CPU还有很多事情要做......