FPGA學習.6——通用頻率計
頻率測量在諸多領域都有廣泛的應用,常用的頻率測量方法有兩種,分別是頻率測量法和週期測量法。
頻率測量法:在時間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:軟體閘門計數器
[email protected](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:軟體閘門
[email protected](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:實際閘門
[email protected](posedge clk_test or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
gate_a <= 1'b0;
else
gate_a <= gate_s;
[email protected](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:標準時鍾週期計數器,計數實際閘門下標準時鍾週期數
[email protected](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:待檢測時鐘週期計數器,計數實際閘門下待檢測時鐘週期數
[email protected](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:實際閘門打一拍(標準時鐘下)
[email protected](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;
[email protected](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:實際閘門下標誌時鐘週期數
[email protected](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:實際閘門打一拍(待檢測時鐘下)
[email protected](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:實際閘門下待檢測時鐘週期數
[email protected](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:待檢測時鐘時鐘頻率計算標誌訊號
[email protected](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:待檢測時鐘訊號時鐘頻率
[email protected](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