文章目錄
- 1 從計數器到分頻器
- 2 偶數分頻器
- 3 奇數分頻器
對讀者的假設
已經掌握:
- 可程式化邏輯基礎
- Verilog HDL基礎
- 使用Verilog設計的Quartus II入門指南
- 使用Verilog設計的ModelSIm入門指南
內容1 從計數器到分頻器
此處所說的分頻器,即把輸入時鐘的頻率降低後再輸出時鐘的模組。今天我們只討論等占空比的偶數分頻和奇數分頻,關於小數分頻以及倍頻將不做介紹,有興趣的朋友可以自行研究。在之之前我們先看下前面講的模-m計數器。
代碼1.1 模-m計數器(預設為模-10計數器)
module mod_m_bin_counter#(parameter M=10) // mod-M( // global clock and asyn reset input clk, input rst_n, // counter interface output max_tick, output min_tick, output [N-1:0] q);// signal declarationlocalparam N = log2(M); // number of bits in counterreg [N-1:0] r_reg;wire [N-1:0] r_next;// body// registeralways@(posedge clk, negedge rst_n) if(!rst_n) r_reg <= 0; else r_reg <= r_next; // next-state logicassign r_next = (r_reg == (M-1)) ? 0 : r_reg + 1'b1;//output logicassign q = r_reg;assign max_tick = (r_reg == (M-1)) ? 1'b1 : 1'b0;// log2 constant functionfunction integer log2(input integer n); integer i;begin log2 = 1; for(i=0; 2**i<n; i = i + 1) log2 = i + 1;endendfunction endmodule
根據這個模-m計數器,我們再寫一個testbench。
代碼1.2 模m-計數器的testbench(重新設定為模-10計數器)
`timescale 1ns/1nsmodule f_div_tb;localparam T=20; // clock periodlocalparam M = 10;localparam N = log2(M);// global clock and asyn resetreg clk, rst_n;wire o_clk;// counter interfacewire max_tick, min_tick;wire [N-1:0] q;// log2 constant functionfunction integer log2(input integer n); integer i;begin log2 = 1; for(i=0; 2**i<n; i = i + 1) log2 = i + 1;endendfunction// clcokalwaysbegin clk = 1'b0; #(T/2); clk = 1'b1; #(T/2);end// resetinitialbegin rst_n = 1'b0; #(T/2) rst_n = 1'b1;end// instmod_m_bin_counter #(.M(M)) m_inst( .clk(clk), .rst_n(rst_n), .max_tick(max_tick), .min_tick(min_tick), .q(q));// stimulus bodyinitial begin // initial input @(posedge rst_n); // wait to deassert rst_n @(negedge clk); // wait for a clock // run 1024 cock cycle repeat(1024) @(negedge clk); // last 1024 clock cycle $stop;endendmodule
但是我在使用Quartus II + Modelsim_Altera模擬的時候出現了下面的錯誤提示。
代碼2的第10行,變數N未定義,然後第11行用N來索引的q數組也沒有定義。奇怪了,我在Quartus II都能綜合通過,而且RTL視圖也是正確的,為什麼Modelsim_Altera就不可以這樣定義呢?咳,把常量(localparam)換成參數(parameter)即可。
代碼1.3 改進後的模-m計數器(重新設定為模-10計數器)
module mod_m_bin_counter#(parameter M=10, N = log2(M)) // mod-M( // global clock and asyn reset input clk, input rst_n, // counter interface output max_tick, output min_tick, output [N-1:0] q);// signal declaration//localparam N = log2(M); // number of bits in counterreg [N-1:0] r_reg;wire [N-1:0] r_next;// body// registeralways@(posedge clk, negedge rst_n) if(!rst_n) r_reg <= 0; else r_reg <= r_next; // next-state logicassign r_next = (r_reg == (M-1)) ? 0 : r_reg + 1'b1;//output logicassign q = r_reg;assign min_tick = (r_reg == 0) ? 1'b1 : 1'b0;assign max_tick = (r_reg == (M-1)) ? 1'b1 : 1'b0;// log2 constant functionfunction integer log2(input integer n); integer i;begin log2 = 1; for(i=0; 2**i<n; i = i + 1) log2 = i + 1;endendfunction endmodule
修改完,在Quartus II裡再綜合一下。接下來就是按照[文檔].艾米電子 - 使用Verilog設計的Quartus II入門指南再做一次模擬。告訴大家一個技巧,如果執行完一次RTL級或門級的模擬,那麼Quartus II會為我們的主模組及其testbench產生do檔案的。這樣假如有錯誤,就無需重新啟動Modelsim_Altera,只需重新load一下do檔案即可。
(路徑格式:Quarter II工程檔案夾\simulation\modelsim\mod_m_bin_counter_run_msim_rtl_verilog.do)
下面看下RTL級模擬波形。
下面討論一下如何測量波形的長度,我們注意兩個工具。在Modelsim>Windows處,開啟 ,對應的工具為 。下面在波形上加( )幾個Cursor,使用滑鼠調整其位置。
注意紅色地區及標識,我們可以清楚地看到兩個Cursor之間的距離為20, 000ps,亦即20ns,也就是模-10計數器的周期。那麼怎麼讓它顯示20ns呢?在座標軸地區( ),右鍵選擇 ,設定所需時間單元。
修改為ns,順便測一下max_tick和min_tick脈衝的期間,顯示如下:
測量完,可通過 ,把不需要的Cursor去掉。
換一種視角看波形。
假設我們以max_tick作為使能訊號,來翻轉某個寄存器,那麼這個變數就會輸出方波,且其周期為模-m周期的2倍。
這就是偶數分頻器的原理。
假如我們分別使用全域時鐘的上升沿和下降沿觸發計數器,然後根據兩個計數器的值來產生時鐘,再拿這兩個時鐘運算,得到所需分頻的時鐘。下面以模-3計數器為例來說明如何3分頻。
方法1
黃色地區分別為分別使用全域時鐘的上升沿和下降沿觸發計數器;綠色地區為根據兩個計數器的輸出q產生的兩個脈衝訊號;青色地區為所需分頻時鐘。
綠色地區是0 ~ 3>>1輸出低電平,3>> ~ 3-1輸出高電平;對應的綠色地區做按位或運算。
方法2
綠色地區是0 ~ 3>>1輸出高電平,3>> ~ 3-1輸出低電平;對應的綠色地區做按位與運算。
以上就是技術分頻的原理。
2 偶數分頻器
根據第1節的解析,下面我們寫一個偶數分頻的案例。
代碼 2.1 偶數分頻(預設分頻比為50, 000, 000)
module f_div#(parameter RATIO=50_000_000)( // global clock and asyn reset input clk, input rst_n, //output clock output reg o_clk);wire max_tick;mod_m_bin_counter #(.M(RATIO/2)) mod_m_inst( .clk(clk), .rst_n(rst_n), .max_tick(max_tick), .min_tick(), .q());always@(posedge clk, negedge rst_n) if(!rst_n) o_clk <= 1'b0; else if(max_tick) o_clk <= ~o_clk;endmodule
第22~26行,我們使用模-m計數器的max_tick來作為時鐘使能,驅動o_clk翻轉。注意第13行的模-m例化參數中,M的值為分頻比RATIO的一半。
下面給出testbench。
代碼2.2 偶數分頻的testbench(重新設定為6分頻)
`timescale 1ns/1nsmodule f_div_tb;localparam RATIO = 6;localparam T=20; // clock period// global clock and asyn resetreg clk, rst_n;wire o_clk;// clcokalwaysbegin clk = 1'b0; #(T/2); clk = 1'b1; #(T/2);end// resetinitialbegin rst_n = 1'b0; #(T/2) rst_n = 1'b1;end// instf_div #(.RATIO(RATIO)) f_div_inst( .clk(clk), .rst_n(rst_n), .o_clk(o_clk));// stimulus bodyinitial begin // initial input @(posedge rst_n); // wait to deassert rst_n @(negedge clk); // wait for a clock // run 1024 cock cycle repeat(1024) @(negedge clk); // last 1024 clock cycle $stop;endendmodule
RTL級模擬波形如下所示。觀察3個Cursor的間距,分別為120ns、20ns,即實現了6分頻動作。
3 奇數分頻器
講完了偶數分頻,下面再寫個奇數分頻。
代碼3.1 奇數分頻(預設為25分頻)
module f_div#(parameter RATIO=25)( // global clock and asyn reset input clk, input rst_n, //output clock output o_clk_p, o_clk_n, o_clk);// log2 constant functionfunction integer log2(input integer n); integer i;begin log2 = 1; for(i=0; 2**i<n; i = i + 1) log2 = i + 1;endendfunctionlocalparam N = log2(RATIO);wire [N-1:0] q_pos, q_neg;mod_m_bin_counter #(.M(RATIO)) mod_m_inst1( .clk(clk), .rst_n(rst_n), .max_tick(), .min_tick(), .q(q_pos));mod_m_bin_counter #(.M(RATIO)) mod_m_inst2( .clk(~clk), .rst_n(rst_n), .max_tick(), .min_tick(), .q(q_neg)); assign o_clk_p = (q_pos <= RATIO>>1) ? 0 : 1; assign o_clk_n = (q_neg <= RATIO>>1) ? 0 : 1;assign o_clk = o_clk_n | o_clk_p; endmodule
第24~31行和第33~40行,我們分別例化了兩個相同參數的模-m計數器,不同點在於第26行和第35行的區別,即上升沿觸發和下降沿觸發兩種。
下面給出testbench。
代碼3.2 奇數分頻的testbench(重新設定為5分頻)
`timescale 1ns/1nsmodule f_div_tb;localparam RATIO = 5;localparam T=20; // clock period// global clock and asyn resetreg clk, rst_n;wire o_clk_p, o_clk_n, o_clk;// clcokalwaysbegin clk = 1'b0; #(T/2); clk = 1'b1; #(T/2);end// resetinitialbegin rst_n = 1'b0; #(T/2) rst_n = 1'b1;end// instf_div #(.RATIO(RATIO)) f_div_inst( .clk(clk), .rst_n(rst_n), .o_clk_p(o_clk_p), .o_clk_n(o_clk_n), .o_clk(o_clk));// stimulus bodyinitial begin // initial input @(posedge rst_n); // wait to deassert rst_n @(negedge clk); // wait for a clock // run 1024 cock cycle repeat(1024) @(negedge clk); // last 1024 clock cycle $stop;endendmodule
下面給出RTL層級模擬波形。紅圈內的Cursor測距顯示,5分頻OK。
好了,今天就囉嗦到這裡,大家有什麼疑問可以留言討論。
參考
[筆記].等占空比分頻器的幾種寫法.[Verilog]
另見
[與艾米一起學FPGA/SOPC].[邏輯實驗文檔連載計劃]