30.【Verilog】Verilog 除法器设计
第一步:分析与整理Verilog 除法器设计
1. 除法器原理(定点)
- 与十进制竖式除法类似,以 27 ÷ 5 为例(二进制):
- 取被除数高位(与除数同宽,如 3bit),与除数比较。若 ≥ 除数,商为 1,相减得余数;否则商为 0,余数为被除数高位本身。
- 将余数与被除数下一 bit 拼接成新数,再次与除数比较,得到下一位商。
- 重复直至所有 bit 处理完毕。
- 注意:商的位宽与被除数相同(除数可能为 1)。第一步比较时,被除数高位应取与被除数最高位对齐的 1bit(例如 27 的二进制
11011,取最高位1扩展为001与101比较)。 - 设计采用流水线,延迟周期数等于被除数位宽。
2. 单步运算单元divider_cell
- 功能:完成一位商的判断和余数计算,并传递中间结果。
- 输入/输出:
dividend:当前步的“部分被除数”,宽为M+1位(比除数多 1 位,防止溢出)divisor:除数(M 位)merchant_ci:来自上一级的商累积值(含扩展位)dividend_ci:原始被除数的剩余低位(用于下一级拼接)- 输出:
merchant(移位累加后的商)、remainder(当前步余数)、dividend_kp/divisor_kp(传递原始值)、rdy(有效标志)
- 核心逻辑:
- 若
dividend >= {1'b0, divisor}→ 商位=1,新余数 = dividend - divisor - 否则商位=0,余数不变
- 商累加:
merchant = (merchant_ci << 1) | 商位
- 若
// parameter M means the actual width of divisormodule divider_cell #(parameter N=5,parameter M=3)(input clk,input rstn,input en,input[M:0]dividend,input[M-1:0]divisor,input[N-M:0]merchant_ci,//上一级输出的商input[N-M-1:0]dividend_ci,//原始除数output reg[N-M-1:0]dividend_kp,//原始被除数信息output reg[M-1:0]divisor_kp,//原始除数信息output reg rdy,output reg[N-M:0]merchant,//运算单元输出商output reg[M-1:0]remainder//运算单元输出余数);always @(posedge clk or negedge rstn)beginif(!rstn)begin rdy<='b0;merchant<='b0;remainder<='b0;divisor_kp<='b0;dividend_kp<='b0;endelseif(en)begin rdy<=1'b1;divisor_kp<=divisor;//原始除数保持不变dividend_kp<=dividend_ci;//原始被除数传递if(dividend>={1'b0,divisor})begin merchant<=(merchant_ci<<1)+1'b1;//商为1remainder<=dividend-{1'b0,divisor};//求余endelsebegin merchant<=merchant_ci<<1;//商为0remainder<=dividend;//余数不变end end// if (en)elsebegin rdy<='b0;merchant<='b0;remainder<='b0;divisor_kp<='b0;dividend_kp<='b0;end end endmodule3. 顶层流水线除法器divider_man
- 参数:
N被除数位宽,M除数位宽,内部N_ACT = M+N-1(扩位,防止中间溢出) - 结构:
- 首级单元:用被除数最高 1bit 扩展为
{M'b0, dividend[N-1]}作为初始 dividend;merchant_ci为 0。 - 之后串联
N_ACT-M个单元(即被除数总位数减除数位宽),每个单元的上一步余数与原始被除数的下一位拼接作为新 dividend。 - 最后一级输出为最终商和余数。
- 首级单元:用被除数最高 1bit 扩展为
- 使用
generate例化多级。
//parameter N means the actual width of dividend//using 29/5=5...4module divider_man #(parameter N=5,parameter M=3,parameter N_ACT=M+N-1)(input clk,input rstn,input data_rdy,//数据使能input[N-1:0]dividend,//被除数input[M-1:0]divisor,//除数output res_rdy,output[N_ACT-M:0]merchant,//商位宽:Noutput[M-1:0]remainder);//最终余数wire[N_ACT-M-1:0]dividend_t[N_ACT-M:0];wire[M-1:0]divisor_t[N_ACT-M:0];wire[M-1:0]remainder_t[N_ACT-M:0];wire[N_ACT-M:0]rdy_t;wire[N_ACT-M:0]merchant_t[N_ACT-M:0];//初始化首个运算单元divider_cell #(.N(N_ACT),.M(M))u_divider_step0(.clk(clk),.rstn(rstn),.en(data_rdy),//用被除数最高位 1bit 数据做第一次单步运算的被除数,高位补0.dividend({{(M){1'b0}},dividend[N-1]}),.divisor(divisor),.merchant_ci({(N_ACT-M+1){1'b0}}),//商初始为0.dividend_ci(dividend[N_ACT-M-1:0]),//原始被除数//output.dividend_kp(dividend_t[N_ACT-M]),//原始被除数信息传递.divisor_kp(divisor_t[N_ACT-M]),//原始除数信息传递.rdy(rdy_t[N_ACT-M]),.merchant(merchant_t[N_ACT-M]),//第一次商结果.remainder(remainder_t[N_ACT-M])//第一次余数);genvar i;generatefor(i=1;i<=N_ACT-M;i=i+1)begin:sqrt_stepx divider_cell #(.N(N_ACT),.M(M))u_divider_step(.clk(clk),.rstn(rstn),.en(rdy_t[N_ACT-M-i+1]),.dividend({remainder_t[N_ACT-M-i+1],dividend_t[N_ACT-M-i+1][N_ACT-M-i]}),//余数与原始被除数单bit数据拼接.divisor(divisor_t[N_ACT-M-i+1]),.merchant_ci(merchant_t[N_ACT-M-i+1]),.dividend_ci(dividend_t[N_ACT-M-i+1]),//output.divisor_kp(divisor_t[N_ACT-M-i]),.dividend_kp(dividend_t[N_ACT-M-i]),.rdy(rdy_t[N_ACT-M-i]),.merchant(merchant_t[N_ACT-M-i]),.remainder(remainder_t[N_ACT-M-i]));end// block: sqrt_stepxendgenerate assign res_rdy=rdy_t[0];assign merchant=merchant_t[0];//最后一次商结果作为最终的商assign remainder=remainder_t[0];//最后一次余数作为最终的余数endmodule4. Testbench 与仿真
`timescale1ns/1ns module test;parameter N=5;parameter M=3;reg clk;reg rstn;reg data_rdy;reg[N-1:0]dividend;reg[M-1:0]divisor;wire res_rdy;wire[N-1:0]merchant;wire[M-1:0]remainder;//clockalways begin clk=0;#5;clk=1;#5;end//driverinitial begin rstn=1'b0;#8;rstn=1'b1;#55;@(negedge clk);data_rdy=1'b1;dividend=25;divisor=5;#10;dividend=16;divisor=3;#10;dividend=10;divisor=4;#10;dividend=15;divisor=1;repeat(32)#10dividend=dividend+1;divisor=7;repeat(32)#10dividend=dividend+1;divisor=5;repeat(32)#10dividend=dividend+1;divisor=4;repeat(32)#10dividend=dividend+1;divisor=6;repeat(32)#10dividend=dividend+1;end//对输入延迟,便于数据结果同周期对比,完成自校验reg[N-1:0]dividend_ref[N-1:0];reg[M-1:0]divisor_ref[N-1:0];always @(posedge clk)begin dividend_ref[0]<=dividend;divisor_ref[0]<=divisor;end genvar i;generatefor(i=1;i<=N-1;i=i+1)begin always @(posedge clk)begin dividend_ref[i]<=dividend_ref[i-1];divisor_ref[i]<=divisor_ref[i-1];end end endgenerate//自校验reg error_flag;always @(posedge clk)begin #1;if(merchant*divisor_ref[N-1]+remainder!=dividend_ref[N-1]&&res_rdy)beginb//testbench 中可直接用乘号而不考虑运算周期error_flag<=1'b1;endelsebegin error_flag<=1'b0;end end//module instantiationdivider_man #(.N(N),.M(M))u_divider(.clk(clk),.rstn(rstn),.data_rdy(data_rdy),.dividend(dividend),.divisor(divisor),.res_rdy(res_rdy),.merchant(merchant),.remainder(remainder));//simulation finishinitial begin forever begin #100;if($time>=10000)$finish;end end endmodule// test- 产生多个随机测试向量,输入到除法器。
- 用移位寄存器链延迟输入,以便与输出对齐,进行自校验。
- 校验条件:
merchant * divisor_ref[N-1] + remainder == dividend_ref[N-1] - 仿真结果:输入后经过 N 个时钟周期开始输出,之后每周期输出一个正确结果,流水工作。
第二步:费曼教学法 – 通俗讲解除法器设计
今天讲的是如何用硬件实现除法,以及一个经典的流水线除法器设计。硬件除法和我们小学学的竖式除法几乎一样,只不过换成二进制。我会用“分糖果”的比喻帮你理解这个过程。
一、二进制竖式除法长什么样?
以 27 ÷ 5 为例(27 =11011,5 =101):
- 看被除数的最高 3 位:
110(十进制 6)≥101(5)? 是→商最高位=1,余数 = 110-101=1。余数后面再接被除数下一位1,变成11(3)。 - 11 < 101 → 商下一位=0,余数不变仍为 11,接下一个被除数位
1→ 111(7)。 - 111 ≥ 101 → 商位=1,余数=111-101=10(2),接最后一个被除数位
0→ 100(4)?等等这里注意:最后一步需取全位。实际上要处理完所有被除数位,商的位数等于被除数位数。
总结:每一步,我们比较“当前余数+下一位”与除数,决定商的一 bit,并更新余数。
二、为什么用流水线?
除法本身是串行的:每一步依赖上一步的结果。如果用状态机串行计算,每输入一个被除数需要 N 个时钟周期才能出结果,吞吐率低。流水线除法器把每一位的判断做成独立的硬件级,每一级只做一位比较,然后传递给下一级。这样,当第一级正在处理第1个数据时,第二级可以同时处理第0个数据的下一位…… 最终每个时钟都能输出一个结果(延迟 N 个周期后)。
三、单步单元里做了什么?
divider_cell就是一级流水:
- 输入:当前“部分被除数”(比除数宽1位,确保相减不借位)、除数、上一级的累积商、原始被除数的低位数据。
- 判断
dividend >= divisor吗?- 是:商 bit=1,新余数 = dividend - divisor;
- 否:商 bit=0,新余数 = dividend。
- 输出:更新后的商(左移1位再+bit)、新余数,以及传递给下一级的原始除数和被除数低位。
注意:dividend的宽度 = M+1,因为余数最大可能接近 2*除数-1,直接用 M+1 位避免溢出。
四、顶层如何串联?
- 第一级:取被除数的最高位(1bit),左边补 M 个 0,得到 M+1 位的初始 dividend。同时传递被除数剩余低位。
- 第二级:第一级的余数(M 位)与原始被除数的次高位拼接成新的 dividend(M+1 位),如此类推。
- 最后一级输出最终的商和余数。
总共需要N 级(N = 被除数位宽)。每级一个时钟,所以延迟 = N 周期。
五、为什么这样设计?
- 可参数化:被除数位数 N 和除数位数 M 可配置,适应不同位宽。
- 流水化:高吞吐量,适合大量连续除法运算。
- 结构规整:易于综合和时序收敛。
六、验证要点
- 功能正确性:每个输出满足
商×除数+余数 = 被除数。 - 流水延迟:第一个结果在 N 周期后出现,之后每周期一个。
- 边界条件:除数为 0(应避免输入),除数为 1,被除数最大/最小值等。
- 资源评估:检查综合后寄存器数量(N 级,每级多个寄存器)。
第三步:详解示例 – 使用流水线除法器并验证
下面给出一个完整的示例,包含如何使用该除法器模块,以及一个简化的 testbench 来理解其行为。
示例:计算 4bit 被除数除以 3bit 除数
假设被除数dividend为 4bit(0~15),除数divisor为 3bit(1~7)。我们例化divider_man,N=4,M=3。
// testbench 片段 `timescale 1ns/1ps module tb_divider; reg clk, rstn, data_rdy; reg [3:0] dividend; reg [2:0] divisor; wire res_rdy; wire [3:0] merchant; wire [2:0] remainder; // 时钟 always #5 clk = ~clk; // 例化 divider_man #(.N(4), .M(3)) u_div ( .clk(clk), .rstn(rstn), .data_rdy(data_rdy), .dividend(dividend), .divisor(divisor), .res_rdy(res_rdy), .merchant(merchant), .remainder(remainder) ); // 产生激励 initial begin clk = 0; rstn = 0; data_rdy = 0; #10 rstn = 1; #10 data_rdy = 1; dividend = 4'd15; divisor = 3'd4; #10; dividend = 4'd10; divisor = 3'd3; #10; dividend = 4'd5; divisor = 3'd2; #10; data_rdy = 0; repeat(10) #10; $finish; end // 监控输出 initial begin $monitor("Time=%0t res_rdy=%b merchant=%0d remainder=%0d", $time, res_rdy, merchant, remainder); end endmodule运行后,你会看到前 4 个时钟无输出,第 5 个时钟开始输出 15/4=3 余 3,接着输出 10/3=3 余 1,5/2=2 余 1。
理解关键:
- 当
data_rdy变高后,数据进入第一级流水。之后每过一周期,数据向后推一级。4 级后,第一个结果出现在res_rdy为高时。 - 你可以连续输入多笔数据,只要大于等于 4 个周期间隔,就能实现流水。
工作中如何应用?
- 何时用:需要高吞吐量的整数除法(如数字信号处理中的某些归一化、坐标转换)。
- 性能权衡:流水级数 = 被除数位宽,延迟和面积随位宽线性增长。对于很少调用的除法,可以用迭代法(状态机)节省面积。
- 替换方案:如果除数是常数,通常用乘法器(乘以倒数)代替除法,更高效。本设计的通用除法器适用于动态除数。
学习建议
- 手动仿真:纸上模拟 4 位除法器的流水过程,画波形。
- 修改参数:尝试不同的 N 和 M,观察资源变化和延迟。
- 编写自校验:像原文那样,用
merchant*divisor+remainder检查输出。 - 对比非流水:写一个状态机实现串行除法,对比吞吐率和面积。
常见错误与注意事项
- 除数不能为 0,testbench 中需避免。
- 商的位宽可能为 N(被除数位宽),但实际最高位可能为 0,输出仍保留完整宽度。
- 流水线首级输入数据要连续,不能有大间隔,否则流水线会“断流”。
- 综合时注意设置时钟频率,确保单级组合逻辑(一次比较+减法)能在周期内完成。
总结
硬件除法器就是竖式除法的硬件流水线。每一级相当于一个老师在批改一位商的答案,然后传递给下一个老师。虽然整个批改过程(延迟)长,但学生们可以连续交卷,老师们流水作业,总吞吐率很高。
作为验证工程师,你要确保每一级的“老师”不判错,还要检查“交接本”(寄存器)不丢失数据。理解了这个设计,你就掌握了流水线和算术单元设计的基础。遇到类似需要高吞吐的数学运算,就可以举一反三。
