资讯动态

FPGA等精度测频Verilog实现:闸门同步、计数器设计与误差分析

发布时间:2026/10/14 18:06:47 来源:尧图企业网站定制
简介这份资源是2015年全国大学生电子设计竞赛F题的等精度测频完整工程面向学习FPGA数字系统设计与Verilog编程的学生及参赛者解决如何用50MHz采样时钟精确测量输入信号频率并通过UART回传结果的问题。压缩包共428个文件、约31.63MB以Quartus工程文件为主包含34个Verilog源码、大量cdb与hdb编译数据库、tdf与qip等工程配置、rpt与logdb报告日志以及jic、sof等下载烧录文件完整保留了从源码到综合适配的全过程。已有1319人学习下载。读者可从中获得测频计核心逻辑、UART串口通信模块、时钟分频与均值处理等可复用代码并借助工程文件理解等精度测量、同步设计与资源优化的实现思路是数字系统设计入门到进阶的典型参考实例。1. 等精度测频到底解决了什么痛点做过频率测量的工程师大多踩过同一个坑用固定闸门去数脉冲低频信号误差大得离谱高频信号又浪费了计数器的位宽。等精度测频这套方法核心思路是让闸门时间跟着被测信号的周期走而不是死守一个固定值。2015年电赛F题把这道题摆到了台面上要求用FPGA实现一套能在宽频范围内保持恒定相对误差的测频系统。它适合谁正在做数字频率计、传感器信号采集、或者需要在FPGA上做高精度时间间隔测量的从业者。你不需要昂贵的频率基准只要有一路稳定的参考时钟就能把相对误差压到跟参考时钟同量级。这篇文章会把Verilog实现拆开讲清楚闸门怎么同步、计数器怎么设计、误差怎么算、代码怎么调。读完你能直接在自己的板子上复现一套可用的等精度测频模块。2. 等精度测频的Verilog实现从闸门同步到计数输出2.1 为什么固定闸门测频会翻车先算一笔账。假设闸门时间固定为1秒被测信号频率是10Hz那么闸门内只能数到10个脉冲±1个字的计数误差就是10%的相对误差。如果被测信号是10MHz同样1秒闸门能数到一千万个脉冲±1个字的误差只有0.00001%。问题在于你不可能为每个频段都换一个闸门时间。等精度测频的做法是用被测信号的边沿去同步闸门的开启和关闭。实际闸门时间不再等于预设值而是被测信号周期的整数倍。这样被测信号的计数误差始终是±1个字而实际闸门时间的误差来自参考时钟的±1个字。最终相对误差由参考时钟的精度决定与被测信号频率无关。这里有一个关键前提参考时钟频率必须远高于被测信号频率否则实际闸门时间的量化误差会反过来主导总误差。常见做法是参考时钟取100MHz以上被测信号上限控制在参考时钟的1/10以内。2.2 闸门同步与计数器架构整个模块分三块闸门同步逻辑、参考时钟计数器、被测信号计数器。闸门同步逻辑负责把预设的闸门时间比如1ms、10ms、100ms同步到被测信号的时钟域产生一个实际闸门信号。参考时钟计数器在实际闸门有效期间对参考时钟计数被测信号计数器对被测信号计数。// 闸门同步将预设闸门时间同步到被测信号时钟域 module gate_sync ( input wire clk_ref, // 参考时钟100MHz input wire clk_test, // 被测信号作为时钟使用 input wire rst_n, input wire gate_preset, // 预设闸门使能来自参考时钟域 output reg gate_actual // 实际闸门被测信号域 ); reg gate_preset_d1, gate_preset_d2; // 两级同步器消除亚稳态 always (posedge clk_test or negedge rst_n) begin if (!rst_n) begin gate_preset_d1 1b0; gate_preset_d2 1b0; end else begin gate_preset_d1 gate_preset; gate_preset_d2 gate_preset_d1; end end // 在实际闸门开启期间等待被测信号的下一个上升沿关闭 always (posedge clk_test or negedge rst_n) begin if (!rst_n) gate_actual 1b0; else if (gate_preset_d2 !gate_actual) gate_actual 1b1; // 开启实际闸门 else if (!gate_preset_d2 gate_actual) gate_actual 1b0; // 关闭实际闸门 end endmodule这段代码的逻辑说明gate_preset来自参考时钟域是一个宽度等于预设闸门时间的脉冲。经过两级同步器后进入被测信号时钟域然后在实际闸门的上升沿和下降沿都对齐到被测信号的边沿。参数说明clk_ref建议不低于100MHzclk_test是被测信号直接接入全局时钟引脚。两级同步器是必须的少一级就可能出现亚稳态导致闸门宽度抖动。2.3 两个计数器的Verilog写法与位宽选择参考时钟计数器和被测信号计数器的结构相同区别在于时钟源和位宽。参考时钟频率高同样闸门时间内计数值大位宽要够。被测信号频率低位宽可以小一些但为了统一处理通常取相同位宽。// 参考时钟计数器在实际闸门期间对clk_ref计数 module counter_ref ( input wire clk_ref, input wire rst_n, input wire gate_actual_sync, // 同步到clk_ref域的实际闸门 output reg [31:0] cnt_ref ); always (posedge clk_ref or negedge rst_n) begin if (!rst_n) cnt_ref 32d0; else if (gate_actual_sync) cnt_ref cnt_ref 1b1; else cnt_ref cnt_ref; // 闸门关闭后保持供读取 end endmodule // 被测信号计数器在实际闸门期间对clk_test计数 module counter_test ( input wire clk_test, input wire rst_n, input wire gate_actual, output reg [31:0] cnt_test ); always (posedge clk_test or negedge rst_n) begin if (!rst_n) cnt_test 32d0; else if (gate_actual) cnt_test cnt_test 1b1; else cnt_test cnt_test; end endmodule逻辑说明两个计数器都在各自时钟域内工作闸门有效时累加无效时保持。gate_actual_sync需要把被测信号域的实际闸门再同步回参考时钟域同样用两级同步器。参数说明位宽取32位是保守做法如果参考时钟100MHz、最大闸门1秒计数值最大1亿27位就够但32位留足余量且对齐字节边界。被测信号计数器同理。2.4 频率计算公式与误差分析测频结果的计算公式f_test (cnt_test × f_ref) / cnt_ref其中f_ref是参考时钟频率cnt_ref是参考时钟计数值cnt_test是被测信号计数值。这个公式成立的前提是实际闸门同时作用于两个计数器且闸门边沿对齐到被测信号周期。相对误差分析被测信号计数误差为±1个字参考时钟计数误差为±1个字。总相对误差约为Δf/f ≈ ±(1/cnt_test 1/cnt_ref)当cnt_test足够大时比如大于1000第一项可以忽略误差主要由参考时钟的±1个字决定。这就是等精度测频的核心优势相对误差恒定不随被测频率降低而恶化。实际工程中cnt_test在低频时可能只有几十这时候第一项就不能忽略了。解决办法是增大预设闸门时间让低频信号也能积累足够的计数值。常见做法是预设闸门时间取10ms、100ms、1s三档根据被测信号频率自动切换。3. 等精度测频的避坑与排查5个血泪教训3.1 闸门信号跨时钟域没做同步计数结果随机跳变现象测频结果偶尔跳变跳变幅度没有规律复位后有时正常有时不正常。原因gate_preset从参考时钟域直接拉到被测信号域使用没有做跨时钟域同步。亚稳态导致闸门宽度随机变化两个计数器的计数窗口不一致。解决所有跨时钟域信号必须经过至少两级同步器。如果信号宽度可能小于目标时钟周期还需要做脉冲展宽或握手处理。上面代码里的gate_sync模块就是干这个的两级触发器缺一不可。3.2 计数器位宽不够高频段直接溢出归零现象被测信号频率较高时测频结果突然变成很小的值或者零。原因被测信号计数器位宽选小了。比如选了16位被测信号10MHz、闸门1秒计数值需要达到一千万16位最大才65535早就溢出了。解决先算最大计数值。cnt_max f_max × gate_max。参考时钟100MHz、闸门1秒cnt_ref最大1亿需要27位。被测信号如果上限10MHz同样需要27位。统一取32位最省心综合工具会自动优化掉未使用的位。3.3 实际闸门关闭后计数器没有保持读数时已经归零现象读取测频结果时发现计数器值总是零或者一个很小的数。原因计数器在闸门关闭后继续计数或者被复位了。如果代码写成else cnt 0闸门一关计数就没了后续读取逻辑根本读不到有效值。解决闸门关闭后计数器必须保持当前值直到下一次测量开始前再复位。上面代码里else cnt cnt就是保持。读取逻辑要在闸门关闭后、下一次闸门开启前完成数据锁存。3.4 参考时钟和被测信号共用地线导致串扰现象测频结果在特定频率点附近误差突然增大换一块板子又正常。原因参考时钟和被测信号走线太近共用地线阻抗导致串扰。被测信号的谐波耦合到参考时钟线上引起参考时钟抖动。解决PCB布局时参考时钟走线远离被测信号输入地平面完整分割。被测信号输入加施密特触发器整形消除边沿抖动。参考时钟源选用低抖动的晶振不要用FPGA内部PLL分频出来的时钟做参考。3.5 闸门时间切换时没有等待当前测量完成现象自动切换闸门档位时测频结果出现一次错误值然后恢复正常。原因闸门时间切换逻辑在测量过程中直接改变了预设值导致当前测量周期的实际闸门宽度异常。解决闸门切换必须等待当前测量周期完全结束。用一个状态机控制测量中→等待闸门关闭→锁存数据→切换档位→启动下一次测量。不要在闸门有效期间修改任何预设参数。4. 把等精度测频做到极致一个自动量程的工程技巧前面讲的都是固定闸门时间的做法。实际产品里被测信号频率可能从几Hz到几十MHz固定闸门时间没法同时兼顾低频精度和高频速度。我一般会加一个自动量程逻辑先用一个短闸门快速估测频率范围再根据估测结果选择最合适的闸门时间。具体做法是设三档闸门1ms、10ms、100ms。上电后先用1ms闸门测一次如果cnt_test小于1000说明信号频率低于1MHz切换到10ms档如果还小于1000切换到100ms档。如果cnt_test大于100000说明信号频率很高保持1ms档甚至缩短到0.1ms档。这样在保证精度的前提下测量速度也能接受。// 自动量程状态机简化版 module auto_range ( input wire clk_ref, input wire rst_n, input wire [31:0] cnt_test, input wire measure_done, // 一次测量完成脉冲 output reg [1:0] gate_sel, // 00:1ms 01:10ms 10:100ms output reg range_locked // 量程锁定标志 ); localparam IDLE 2d0, EVAL 2d1, LOCK 2d2; reg [1:0] state; always (posedge clk_ref or negedge rst_n) begin if (!rst_n) begin state IDLE; gate_sel 2b00; range_locked 1b0; end else begin case (state) IDLE: begin if (measure_done) state EVAL; end EVAL: begin if (cnt_test 32d1000) begin if (gate_sel 2b10) gate_sel gate_sel 1b1; // 增大闸门 else state LOCK; end else if (cnt_test 32d100000) begin if (gate_sel 2b00) gate_sel gate_sel - 1b1; // 减小闸门 else state LOCK; end else begin state LOCK; end end LOCK: begin range_locked 1b1; if (!measure_done) state IDLE; // 等待下一次测量 end endcase end end endmodule逻辑说明状态机在每次测量完成后评估cnt_test如果太小就增大闸门时间太大就减小闸门时间直到落在合理区间后锁定量程。参数说明cnt_test的阈值1000和100000是根据经验取的对应相对误差约0.1%和0.001%。gate_sel输出到闸门生成模块控制预设闸门宽度。range_locked置位后不再自动切换除非收到复位或手动切换命令。这个自动量程逻辑有一个细节要注意评估必须在测量完成后进行不能在测量过程中改闸门。另外量程切换后第一次测量的结果要丢弃因为闸门宽度变了计数器还没稳定。我一般会在切换后等一个完整测量周期再采信数据。验证方法用信号发生器输出一个从1Hz到20MHz的扫频信号观察测频结果的相对误差是否始终保持在参考时钟精度附近。如果某个频段误差突然变大检查该频段对应的闸门档位是否合理以及计数器是否接近溢出。最后说一个我自己的习惯每次改完闸门逻辑先用一个固定频率信号跑100次测量看结果的标准差。如果标准差大于理论值说明同步逻辑或者计数器保持逻辑有问题。这个习惯帮我省了很多调试时间。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑