
简介这是一份面向FPGA初学者与数字系统设计学习者的完整工程资料围绕Quartus II 13.1与Verilog HDL实现等精度数字频率计可解决宽频率范围内测量误差难以控制的问题适用于通信测试、科研仪器及课程设计等场景。压缩包共390个文件约17.56MB以cdb、hdb、tdf等Quartus工程与综合中间文件为主配合v源码、vo仿真输出、rpt报告及qsf约束文件完整保留了从设计到综合的工程结构。资源已有1411人学习下载热度较高。读者可从中获取等精度测量核心算法、分频器与同步电路设计、串口通信模块以及Modelsim仿真验证的完整实现并借助工程文件与报告理解综合、布局布线流程适合对照学习与二次开发。1. 从一块“测不准”的板子说起等精度频率计到底解决了什么低频段测频误差大得离谱高频段反而准得吓人——这是很多刚接触数字频率计的人都会撞上的反直觉现象。普通计数式频率计在低频时一个闸门时间内可能只计到几个脉冲±1 个数的量化误差直接让读数飘到没法看。而等精度频率计的核心思路就是让闸门时间跟着被测信号走用被测信号的整数个周期去“同步”闸门再拿标准高频时钟去填充这段闸门从而在整个测量范围内把相对误差压到同一量级。这次拆的是一份基于 Quartus II 13.1 和 Verilog 的等精度数字频率计工程覆盖从分频、同步闸门、计数到数码管显示的完整链路适合正在做课程设计、FPGA 入门项目或者想搞懂“为什么我的频率计一测低频就翻车”的从业者。它不依赖任何软核和 IP 核纯 RTL 手写仿真和综合都能在 Quartus II 13.1 里跑通拿来改参数、换显示位宽、移植到不同开发板都算顺手。2. 等精度测频的原理与模块划分为什么闸门要“等”信号2.1 等精度测频的数学底子先把公式摆出来不然后面写代码容易变成抄连线。等精度测频的经典做法是设定一个预置闸门时间 T_pr实际闸门时间 T_gate 并不等于 T_pr而是被测信号 clk_fx 的整数个周期且 T_gate 尽量接近 T_pr。在 T_gate 内同时用两个计数器分别数被测信号周期数 N_x 和标准时钟周期数 N_s。被测频率 f_x (N_x / N_s) * f_s其中 f_s 是标准时钟频率。关键点在于闸门开启和关闭都由被测信号同步所以 N_x 没有 ±1 误差只有 N_s 存在 ±1 误差。相对误差约为 1/N_s而 N_s T_gate * f_s只要 f_s 足够高、T_gate 足够长整个量程内误差就基本恒定。这就是“等精度”三个字的来源——不是精度真的完全相等而是相对误差不再随被测频率下降而恶化。常见做法是取 f_s 为 50 MHz 或 100 MHz 的板载晶振T_pr 取 1 s、0.1 s 等档位。T_pr 越长N_s 越大误差越小但刷新率越低。这个取舍后面在参数章节会具体说。2.2 顶层模块怎么切这份工程按功能切成几个独立模块顶层只做例化和互连不塞逻辑。典型划分如下模块名功能关键端口clk_div把板载高频时钟分频出标准时钟 f_s 和扫描时钟clk_in, clk_s, clk_scangate_sync用被测信号同步预置闸门产生实际闸门 T_gateclk_fx, gate_pr, gate_actcnt_fx在 T_gate 内数被测信号周期数 N_xclk_fx, gate_act, N_xcnt_fs在 T_gate 内数标准时钟周期数 N_sclk_s, gate_act, N_sdiv_calc做除法或倒数运算得到频率值N_x, N_s, freq_valseg_disp动态扫描数码管显示clk_scan, data, seg, sel这种切法的好处是每个模块都能单独仿真。gate_sync 是整个设计的灵魂它决定了闸门边沿和被测信号的对齐关系。如果这里写错后面计数器数出来的 N_x 会多一个或少一个误差直接翻倍。2.3 同步闸门的 Verilog 写法下面这段是 gate_sync 的核心逻辑用被测信号对预置闸门做两级同步再提取上升沿和下降沿来开关实际闸门。// gate_sync.v // 用被测信号 clk_fx 同步预置闸门 gate_pr产生实际闸门 gate_act module gate_sync( input wire clk_fx, // 被测信号作为同步时钟 input wire gate_pr, // 预置闸门来自标准时钟域 output reg gate_act // 实际闸门宽度为 clk_fx 整数个周期 ); reg gate_pr_d1, gate_pr_d2; // 两级同步消除亚稳态 always (posedge clk_fx) begin gate_pr_d1 gate_pr; gate_pr_d2 gate_pr_d1; end // 检测同步后闸门的上升沿和下降沿 wire gate_rise gate_pr_d1 ~gate_pr_d2; wire gate_fall ~gate_pr_d1 gate_pr_d2; always (posedge clk_fx) begin if (gate_rise) gate_act 1b1; // 被测信号上升沿到来时开闸 else if (gate_fall) gate_act 1b0; // 被测信号上升沿到来时关闸 end endmodule逻辑说明gate_pr 来自标准时钟域直接拿去做使能会有亚稳态风险所以先用 clk_fx 打两拍。打拍之后gate_pr_d1 和 gate_pr_d2 的边沿就对齐到了 clk_fx 的上升沿。开闸和关闸都发生在 clk_fx 上升沿因此 gate_act 的高电平宽度必然是 clk_fx 周期的整数倍。这样 cnt_fx 在 gate_act 内数到的 N_x 就是精确的整数没有 ±1 误差。参数说明clk_fx 是被测信号频率范围取决于板子和前端调理电路常见做法是加一级施密特触发器或高速比较器把正弦波整形成方波。gate_pr 的宽度由标准时钟域的分频器决定对应 1 s、0.1 s 等档位。gate_act 直接送给两个计数器做使能。2.4 计数器和除法器的落地细节cnt_fx 和 cnt_fs 都是带使能的二进制计数器位宽要够。假设 f_s 50 MHzT_gate 最大 1 sN_s 最大 50 000 000需要 26 位。N_x 在测低频时很小测高频时可能到几 MHz 甚至几十 MHz1 s 闸门下 N_x 最大也是几十 M同样取 26 位以上比较稳。实际工程里为了省资源可以取 32 位综合工具会自动优化。// cnt_fs.v module cnt_fs( input wire clk_s, input wire gate_act, input wire rst_n, output reg [31:0] N_s ); always (posedge clk_s or negedge rst_n) begin if (!rst_n) N_s 32d0; else if (gate_act) N_s N_s 1b1; // 闸门内累加标准时钟周期 else N_s N_s; // 闸门关闭时保持 end endmodule除法器是另一个坑。Verilog 的/在综合时不一定能生成理想电路尤其是 32 位除法资源消耗大、时序差。常见做法有两种一是用 Quartus 的 lpm_divide IP 核二是自己写一个基于移位减的时序除法器。如果只是课程设计直接调用 lpm_divide 最省事如果要移植到其他工具链建议手写状态机除法避免 IP 依赖。// div_calc.v 调用 lpm_divide 的示例 // 注意lpm_divide 需要在使用前通过 MegaWizard 生成 module div_calc( input wire [31:0] N_x, input wire [31:0] N_s, input wire [31:0] f_s, // 标准时钟频率单位 Hz output wire [31:0] freq_val // 被测频率单位 Hz ); wire [31:0] quotient; wire [31:0] remainder; lpm_divide u_div ( .numer(N_x * f_s), // 分子 N_x * f_s .denom(N_s), // 分母 N_s .quotient(quotient), .remainder(remainder), .clock(clk_s), .aclr(!rst_n) ); assign freq_val quotient; endmodule这里有个细节N_x * f_s 可能溢出 32 位。f_s 50 000 000N_x 最大 50 000 000乘积是 2.5e15远超 32 位。所以要么把位宽扩到 64 位要么先做除法再乘。更稳妥的做法是先用 N_x / N_s 得到小数再乘 f_s但定点小数在 Verilog 里要自己定标。实际工程里常用 64 位中间变量Quartus 对 64 位乘法支持还行但时序要看好。3. 在 Quartus II 13.1 里从建工程到烧板一份可抄的流程3.1 工程创建与文件组织Quartus II 13.1 是老版本界面和现在的新版差别不小但流程一致。启动后走 File - New Project Wizard指定工程目录和顶层实体名。顶层实体名要和顶层 .v 文件名一致否则综合会报找不到顶层。建议目录结构如下freq_meter/ ├── rtl/ │ ├── freq_meter_top.v │ ├── gate_sync.v │ ├── cnt_fx.v │ ├── cnt_fs.v │ ├── div_calc.v │ └── seg_disp.v ├── sim/ │ └── tb_freq_meter.v ├── ip/ │ └── lpm_divide.v └── qsf/ └── freq_meter.qsf把 rtl 和 ip 目录都加入工程。Quartus II 13.1 对中文路径支持不好工程路径全用英文和数字别带空格。这一步看着废话但每年都有人因为路径里有中文导致综合失败血泪经验。3.2 引脚分配与时钟约束引脚分配在 Assignments - Pin Planner 里做。需要分配的引脚包括板载晶振输入、被测信号输入、数码管段选和位选、复位按键。不同开发板引脚不同这里不写具体引脚号按板子原理图来。时钟约束在 Quartus II 13.1 里通过 TimeQuest Timing Analyzer 做。至少要给板载晶振加一个 create_clock。如果被测信号也作为时钟用同样要约束否则时序报告里会有一堆未约束路径虽然不一定影响功能但频率高了容易翻车。# freq_meter.sdc create_clock -name clk_50m -period 20.000 [get_ports clk_in] create_clock -name clk_fx -period 1000.000 [get_ports clk_fx] derive_pll_clocks derive_clock_uncertainty这里 clk_fx 的周期先按 1 kHz 给实际被测信号频率范围广约束只能覆盖一个典型值。更严谨的做法是用 set_input_delay 来描述但课程设计级别用 create_clock 也能过。3.3 综合、仿真与烧录综合前先在 Assignments - Settings - Compiler Settings 里选好器件型号。综合完看 Compilation Report 里的 Resource Usage如果逻辑单元占用超过 70%后面加功能要小心。时序报告重点看 Setup Slack 和 Hold Slack负数说明时序不满足需要降频或加流水线。仿真建议用 ModelSim-Altera 做功能仿真。写一个 testbench给 clk_in 和 clk_fx 分别加激励跑 2 ms 看 N_x 和 N_s 是否合理。// tb_freq_meter.v timescale 1ns/1ps module tb_freq_meter; reg clk_in 0; reg clk_fx 0; reg rst_n 0; wire [7:0] seg; wire [3:0] sel; // 50 MHz 板载时钟 always #10 clk_in ~clk_in; // 被测信号1 kHz周期 1 ms always #500000 clk_fx ~clk_fx; initial begin #100 rst_n 1; #2000000000 $stop; // 跑 2 ms 后停止 end freq_meter_top u_top ( .clk_in(clk_in), .clk_fx(clk_fx), .rst_n(rst_n), .seg(seg), .sel(sel) ); endmodule仿真时重点看 gate_act 的宽度是不是 clk_fx 周期的整数倍以及 N_s 是否接近 T_gate * f_s。如果 N_s 差很多检查 gate_pr 的生成逻辑如果 N_x 多一个或少一个检查 gate_sync 的边沿检测。烧录用 Quartus II 13.1 自带的 Programmer选好 USB-Blaster 和 .sof 文件点 Start。如果板子上有配置芯片也可以生成 .jic 文件烧到 EPCS 里掉电不丢。4. 避坑与排查等精度频率计最容易翻车的五个地方4.1 现象低频测出来误差巨大高频反而正常原因闸门没有用被测信号同步还是用的预置闸门直接使能计数器。这样 N_x 存在 ±1 误差低频时 N_x 小相对误差就大。解决检查 gate_sync 模块是否真正用 clk_fx 对 gate_pr 做了同步并且 gate_act 的开关都发生在 clk_fx 上升沿。可以用 SignalTap 抓 gate_act 和 clk_fx看边沿是否对齐。4.2 现象N_s 计数偶尔多 1 或少 1频率值跳变原因gate_act 和 clk_s 是异步关系gate_act 的边沿可能落在 clk_s 的建立保持窗口内导致 cnt_fs 的使能出现亚稳态。解决在 cnt_fs 里对 gate_act 用 clk_s 打两拍再使用。注意打拍会引入延迟但闸门时间足够长时影响可忽略。// 在 cnt_fs 内部对 gate_act 同步 reg gate_act_d1, gate_act_d2; always (posedge clk_s) begin gate_act_d1 gate_act; gate_act_d2 gate_act_d1; end // 用 gate_act_d2 做使能4.3 现象数码管显示闪烁或某一位不亮原因动态扫描频率太低或者位选和段选没有对齐。常见做法是扫描时钟取 1 kHz 左右每位显示 1 ms人眼看不到闪烁。如果扫描时钟只有几十 Hz就会闪。解决检查 clk_div 里扫描时钟的分频比。假设板载 50 MHz要得到 1 kHz分频系数是 50000。位选切换和段选数据更新要在同一个扫描时钟边沿上做避免竞争。4.4 现象综合报错“cannot resolve lpm_divide”原因lpm_divide 没有通过 MegaWizard 生成或者生成后没有加入工程。解决在 Quartus II 13.1 里走 Tools - MegaWizard Plug-In Manager选 LPM_DIVIDE生成 .v 文件后加入工程。注意生成时选好输入输出位宽位宽不匹配会报错。4.5 现象烧到板子上没反应数码管全灭原因复位按键极性搞反了或者引脚分配错了。解决先确认复位是高电平有效还是低电平有效代码里 rst_n 是低有效如果按键按下是低电平那没问题如果按键按下是高电平就要改成 rst。引脚分配用 Pin Planner 里的 Node Finder 核对别对着原理图手抄错行。5. 把误差再压一档多周期同步与分段校准的实操技巧等精度测频在 1 s 闸门下50 MHz 标准时钟的 N_s 是 5e7相对误差约 2e-8已经不错了。但如果想再压一档或者板载时钟只有 10 MHz可以上多周期同步。思路是连续测多个闸门把 N_x 和 N_s 分别累加再做一次除法。这样等效闸门时间变成 M * T_gateN_s 扩大 M 倍误差降到 1/(M * N_s)。代价是刷新率降到 1/M。具体实现上加一个周期计数器 cycle_cnt每来一个 gate_act 下降沿加一累加到 M 后触发一次除法同时清零累加器。注意累加器位宽要够M 取 8 时N_s 累加值可能到 4e8需要 29 位以上取 32 位安全。// 多周期累加示例 reg [31:0] N_s_acc, N_x_acc; reg [3:0] cycle_cnt; parameter M 8; always (posedge clk_s or negedge rst_n) begin if (!rst_n) begin N_s_acc 0; N_x_acc 0; cycle_cnt 0; end else if (gate_fall_sync) begin N_s_acc N_s_acc N_s; N_x_acc N_x_acc N_x; if (cycle_cnt M - 1) begin cycle_cnt 0; // 触发除法用 N_x_acc 和 N_s_acc end else cycle_cnt cycle_cnt 1; end end另一个技巧是分段校准。等精度测频的理论误差是恒定的但实际电路里前端调理电路、比较器延迟、晶振精度都会引入系统误差。常见做法是在已知频率点比如 1 kHz、10 kHz、100 kHz、1 MHz各测一组数据算出差值在显示前做线性补偿。补偿系数可以存在寄存器里用拨码开关选择档位。这个做法在课程设计里算加分项但别过度拟合取 3 到 5 个点做分段线性就够了。验证方法上我一般会拿信号发生器输出几个频点同时用一台商用频率计做对比记录偏差。如果偏差在 ±1 个字以内说明设计没问题如果偏差随频率线性增大多半是标准时钟频率设错了比如把 50 MHz 写成 50 000 000 但实际晶振是 48 MHz。从那以后我每次改 f_s 参数都强制走一遍仿真加实测对比不敢只信代码里的注释。希望帮到你。本文还有配套的精品资源点击获取