第三十五章 等精度频率计实验

本章在讲什么

前几章 FPGA 都在"输出"——输出波形、输出数据、输出时间。本章反过来,让 FPGA 当一次测量仪器:测量输入信号的频率,从 5Hz 到 133.33333MHz 都要测得准,结果用 LCD 屏显示。

频率测量的经典难题是 ±1 误差。两种传统方法各有短板:

  • 频率测量法(M法):固定时间 t 内数被测信号的脉冲数 N。闸门开闭与被测信号不同步,最多差 1 个脉冲。信号频率越低,这 1 个脉冲占比越大——适合测高频
  • 周期测量法(T法):用基准时钟数被测信号一个周期有多宽。同样有 ±1 个基准时钟周期的误差,信号频率越高周期越窄,误差占比越大——适合测低频

等精度测量法把两者的优点合二为一:让实际闸门时间自动对齐被测信号的整数个周期,从而消除被测信号的 ±1 误差,只留下高频基准时钟的 ±1 误差——而基准时钟很快,这点误差影响极小,最终全频段精度一致

核心概念拆解

35.1 等精度测量原理:用被测信号上升沿"锁"住闸门

核心操作:预置一个软件闸门,但只在被测信号上升沿到来时才真正开启/关闭闸门。这样实际闸门内被测信号的周期数必然是整数(fx_cnt 无误差),闸门内同时数基准时钟脉冲得到 fs_cnt(最多差 1)。

由同一闸门时间列出等式:

GATE_TIME = fs_cnt / clk_fs = fx_cnt / clk_fx

解出被测频率:

clk_fx = fx_cnt / fs_cnt × clk_fs

误差分析(本章最值得记住的结论):

δ ≤ 1/fs_cnt = 1/(GATE_TIME × clk_fs)

相对误差与被测信号频率完全无关,只取决于闸门时间和基准时钟频率。本实验基准时钟 100MHz、闸门 1s,误差约 10⁻⁸——这就是"等精度"的含义。

35.2 系统架构:测量、计算、显示三大块

top_cymometer
 ├── clk_test      (被测时钟产生:50MHz 分频出 5Hz 测试信号)
 ├── pll_100m      (PLL:产生 100MHz 基准时钟 clk_fs + 133.33MHz 高频测试时钟)
 ├── cymometer     (核心:闸门产生/同步、双计数、触发除法)
 ├── div_fsm       (移位减法除法器:算 fx_cnt×100M / fs_cnt)
 └── lcd_rgb_char  (LCD 显示测得的频率值)

板上的物理连接很朴素:J3 扩展口用跳帽/杜邦线把 FPGA 的时钟输出引脚(K14 输出 5Hz 或 M15 输出 133MHz)和被测输入引脚(J14)短接,即"自产自测"。

35.3 cymometer 模块:三个时钟域的协作

这是本章最精巧的模块,同时工作在 50MHz 系统时钟、未知频率的被测时钟、100MHz 基准时钟三个时钟域下。

第一步:软件闸门。用系统时钟产生周期 1.5s 的门控信号——前 0.25s 和后 0.25s 是低电平(留给数据传递和清零),中间 1s 高电平作为软件闸门 gate_sclk

第二步:闸门同步(等精度的灵魂)。把软件闸门用被测时钟打拍同步,得到实际闸门 gate_fx

always@(posedge clk_fx or negedge sys_rst_n)begin
    if(!sys_rst_n) begin
        gate_fx <= 1'b0;  gate_fx_d0 <= 1'b0;
        gate_fx_d1 <= 1'b0;  gate_fx_d2 <= 1'b0;  gate_fx_d3 <= 1'b0;
    end
    else begin
        gate_fx   <= gate_sclk;   // 关键:用被测时钟采样软件闸门
        gate_fx_d0 <= gate_fx;
        gate_fx_d1 <= gate_fx_d0;
        gate_fx_d2 <= gate_fx_d1;
        gate_fx_d3 <= gate_fx_d2;
    end
end

gate_fx 的每一次翻转都发生在被测时钟的节拍上,闸门开启到关闭之间的时间必然是被测信号周期的整数倍——±1 被测误差就此消除。打四拍既完成亚稳态防护,也顺便生成边沿标志:

assign gate_fx_pose = ((gate_fx) && (!gate_fx_d3));   // 上升沿
assign gate_fx_nege = ((!gate_fx_d2) && gate_fx_d3);  // 下降沿

同样用打拍把 gate_fx 同步到基准时钟域(gate_fs),取其下降沿 gate_fs_nege 做缓存触发。

第三步:双时钟计数。实际闸门为高期间,两个计数器各数各的:

// 被测时钟域:数被测脉冲
always@(posedge clk_fx) begin
    if(gate_fx_d2) cnt_fx <= cnt_fx + 1;
    else           cnt_fx <= 0;
end
// 基准时钟域:数基准脉冲
always@(posedge clk_fs) begin
    if(gate_fx)    cnt_fs <= cnt_fs + 1;
    else if(gate_fs_nege) cnt_fs <= 0;
end

第四步:计算与锁存。公式是 clk_fx = cnt_fx × 100M / cnt_fs,先乘后除:

  • numer <= cnt_fx_reg * CLK_FS_FREQ(57 位被除数);
  • cnt_gate_fs 的特定计数值(利用闸门低电平阶段,即 T1-T2/2±TIME 等时刻)对分子、分母打拍缓存,保证送进除法器的数据稳定;
  • 产生一拍 calc_flagnumer_regcnt_fs_reg_reg 输出到 dividend/divisor,同时拉高 en 启动除法器;
  • 收到 vld_out(商有效)后 data_fx <= quotient

两个健壮性细节(工程思维的体现):

  • 上电清零fx_flag 在被测时钟和闸门同时为高时置 1,为 0 时强制 data_fx = 0,避免上电显示乱码;
  • 断线清零cnt_dely 计时 2s(超过闸门周期 1.5s)没有新的实际闸门出现,flag_dely 置 1 把频率值清零——表示被测信号已消失。

35.4 div_fsm:模拟手算的移位减法除法器

FPGA 里除法不能指望 / 运算符(多数综合工具无法高效综合),且这里被除数高达 57 位,必须自己写除法器。算法是模拟手算竖式

  1. 被除数高位补零扩展成 2 倍位宽,除数低位补零对齐;
  2. 每轮先比较再移位:a ≥ b 则商上 1、a = a - ba < b 则商上 0;
  3. a 左移一位,重复,直到移满位宽;
  4. 结束时 a 的高半段是余数、低半段是商。

以 7÷2 为例:高位补零后逐次移位,第三次移位后 a>b 就减,四次移位后高 4 位是余数 1、低 4 位是商 3。用四状态状态机实现:

localparam IDLE  = 2'b00;  // 空闲,等 en
localparam SUB   = 2'b01;  // 比较:大于则减并商1,否则商0
localparam SHIFT = 2'b10;  // 左移一位,count计数
localparam DONE  = 2'b11;  // 输出商和余数,vld_out拉高一拍

// SUB状态核心逻辑
SUB: begin
    if(dividend_e >= divisor_e) begin
        quotient_e <= {quotient_e[DATAWIDTH-2:0], 1'b1};  // 商左移进1
        dividend_e <= dividend_e - divisor_e;             // 减
    end
    else
        quotient_e <= {quotient_e[DATAWIDTH-2:0], 1'b0};  // 商左移进0
end

除法器运行期间 ready 拉低(不接受新数据),算完 vld_out 拉高一拍通知上层取结果。

35.5 显示端:二进制转 BCD 再索引字模

除法器输出的频率值是二进制(不像上一章 RTC 天生就是 BCD),而 LCD 字模索引需要十进制的每一位。所以显示链路里加了一个 binary2bcd 模块把二进制转成 BCD 码,然后 lcd_display 用与上章相同的字模方式显示 9 位数字(data[35:32] 亿位 ~ data[3:0] 个位),外加字符 "H"、"z" 组成单位 Hz。

图片对照

等精度测量原理

图:等精度测量原理——实际闸门由被测信号上升沿开启和关闭,闸门内被测周期数为整数

硬件原理图

图:硬件连接——跳帽把时钟输出引脚与被测输入引脚短接,实现自产自测

测频原理示意图

图:软件闸门时序——1.5s 周期,中间 1s 实际闸门,前后各 0.25s 留给数据传递

实验现象

图:测 5Hz 信号,LCD 显示 000000005Hz

实验现象2

图:测 133.33333MHz 信号,显示 133333334Hz(与实际仅差 4Hz)

初学者容易踩的坑

  1. 闸门没用被测时钟同步:直接用系统时钟产生的软件闸门去计数,±1 被测误差依旧存在,"等精度"名存实亡。gate_fx <= gate_sclk 这一句在被测时钟域里的赋值是整个实验的灵魂。
  2. 跨时钟域不打底:三个时钟域之间的信号传递(gate_sclk→clk_fx、gate_fx→clk_fs)如果不打两拍以上,亚稳态会偶发毁掉计数值。打拍的另一个目的是稳定地取边沿。
  3. 除法器位宽不够:被测频率可达 133MHz,cnt_fx × 100M 的乘积远超 32 位,本实验用了 57 位。中间量被截断会得到莫名其妙的测频结果。
  4. 除数为零:被测信号断开时 cnt_fs = 0,除法器除零行为未定义。本实验靠 fx_flag/flag_dely 清零机制规避,自己设计时也要有这道防线。
  5. 等不了结果就显示:除法器是逐位移位计算的(57 位要算上百拍),必须在 vld_out 有效后才更新 data_fx,提前取数显示的是旧值或半成品。
  6. 测高频时忘了引脚约束:133MHz 走普通 IO 已接近极限(实测差了 4Hz),被测时钟输入引脚需要 set_property CLOCK_DEDICATED_ROUTE FALSE 等特殊处理,且高频测试优先用短跳帽而非杜邦线。
  7. TB 不缩参数:闸门 1.5s、被测时钟 5Hz 直接仿真是灾难。TB 里把 CNT_GATE_MAX 缩到 7500、DIV_N 缩到 10、被测时钟用 always #100(5MHz),几毫秒就能看到完整测量周期。

小结 & 下一步

本章是全书"数学含量"最高的一章,收获可以归结为:

  • 算法思想:等精度测量法用"闸门与被测信号同步"这一招,把全频段精度统一到 1/(GATE_TIME × clk_fs),彻底摆脱 ±1 被测误差;
  • 多时钟域设计:一个模块内 50MHz/100MHz/未知时钟共存,打拍同步、边沿提取、下降沿缓存是标准动作;
  • 自制除法器:理解了移位减法算法如何模拟手算竖式,以及为什么大位宽除法必须自建;
  • 仪器化思维:上电清零、断线清零、计算完成握手(en/ready/vld_out),测量结果要"可信"还要"可呈现"。

下一步:第三十六章是第五篇的收官之战——RGB LCD 触摸屏实验。将学习 I2C 又一次实战(触摸控制器 GT9xx 也走 I2C 总线)、触摸坐标解析以及触摸与显示的联动,为整个实战篇画上句号。