assembly AVR组件-要屏蔽的位数

hmae6n7t  于 2022-11-13  发布在  其他
关注(0)|答案(6)|浏览(151)

在我的ATtiny 84 a AVR汇编程序中,我在一个寄存器中得到了一个0到7之间的位数,比如r16。现在我需要创建一个设置了该位数的掩码。为了使它更复杂,操作的时序必须相同,而不管设置了哪个位。
例如,如果r16 = 5,则结果掩码将为0x 20(第5位设置)。
到目前为止,我已经将一个位移位了LSL,并使用r16(位数)作为循环计数器,然后为了保持准确的时序(无论位数如何),执行了NOP8-r16次的虚拟循环。
汇编指令SBR根据掩码设置寄存器中的位,因此无法使用。汇编指令SBI根据位号设置I/O寄存器中的位,但它是常量,而不是寄存器(我可以将I/O寄存器用作临时寄存器)。
然后使用掩码清除存储器位置中的位,因此如果有另一种解决方案可以从寄存器中的位数执行此操作,那么也可以。
我有另一个解决方案来尝试(基于移位的进位),但我希望有人有一个比循环和移位更优雅的解决方案。

bwitn5fc

bwitn5fc1#

我认为你的移位和进位的直觉是一个很好的解决方案,你基本上会递减索引寄存器,当递减量为零时设置进位,然后将进位移位到输出寄存器。
您可以使用subtract来执行递减,当索引命中0时,它将自动设置进位位。
您可以使用rotate right 来代替shift,因为这可以让您向右移动位以匹配decement。
然后,您可以非常巧妙地在输出中使用一个sentinel位作为伪循环计数器,在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操作,所以我认为赢家的大小,令人惊讶的(甚至对我来说!)击败了强大的领域的查找表为基础的条目。
还具有对脱离键合条件的合理处理,除输入和输出外,没有其他寄存器发生变化。重复旋转还有助于防止ALU移位器门中的存款。
非常感谢@ReAI和@PeterCordes,他们的指导和灵感使这段代码成为可能!:)

vfh0ocws

vfh0ocws2#

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
dgsult0t

dgsult0t3#

由于你的输出只有8个变量,你可以使用一个查找表。它将做完全相同的操作,无论输入是什么,因此有完全相同的执行时间。

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
w1jd8yoj

w1jd8yoj4#

8字节对齐的查找表可简化索引,适用于支持lpm-从程序存储器加载的AVR芯片。(根据@AterLux的答案优化)。将表对齐8意味着所有8个条目的地址的高位字节相同。低3位没有换行,因此我们可以使用ori,而不必对subi的地址取反。(adiw仅适用于0..63,因此可能无法表示地址。)

我展示的是最好的情况,首先你可以方便地在r30(Z的下半部分)中生成输入,否则你需要一个mov。而且,这太短了,不值得调用一个函数,所以我没有展示ret,只是一个代码片段。
假设输入有效(在0..7内);如果需要忽略高位,请考虑@真实的's,或者只考虑andi r30, 0x7
如果你可以很容易地重新加载Z,或者不需要保留它,这是很好的。如果重敲Z很糟糕,你可以考虑在初始启动时在RAM中建立表(用循环),这样你就可以使用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字节对齐边界处找到该表,则可以删除lo8(table) = 0,这样您就可以删除ori,而直接将r30用作地址的低位字节。
成本ori版本,不包括重新加载Z之后的内容,或者更糟糕的保存/恢复Z。(如果Z在你需要的时候很宝贵,考虑一个不同的策略)。

  • 大小= 3个字的代码+ 8个字节(4个字)的数据=7个字。(如果您不注意程序内存的布局,最多可加上7个字节的对齐填充)
  • 循环次数= 1(ldi)+ 1(ori)+ 3(lpm)=5个循环次数

在的循环中,如果您需要相同的256 B程序内存块中的其他数据,则ldi r31, hi8只能提升/完成一次。
如果你能将表对齐256,那么就节省了一个字的代码和一个周期的时间。如果你也将ldi从循环中提升出来,那么就只剩下3个周期的lpm了。
(未经测试,除了clang -target avr,我没有AVR工具链。我认为GAS / clang只需要普通的符号引用,并在内部处理symbol * 2。这确实可以成功地与clang -c -target avr -mmcu=atmega128 shl.s组装,但反汇编.o会导致llvm-objdump -d 10.0.0崩溃。)

du7egjpx

du7egjpx5#

谢谢大家的创造性的答案,但我去与查找表作为一个宏。我发现这是最灵活的解决方案,因为我可以很容易地有不同的查找表为各种目的在一个固定的7个周期。

; @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)

空间不是问题,但速度是问题。然而,我认为这是一个很好的妥协,我不被迫对齐四字上的数据。7对5周期是要付出的代价。
我已经有一个“零”寄存器保留通过整个程序,所以它的成本我没有额外的做16位加法。

wbrvyc0a

wbrvyc0a6#

在7条指令/ 7个节拍中,也可以不使用查找表。输出必须在高位寄存器中。寄存器压力较低,不需要宝贵的Z寄存器,也不需要包含零的寄存器:

;; R22 = 1 << (R11 & 7)
ldi  R22, 1
sbrc R11, 1
ldi  R22, 4
sbrc R11, 2
swap R22
sbrc R11, 0
lsl  R22

对于右移R22 = 0x80 >> (R11 & 7)或对于补码R22 = 0xff ^ (1 << (R11 & 7)),这可以容易地进行调整。
如果移位偏移量已经在R30中,则从查找表加载的版本可以少1个指令,并且不需要包含零的寄存器:

;; Offset in R30 = ZL
clr   ZH
subi  ZL, low(-(table))   ;; GNU-Syntax: lo8(-(table))
sbci  ZH, high(-(table))  ;; GNU-Syntax: hi8(-(table))
lpm   R22, Z

相关问题