FPGA—简易频率计(附代码)

目录

1. 内容概要

2. 理论学习

3. 实操

3.1 整体设计

3.2 频率计算模块

3.2.1 模块框图

3.2.2  波形图绘制

3.2.3  RTL代码

3.3 顶层模块

3.4 仿真验证

3.5 上板验证

4. 总结

1. 内容概要

       频率测量在电子设计领域和测量领域经常被使用,本文讲解等精度测量法的原理和实现方法,使用FPGA 设计并实现一个简易频率计。

2. 理论学习

      常用的频率测量方法有三种,计频法、计时法、等精度法。
      计频法:在时间 t 内对被测信号的脉冲数进行计数,然后求出单位时间内的脉冲数,即为被测信号的频率。由于时间 t 可能存在不是时钟周期N的整数倍,故存在误差。当频率越大时,误差就相对较小,计频法更适合测高频信号。

      计时法:在一个被测信号的周期内,测量基准时钟的个数,得到被测信号的周期,再将其转化为频率。当被测信号频率越小,多计或少计的那个不完整的基准时钟在整个被测信号周期中占比就更小,故更适合低频信号。

       由上面讲解知,计频法与计时法可能存在±1个脉冲的误差,而且不能适应时钟频率变化跨度大的检测,在应用上存在局限性。

       等精度测量法与上面两种不同,其最大的特点是软件闸门是我们自己设定的一个时间,在被测量时钟信号下同步这样就得到一个实际的闸门,软件闸门不一定等于实际闸门,它会出现正负1的时钟误差,不能确定软件闸门的是否是被测量时钟下的整数倍,但一定能确定实际闸门是被测时钟的整数倍。在实际门控时间内同时对被测信号和标准时钟信号进行时钟周期的计数,然后通过频率与时间的关系就可以计算出被测时钟频率。实际门控信号会产生对标准时钟信号±1时钟周期的误差,为了减小此误差将标准时钟频率设置极高以及增大软件阀门时钟信号时间,误差就会非常小。等精度测量原理示意图如下图。

等精度测量法求被测信号频率的计算方法:

    在实际阀门Tx下同时对被测信号和标准时钟信号进行时钟周期的计数分别设为X与Y。

    实际阀门Tx = Tfx * X = (1/fx)  * X  = (1/fs)* Y ;只有fx一个未知量。  (Tfs表示一个被测时钟周期时间)

    由上可得  fx = (X * fs ) / Y

3. 实操

3.1 整体设计

 实验目标: 对输入的未知时钟信号做频率测量,并将测量结果显示在数码管上。

 整体实验框图

 工程各模块关系图

由图可知, 本实验工程包括 4 个子模块。

频率计算模块(freq_meter_calc)是实验工程的核心模块,它将输入的待检测信号利用等精度测量法进行计算,得出被测时钟信号时钟频率并输出;

被测时钟生成模块(clk_test_gen)使用PLL IP核产生某一频率的待检测时钟信号,之前文章有讲不详细说明;

数码管显示模块(seg_dynamic)接收频率计算模块输出的计算结果,并显示在数码管上,之前文章有讲不详细说明;

顶层模块(freq_meter)将上述 3 个子功能模块进行实例化,连接各自对应信号,外部输入时钟、复位和待检测信号,输出段选、位选和待检测数据。

3.2 频率计算模块

3.2.1 模块框图

       模块内部实例化一个时钟生成 IP 核,负责将 50MHz 系统时钟信号(sys_clk)倍频生成100MHz 标准时钟。为什么要使用 100MHz 时钟信号作为标志信号呢?增大“标准时钟信号”的频率 fs,可以减小误差,提高测量精度。

3.2.2  波形图绘制

3.2.3 RTL代码

代码分为四个部分:

step1: 按照原理生成软件闸门、实际闸门

step2:得到待测信号的周期数 X

step3:得到标准信号的周期数 Y

step4:  利用公式进行频率计算

`timescale  1ns/1ns

module  freq_meter_calc
(
    input   wire            sys_clk     ,   
    input   wire            sys_rst_n   ,   
    input   wire            clk_test    ,   //待检测时钟

    output  reg     [33:0]  freq            //待检测时钟频率

);
parameter   CNT_GATE_S_MAX  =   28'd37_499_999  ,   //软件闸门计数器计数最大值  1.5s
            CNT_RISE_MAX    =   28'd6_250_000   ;   //软件闸门拉高计数值,   1.25s
parameter   CLK_STAND_FREQ  =   28'd100_000_000 ;   //标准时钟时钟频率,    100Mhz

wire            clk_stand           ;   
wire            gate_a_flag_s       ;   
wire            gate_a_flag_t       ;   

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           ;   
reg     [63:0]  freq_reg            ;
reg             calc_flag_reg       ;

//step1:按照原理生成软件闸门、实际闸门
//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;
		
//step2:得到待测信号的周期数 X
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_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;	
		
//cnt_clk_test:待检测时钟周期计数器,计数实际闸门下待检测时钟周期数。  x++
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_flag_t:实际闸门下降沿(待检测时钟下)
assign  gate_a_flag_t = ((gate_a_test_reg == 1'b1) && (gate_a_test == 1'b0))
                        ? 1'b1 : 1'b0;
						
//cnt_clk_test_reg:实际闸门下待检测时钟周期数,  x
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_flag_t == 1'b1)
        cnt_clk_test_reg   <=  cnt_clk_test;	

//step3:得到标准信号的周期数 y		
//使用PLL ipcore生成100Mhz信号
clk_gen clk_gen_inst
(
    .RESET    (~sys_rst_n ),
    .CLK_IN1  (sys_clk    ),
     
    .CLK_OUT1 (clk_stand  )
);	

//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_flag_s:实际闸门下降沿(标准时钟下)
assign  gate_a_flag_s = ((gate_a_stand_reg == 1'b1) && (gate_a_stand == 1'b0))
                        ? 1'b1 : 1'b0;
						
//cnt_clk_stand:标准时钟周期计数器,计数实际闸门下标准时钟周期数。  y++
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_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_flag_s == 1'b1)
        cnt_clk_stand_reg   <=  cnt_clk_stand;
		
//step4: 利用公式进行频率计算		
//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_reg    <=  64'd0;
    else    if(calc_flag == 1'b1)
        freq_reg    <=  (CLK_STAND_FREQ * cnt_clk_test_reg / cnt_clk_stand_reg );    //   (100MHZ*X)/Y 

 //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)
        freq <= 34'd0;
    else if(calc_flag_reg == 1'b1)
        freq <= freq_reg[33:0];	  

endmodule

       * 注意  freq_reg   <=  (CLK_STAND_FREQ * cnt_clk_test_reg / cnt_clk_stand_reg );此语句当中的公式 CLK_STAND_FREQ * cnt_clk_test_reg / cnt_clk_stand_reg  是一步步进行运算的,先进行的乘法运算需要较大的位宽所以  freq_reg 位宽为64位,后面除法运算结束位宽也会缩小,所以赋给freq的值位宽又为34。 

3.3 顶层模块

`timescale  1ns/1ns

module  freq_meter
(
    input   wire            sys_clk     ,   
    input   wire            sys_rst_n   ,   
    input   wire            clk_test    ,   //待检测时钟

    output  wire            clk_out     ,   //生成的待检测时钟
    output  wire    [5:0]   sel         ,   //数码管位选信号
    output  wire    [7:0]   seg             //数码管段选信号

);

wire    [33:0]  freq    ;   //计算得到的待检测信号时钟频率
wire            CLK_OUT1;   //PLL核输出时钟 
//使用PLL ip核生成待测信号
clk_test_gen    clk_test_gen_inst
(
    .RESET     (~sys_rst_n ),  //复位端口,高电平有效
    .CLK_IN1   (sys_clk    ),  //输入系统时钟

    .CLK_OUT1  (CLK_OUT1   )   //输出生成的待检测时钟信号
);
ODDR2
#(
    .DDR_ALIGNMENT ("NONE" ), 
    .INIT          (1'b0   ), 
    .SRTYPE        ("SYNC" )  
)
ODDR2_inst
(
    .Q  (clk_out    ), 
    .C0 (CLK_OUT1   ), 
    .C1 (~CLK_OUT1  ), 
    .CE (1'b1       ), 
    .D0 (1'b1       ), 
    .D1 (1'b0       ), 
    .R  (1'b0       ), 
    .S  (1'b0       )  
);

freq_meter_calc freq_meter_calc_inst
(
    .sys_clk    (sys_clk    ),  
    .sys_rst_n  (sys_rst_n  ),  
    .clk_test   (clk_test   ),   //待检测时钟

    .freq       (freq       )    //待检测时钟频率  
);

seg_dynamic     seg_dynamic_inst
(
    .sys_clk     (sys_clk    ), 
    .sys_rst_n   (sys_rst_n  ), 
    .data        (freq/1000  ), 
    .point       (6'b001000  ), 
    .seg_en      (1'b1       ), 
    .sign        (1'b0       ), 

    .sel         (sel        ), 
    .seg         (seg        ) 
);
endmodule

3.4 仿真验证

顶层仿真代码:

`timescale  1ns/1ns

module tb_freq_meter();

wire    [5:0]   sel         ;
wire    [7:0]   seg         ;

reg             sys_clk     ;
reg             sys_rst_n   ;
reg             clk_test    ;

initial
    begin
        sys_clk     =   1'b1;
        sys_rst_n   <=  1'b0;
        #200
        sys_rst_n  <=  1'b1;
        #500
        clk_test      =   1'b1;
    end

always  #10     sys_clk =   ~sys_clk    ;   //50MHz系统时钟
always  #100    clk_test=   ~clk_test    ;   //5MHz待检测时钟

//重定义软件闸门计数时间,缩短仿真时间
defparam freq_meter_inst.freq_meter_calc_inst.CNT_GATE_S_MAX    = 240   ;    //计时最长时间  240X20ns
defparam freq_meter_inst.freq_meter_calc_inst.CNT_RISE_MAX      = 40    ;    

freq_meter  freq_meter_inst
(
    .sys_clk     (sys_clk   ),   
    .sys_rst_n   (sys_rst_n ),   
    .clk_test    (clk_test  ),   

    .clk_out     (clk_out   ),   
    .sel         (sel       ),   
    .seg         (seg       )
);

endmodule

仿真结果:

3.5 上板验证

硬件要求:使用短路帽或导线连接 I/O 口 H18 和 H17

模拟输出的待测时钟信号的时钟频率为 250.555MHz。

   存在一点误差是正常的!

4. 总结

      * 使用PLL IP核时因注意将BUFG关闭,否者会出现的报错。Port <sys_clk> has illegal connections. This port is connected to an input buffer and other compo

        原因是“clk”信号作为其他component的时钟,同时作为IPCore<CLOCK>的输入,故应当去掉一个。

 * 注意模块间的连接

说明:

本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,以下内容如有疑惑或错误欢迎评论区指出。

开发软件:ise14.7     仿真:modelsim 10.5 

如需上述资料私信或留下邮箱!

猜你喜欢

转载自blog.csdn.net/m0_72885897/article/details/128989741