Assembly 如何反转一个字节

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

我现在正在做一个项目,碰巧我不得不颠倒一个字节的顺序。我目前使用的是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   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