
做FPGA工程碰到除法运算第一反应基本就是写个“/”。但项目稍微上点规模时序收敛困难、除法器延迟不可控、位宽一变就要重新综合这些问题一出来你就绕不开Xilinx家的除法器IP核。这个IP在Vivado里叫Divider Generator对应的产品指南就是PG151。PG151这个文档说心里话啃一遍能少踩不少坑但文档写得比较工程化参数又多新手看了容易懵。这篇笔记我就按自己的实际使用过程把PG151里最核心的参数、配置流程、仿真方法和常见坑一次讲清楚。顺便说一下这篇内容适合三类人刚接触Xilinx FPGA、要在工程里做整数除法或定点除法的新手被除法运算拖累时序、想换IP核方案的老手以及做数据采集、信号处理、电机控制这些需要频繁做归一化和比例运算的工程开发者。看完你至少能知道怎么配出一个可用的除法器IP怎么快速验证它以及遇到数据错位、时序不过这类问题时从哪里入手排查。1. 这块IP到底帮你解决了什么问题1.1 为什么不直接用“/”Verilog和VHDL里的“/”符号仿真时用起来非常爽一拍脑门就写下去了。但综合到FPGA里事情就没那么简单。除法本质上是一个多周期迭代运算组合逻辑实现宽位除法会产生巨大的路径延迟位宽到16位以上就很难跑到100MHz以上。而且综合工具对“/”的处理方式很不透明同一个RTL在不同器件、不同版本上综合出来的除法器结构可能完全不一样资源占用和时序性能没法提前预估。更麻烦的是除法器一旦作为组合逻辑插在关键路径上时序不满足的时候你连在哪里插流水寄存器都难以下手。这时候官方除法器IP的价值就体现出来了它内部已经做好了多级流水线延迟是可配置的确定值时序是厂家验证过的资源占用也相对可预估。1.2 Divider Generator的核心能力PG151对应的就是LogiCORE IP Divider Generator这个IP能实现无符号整数除法、有符号整数除法、带小数位的定点除法支持Radix-2、Radix-4、High Radix三种运算结构输入输出都走AXI4-Stream协议。你只需要配好位宽和算法类型它会自动生成带tvalid/tready握手的流水线除法器延迟多少拍、资源用多少LUT生成IP的时候就能在界面里看到。实际操作中这个IP在Zynq、Kintex、Artix这些系列上都能用Vivado里搜“Divider Generator”就能找到。它适用的场景很广光通信里的归一化处理、电机控制里的转速比例运算、传感器信号的标定换算、图像处理里的像素均值计算凡是需要做除法的地方基本都能套上。1.3 谁需要看这篇笔记如果你只是做算法仿真那用“/”完全没问题仿真器对除法支持得很好。但你要上板、要跑时序、要稳定工作就要考虑用IP核。这篇笔记就是从“我想要一个除法器”到“我有了一个能上板的除法器”的完整过程梳理重点放在配置思路和验证方法上你在自己工程里遇到相似问题可以直接照着手。2. 动手配置前先搞懂这几个关键参数PG151配起来并不复杂但有几个参数没理解清楚就直接配后面一定会出问题。我把最关键的几个列出来逐个说明白。2.1 算法类型怎么选Algorithm Type可选Radix-2、Radix-4和High Radix这三者的区别本质上是“用时间换资源”还是“用资源换时间”。Radix-2是基础方式。每个时钟周期处理1位二进制除法逻辑简单、资源占用小不占用DSP48和BRAM但延迟最大。商是32位的话光运算就需要32个时钟周期左右。适合对延迟不敏感、资源紧张的小规模设计。Radix-4是折中方案。每个周期处理2位延迟比Radix-2减半逻辑资源会增加一些。High Radix是高吞吐方案。一次处理4位甚至更多延迟显著降低但需要用到DSP48和BRAM资源开销明显变大。适合宽位宽、高吞吐、对延迟敏感的场景。实际选型的时候我习惯先看延迟需求再看资源余量。如果只是汽车仪表里的占空比换算Radix-2就够了如果是通信基带里的高速归一化直接上High Radix。吃不准的时候先在IP配置界面里把算法类型都试一遍界面会显示对应延迟和资源估算比拍脑袋选靠谱得多。2.2 位宽、符号与余数格式这里有几个容易混淆的配置项。首先是除数和被除数的位宽。PG151允许Dividend Width和Divisor Width分别配置注意这里的位宽是按实际输入数据位宽走不是按“我能接受的运算范围上限”走。比如被除数最大是5000那是13位能表示的范围2^138192就写13没必要填16位。位宽每多一位延迟和资源都会往上走。其次是商和余数的位宽。虽然界面里给了单独的商位宽和余数位宽选项但通常在配置时商位宽要等于被除数位宽余数位宽要等于除数位宽这样才不会出现溢出或截断的问题。具体规则文档上有实际使用中遵循这个原则最省心。然后是符号类型。无符号和有符号要提前定好这决定了IP内部对最高位的处理方式。有符号数除法被除数和除数的最高位是符号位商和余数都按有符号生成。但有个细节要特别注意对有符号除法余数的符号跟被除数一致跟除数的符号无关。也就是说 -7 除以 2商是 -3余数是 -1而不是 1。这个行为跟C语言里的整数除法规则是一样的但如果你数学上期望余数总是非负就得自己额外处理了。2.3 接口与握手信号PG151的接口走的是AXI4-Stream协议最核心的就是那套tvalid/tready握手。这个IP的一个特点是内部没有缓冲队列所以外部模块必须等到 tvalid 拉高且 tready 为高的时候才能真正送进数据。很多新手写testbench的时候直接把数据往输入口一放就开始等结果结果数据根本没进去仿真永远等不到输出的tvalid。另外Remainder Type这个选项也值得多说一句。它有两种Remainder和Fractional。Remainder模式输出的是整数余数比如255/10得到商25余5Fractional模式下余数端口会输出一个小数格式的余数并通过额外的小数位宽来表示精度。如果你做的是定点小数除法比如把0~1范围的ADC值等比换算到电机占空比用Fractional模式会更方便省去在外部再写定点乘法逻辑。3. Vivado里的实际操作流程3.1 创建IP核与基础配置在Vivado里实际操作时先打开IP Catalog搜索“Divider”会看到两个相关IP一个叫Divider Generator另一个是Divider也可能是其他名称看版本。这里认准Divider Generator它的Documentation里会标出PG151。双击打开配置界面第一步在Basic选项卡里设置Algorithm Type按2.1节的原则选先用Radix-2出个最简版本。Divisor Width除数位宽。Dividend Width被除数位宽。Quotient Width商位宽一般等于被除数位宽加除数位宽的最大值或按你的结果范围来。保守起见直接用默认值。Remainder Width余数位宽一般与被除数位宽一致。Remainder Type按需求选Remainder或Fractional。第二步在Interface选项卡里主要设置输入输出是否带tready信号、时钟使能、复位方式。默认情况下这些选项都已经勾好新手别乱动直接保持默认就行。配置完之后界面右下方会显示一个Latency值这个值就是IP从输入到输出需要的时钟周期数做时序设计就要靠这个值对齐数据。如果你用的是可变的Flow Control还需要注意IP文档里关于tvalid/tready时序图的说明。3.2 端口与例化代码配置完成后生成IP例化代码在Vivado的Output Products里可以看到。核心端口就几个aclk、s_axis_dividend_tvalid、s_axis_dividend_tdata、s_axis_divisor_tvalid、s_axis_divisor_tdata、m_axis_dout_tvalid、m_axis_dout_tdata以及可选的tready引脚。有一点要提醒PG151的输入数据是分为两路的一路给被除数一路给除数而不是拼在一个宽数据里。这个跟很多其他IP不一样第一次用的时候容易搞混结果把除数和被除数接到一根总线上算出来的数全不对。例化时s_axis_dividend_tdata和s_axis_divisor_tdata的位宽分别对应你配置的两个输入位宽。m_axis_dout_tdata的输出位宽等于商位宽加余数位宽。比如商是32位余数也是32位那m_axis_dout_tdata就是64位高32位是商低32位是余数。这个拼接顺序在不同配置下可能有差异确认的方法很简单查生成的HDL例化模板里的注释或者直接仿真一把0除法的例子看高低位。3.3 时延到底怎么算关于Latency的计算最稳妥的办法不是自己推导而是看生成后IP核的例化模板和产品指南里的“Latency”章节。不同算法类型的延迟计算公式是Radix-2延迟约为商宽度加3到4拍具体数值与是否启用流水线和时钟使能有关。Radix-4延迟约为商宽度的一半加若干固定拍。High Radix延迟更低但取决于每次处理的位数和内部级数。听起来有点模糊但你不用记这些公式因为Vivado在配置界面会把最终Latency直接显示给你看而且生成IP之后在源码里搜“C_LATENCY”这个常量也能拿到精确值。在实际工程里Latency也不需要手动同步到其他模块。正确做法是用一个FIFO把输入数据缓存起来等除法器输出有效时再根据Latency从FIFO里读出对应的原始数据。这样无论IP延迟怎么配只要Latency参数配置正确数据对齐就不会出问题。4. 仿真验证与数值边界测试4.1 testbench怎么写IP配好之后第一步永远是仿真不是上板。写testbench时要严格按AXI4-Stream时序来驱动输入。一个我常用且可靠的testbench流程是这样的初始化时把aclk拉起来然后所有tvalid信号置低输入数据置为0。等几个周期让IP内部复位完成。给s_axis_dividend_tvalid置高同时把被除数数据放到总线上给s_axis_divisor_tvalid置高把除数放到总线上。保持至少一个时钟周期等tvalid和tready都拉高数据被采样进去。把tvalid拉低等待m_axis_dout_tvalid拉高。m_axis_dout_tvalid拉高后从m_axis_dout_tdata里取出商和余数跟期望值比对。这里最容易犯的错误是第4步只把数据放上去一个周期就撤走没有确认tready是否拉高。PG151在繁忙时会拉低tready数据没被采到但testbench已经继续往下走了最后输出端的tvalid永远不来。诊断方法很简单在仿真波形里看s_axis_dividend_tvalid、s_axis_dividend_tready和s_axis_dividend_tdata三个信号三方同时为高才算真正发送成功。4.2 有符号数与除零边界有符号数测试相比无符号数要多加几组用例。正数除正数如 100 / 7。负数除正数如 -100 / 7。正数除负数如 100 / -7。负数除负数如 -100 / -7。每条用例都要分别检查商和余数。做有符号验证时建议把符号位扩展位和数据进行拼接测试数据用二进制补码形式给出这样可以顺便确认输入的tdata位宽和数据格式有没有配错。除零问题也是必然要测的。PG151在这种输入下不会报错也不会拉低tvalid而是直接输出一个结果。实测下来商输出通常是全1余数就是被除数本身。这不一定是崩溃但绝对不是你想要的运算结果所以上层最好在输入前做除数判零保护不要指望IP自己处理异常。还有一个容易忽略的点由于除法是迭代运算同一组输入想要连续送入时必须等整套握手完成不能像普通寄存器那样每拍都写。否则会有一笔数据被吞掉输出结果与输入对应不上。这个在流水线里很难从波形上一眼看出通常表现为偶尔结果错一次排查起来很费劲。所以设计输入侧逻辑时一定要用valid/ready握手来做流控别贪图方便直接连续驱动数据。4.3 定点小数的除法处理技巧做定点小数除法时除法器本身其实不需要特殊配置技巧主要在被除数侧。核心思想是放大被除数再结合输出的小数位宽截取结果。简单说如果你要把一个0~1范围内的信号按比例换算成另一个定点数一种做法是在除法之前把被除数左移N位变成定点格式然后配置Remainder Type为Fractional让IP输出带小数的余数。这样得到的除法结果就是带分数字段的定点值之后再做位截取或四舍五入精度和效率都很可控。我在实际工程里用过这个方案处理电流环的PWM查表数据1024点归一化表用这个IP换算出占空比效果很稳定时序也能跑满。相比自己在RTL里写迭代除法器工作量省了不止一半。5. 常用场景与性能实测5.1 适合用这片IP的场景除法器IP不是一个高频使用的IP它的适用场景非常集中统计下来主要是这么几类第一类是控制环路里的比例换算比如电机控制、电源控制里的误差归一化和PID系数换算。这类场景除法运算的频率不高但对延迟和稳定性要求比较高用Radix-2配个正常流水线就够跑。第二类是信号处理前端的标定算法比如ADC采样值的量程缩放、传感器非线性补偿中的分段线性插值计算。这类场景的被除数往往是待标定的测量值数据宽度变化频繁移植性和灵活性比自写除法器好不少。第三类是通信协议里的速率匹配或时间戳换算比如把接收字节数换算成时间把时钟周期数换算成波特率分频值。这类场景通常位宽大、运算频率低对资源不敏感但延迟要可控。5.2 资源占用与时序实测我在Artix-7上实际测过一组数据除数和被除数都是32位无符号整数使用Radix-2算法综合后大约用了几百个LUT和FF不占用DSP和BRAM最大时钟频率能跑到200MHz以上。用到High Radix算法时DSP48会被占用BRAM也会被用到最大延迟明显降低但逻辑资源也上涨了。具体资源数跟位宽和配置有关我建议以IP配置界面里的资源估算为准不同版本Vivado、不同器件系列给的数字略有差异不能跨工程照搬。有一点值得说明IP核虽然时延固定但如果你在输出端还接了一些组合逻辑时许余量还是会往下降。所以IP输出之后尽量直接接寄存器再做后续处理别在输出路径上堆组合逻辑。5.3 和自写除法器对比我自己写过一段恢复余数除法器测试下来功能没问题但有个很头疼的问题可复用性太差。每次换一个位宽就要重新修改内部迭代逻辑和计数器参数各个位宽的延迟还不一样仿真和时序分析都要重做一遍。用PG151替代之后位宽变化只需要重新配一次IP接口和延迟自动更新省下来的时间非常多。资源上自写除法器做得精简的话资源可以比IP小一些。但换来的是时序不确定、延迟不确定、调试成本高。工程开发不是做学术研究稳定性和可维护性优先级更高所以我现在的建议是凡是需要真正上板工作、要求时序收敛的除法就直接用PG151。6. 常见问题与避坑速查表6.1 仿真卡死/数据没出来这个问题的绝大多数原因都出在握手信号上。IP核的输入不是简单的写接口不管tvalid有没有拉高IP在未准备好接收数据时会把tready拉低。如果你的testbench只把数据放上去就撤了等于什么都没发生。排查步骤按照顺序来先看s_axis_dividend_tvalid、s_axis_divisor_tvalid和对应tready是不是实现了有效握手再看输入数据位宽和拼接方式是否跟IP配置一致最后看是否等待了足够长的Latency周期。很多时候是因为tvalid持续时间太短帧头没进去后面全白等。用仿真波形把tvalid/tready/tdata三路信号拉出来看问题在哪里一秒就找到了。6.2 时延不对导致数据错位如果你发现除法结果是对的但输出和输入的原始数据对不上号那多半是延迟对齐问题。IP有固定Latency但你的数据处理链路里如果还有FIFO、寄存器或者跨时钟域逻辑延迟就要重新计算。解决思路是不要把数学期望绑定在“多少拍之后输出”上而是用IP输出的m_axis_dout_tvalid作为数据有效的标志同时用同源的控制信号去同步你的数据通路。比如把被除数、除数和相关标志位一起存入FIFO等除法完成后再从FIFO对应深度处读出原始数据。这样不管IP延迟是多少只要控制通路结构不变数据就不会错。6.3 资源占用超过预期Radix-2算法理论上不占用DSP和BRAM但如果你配置成High Radix这两类资源就会被大量消耗。很多项目一开始图延迟低直接上High Radix到布局布线阶段发现BRAM不足才来换算法成本非常高。建议先按Radix-2跑一版测量时序是否满足如果不满足再考虑升级到Radix-4。实在需要High Radix也最好在配置阶段就把资源估算导出来跟工程里其他模块做一次联合评估别等综合完成才后悔。另外IP核生成的输出产物里有资源利用报告上板之前先看一眼心里有底。还有一个常见坑同一份代码在仿真里正常、上板后偶发错误很多人怀疑是除法器IP的问题实际上往往是你把输出数据在组合逻辑里直接用或者忘记等tvalid就直接采样。IP本身是经过验证的多数问题出在数据通路设计上排查时先检查周围逻辑再有针对性地看IP配置。