当前位置: 首页 > news >正文

30.【Verilog】Verilog 除法器设计

第一步:分析与整理Verilog 除法器设计

1. 除法器原理(定点)

  • 与十进制竖式除法类似,以 27 ÷ 5 为例(二进制):
    1. 取被除数高位(与除数同宽,如 3bit),与除数比较。若 ≥ 除数,商为 1,相减得余数;否则商为 0,余数为被除数高位本身。
    2. 将余数与被除数下一 bit 拼接成新数,再次与除数比较,得到下一位商。
    3. 重复直至所有 bit 处理完毕。
  • 注意:商的位宽与被除数相同(除数可能为 1)。第一步比较时,被除数高位应取与被除数最高位对齐的 1bit(例如 27 的二进制11011,取最高位1扩展为001101比较)。
  • 设计采用流水线,延迟周期数等于被除数位宽。

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 endmodule

3. 顶层流水线除法器divider_man

  • 参数N被除数位宽,M除数位宽,内部N_ACT = M+N-1(扩位,防止中间溢出)
  • 结构
    • 首级单元:用被除数最高 1bit 扩展为{M'b0, dividend[N-1]}作为初始 dividend;merchant_ci为 0。
    • 之后串联N_ACT-M个单元(即被除数总位数减除数位宽),每个单元的上一步余数与原始被除数的下一位拼接作为新 dividend。
    • 最后一级输出为最终商和余数。
  • 使用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];//最后一次余数作为最终的余数endmodule

4. 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):

  1. 看被除数的最高 3 位:110(十进制 6)≥101(5)? 是→商最高位=1,余数 = 110-101=1。余数后面再接被除数下一位1,变成11(3)。
  2. 11 < 101 → 商下一位=0,余数不变仍为 11,接下一个被除数位1→ 111(7)。
  3. 111 ≥ 101 → 商位=1,余数=111-101=10(2),接最后一个被除数位0→ 100(4)?等等这里注意:最后一步需取全位。实际上要处理完所有被除数位,商的位数等于被除数位数。

总结:每一步,我们比较“当前余数+下一位”与除数,决定商的一 bit,并更新余数。

二、为什么用流水线?

除法本身是串行的:每一步依赖上一步的结果。如果用状态机串行计算,每输入一个被除数需要 N 个时钟周期才能出结果,吞吐率低。流水线除法器把每一位的判断做成独立的硬件级,每一级只做一位比较,然后传递给下一级。这样,当第一级正在处理第1个数据时,第二级可以同时处理第0个数据的下一位…… 最终每个时钟都能输出一个结果(延迟 N 个周期后)。

三、单步单元里做了什么?

divider_cell就是一级流水:

  1. 输入:当前“部分被除数”(比除数宽1位,确保相减不借位)、除数、上一级的累积商、原始被除数的低位数据。
  2. 判断dividend >= divisor吗?
    • 是:商 bit=1,新余数 = dividend - divisor;
    • 否:商 bit=0,新余数 = dividend。
  3. 输出:更新后的商(左移1位再+bit)、新余数,以及传递给下一级的原始除数和被除数低位。

注意:dividend的宽度 = M+1,因为余数最大可能接近 2*除数-1,直接用 M+1 位避免溢出。

四、顶层如何串联?

  • 第一级:取被除数的最高位(1bit),左边补 M 个 0,得到 M+1 位的初始 dividend。同时传递被除数剩余低位。
  • 第二级:第一级的余数(M 位)与原始被除数的次高位拼接成新的 dividend(M+1 位),如此类推。
  • 最后一级输出最终的商和余数。

总共需要N 级(N = 被除数位宽)。每级一个时钟,所以延迟 = N 周期。

五、为什么这样设计?

  • 可参数化:被除数位数 N 和除数位数 M 可配置,适应不同位宽。
  • 流水化:高吞吐量,适合大量连续除法运算。
  • 结构规整:易于综合和时序收敛。

六、验证要点

  1. 功能正确性:每个输出满足商×除数+余数 = 被除数
  2. 流水延迟:第一个结果在 N 周期后出现,之后每周期一个。
  3. 边界条件:除数为 0(应避免输入),除数为 1,被除数最大/最小值等。
  4. 资源评估:检查综合后寄存器数量(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 个周期间隔,就能实现流水。

工作中如何应用?

  • 何时用:需要高吞吐量的整数除法(如数字信号处理中的某些归一化、坐标转换)。
  • 性能权衡:流水级数 = 被除数位宽,延迟和面积随位宽线性增长。对于很少调用的除法,可以用迭代法(状态机)节省面积。
  • 替换方案:如果除数是常数,通常用乘法器(乘以倒数)代替除法,更高效。本设计的通用除法器适用于动态除数。

学习建议

  1. 手动仿真:纸上模拟 4 位除法器的流水过程,画波形。
  2. 修改参数:尝试不同的 N 和 M,观察资源变化和延迟。
  3. 编写自校验:像原文那样,用merchant*divisor+remainder检查输出。
  4. 对比非流水:写一个状态机实现串行除法,对比吞吐率和面积。

常见错误与注意事项

  • 除数不能为 0,testbench 中需避免。
  • 商的位宽可能为 N(被除数位宽),但实际最高位可能为 0,输出仍保留完整宽度。
  • 流水线首级输入数据要连续,不能有大间隔,否则流水线会“断流”。
  • 综合时注意设置时钟频率,确保单级组合逻辑(一次比较+减法)能在周期内完成。

总结

硬件除法器就是竖式除法的硬件流水线。每一级相当于一个老师在批改一位商的答案,然后传递给下一个老师。虽然整个批改过程(延迟)长,但学生们可以连续交卷,老师们流水作业,总吞吐率很高
作为验证工程师,你要确保每一级的“老师”不判错,还要检查“交接本”(寄存器)不丢失数据。理解了这个设计,你就掌握了流水线和算术单元设计的基础。遇到类似需要高吞吐的数学运算,就可以举一反三。

http://www.jsqmd.com/news/812771/

相关文章:

  • ModTheSpire终极指南:为《杀戮尖塔》构建安全高效的模组生态
  • 人生啊人生
  • 基于LLM与Playwright的智能网页自动化:Web-Use项目实战解析
  • XGBoost在数据中心服务器能耗预测中的实践与优化
  • 大型语言模型开发的环境成本与优化策略
  • 哔哩下载姬DownKyi:你的B站视频下载与处理终极指南
  • 标识标牌制作核心技术拆解与四川优质厂家参考:文化打造标识标牌厂家推荐/景区导视牌厂家推荐/实力盘点 - 优质品牌商家
  • 【2026年华为暑期实习-非AI方向(通软嵌软测试算法数据科学)- 5月13日-第二题- 树的合并】(题目+思路+JavaC++Python解析+在线测试)
  • NeumAI向量检索平台:构建生产级RAG应用的端到端Pipeline实践
  • 通讯录系统数据库设计与实现
  • 2026年民宿烤漆门权威厂家排行 核心能力实测对比 - 优质品牌商家
  • 别再纠结了!Mkdocs、Sphinx、Teadocs、docsify,哪个文档框架更适合你的项目?(附快速上手对比)
  • JESD204B接口技术:高速数据传输与确定性延迟设计
  • 数据科学智能代理规则库:从经验到自动化决策的工程实践
  • 2026年当下,如何挑选一款高效安全的暖风机?从产业格局到品牌推荐 - 2026年企业推荐榜
  • 告别迷茫:用RADE在CATIA V5中创建你的第一个CAA模块(Framework/Module/Workshop详解)
  • 开源数据安全代理规则库:构建高效访问控制与动态脱敏实战指南
  • 阶跃星辰推情感化语音模型
  • 从玩具到工具:Dobot Magician桌面机械臂开箱与Blockly图形化编程初体验
  • Token风暴来袭:科技巨头火拼升级,软件行业重塑,个体革命降临!
  • 2026届最火的十大AI辅助写作方案实测分析
  • Taotoken 用量看板与成本管理功能实际使用感受
  • RedBox容器编排工具:在Docker与K8s间的轻量级生产实践
  • 从BYOD到自建设备:工程师如何掌握硬件定义权与系统设计
  • 淘宝淘金币自动化脚本终极指南:每天节省30分钟,解放你的双手
  • 2026年Q2控糖大米品牌排行:无糖控糖大米、有机五常大米、有机大米价格、有机大米批发、有机大米标准、稻花香有机大米选择指南 - 优质品牌商家
  • StreamCap快速上手:3分钟掌握跨平台直播自动化录制工具
  • Qt For Android实战:从零搭建Qt5.14.2安卓开发环境与避坑指南
  • 基于MCP协议构建AI图像生成服务器:让Claude等助手直接画图
  • AceForge:基于约定优于配置的现代化项目脚手架工具深度解析