频率测量在诸多领域都有广泛的应用,常用的频率测量方法有两种,分别是频率测量法和周期测量法。
频率测量法:在时间t内对被测时钟信号的时钟周期N进行计数,然后求出单位时间内的时钟周期数,即为被测时钟信号的时钟频率。
周期测量法:先测量出被测时钟信号的时钟周期T,然后根据频率f = 1/T求出被测时钟信号的频率。
但是上述两种方法都会产生±1个被测时钟周期的误差,在实际应用中有一定的局限性;而且根据两种方式的测量原理,很容易发现频率测量法适合于测量高频时钟信号,而周期测量法适合于低频时钟信号的测量,但二者都不能兼顾高低频率同样精度的测量要求。
等精度测量法与前两种方式不同,其最大的特点是,测量的实际门控时间不是一个固定值,它与被测时钟信号相关,是被测时钟信号周期的整数倍。在实际门控信号下,同时对标准时钟和被测时钟信号的时钟周期进行计数,再通过公式计算得到被测信号的时钟频率。
由于实际门控信号是被测时钟周期的整数倍,就消除了被测信号产生的±1时钟周期的误差,但是会产生对标准时钟信号±1时钟周期的误差。等精度测量原理示意图如图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。
第一部分:软件闸门gate_s及相关信号的设计与实现
由等精度测量原理可知,实现等精度测量必不可少的是实际闸门,而实际闸门是由软件闸门得来,所以我们先来生成一下软件闸门。我们计划一个完整周期的软件闸门为1.5s,前0.25s保持低电平,中间1s保持高电平,最后0.25s保持低电平。低电平部分是为了将各计数器清0,并计算待测时钟信号时钟频率;高电平部分就是软件闸门有效部分,高电平保持1s是为了提高测试精度
软件闸门的生成我们需要声明计数器进行时间计数,计数时钟使用系统时钟sys_clk。声明软件闸门计数器cnt_gate_s,计数时钟为50MHz系统时钟,时钟周期为20ns,计数器cnt_gate_s初值为0,在(0 – CNT_GATE_S_MAX)范围内循环计数。
声明软件闸门gate_s,只有计数器cnt_gate_s计数在((CNT_RISE_MAX+1)-(CNT_GATE_S_MAX-CNT_RISE_MAX))范围内保持有效高电平,高电平保持时间为1s,其他时刻均为低电平。两信号波形图如下。
第二部分:实际闸门gate_a的设计与实现
生成软件闸门后,使用被测时钟对软件闸门进行同步生成实际闸门gate_a,实际闸门波形图如下。
第三部分:实际闸门下,标准信号和被测信号时钟计数相关信号的波形设计与实现
在实际闸门下,分别对标准信号和被测信号的时钟周期进行计数。声明计数器cnt_clk_stand,在实际闸门下对标准时钟信号clk_stand进行时钟周期计数;声明计数器cnt_clk_test,在实际闸门下对被测时钟信号clk_test进行时钟周期计数,两计数器波形如下。
计数器cnt_clk_stand、cnt_clk_test在实际闸门下计数完成后,需要进行数据清零,方便下次计数。但是被测时钟频率的计算需要计数器的数据,所以在计数器数据清零之前我们需要将计数器数据做一下寄存,对于数据寄存的时刻,我们选择实际闸门的下降沿。
声明寄存器cnt_clk_stand_reg;在标准时钟信号clk_stand同步下对实际闸门打一拍得到gate_a_s;使用实际闸门gate_a和gate_a_s得到标准时钟下的实际闸门下降沿标志信号gate_a_fall_stand。当gate_afall_stand信号为高电平时,将计数器cnt_clk_stand数值赋值给寄存器cnt_clk_stand_reg。
对于计数器cnt_clk_test的数值寄存,我们使用相同的方法,声明寄存器cnt_clk_test_reg;在被检测时钟信号clk_test同步下对实际闸门打一拍得到gate_a_t;使用实际闸门gate_a和gate_a_t得到被检测时钟下的实际闸门下降沿标志信号gate_a_fall_test。当gate_a_fall_test信号为高电平时,将计数器cnt_clk_test数值赋值给cnt_clk_test_reg。
上述各信号的信号波形如下
第四部分:频率计算结果freq等相关信号波形的设计与实现
实际闸门下的标准时钟和被测时钟的周期个数已经完成计数,且对结果进行了寄存,标准时钟信号的时钟频率为已知量,得到这些参数,结合公式可以进行频率的求解。同时,新的问题出现,在哪一时刻进行数据求解。
我们可以利用最初声明的软件闸门计数器cnt_gate_s,声明计算标志信号calc_flag,在计数器cnt_gate_s计数到最大值,将calc_flag拉高一个时钟周期的高电平作为计算标志,计算被检测时钟信号时钟频率freq_reg(注意变量位宽是否满足要求);然后在系统时钟下将计算标志信号calc_flag打一拍,得到时钟频率输出标志信号calc_flag_reg,当时钟频率输出标志信号calc_flag_reg为高电平时,将时钟频率计算结果freq_reg赋值给输出信号freq。各信号波形图如下。
到了这里,频率计算模块涉及的各信号波形均已设计并实现,经过整合后就得到频率计算模块整体波形图。
下面贴出代码仅供参考:
module freq_meter_calc
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire clk_test , //待检测时钟
output reg [33:0] freq //待检测时钟频率
);
//********************************************************************//
//****************** Parameter And Internal Signal *******************//
//********************************************************************//
//parameter define
parameter CNT_GATE_S_MAX = 28'd37_499_999 , //软件闸门计数器计数最大值
CNT_RISE_MAX = 28'd6_250_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 ; //实际闸门下降沿(待检测时钟下)
//reg define
reg [27:0] cnt_gate_s ; //软件闸门计数器
reg gate_s ; //软件闸门
reg gate_a ; //实际闸门
reg gate_a_test ;
reg gate_a_stand ; //实际闸门打一拍(标准时钟下)
reg gate_a_stand_reg ;
reg gate_a_test_reg ; //实际闸门打一拍(待检测时钟下)
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_reg ; //实际闸门下待检测时钟周期数
reg calc_flag ; //待检测时钟时钟频率计算标志信号
//********************************************************************//
//***************************** 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;
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;
//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_stand == 1'b0)
cnt_clk_stand <= 48'd0;
else if(gate_a_stand == 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_test == 1'b0)
cnt_clk_test <= 48'd0;
else if(gate_a_test == 1'b1)
cnt_clk_test <= cnt_clk_test + 1'b1;
//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_test;
always@(posedge clk_stand or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
gate_a_stand_reg <= 1'b0;
else
gate_a_stand_reg <= gate_a_stand;
//gate_a_fall_s:实际闸门下降沿(标准时钟下)
assign gate_a_fall_s = ((gate_a_stand_reg == 1'b1) && (gate_a_stand == 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)
cnt_clk_stand_reg <= 32'd0;
else if(gate_a_fall_s == 1'b1)
cnt_clk_stand_reg <= cnt_clk_stand;
//gate_a_test:实际闸门打一拍(待检测时钟下)
always@(posedge clk_test or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
gate_a_test_reg <= 1'b0;
else
gate_a_test_reg <= gate_a_test;
//gate_a_fall_t:实际闸门下降沿(待检测时钟下)
assign gate_a_fall_t = ((gate_a_test_reg == 1'b1) && (gate_a_test == 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)
cnt_clk_test_reg <= 32'd0;
else if(gate_a_fall_t == 1'b1)
cnt_clk_test_reg <= cnt_clk_test;
//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:待检测时钟信号时钟频率
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
freq <= 34'd0;
else if(calc_flag == 1'b1)
freq <= (CLK_STAND_FREQ / cnt_clk_stand_reg * cnt_clk_test_reg);
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//---------- clk_gen_inst ----------
clk_gen clk_gen_inst
(
.RESET (~sys_rst_n ),
.CLK_IN1 (sys_clk ),
.CLK_OUT1 (clk_stand )
);
endmodule