我正在学习汇编语言(对于68000微处理器),我遇到了以下问题:
编写一个68000汇编语言程序,执行5 * X. + 6 * Y + [Y / 8] - > [D1.L],其中x是存储在最低字节中的无符号8位数 D0和Y是存储在D1的高16位中的16位有符号数。忽略其余部分 Y / 8。
这就是解决方案:
ANDI.W #$OOFF,DO ;CONVERT X TO UNSIGNED 16-BIT
MULU.W #5,DO ;COMPUTE UNSIGNED 5*X IN D0.L
SWAP.W D1 ;MOVE Y TO LOW 16 BITS IN D1
M0VE.W D1 ,D2 ;SAVE Y TO LOW 16 BITS OF D2
MULS.W #6,D1 ;COMPUTE SIGNED 6*Y IN D1.L
ADD.L DO,D1 ;ADD 5*X WITH 6*Y
EXT.L D2 ;SIGN EXTEND Y TO 32 BITS
ASR.L #3,D2 ;PERFORM Y/8;DISCARD REMAINDER
ADD.L D2,Dl ;PERFORM 5*X+6*Y +Y/8
FINISH JMP FINISH
但是,我不了解第一行。我想我们必须将其转换为16位,因为Y是一个16位有符号数,但我不知道ANDI
指令在这种情况下是如何工作的。
此外,在示例中,他们使用ASR.L #3,D2
将D2除以8;所以,如果我使用ASR.L #2,D2
,那么我将D2除以4?或者它是如何工作的?
如果我使用LSL.L #3,D2
,那么我会将D2乘以8?
最后,他们为什么签名将Y扩展到32位?
感谢您的帮助!
答案 0 :(得分:2)
我不知道68000 asm,但是我认为ANDI会立即与AND,所以result_16b = 0x00FF & X_8b;
这将确保结果的高8位为零,低8位包含X值。 / p>
向左/向右移动实际上乘以/除以2的幂。
考虑二进制8b中的值10
:
0000 1010
(8 + 2 = 10)
{1}}向左移位1位= 16 + 4 = 20(10 * 2)
0001 0100
10向右移动1个位置= 4 + 1 = 5(10/2)
0000 0101
10后向右移2个位置= 2(10/4截断)
所以在某些CPU上做X * 5可能会更快(比使用0000 0010
):
MUL
它的原理是相同的,因为我们只需左右移动数字就可以得到* 10和/ 10的十进制数。如同45左移2个位置是45 * 100 = 4500.而45向右移动1个位置是4(45/10截断)。
他们可能会将最后的指令扩展到32b,因为结果应该是D1.L?我认为之前的MUL可能导致32b结果值?检查指令参考以获取详细信息。
但是我对问题描述中的那些x4 = (x<<2); // copy of x shifted left twice (*4)
x = x + x4; // sum of original plus copy
感到困惑,在其他汇编程序中[]
的使用通常表示&#34; x&#34;中的值的使用。作为从内存中获取值的地址,而不是直接使用值x。但是在你的问题描述中它没有多大意义,所以它可能只是关于价值......
关于AND和EXT。是的,这是查看它的一种方式,记住你用AND扩展无符号数,并用EXT签名,但是在学习汇编语言时,你应该总是努力争取第二个视角,即&#34;它实际上做了什么&#34;,并学习它们。
它的作用是在比特级上重要的大部分时间。
所以你有8位无符号数X,你想要它加到16位数tempY,什么是捕获?
如果你只是添加tempY_low8b + X,你会得到正确的tempY_low8b,但是如果low8b加上溢出,tempY_high8b可能会丢失+1。
如何解决这个问题?您可以将X值扩展为16b,也可以检测溢出并修复tempY_high8b。在68k上我认为操纵高8b的tempY是非常不切实际的(在x86上它可以归结为简单的[x]
,如果ADC ah,0
寄存器用作tempY),那么它就是这样的更容易扩展X.
以位为单位扩展X意味着将X复制到能够容纳16位以上的位,并将高位8位复位为零(无符号8b值将使所有扩展位为零,扩展时为&#34;显而易见&# 34;二进制数如何工作)。由于你在ax = ah:al
中有X,它有16位以上,你不需要复制,只需清除高位。这是D0
有利于什么,通过不在&#34;掩码中提供它来清除特定位置&#34;值,所以当你做AND X,掩码时,结果中的每个位,掩码有0位,也将变为零。 X的其他位(掩码有1)将保持其值。所以AND
正在这样做,用掩码ANDI.W #$OOFF,DO
掩盖D0
16b,清除高8b,保持低8b。
如果您考虑装配中的某些问题,并且您已经意识到需要将位i,j和k重置为零,那么您现在可以回想起那里有0000 0000 1111 1111
个单一AND
做到这一点(使用适当的掩码,i,j和k归零)。
现在Y是签名号码,所以扩展这个号码更加棘手。如果Y的最高位设置为0,则表示该数字为正数,您可以通过在其前面添加零来扩展它。但是如果最高位为1,则为负,并且将相同的负值扩展到更多位是在其前面添加一个。
0010 = +2 in signed 4b -> extended to 8b -> 0000 0010 = +2 in signed 8b
1110 = -2 in signed 4b -> extended to 8b -> 1111 1110 = -2 in signed 8b
没有具有这种功能的基本位操作符,以及&#34;将一些位复制到所有其他高位&#34;,这就是为什么这有专门指令的原因{ {1}}。
如果您必须坚持基本的位操作,您可以通过首先将窄位移位,在更宽的版本中将最高位置于最高位位置来执行相同操作,然后使用&#34;算法&#34 ;向右移回原来的位置。算术右移将使用最高位的副本填充新位
EXT
0010 to 8b: shift left 4 = 0010 0000 -> a.shift right 4 -> 0000 0010
这就是为什么大多数处理器都有两种移位位右指令变体,一种是将0置为新位,另一种是保持最高位。左移,有些CPU确实有两个变量,两者都相同,将低位设置为0.
因此,如果您确实知道您将无符号数字延长1110 to 8b: shift left 4 = 1110 0000 -> a.shift right 4 -> 1111 1110
并由AND
签名,那么它就可以了,但如果您还了解内部发生了什么,那么您可以有时会找出不同的可能性。
例如:
EXT
将 EOR.W D1,D1
MOVE.B D0,D1
16b无符号数放入D1
中的8b无符号数。我会让你自己想一个这个,作为练习。 :)
所以不要盲目地学习&#34;如何做到这一点和那个&#34;而是总是检查你完全理解操作及其目的。这样您就可以更快地构建自己的解决方案。毕竟,CPU实际上很少有基本操作。
通常所有CPU都有(并非总是所有这些,但从基本的那些足以模拟其余部分):
因此,如果你学习了前三组,并习惯于在位级别上考虑它们,那么在对它的语法进行简短的学习之后,你可以在任何其他CPU上得到很大的帮助。&#34;不同的& #34;汇编。这些是非常普遍的。这也有助于在更高级语言编程时,因此更好地了解什么是CPU的廉价本机操作,以及什么是更复杂的计算,因此您可以选择更简单的问题解决方案。
答案 1 :(得分:1)
原始帖子中的答案效率很低。与“正常”的4到12个周期的指令时间相比,应尽可能避免使用MULU和DIV指令,因为它们分别花费70/140个周期。
首先,您可以更改公式,以便在编码时更好地工作
5 * X + 6 * Y = (X + Y) * 4 + X + 2 * Y
= (X + Y) << 2 + X + Y + Y
ORG $1000
; Compute 5*X + 6*Y + [Y/8] -> [D1.L],
; X = D0.B (unsigned). Y = top 16 bits D1 (signed)
START
MOVEQ #0,D2 ; MOVEQ faster than CLR
MOVE.B D0,D2 ; D2 now 32 bit X
SWAP D1 ; move Y to bottom 16 bits
EXT.L D1 ; extend to long word.
MOVE.L D1,D3 ;
ADD.L D2,D3 ; add X
LSL.L #2,D3 ; multiply by 4 = 4(X+Y)
ADD.L D2,D3 ; add another X
ADD.L D1,D3
ADD.L D1,D3 ; add another 2 Y
ASR.L #3,D1 ; Y/8
ADD.L D3,D1 ; add (5X+6Y)
FINISH
END START
要回答您的问题:
EXT执行带符号的扩展,因为值的高位本质上是指示它是正还是负的指示符。清除值的高位将执行无符号扩展。在“二进制/二进制补码”中,正值的高位为零。在值上使用AND会将不属于“ MASK”的所有位都设置为零,因此可以用来清除这些位。
每次将值移位1时,即乘以(左)或除以(右)2。也可以通过重复加法乘(小),这就是上面的代码。
编写这样的自定义代码有一些缺点,因为它是针对特定问题的优化解决方案,并且进行较小的更改将需要重新编码。