FPGA之简易频率计的设计
文章目錄
- 前言
- 一、等精度測量法
- 二、頻率計算模塊設計
- 1.波形圖分析
- 2.RTL代碼
- 三、數碼管顯示模塊設計
- 1.數碼管之二進制轉8421BCD碼部分
- 2.數碼管顯示部分
- 四、頂層代碼設計
- 1.RTL代碼
- 2.RTL視圖
- 3.仿真測試模塊
- 五、上板驗證
- 總結
前言
頻率測量在電子設計領域和測量領域經常被使用,本篇文章將使用基于等精度測量的方法設計一個建議頻率計,計算出輸入信號的頻率并顯示在數碼管上。
一、等精度測量法
原理:等精度測量法是在實際門控信號下,同時對標準時鐘和被測時鐘的時鐘周期進行計數,再通過公式計算得到被測信號的時鐘頻率。
誤差分析:結合原理和原理圖得知,被測時鐘信號的時鐘頻率fx的相對誤差與被測時鐘信號無關;增大軟件閘門的有效范圍或者提高標準時鐘信號的頻率fs,可以減小誤差,提高測量精度。
計算方法:分別對實際閘門下被測時鐘信號和標準時鐘信號的周期數進行計數。
設實際閘門下被測時鐘信號周期數為X,設被測信號時鐘周期為Tx,它的時鐘頻率fx=1/Tx,由此:XTx=X/fx=Tx。
設實際閘門下標準時鐘信號周期數為Y,設被測信號時鐘周期為Ts,它的時鐘頻率fs=1/Ts,由此:YTs=X/fs=Ts。
等式變換得到被測時鐘信號頻率計算公式:fx=Xfs/Y。
二、頻率計算模塊設計
1.波形圖分析
注:圖片摘自《FPGA Verilog開發實戰指南》
通過波形圖的設計可以起到事半功倍的效果。
- clk_stand:標準時鐘,通過PLL IP核產生。
- 軟件閘門gate_s:gate_s,設計一個總長度為1.5s,前低電平長度為0.25s,高電平長度為2s,后低電平長度為0.25s的軟件閘門。
- 閘門計數器cnt_gate_s:閘門需要時間控制,所以必須使用到計數器,因為開發板自帶晶振是50MHz的,所以由低電平到達高電平的時間計數值為CNT_RISE_MAX=28’d12_499_999,總長度計數值為CNT_GATE_S_MAX=28’d74_999_999,所以有效時間計數值為CNT_GATE_S_MAX-CNT_RISE_MAX
- gate_a:軟件閘門需要與系統時鐘進行同步處理,用一級寄存器gate_a實現
- gate_a_stand:將軟件閘門與標準時鐘同步,可以產生下降沿信號gate_a_fall_s
- gate_a_fall_s:標準時鐘周期計數停止標志位
- cnt_clk_stand:標準時鐘周期計數器
- cnt_clk_stand_reg:標準時鐘計數值寄存器
- gate_a_test:將軟件閘門與待測時鐘同步,可產生下降沿信號gate_a_fall_t
- gate_a_fall_t:待測時鐘周期計數停止標志位
- cnt_clk_test:待測時鐘周期計數器
- cnt_clk_test_reg:待測時鐘計數值寄存器
- calc_flag:開始計算待測信號頻率標志位,在閘門計數器記滿時產生一個脈沖
- freq_reg:頻率計數值寄存器
- calc_flag_reg:將calc_flag信號延遲一拍,此時頻率計算已經結束,可以產生輸出標志。
- freq:輸出頻率計算值
2.RTL代碼
module freq_meter_calc #(parameter CNT_RISE_MAX=28'd12_499_999,parameter CNT_GATE_S_MAX=28'd74_999_999,parameter CLK_STAND_FREQ=28'd100_000_000 ) (input wire clk,input wire rst_n,input wire clk_test,output reg [33:0]freq );reg [27:0]cnt_gate_s;reg gate_s;reg gate_a;reg gate_a_stand;wire gate_a_fall_s;reg [47:0]cnt_clk_stand;reg [47:0]cnt_clk_stand_reg;reg gate_a_test;wire gate_a_fall_t;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;wire clk_stand;clk_stand_pll clk_stand_pll0(.areset(~rst_n),.inclk0(clk),.c0(clk_stand));//閘門計數器always @(posedge clk or negedge rst_n)if(!rst_n)cnt_gate_s<=1'b0;else if(cnt_gate_s==CNT_GATE_S_MAX)cnt_gate_s<=1'b0;else cnt_gate_s<=cnt_gate_s+1'b1;//閘門控制,always @(posedge clk or negedge rst_n)if(!rst_n)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;//將閘門打一拍,與時鐘同步always @(posedge clk or negedge rst_n)if(!rst_n)gate_a<=1'b0;else gate_a<=gate_s;//將標準時鐘閘門再打一拍,以便生成標志信號gate_a_fall_salways @(posedge clk_stand or negedge rst_n)if(!rst_n)gate_a_stand<=1'b0;else gate_a_stand<=gate_a;//標準頻率計數截止信號assign gate_a_fall_s=(gate_a_stand && !gate_a) ? 1'b1:1'b0;//標準時鐘頻率計數器always @(posedge clk_stand or negedge rst_n)if(!rst_n)cnt_clk_stand<=1'b0;else if(!gate_a)cnt_clk_stand<=1'b0;else if(gate_a)cnt_clk_stand<=cnt_clk_stand+1'b1;//標準時鐘頻率周期個數寄存器always @(posedge clk_stand or negedge rst_n)if(!rst_n)cnt_clk_stand_reg<=1'b0;else if(gate_a_fall_s)cnt_clk_stand_reg<=cnt_clk_stand;//將待測時鐘打一拍,以便生成gate_a_fall_talways @(posedge clk_test or negedge rst_n)if(!rst_n)gate_a_test<=1'b0;else gate_a_test<=gate_a;//待測時鐘計數截止信號assign gate_a_fall_t=(gate_a_test && !gate_a) ? 1'b1:1'b0;//待測時鐘頻率計數器always @(posedge clk_test or negedge rst_n)if(!rst_n)cnt_clk_test<=1'b0;else if(!gate_a)cnt_clk_test<=1'b0;else if(gate_a)cnt_clk_test<=cnt_clk_test+1'b1;//待測時鐘頻率周期個數寄存器always @(posedge clk_test or negedge rst_n)if(!rst_n)cnt_clk_test_reg<=1'b0;else if(gate_a_fall_t)cnt_clk_test_reg<=cnt_clk_test;//頻率計算標志信號always @(posedge clk or negedge rst_n)if(!rst_n)calc_flag<=1'b0;else if(cnt_gate_s==CNT_GATE_S_MAX)calc_flag<=1'b1;elsecalc_flag<=1'b0;//待測時鐘頻率計算always @(posedge clk or negedge rst_n)if(!rst_n)freq_reg<=1'b0;else if(calc_flag)freq_reg<=(CLK_STAND_FREQ*cnt_clk_test_reg/cnt_clk_stand_reg);//將計算標志信號打一拍,不然freq輸出的是計算之前的值always @(posedge clk or negedge rst_n)if(!rst_n)calc_flag_reg<=1'b0;else calc_flag_reg<=calc_flag;//頻率值輸出always @(posedge clk or negedge rst_n)if(!rst_n)freq<=1'b0;else if(calc_flag_reg)freq<=freq_reg[33:0];endmodule三、數碼管顯示模塊設計
因為數碼管部分代碼不是本次的重點,所以只給出代碼,不給出設計過程
1.數碼管之二進制轉8421BCD碼部分
module bcd_8421(input wire clk,input wire rst_n,input wire [19:0]data,output reg [3:0]unit,output reg [3:0]ten,output reg [3:0]hun,output reg [3:0]tho,output reg [3:0]t_tho,output reg [3:0]h_hun);reg [4:0]cnt_shift;reg [43:0]data_shift;reg shift_flag;always @(posedge clk or negedge rst_n)if(!rst_n)cnt_shift<=1'b0;else if(cnt_shift==5'd21 && shift_flag)cnt_shift<=1'b0;else if(shift_flag)cnt_shift<=cnt_shift+1'b1;elsecnt_shift<=cnt_shift;always @(posedge clk or negedge rst_n)if(!rst_n)data_shift<=1'b0;else if(!cnt_shift)data_shift<={24'd0,data};else if(cnt_shift<=20 && (!shift_flag))begindata_shift[23:20]<=(data_shift[23:20]>4)? (data_shift[23:20]+2'd3):(data_shift[23:20]);data_shift[27:24]<=(data_shift[27:24]>4)? (data_shift[27:24]+2'd3):(data_shift[27:24]);data_shift[31:28]<=(data_shift[31:28]>4)? (data_shift[31:28]+2'd3):(data_shift[31:28]);data_shift[35:32]<=(data_shift[35:32]>4)? (data_shift[35:32]+2'd3):(data_shift[35:32]);data_shift[39:36]<=(data_shift[39:36]>4)? (data_shift[39:36]+2'd3):(data_shift[39:36]);data_shift[43:40]<=(data_shift[43:40]>4)? (data_shift[43:40]+2'd3):(data_shift[43:40]);endelse if(cnt_shift<=5'd20 && shift_flag)data_shift<=data_shift<<1;elsedata_shift<=data_shift;always @(posedge clk or negedge rst_n)if(!rst_n)shift_flag<=1'b0;elseshift_flag<=~shift_flag;always @(posedge clk or negedge rst_n)if(!rst_n)beginunit<=1'b0;ten<=1'b0;hun<=1'b0;tho<=1'b0;t_tho<=1'b0;h_hun<=1'b0;endelse if(cnt_shift==5'd21)beginunit<=data_shift[23:20];ten<=data_shift[27:24];hun<=data_shift[31:28];tho<=data_shift[35:32];t_tho<=data_shift[39:36];h_hun<=data_shift[43:40];endendmodule2.數碼管顯示部分
module seg #(parameter CNT_MAX=16'd49999 //49999 ) (input wire clk,input wire rst_n,input wire [5:0]point,input wire [19:0]data,input wire seg_en,input wire sign,output reg [5:0]sel,output reg [7:0]seg);wire [3:0]unit;wire [3:0]ten;wire [3:0]hun;wire [3:0]tho;wire [3:0]t_tho;wire [3:0]h_hun;bcd_8421 bcd_8421(.clk(clk),.rst_n(rst_n),.data(data),.unit(unit),.ten(ten),.hun(hun),.tho(tho),.t_tho(t_tho),.h_hun(h_hun));reg [23:0]data_reg;reg [15:0]cnt_1ms;reg flag_1ms;reg [2:0]cnt_sel;reg [5:0]sel_reg;reg [3:0]data_disp;reg dot_disp;//控制數碼管顯示always @(posedge clk or negedge rst_n)if(!rst_n)data_reg<=1'b0;//若顯示的十萬位為非零數據或需要顯示小數點,六個數碼管全顯示else if(h_hun || point[5])data_reg<={h_hun,t_tho,tho,hun,ten,unit};//若顯示的萬位數為非零數據或需要顯示小數點,數值顯示在5個數碼管上else if((t_tho || point[4]) && sign)//顯示負號data_reg<={4'd10,t_tho,tho,hun,ten,unit};//定義4'd10為顯示負號else if((t_tho || point[4]) && !sign)data_reg<={4'd11,t_tho,tho,hun,ten,unit};//定義4‘d11為不顯示//若顯示的千位數為非零數據或需要顯示小數點,數值顯示在4個數碼管上else if((tho || point[3]) && sign)data_reg<={4'd11,4'd10,tho,hun,ten,unit};else if((tho || point[3]) && !sign)data_reg<={4'd11,4'd11,tho,hun,ten,unit};//若顯示的百位數為非零數據或需要顯示小數點,數值顯示在3個數碼管上else if((hun || point[2]) && sign)data_reg<={4'd11,4'd11,4'd10,hun,ten,unit};else if((hun || point[2]) && !sign)data_reg<={4'd11,4'd11,4'd11,hun,ten,unit};//若顯示的十位數為非零數據或需要顯示小數點,數值顯示在2個數碼管上else if((ten || point[2]) && sign)data_reg<={4'd11,4'd11,4'd11,4'd10,ten,unit};else if((ten || point[2]) && !sign)data_reg<={4'd11,4'd11,4'd11,4'd11,ten,unit};//若顯示的個位數為非零數據或需要顯示小數點,數值顯示在1個數碼管上else if((unit || point[1]) && sign)data_reg<={4'd11,4'd11,4'd11,4'd11,4'd10,unit};else data_reg<={4'd11,4'd11,4'd11,4'd11,4'd11,unit};//計數器計數1msalways @(posedge clk or negedge rst_n)if(!rst_n)cnt_1ms<=1'b0;else if(cnt_1ms==CNT_MAX)cnt_1ms<=1'b0;else cnt_1ms<=cnt_1ms+1'b1;//計數標志位always @(posedge clk or negedge rst_n)if(!rst_n)flag_1ms<=1'b0;else if(cnt_1ms==CNT_MAX-1'b1)flag_1ms<=1'b1;elseflag_1ms<=1'b0;//cnt_sel:從0到5的循環,用于選擇當前顯示的數碼管always @(posedge clk or negedge rst_n)if(!rst_n)cnt_sel<=1'b0;else if(cnt_sel==3'b101 && flag_1ms)cnt_sel<=1'b0;else if(flag_1ms)cnt_sel<=cnt_sel+1'b1;elsecnt_sel<=cnt_sel;//數碼管位選信號寄存器always @(posedge clk or negedge rst_n)if(!rst_n)sel_reg<=6'b000_000;else if(!cnt_sel && flag_1ms)sel_reg<=6'b000_001;else if(flag_1ms)sel_reg<=sel_reg<<1;elsesel_reg<=sel_reg;//控制數碼管的位選信號,使六個數碼管輪流顯示always @(posedge clk or negedge rst_n)if(!rst_n)data_disp<=1'b0;else if(seg_en && flag_1ms)case(cnt_sel)3'd0:data_disp<=data_reg[3:0];3'd1:data_disp<=data_reg[7:4];3'd2:data_disp<=data_reg[11:8];3'd3:data_disp<=data_reg[15:12];3'd4:data_disp<=data_reg[19:16];3'd5:data_disp<=data_reg[23:20];default:data_disp<=1'b0;endcaseelsedata_disp<=data_disp;//dot_disp:小數點低電平點亮,需對小數點有效信號取反always @(posedge clk or negedge rst_n)if(!rst_n)dot_disp<=1'b1;else if(flag_1ms)dot_disp<=~point[cnt_sel];elsedot_disp<=dot_disp;//控制數碼管段選信號,顯示數字always @(posedge clk or negedge rst_n)if(!rst_n)seg<=8'b1111_1111;elsecase(data_disp)4'd0:seg<={dot_disp,7'b100_0000};4'd1:seg<={dot_disp,7'b111_1001};4'd2:seg<={dot_disp,7'b010_0100};4'd3:seg<={dot_disp,7'b011_0000};4'd4:seg<={dot_disp,7'b001_1001};4'd5:seg<={dot_disp,7'b001_0010};4'd6:seg<={dot_disp,7'b000_0010};4'd7:seg<={dot_disp,7'b111_1000};4'd8:seg<={dot_disp,7'b000_0000};4'd9:seg<={dot_disp,7'b001_0000};4'd10:seg<=8'b1011_1111;4'd11:seg<=8'b1111_1111;default:seg<=8'b1100_0000;endcase//sel:數碼管位選信號賦值always @(posedge clk or negedge rst_n)if(!rst_n)sel<=6'b000_000;elsesel<=~sel_reg; endmodule四、頂層代碼設計
頂層代碼中包含三個部分:待測時鐘輸出模塊,待測時鐘頻率計算模塊,數碼管顯示模塊
其中待測時鐘輸出模塊我們直接用PLL產生的100MHz,也可以在PLL中再添加新的時鐘信號來輸出。
1.RTL代碼
module freq_meter_top(input wire clk,input wire rst_n,input wire clk_test,output wire clk_out,output wire [5:0]sel,output wire [7:0]seg );wire [33:0]freq;clk_stand_pll clk_stand_pll0(.areset(~rst_n),.inclk0(clk),.c0(clk_out));seg seg0(.clk(clk),.rst_n(rst_n),.point(6'b001000),.data(freq/1000),.seg_en(1'b1),.sign(1'b0),.sel(sel),.seg(seg));freq_meter_calc freq_meter_calc0(.clk(clk),.rst_n(rst_n),.clk_test(clk_test),.freq(freq));endmodule2.RTL視圖
3.仿真測試模塊
`timescale 1ns/1ns `define clk_period 20module freq_meter_top_tb;reg clk;reg rst_n;reg clk_test;wire clk_out;wire [5:0]sel;wire [7:0]seg;freq_meter_top freq_meter_top0(.clk(clk),.rst_n(rst_n),.clk_test(clk_test),.clk_out(clk_out),.sel(sel),.seg(seg));initial clk=1'b1;always #(`clk_period/2) clk=~clk;initial clk_test=1'b1;always #100 clk_test=~clk_test;initial beginrst_n=1'b0;#(`clk_period*20+1);rst_n=1'b1;enddefparam freq_meter_top0.freq_meter_calc0.CNT_GATE_S_MAX=240;defparam freq_meter_top0.freq_meter_calc0.CNT_RISE_MAX=40;endmodule仿真波形圖:
仿真激勵文件中我們設計了一個5MHz的待測信號輸入,可以看到計數值freq輸出為4968944hz,與5MHz有些許誤差,不過這足以證明我們的實驗已經成功了,下面就可以開始分配引腳、布局布線、燒錄開發板了
五、上板驗證
我們設數碼管的單位是MHz,可以看到,我們輸入100MHz時,輸出就是100MHz,驗證成功。
總結
頻率計的設計相對于前面的設計是復雜了很多的,但是也可以很大程度的鍛煉能力,同時設計中的一些細節問題,比如對應不同時鐘上升沿的計數值需要注意。每個開發板上的數碼管配置都不一定相同,所以在調用數碼管的時候,如果發現無法正常顯示,可以 查看一下自己數碼管的配置,再對代碼進行修改。
總結
以上是生活随笔為你收集整理的FPGA之简易频率计的设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于SpringBoot架构的心理健康测
- 下一篇: 【STM32】R05D电控红外协议的美的