1. 引言:为什么FPGA除法器设计是个技术活?
在FPGA开发中,加减乘运算通常都有现成的、高效的硬件原语(比如DSP Slice)可以直接调用,但除法运算却是个例外。如果你直接把除法运算符“/”写进Verilog代码,综合工具虽然不会报错,但生成出来的电路可能让你大吃一惊——面积巨大、时序紧张、功耗飙升。这是因为除法在数学上不是一个“友好”的操作,它无法像乘法那样简单地分解为移位和加法。在硬件层面实现一个既快又省的除法器,一直是数字电路设计中的一个经典挑战。
我做过不少需要高速数据处理的FPGA项目,比如软件无线电和图像处理,里面经常涉及到归一化、比例计算,都离不开除法。早期图省事,直接用IP核或者让工具自动推断,结果在时序收敛和资源占用上吃了不少亏。后来下定决心,把几种主流的除法器算法都亲手实现和优化了一遍,才算是摸清了门道。今天,我就以一个“踩过坑”的过来人身份,跟你聊聊FPGA里几种高效除法器的设计,从最直观的“恢复余数法”,到它的优化版“不恢复余数法”,再到追求更高性能的“级数展开法”和“Newton-Raphson迭代法”。我们会深入它们的原理、用Verilog实现的关键细节,并重点对比它们在资源、速度和精度上的取舍,帮你下次做设计时,能根据需求选出最合适的方案。
2. 基石算法:恢复余数法与非恢复余数法
这两种算法是除法器设计的起点,思路和我们手算除法非常相似,属于“数字递归”算法。理解它们,是通往更高级算法的基础。
2.1 恢复余数法:最直观的手算模拟
恢复余数法的思想非常直接。假设我们要计算被除数Y除以除数D,目标是得到商Q和余数R(Y = D * Q + R)。算法从被除数的最高位开始,逐位确定商的每一位。
核心步骤是这样的:
- 初始化余数寄存器为被除数(或它的高n位,取决于数据位宽设定)。
- 对于从高到低的每一位(比如第i位): a. 将当前余数左移一位(相当于乘以2),腾出低位给新的被除数位(如果有的话)。 b. 尝试:从当前余数中减去除数D。 c. 判断:如果相减结果大于等于0,说明这一位的商可以设为1,并且这个相减结果就是新的余数。 d. 恢复:如果相减结果小于0,说明这一位的商只能是0。这时我们不能接受负的余数,所以需要把刚才减去的D再加回来,恢复成原来的余数。这就是“恢复”一词的由来。
- 重复步骤2,直到处理完所有位。
你可以把它想象成我们小学学的竖式除法。每一步我们都在猜商是1还是0,如果猜大了(导致余数为负),就撤销操作,改猜0。
硬件实现与坑点: 在Verilog里实现恢复余数法,一个朴素的思路是使用一个状态机,每个时钟周期完成一次“尝试-判断-恢复/更新”的循环。但这样做的延迟很高,需要n个周期才能完成n位商的计算。为了提高吞吐率,我们常用流水线设计。
我写的一个流水线化恢复余数除法器核心模块大致如下。它把每一次迭代拆分成两级流水:第一级做减法和结果判断,第二级根据判断结果选择是更新余数(商为1)还是恢复余数(商为0)。这样,虽然单个除法计算的延迟还是O(n),但电路可以同时处理多个除法运算,吞吐率接近每个周期完成一次迭代。
module restoring_iteration (
input clk,
input rst_n,
input valid_in,
input [15:0] current_remainder, // 当前余数
input [7:0] divisor, // 除数D
input [3:0] shift_amount, // 当前迭代对应的移位量(2^i)
output reg valid_out,
output reg quotient_bit, // 当前位的商(0或1)
output reg [15:0] next_remainder // 下一轮余数
);
reg [15:0] trial_sub_result; // 尝试减法的结果
reg trial_valid;
// 第一级流水:尝试减法
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
trial_valid <= 1'b0;
trial_sub_result <= 16'd0;
end else begin
trial_valid <= valid_in;
if (valid_in) begin
trial_sub_result <= current_remainder - (divisor << shift_amount);
end
end
end
// 第二级流水:判断与恢复
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
valid_out <= 1'b0;
quotient_bit <= 1'b0;
next_remainder <= 16'd0;
end else begin
valid_out <= trial_valid;
if (trial_valid) begin
if (trial_sub_result[15] == 1'b0) begin // 结果非负,减法成功
quotient_bit <= 1'b1;
next_remainder &l

319

被折叠的 条评论
为什么被折叠?



