
简介面向FPGA初学者与数字系统设计学习者的等精度数字频率计工程包基于Quartus II 13.1与Verilog HDL实现覆盖1Hz至150MHz宽频段测量误差控制在0.01%以内涉及采样量化、分频器、同步电路、误差校正、串口通信及Modelsim仿真验证等核心环节。压缩包共390个文件、约17.56MB以cdb、hdb、tdf、v等工程与综合中间文件为主辅以bak备份、rpt报告、vo仿真输出及qsf、qpf工程配置较完整地保留了从源码到布局布线的设计痕迹便于按模块追踪实现思路。已有1412人学习下载。读者可据此复现频率计核心架构理解等精度测量中闸门时间与计数同步的配合方式并借助仿真与报告文件排查时序问题适合课程设计、竞赛训练及自学参考。1. 等精度测频为什么低频测量不该死在±1 个字上把一台等精度数字频率计做到 1Hz 到 50MHz 全量程听起来是量程问题实际是误差分配问题。用 Quartus II 13.1 加 Verilog 搭这套东西的人多半先在低频段遇到过问题闸门给 1 秒输入 10Hz读数在 9 和 11 之间来回跳。等精度测频多周期同步测频解决的正是这件事——它让相对误差只跟标准时钟频率和闸门时间有关跟被测频率彻底无关。这也是范围广三个字背后唯一站得住的解释不是靠分段换算法而是靠一个统一的误差公式覆盖全频段。2. 从固定闸门到同步闸门误差为什么与被测频率脱钩2.1 直接计数法的关键问题±1 个字是个绝对量最朴素的测频法是固定闸门计数开一个宽度为 T 的闸门数里面有多少个被测脉冲得到 N于是 f N / T。这个式子看起来没问题但它的误差来源是量化的闸门开启和关闭的时刻与被测脉冲到来的时刻毫无关系所以 N 一定存在 ±1 个字的量化误差。关键在于这个 ±1 是绝对值不是比例。当 T 1s、被测信号 10Hz 时N 大约是 10误差 ±1 就意味着 ±10% 的相对误差读数在 9 和 11 之间跳同样这套逻辑去测 10MHzN 10^7±1 的相对误差只有 10^-7。同一个电路低频段精度差六个数量级这就是直接计数法只能用在高频够准、低频不管场景的原因。教科书里通常的补救办法是低频改用测周期法测一个被测周期内数了多少个标准时钟f f_s / Ns。此时 ±1 落在标准时钟上低频精度上去了但高频段又塌了——因为高频时一个周期内标准时钟只有个位数量化误差被放大。于是传统方案必须分段高频用计数法低频用测周期法中间定一个切换点。切换点附近两边都不太准这就是老式频率计换量程时读数抖一下的来源。2.2 把闸门交给被测信号多周期同步的时序等精度法最重要的一点是让闸门不再由标准时钟独自决定而是由被测信号锁定。预置闸门仍然由标准时钟分频产生但它只作为请求实际闸门的开和关都要等被测信号的上升沿。时刻发生了什么计数器状态t0预置闸门 pr 由低变高两个计数器仍停在复位值t1被测信号第 k 个上升沿到达gate_x 拉高Nx 与 Ns 同时开始累加t2预置闸门 pr 由高变低只是请求关闸实际闸门还开着t3被测信号第 kN 个上升沿到达gate_x 拉低两个计数器同时冻结从 t1 到 t3被测信号恰好走完了 N 个完整周期一个不多一个不少。这就是等精度法的关键被测信号的计数值 Nx 里没有量化误差它是精确的整数 N。真正的量化误差被转移到了标准时钟的计数 Ns 上因为 t1 和 t3 相对标准时钟是异步的Ns 会有 ±1 的不确定度。于是实际闸门时间 T_gate N / f_x同时也等于 Ns / f_s近似差一个标准时钟周期以内。两式联立f_x Nx × f_s / Ns注意这个式子里根本没有预置闸门时间这一项。预置闸门只决定测量大概持续多久不进入最终计算。这也是实现时最容易写错的地方——很多人把 T_pr 直接代进公式结果一算就偏。2.3 误差公式的来历为什么只剩时基和 ±1 个标准时钟对 f_x Nx·f_s/Ns 做误差传递Nx 是精确整数相对误差 0剩下两项δ ±1/Ns |Δf_s / f_s| ±1/(T_gate × f_s) 时基精度第一项是标准时钟的量化误差第二项是晶振本身的准确度和温漂。以一个 50MHz、±10ppm 的晶振、1 秒闸门为例量化项是 1/(1×5×10^7) 2×10^-8时基项是 1×10^-5。也就是说在这个配置下晶振的准确度反而成了主要误差源量化误差已经压到可以忽略。这也解释了为什么等精度频率计要在时基上用温补晶振——电路做得再巧晶振不准一切都是白搭。更值得记住的是 δ 的表达式里没有 f_x。这正是等精度三个字的字面含义误差和被测频率无关。测 1Hz 和测 50MHz量化误差完全一样。3. Verilog 里的实现闸门握手、双计数器与跨时钟域3.1 请求与响应分离闸门握手的 Verilog 写法直接的错误写法是在被测时钟域里判断预置闸门的电平然后直接把 gate_x 拉起来。这样做的问题是预置闸门相对被测信号是异步的你在哪一个被测沿上看见它取决于两个时钟的相位关系闸门边沿可能落在任意位置。正确的做法是把请求和实际动作拆开。请求在标准时钟域产生动作在被测时钟域完成。// 时钟域clk_s标准时钟50MHz // pr 是预置闸门由 clk_s 分频产生宽度就是设定的闸门时间 always (posedge clk_s or negedge rst_n) begin if (!rst_n) begin arm_req 1b0; close_req 1b0; end else begin if (pr_rise) arm_req 1b1; // 预置闸门开请求开闸 if (pr_fall) close_req 1b1; // 预置闸门关请求关闸 if (meas_done) begin // 一次测量结束清掉请求 arm_req 1b0; close_req 1b0; end end end这两个请求信号只是意图它们的电平会一直保持直到被测时钟域给出完成信号。这样就不存在错过边沿的问题哪怕被测信号频率只有 1Hz请求也会一直挂着等它。接下来在被测时钟域里响应请求。这里用两级触发器第一是为了跨时钟域同步避免亚稳态第二是这两级触发器的输出天然对齐在被测上升沿上正好满足闸门起止沿必须落在被测沿的要求。// 时钟域clk_x被测信号已整形成方波 reg arm_s1, arm_s2, close_s1, close_s2; always (posedge clk_x or negedge rst_n) begin if (!rst_n) begin arm_s1 1b0; arm_s2 1b0; close_s1 1b0; close_s2 1b0; end else begin arm_s1 arm_req; arm_s2 arm_s1; close_s1 close_req; close_s2 close_s1; end end wire gate_x arm_s2 ~close_s2; // 实际闸门起止都对齐被测上升沿两级同步会引入 2 个被测周期的固定延迟但开闸和关闸走的是完全相同的路径延迟互抵消闸门宽度不受影响。这是整个设计里最值得仔细核对的一处只要开、关两条路径的同步级数不一致闸门就会被悄悄拉长或缩短一两个被测周期低频段读数直接偏。还有一个边界情况需要处理如果闸门时间内被测信号一个上升沿都没来arm_req 会一直挂着测量卡住。所以要在标准时钟域加一个超时计数超时后强制清请求并置一个频率过低标志前端据此切到更长的闸门或提示量程不足。3.2 双计数器与位宽预算两个计数器的位宽不能随便拍。以 50MHz 标准时钟、最大闸门 1 秒计算计数器计数对象最大值最小位宽Nx被测上升沿1 秒内5×10^726 位取 32 位留余量Ns标准时钟1 秒内5×10^726 位取 32 位留余量中间积 Nx×f_s用于除法5×10^7×5×10^7 ≈ 2.5×10^1552 位取 64 位// Nx被测域计数gate_x 有效期间每个 clk_x 加一 // 因为 gate_x 的起止都由 clk_x 锁存累加值恰好等于闸门内的完整周期数 always (posedge clk_x or negedge rst_n) begin if (!rst_n) cnt_x 32d0; else if (gate_x) cnt_x cnt_x 1b1; end // Ns标准时钟域计数先把 gate_x 同步过来再数 reg gs1, gs2; always (posedge clk_s or negedge rst_n) begin if (!rst_n) begin gs1 1b0; gs2 1b0; end else begin gs1 gate_x; gs2 gs1; end end always (posedge clk_s or negedge rst_n) begin if (!rst_n) cnt_s 32d0; else if (gs2) cnt_s cnt_s 1b1; end这里有一个必须想清楚的点cnt_x 数出来的是精确的 N而 cnt_s 因为同步的关系会有 ±1 的不确定度。这个不对称是刻意的——误差全部集中到 Ns 上正是 2.3 节误差公式成立的前提。如果两边都做同样的同步处理反而会引入两个独立的 ±1误差翻倍。3.3 两级同步器亚稳态不是玄学跨时钟域这件事很多人是在仿真里看不到问题、上板后偶尔出现异常读数时才开始认真对待的。原因很直接仿真器不会模拟触发器的建立保持时间违例gate_x 从 0 变 1 的时刻如果正好落在 clk_s 的上升沿附近第一个采样触发器的输出可能是 0、可能是 1也可能在一段时间内处于中间电平。中间电平本身不可怕可怕的是它传播到组合逻辑后不同的下游路径会给出不同的判决结果。如果这个信号同时进了计数器的使能端和别的判断逻辑就会出现这一拍计了下一拍没计的诡异现象表现成偶发的大数。两级同步器的本质是给亚稳态一个衰减窗口。第一级触发器可能进入亚稳态但整个时钟周期内它大概率会稳定下来第二级再采样时就是干净的。理论上仍然有失败概率但那是个 MTBF 问题——在 50MHz 这个量级两级同步的 MTBF 通常远大于设备寿命。注意两级同步只对单比特信号有效。如果跨时钟域传的是多位计数值必须用格雷码或者加握手否则各位的采样时刻不一致拼出来的数可能是错的。3.4 频率运算除法器怎么在 FPGA 里不出错到了算 f_x Nx × f_s / Ns 这一步最容易出问题。用组合逻辑写一个 64 位乘法再立刻做 64 位除法Quartus II 13.1 的综合器会给你一个巨长的组合路径时序报告会出现大量违例上板后算出来的数看起来像随机数。常见的实现思路是拆成两步乘法用一个周期完成64 位乘法器在中等规模的器件里还能接受除法改成多周期的移位减法状态机。// 移位减法除法器64 位被除数 / 32 位除数64 拍出商 // 每拍只做一次 33 位比较和减法组合路径短容易收敛 reg [63:0] dividend; reg [31:0] divisor; reg [63:0] quo; reg [32:0] rem; // 33 位留出移位后的进位 reg [6:0] bit_cnt; reg busy; wire [32:0] rem_shift {rem[31:0], dividend[63]}; always (posedge clk_s or negedge rst_n) begin if (!rst_n) begin busy 1b0; quo 64d0; rem 33d0; bit_cnt 7d0; end else if (start_div) begin busy 1b1; rem 33d0; quo 64d0; bit_cnt 7d64; dividend {32d0, cnt_x} * FS_HZ; // 被除数 Nx × f_s divisor cnt_s; end else if (busy) begin if (rem_shift {1b0, divisor}) begin rem rem_shift - {1b0, divisor}; // 够减商位补 1 quo {quo[62:0], 1b1}; end else begin rem rem_shift; // 不够减商位补 0 quo {quo[62:0], 1b0}; end dividend {dividend[62:0], 1b0}; bit_cnt bit_cnt - 7d1; if (bit_cnt 7d1) busy 1b0; end end参数说明FS_HZ是标准时钟频率的常数如 50_000_000cnt_x、cnt_s来自 3.2 节的两个计数器quo输出的就是频率值单位 Hz。rem用了 33 位而不是 32 位是因为移位后可能临时超出除数位宽用 32 位会丢掉最高位导致商在特定输入下错得毫无规律——这个坑非常隐蔽仿真时用随机数才容易撞出来。64 拍对 1 秒一次的测量来说完全不是瓶颈。真正的瓶颈是cnt_x * FS_HZ这个乘法如果综合后时序紧张可以把标准时钟改成 2 的幂次比如 2^26 Hz 约 67.1MHz这时乘 f_s 就退化成左移 26 位一个乘法器都不用。这个取舍在第 6 章还会再提。4. 量程所谓范围广是怎么撑出来的4.1 高频段预分频不是简单的除以 N等精度法保证了误差公式里没有 f_x但它没保证计数器能跑那么快。被测信号 100MHz你的 cnt_x 就得在 100MHz 下工作这在低端器件上已经开始吃力再往上瓶颈会从逻辑转到 IO 和触发器本身。常规做法是在输入端加前置分频用被测信号驱动一串触发器做 2^k 分频得到频率降下来的信号再送进等精度内核。这时候要注意一个容易想错的地方——分频后的信号它的上升沿与被测信号的某些上升沿严格对应但不再是全部。所以闸门对齐到分频后的沿等价于对齐到被测信号的第 2^k 个沿。Nx 变成原来的 1/2^k最后乘回去就行。这个乘回去是左移不引入任何误差。所以预分频不会破坏等精度性质它只是把可测上限从计数器能跑多快变成前置分频器能跑多快。真正的血泪经验在这里很多方案标称 100MHz实际打对折问题不在算法在输入级的比较器和分频触发器选型。4.2 低频段闸门时间就是分辨率的另一个名字低频段的限制不在电路在时间。因为 Nx 是精确整数1Hz 输入在 1 秒闸门下只数到 1但 Ns 有 5×10^7误差仍然是 2×10^-8。这就是等精度法在低频段最反直觉的地方被测信号一个周期只来一次你的精度却和测 50MHz 时一模一样。代价是刷新率。闸门时间是硬约束想提高分辨率就得拉长闸门读数就变慢。预置闸门量化误差50MHz 时基理论刷新率适用场景10ms2×10^-6100 次/秒高频快速跟踪100ms2×10^-710 次/秒常规显示1s2×10^-81 次/秒校准、低频高精度工程上一般是自适应先给一个短闸门粗测根据 Nx 的大小决定要不要拉长。这里有个死锁风险——如果粗测时 Nx 太小甚至为 0说明闸门内一个沿都没有必须靠 3.1 节提到的超时保护跳出来否则状态机永远停在等沿的状态显示定格。4.3 输入调理把模拟世界的抖动挡在计数器之外被测信号进 FPGA 之前必须是干净的方波边沿要陡。这一步做不好后面全是徒劳一个带 5% 噪声的正弦波直接进 IO计数器会在过零点附近反复触发读出来偏大且剧烈跳动。典型做法是高速比较器加迟滞。阈值设在信号幅度的中点迟滞取幅度的 10% 到 20%这样噪声幅度小于迟滞窗口时就不会产生额外翻转。输入端的阻抗也要注意1MΩ 并联几十 pF 是比较常见的取值探头电容和走线电容会直接影响高频段的边沿质量。提示高频段测量时信号幅度对读数的影响往往比频率本身更大。如果发现同一频率下改变幅度读数就变先查输入调理别怀疑算法。5. 避坑与排查四个把等精度做回普通测频的坑5.1 闸门直接用了预置信号低频误差悄悄回来现象高频段读数很稳切到低频几十 Hz 以下末位开始大幅跳动1Hz 输入时读数在 0、1、2 之间乱跳。原因为了省事直接把预置闸门 pr 当成 gate_x 用或者在 clk_x 域里用组合逻辑判断 pr 电平。这时闸门的起止沿与被测沿无关Nx 重新有了 ±1 的量化误差等精度退化成了普通计数法。因为高频时 Nx 很大±1 看不出来低频才露馅。解决检查 gate_x 的产生路径确认它只由 clk_x 驱动的触发器输出组合而成并且开、关两条路径的同步级数一致。可以在仿真里故意把 clk_x 和 clk_s 设成接近整数比的关系看闸门宽度是否恒定。5.2 跨时钟域没同步计数偶尔差一个现象连续测同一个信号99% 的时候读数一致偶尔跳出一个明显偏大或偏小的值重启后消失长时间运行又出现。原因gate_x 没有经过同步就直接进了 clk_s 域当使能。亚稳态导致 cnt_s 在某些时刻多计或少计而且这种错误不会自我纠正会一直保留到下次复位。解决所有跨域信号必须过两级同步多位数据用格雷码或者加有效标志。排查时可以把 cnt_x 和 cnt_s 一起上传到上位机看波形正常情况下 Ns/Nx 的比值应该非常稳定出现单点跳变就是同步没做好。5.3 除法器位宽不够高位被截断现象测 10MHz 时显示 1MHz测 1MHz 时显示 0.1MHz看起来像是整体缩小了 10 倍换量程后小数点位置又不对。原因中间积 Nx × f_s 用 32 位存了。1×10^6 × 5×10^7 5×10^13远超 32 位能表示的 4.29×10^9高位直接被截掉算出来的商自然不对。解决中间积至少 64 位并且在做除法之前先确认除数不为 0Ns 为 0 时直接输出 0 并置错误标志否则会得到一个大得离谱的数。仿真的时侯用边界值跑Nx 1、Nx 最大、Ns 1、Ns 最大四种组合能覆盖绝大多数位宽问题。5.4 前端没有整形标称量程打对折现象标称 50MHz实际测到 20MHz 以上读数开始偏小而且同一个频率下改变输入幅度读数跟着变。原因被测信号是正弦或者带噪声直接进 FPGA 的 IO。IO 的阈值是固定的信号幅度不够或者边沿太缓时翻转时刻飘忽甚至漏掉计数器少数脉冲。这不是算法问题是模拟前端问题。解决加高速比较器做整形配迟滞网络高频段先做前置分频再进内核。验证方法很简单用一个已知频率的方波边沿陡、幅度足做参考如果方波测得准、正弦测不准问题一定在前端。6. 设计取舍分辨率、刷新率与资源的三方博弈这套设计里没有最优解只有针对场景的平衡。三件事互相牵制想要高分辨率就得拉长闸门拉长闸门就牺牲刷新率想要宽量程就得上预分频预分频多了又要多一级同步和一次移位还原想要省资源就不能用 64 位乘法器那就得把时基改成 2 的幂次。我个人倾向于把时基选成 2^26 Hz 约 67.1MHz而不是常见的 50MHz。理由是 2.3 节的误差公式里量化项 1/(T_gate·f_s) 在 1 秒闸门下已经是 1.5×10^-8远小于普通晶振 10^-5 量级的准确度多出来那点频率对精度没有实质帮助而把 f_s 变成 2 的幂f_x Nx 26 / Ns整个乘法器直接消失省下来的逻辑资源可以拿去做更宽的计数器或者 BCD 转换时序也更容易收敛。代价是那个频率的晶振不好买得多一级锁相或者用整数分频凑——这就是取舍没有免费的部分。验证一套等精度频率计是否真的等精度有一个很直接的判据用信号发生器从 1Hz 扫到上限记录每个频点的相对误差画出来应该是一条接近水平的线。如果这条线在高频段翘起来查前端和分频如果在低频段翘起来查闸门握手如果中间有个鼓包查量程切换点附近的逻辑。这条曲线比任何单项测试都能说明问题因为它一眼就能看出误差是结构性的还是局部性的。还有一个容易被忽略的验证角度把同一个信号同时送给两套独立的计数器通道比较两路读数的一致性。系统性误差两路都有不会暴露随机性误差会表现为两路不一致。这个方法能很快区分算法错和实现抖。从那以后我每次做跨时钟域的测量类设计都强制自己先画出三个时钟域的信号流图把每个跨域点标出来确认每个点都有同步、每个同步的延迟都不影响最终结果然后才开始写代码。这套习惯让我少熬了很多夜。希望帮到你。本文还有配套的精品资源点击获取