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 位加法不需要额外费用。