在68HC12上反转一个字节的位

9

我正在一门微处理器课程中,使用Freescale CodeWarrior中的汇编语言来编程68HCS12微控制器。本周我们的任务是反转一个字节,所以如果字节是00000001,则输出应为10000000,或00101011到11010100。我们必须使用汇编语言,并被告知可以使用旋转和移位(但不仅限于)来完成此任务。我真的不知道该从哪里开始。

9个回答

8
提示:如果你进行了一次移位操作,一个比特将被移出并且一个零(可能)会被移入。那个被移出的比特去哪里了?你需要将其移入目标寄存器或内存地址的另一端。
我相信25年前我可以在Z80机器代码中完成这个操作而不使用汇编程序 :)

2
有更加巧妙的方法:http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious(用C语言编写,但也可以用汇编语言实现...) - Spacedman
在汇编语言中,实际上比C更容易,因为你可以通过旋转进位来实现。将一个位移入进位标志然后执行类似于“adc same,same”的操作,将进位标志移动到寄存器底部。 - Peter Cordes

6

将两个寄存器视为位堆栈。如果您一次从一个寄存器移动一个位,会发生什么?


6
如果您可以承受256字节的额外代码大小,那么在68HCS12上反转一个字节的最有效方式可能是使用查找表。但我相信这不是您的教练所期望的。
对于“正常”的解决方案,请单独考虑数据位。旋转和移位允许您移动位。对于第一种解决方案,通过按位“与”操作来隔离八个位,将它们移动到目标位置(移位、旋转...),然后再次组合它们(使用按位“或”操作)。这不会是最有效或最简单的实现,但您应该首先集中精力获得正确的结果--优化可以等待。

好的,我已经想出了如何使用移位和旋转来完成它。然而,如果我们编写最有效率的代码,我们可以获得额外的学分。他不在乎我们如何做到这一点。老实说,我不知道如何制作查找表。在研究这个问题的答案之前,我有点读过相关资料,但我并没有真正理解如何实现它们。 - dohlfhauldhagen
假设你的程序在其生命周期内将进行无数次位反转。当您启动程序时,只需通过位旋转执行所有256种可能性,并将结果存储在连续的256个字节的内存中,从BASE开始。现在,每当您需要翻转寄存器的位时,只需查看(BASE + value)。那是一个查找表(LUT)。如果您可以预先计算它们,则甚至可以将LUT硬编码到汇编中作为256个常量的一块。然后就不需要初始化了。胜利。 - Spacedman
1
为了节省256字节表格的空间,您可以拥有一个16字节的表格,其中包含每次四个位(半字节)的值。然后算法将是“revval = revdigit [inval&0x0f] << 4 | revdigit [inval >> 4]”。如果我是一名教授,我会喜欢两个部分,其中一个移位在索引中,另一个移位在外部。 - Olof Forshell

4
当你进行右移操作时,最不重要的位会被放入进位标志中。
当您进行旋转操作时,进位标志用于填充结果的空出位(对于ROL是最低有效位,对于ROR则是最高有效位)。

3
例如,如果您在所有字节中有一个数字,最简单的方法是:
mov al, 10101110
mov ecx, 8

我们在循环中将8放入ECX。
mov ebx, 0 

在bl中,我们将得到结果,并创建ebx,只是为了更好地观察发生了什么。
loop1:
sal al, 1;           

现在的进位标志(carry flag)中,您拥有最左边的一位。
rcr bl, 1;           

现在您将“carry”中的内容添加到“bl”中

loop loop1

而这就是全部


3
首先,制定出你需要执行的算法。可以用伪代码、C语言、简明英语或图表等方式进行表达。一旦清除了这个概念障碍,实际实现就应该很简单。
你的CPU可能有指令,能让你移动和/或旋转一个寄存器,可能还包括进位标志作为一个额外的位。这些指令将非常有用。

1

这原本是一条评论,但我想算了!

为了节省空间,可以使用一个16字节的表格来存储每次四位(半字节)的值,以代替256字节的表格。然后算法如下:

revval=(revdigit[inval&0x0f]<<4)|
        revdigit[inval>>4];

如果我是一位教授,我肯定会喜欢其中一个移位在索引内部,另一个移位在外部的两个部分。


0

我也不得不为大学编写这个位反转程序(针对8位)。这是我的做法:

MOV AL, 10001011B ;set the value to test
MOV CL, 7
MOV DH, 1
MOV DL, 0

loop1: PUSH AX
AND AL, DH 
PUSH CX
MOV CL, DL
SHR AL, CL
POP CX
MOV BH, AL
SHL BH,CL
OR CH,BH
DEC CL
INC DL
SHL DH, 1
POP AX
CMP DL, 8
JE END
JMP LOOP1

END:

我没有注释它,所以这里是它的工作原理: DH 是一个在字节中移动的 1,第一次为 00000001;第二次为 00000010等等。当你用 AL 进行 AND 运算时,你会得到 0 或类似于 10010000 的结果,你必须将其向右移动,以获得 01。 然后,将其放入 BH,并将其移动到所需的位置,即对于字节 0,位置为 7;对于字节 1,位置为 6 等等。然后,进行 OR 运算得出我们的最终结果,并进行必要的 INC 和 DEC 操作。不要忘记有条件的跳转,并为下一个循环弹出 AX :) 结果将存储在 CH 中。

0
以下代码利用旋转和移位。我使用Intel x86语法,请参见右侧的说明:
    mov cx, 8           ; we will reverse the 8 bits contained in one byte
loop:                   ; while loop
    ror di              ; rotate `di` (containing value of the first argument of callee function) to the Right in a non-destructive manner
    adc ax, ax          ; shift `ax` left and add the carry, the carry is equal to 1 if one bit was rotated from 0b1 to MSB from previous operation
    dec cx              ; Decrement cx
    jnz short loop      ; Jump if cx register Not equal to Zero else end loop and return ax

我使用 dec 指令而不是 sub,因为它只需要一个字节,而 sub 需要三个字节。此外,编译器似乎总是通过选择 dec 来进行优化。

编辑:还要注意的是,rcl ax(3 字节)虽然等价于adc ax, 0(2 字节)后跟shl ax(2 字节),但效率更低。 请参见下面的评论,非常感谢 Peter Cordes 的见解。


你不需要使用缓慢的rcl指令,可以使用adc ax,ax。两者在16位模式下都是2个字节,而且adc指令在现代CPU上更快。你关于shl eax,1adc al,0的解释是错误的:正确的应该是shl ax,1adc ax,0,操作数大小与rcl相同,在加入进位之前进行移位。此外,dec cl是2个字节;也许你想到的是dec cx?单字节的inc/dec操作码只适用于16或32位操作数大小,而不是8位。(如果你真的为了速度而优化,你会使用缓慢的loop指令。但不要这样做) - Peter Cordes
你也可以使用ror di, 1代替shr来使它非破坏性。另外,你只设置了CX = 8,但DI和AX是16位寄存器。 - Peter Cordes
顺便说一下,我对你关于慢循环的评论感到困惑,能否详细说明一下?我还查了ror和shr https://dev59.com/iG445IYBdhLWcg3wOnrW,但是使它非破坏性有什么意义呢? - Antonin GAVREL
循环指令为什么慢?英特尔不能高效地实现它吗?关于非破坏性的问题:如果您在循环中使用 ror di,1 16 次,则 DI 的最终值将与初始值相同。但是,您的 shr 循环会使 DI=0(如果您进行了 16 次迭代)。因此,您可以选择哪个更有用:一个清零的寄存器还是原始值。 - Peter Cordes
好的,你说得对,很抱歉我错过了,已经修改了。另外关于循环指令,似乎根本不值得使用? - Antonin GAVREL
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接