首页 > 解决方案 > 如何在 avr 汇编中将 32 位数除以 16 位数?

问题描述

我正在尝试使用 AVR 汇编语言创建一个将无符号 32 位数字除以无符号 16 位数字的函数。由于我使用的是 ATmega128 微控制器,因此我无法使用 div 指令,我能找到的大多数示例似乎都在使用。我也一直试图想出一个算法来做这个除法,但没有这样的运气。如果有人可以帮助我或指出我正确的方向,将不胜感激。

标签: assemblyavrdivision

解决方案


最简单的方法是使用逐位除法。这基本上是我们在小学学到的一种速记除法,但使用以 2 为底而不是以 10 为底。这简化了商数选择,当然,其中每个数字都是一个位。在每个步骤中,如果部分余数大于或等于除数,则从部分余数中减去除数,并将最低有效商位记为1。

在一个经典的安排中,从一对覆盖被除数的寄存器开始,使得被除数的最高有效一半在余数寄存器中,而被除数的最低有效一半在商寄存器中。每一步都从左移连接的寄存器对开始。这会将下一个被除数位附加到部分余数,同时在商寄存器的最低有效端为下一个商位创建空间。在对除数中的位数进行循环后,所有被除数位都已移出商寄存器,该寄存器现在只保存收集的商位。

作为 ISO-C99 代码,此算法通常称为非执行二进制除法,如下所示。AVR 是一个 8 位架构,r0通过提供字节大小的寄存器r31,我在注释中注释了我计划如何将 C 代码中的操作数映射到寄存器。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint16_t udiv_32_16 (uint32_t dividend, uint16_t divisor)
{
    // dividend in r3:r2:r1:r0, divisor in r5:r4
    uint16_t quot = dividend;        // r1:r0
    uint16_t rem  = dividend >> 16;  // r3:r2
    uint8_t  bits = 16;              // r6
    uint8_t  carry;                  // carry flag
    do {
        // (rem:quot) << 1, with carry out
        carry = rem >> 15;
        rem  = (rem << 1) | (quot >> 15);
        quot = quot << 1;
        // if partial remainder greater or equal to divisor, subtract divisor
        if (carry || (rem >= divisor)) {
            rem = rem - divisor;
            quot = quot | 1;
        }
        bits--;
    } while (bits);
    return quot;
}

int main (void)
{
    uint32_t dividend;
    uint16_t divisor, quotient, ref, dividend_hi;

    dividend = 1;
    do {
        printf ("\rdividend=%08x", dividend);
        dividend_hi = dividend >> 16;
        divisor = 1; 
        while (dividend_hi < divisor) {
            ref = dividend / divisor;
            quotient = udiv_32_16 (dividend, divisor);
            if (quotient != ref) {
                printf ("\n!!!! dividend=%08x  divisor=%04x  quotient=%04x  ref=%04x\n", dividend, divisor, quotient, ref);
                return EXIT_FAILURE;
            }
            divisor++;
        }
        dividend++;
    } while (dividend);
    return EXIT_SUCCESS;
}

我从未编写过 AVR 处理器,因此我查阅了 AVR 指令集手册 (Atmel 2016) 以获取可用指令。该指令集旨在通过通过进位标志链接来轻松构建多字节算术。多字节操作数的左移可以通过在最低有效字节上进行初始左移来完成,然后对每个高位字节进行左移。每次循环都会在最低有效位处获取进位位,并将最高有效位移动到进位位。多字节比较是由一个初始比较构造的,该比较输出一个借位到进位位,然后是高阶字节的比较进位指令。减法以类似的方式工作。

由于缺乏 AVR 平台和 AVR 开发工具,下面的汇编代码是通过猜测诸如标签和注释之类的汇编语言约定而编写的,未经测试。

;;------------------------------------------------------------
;; AVR 32 / 16 -> 16 bit division by the non-performing method
;; 
;; input:    r3:r2:r1:r0 dividend --> rem:quot
;;           r5:r4       divisor
;; output:   r1:r0       quotient
;; destroys: r0, r1, r2, r3, r6
;;------------------------------------------------------------

    ldi  r6, 16 ; bits = 16
0:
    lsl  r0     ; shift
    rol  r1     ;  rem:quot 
    rol  r2     ;   left 
    rol  r3     ;    by 1
    brcs 1f     ; if carry out, rem > divisor
    cp   r2, r4 ; is rem less
    cpc  r3, r5 ;  than divisor ?
    brcs 2f     ; yes, when carry out
1:  
    sub  r2, r4 ; compute
    sbc  r3, r5 ;  rem -= divisor
    ori  r0, 1  ; record quotient bit as 1
2:
    dec  r6     ; bits--
    brne 0b     ; until bits == 0

推荐阅读