简介面向FPGA数字系统课程设计、Verilog HDL入门与电子测量实践的频率计资料围绕10Hz10MHz信号测量展开适合需要完成六位数码管显示、闸门时间切换与溢出/闸门指示的学生和自学者对照学习。压缩包仅含1个doc文档约412KB集中收录设计报告与各模块代码仿真说明按分频器、门控电路、计数器、锁存与译码显示、顶层电路及误差分析组织便于顺着设计流程理解从原理框图到ISE仿真实现的完整链路。报告写明1S、0.1S、0.01S三档闸门选择、复位按键、gate与over指示等指标也交代了ELB实验板与SPARTAN-3A平台的实现条件可用来核对模块划分、仿真波形与测试结论。已有707人学习可作为课程设计参考、实验报告撰写范本以及Verilog模块拆分与仿真调试的对照材料尤其适合在动手编码前先梳理测频原理与显示控制逻辑。1. 数字频率计24MHz 的板载时钟凭什么敢量 10MHz 的输入直觉里要测 10MHz 的信号采样时钟怎么也得比它快一个数量级。数字频率计偏不这么干它把被测信号整形成方波送进一扇由时基信号控制开闭的闸门在精确的 1 秒或 0.1 秒、0.01 秒里数通过了多少个脉冲再用 fN/T 反推频率。整条链路上真正决定精度的只有一件事——那扇门开了多久。这块实验板只有一颗 24MHz 晶振它得先被分频成三种闸门宽度和一个 1ms 扫描节拍再派生出测量使能、锁存、清零三根控制线。把这套机制拆开看数字频率计本质上是一台用已知时间去量未知时间的比较器不是高频采样设备。2. 测频的数学骨架fN/T 把精度压力全甩给了时基2.1 闸门、时基与计数器三个角色各管一件事测频链路可以拆成三段互不重叠的职责。第一段是输入调理被测正弦波经放大整形变成同频率的方波这一步只保证边沿陡、幅度够不改变频率。第二段是时间控制时基发生器由晶振加分频器构成输出宽度精确已知的门控信号去开、关闸门。第三段是计数与呈现闸门开着的时候脉冲进十进制计数器闸门一关立刻锁存、译码、扫描显示。三者的耦合关系用一张表能说清角色输入输出决定什么时基发生器24MHz 晶振1s / 0.1s / 0.01s 闸门测量精度上限、量程闸门门控电路闸门信号 被测方波计数使能窗口实际计到多少个数计数器计数使能 被测方波六位 BCD读数分辨率、是否溢出关键在第二行闸门不是开关它是一个时间窗口窗口宽度 T 的准确度直接乘进最终结果。晶振频偏 100ppm测出来就偏 100ppm跟被测信号本身没有任何关系。这也是为什么频率计敢用慢时钟测快信号——被测信号只贡献数不贡献时间。从设计动机上讲这个分工是为了把误差集中到一个可优化的点上。放大器可以做得不精密整形电路可以做得粗糙只要边沿干净唯独时基这条路径必须用晶振、必须走最短的布线、必须单独约束。所有资源都往一个地方堆比把所有环节都做精密要划算得多。2.2 ±1 计数误差主门开闭时刻与被测沿的随机相位闸门开启的那一瞬间与被测脉冲到来的时刻没有同步关系。假设计数器很诚实它只会数完整通过闸门的上升沿那么当闸门关闭时最后一个脉冲可能刚好在门内、也可能刚好在门外两种情况下计数结果差 1。这就是数字化仪器特有的量化误差写成公式是δ 1/N 1/(f_x · T)其中 f_x 是被测频率T 是闸门时间。误差的绝对值固定是 ±1 个数相对误差跟总数 N 成反比——这是理解整个档位设计的关键。同一个 1 个数在 N10 时是 10% 的误差在 N100000 时是 0.001% 的误差。注意把闸门时间延长 10 倍只能把相对误差降 10 倍代价是测量刷新率降 10 倍。这条曲线决定了没有任何单一档位能通吃全量程。实际设计中还会叠加第二项误差时基的相对误差 Δf/f。它由晶振精度决定通常在 10⁻⁵ 到 10⁻⁶ 量级与 ±1 误差独立合成。低频段 ±1 误差占绝对主导高频段两者量级接近这也是为什么 10Hz 附近的测量结果看着跳而 1MHz 附近读数很稳。2.3 三档闸门的量程账为什么低频必须死等 1 秒六位数码管意味着计数器最大只能表示 999999。这条硬件约束反过来卡死了每个档位的量程上限算一遍就明白闸门时间 T计数 N f_x·T不溢出的上限±1 相对误差在 10Hz1sf_x999.999 kHz10%0.1sf_x/109.99999 MHz100%0.01sf_x/10099.9999 MHz1000%从表里能读出两个硬结论。第一10MHz 的信号如果用 0.1s 闸门计数恰好是 1,000,000正好越过 999999——差一个数就溢出这个边界卡得非常紧。原始设计里 1MHz 输入在 1s 档下读数 999.996 kHz已经在溢出边缘反复横跳。第二10Hz 信号如果用 0.01s 闸门只数到 0.1 个数结果恒为 0测量彻底失效。所以档位不是给用户选精度用的是给不同量程段配不同的分辨率。低频段牺牲刷新率换分辨率高频段牺牲分辨率换量程中间用 0.1s 过渡。这套三档划分看似随意其实是被六位显示和10Hz~10MHz两个指标反推出来的唯一解。顺便解释显示单位为什么定成 kHz 而不是 Hz1s 档下 N 就是 Hz 数除以 1000 变成 kHz 后六位数刚好覆盖 0.000~999.999 kHz一位不浪费。小数点位置随档位左移——1s 档在第三位后0.1s 档在第二位后0.01s 档在第一位后——读数始终是同样六个数码管、同一个单位、不同的小数点用户不用做心算换算。3. 分频链与门控状态机从 24MHz 到测量、锁存、清零三根控制线3.1 计数翻转式分频阈值不是分频比差一就差在闸门宽度上最省资源的分频写法是计到某个数就归零并翻转输出。这里有个极易踩的坑翻转阈值 N 和输出周期不是倒数关系。计数器从 0 计到 N 共 N1 个时钟输出翻转一次所以输出方波的完整周期是 2(N1) 个输入时钟高电平持续 N1 个时钟。要让高电平正好是 1 秒在 24MHz 下需要N 1 24_000_000 → N 23_999_999原始代码里写的是if(count124000000)实际得到的高电平是 24_000_001 个时钟多出 1 个。单个时钟是 41.7ns相对 1 秒是 4×10⁻⁸肉眼不可见但这类差一会在级联时累积也会让仿真波形和理论计算对不上。// 24MHz 拆成三种闸门宽度 1ms 扫描节拍 module clk_div ( input wire clk, // 板载 24MHz 晶振 input wire rst_n, // 低电平复位 output reg gate_1s, // 高电平持续 1s output reg gate_01s, // 高电平持续 0.1s output reg gate_001s, // 高电平持续 0.01s output reg tick_1ms // 1ms 高、1ms 低的方波 ); // 计到 TH 归零并翻转则高电平宽度 TH 1 个输入时钟 localparam integer TH_1S 24_000_000 - 1; localparam integer TH_01S 2_400_000 - 1; localparam integer TH_001S 240_000 - 1; localparam integer TH_1MS 24_000 - 1; reg [24:0] c1s, c01s, c001s, c1ms; always (posedge clk or negedge rst_n) begin if (!rst_n) begin {c1s, c01s, c001s, c1ms} 0; {gate_1s, gate_01s, gate_001s, tick_1ms} 0; end else begin // 四路互不干扰各自计满翻转 if (c1s TH_1S) begin c1s 0; gate_1s ~gate_1s; end else c1s c1s 1b1; if (c01s TH_01S) begin c01s 0; gate_01s ~gate_01s; end else c01s c01s 1b1; if (c001s TH_001S) begin c001s 0; gate_001s ~gate_001s; end else c001s c001s 1b1; if (c1ms TH_1MS) begin c1ms 0; tick_1ms ~tick_1ms; end else c1ms c1ms 1b1; end end endmodule位宽取 25 位是因为 23_999_999 2²⁵再窄一位就会截断。四个计数器并行放在一个 always 块里综合后是四个独立的模值比较器加四组寄存器彼此没有依赖时序路径互不影响。常见的替代写法是把 1ms 再分频十次得到 10ms、100ms、1s串成一条链省寄存器但引入累积相位误差而且最后一级要等前面全部走完才有输出启动延迟明显。并行写法多花几十个触发器换来四路闸门的边沿严格对齐同一时钟域这在需要切换档位时很关键。3.2 闸门取的是方波电平不是上升沿注意上面代码输出的是占空比 50% 的方波不是窄脉冲。门控电路取的是它的高电平区间做闸门所以真正被用到的只有高电平那一段低电平那一段等于关门休息。这么做的好处是闸门宽度直接等于高电平持续时间不需要额外做边沿检测或者单稳态。如果改成输出单周期脉冲那脉冲必须再展宽成闸门展宽电路本身又引入不确定性多一层就多一份误差。方波电平是最朴素的时间标尺宽度由计数器阈值直接决定中间不经过任何状态机。副作用是命名容易误导。一份代码里如果出现clk_1hz这类名字实际输出可能是 0.5Hz 方波周期 2 秒、高电平 1 秒因为设计者关心的是高电平 1 秒随手按闸门时间命名了。读到别人的频率计代码时先看波形宽度再看名字血泪经验。3.3 门控状态机测量、锁存、清零的时序怎么排出来闸门只给出什么时候该计数但一次完整的测量需要三段动作门开时计数、门关后锁存、锁存完清零。这三段必须严格串行否则会出现显示的数还在变或者上一次的余数混进下一次。原始设计用了一个很取巧的办法把闸门的上升沿直接当作状态机的启动信号然后用 1ms 节拍往上数数到不同值就翻转不同的控制线。闸门上升沿 │ ├─ step 1~9 : meas_en 1 计数窗口 ├─ step 11 : latch_en 1 把计数值打进锁存器 ├─ step 13 : clear_en 1 计数器清零step 归零 └─ 其余 step : 三根线全为 0 空转等待用代码表达就是一个计数式状态机module gate_ctrl ( input wire rst_n, input wire tick_1ms, // 1ms 节拍 input wire gate, // 选中的闸门方波 input wire start, // 闸门上升沿产生的启动脉冲 output reg meas_en, output reg latch_en, output reg clear_en ); reg [3:0] step; always (posedge tick_1ms or negedge rst_n) begin if (!rst_n) begin step 0; meas_en 0; latch_en 0; clear_en 0; end else if (start) begin step 1; // 上升沿重新起跑 end else if (step ! 0) begin step step 1b1; case (step) 4d1 : meas_en 1b1; // 开闸 4d9 : meas_en 1b0; // 关闸 4d11: latch_en 1b1; // 锁存 4d12: latch_en 1b0; 4d13: clear_en 1b1; // 清零 4d14: begin clear_en 1b0; step 0; end default: ; endcase end end endmodule这段代码里最值得琢磨的是 step 的推进节奏。整个状态机由 1ms 节拍驱动从 1 数到 13 需要 13ms也就是一次完整的测量—锁存—清零周期至少 13ms。对于 0.01s 档闸门只有 10ms来说这个开销已经和闸门本身同量级了。提示状态机的推进节拍必须显著快于最短闸门时间否则最短档的时序会被状态机拖长。0.01s 档配 1ms 节拍只剩 10 个节拍可用余量非常紧。3.4 测量使能、锁存、清零的三个时间窗口三根控制线的先后顺序不能换。如果先清零再锁存锁存器拿到的是零如果锁存和清零同时有效结果取决于综合后两个触发器谁先翻转属于典型的综合器心情问题仿真能过、上板翻车。// 计数器内部对三根线的优先级判断clear 必须压过 meas_en always (posedge clk_in or negedge rst_n) begin if (!rst_n) {d5,d4,d3,d2,d1,d0} 0; else if (clear_en) {d5,d4,d3,d2,d1,d0} 0; // 最高优先级 else if (meas_en) /* 正常计数 */; end优先级写成clear meas hold是有讲究的。闸门关断和清零之间隔了 2 个 1ms 节拍理论上不会重叠但按键复位、档位切换都可能把状态机打乱让两根线同时拉高。给 clear 最高优先级最坏情况也只是丢掉一次测量结果不会把脏数据锁存进显示。另一个容易被忽略的点是 meas_en 的下降沿。计数器是在被测信号clk_in的上升沿采样的而 meas_en 是在 1ms 时钟域翻转的两者异步。闸门关闭那一刻计数器可能刚好在采一个边沿导致关闸后还多计了一个数。这个 ±1 已经被算进 2.2 节的量化误差里了不需要额外处理但要知道它确实存在。4. 六位 BCD 计数与动态扫描一次扫描要同时干三件事4.1 进位链低位全 9 才进位别写成嵌套 if十进制计数器和二进制计数器的区别在于每位到 9 就归零并向高位进一。最直白的写法是嵌套判断先判断六位是不是全 9溢出再判断低五位是不是全 9进第六位再判断低四位……一路嵌套下去。原始代码就是这么写的能跑但六层 if-else 综合出来的优先级链很长而且每加一种判断就要动整块结构。更清爽的做法是把进位抽成一条独立的链module bcd_counter ( input wire rst_n, input wire clk_in, // 被测信号已整形为方波 input wire meas_en, // 闸门内为高 input wire clear_en, // 测量结束清零 output reg [3:0] d0,d1,d2,d3,d4,d5, // d0 为最低位 output reg ovf // 溢出标志1 表示超过六位 ); // 进位链cK 为 1 表示第 K 位这一拍要加一 wire c0 meas_en; wire c1 c0 (d0 4d9); wire c2 c1 (d1 4d9); wire c3 c2 (d2 4d9); wire c4 c3 (d3 4d9); wire c5 c4 (d4 4d9); wire c6 c5 (d5 4d9); // 999999 再加一 → 溢出 always (posedge clk_in or negedge rst_n) begin if (!rst_n) begin {d5,d4,d3,d2,d1,d0} 24d0; ovf 1b0; end else if (clear_en) begin {d5,d4,d3,d2,d1,d0} 24d0; ovf 1b0; end else begin if (c0) d0 (d0 4d9) ? 4d0 : d0 1b1; if (c1) d1 (d1 4d9) ? 4d0 : d1 1b1; if (c2) d2 (d2 4d9) ? 4d0 : d2 1b1; if (c3) d3 (d3 4d9 p a hrefhttps://download.csdn.net/download/dongzai_ld/7233499 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p