基于FPGA数字频率计的设计(可测频率、占空比、相位差)

发布时间 2023-09-24 09:45:04作者: 余你余生

设计一款数字频率计,可测量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博客