assembly - 如何在 avr 汇编中将 32 位数除以 16 位数?
问题描述
我正在尝试使用 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, 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
推荐阅读
- python - 快速替换字符串中的字符并检查子字符串是否为回文
- apache-spark - Cassandra + Spark 执行器超融合
- android - 如何从 Firestore 服务器获取时间戳,而无需担心设备时钟与 Firestore 不同步的情况
- c++ - Clang 工具(clang-tidy、iwyu 等)的单遍运行以及编译?
- matlab - fmincon:优化 MPC 的问题
- reactjs - 为什么我需要在 `useEffect` 中定义事件处理程序 `handleStatusChange`?
- r - 在 levelplot 中每种颜色的中间放置刻度
- java - 无法使用 geoTools 获取 FeaturesCollection 中的内容
- flutter - 删除 Firebase 存储中的所有文件
- laravel - Laravel 部署 - 从 git 部署时避免覆盖存储目录