Assembly 如何反转一个字节
我现在正在做一个项目,碰巧我不得不颠倒一个字节的顺序。我目前使用的是AVR Studio Mega32微控制器 例如:Assembly 如何反转一个字节,assembly,byte,reverse,avr,atmega,Assembly,Byte,Reverse,Avr,Atmega,我现在正在做一个项目,碰巧我不得不颠倒一个字节的顺序。我目前使用的是AVR Studio Mega32微控制器 例如: 0000 0001 becomes 1000 0000 0001 0110 becomes 0110 1000 1101 1001 becomes 1001 1011 首先,我有: ldi r20,0b00010110 什么是反转字节以使r20变为01101000的最简单方法?我现在不能提供AVR代码。但一般的位反转技术如下: abcd efgh p badc fehg
0000 0001 becomes 1000 0000
0001 0110 becomes 0110 1000
1101 1001 becomes 1001 1011
首先,我有:
ldi r20,0b00010110
什么是反转字节以使r20变为01101000的最简单方法?我现在不能提供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)
下面是一个片段——它是为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;
}
静态内联\uuuuu属性\uuuuu((始终\u内联))
uint8\u t avr\u反向字节(uint8\u t x)
{
x=((x&0x55)>1);
x=((x&0x33)>2);
/*x=((x&0x0f)>4)*/
__asm_uu(“交换%0”):“=r”(x):“0”(x));/*交换半字节*/
返回x;
}
因此,除了使用
swap
指令实现的最终半字节交换之外,与“C”代码相比没有太大的改进。另一种简单的方法是使用进位标志:
重复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个周期在完全“展开”时反转一个字节。在循环中,代码大小可以减少,但执行时间会增加。对于字节的两部分,4位(16个条目)查找表看起来是一个很好的折衷方案(如@Aki在注释中指出的)
AVR指令每个2字节,因此一个16字节的表占用的空间与8条指令相同。(事实证明,无论是速度还是大小,这都是不值得的,除非您可以将阵列对齐256字节,以使索引比gcc便宜得多。)
可以使用每个字节的高半部和低半部来打包LUT。但这在索引(使用索引第4位上的分支在屏蔽之前有条件地交换)方面的成本要比在表大小(8字节=4条指令)方面节省的成本高
让我们比较一下AVR GCC的功能。gcc4.6在编译Brett的代码时出现了令人惊讶的严重优化缺失(实际上提升到
int
,而没有充分利用将结果截断到uint8\t的优势)。具有讽刺意味的是,它使用SWAP指令将x4
优化为旋转。(AVR没有多计数旋转指令,正常旋转是通过进位进行旋转。移位仅适用于每条指令一个计数。)
#包括
uint8\u t反向字节\u alu(uint8\u t x)
{
uint8_t xeven=x&0x55,xodd=x&0xaa;
x=(xeven>1);//交换相邻位对
xeven=x&0x33,xodd=x&0xcc;
x=(xeven>2);//交换相邻的2位块
x=((x>4));//4位旋转被识别为交换
返回x;
}
。我没有看到任何遗漏的优化
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
编译到17条指令,以及。(不访问闪存或内部SRAM时,在某些CPU上为1个周期)
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
因此,通过更好的索引,这可能会在速度上领先,但总大小(代码+表格)显然看起来更糟。稍微调整一下,就可以从注释中的代码片段(我的)中获得额外的性能
当我们确保LUT与16字节边界对齐时,我们可以通过xoring生成地址。此外,我们还可以通过索引对表进行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
\uuuuu属性(对齐(16))//使索引更便宜
静态常数uint8\u t反向零位异或[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\u t倒车辅助系统(uint8\u t x)
{
uint8_t*p=(uint8_t*)((uint16_t)反向零位异或^(x&15));
x^=p[0];
x=((x>4));
uint8_t*q=(uint8_t*)((uint16_t)反向零位异或^(x&15));
x^=q[0];
返回x;
}
倒车辅助系统:
本地设计院r18,lo8(反向咬边)
//本地设计院r19,hi8(反向咬边)
ldi r31,hi8(反向)//直接使用r31而不是r19
mov r30,r24
//本地设计院(ldi)r31,lo8(0)
安迪r30,lo8(15)
//安迪r31,hi8(15)
三次采油r30、r18
//提高采收率r31,r19
ld r25,Z
提高采收率r25,r24
互换r25
莫夫r30,r25
//本地设计院(ldi)r31,lo8(0)
安迪r30,lo8(15)
//安迪r31,hi8(15)
三次采油r30、r18
//提高采收率r31,r19
ld r24,Z
提高采收率r24,r25
ret
看一看,这里有一些解决方法的建议。您还可以了解更多如何反转字节的算法(代码是C语言的,但应该很容易移植到asm)对谁来说容易?我个人会使用(表16[orig&15]4);`人们可以很容易地将其转换为汇编程序,只需稍加脑力即可生成表。此外,内存需求和速度在某种程度上处于良好的平衡状态。@AkiSuihkonen:表索引在AVR上并不便宜,4位移位需要交换+掩码。所以它可能仅仅比纯ALU版本快。AVR gcc识别带有或不带有&0x0f
/&0xf0
的x=((x>4))
旋转惯用法,并将其编译为交换指令。您不需要内联asm。讽刺的是,函数的其余部分编译得非常糟糕。出于某种原因,如果在前2个表达式中没有显式强制转换|
的操作数,gcc将无法将int
(从整数提升)优化为实际的1寄存器操作,并使用2个寄存器。(我没有检查当前的gcc,因为Godbolt只有AVR gcc4.6。)“我们可以
# 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
__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