【问题标题】:Reversing bits in a byte with AVR使用 AVR 反转字节中的位
【发布时间】:2020-04-13 00:11:13
【问题描述】:

我目前正在处理一个问题,希望我构建一个子程序来反转 R16 中的位。

00000011 => 11000000
or
10101000 => 00010101

对于我们使用 AVR 子集的类,子例程需要在 norfair 中工作。

这是我目前所拥有的,任何帮助将不胜感激!

ldi r16,3 ;00000011

【问题讨论】:

  • 你可以使用查找表吗?例如对于 4 位的一半。在没有位反转指令的 ISA 上,这通常是最快的。
  • 我不相信这么伤心
  • 你对要做什么算法有任何想法吗?对于初学者怎么样,我建议您右移 R16,找出从它的末尾移出的位,然后左移一个临时寄存器并将该位放在最不重要的位置。你能看到之后要做什么来完成这个程序吗?
  • 这是反转字节中的位,而不是反转字节
  • 糟糕。让我改一下标题

标签: assembly bit-manipulation avr atmega


【解决方案1】:

天真的解决方案是使用移位运算符遍历位并检查。但请注意,AVR 没有barrel shifter,因此它只能移动 1,any other shift counts need more than 1 instruction。这是来自著名的 bithacks 页面的obvious solution

uint8_t reverse_obvious(uint8_t v)
{
    uint8_t r = v & 1; // r will be reversed bits of v; first get LSB of v
    uint8_t s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end

    for (v >>= 1; v; v >>= 1)
    {   
        r <<= 1;
        r |= v & 1;
        s--;
    }
    r <<= s; // shift when v's highest bits are zero
    return r;
}

上面的 sn-p 除了最后一个需要在 AVR 中循环的 r &lt;&lt;= s 外,只使用了 1 的移位。您可以通过始终运行 8 个循环来避免这种情况

uint8_t reverse(uint8_t x)
{
    uint8_t mask_up = 0x01;
    uint8_t mask_down = 0x80;
    uint8_t result = 0;
    for (; mask_down; mask_down >>= 1, mask_up <<= 1)
    {
        if (x & mask_up)
            result |= mask_down;
    }
    return result;
}

另一种方法是 2 移位,但我想这是没有查找表的最佳方法。 AVR 有大量可用的 ROM 表,所以这样应该会更有效率

uint8_t reverse(uint8_t x)
{
    x = (((x & 0xaaU) >> 1) | ((x & 0x55U) << 1));
    x = (((x & 0xccU) >> 2) | ((x & 0x33U) << 2));
    x = (((x & 0xf0U) >> 4) | ((x & 0x0fU) << 4));
    return x;
}

一些编译器还具有反转位的内置函数。例如 Clang 有 __builtin_bitreverse8() 和 GCC 有 __builtin_avr_insert_bits() 可用于反转位:

// reverse the bit order of bits
__builtin_avr_insert_bits (0x01234567, bits, 0)

不幸的是their outputs are terrible


还有很多关于反转位的问题都有很好的答案。尝试将 C 代码转换为汇编代码并与 result on compiler explorer 进行比较

【讨论】:

  • 请注意,AVR 移位指令一次只能移动 1 位。因此,整个字节的 256 字节查找表会很好,但半字节半的 16 字节 LUT 需要移位才能使用。我想对于低半和高半的 2x 16 字节表,您只需要 4 次移位、2 次加载和一个 OR 即可使用,这比 16 次操作 + 循环开销来执行 8x lsr 到 CF + 8x ADC 相同,同样是 ROL reg。但这意味着使用 (x &amp; 0xf0U) &gt;&gt; 4 等的版本在 AVR 上可能很糟糕。
  • IDK,如果您可以说服编译器生成移入 CF 并移回另一个寄存器的代码。但这显然至少是在移位之间使用 1 和 OR 到另一个寄存器中实际执行 AND 的两倍。如果还需要 MOV 来复制 reg,则可能是 3 倍。
  • @PeterCordes AVR 有一个swap 指令,因此它可以用作快速移位4,如果您打开my other answer gcc.godbolt.org/z/TzKr53 中的godbolt 链接,编译器就会这样做。对于上面的第一个解决方案,编译器实际上并不使用 CF,而是使用一对同样快的 SBRC/ORI
  • 它在每个 SBRC/ORI 之前/之后使用 mov,但对于带有 mask_downup gcc.godbolt.org/z/rn7g2o 的那个。这可能是一个错过的优化;我还没弄清楚它在做什么。理想的情况是 mov r18, r24 然后 8x lsr r18 / rol r24 / ret 来实现该功能。感谢您指出swap;对此有指导是有意义的。
【解决方案2】:

AVR 具有swap 指令,允许交换寄存器中的 4 位半字节。您可以使用它来缩短代码。

mov r17, r16
ror r16  // rotate right, pushing bit 0 out to C flag
rol r17  // rotate left, moving C flag into bit 0 and pushing bit 7 out to C flag
ror r16
rol r17
ror r16
rol r17
ror r16
rol r17
ror r16
andi r16, 0xF0 // isolating bit 4 to 7
andi r17, 0x0F // isolating bit 0 to 3
or r16, r17    // combining
swap r16       // swapping the nibbles

【讨论】:

  • 更难理解,但似乎有效。也赢得 3 个周期(14 个而不是 17 个)。
【解决方案3】:

您可以使用rolror 操作码(通过进位左右旋转)来做到这一点。

ldi  r16,0b10101000   // value to reverse
push r17
rol  r16        // this push r16 msb bit in carry in left direction
ror  r17        // this push carry to r17 in right direction
rol  r16
ror  r17
rol  r16
ror  r17
rol  r16
ror  r17
rol  r16
ror  r17
rol  r16
ror  r17
rol  r16
ror  r17
rol  r16
ror  r17       // now r17 is reversed value
mov  r16,r17   // now r16 is reversed value
pop  r17

** 未经测试,但应该可以工作 ;-) **

你也可以通过循环来改进它!

【讨论】:

    猜你喜欢
    • 2014-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-21
    • 2017-08-09
    • 2011-06-22
    • 2021-09-24
    • 1970-01-01
    相关资源
    最近更新 更多