AVR 汇编 - 要屏蔽的位数
AVR assembly - bit number to mask
在我的 ATtiny84a AVR 汇编程序中,我在寄存器中得到了一个介于 0 和 7 之间的位号,假设为 r16。现在我需要创建一个带有该位数集的掩码。更复杂的是,无论设置什么位,操作的时序必须相同。
例如,如果 r16 = 5,则生成的掩码将为 0x20(第 5 位设置)。
到目前为止,我已经将位移动到 LSL 的位置,并使用 r16(位数)作为循环计数器,然后无论位数如何都保持准确的计时,做一个NOP 8-r16 次的虚拟循环。
汇编指令SBR 通过掩码设置寄存器中的位,因此无法使用。
汇编指令 SBI 根据位数在 I/O 寄存器中设置一个位,但它是一个常量,而不是寄存器(我本可以使用 I/O 寄存器作为临时寄存器)。
掩码然后用于清除内存位置中的位,因此如果有另一种解决方案可以从寄存器中的位编号执行此操作,那么也可以。
我有另一种解决方案可以尝试(基于进位的移位)但我希望有人有比循环和移位更优雅的解决方案。
我认为你对轮班和进位的直觉是一个很好的解决方案。您基本上会递减变址寄存器,当递减量为零时设置进位,然后将进位移入输出寄存器。
可以用subtract
做递减,索引到0时自动设置进位。
您可以使用旋转 right 而不是移位,因为这可以让您在正确的方向上移动位以匹配位移。
然后你可以变得非常棘手,并在输出中使用一个标记位作为伪循环计数器,在 8 次循环迭代后终止。
所以像...
; Assume r16 is the index 0-7 of the bit to set in the output byte
; Assume r17 is the output byte
; r17 output will be 0 if r16 input is out of bounds
; r16 is clobbered in the process (ends up as r16-8)
ldi r17, 0b10000000 ; Sort of a psuedo-counter. When we see this
; marker bit fall off the right end
; then we know we did 8 bits of rotations
loop:
subi r16,1 ; decrement index by 1, carry will be set if 0
ror r17 ; rotate output right, carry into the high bit
brcc loop ; continue until we see our marker bit come output
我计算了 4 个字(8 字节)的存储空间和 24 个循环,在所有 AVR 上执行此操作,所以我认为尺寸上的赢家,令人惊讶的是(甚至对我来说!)击败了强大的查找领域-table 基于条目。
还具有对债券外条件的合理处理,并且除了输入和输出之外没有其他寄存器发生变化。重复旋转也有助于防止 ALU 换档器门中积碳。
非常感谢@ReAI 和@PeterCordes,是他们的指导和启发使此代码成为可能! :)
9个字,9个循环
ldi r17, 1
; 4
sbrc r16, 2 ; if n >= 4
swap r17 ; 00000001 -> 00010000, effectively shift left by 4
; 2
sbrc r16, 1
lsl r17
sbrc r16, 1
lsl r17
; 1
sbrc r16, 0
lsl r17
由于您的输出只有 8 个变体,您可以使用查找 table。它会做
因此,无论输入是什么,相同的操作都具有完全相同的执行时间。
ldi r30, low(shl_lookup_table * 2) // Load the table address into register Z
ldi r31, high(shl_lookup_table * 2)
clr r1 // Make zero
add r30, r16 // Add our r16 to the address
adc r31, r1 // Add zero with carry to the upper half of Z
lpm r17, Z // Load a byte from program memory into r17
ret // assuming we are in a routine, i.e. call/rcall was performed
...
shl_lookup_table:
.db 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
8 字节对齐查找-table 简化索引 应该对支持 lpm
- 从程序内存加载的 AVR 芯片有好处。 (根据@AterLux 的回答进行了优化)。将 table 对齐 8 意味着所有 8 个条目都具有相同的地址高字节。并且低 3 位没有回绕,因此我们可以使用 ori
而不必取反 subi
的地址。 (adiw
仅适用于 0..63,因此可能无法表示地址。)
我展示了最好的情况,您可以首先方便地在 r30
(Z 的低半部分)中生成输入,否则您需要 mov
。此外,这太短了,不值得调用一个函数,所以我没有显示 ret
,只是一个代码片段。
假设输入有效(在 0..7 中);如果您需要忽略高位,请考虑@ReAl's,或者只是 andi r30, 0x7
如果在此之后您可以轻松地重新加载 Z,或者不需要保留它,那就太好了。如果破坏 Z 很糟糕,您可以考虑在初始启动期间(使用循环)在 RAM 中构建 table,这样您就可以使用 X 或 Y 作为具有数据加载的指针,而不是 lpm
。或者如果您的 AVR 不支持 lpm
.
## gas / clang syntax
### Input: r30 = 0..7 bit position
### Clobbers: r31. (addr of a 256-byte chunk of program memory where you might have other tables)
### Result: r17 = 1 << r30
ldi r31, hi8(shl_lookup_table) // Same high byte for all table elements. Could be hoisted out of a loop
ori r30, lo8(shl_lookup_table) // Z = table | bitpos = &table[bitpos] because alignment
lpm r17, Z
.section .rodata
.p2align 3 // 8-byte alignment so low 3 bits of addresses match the input.
// ideally place it where it will be aligned by 256, and drop the ORI
// but .p2align 8 could waste up to 255 bytes of space! Use carefully
shl_lookup_table:
.byte 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
如果可以在 256 字节对齐边界找到 table,则可以删除 lo8(table)
= 0,这样就可以删除 ori
和直接用r30
作为地址的低字节即可。
成本 ori
的版本,不包括重新加载 Z
之后的内容,或者更糟 saving/restoring Z
. (如果 Z 在你需要的时候很珍贵,请考虑不同的策略)。
- 大小=3字代码+8字节(4字)数据=7字。 (如果您不注意程序内存的布局,加上最多 7 个字节的对齐填充)
- 周期 = 1(ldi) + 1(ori) + 3(lpm) = 5 周期
在循环中,如果您需要同一 256B 程序内存块中的其他数据,ldi r31, hi8
只能提升/完成一次。
如果能把table对齐256,就省了一个字的代码,一个周期的时间。如果你也将 ldi
提升到循环之外,那么 就 3 周期 lpm
.
(未经测试,除了 clang -target avr
,我没有其他 AVR 工具链。我认为 GAS / clang 只需要正常的符号引用,并在内部处理 symbol * 2
。这 assemble 使用 clang -c -target avr -mmcu=atmega128 shl.s
成功,但反汇编 .o 崩溃 llvm-objdump -d
10.0.0.)
感谢大家富有创意的回答,但我将查找 table 作为宏进行了处理。我发现这是最灵活的解决方案,因为我可以轻松地在固定的 7 个周期内针对各种目的进行不同的查找 table。
; @0 mask table
; @1 bit register
; @2 result register
.MACRO GetMask
ldi ZL,low(@0)
ldi ZH,high(@0)
add ZL,@1
adc ZH,ZERO
lpm @2,Z
.ENDM
bitmask_lookup:
.DB 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
inverse_lookup:
.DB ~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80
lrl2_lookup:
.DB 0x04,0x08,0x10,0x20,0x40,0x80,0x01,0x02
ldi r16,2
GetMask bitmask_lookup, r16, r1 ; gives r1 = 0b00000100
GetMask inverse_lookup, r16, r2 ; gives r2 = 0b11111011
GetMask lrl2_lookup, r16, r3 ; gives r3 = 0b00010000 (left rotate by 2)
Space 不是什么大问题,但速度才是。但是,我认为这是一个很好的折衷方案,我不会被迫在四字上对齐数据。 7 vs 5 周期是要付出的代价。
我已经在整个程序中保留了一个“零”寄存器,因此执行 16 位加法不需要额外费用。
在我的 ATtiny84a AVR 汇编程序中,我在寄存器中得到了一个介于 0 和 7 之间的位号,假设为 r16。现在我需要创建一个带有该位数集的掩码。更复杂的是,无论设置什么位,操作的时序必须相同。
例如,如果 r16 = 5,则生成的掩码将为 0x20(第 5 位设置)。
到目前为止,我已经将位移动到 LSL 的位置,并使用 r16(位数)作为循环计数器,然后无论位数如何都保持准确的计时,做一个NOP 8-r16 次的虚拟循环。
汇编指令SBR 通过掩码设置寄存器中的位,因此无法使用。 汇编指令 SBI 根据位数在 I/O 寄存器中设置一个位,但它是一个常量,而不是寄存器(我本可以使用 I/O 寄存器作为临时寄存器)。
掩码然后用于清除内存位置中的位,因此如果有另一种解决方案可以从寄存器中的位编号执行此操作,那么也可以。
我有另一种解决方案可以尝试(基于进位的移位)但我希望有人有比循环和移位更优雅的解决方案。
我认为你对轮班和进位的直觉是一个很好的解决方案。您基本上会递减变址寄存器,当递减量为零时设置进位,然后将进位移入输出寄存器。
可以用subtract
做递减,索引到0时自动设置进位。
您可以使用旋转 right 而不是移位,因为这可以让您在正确的方向上移动位以匹配位移。
然后你可以变得非常棘手,并在输出中使用一个标记位作为伪循环计数器,在 8 次循环迭代后终止。
所以像...
; Assume r16 is the index 0-7 of the bit to set in the output byte
; Assume r17 is the output byte
; r17 output will be 0 if r16 input is out of bounds
; r16 is clobbered in the process (ends up as r16-8)
ldi r17, 0b10000000 ; Sort of a psuedo-counter. When we see this
; marker bit fall off the right end
; then we know we did 8 bits of rotations
loop:
subi r16,1 ; decrement index by 1, carry will be set if 0
ror r17 ; rotate output right, carry into the high bit
brcc loop ; continue until we see our marker bit come output
我计算了 4 个字(8 字节)的存储空间和 24 个循环,在所有 AVR 上执行此操作,所以我认为尺寸上的赢家,令人惊讶的是(甚至对我来说!)击败了强大的查找领域-table 基于条目。
还具有对债券外条件的合理处理,并且除了输入和输出之外没有其他寄存器发生变化。重复旋转也有助于防止 ALU 换档器门中积碳。
非常感谢@ReAI 和@PeterCordes,是他们的指导和启发使此代码成为可能! :)
9个字,9个循环
ldi r17, 1
; 4
sbrc r16, 2 ; if n >= 4
swap r17 ; 00000001 -> 00010000, effectively shift left by 4
; 2
sbrc r16, 1
lsl r17
sbrc r16, 1
lsl r17
; 1
sbrc r16, 0
lsl r17
由于您的输出只有 8 个变体,您可以使用查找 table。它会做 因此,无论输入是什么,相同的操作都具有完全相同的执行时间。
ldi r30, low(shl_lookup_table * 2) // Load the table address into register Z
ldi r31, high(shl_lookup_table * 2)
clr r1 // Make zero
add r30, r16 // Add our r16 to the address
adc r31, r1 // Add zero with carry to the upper half of Z
lpm r17, Z // Load a byte from program memory into r17
ret // assuming we are in a routine, i.e. call/rcall was performed
...
shl_lookup_table:
.db 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
8 字节对齐查找-table 简化索引 应该对支持 lpm
- 从程序内存加载的 AVR 芯片有好处。 (根据@AterLux 的回答进行了优化)。将 table 对齐 8 意味着所有 8 个条目都具有相同的地址高字节。并且低 3 位没有回绕,因此我们可以使用 ori
而不必取反 subi
的地址。 (adiw
仅适用于 0..63,因此可能无法表示地址。)
我展示了最好的情况,您可以首先方便地在 r30
(Z 的低半部分)中生成输入,否则您需要 mov
。此外,这太短了,不值得调用一个函数,所以我没有显示 ret
,只是一个代码片段。
假设输入有效(在 0..7 中);如果您需要忽略高位,请考虑@ReAl's,或者只是 andi r30, 0x7
如果在此之后您可以轻松地重新加载 Z,或者不需要保留它,那就太好了。如果破坏 Z 很糟糕,您可以考虑在初始启动期间(使用循环)在 RAM 中构建 table,这样您就可以使用 X 或 Y 作为具有数据加载的指针,而不是 lpm
。或者如果您的 AVR 不支持 lpm
.
## gas / clang syntax
### Input: r30 = 0..7 bit position
### Clobbers: r31. (addr of a 256-byte chunk of program memory where you might have other tables)
### Result: r17 = 1 << r30
ldi r31, hi8(shl_lookup_table) // Same high byte for all table elements. Could be hoisted out of a loop
ori r30, lo8(shl_lookup_table) // Z = table | bitpos = &table[bitpos] because alignment
lpm r17, Z
.section .rodata
.p2align 3 // 8-byte alignment so low 3 bits of addresses match the input.
// ideally place it where it will be aligned by 256, and drop the ORI
// but .p2align 8 could waste up to 255 bytes of space! Use carefully
shl_lookup_table:
.byte 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
如果可以在 256 字节对齐边界找到 table,则可以删除 lo8(table)
= 0,这样就可以删除 ori
和直接用r30
作为地址的低字节即可。
成本 ori
的版本,不包括重新加载 Z
之后的内容,或者更糟 saving/restoring Z
. (如果 Z 在你需要的时候很珍贵,请考虑不同的策略)。
- 大小=3字代码+8字节(4字)数据=7字。 (如果您不注意程序内存的布局,加上最多 7 个字节的对齐填充)
- 周期 = 1(ldi) + 1(ori) + 3(lpm) = 5 周期
在循环中,如果您需要同一 256B 程序内存块中的其他数据,ldi r31, hi8
只能提升/完成一次。
如果能把table对齐256,就省了一个字的代码,一个周期的时间。如果你也将 ldi
提升到循环之外,那么 就 3 周期 lpm
.
(未经测试,除了 clang -target avr
,我没有其他 AVR 工具链。我认为 GAS / clang 只需要正常的符号引用,并在内部处理 symbol * 2
。这 assemble 使用 clang -c -target avr -mmcu=atmega128 shl.s
成功,但反汇编 .o 崩溃 llvm-objdump -d
10.0.0.)
感谢大家富有创意的回答,但我将查找 table 作为宏进行了处理。我发现这是最灵活的解决方案,因为我可以轻松地在固定的 7 个周期内针对各种目的进行不同的查找 table。
; @0 mask table
; @1 bit register
; @2 result register
.MACRO GetMask
ldi ZL,low(@0)
ldi ZH,high(@0)
add ZL,@1
adc ZH,ZERO
lpm @2,Z
.ENDM
bitmask_lookup:
.DB 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
inverse_lookup:
.DB ~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80
lrl2_lookup:
.DB 0x04,0x08,0x10,0x20,0x40,0x80,0x01,0x02
ldi r16,2
GetMask bitmask_lookup, r16, r1 ; gives r1 = 0b00000100
GetMask inverse_lookup, r16, r2 ; gives r2 = 0b11111011
GetMask lrl2_lookup, r16, r3 ; gives r3 = 0b00010000 (left rotate by 2)
Space 不是什么大问题,但速度才是。但是,我认为这是一个很好的折衷方案,我不会被迫在四字上对齐数据。 7 vs 5 周期是要付出的代价。
我已经在整个程序中保留了一个“零”寄存器,因此执行 16 位加法不需要额外费用。