我目前正在开发一个项目,而且我必须颠倒一个字节的顺序。我目前正在使用AVR Studio Mega32微控制器。
例如:
0000 0001 becomes 1000 0000
0001 0110 becomes 0110 1000
1101 1001 becomes 1001 1011
首先,我有这个:
ldi r20,0b00010110
反转字节的最简单方法是什么,以便r20变为01101000?
答案 0 :(得分:3)
这是一个片段 - 它是为GNU工具链(avr-gcc,binutils,avr-libc等)编写的 - 但它应该很容易适应:
static inline __attribute__ ((always_inline))
uint8_t avr_reverse_byte (uint8_t x)
{
x = ((x & 0x55) << 1) | ((x & 0xaa) >> 1);
x = ((x & 0x33) << 2) | ((x & 0xcc) >> 2);
/* x = ((x & 0x0f) << 4) | ((x & 0xf0) >> 4); */
__asm__ ("swap %0" : "=r" (x) : "0" (x)); /* swap nibbles. */
return x;
}
因此,除了使用swap
指令实现的最终hi-lo半字节交换外,对'C'代码的改进并不大。
答案 1 :(得分:2)
我刚才不能提供AVR代码。但一般的位反转技术如下:
abcd efgh p
badc fehg p = ((p and 0AAh) shr 1) or ((p shl 1) and 0AAh)
dcba hgfe p = ((p and 033h) shr 2) or ((p shl 2) and 033h)
hgfe dcba p = ((p and 00Fh) shr 4) or ((p shl 4) and 0F0h)
答案 2 :(得分:2)
另一个简单的方法是使用进位标志:
重复8x:
lsl r20 ; shift one bit into the carry flag
ror r0 ; rotate carry flag into result
(r20
中的输入,r0
中的输出,r20
的内容已被销毁;寄存器可以自由更改。)
这使用16个指令@ 2个字节,每个1个周期= 32个字节的程序存储器,16个周期在完全“展开”时反转一个字节。包装在一个循环中,代码大小可以减少,但执行时间会增加。
答案 3 :(得分:1)
针对字节的两半的4位(16项)查找表似乎是一个不错的折衷(就像@Aki在评论中指出的那样)。
AVR指令每个为2个字节,因此一个16字节的表与8个指令占用相同的空间。 (事实证明,速度或大小不值得,除非您可以将数组对齐256个字节以使索引比gcc便宜得多。)
可以使用每个字节的高低一半来打包LUT。但这会比在表大小(8字节= 4条指令)方面节省更多的钱(在索引的第4位上使用分支在屏蔽前有条件地进行SWAP)要花费更多的钱。
让我们比较一下AVR GCC的功能。在编译Brett的代码时,gcc4.6令人惊讶地错过了优化(实际上是提升为int
,而没有充分利用将结果截断为uint8_t的优势)。具有讽刺意味的是,它使用SWAP指令确实将x<<4 | x>>4
优化为旋转。 (AVR没有多计数轮换指令,正常轮换是随身携带。移位仅可用于每条指令一次计数。)
#include <stdint.h>
uint8_t reverse_byte_alu (uint8_t x)
{
uint8_t xeven = x & 0x55, xodd = x & 0xaa;
x = (xeven << 1) | (xodd >> 1); // swap adjacent bit pairs
xeven = x & 0x33, xodd = x & 0xcc;
x = (xeven << 2) | (xodd >> 2); // swap adjacent 2-bit chunks
x = ((x << 4) | (x >> 4)); // 4-bit rotate is recognized as SWAP
return x;
}
compiles into this asm with gcc4.6 -O3
(Godbolt compiler explorer) 。我没有看到任何错过的优化。
reverse_byte_alu:
mov r25,r24
andi r25,lo8(85)
lsl r25
andi r24,lo8(-86)
lsr r24
or r25,r24 # swap of adjacent bits done
mov r24,r25
andi r24,lo8(51)
lsl r24
lsl r24 # << 2
andi r25,lo8(-52)
lsr r25
lsr r25 # >> 2
or r24,r25 # swap pairs of bits done
swap r24 # swap nibbles
ret
16条指令,每条指令2个字节,全部1个周期。 (ret
除外)https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_instruction_list.html
因此,这比@JimmyB的答案稍好。@ JimmyB的答案需要16个单周期指令,而不包含ret
。 (但是可以将其汇总为一个小循环)。
数组索引在AVR中并不便宜。寻址方式的唯一选择是后递增或递减,无立即移位。因此必须将16位数组地址添加到4位半字节值中。如果您的数组位于地址空间的低256字节中,这可能会更便宜。或者,如果您的数组是256字节对齐的,则只需设置指针寄存器的高字节并将查找值放在低字节即可。 (gcc缺少此优化)。
利用gcc在16个字节的边界上对齐数组可使地址计算更便宜,但指令总数为18,其中一些是多周期指令。
__attribute__((aligned(16))) // makes indexing cheaper
static const uint8_t reverse_nibble[16] = {
0, 0b1000, 0b0100, 0b1100,
0b0010, 0b1010, 0b0110, 0b1110,
0b0001, 0b1001, 0b0101, 0b1101,
0b0011, 0b1011, 0b0111, 0b1111
};
uint8_t reverse_byte_nibble_LUT (uint8_t x) {
uint8_t hi = reverse_nibble[x>>4];
hi = ((hi << 4) | (hi >> 4)); // SWAP instead of SWAP+AND for just a shift
uint8_t lo = reverse_nibble[x & 0x0f];
return hi | lo;
}
编译为17条指令和LD is a 2-cycle instruction when accessing FLASH with a non-increment/decrement addressing mode。 (在某些CPU上,当不访问闪存或内部SRAM时为1个周期。)
# gcc4.6 output, not optimal
mov r26,r24
swap r26
andi r26,lo8(15)
ldi r27,lo8(0)
subi r26,lo8(-(reverse_nibble)) # AVR doesn't have add-immediate byte, only sub
sbci r27,hi8(-(reverse_nibble)) # X register = (x>>4) - (-LUT_base)
ld r25,X
swap r25
mov r30,r24
ldi r31,lo8(0)
andi r30,lo8(15)
andi r31,hi8(15) # missed opt, this is a double-redundant 0 & 0
subi r30,lo8(-(reverse_nibble))
sbci r31,hi8(-(reverse_nibble))
ld r24,Z
or r24,r25
ret
ldi r27, 0
/ sbci r27
可能是错过的优化。使用16字节的对齐表,我们无法进位高字节。我认为我们可以做到:
# generate r30 = x&0x0f
subi r30,lo8(-(reverse_nibble)) # ORI would work, too. no-op with 256-byte aligned table
ldi r31,hi8(reverse_nibble) # reuse this for hi and lo
因此,通过更好的索引可以在速度上领先一步,但是总大小(代码+表)肯定看起来更糟。
答案 4 :(得分:1)
稍作调整,便可以从注释中的代码片段中获得额外的性能。
当我们确保将LUT对齐到16字节边界时,可以通过异或生成地址。同样,我们可以通过索引对表进行XOR,从而允许对参数x
进行就地修改。我已经注释掉了GCC生成的不必要的指令。
__attribute__((aligned(16))) // makes indexing cheaper
static const uint8_t reverse_nibble_xor[16] = {
0 ^ 0, 1 ^ 0b1000, 2 ^ 0b0100, 3 ^ 0b1100,
4 ^ 0b0010, 5 ^ 0b1010, 6 ^ 0b0110, 7 ^ 0b1110,
8 ^ 0b0001, 9 ^ 0b1001, 10 ^ 0b0101, 11 ^ 0b1101,
12 ^ 0b0011, 13 ^ 0b1011, 14 ^ 0b0111, 15 ^ 0b1111
};
uint8_t reverse_ams(uint8_t x)
{
uint8_t *p = (uint8_t *)((uint16_t)reverse_nibble_xor ^ (x & 15));
x ^= p[0];
x = ((x << 4) | (x >> 4));
uint8_t *q = (uint8_t *)((uint16_t)reverse_nibble_xor ^ (x & 15));
x ^= q[0];
return x;
}
reverse_ams:
ldi r18,lo8(reverse_nibble)
// ldi r19,hi8(reverse_nibble)
ldi r31,hi8(reverse_nibble) // use r31 directly instead of r19
mov r30,r24
// ldi r31,lo8(0)
andi r30,lo8(15)
// andi r31,hi8(15)
eor r30,r18
// eor r31,r19
ld r25,Z
eor r25,r24
swap r25
mov r30,r25
// ldi r31,lo8(0)
andi r30,lo8(15)
// andi r31,hi8(15)
eor r30,r18
// eor r31,r19
ld r24,Z
eor r24,r25
ret