【问题标题】:ARM parity of low 8 bits of a register寄存器低 8 位的 ARM 奇偶校验
【发布时间】:2020-08-20 19:32:08
【问题描述】:

我发现汇编语言有一些困难,不幸的是,当我在谷歌搜索信息时,我找不到任何可以帮助我解决问题的东西。 我已经编写了这段代码,我正在寻求帮助,看看是否有办法让它更简单(如果可能的话)。另外,如果 cmets 有误,请告诉我。

        NAME main
        PUBLIC main
        SECTION .text: CODE (2)
        THUMB

main    
        LDR R4, =0x0097         ; R4 = 97 in hex 
        BL SUBROUTINE           ; Go to Subroutine

STOP    B STOP

SUBROUTINE
        MOV R1, #1              ; Initialize R1 to 1        
        MOV R2, #0              ; Initialize R2 to 0        
        MOV R0, #0              ; Initialize R0 to 0        
        PUSH {R4}               

LOOP
        CMP R0, #8              ; Bits counter
        BEQ DONE                ; Go to DONE R0 = 8
        ADD R0, R0, #1          ; Calculates the bits
        AND R3, R4, R1          ; Checks if R3 = R4
        CMP R3, #1              ; Comparing result with 1
        BEQ ONE                 ; Jump to ONE
        LSR R4, R4, #1          ; Right shift by 1
        B LOOP

ONE
        ADD R6, R6, #1          ; Saving #1 in R6
        LSR R4, R4, #1          ; Right shift by 1
        B LOOP

RETURN0
        MOV R2, #0              
        POP {R4}
        B STOP

RETURN1
        MOV R2, #1
        POP {R4}
        B STOP

DONE
        CMP R6, #2
        BEQ RETURN0
        CMP R6, #4
        BEQ RETURN0
        CMP R6, #6
        BEQ RETURN0
        CMP R8, #8
        BEQ RETURN0
        B RETURN1

        END

任务如下: 该子程序在寄存器 R4 中有一个输入参数并返回一个 寄存器 R2 中的值。该子程序将检查 8 个最小的奇偶校验 输入参数的有效位。如果奇偶校验为偶数,则值为 0 返回,如果奇偶校验为奇数,则返回值 1。偶校验意味着 的个数是偶数,而个数的奇数 很奇怪。

提前致谢

【问题讨论】:

  • 任何使您更难帮助您的事情都会导致您获得帮助的机会大大减少。具体来说,您应该将代码以文本形式包含在问题中,而不应以图像形式包含在外部网站上。
  • 鉴于您的代码似乎没有任何问题,您的帖子可能更适合 Code Review

标签: assembly arm thumb parity


【解决方案1】:

您的编程风格已经相当不错,并且您对代码进行了彻底的注释。这是非常有价值的,你应该继续做的事情。该算法本身似乎是正确的,并且以可接受的方式实现,尽管它可以更有效地完成。

假设您在 ARM 模式下编程,我一直在写这个答案。但是,许多建议也适用于 Thumb 模式。我想你不能使用 Thumb 2 指令。特定于拇指的建议以倾斜字体标注。

在编写高效的汇编代码时,最重要的是了解您正在编程的架构的指令集。您的代码是为 ARM 编写的,它有许多有用的指令和特性来加快速度。让我们从一些基本的改进开始。

首先,您使用此序列来隔离R4 的最低有效位,然后检查它是否为非零:

        ADD R0, R0, #1          ; Calculates the bits
        AND R3, R4, R1          ; Checks if R3 = R4
        CMP R3, #1              ; Comparing result with 1
        BEQ ONE                 ; Jump to ONE

这可以更有效地完成。首先,请注意,您可以在 AND 指令中使用立即数,因此无需为此在寄存器中保留 1:

        AND   R3, R4, #1

接下来,您可以告诉处理器直接从AND 指令的结果中设置标志,而不是按位比较AND#1 的结果。如果结果为零,这将设置零标志(可能还有其他一些标志,不要太在意),因此您可以立即对结果进行分支。

        ANDS  R3, R4, #1        ; check if least significant bit set in R4
        BNE   ONE               ; jump to ONE if it is

现在这个ANDS 完成了工作,但不必要地将其结果写入R3。我们在那里真的不需要它。快速查看指令集参考告诉我们TSTANDS 做同样的事情,但丢弃了结果,只设置了标志。这正是我们想要的。

        TST   R4, #1            ; check if least signficant bit set in R4
        BNE   ONE               ; jump to ONE if it is

现在我们可以做的下一件事就是摆脱那个条件分支。 ONE 分支中的代码之间的唯一区别是它增加了 R6。除了条件分支,我们可以简单地使用 ARM 的 条件执行 功能,仅在设置零标志时执行 ADD 指令:

        TST   R4, #1             ; check if least significant bit set in R4
        ADDNE R6, R6, #1         ; increment R6 if it is

这使代码更加高效!我们可以通过将TST 合并到LSR 指令中来进一步改进。看,如果我们告诉LSR 设置标志,它会将进位标志设置为移出的最后一位。这正是我们感兴趣的!所以我们可以这样做

        LSRS  R4, R4, #1         ; shift R4 to the right and set flags
        ADDCS R6, R6, #1         ; increment R6 if a 1 was shifted out

请注意,在条件执行不可用的其他架构上,您可以使用 add-with-carry 指令达到与ADDCS R6, R6, #1 类似的效果:

        ADC   R6, R6, #0         ; add 1 to R6 if carry is set

这也是我在拇指模式下会做的事情。由于拇指模式下没有立即操作数ADC,因此您必须将一个寄存器设置为零。

        MOVS  R1, #0
        ...
        LSRS  R4, R4, #1
        ADCS  R6, R1, #0         ; add carry to R6

除了设置进位标志外,LSRS 还会在结果为零时设置零标志。因此,如果我们简单地迭代直到R4 中的所有位都被移出,我们就可以取消循环计数器,从而为我们节省了一个寄存器和一堆指令。请注意,如果在R4 中设置了任何额外的位(除了您检查的至少 8 位),这可能不会产生正确的结果,因此您可能需要先用AND R4, R4, #0xff 屏蔽这些位。这是代码:

LOOP:   LSRS  R4, R4, #1         ; shift R4 to the right and set flags
        ADDCS R6, R6, #1         ; increment R6 if a 1 was shifted out
        BNE   LOOP               ; loop until R4 is 0.

很遗憾,所有拇指指令都设置了标志,因此您无法进行此优化。

您可以类似地优化DONE 部分中的代码:本质上,您只需检查R6 是偶数还是奇数,如果是奇数则返回1,如果是偶数则返回0。您可以用单个测试替换整个级联跳转:

        TST   R6, #1             ; set the zero flag if R6 is even
        BEQ   RETURN0            ; return 0 if even
        B     RETURN1            ; otherwise return 1

但是,意识到这与返回R6 的最低有效位基本相同,因此您可以将整个代码替换为

        AND   R0, R6, #1         ; set R0 to 1 if R6 is odd, 0 if R6 is even
        POP   {R4}
        B     STOP

这有点短,不是吗?

在拇指代码中,使用一些聪明的想法可以实现类似的性能。请注意,我们只关心R6 的最低有效位,而丢弃高位无关紧要。因此我们可以写

        MOVS R0, #0              ; parity accumulator
        SUBS R1, R0, #2          ; mask (clear in bit 0, 1 everywhere else)
LOOP:   LSRS R4, R4, #1          ; shift out one bit from R4 and set flags
        ADCS R0, R0, R1          ; add that bit to R0
        CMP  R4, #0              ; are we done?
        BNE  LOOP                ; loop until we are
        BICS R0, R1              ; isolate parity

然后可以在R0 中找到结果。

现在进行一些算法改进:您的代码可以解决问题,但它确实很慢,因为它对每个数字进行一次迭代。一种更快的方法是使用XOR 指令将位压缩在一起。这使我们可以只用 3 步而不是像您的代码那样计算 8 步来计算奇偶校验:

        LSR   R3, R6, #4        ; keep a copy of R6 shifted by 4 places
        EOR   R6, R6, R3        ; and xor it into R6
        LSR   R3, R6, #2
        EOR   R6, R6, R3        ; same but shifted by 2 places
        LSR   R3, R6, #1
        EOR   R6, R6, R3        ; same but shifted by 1 place
        AND   R0, R6, #1        ; isolate parity

相同的代码可以在拇指模式下编写,但您可能需要在两者之间移动一些额外的数据。

这可以使用移位操作数进一步改进,这是另一个 ARM 特有的功能:

        EOR   R6, R6, R6, LSR #4 ; xor R6 with R6 shifted right 4 places
        EOR   R6, R6, R6, LSR #2 ; xor R6 with R6 shifted right 2 places
        EOR   R6, R6, R6, LSR #1 ; xor R6 with R6 shifted right 1 place
        AND   R0, R6, #1         ; isolate parity

这是通常最快的方法,不使用任何指令集扩展。如果您有足够先进的处理器,您可以使用CNT 指令一步计算位数,但无论如何这并不值得。

【讨论】:

  • @TomášChabada:这种将高半部分降低并合并的模式适用于任何关联缩减,以 log2(width) 步长连续缩小到 1 个元素。例如向量元素的 SIMD 水平和 / 乘积 / 最小值 / 最大值 / AND / OR / XOR。在这种情况下,单个寄存器中的位异或,因为按位运算没有任何干扰相邻位的进位。但是,是的,这是一个很好的技巧,在 ARM 上甚至更酷,它可以作为另一条指令的一部分进行转换。
  • 这是拇指而不是手臂,我们可以称其为谓词和吗? (来自今天的其他问题)。与 eors 一样,尽管如此,但必须查找这些内容……但是 arm 的答案很棒。可能只是将其保留为 if arm then else if thumb then 并显示在那里可以做什么,两个答案合二为一...
  • 啊,如果要求被正确复制,家庭作业会定义 r4,因此 r4/r5 可能在另一个问题中......
  • @old_timer 哦...我完全错过了这是一个拇指问题。将不得不修改答案来解决这个问题。
  • 我会认真地做,如果这是手臂,这就是你可以做的事情,这是你可以做的事情,这是你被限制在做的事情......不要在这里失去伟大的工作。
【解决方案2】:

下次使用 CODE(编辑器中的大括号)代替 printscreen(例如,您不能从 prtscn 复制粘贴)。 我从未使用过 ARM 汇编语言,但我会使用这种方法:

  1. AND-out 输入的前 24 位(如果您不确定它们是否始终为 0)
  2. 将输入移动到任何 GPR(比如 R5)
  3. 将 R5 移动到任何其他 GPR(比如 R6)中
  4. 与除最低有效位之外的 R6 的所有位
  5. 测试 R6 是否为零(如果不是零,则增加一些计数器 (GPR))
  6. R5 逻辑右移
  7. 转到 3。(重复八次)
  8. 您的计数器中有多个 1

这将是我的方法。不过,我不确定它是否是最好的。它应该更简单。如果 ARM 有任何方法可以通过进位进行轮换,那就更容易了(如果进位位为零,您将跳过计数器的递增)。

【讨论】:

  • 通过进位循环可通过RRX 指令/移位修饰符获得。
  • 很高兴知道(正如我已经说过的,我没有使用 ARM 的经验)。然后很容易逻辑右移 - 如果不进行计数器增量,则跳转。我也完全同意你的观点,ASM 编程需要相当深入的架构知识。
  • 我已经在我的问题中解决了这个方向的一系列方法。我认为您不需要RRX,因为旋转进位的特点是能够旋转进位标志,这不是您在这里需要的。旋转/移出进位标志是所有移位和旋转在 ARM 上所做的事情(如果您要求设置标志),因此可以轻松实现。 ARM 具有指令集为您提供的所有灵活性,非常有趣。
  • 如果您想使用简单的计数循环一次一位地将奇偶校验实现为popcount(x) & 1,您可以右移以将一位从寄存器移动到进位标志,然后adc r6, 0将 CF 添加到另一个寄存器中。有趣的事实:x86 有一个根据每个结果的低 8 位更新的奇偶校验标志,因此 test al, al / setp al 可以在 x86 上的 2 条指令中为您提供奇偶校验。但 ARM 没有。
猜你喜欢
  • 2015-06-29
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-06
相关资源
最近更新 更多