使用 EXT、AND 扩展数字。还有 ASR 和 LSL 是如何工作的(68000 汇编语言)

Extend numbers using EXT, AND. Also how ASR and LSL work (68000 assembly language)

我正在学习汇编语言(针对 68000 微处理器),我遇到了以下问题:

Write a 68000 assembly language program that will perform 5*X + 6*Y+ [Y/8] -> [D1.L], where x is an unsigned 8-bit number stored in the lowest byte of D0 and Y is a 16-bit signed number stored in the upper 16 bits of D1. Neglect the remainder of 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 位?

感谢您的帮助!

我不知道 68000 asm,但我想 ANDI 会与立即数 AND 运算,所以 result_16b = 0x00FF & X_8b; 这将确保结果的高 8 位为零,低 8 位包含 X 值。

移位 left/right 实际上是 multiplying/dividing 2 的幂。

考虑二进制 8b 中的值 10
0000 1010 (8+2 = 10)
0001 0100 左移 1 个位置后 = 16+4 = 20 (10*2)
0000 0101 10 右移 1 个位置后 = 4+1 = 5 (10/2)
0000 0010 向右移动 2 个位置后的 10 = 2(截断 10/4)

所以在某些 CPU 上执行 X*5 可能会更快(比使用 MUL):

x4 = (x<<2); // copy of x shifted left twice (*4)
x = x + x4;  // sum of original plus copy

这个原理是一样的,我们在小数中做*10和/10只是左右移动数字。像 45 向左移动 2 个位置是 45*100 = 4500。而 45 向右移动 1 个位置是 4(45/10 截断)。

他们可能将最后的指令扩展到 32b,因为结果应该是 D1.L?我认为之前的 MUL 可能会导致 32b 结果值?查看说明参考了解详情。

但是我对问题描述中的那些 [] 感到困惑,在其他汇编器中 [x] 的用法通常表示使用 "x" 中的值作为从中获取值的地址内存,而不是直接使用值 x。但是在你的问题描述中并没有多大意义,所以它可能只是关于价值观......


关于 AND 和 EXT。是的,这是一种看待它的方式,记住你用 AND 扩展无符号数,用 EXT 签名,但是在学习汇编语言时,你应该始终争取第二个视角,即 "what it actually does",并学习两者。

它所做的大部分时间在位级别上都很重要。

所以你有 8 位的无符号数 X,你想把它加到 16 位的数 tempY 上,有什么问题?
如果你只是添加 tempY_low8b + X,你会得到正确的 tempY_low8b,但是 tempY_high8b 可能缺少 +1,如果 low8b 添加溢出。

如何解决?您可以将 X 值扩展到 16b,或者检测溢出并修复 tempY_high8b。在 68k 上,我认为操作 tempY 的高 8b 是非常不切实际的(在 x86 上它可能归结为简单的 ADC ah,0,如果 ax = ah:al 寄存器用作 tempY),因此扩展十.

根据位扩展 X 意味着将 X 复制到能够容纳 16+ 位的东西,并将高 8 位重置为零(无符号 8b 值将使所有扩展位为零,扩展时,"obvious" 来自二进制数的工作原理)。由于你在D0中有X,它有16+位,你不需要复制,只需清除高位即可。这就是 AND 的好处,通过不在 "mask" 值中提供它来清除特定位,所以当你执行 AND X,mask 时,结果中的每一位,其中 mask 有 0 位,将成为也为零。 X 的其他位(其中 mask 为 1)将保持其值。所以 ANDI.W #$OOFF,DO 正在这样做,用掩码 0000 0000 1111 1111 屏蔽 D0 的 16b,清除上部 8b,保留下部 8b。

如果您考虑汇编中的某些问题,并且意识到需要将位 i、j 和 k 重置为零,您现在可以回想起有一个 AND 能够做到那(使用适当的掩码,将 i、j 和 k 归零)。

现在Y是有符号数,所以扩展这个比较棘手。如果 Y 的最高位设置为 0,则表示该数为正数,可以通过在其前面加零来扩展它。但是如果最高位为1,则为负数,将相同的负值扩展到更多位就是在其前面加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

没有基本的位运算符具有这样的功能,至于"copy some bit up to the all other upper bits",所以这里有专门的指令EXT

如果你必须坚持基本的位操作,你可以通过先向上移动窄位来实现同样的效果,让最高位在更宽版本的最高位位置,然后使用 "arithmetic"右移回到原来的位置。算术右移将用最高位的副本填充新位。
0010 to 8b: shift left 4 = 0010 0000 -> a.shift right 4 -> 0000 0010
1110 to 8b: shift left 4 = 1110 0000 -> a.shift right 4 -> 1111 1110

这就是为什么大多数处理器都有两种右移指令变体,一种是将 0 放入新位,另一种是保留最高位。左移,虽然一些 CPUs 确实有两个变体,但在两者中都是相同的,将较低的位设置为 0.

所以如果你知道你用 AND 扩展无符号数并用 EXT 签名,它就可以了,但如果你还了解内部发生的事情,你有时可以想出出不同的可能性。

例如:

    EOR.W   D1,D1
    MOVE.B  D0,D1

将从D0中的8b无符号扩展为D116b无符号数。作为练习,我会让你自己想出这个。 :)

所以不要盲目学习"how is done that and that",而是要经常检查你是否完全理解这些操作及其目的。这将使您能够更快地构建自己的解决方案。毕竟,CPU.

的基本操作实际上很少

通常所有 CPU 都有(不总是所有这些,但从基本的开始就足以模拟其余部分):

  • 位操作:旋转、移位、AND、OR、XOR (EOR)、NOT 和复制(move/load/store 变体)(x86 测试)。
  • 一些算术部分,如:NEG、ADD、SUB、MUL、DIV、INC、DEC(有些也比较)
  • jump/call/ret 和其他流量控制操作
  • 堆栈效用函数:push/pop/...(可以被move/add/sub模拟)
  • 其他一切通常是 CPU/platform 的专门控制,或 specialized/complex 基础知识的变体,或更高的数学函数,如 sin/cos/...

因此,如果您学习了前三组,并习惯于在位级别上思考它们,那么在短暂学习它的语法后,您可以在任何其他 CPU 上走得更远 [=113] =] 汇编程序。这些是很普遍的。这在使用高级语言编程时也有帮助,因此更好地了解什么是 CPU 的廉价本机操作,什么是更复杂的计算,因此您可以选择更简单的问题解决方案。

原文post中的答案非常低效。应尽可能避免使用 MULU 和 DIV 指令,因为它们分别需要大约 70/140 个周期,而 "normal" 指令时间为 4 到 12 个周期。

首先,您可以更改方程式,使其在编码时效果更好

5 * X + 6 * Y = (X + Y) * 4 + X + 2 * Y
              = (X + Y) << 2 + X + Y + Y

         ORG 00
         ; 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 执行有符号扩展,因为值的最高位本质上是 postive 或负数的指示符。清除值的最高位执行无符号扩展;在“binary/two's complement”中,正值的最高位为零。在一个值上使用 AND 将所有不属于 'MASK' 的位设置为零,因此可用于清除位。

每次将一个值移动 1 时,您就是在乘(左)或除(右)2。您也可以通过重复加法来乘(小),这就是上面的代码所做的。

编写如上所述的自定义代码存在缺点,因为它是针对特定问题的优化解决方案,微小的更改将需要重新编码。