我为一个带有ATmega2560处理器的avr项目编写了一个快速" 8位反向" -routine。 我正在使用
首先,我创建了一个反向字节的全局查找表(大小:0x100):
uint8_t BitReverseTable[]
__attribute__((__progmem__, aligned(0x100))) = {
0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,
0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0,
[...]
0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF
};
这可以按预期工作。这是我打算使用的宏,这应该只花费我5个圆柱:
#define BITREVERSE(x) (__extension__({ \
register uint8_t b=(uint8_t)x; \
__asm__ __volatile__ ( \
"ldi r31, hi8(table)" "\n\t" \
"mov r30, ioRegister" "\n\t" \
"lpm ioRegister, z" "\n\t" \
:[ioRegister] "+r" (b) \
:[table] "g" (BitReverseTable) \
:"r30", "r31" \
); \
}))
编译(或不编译)的代码。
int main() /// Test for bitreverse
{
BITREVERSE(25);
return 0;
}
这是我从编译器得到的错误:
c:/winavr-20100110/bin/../lib/gcc/avr/4.3.3/../../../../avr/bin/as.exe -mmcu=atmega2560 -o bitreverse.o C:\Users\xxx\AppData\Local\Temp/ccCefE75.s
C:\Users\xxx\AppData\Local\Temp/ccCefE75.s: Assembler messages:
C:\Users\xxx\AppData\Local\Temp/ccCefE75.s:349: Error: constant value required
C:\Users\xxx\AppData\Local\Temp/ccCefE75.s:350: Error: constant value required
我想问题出在这里:
:[table] "g" (BitReverseTable) \
从我的观点来看,BitReverseTable是数组的内存位置,它在编译时是固定的并且是已知的。因此它是不变的。 也许我需要将BitReverseTable转换成某种东西(我尝试过任何我能想到的东西)。也许我需要另一个约束(" g"是我的最后一次测试)。我确定我用过任何可能而且不可能的东西。 我编写了一个汇编程序版本,它工作正常,但它不是一个内联汇编代码,而是一个适当的函数,它增加了另外6个循环(用于调用和返回)。
非常欢迎任何建议或建议!
上答案 0 :(得分:1)
以下似乎适用于avr-gcc(GCC)4.8.2,但对我来说确实有一种明显的hacky回味。
编辑修复OP(Thomas)在评论中指出的问题:
Z
注册的高字节为r31
(我已r30
交换r31
lpm r,Z
(仅限较早的AVR lpm r0,Z
)感谢您的修复,托马斯!我有一个ATmega2560主板,但我更喜欢Teensies(部分原因是因为原生USB),所以我只对代码进行了编译测试,没有运行它来验证。我应该提到那个;道歉。
const unsigned char reverse_bits_table[256] __attribute__((progmem, aligned (256))) = {
0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240,
8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248,
4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244,
12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241,
9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249,
5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245,
13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253,
3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255,
};
#define USING_REVERSE_BITS \
register unsigned char r31 asm("r31"); \
asm volatile ( "ldi r31,hi8(reverse_bits_table)\n\t" : [r31] "=d" (r31) )
#define REVERSE_BITS(v) \
({ register unsigned char r30 asm("r30") = v; \
register unsigned char ret; \
asm volatile ( "lpm %[ret],Z\n\t" : [ret] "=r" (ret) : [r30] "d" (r30), [r31] "d" (r31) ); \
ret; })
unsigned char reverse_bits(const unsigned char value)
{
USING_REVERSE_BITS;
return REVERSE_BITS(value);
}
void reverse_bits_in(unsigned char *string, unsigned char length)
{
USING_REVERSE_BITS;
while (length-->0) {
*string = REVERSE_BITS(*string);
string++;
}
}
对于仅支持lpm r0,Z
的旧版AVR,请使用
#define REVERSE_BITS(v) \
({ register unsigned char r30 asm("r30") = v; \
register unsigned char ret asm("r0"); \
asm volatile ( "lpm %[ret],Z\n\t" : [ret] "=t" (ret) : [r30] "d" (r30), [r31] "d" (r31) ); \
ret; })
我们的想法是使用local reg var r31
来保留Z
寄存器对的高字节。 USING_REVERSE_BITS;
宏在当前作用域中定义它,使用内联汇编有两个目的:避免将表地址的低部分不必要地加载到寄存器中,并确保GCC知道我们已将值存储到它(因为它是一个输出操作数)没有任何方式知道该值应该是什么,因此希望将其保留在整个范围内。
REVERSE_BITS()
宏产生结果,告诉编译器它需要寄存器r30
中的参数,以及USING_REVERSE_BITS;
中r31
设置的表地址高字节。 / p>
听起来有点复杂,但那只是因为我不知道如何更好地解释它。这真的很简单。
使用avr-gcc-4.8.2 -O2 -fomit-frame-pointer -mmcu=atmega2560 -S
编译上面的内容会产生汇编源。 (我建议使用-O2 -fomit-frame-pointer
。)
省略评论和正常指令:
.text
reverse_bits:
ldi r31,hi8(reverse_bits_table)
mov r30,r24
lpm r24,Z
ret
reverse_bits_in:
mov r26,r24
mov r27,r25
ldi r31,hi8(reverse_bits_table)
ldi r24,lo8(-1)
add r24,r22
tst r22
breq .L2
.L8:
ld r30,X
lpm r30,Z
st X+,r30
subi r24,1
brcc .L8
.L2:
ret
.section .progmem.data,"a",@progbits
.p2align 8
reverse_bits_table:
.byte 0
.byte -128
; Rest of data omitted for brevity
如果您想知道,在ATmega2560上,GCC将第一个8位参数和8位函数结果放在寄存器r24
中。
据我所知,第一个功能是最佳的。 (对于仅支持lpm r0,Z
的旧版AVR,您可以添加一项动作,将结果从r0
复制到r24
。)
对于第二个函数,设置部分可能不是最佳的(对于一个,你可以先做tst r22
breq .L2
来加速零长度数组检查),但我我不确定自己是否可以写一个更快/更短的一个;这对我来说当然是可以接受的。
第二个函数中的循环对我来说是最佳的。它使用r30
的方式我起初发现奇怪和可怕,但后来我意识到这很有道理 - 使用的寄存器更少,以这种方式重用r30
没有任何害处(即使它是Z
的低部分也注册了),因为它将在下一次迭代开始时从string
加载一个新值。
请注意,在我之前的编辑中,我提到交换函数参数的顺序产生了更好的代码,但是随着Thomas的添加,不再是这种情况。寄存器改变了,就是这样。
如果您确定始终提供大于零length
,请使用
void reverse_bits_in(unsigned char *string, unsigned char length)
{
USING_REVERSE_BITS;
do {
*string = REVERSE_BITS(*string);
string++;
} while (--length);
}
产量
reverse_bits_in:
mov r26,r24 ; 1 cycle
mov r27,r25 ; 1 cycle
ldi r31,hi8(reverse_bits_table) ; 2 cycles
.L4:
ld r30,X ; 2 cycles
lpm r30,Z ; 3 cycles
st X+,r30 ; 2 cycles
subi r22,lo8(-(-1)) ; 1 cycle
brne .L4 ; 2 cycles
ret ; 4 cycles
开始让我印象深刻:每个字节10个周期,4个设置周期,3个周期清理(如果没有跳转,brne
只需一个周期)。循环计数我列在我的头顶,所以可能有小错误(这里或那里的一个循环)。 r26:r27
为X
,函数的第一个指针参数在r24:r25
中提供,长度为r22
。
reverse_bits_table
位于正确的部分,并且正确对齐。 (.p2align 8
确实对齐到256个字节;它指定低8位为零的对齐。)
尽管GCC因多余的寄存器动作而臭名昭着,但我真的很喜欢它上面生成的代码。当然,总有空间去做;对于重要的代码序列,我建议尝试不同的变体,甚至更改函数参数的顺序(或在本地作用域中声明循环变量),等等,然后使用-S
进行编译以查看生成的代码。 AVR instruction timings很简单,因此比较代码序列非常容易,看看是否有一个明显更好。我想首先删除指令和注释;它使组装更容易阅读。
令人讨厌的余味的原因是GCC documentation明确表示“定义这样的寄存器变量不会保留寄存器;它仍然可用于流量控制确定变量值的地方的其他用途是不是“,我只是不相信这对GCC开发人员来说意味着和我一样。即使它现在做了,也可能在未来;没有标准的GCC开发人员应该遵守这里,因为这是GCC特有的功能。
另一方面,我只依赖于上面记录的GCC行为,虽然“hacky”,但它确实从直接的C代码生成有效的汇编。
就我个人而言,无论何时更新avr-gcc,我都建议重新编译上面的测试代码,并查看生成的程序集(可能使用sed删除注释和标签,并与已知的正常版本进行比较?)。 / p>
有问题吗?