【问题标题】:Extend numbers using EXT, AND. Also how ASR and LSL work (68000 assembly language)使用 EXT、AND 扩展数字。还有 ASR 和 LSL 如何工作(68000 汇编语言)
【发布时间】:2023-03-18 13:26:02
【问题描述】:

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

编写一个 68000 的汇编语言程序,将执行 5*X + 6*Y+ [Y/8] -> [D1.L],其中 x 是存储在最低字节中的无符号 8 位数 D0 和 Y 是一个 16 位有符号数,存储在 D1 的高 16 位中。忽略其余部分 年/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 位?

感谢您的帮助!

【问题讨论】:

  • 其中有 OCR 错误吗? M0VE 而不是 MOVE(零与大写-O)?而add.l D2, Dl 而不是 D1。 (lcase ell 与一个)。不过,感谢您没有发布文字图片。
  • 问题引用的问题不清楚何时应该将事物扩展到 32 位。这就像((int32_t)y)>>3 + ((int16_t)(5*(uint16_t)x + 6*y)),所以 5x+6y 结果在与 y/8 相加之前被截断为 16 位。
  • 另请注意,算术右移与有符号整数除法的语义不同(例如,在零附近的区域中)。见compiler output for both ways。所以这个答案是基于/ 的非 C 语义。不过,我不明白为什么它和结果在[] 中。

标签: assembly 68000


【解决方案1】:

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

向左/向右移动实际上是乘以/除以 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 10 右移 2 个位置 = 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 签名,但是在学习汇编语言时,你也应该始终争取第二种观点,即“它实际上做了什么”,并且两个都学。

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

所以你有 8 位无符号数 X,你希望它添加到 16 位数 tempY,有什么问题?
如果你只添加 tempY_low8b + X,你会得到正确的 tempY_low8b,但如果 low8b 添加溢出,则 tempY_high8b 可能会丢失 +1。

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

在位方面扩展 X 意味着将 X 复制到能够容纳 16+ 位的东西,并将高 8 位重置为零(无符号 8b 值将使所有扩展位为零,扩展时,从二进制如何“显而易见”数字工作)。因为您在D0 中有 X,它有 16 位以上,所以您不需要复制,只需清除高位即可。这就是AND 的好处,通过不在“掩码”值中提供特定位来清除特定位,因此当您执行 AND X,mask 时,结果中的每一位(掩码为 0 位)也将变为零。 X 的其他位(其中掩码为 1)将保持其值。所以ANDI.W #$OOFF,DO 正在这样做,用掩码0000 0000 1111 1111 屏蔽D0 的16b,清除上8b,保留下8b。

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

现在 Y 是有符号数,所以扩展这个比较棘手。如果 Y 的最高位设置为 0,则表示该数为正数,您可以通过在其前面添加零来扩展它。但是如果最高位为1,则为负数,将相同的负值扩展到更多位就是在其前面添加一个。
0010 = +2 in signed 4b -&gt; extended to 8b -&gt; 0000 0010 = +2 in signed 8b
1110 = -2 in signed 4b -&gt; extended to 8b -&gt; 1111 1110 = -2 in signed 8b

没有具有这种功能的基本位运算符,例如“将一些位复制到所有其他高位”,所以这就是为什么它有专门的指令EXT

如果您必须坚持基本的位操作,您可以通过首先将窄位向上移动来执行相同操作,以便在更宽版本中也让最高位在最高位位置,然后使用“算术”右移回原来的位置。算术右移将用最高位的副本填充新位。
0010 to 8b: shift left 4 = 0010 0000 -&gt; a.shift right 4 -&gt; 0000 0010
1110 to 8b: shift left 4 = 1110 0000 -&gt; a.shift right 4 -&gt; 1111 1110

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

因此,如果确实知道您将未签名的数字扩展为 AND 并由 EXT 签名,它会做,但如果您也了解内部发生的事情,您有时可以找出不同的可能性。

例如:

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

将放入D1 16b 无符号数,从D0 中的 8b 无符号数扩展而来。我会让你自己计算这个,作为练习。 :)

所以不要只是盲目地学习“那个和那个是怎么做的”,而要经常检查你是否完全理解了这些操作及其目的。这将使您能够更快地构建自己的解决方案。毕竟,CPU 可以进行的基本操作实际上很少。

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

  • 位操作:旋转、移位、AND、OR、XOR (EOR)、NOT 和复制(移动/加载/存储变体)(x86 测试)。
  • 几个算术部分,例如:NEG、ADD、SUB、MUL、DIV、INC、DEC(还有一些比较)
  • jump/call/ret 和其他流控制操作
  • 堆栈实用功能:push/pop/...(可以通过 move/add/sub 模拟)
  • 其他一切通常要么是对 CPU/平台的专门控制,要么是基础的专门/复杂变体,或者是诸如 sin/cos/ 之类的高等数学函数...

因此,如果您学习了前三组,并习惯于在位级别上考虑它们,那么在短暂了解其“不同”汇编程序的语法之后,您可以在任何其他 CPU 上取得相当大的进步。这些是相当普遍的。这在使用高级语言进行编程时也很有帮助,因此请更好地了解 CPU 的廉价本机操作以及更复杂的计算,以便您可以选择更简单的问题解决方案。

【讨论】:

  • 在我自己之后阅读它......我希望你也能流利地使用 dec hex bin 转换,否则有些数字可能看起来有点奇怪。如果不是,请先看一下二进制文件。一旦你理解了二进制,它就有了这个不错的好处,这组 4 位(称为“半字节”)恰好形成一个十六进制数字(你可以尝试,0000b = 0,而1111b 是 8+ 4+2+1 = 15 = F 六进制,所以 4 位覆盖所有从 0 到 15 (F))。
  • 谢谢!我想我终于明白了。只是要确定;如果我想扩展一个无符号数,我可以使用ANDI,但对于有符号数,我应该使用EXT,对吗?
  • @Jack:是的,使用AND 进行零扩展,使用EXT 进行符号扩展。这就是这段代码正在做的事情。如果在 m68k 中有专用的零扩展指令(可能是 16->32?),则 IDK。顺便说一句,@ped:muls 都产生 16 位结果(.Woperand-size)。我假设它们不会将 dest 寄存器的高位归零,但是自从我查看 m68k 以来已经有很长时间了。
  • @Jack 我想添加简短的段落为什么 AND 和 EXT,最后我写了一个小故事......我希望它有所帮助。 :D Peter:是的,我警告过我不知道 68000 汇编程序,这取决于 Jack 查看指令参考指南以了解它们在 68k 上的详细功能。
  • @Jack:例如为什么用“位”思考可能是值得的,你可能想研究这个:codereview.stackexchange.com/a/133840/110284
【解决方案2】:

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

首先,您可以更改方程式,以便在编码时更好地工作

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。您还可以通过重复加法乘(小),这就是上面的代码。

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

【讨论】:

  • "一个小的改动需要重新编码" 这就是为什么我们有编译器可以为你做这个优化,在乘法指令和移位/加法之间进行选择,具体取决于常数和调整选项。因此,您可以将其视为讨论编译器在各种情况下应该做什么,即在选择策略并权衡代码大小与性能时应该遵循哪些规则。
猜你喜欢
  • 1970-01-01
  • 2011-09-21
  • 1970-01-01
  • 1970-01-01
  • 2018-11-19
  • 1970-01-01
  • 1970-01-01
  • 2016-04-15
相关资源
最近更新 更多