基于FPGA数字频率计的设计(可测频率、占空比、相位差)
设计一款数字频率计,可测量1hz-100Mhz频率,占空比,以及两路同频时钟信号的相位差。
测量频率的方法:等精度测量。
等精度测量原理:测量的实际门控时间不是一个固定值,它与被测时钟信号相关,是被测时钟信号周期的整数倍。在实际门控信号下,同时 对标准时钟和被测时钟信号的时钟周期进行计数,再通过公式计算得到被测信号的时钟频率。由于实际门控信号是被测时钟周期的整数倍,就消除了被测信号产生的±1 时钟周期 的误差,但是会产生对标准时钟信号±1 时钟周期的误差。等精度测量原理示意图如图 。
结合等精度测量原理和原理示意图可得:被测时钟信号的时钟频率 fx 的相对误差与被 测时钟信号无关;增大“软件闸门”的有效范围或者提高“标准时钟信号”的时钟频率 fs,可以减小误差,提高测量精度。首先我们先分别对实际闸门下被测时钟信号和标准时钟信号的时钟周期进行计数。 实际闸门下被测时钟信号周期数为 X,设被测信号时钟周期为 Tfx,它的时钟频率 fx = 1/Tfx,由此可得等式:X * Tfx = X / fx = Tx(实际闸门)。 实际闸门下标准时钟信号周期数为 Y,设被测信号时钟周期为 Tfs,它的时钟频率 fs = 1/Tfs,由此可得等式:Y * Tfs = Y / fs = Tx(实际闸门)。其次,将两等式结合得到只包含各自时钟周期计数和时钟频率的等式:X / fx = Y / fs = Tx(实际闸门),等式变换,得到被测时钟信号时钟频率计算公式:fx = X * fs / Y。 最后,将已知量标准时钟信号时钟频率 fs 和测量量 X、Y 带入计算公式,得到被测时 钟信号时钟频率 fx。
占空比如何计算;
分别对输入信号在高电平时间内计数,低电平时间内计数。占空比 = 高电平计数值 / (高电平计数值 + 低电平计数值)*100。乘100,因为是百分比。程序中描述:duty_reg <= (cnt_clk_test_high_reg*100)/(cnt_clk_test_high_reg+cnt_clk_test_low_reg);
相位差如何计算:
只需将两个待测时钟信号异或,将异或后的信号再将高电平时间计算出来即为时间差。程序中描述:phase_reg <= (cnt_clk_test_high_phase_reg*180)/(cnt_clk_test_high_phase_reg+cnt_clk_test_low_phase_reg)。这里乘180,因为是异或后,异或信号周期减半。
整体模块设计:
RTL代码设计:
`timescale 1ns/1ns module freq_meter_calc ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire clk_test , //待检测时钟 input wire clk_test_1 , //待检测时钟 output reg [7:0] duty , //待检测时钟占空比 output reg [8:0] phase , //待检测两个同频时钟相位差 output reg [33:0] freq //待检测时钟频率 ); //********************************************************************// //****************** Parameter And Internal Signal *******************// //********************************************************************// //parameter define parameter CNT_GATE_S_MAX = 28'd74_999_999 , //软件闸门计数器计数最大值 CNT_RISE_MAX = 28'd12_500_000 ; //软件闸门拉高计数值 parameter CLK_STAND_FREQ = 28'd100_000_000 ; //标准时钟时钟频率 //wire define wire clk_stand ; //标准时钟,频率100MHz wire gate_a_fall_s ; //实际闸门下降沿(标准时钟下) wire gate_a_fall_t ; //实际闸门下降沿(待检测时钟下) wire clk_test_phase ; //测相位差 //reg define reg [27:0] cnt_gate_s ; //软件闸门计数器 reg gate_s ; //软件闸门 reg gate_a ; //实际闸门 reg gate_a_stand ; //实际闸门打一拍(标准时钟下) reg gate_a_test ; //实际闸门打一拍(待检测时钟下) reg [47:0] cnt_clk_stand ; //标准时钟周期计数器 reg [47:0] cnt_clk_stand_reg ; //实际闸门下标志时钟周期数 reg [47:0] cnt_clk_test ; //待检测时钟周期计数器 reg [47:0] cnt_clk_test_high ; //待检测时钟周期占空比高计数器 reg [47:0] cnt_clk_test_low ; //待检测时钟周期占空比低计数器 reg [47:0] cnt_clk_test_high_reg ; //待检测时钟周期占空比高计数器 reg [47:0] cnt_clk_test_low_reg ; //待检测时钟周期占空比低计数器 reg [47:0] cnt_clk_test_high_phase ; //待检测时钟周期占空比高计数器 reg [47:0] cnt_clk_test_low_phase ; //待检测时钟周期占空比低计数器 reg [47:0] cnt_clk_test_high_phase_reg ; //待检测时钟周期占空比高计数器 reg [47:0] cnt_clk_test_low_phase_reg ; //待检测时钟周期占空比低计数器 reg [47:0] cnt_clk_test_reg ; //实际闸门下待检测时钟周期数 reg calc_flag ; //待检测时钟时钟频率计算标志信号 reg [63:0] freq_reg ; //待检测时钟频率寄存 reg [7:0] duty_reg ; //待检测时钟占空比寄存 reg [8:0] phase_reg ; //待检测时钟相位差寄存 reg calc_flag_reg ; //待检测时钟频率输出标志信号 assign clk_test_phase=clk_test^clk_test_1; //********************************************************************// //***************************** Main Code ****************************// //********************************************************************// //cnt_gate_s:软件闸门计数器 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_gate_s <= 28'd0; else if(cnt_gate_s == CNT_GATE_S_MAX) cnt_gate_s <= 28'd0; else cnt_gate_s <= cnt_gate_s + 1'b1; //gate_s:软件闸门 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) gate_s <= 1'b0; else if((cnt_gate_s>= CNT_RISE_MAX) && (cnt_gate_s <= (CNT_GATE_S_MAX - CNT_RISE_MAX))) gate_s <= 1'b1; else gate_s <= 1'b0; //gate_a:实际闸门 always@(posedge clk_test or negedge sys_rst_n) if(sys_rst_n == 1'b0) gate_a <= 1'b0; else gate_a <= gate_s; //cnt_clk_stand:标准时钟周期计数器,计数实际闸门下标准时钟周期数 always@(posedge clk_stand or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_clk_stand <= 48'd0; else if(gate_a == 1'b0) cnt_clk_stand <= 48'd0; else if(gate_a == 1'b1) cnt_clk_stand <= cnt_clk_stand + 1'b1; //cnt_clk_test:待检测时钟周期计数器,计数实际闸门下待检测时钟周期数 always@(posedge clk_test or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_clk_test <= 48'd0; else if(gate_a == 1'b0) cnt_clk_test <= 48'd0; else if(gate_a == 1'b1) cnt_clk_test <= cnt_clk_test + 1'b1; reg clk_test_reg ; always@(posedge clk_stand or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk_test_reg <= 1'b0; else clk_test_reg <= clk_test; reg clk_test_phase_reg ; always@(posedge clk_stand or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk_test_phase_reg <= 1'b0; else clk_test_phase_reg <= clk_test_phase; //cnt_clk_test_high:待检测时钟周期高计数器,计数实际闸门下待检测时钟周期数 always@(posedge clk_stand or negedge sys_rst_n) begin if(sys_rst_n == 1'b0) begin cnt_clk_test_high <= 48'd0; cnt_clk_test_low <= 48'd0; end else if(calc_flag_reg == 1'b1) begin cnt_clk_test_high <= 48'd0; cnt_clk_test_low <= 48'd0; end else if(gate_a == 1'b0) begin cnt_clk_test_high <= cnt_clk_test_high ; cnt_clk_test_low <= cnt_clk_test_low; end else if(gate_a == 1'b1) begin if(clk_test_reg==1'b1) cnt_clk_test_high <= cnt_clk_test_high + 1'b1; else cnt_clk_test_low <= cnt_clk_test_low + 1'b1 ; end else begin cnt_clk_test_high <= cnt_clk_test_high ; cnt_clk_test_low <= cnt_clk_test_low; end end //cnt_clk_test_high_phase:待检测时钟周期高计数器,计数实际闸门下待检测时钟周期数 always@(posedge clk_stand or negedge sys_rst_n) begin if(sys_rst_n == 1'b0) begin cnt_clk_test_high_phase <= 48'd0; cnt_clk_test_low_phase <= 48'd0; end else if(calc_flag_reg == 1'b1) begin cnt_clk_test_high_phase <= 48'd0; cnt_clk_test_low_phase <= 48'd0; end else if(gate_a == 1'b0) begin cnt_clk_test_high_phase <= cnt_clk_test_high_phase ; cnt_clk_test_low_phase <= cnt_clk_test_low_phase; end else if(gate_a == 1'b1) begin if(clk_test_phase_reg==1'b1) cnt_clk_test_high_phase <= cnt_clk_test_high_phase + 1'b1; else cnt_clk_test_low_phase <= cnt_clk_test_low_phase + 1'b1 ; end else begin cnt_clk_test_high_phase <= cnt_clk_test_high_phase ; cnt_clk_test_low_phase <= cnt_clk_test_low_phase; end end //gate_a_stand:实际闸门打一拍(标准时钟下) always@(posedge clk_stand or negedge sys_rst_n) if(sys_rst_n == 1'b0) gate_a_stand <= 1'b0; else gate_a_stand <= gate_a; //gate_a_fall_s:实际闸门下降沿(标准时钟下) assign gate_a_fall_s = ((gate_a_stand == 1'b1) && (gate_a == 1'b0)) ? 1'b1 : 1'b0; //cnt_clk_stand_reg:实际闸门下标志时钟周期数 always@(posedge clk_stand or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin cnt_clk_stand_reg <= 32'd0; cnt_clk_test_low_reg <= 48'd0; cnt_clk_test_high_reg <= 48'd0; cnt_clk_test_low_phase_reg<=48'd0; cnt_clk_test_high_phase_reg<=48'd0; end else if(gate_a_fall_s == 1'b1) begin cnt_clk_stand_reg <= cnt_clk_stand; cnt_clk_test_low_reg <= cnt_clk_test_low; cnt_clk_test_high_reg <= cnt_clk_test_high; cnt_clk_test_low_phase_reg<=cnt_clk_test_low_phase; cnt_clk_test_high_phase_reg<=cnt_clk_test_high_phase; end //gate_a_test:实际闸门打一拍(待检测时钟下) always@(posedge clk_test or negedge sys_rst_n) if(sys_rst_n == 1'b0) gate_a_test <= 1'b0; else gate_a_test <= gate_a; //gate_a_fall_t:实际闸门下降沿(待检测时钟下) assign gate_a_fall_t = ((gate_a_test == 1'b1) && (gate_a == 1'b0)) ? 1'b1 : 1'b0; //cnt_clk_test_reg:实际闸门下待检测时钟周期数 always@(posedge clk_test or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin cnt_clk_test_reg <= 32'd0; end else if(gate_a_fall_t == 1'b1) begin cnt_clk_test_reg <= cnt_clk_test; end //calc_flag:待检测时钟时钟频率计算标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) calc_flag <= 1'b0; else if(cnt_gate_s == (CNT_GATE_S_MAX - 1'b1)) calc_flag <= 1'b1; else calc_flag <= 1'b0; //freq_reg:待检测时钟信号时钟频率寄存 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin freq_reg <= 64'd0; duty_reg <= 8'd0; end else if(calc_flag == 1'b1) begin freq_reg <= (CLK_STAND_FREQ * cnt_clk_test_reg / (cnt_clk_stand_reg)); duty_reg <= (cnt_clk_test_high_reg*100)/(cnt_clk_test_high_reg+cnt_clk_test_low_reg); phase_reg <= (cnt_clk_test_high_phase_reg*180)/(cnt_clk_test_high_phase_reg+cnt_clk_test_low_phase_reg); end //calc_flag_reg:待检测时钟频率输出标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) calc_flag_reg <= 1'b0; else calc_flag_reg <= calc_flag; //freq:待检测时钟信号时钟频率 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin freq <= 34'd0; duty <= 8'd0 ; end else if(calc_flag_reg == 1'b1) begin freq <= freq_reg[33:0]; duty <= duty_reg ; phase <= phase_reg ; end //********************************************************************// //*************************** Instantiation **************************// //********************************************************************// //---------- clk_gen_inst ---------- clk_gen clk_gen_inst ( .areset (~sys_rst_n ), .inclk0 (sys_clk ), .c0 (clk_stand ) ); endmodule
激励代码:
`timescale 1ns/1ns module tb_freq_meter_calc(); //********************************************************************// //****************** Parameter And Internal Signal *******************// //********************************************************************// //wire define reg sys_clk ; reg sys_rst_n ; reg clk_test_a ; reg clk_test_b ; wire [7:0] duty ; wire [33:0] freq ; wire [8:0] phase ; //时钟、复位、待检测时钟的生成 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #200 sys_rst_n <= 1'b1; #20 clk_test_b = 1'b1 ; #200 clk_test_a = 1'b1; end //这里设置 模拟输入时钟 always #10 sys_clk = ~sys_clk ; //50MHz系统时钟 //这里设置 模拟待测输入时钟 always #1000000 clk_test_a= ~clk_test_a ; //5MHz待检测时钟 always #1000000 clk_test_b= ~clk_test_b ; //5MHz待检测时钟 //设置相位差 可调节 /* // (clk_test_1/clk_test_1+clk_test) *2 always begin #70 clk_test_a= ~clk_test_a ; //5MHz待检测时钟 #30 clk_test_b= ~clk_test_b ; //5MHz待检测时钟 end */ //这里可以设置可以占空比可调节 /*initial begin clk_test_a=1; forever begin //产生占空比为60%,5Mhz的方波 #280; clk_test_a=0; #120; clk_test_a=1; end end // 这里我解释一下如何计算 这个占空比 开始clk_test_a=1; 过了280ns后 变为clk_test_a=0; 再过 120ns 变为 clk_test_a=1; 如此循环 他的占空比 和频率 可以这样 initial begin clk_test_b=1; forever begin //产生占空比为60%,5Mhz的方波 #280; clk_test_b=0; #120; clk_test_b=1; end end */ //重定义软件闸门计数时间,缩短仿真时间 defparam freq_meter_inst.freq_meter_calc_inst.CNT_GATE_S_MAX = 2400 ; defparam freq_meter_inst.freq_meter_calc_inst.CNT_RISE_MAX = 400 ; //********************************************************************// //*************************** Instantiation **************************// //********************************************************************// //------------- freq_meter_inst ------------- tb_freq_meter_calc tb_freq_meter_calc_inst ( .sys_clk (sys_clk ) , //系统时钟,频率50MHz .sys_rst_n (sys_rst_n) , //复位信号,低电平有效 .clk_test (clk_test_a ) , //待检测时钟0 .clk_test_1 (clk_test_b) , //待检测时钟1 .clk_out (clk_out ) , //顶层模块输出 .duty (duty ) , //待检测时钟占空比 .phase (phase ) , //待检测两个同频时钟相位差 .freq (freq ) //待检测时钟频率 ); endmodule
仿真结果如下:
频率为5MHZ、占空比为50%、相位差为0
频率为5MHZ、占空比为60%、相位差为0
频率为5MHZ、占空比为50%、相位差为36°
20/200=0.1
0.1*360=36
频率为5MHZ、占空比为50%、相位差为90°
频率为1.667MHZ、占空比为50%、相位差为120°
200/600=1/3
1/3*360=120
若有不对的地方,敬请指正,万分感谢。
参考资料:
1、野火FPGA教程:101-第三十四讲-简易频率计的设计与验证(五)_哔哩哔哩_bilibili
2、基于FPGA的等精度多功能测频仪( 四)相位差测量——完_fpga相位差测量_是你的橙汁的博客-CSDN博客