问题描述
我正在尝试使用 AVR 汇编语言制作一个将无符号 32 位数字除以无符号 16 位数字的函数。由于我使用的是 ATmega128 微控制器,因此我无法使用 div 指令,我能找到的大多数示例似乎都在使用。我也一直试图想出一个算法来进行这个划分,但没有这样的运气。如果有人可以帮助我或为我指明正确的方向,我将不胜感激。
解决方法
最简单的方法是使用按位除法。这基本上是我们在小学学到的普通除法,但使用基数 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,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