数字电路中的除法与求余
一、硬件除法器的技术路线
1. 移位-减法类(digit recurrence,逐位产生商)
最直观的实现,即二进制长除法:
-
恢复余数法(restoring) :余数左移 → 减除数 → 若为负则加回(恢复)并置商位 0,否则置 1。
-
不恢复余数法(non-restoring) :不再"加回",而是根据当前余数符号决定下一位加还是减,省掉恢复步骤。
-
SRT 除法(Sweeney--Robertson--Tocher,现代 CPU 整数除的主力):
- 核心思想:允许商的"数字"取冗余值域 (radix-4 时取 {−2,−1,0,1,2}\{-2,-1,0,1,2\}{−2,−1,0,1,2}),只看余数与除数的最高几位即可查表选出商数字,不必做完整比较;
- 余数用 CSA(进位保留加法器) 保持冗余形式,每轮不传播进位,缩短迭代关键路径;
- 末端需要余数校正 (余数可能为负)与在线商转换(把冗余商转成标准二进制),这部分控制逻辑在小位宽下是面积大头。
代价:radix-2 每周期出 1 位商(N 位需 N 周期),radix-4 出 2 位(约 N/2 周期),radix-8/16 更少但需预缩放。典型整数除法器是非流水的:延迟几十周期、吞吐约每 20~30 周期一次------这正是除法拖慢性能的原因。
2. 乘法迭代类(multiplicative / functional iteration)
把除法变成"求倒数 + 一次乘法",用乘法器 / 融合乘加(FMA)完成:
- Newton--Raphson :xn+1=xn(2−D xn)x_{n+1} = x_n(2 - D\,x_n)xn+1=xn(2−Dxn),平方收敛 ,每轮有效位数翻倍。需要小 LUT/ROM 给出 8~12 位初值,再迭代 3~5 次。
- Goldschmidt :分子分母同乘校正因子 RnR_nRn,每轮两次乘法且可并行,部分 GPU 采用。
优点:复用已有的 FMA / 乘法器 ,面积增量小、可流水、吞吐高,适合浮点除法。
缺点:IEEE-754 要求的正确舍入需额外精度与收尾逻辑,是复杂度激增之处。
3. 预计算约简(面向模运算 / 密码学)
- Barrett 约简 :预先算 μ=⌊b2k/m⌋\mu=\lfloor b^{2k}/m \rfloorμ=⌊b2k/m⌋,用乘法和移位估算商,避免真正的除法,适合偶发的模约简。
- Montgomery 乘法 :转到 Montgomery 域后,约简只需 k 次"乘-加-移位"迭代,完全不需要除以 m(只除以基数,即移位),是 RSA/ECC 硬件的主流。
4. 查表类
位宽很小(如 ≤8 位)或只需要倒数初值时,直接 LUT/ROM 查表可能比逻辑更省面积。
二、求余(mod)怎么来
- 通用情况:余数就是除法器的第二个输出,几乎不额外花面积(需处理符号语义:C/Verilog 中余数符号跟随被除数,属"截断除法",硬件只在末位调整)。
- 模 2n2^n2n :直接取低 n 位,零成本。
- 模 **2n−12^n-12n−1** (梅森数) :折叠相加 + 端回进位(end-around carry),如 mod 255 即把各字节相加再折回。
- 常量除数 :用"魔数"强度削减,把
x / d、x % d变成"乘常量 + 右移"。 - 计数器取模 N:无需除法器,用"累加器 + 比较器 + 减法"(到 N 就减 N)即可,一个加法器搞定。DDS/NCO 分频常用。
三、面积与延迟量级对比
| 方案 | 面积 | 延迟 / 吞吐 | 典型用途 |
|---|---|---|---|
| 移位-减法 radix-2(串行) | 小~中(N 位加法器 + 移位 + 控制) | N 周期,无流水 | 面积敏感 MCU、软核 |
| SRT radix-4 + CSA | 中(商选择 LUT + CSA + 末端校正) | 约 N/2 周期,非流水 | CPU 整数除 |
| Newton / Goldschmidt | 复用 FMA,附加 LUT + 收尾逻辑 | 长延迟、可流水、高吞吐 | 浮点除、GPU |
| Barrett / Montgomery | 以乘法器为主 | 可流水、高吞吐 | RSA / ECC |
| 常量除数 / 2n2^n2n | ≈ 0 | 1 周期(移位 / 位掩码) | 编译器、地址对齐 |
| 计数器 mod N | 一个加法器 + 比较器 | 1 周期 | 分频、NCO |
补充:除法在"单位面积能提供的吞吐"这一指标上最差,往往不如同宽度的并行乘法器划算------乘法面积大,但延迟固定、可流水线化,而除法的迭代特性使其难以流水线化。不过它并非"面积一定比乘法器大":SRT 的主体是加法器/CSA,面积常与乘法器相当甚至更小,真正吃亏的是延迟与吞吐 。另外,小位宽时控制逻辑(商选择、余数校正、商格式转换、前导零检测 LZD)反而占主导。
四、常量除数(非 2n2^n2n)专题
1. 硬件上发生什么
除数是常量 ddd 时,硬件不做除法,而是走"乘倒数 + 移位":
x/d≈(x⋅m)≫s,x mod d=x−(x/d)⋅d x / d \approx (x \cdot m) \gg s,\qquad x \bmod d = x - (x/d)\cdot d x/d≈(x⋅m)≫s,xmodd=x−(x/d)⋅d
其中魔数 mmm 与移位量 sss 在编译/综合期算好并固化。对应硬件只有三件事:
- 一个 "乘以常量"的乘法器;
- 一个常量移位(接线,免费);
- 求余时再一步 x−q⋅dx - q\cdot dx−q⋅d(ddd 是常量,q⋅dq\cdot dq⋅d 只是几个移位相加)。
经典例子(32 位无符号):
verilog
// x / 10 : 魔数 0xCCCCCCCD,取高 32 位后再右移 3
wire [31:0] q10 = (x * 32'hCCCCCCCD) >> 35; // 综合成 mulhi + 常量移位
// x % 10 : x - q*10 = x - (q<<3) - (q<<1)
wire [31:0] r10 = x - (q10 << 3) - (q10 << 1);
// 反例:除数运行时可变,魔数无法固化 → 退回真除法器
wire [31:0] q = x / d; // d 非 localparam,必然例化除法器
这是常量乘法 而非通用乘法,部分积都是 xxx 的移位副本或纯常量,没有真正的乘法阵列------这才是它便宜的原因。
2. 成本随除数"形态"变化
| 除数形态 | 面积 | 延迟 | 说明 |
|---|---|---|---|
| 2n2^n2n | ≈ 0 | 0 | 纯接线(移位 / 取位) |
| 2n±12^n \pm 12n±1 | 1 个加法器 | 1 级加法 | x≪n±xx \ll n \pm xx≪n±x,取模还能用折叠 |
| popcount 小(如 3、5、10、100) | 很小 | 1~2 级加法 | 部分积项少,q⋅dq\cdot dq⋅d 也少 |
| 一般常量(如 0x9E3779B1) | 接近通用乘法器 | 约 log(项数)\log(\text{项数})log(项数) 级加法 | 魔数接近满位宽,部分积项多 |
| 需要"加 x 修正"的除数 | 再多一个加法器 | +1 级加法 | 见下 |
关键细节:
- 魔数常量的位密度(popcount)决定面积 。魔数一般接近 2N2^N2N、并不稀疏,所以常量乘法器面积约为同宽度通用乘法器的几分之一到接近,但仍远小于完整 SRT 除法器(含商选择 LUT、CSA、末位校正、在线商转换、前导零检测)。
- 部分除数需要额外修正 。只做"乘 + 移位"对某些 ddd 会有 ±1\pm 1±1 误差,需再插一个"加 xxx 再移位"(Hacker's Delight 中的 add-indicator)。例如 32 位除 7:
verilog
// x / 7 : t = mulhi(x, 0x24924925); q = (t + ((x - t) >> 1)) >> 2
- 求余几乎不额外花钱 :ddd 是常量,q⋅dq\cdot dq⋅d 就是 qqq 的几个移位相加,一个小的加法树 + 一个减法器。若 ddd 的 popcount 小(如 10 =
1010,仅 2 项),这一级几乎是白送。 - 临界路径 = 常量乘法加法树 → 常量移位 →(求余时)乘减链 ,深度约几级加法器。综合后单周期可能跑不到高频,但插流水寄存器后即 1 结果/周期------这正是相对除法器的最大优势。
3. 横向对比
| 方案 | 面积量级 | 延迟 | 吞吐 |
|---|---|---|---|
| 通用 SRT 除法器 | 中(加法器/CSA + 控制) | N ~ N/2 周期 | 非流水,极低 |
| 常量除数 = 2n2^n2n | 0 | 0 | 1/周期 |
| 常量除数 = 2n±12^n\pm12n±1 | 1 加法器 | 1 级加法 | 1/周期 |
| 常量除数(一般,魔数) | 1 个常量乘法器 + 修正 | 几级加法,可流水 | 1/周期 |
| 通用乘法器 | O(N2)O(N^2)O(N2) 级 | 约 logN\log NlogN 级 | 1/周期 |
直观结论:常量除数把"迭代型、非流水、低吞吐"的除法变成"组合型、可流水、全吞吐"的乘法,因此面积小、延迟低、吞吐高;它不是"零成本",但性价比远高于真除法器。
五、工程建议
- 优先消灭除法 :除数能取 2n2^n2n 就取(移位/掩码);是常量就交给综合器做强度削减(Verilog 里对常量
/、%一般能自动优化,但务必看综合报告确认没例化除法器)。 - 除数必须是编译期常量:只要它运行时可变(哪怕只是配置寄存器),魔数就无法固化,会退回真除法器或倒数 LUT + 迭代,成本立刻上一个量级。
- 精确性要自己保证 :
(x*m)>>s的取整方式、是否需要 add 修正、有符号/无符号语义、以及余数符号(跟随被除数)都要按 Hacker's Delight 的结论来,不要自行取 ⌈2k/d⌉\lceil 2^k/d \rceil⌈2k/d⌉,会 off-by-one。 - 需要精确商 + 要吞吐:做流水线化 SRT(radix-4/8,余数保持 CSA 冗余形式)。
- 面积紧 + 吞吐低 :用位串行除法器(如 Xilinx Divider Generator 的 radix-2 无流水配置、Altera
lpm_divide),或全芯片分时共享一个除法器。 - 浮点:单独做除法器不划算,复用 FMA + 小 LUT 初值走 Newton 迭代。
- 密码学大数模乘:用 Montgomery,而不是 Barrett 再配合除法。
- 位宽很小(≤8 位) :先评估 LUT/ROM 方案。
- 不要忘记 LZD/归一化、末端余数校正、冗余商转换这些"零碎"逻辑------它们常是小位宽除法器真正的面积来源。
参考
- Hacker's Delight(常量除法 / 魔数生成)
- IEEE-754(浮点除法的正确舍入要求)