在AVR Studio中使用汇编确认寄存器中的值是否是3的倍数

问题描述 投票:0回答:2

我想使用 AVR Studio 和汇编语言确定寄存器中的数字是否是 3 的倍数,但在 AVR ATmega 8515 上,所以没有 div 指令/语法

我已经尝试了几种方法,例如添加寄存器和 0b0011 期望设置进位标志,但事实并非如此。

assembly bit-manipulation avr integer-division
2个回答
2
投票

所以一个直接的方法是让avr-gcc编译一段C代码,然后查看它生成的程序集1

对于 8 位无符号值,请考虑以下 C99:

#include <stdint.h>
#include <stdbool.h>

bool is_udiv3_8 (uint8_t x)
{
    return x % 3 == 0;
}

用类似的东西

> avr-gcc x.c -mmcu=atmega8515 -O2 -S

我们得到

x.s
(或者,将
-save-temps
添加到普通编译中,然后截取*.s文件)。

is_udiv3_8:
    ldi r25,lo8(-85) ;1
    mul r24,r25      ;2
    mov r25,r1       ;3
    clr __zero_reg__ ;4
    lsr r25          ;5
    mov r18,r25      ;6
    lsl r18          ;7
    add r25,r18      ;8
    ldi r18,lo8(1)   ;9
    cpse r24,r25     ;10
    ldi r18,0        ;11
    mov r24,r18      ;12
    ret

avr-gcc 的作用是将输入与 -256/3 相乘,并以乘积的高字节作为除以 3 后的商。经过一番调整,如果输入能被整除,则在 R24 中返回 True (1) 3 且 False (0),否则。

您可以将其扩展到 16 位值,但您需要 16×16=32 乘法的高位字。

此时您还记得2,以 B 为基数的自然数 N 可以被 B+1 整除,当且仅当以 B 为基数的数字上的交替交叉和可以被 B+1 整除。

例如,在基 B=2 中:自然数 N 能被 3 整除,当且仅当 N 的各位的交替叉和能被 3 整除。

汇编编写:

  • 首先对位进行循环以获得二进制交叉和。为了提高速度,您将展开该循环,这将获得 8

    sbrs
    和 8
    sbrc

  • 叉和q满足-8<= q <= 8. Negating does not change whether q is divisible by 3, thus continue with |q| which is non-negative.

  • 减去 3,直到结果为 0、1 或 2。

  • 如果值达到 0,则返回 True,否则返回 False(达到 1 或 2)。

.text
.global is_div3_asm

is_div3_asm:
    ;; R26 holds R25:R24 mod 3
    clr r26
.Loop_bits:
    ;; Loop over all bits of R25:R24 to compute the alternating cross sum
    ;; over the binary digits of R25:R24.
    sbiw r24, 0
    breq .Loop_done
    sbrc r24, 0 $ inc  r26
    sbrc r24, 1 $ dec  r26
    lsr  r25 $ ror r24
    lsr  r25 $ ror r24
    rjmp .Loop_bits
.Loop_done:
    ;; r26 = abs(r26)
    sbrc r26, 7
    neg  r26
    ;; Now we have 0 <= r26 <= 8, so reduce to r26 < 3...
    cpi  r26, 3
    brlt .Ltobool
    subi r26, 3
    ;; ...now we have 0 <= r26 <= 5, so at most one more sub 3 will do.
    cpi  r26, 3
    brlt .Ltobool
    subi r26, 3
.Ltobool:
    ;; Return a bool in R24 as of avr-gcc ABI.
    ldi r24, 1                  ; True
    cpi r26, 0
    breq .Ldone
    ldi r24, 0                  ; False
.Ldone:    
    ret

此函数符合 avr-gcc ABI 和调用约定。您可以通过原型在 C/C++ 中使用它

extern bool is_div3_asm (uint16_t); // C
extern "C" bool is_div3_asm (uint16_t); // C++

1请注意,有不同的汇编方言。这个答案使用 GNU assemlby 方言,因为它与 GNU 汇编器兼容并由 avr-gcc 和 avr-g++ 生成。

2结果实际上更强:自然数 N 与 B+1 模相同,就像 N 在基 B 中的交替叉和一样。证明只是几行模运算,归结为到 B == -1 mod B+1.


1
投票

这只是对 @emacs 让我发疯的答案的一点评论和阐述,但对于 Stackoverflow 评论来说太长太复杂,因此这是一个社区 wiki Stackoverflow 答案。

我在 godbolt.org 上使用不同版本的 avr-gcc(12.2.0 和 5.4.0)以及相同的编译选项(

-std=c99  -O2 -g -mmcu=atmega8515
)编译了 8、16、24 和 32 位版本。事实证明,avr-gcc 12.2.0 生成的代码明显短于 avr-gcc 5.4.0 生成的代码。

由于 @emacs 让我发疯的答案 可以解决 avr-gcc 5.4.0 中相对复杂的代码,因此查看 12.2.0 中更简单的代码也可能很有用。

avr-gcc 5.4.0 的

uint8_t
变体似乎是 @emacs 让我发疯的答案 的作用:

is_udiv8_by_3:
        ldi r25,lo8(-85)  /* 1 */
        mul r24,r25       /* 2 */
        mov r25,r1        /* 1 */
        clr __zero_reg__  /* 1 */
        lsr r25           /* 1 */
        mov r18,r25       /* 1 */
        lsl r18           /* 1 */
        add r25,r18       /* 1 */
        ldi r18,lo8(1)    /* 1 */
        cpse r24,r25      /* 1 */
        ldi r18,0         /* 1 */
        mov r24,r18       /* 1 */
        ret               /* 13 cycles plus ret */
avr-gcc 12.2.0 的

uint8_t
变体:

is_udiv8_by_3:
        ldi r25,lo8(-85)  /* 1 */
        mul r24,r25       /* 2 */
        mov r25,r0        /* 1 */
        clr r1            /* 1 */
        ldi r24,lo8(1)    /* 1 */
        cpi r25,lo8(86)   /* 1 */
        brlo .L2          /* 2/1 */
        ldi r24,0         /* -/1 */
.L2:
        ret               /* 9 cycles plus ret */

avr-gcc 12.2.0 中的

uint16_t
变体并不比 avr-gcc 5.4.0 中的
uint8_t
变体长很多:

is_udiv16_by_3:
        ldi r20,lo8(-85)  /* 1 */
        ldi r21,lo8(-86)  /* 1 */
        mul r24,r20       /* 2 */
        movw r18,r0       /* 2 */
        mul r24,r21       /* 2 */
        add r19,r0        /* 1 */
        mul r25,r20       /* 2 */
        add r19,r0        /* 1 */
        clr r1            /* 1 */
        ldi r24,lo8(1)    /* 1 */
        cpi r18,86        /* 1 */
        sbci r19,85       /* 1 */
        brlo .L5          /* 2/1 */
        ldi r24,0         /* -/1 */
.L5:
        ret               /* 18 cycles plus ret */

顺便说一句,来自

@emacs 的 
is_div3_asm 函数的周期计数让我发疯 取决于输入值,即使对于
.Loop_bits
循环的单次迭代也会超过 18 个周期。

当将类型大小增加到 24 位(

__uint24
变体)和 32 位时,avr-gcc 12.2.0 最终开始调用除法函数
__udivmodpsi4
:

is_udiv24_by_3:
        ldi r18,lo8(3)
        ldi r19,0
        ldi r20,0
        rcall __udivmodpsi4
        ldi r24,lo8(1)
        or r18,r19
        or r18,r20
        breq .L7
        ldi r24,0
.L7:
        ret

uint32_t
调用不同的除法函数
__udivmodsi4
并且也更长:

is_udiv32_by_3:
        push r28
        push r29
        rcall .
        rcall .
        in r28,__SP_L__
        in r29,__SP_H__
        ldi r18,lo8(3)
        ldi r19,0
        ldi r20,0
        ldi r21,0
        rcall __udivmodsi4
        std Y+1,r22
        std Y+2,r23
        std Y+3,r24
        std Y+4,r25
        ldi r24,lo8(1)
        ldd r18,Y+1
        ldd r19,Y+2
        ldd r20,Y+3
        ldd r21,Y+4
        or r18,r19
        or r18,r20
        or r18,r21
        breq .L12
        ldi r24,0
.L12:
        pop __tmp_reg__
        pop __tmp_reg__
        pop __tmp_reg__
        pop __tmp_reg__
        pop r29
        pop r28
        ret

因此寻找其他算法,例如来自 @emacs 的替代交叉和算法让我发疯的答案快速整除测试(按 2,3,4,5,.., 16)?@Peter Cordes 链接到仅对于 16 位以上的整数大小看起来很有趣。

需要更仔细地考虑代码大小和循环周期。

© www.soinside.com 2019 - 2024. All rights reserved.