资讯动态

FPGA除法器实现指南:从原理到IP核配置与Verilog实战

发布时间:2026/10/17 9:48:58 来源:尧图企业网站定制
在FPGA上做除法十个人有九个会皱眉头。加法、乘法随手写触发器一接就是个流水线唯独除法器既费资源又拖时序搞不好还跟仿真对不上。这篇应用笔记不打算从门级电路讲起而是把我自己在实际项目中用除法器的经验整理一遍从为什么除法在FPGA里这么麻烦、常见的几种实现方案到怎么配IP核、怎么手写一个轻量级的移位减法除法器再到调试中踩过的坑。适合刚接触FPGA、想搞懂除法器原理的同学也适合项目里要做定点除法的工程师参考。1. 为什么FPGA里除法器是个麻烦事1.1 FPGA“天生”偏科乘法有硬件除法靠人肉FPGA的本质是查找表加触发器再加上一些专用的硬核资源。你去查任何一款主流芯片的资源清单都会发现里面有乘法器、有块内存、有高速串行收发器但唯独没有除法器硬核。这不是芯片厂商偷懒而是除法的电路实现代价确实高乘法器可以靠一组与门阵列加进位链直接怼出结果延迟固定、资源可控除法却需要“逐位试商”本质是一个迭代过程。你回想一下自己手算长除法被除数一位一位往下挪每次试商“够不够减”够就上1不够就上0。电路实现也是一样的逻辑每算出一位商至少需要一个时钟周期算8位商就得8个周期。位数一多延迟线性增长。更麻烦的是除法里面还带着“比较余数是否小于除数”这种判断这个判断在硬件里既消耗组合逻辑又影响关键路径时序稍紧就跑不上频率。我见过不少初学者第一次在FPGA里写除法直接拿/运算符一写综合器要么报错要么悄悄生成一个极其庞大的组合逻辑。结果是资源爆掉、时序红线、仿真里看着对上板就乱。核心原因就是没有意识到除法不是“一条指令”而是一整套状态机加数据通路的组合。1.2 除法器最常见的应用场景与需求拆解除法器在FPGA项目里的出现频率其实不低只是很多人没意识到自己要用的是除法。拿我接触过的项目举例数字信号处理里的归一化比如把采集到的最大值作为分母把所有样本除以这个值映射到0到1之间。控制系统里的比例换算PID控制器的输出往往要除以某个系数才能匹配执行机构的量程。测频计频测量脉冲频率时用计数值除以闸门时间或者用系统时钟除以计数值。图像处理里的透视变换、坐标变换求投影系数时经常碰到除法。这些场景有一个共同特点要求的精度不一定是浮点级大多数是定点整数除法能接受一定范围内的小数截断。这个特点非常重要因为它直接决定了方案选型——如果只需要8位整数商加几位小数手写状态机完全够用如果要求32位浮点除法那就必须考虑IP核或者更复杂的CORDIC类方案。所以拿到需求的第一步不是打开代码编辑器而是先问三个问题被除数和除数的位宽是多少有符号还是无符号结果要的是整数商还是带小数的定点格式允许多少周期的计算延迟数据是一笔一笔来还是连续流水进来这三个问题答完方案基本就定了一半。1.3 延迟、吞吐率、资源的三方权衡除法器的设计永远绕不开三个指标延迟latency、吞吐率throughput、资源占用。延迟是“输入进去到输出出来要几个周期”吞吐率是“每几个周期能进一笔新数据”资源则是花掉的LUT、触发器和DSP块数量。这三者很难兼得。延迟低的方案往往组合逻辑巨大资源爆炸流水线化可以提高吞吐率但会引入多级寄存器延迟反而增加想省资源就用迭代除法但算一笔要等好多个周期。拿一个具体的例子说16位无符号除法用一位一位试商的方式算一笔需要16个周期左右资源很少就两个窄加法器和少量控制逻辑。如果把它拆成16级流水线每级处理一位那么吞吐率能翻到每周期一笔但触发器数量会翻好几倍而且每一级的余数传递逻辑会让关键路径变长最高频率可能反而下降。工程上没有“最好的除法器”只有“当前场景最合适的除法器”。这也是我写这篇笔记的初衷把每种方案的适用边界讲清楚你照着需求去对应就行。2. 四种实现方案原理和适用场景2.1 恢复余数法把手工长除法搬进电路恢复余数法是最直观的除法器实现本质上就是把纸面上的长除法翻译成硬件语言。它的核心流程分四步把被除数逐位移入一个“余数寄存器”每次移入一位后用当前余数尝试减去除数如果够减商位写1余数保留减法后的结果如果不够减余数要“恢复”到减法前的值商位写0。你算一下8位除法从最高位开始一位一位处理被除数正好8个周期出结果。控制逻辑也很简单一个计数器加一个比较器就够。这种方案的优点是代码逻辑和手算过程一一对应几乎不会写错调试也容易。缺点是“恢复”这一步浪费了时间每次不够减时减法已经算完了又要把除数加回去等于白折腾一拍。而且这里的减法器位宽要随被除数位数增长组合路径变长频率受限。我一般把这种方案当作“快速实现兜底方案”。如果你的项目里除法不是性能瓶颈只是偶尔算一次用恢复余数法完全够几天就能调完。后面章节里我会给出完整代码。2.2 非恢复余数法用符号判断替代“恢复”非恢复余数法也叫加减交替法是对恢复余数法的改进。它的核心变化在于不够减的时候不恢复余数而是让余数保持为负下一次迭代时根据当前余数的正负决定加除数还是减除数。规则是这样的如果当前余数为正下一步做减法商位记1如果当前余数为负下一步做加法商位记0。这样每一步只做一次加法或减法不需要“恢复”操作省掉一拍。但代价是控制逻辑稍复杂余数寄存器要用带符号数表示而且最后可能需要一个修正周期如果结束时余数为负要加一次除数把它修正成正余数。数学上两种算法等价非恢复法只是把“恢复”操作分散到了后续步骤里。在硬件上它的好处是省掉了恢复路径上那个额外加法器关键路径更短缺点是处理有符号数时需要小心符号扩展、补码运算、最后修正这几个环节容易出bug。老实说工程上手写非恢复法的情况比恢复法少因为两者资源差异并不大恢复法代码更直白。但如果你的除法器位宽很大、要跑流水线非恢复法的优势就体现出来了——组合逻辑更简单容易吃频率。2.3 查表法、CORDIC与SRT算法换个思路解决除法除了上面两种基础的迭代法还有一些经过优化、面向特定场景的算法。查表法适合除数固定、被除数可变的情况。比如你要算x/10可以预先算好1/10的小数定点值然后把除法变成乘法x * 0.1。更极端的做法是直接用LUT存储一张结果表输入被除数输出商。这种方案延迟低到一个周期但资源随位宽指数增长——16位被除数对应65536个表项基本不可行。所以查表一般只用在位宽小于8的场合。CORDIC坐标旋转数字计算法是一种通过迭代旋转逼近目标值的算法它以加法、移位和查表为基本操作不需要乘法器非常适合在FPGA上做三角函数、开方、对数和除法。CORDIC做除法的原理是把这个除法问题转换为坐标平面的旋转迭代迭代次数越多精度越高。它的好处是资源结构和乘法无关、适合在DSP资源紧张的芯片上跑缺点是迭代次数和延迟较大位数越多越明显。SRT算法是介于“逐位迭代”和“高基数查表”之间的一种折中。它通过查表的方式一次决定多位商比如一次决定两位、三位用更少的迭代次数换取更高的吞吐率。高端芯片的浮点除法器内部往往就是SRT系列的改进算法。但在普通FPGA上SRT的查表逻辑会消耗大量LUT除非你的除法器位宽特别大、性能要求又特别高否则不太划算。2.4 IP核大多数人最终的归宿聊完手写方案必须说一句实在话大多数FPGA项目里除法器最终都用IP核解决。几乎每个主流FPGA厂商的IDE里都自带除法器IP名字一般叫Divider Generator之类。这类IP内部已经把高基数算法、流水线级数、资源优化做完了你只需要填几个参数就能拿到一个经过充分验证、时序收敛的模块。用IP核的好处很明显内部算法经过优化同样的位宽和延迟要求下资源往往比手写更少自带仿真模型和时序约束不容易出现功能或时序问题接口标准化有握手信号、有结果有效标志接入系统很省事。代价也不是没有。IP核的参数繁多配置错了很难察觉不同版本工具生成的IP核接口有差异代码移植麻烦某些IP核还要求一定的最小延迟导致输出时序和你预想的不一样。更重要的是IP核生成的是一个黑盒你不懂内部原理出了问题都不知道该从何查起。所以我一直有个观点IP核可以用但你必须知道里面大概是什么原理。配置的时候能明白每个参数在影响什么出了问题才能判断是IP配置错了还是外部逻辑错了。3. 除法器IP核配置实操参数不是乱填的3.1 无符号/有符号、位宽、小数格式怎么选我以最常见的除法器IP核配置界面为例讲讲每一个关键参数背后的影响。首先是操作数格式。大多数IP会问你是Unsigned还是Signed。这个选择影响的不只是符号扩展逻辑还决定了你输入数据的取值范围和结果的解析。工程里经常有人选了无符号结果输入数据是补码形式的负数算出来的商完全不对。反过来如果你确实只用正数别画蛇添足选有符号——有符号模式下IP通常会多占资源去做符号处理。然后是位宽。被除数和除数的位宽要按实际数据范围定不要图省事统一填32位。位宽每增加一位延迟和资源都可能成倍增长。我见过有人把计数器的16位值和常数10相除硬是填了32位的除数和被除数结果IP核占的资源翻了三四倍延迟多了十几个周期其实根本没必要。还有一个容易被忽略的选项输出是“整数余数”还是“小数流水”。如果只要整数商和余数那就选余数模式如果你想要带小数的定点结果需要在配置里指定小数宽度。比如计算结果要保留4位小数IP核会内部把被除数左移4位再除最后给出带4位小数的定点商和余数。这个机制理解起来不难但很容易混淆它本质上是通过放大被除数来获得小数精度代价是需要额外的位宽和延迟。3.2 基数Radix与延迟之间的博弈IP核配置里还有一个参数叫“基数”常见的有Radix-2、Radix-4、Radix-16有些工具里叫“每次迭代位数”。基数本质上决定了一次迭代能算出几位商。Radix-2就是传统的一位一迭代实现最简单资源最少延迟最长。Radix-4一次算两位商延迟能砍半但内部需要判断更多条件、组合逻辑更复杂。Radix-16一次算四位商延迟最短但资源占用明显上升。我做一个8位无符号除法的对比测试时的数据大致如下基数迭代次数相对延迟相对资源Radix-28高低Radix-44中中Radix-162低高注意这里的迭代次数只是算法层面的估算IP核实际还会加上输入寄存、输出寄存、握手逻辑的额外周期所以最后给出的latency参数会更大一些。选择基数时我的建议是默认用Radix-2只有在时序吃紧、延迟预算不够时才考虑提高基数。很多人一上来就选Radix-16结果资源翻倍综合一处就在时序报告里看到一堆红色。3.3 三件必须核对的事输入握手、余数输出、除数为0配置完IP核生成之前建议再检查三件事这三件事是我踩过坑才养成的习惯。第一确认输入数据是在握手信号为高时才有效。大多数除法器IP都有类似tvalid、tready的AXI-Stream风格握手信号。你必须在外部逻辑里保证数据稳定且握手有效才能被正确采样。如果只给数据不管tvalidIP可能根本不会开始计算。第二确认余数端口的语义。有的IP输出余数有的输出小数有的两个都出。输出小数模式时低位部分是小数位但小数位本身又可能分为“截断”和“四舍五入”两种处理方式。不确定时用几组手工可算的数据做仿真验证一下比如100/7、99/7这种带余数的一算就知道IP是怎么处理尾数的。第三明确除数为0时的行为。IP核手册通常会写“结果未定义”。外部逻辑必须自己拦截除数为0的情况。否则除法器会一直迭代不出结果或者输出一个垃圾值在工程上可能引发严重的主逻辑错误。4. 手写一个轻量级移位减法除法器4.1 状态机与数据通路设计思路如果你不想依赖IP核或者项目里只是偶尔需要一笔除法完全可以用几十行Verilog写一个轻量级除法器。下面我自己常用的这个模块基于恢复余数法结构清晰、可读性好稍微改改就能放到项目里用。整体设计分三部分状态机IDLE等待启动CALC逐位运算DONE输出结果数据通路被除数寄存器同时扮演“待处理数据”和“商寄存器”的双重角色每一拍左移一位最高位移入余数寄存器的最低位组合逻辑根据“移位后余数减除数”的符号位决定商位写1还是写0以及是否恢复余数。这里有个容易绕晕的点被除数和商共用一个寄存器。启动时寄存器里放被除数每个周期左移一位左边移出的位进入余数寄存器右边则根据试商结果写入新位。N个周期后被除数的原始数据全部移出寄存器里剩下的正好就是商。这个过程和手工长除法的“一位一位拖下来”完全对应。4.2 完整Verilog代码与关键点注释下面代码实现的是无符号整数除法参数W可以按需调整。我把关键点的注释都写在旁边方便直接抄去用。module divider_restoring #( parameter W 8 )( input wire clk, input wire rst_n, input wire start, // 启动信号高电平触发一笔除法 input wire [W-1:0] dividend, // 被除数 input wire [W-1:0] divisor, // 除数调用方需保证非零 output reg [W-1:0] quotient, // 商 output reg [W-1:0] remainder, // 余数 output reg valid // 结果有效高电平持续一个周期 ); localparam IDLE 2d0; localparam CALC 2d1; localparam DONE 2d2; localparam CNT_W $clog2(W) 1; reg [1:0] state; reg [CNT_W-1:0] cnt; reg [W-1:0] q; // 商/被除数共用移位寄存器 reg [W-1:0] d; // 除数锁存 reg [W:0] r; // W1位余数寄存器多出的1位用于符号判断 wire [W:0] r_shift {r[W-1:0], q[W-1]}; // 左移余数并移入被除数当前最高位 wire [W:0] r_sub r_shift - {1b0, d}; // 试减 wire [W-1:0] q_shifted {q[W-2:0], 1b0}; // 商寄存器左移低位等待写入 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; valid 1b0; quotient d0; remainder d0; cnt d0; q d0; d d0; r d0; end else begin case (state) IDLE: begin valid 1b0; if (start) begin q dividend; d divisor; r d0; // 余数清零 cnt W - 1; // 需要迭代W拍 state CALC; end end CALC: begin // 根据试减结果决定商位和余数 if (r_sub[W]) begin // 不够减恢复余数为移位前的值商位写0 r r_shift; q q_shifted; end else begin // 够减保留减法结果商位写1 r r_sub; q q_shifted | 1b1; end cnt cnt - 1; if (cnt 0) begin state DONE; // 最后一拍结束下一周期进入输出状态 end end DONE: begin quotient q; // 此刻q中存的是计算完成的商 remainder r[W-1:0]; // 余数取低W位 valid 1b1; state IDLE; end default: state IDLE; endcase end end endmodule几点说明余数寄存器是W1位因为试减后的差范围可能超出W位最高位用来判断正负。输出余数时只取低W位。q寄存器启动时存被除数迭代完成后存商注意不要在第1到第W-1拍之间去读q的值那时候里面的数据是“被除数残余加已经算出的部分商”不是最终结果。这个模块每笔除法占W1个周期W拍迭代加1拍输出。如果连续启动第二次的start可以在valid拉高的那一拍给出这样能流水衔接但两笔之间会有1拍空档不是全流水。4.3 仿真验证与测试向量推荐这种状态机式的模块仿真其实比写代码还重要。我推荐至少验证以下几组数据整除场景100/5商20余0验证正常路径余数场景100/7商14余2验证余数逻辑被除数小于除数3/7商0余3验证商为0的边界大被除数小除数255/1商255余0验证迭代极限除数接近被除数15/14商1余1验证试商边界。写testbench时注意start信号拉高后要能维持到模块内部锁存完成一般拉高一拍即可但你得保证在valid拉高之前不要再变化输入数据。仿真波形里重点看两个点一是q寄存器在迭代过程中逐位变成商的过程二是r寄存器减法后的符号位变化。这两条线看懂了恢复余数法就彻底理解了。5. 实际项目中踩过的坑和排查方法5.1 问题速查表把我在多个项目里遇到的除法器相关问题整理成一张速查表方便你遇到类似现象时直接对照。现象可能原因排查方法结果一直是0start只给了一拍还没锁存完就被清掉检查start时序确保在IDLE状态稳定置位商和余数都不对但波形看似正常被除数和除数接反了对照寄存器分配确认与IP配置一致数据跑到后面阶段全乱了除法器延迟没补偿其他路径已经先到了用FIFO或者延迟链对齐各路数据仿真通过上板偶尔出错复位顺序问题除法器在复位期间被误触发检查复位时序加启动锁存资源异常变大位宽填得过大或基数选得过高按实际数据范围重新配置位宽和基数除数为0时输出不定值没做除零保护前端逻辑增加除数非零校验小数除法尾数不对对小数格式理解错误用100/7这类数据仿真确认四舍五入方式5.2 延迟不匹配导致的数据错位这是我在多路并行数据处理项目里踩过最深的坑。当时有三路数据并行处理一路做了除法另外两路只是简单滤波我用延迟链给滤波路由做了对齐。结果仿真的时候发现除法那路输出的位置总比预期晚了几个周期而且不是固定晚一拍是晚了一个跟配置相关的周期数。排查后才发现我当时配的除法器IP核内部有输入寄存、若干级迭代流水线、输出寄存总latency不是整数倍而是跟位宽和基数相关的一个公式。我拍脑袋估了个延迟值去对齐自然对不上。解决方法是从IP配置界面读准确的latency参数如果IP不显示就写一个简单testbench用计数器实测从拉高start到valid拉高数一拍。这个方法虽然土但永远有效。数据通路并行的项目务必在架构阶段就把各路延迟算清楚不要留到调试阶段再补救。5.3 除数为0、溢出与符号处理除数为0无论在哪个除法器里都是未定义行为。IP核手册一般会明确说“结果未定义”手写模块则会出现迭代不收敛、输出漂移的情况。工程上的标准做法是在除法器前面加一个判断divisor 0时给输出饱和值同时拉高一个错误标志。饱和值取最大值还是0取决于业务逻辑但无论如何不能让垃圾值悄悄混进后续计算。溢出问题主要出现在有符号除法里。比如-128 / -1数学结果是128但8位有符号数的最大值是127结果就会溢出。IP核可能会按照补码规则给出一个奇怪的负值。有符号除法一定要把结果范围算清楚必要时把位宽扩展一位。关于符号处理手写除法器时最简单可靠的做法是先把有符号数转绝对值用无符号除法算出结果最后根据输入符号调整商和余数的符号。虽然多几步逻辑但不容易在补码运算上出bug。数学规则是商两个操作数同号为正异号为负余数符号跟随被除数。5.4 资源莫名变大的排查思路有一次我帮一个同事看一个除法器相关的设计他报告说资源翻了三倍但逻辑代码明明只加了一个除法运算。打开综合报告一看那个除法被综合成了上百级的组合逻辑比较器占了几千个LUT。原因是他直接把/运算符写了进去而那个除数是一个寄存器变量综合器无法优化成常数除法只能老老实实做一个通用除法器。排查这类问题我的思路是先在综合报告里找到对应模块看被综合出的资源类型如果是大量LUT和比较器大概率是组合逻辑除法确认是否用了/运算如果用了/且除数不是常数赶紧替换成IP核或手写状态机方案如果除数是常数直接用移位加加法实现比如除以2、除以10这类可以用移位和加法组合完成。很多资源问题都不是“除法器本身大”而是它的实现方式完全不符合预期。打开综合网表看一眼往往比盲猜高效得多。6. 除法器进一步优化面积、速度和精度6.1 高基算法与流水线化改造手写状态机除法器的吞吐率是每W1拍一笔如果数据是一股连续流进来这个速度往往不够用。最常见的优化是流水线化。流水线的思路是把迭代过程拆成多级每一级专门处理一位或一个固定步骤。比如8位除法拆成8级流水线每一级完成一次“左移、试减、写商位”。数据可以逐拍进入流水线结果逐拍输出吞吐率提升到每周期一笔代价是触发器资源大约变成原来的8倍。还有一个折中方案是“半流水线”把8位迭代拆成2级每级做4次迭代吞吐率提升到每2拍一笔资源翻倍但没那么夸张。实际项目中如果吞吐率要求不高不建议盲目全流水线化资源成本和功耗回报不成正比。高基算法的优化则是从数学层面减少迭代次数。手写实现时每拍可以处理2位甚至4位商逻辑上需要更复杂的“试商表”。这个方案的工程实现比流水线化难不少容易在边界条件上出错只建议在IP核无法满足延迟预算时才考虑。6.2 CORDIC、多路复用与查表替换如果你的FPGA芯片DSP资源紧张、LUT有富余CORDIC是一个很好的备选方案。CORDIC做除法的迭代次数固定和位宽相关不依赖乘法器。它还能同时算出开方、三角函数等结果一套硬件多路复用适合做多功能运算单元的场景。多路复用是另一个实用技巧如果系统里有多个除法需求但它们的计算时间不重叠可以用一个除法器加上一个仲裁逻辑轮流处理多路数据。比如两路数据各10拍算一笔合用一个16拍延迟的除法器完全可行。这个思路能省一半资源代价是控制复杂度上升。查表替换只适合除数是常数的场景。除以常数时可以把1/常数预先算成定点小数保存在一个只读存储器里除法变成乘法和移位。16位被除数乘16位常数加一次移位资源远小于通用除法器。图像的归一化运算如果分母固定就可以这么优化。6.3 用乘法代替除法乘以倒数再修正最后聊聊一个工程上经常用到的“歪招”把除法换成乘法。如果你要算x / 7可以先算x * (1/7)而1/7是一个无限循环小数在定点数里只能用近似值。取1/7的定点近似为0.142857乘以x后再做一次修正比如加上一个小的偏移来抵消截断误差。这种做法在很多DSP算法里都有应用精度可控速度和资源都比除法器好得多。具体做法是把1/除数按需要的精度量化成带符号定点常数比如16位定点用一个乘法器做定点乘法对乘法结果做右移和舍入。误差来源主要是常数近似误差和乘法截断误差可以通过增加常数位数和采用舍入而不是截断来改善。我做一个8位除法场景时用16位倒数常数加一次乘法替代除法误差控制在千分之一以内资源只是原来除法器的零头。但这里有个前提除数必须固定。除数一旦变成变量倒数也要跟着变就得实时查表或者重新计算倒数反而得不偿失。最后分享一个我自己的习惯。接到带除法的需求我一般先估算三件事数据率多少、延迟允许多少、位宽和符号是什么。这三件事能帮我快速排除掉一大半不合适的方案。能用常数优化的就用常数优化必须用通用除法的先看IP核手写状态机只作为最后的兜底手段。这个判断顺序帮我少加了不少班也少看了好几次半夜两点综合失败以后吓人的错误报告。希望这篇笔记也能让你在FPGA里遇到除法时心里先有个底。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑