例1.四位計數器(同步使能、異步復位)
// Module Name: counter_4bit // Description: 4bit異步復位同步使能二進制計數器 module counter_4bit( input clk, //系統時鍾信號 input rst, //系統復位按鍵 input en, //計數器使能端 output reg [3:0]q //計數器計數值輸出 ); //同步使能,異步復位 always@(posedge clk,posedge rst) if(rst) q <= 0; else if(en) q <= q + 1'b1; //計數器加一 endmodule
testbench測試代碼如下:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: counter_4bit_tb // Description: 4bit計數器模塊測試文件 ////////////////////////////////////////////////////////////////////////////////// `define clk_period 20 //宏定義時鍾周期 module counter_4bit_tb(); reg clk; //用於產生時鍾信號 reg rst; //用於產生復位信號 reg en; //用於產生使能信號 wire [3:0]q; //計數器計數值輸出 //例化測試模塊 counter_4bit counter_4bit_test( .clk(clk), //系統時鍾信號 .rst(rst), //系統復位按鍵 .en(en), //計數器使能端 .q(q) //計數器計數值輸出 ); //開始測試 //生成時鍾信號 initial clk = 1; always #(`clk_period/2) clk = ~clk; //clk每5ns翻轉一次,產生100M時鍾信號 initial begin rst = 1; en = 0; #(`clk_period * 5 + 1 ); rst = 0; #(`clk_period * 5); en = 1; #(`clk_period *20); //因為4bit計數16個clk就清零,所以延時20個時鍾周期 $stop; end endmodule
測試結果如下:

綜合的電路圖如下:

計數器是我們設計的第一個時序邏輯電路,也是最基本、最重要的時序邏輯電路,由圖中可以看到一個計數器由加法器和D觸發器組成;
特別要注意的一點,在用verilog描述計數寄存器加一的時候,我們沒有先寫一個加法器,然后例化調用,而是直接采用 q <= q + 1'b1這樣描述加法器,這點在綜合出的電路圖也可以看出,加法器不再是門電路的組合,而是用一個圓圈替代,這時,數字邏輯設計的思想就又抽象了一層,不再是門電路的組合,而是這些具有邏輯功能的小方塊的組合,所以,抽象的思想在數字邏輯設計中至關重要;
在編寫testbench仿真測試的時候,我們也不再像測試組合邏輯電路那樣利用窮舉法測試功能,而是利用時鍾測試,比如測試4bit的加法器,一要測試它能不能在每個時鍾沿加一,而要測試它計滿時可不可以自動清零;所以,在測試時序邏輯電路的時候要准確把握clk時鍾信號;
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Create Date: 2018/05/22 20:32:26 // Module Name: flashled // Description: 產生一個時鍾信號,讓LED燈按1hz的頻率閃爍 ////////////////////////////////////////////////////////////////////////////////// //`define SIMULATION /*** 仿真時保留,板級驗證時注釋 ***/ module flashled( input clk, //時鍾信號輸入 input rst, //復位信號輸入 input en, //使能信號輸入 output [15:0]led //led信號輸出 ); `ifdef SIMULATION //仿真情況下 parameter CNT_MAX = 26'd49; `else //板級驗證情況下 parameter CNT_MAX = 26'd49_999_999; `endif reg [25:0]cnt; //最大計數值49_999_999 reg [15:0]led_clk; //輸出驅動led的1hz信號 //計數器功能描述 always@(posedge clk,posedge rst) if(rst) cnt <= 0; else if(en)begin if(cnt == CNT_MAX) cnt <= 0; else cnt <= cnt + 1'b1; end //產生1hz信號 always@(posedge clk,posedge rst) if(rst) led_clk <= 0; else if(cnt == CNT_MAX) led_clk <= ~led_clk; //每計滿50_000_000個時鍾周期(500ms),輸出信號翻轉一下 else led_clk <= led_clk; //經1hz信號輸出到led assign led = led_clk; endmodule
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: flashled_tb // Description: flashled模塊測試文件 ////////////////////////////////////////////////////////////////////////////////// `define clk_period 10 //100M時鍾信號 module flashled_tb(); reg clk; //用於產生時鍾信號 reg rst; //用於產生復位信號 reg en; //用於產生使能信號 wire [15:0]led; //觀察led輸出 //例化測試模塊 flashled flashled_test( .clk(clk), //時鍾信號輸入 .rst(rst), //復位信號輸入 .en(en), //使能信號輸入 .led(led) //led信號輸出 ); //產生時鍾信號 initial clk = 1; always #(`clk_period/2) clk = ~clk; //開始測試 initial begin rst = 1; //復位; en = 0; #(`clk_period * 5); rst = 0; #(`clk_period * 5); en = 1; //使能 #(`clk_period * 50 * 5 ); //仿真情況下計數器每計50次翻轉,所以應該延時50 * 5個時鍾周次觀察翻轉情況 $stop; end endmodule
綜合電路圖如下:

在編寫testbench測試分頻器模塊時,由於計數器計數值為49_999_999 ,所以花費長時間,因為只需要測試功能,看模塊計到設定值后會不會翻轉信號,所以這樣就是在浪費時間;
為了加快仿真速度,可以在仿真時將最大值改為49,這樣就非常快,為了方便,可以在verilog中使用條件編譯:
`ifdef SIMULATION //仿真情況下 parameter CNT_MAX = 26'd49; `else //板級驗證情況下 parameter CNT_MAX = 26'd49_999_999; `endif
//1.取某一位直接操作
wire [2:0]m; assign m = out[5:3]; //2.循環移位(移位寄存器)
reg [7:0] shift_a; always@(posedge clk) shift_a <= {shifta[0],shift[7:1]}; reg [7:0] shift_a; wire data; always@(posedge clk) shift_a <= {data,shift[7:1]}; reg [7:0] shift_a; wire data; always@(posedge clk) shift_a <= {shift[7:1],data}; //3、拼接
wire [3:0]x; wire [3:0]y; wire [7:0]z; wire [31:0]n; assign z = {x,y}; assign n = {x,7{x}};
verilog設計代碼如下(計數器部分和分頻器相同):
//移位寄存器功能描述 always@(posedge clk,posedge rst) if(rst) led_temp <= 26'h0001; else if(cnt == CNT_MAX) led_temp <= {led_temp[14:0],led_temp[15]}; //循環左移 else led_temp <= led_temp; //輸出到led assign led = led_temp;
testbench仿真測試文件和分頻器相同;
測試結果如下:


綜合出的電路圖如下:

例4.BCD計數器
計數器是二進制的,但我們所熟悉的是十進制,所以4bit BCD計數器相當於十進制的計數器:從0計到9,然后進位,清零,設計圖如下:
BCD計數器常常應用在數碼管顯示中,比如要要是1234,在單片機中通常會用C語言中的除法和取余提取每一位送到數碼管顯示,但是在數字邏輯電路中,除法器很耗費資源且效率不高,所以不采用除法,那如何進行顯示呢?這個時候就可以將1234轉換為對應的BCD碼:0001_0010_0011_0100;這樣只需要每次送入一個4bitBCD碼就可以顯示(如果以1ms的頻率刷新,就是數碼管動態掃描顯示,也是下一個例子);
verilog代碼:
//////////////////////////////////////////////////////////////////////////////////// Module Name: BCDcounter // Description: BCD計數器 ////////////////////////////////////////////////////////////////////////////////// module BCDcounter( input clk, //時鍾信號輸入 input rst, //復位信號輸入 input cin, //進位信號輸入 output cout, //進位信號輸出 output [3:0]q //4bitBCD碼輸出 ); reg [3:0]cnt; //計數器最大值為9,所以需要4bit計數器 //BCD計數器計數功能描述 always@(posedge clk,posedge rst) if(rst) cnt <= 0; else if(cin)begin if(cnt == 4'd9) cnt <= 0; else cnt <= cnt + 1'b1; end assign cout = cin && (cnt == 4'd9); /** 此處不能使用時序邏輯,需要在計滿9的時候同步輸出cout信號,然后在下次清零是cout恢復為0 **/ assign q = cnt; //BCD計數值輸出 endmodule
testbench測試文件如下:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: BCDcounter_tb // Description: BCDcounter模塊測試文件 ////////////////////////////////////////////////////////////////////////////////// `define clk_period 10 //100M時鍾信號 module BCDcounter_tb(); reg clk; //時鍾信號輸入 reg rst; //復位信號輸入 reg cin; //進位信號輸入 wire cout; //進位信號輸出 wire [3:0]q; //4bitBCD碼輸出 //例化測試模塊 BCDcounter BCDcounter_test( .clk(clk), //時鍾信號輸入 .rst(rst), //復位信號輸入 .cin(cin), //進位信號輸入 .cout(cout), //進位信號輸出 .q(q) //4bitBCD碼輸出 ); //產生100M時鍾信號 initial clk = 1; always #(`clk_period/2) clk = ~clk; //開始測試 initial begin rst = 1; //復位 cin = 0; //無進位 #(`clk_period * 2); rst = 0; #(`clk_period * 2); cin = 1; //產生進位,計數器開始計數 #(`clk_period * 15); //1個clk計一次數,總共計10次,所以延時15個clk觀察波形 $stop; end endmodule
測試結果如下:

綜合電路圖如下:

至此一個4bitBCDcounter就設計完成了,一般使用時,都是通過級聯實現多位BCD計數器,比如四個BCD計數器級聯起來最大值為9999,可以很方便的應用在數碼管動態顯示上;
verilog代碼如下:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: BCDcounter_top // Description: 4個4bit計數器進行級聯,實現計數最大值為9999 ////////////////////////////////////////////////////////////////////////////////// module BCDcounter_top( input clk, //時鍾信號輸入 input rst, //復位信號輸入 input cin, //進位信號輸入 output cout, //進位信號輸出 output [15:0]q //BCD碼輸出 ); wire cout0,cout1,cout2; //4個BCD計數器級聯之間連接線 wire [3:0]q0,q1,q2,q3; //每個BCD計數器的輸出 assign q = {q3,q2,q1,q0}; //拼接成16位輸出 //例化4個BCD計數器 BCDcounter BCDcounter0( .clk(clk), //時鍾信號輸入 .rst(rst), //復位信號輸入 .cin(cin), //進位信號輸入 .cout(cout0), //進位信號輸出 .q(q0) //4bitBCD碼輸出 ); BCDcounter BCDcounter1( .clk(clk), //時鍾信號輸入 .rst(rst), //復位信號輸入 .cin(cout0), //進位信號輸入 .cout(cout1), //進位信號輸出 .q(q1) //4bitBCD碼輸出 ); BCDcounter BCDcounter2( .clk(clk), //時鍾信號輸入 .rst(rst), //復位信號輸入 .cin(cout1), //進位信號輸入 .cout(cout2), //進位信號輸出 .q(q2) //4bitBCD碼輸出 ); BCDcounter BCDcounter3( .clk(clk), //時鍾信號輸入 .rst(rst), //復位信號輸入 .cin(cout2), //進位信號輸入 .cout(cout), //進位信號輸出 .q(q3) //4bitBCD碼輸出 ); endmodule
testbench測試文件與之前一樣,但是需要增大延時時長,因為要計數到9999;
測試結果如下:

綜合電路圖如下:

例5.數碼管動態掃描顯示

設計圖如上圖,在數碼管靜態顯示的基礎上只需要添加一個分頻器,將系統時鍾分頻為1ms的時鍾信號,然后每1ms同時改變段選和位選,刷新顯示,這就是數碼管動態顯示的原理;位選信號已經由2-4譯碼器控制,只有輸入2位選擇信號,所以還需一個四選一數據選擇器,和2-4譯碼器共享一個選擇信號sel,這樣只需要改變sel的值就可以了;
verilog代碼如下(這是頂層文件,還需要之前):
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: Dynamic_seg_display // Description: 4位數碼管動態掃描顯示 ////////////////////////////////////////////////////////////////////////////////// `define SIMULATION /*** 仿真時保留,板級驗證時注釋 ***/ module Dynamic_seg_display( input clk, //時鍾信號 input rst, //復位信號 input [15:0]data_display, //16位待顯示數據顯示(4個BCD碼) output [6:0]segments, //數碼管段碼 output [3:0]wei_sel //數碼管位碼 ); `ifdef SIMULATION //仿真情況下 parameter CNT_MAX = 26'd49; `else //板級驗證情況下 parameter CNT_MAX = 26'd99_999; `endif reg [22:0]cnt; //最大計數值4_999_999,需要23位計數器 reg [1:0]wei; //選擇哪一位顯示 wire [3:0]data_display_temp; //顯示信號傳輸 //計數器功能描述 always@(posedge clk,posedge rst) if(rst) cnt <= 0; else if(cnt == CNT_MAX) cnt <= 0; else cnt <= cnt + 1'b1; //控制數碼管動態掃描顯示(每隔1ms加一,刷新段選和位選) always@(posedge clk,posedge rst) if(rst) wei <= 2'b00; else if(cnt == CNT_MAX) wei <= wei + 1'b1; //每計滿100_000個時鍾周期(1ms),位選加一,切換下一位 else wei <= wei; //例化四選一多路器,選擇輸入哪一位顯示段碼信號 mux4 mux4_1( .sel(wei), .data_in(data_display), .data_out(data_display_temp) ); //例化數碼管譯碼模塊 seg_display seg_display0( .data_display(data_display_temp), //數碼管待顯示數據 .wei(wei), //選擇哪一位顯示 .segments(segments), //數碼管段碼 .wei_sel(wei_sel) //數碼管位碼 ); endmodule //四選一多路器 module mux4( input [1:0]sel, input [15:0]data_in, output reg [3:0]data_out ); always@(*) case(sel) //對應位 4'h0: data_out = data_in[3:0]; 4'h1: data_out = data_in[7:4]; 4'h2: data_out = data_in[11:8]; 4'h3: data_out = data_in[15:12]; default: data_out = 4'bz; endcase endmodule
testbench測試代碼如下:
`timescale 1ns / 1ps //////////////////////////////////////////////////////////////////////////////////// Module Name: Dynamic_seg_display_tb // Description: 數碼管動態掃描顯示控制模塊測試文件 ////////////////////////////////////////////////////////////////////////////////// `define clk_period 10 //100M時鍾信號 module Dynamic_seg_display_tb(); reg clk; //時鍾信號 reg rst; //復位信號 reg [15:0]data_display; //16位待顯示數據顯示(4個BCD碼) wire [6:0]segments; //數碼管段碼 wire [3:0]wei_sel; //數碼管位碼 //例化測試模塊 Dynamic_seg_display Dynamic_seg_display_test( .clk(clk), //時鍾信號 .rst(rst), //復位信號 .data_display(data_display), //16位待顯示數據顯示(4個BCD碼) .segments(segments), //數碼管段碼 .wei_sel(wei_sel) //數碼管位碼 ); //產生100M時鍾信號 initial clk = 1; always #(`clk_period/2) clk = ~clk; //開始測試 initial begin rst = 1; data_display = 16'h4321; //數碼管上顯示"1234" #(`clk_period * 2); rst = 0; #(`clk_period * 50 * 5); //計50次數刷新一下,觀察5次 $stop; end endmodule
測試結果如下:

綜合后的電路圖如圖:

注:
將上一例中四個級聯BCD計數器的16bit輸出和該例中數碼管動態掃描控制模塊的16bit輸入接起來,控制BCD計數的頻率為1hz,就形成一個簡易計時器,從0000一直計到9999;
verilog代碼如下(這是頂層文件,底層模塊調用之前的數碼管動態掃描模塊和4個級聯BCD計數器模塊):
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: easyclock // Description: 簡易數字鍾:按1hz的頻率從0000-9999顯示 ////////////////////////////////////////////////////////////////////////////////// //`define SIMULATION /*** 仿真時保留,板級驗證時注釋 ***/ module easyclock( input clk, //時鍾信號輸入 input rst, //復位信號輸入 output [6:0]segments, //數碼管段碼 output [3:0]wei_sel //數碼管位碼 ); `ifdef SIMULATION //仿真情況下 parameter CNT_MAX = 26'd49; `else //板級驗證情況下 parameter CNT_MAX = 26'd49_999_999; `endif reg clk_1hz; //1hz時鍾信號 reg [25:0]cnt; //計數器計數寄存起(用於分頻器) wire [15:0]data_display; //待顯示數據 //計數器 always@(posedge clk,posedge rst) if(rst) cnt <= 0; else if(cnt == CNT_MAX) cnt <= 0; else cnt <= cnt + 1'b1; //產生1hz信號 always@(posedge clk,posedge rst) if(rst) clk_1hz <= 0; else if(cnt == CNT_MAX) clk_1hz <= ~clk_1hz; //每計滿50_000_000個時鍾周期(500ms),輸出信號翻轉一下 else clk_1hz <= clk_1hz; //例化4個BCD計數器 BCDcounter_top BCDcounter_top0( .clk(clk_1hz), /** 因為BCD需要按1hz計數,所以需要一個1hz時鍾信號 **/ .rst(rst), //復位信號輸入 .cin(1'b1), //進位信號輸入 .cout(), //進位信號輸出 .q(data_display) //BCD碼輸出 ); //例化數碼管動態掃描顯示控制模塊 Dynamic_seg_display Dynamic_seg_display0( .clk(clk), //時鍾信號 .rst(rst), //復位信號 .data_display(data_display), //16位待顯示數據顯示(4個BCD碼) .segments(segments), //數碼管段碼 .wei_sel(wei_sel) //數碼管位碼 ); endmodule
綜合電路圖如下:

例6.74HC595驅動
在上一例中講述了數碼管動態掃描的設計,但是僅僅是4位數碼管顯示,就占用了8+4個IO,如果是8段8位數碼管,就會占用16個IO,造成IO浪費,所以可以采用74HC595串轉並芯片兩片級聯,只采用3個IO完成8段8位數碼管的顯示;
verilog描述如下:
////////////////////////////////////////////////////////////////////////// //module: 74HC595驅動模塊 //description: 將輸入的16位數據串行輸出到74HC595 ////////////////////////////////////////////////////////////////////////// module HC595drive( input clk, //50M系統時鍾 input rst, //低電平復位 input [7:0]seg, //8bit段選信號 input [7:0]sel, //8bit位選信號 output reg DIO, //74HC595數據引腳 output reg SHCP, //74HC595移位脈沖引腳 output reg STCP //74HC595輸出脈沖引腳 ); reg clk_595; reg [1:0]divid_cnt; reg [4:0]count; //產生74HC595工作時鍾12.5M always@(posedge clk,negedge rst) if(!rst)begin divid_cnt <= 0; clk_595 <= 0; end else if(divid_cnt == 2'd3)begin clk_595 <= 1; divid_cnt <= 0; end else begin divid_cnt <= divid_cnt + 1'b1; clk_595 <= 0; end //對74HC595工作時鍾clk_595計數 always@(posedge clk,negedge rst) if(!rst) count <= 0; else if(clk_595 == 1)begin //一共串行發送16bit數據 if(count == 5'd31) count <= 0; else count <= count + 1'b1; end else count <= count; //count清零 //功能描述 always@(posedge clk,negedge rst) if(!rst)begin DIO <= 0; SHCP <= 0; STCP <= 0; end else case(count) 5'd0: begin DIO <= seg[7]; SHCP <= 0; STCP <= 1; end //HEX_DP,輸出 5'd1: begin SHCP <= 1; STCP <= 0; end //移位 5'd2: begin DIO <= seg[6]; SHCP <= 0; STCP <= 0; end //HEX_G 5'd3: begin SHCP <= 1; end 5'd4: begin DIO <= seg[5]; SHCP <= 0; end //HEX_F 5'd5: begin SHCP <= 1; end 5'd6: begin DIO <= seg[4]; SHCP <= 0; end //HEX_E 5'd7: begin SHCP <= 1; end 5'd8: begin DIO <= seg[3]; SHCP <= 0; end //HEX_D 5'd9: begin SHCP <= 1; end 5'd10: begin DIO <= seg[2]; SHCP <= 0; end //HEX_C 5'd11: begin SHCP <= 1; end 5'd12: begin DIO <= seg[1]; SHCP <= 0; end //HEX_B 5'd13: begin SHCP <= 1; end 5'd14: begin DIO <= seg[0]; SHCP <= 0; end //HEX_A 5'd15: begin SHCP <= 1; end 5'd16: begin DIO <= sel[7]; SHCP <= 0; end //第7位 5'd17: begin SHCP <= 1; end 5'd18: begin DIO <= sel[6]; SHCP <= 0; end //第6位 5'd19: begin SHCP <= 1; end 5'd20: begin DIO <= sel[5]; SHCP <= 0; end //第5位 5'd21: begin SHCP <= 1; end 5'd22: begin DIO <= sel[4]; SHCP <= 0; end //第4位 5'd23: begin SHCP <= 1; end 5'd24: begin DIO <= sel[3]; SHCP <= 0; end //第3位 5'd25: begin SHCP <= 1; end 5'd26: begin DIO <= sel[2]; SHCP <= 0; end //第2位 5'd27: begin SHCP <= 1; end 5'd28: begin DIO <= sel[1]; SHCP <= 0; end //第1位 5'd29: begin SHCP <= 1; end 5'd30: begin DIO <= sel[0]; SHCP <= 0; end //第0位 5'd31: begin SHCP <= 1; end endcase endmodule
testbench如下:
`timescale 1ns/1ps ////////////////////////////////////////////////////////////////////////// //module: 74HC595驅動測試模塊 ////////////////////////////////////////////////////////////////////////// `define clk_period 20 //50M系統時鍾 module HC595drive_tb(); reg clk; //50M系統時鍾 reg rst; //低電平復位 reg [7:0]seg; //8bit段選信號 reg [7:0]sel; //8bit位選信號 wire DIO; //74HC595數據引腳 wire SHCP; //74HC595移位脈沖引腳 wire STCP; //74HC595輸出脈沖引腳 //例化測試模塊 HC595drive HC595drive_test( .clk(clk), .rst(rst), .seg(seg), .sel(sel), .DIO(DIO), .SHCP(SHCP), .STCP(STCP) ); //產生50M時鍾信號 initial clk = 1; always #(`clk_period / 2) clk = ~clk; //開始測試 initial begin rst = 0; //復位 seg = 8'hc0; //顯示數字"1" sel = 8'ha5; //10100101 #(`clk_period * 5); rst = 1; #(`clk_period * 32 * 4); //傳送16位需要32個clk,clk又被進行4分頻 #(`clk_period * 10); $stop; end endmodule
測試結果如下:

例7.定時器
玩過單片機的小伙伴都很熟悉,單片機的基本外設就是定時器,定時器的核心也是計數器,設定工作方式(單次計數和循環計數)和定時值后,就開始計數,計滿之后產生計數慢標志信號,提示設定的定時時間到達;
所以,如果要用計數器來設計一個定時器,需要實現的功能有:
1、定時時間參數通過一個端口輸入,調節該值可以修改定時時間;
2、設置一個計數模式控制信號,當該信號為1時為循環定時模式,當該信號為0時為單次定時模式;
3、設置一個計數啟動信號,在循環定時模式下,該信號為高電平使能計時,為低電平則停止計時;當單次計數模式下,該信號的一個單基准時鍾周期的脈沖使能一次定時;
4、輸出計數器定時計數值,該值用於產生特定占空比的方波;
設計圖如下:

verilog代碼如下:
module timer( input clk, //50M時鍾信號 input rst, //低電平復位 input [31:0]timer_value, //定時值 input mode, //定時器模式選擇 input en, //定時器使能控制端 output [31:0]cnt_value, //定時器計數值實時輸出 output full_flag //定時器計滿輸出標志位 ); reg [31:0]cnt; //32bit計數器 reg oneshot; //在單次定時模式中,使能端一個脈沖就啟動,但是計數器需要en保持為1才會計數,所以需要另外一個單次使能信號,當外部一個脈沖時,它會變為1,保持至定時結束; //功能描述 assign full_flag = (cnt == timer_value)?1'b1:1'b0; //輸出計滿標志位 assign cnt_value = cnt; //實時輸出計數值 always@(posedge clk,negedge rst) if(!rst) cnt <= 0; else if(mode)begin //mode = 1,循環定時模式 if(en && cnt < timer_value) cnt <= cnt +1'b1; //使能狀態下且計數值未滿,加一 else cnt <= 0; //其余情況下cnt清零 end else begin //mode = 0,單次定時模式 if(oneshot) cnt <= cnt + 1'b1; else cnt <= 0; end //利用外部一個脈沖產生oneshot信號供定時器單次定時模式下使用 //功能:當外部en產生一個高脈沖時,它會變為1,保持至定時結束; always@(posedge clk,negedge rst) if(!rst) oneshot <= 0; else if(en) oneshot <= 1'b1; else if(cnt >= timer_value) //計滿清零 oneshot <= 0; endmodule
testbench測試文件如下:
`timescale 1ns/1ps `define clk_period 20 module timer_tb(); reg clk; //50M時鍾信號 reg rst; //低電平復位 reg [31:0]timer_value; //定時值 reg mode; //定時器模式選擇 reg en; //定時器使能控制端 wire [31:0]cnt_value; //定時器計數值實時輸出 wire full_flag; //定時器計滿輸出標志位 //例化測試模塊 timer timer_test( .clk(clk), //50M時鍾信號 .rst(rst), //低電平復位 .timer_value(timer_value), //定時值 .mode(mode), //定時器模式選擇 .en(en), //定時器使能控制端 .cnt_value(cnt_value), //定時器計數值實時輸出 .full_flag(full_flag) //定時器計滿輸出標志位 ); //產生50M時鍾信號 initial clk = 1; always #(`clk_period / 2) clk = ~clk; //開始測試 initial begin rst = 0; //系統復位 mode = 0; //首先測試模式0,單次計數模式 en = 0; //未使能 timer_value = 32'd10; //定時器計數值為10 #(`clk_period * 2); rst = 1; #(`clk_period * 2); en = 1; //產生一個高脈沖,使能定時器 #(`clk_period * 2); en = 0; #(`clk_period * 15); //系統計數10個clk會輸出計滿flag,在延時觀察5個clk觀察定時器是否停止 mode = 1; //開始測試定時器模式1,循環計數模式 #(`clk_period); en = 1; //使能定時器 #(`clk_period * 10 * 5); //系統計數10個clk會輸出計滿flag,一共觀察5次flag en = 0; //停止使能 #(`clk_period * 10); //等待10個clk,觀察定時器是否已停止工作 $stop; //停止測試 end endmodule
測試結果如下:
單次計數模式測試:

循環計數模式測試:

綜合電路圖如下:

定時器至此就設計結束,定時器有很多應用,比如產生PWM驅動無源蜂鳴器,驅動直流電機調速,驅動舵機,驅動步進電機,驅動RGBLED變色,總之利用定時器可以很方便產生可變PWM,利用好PWM也是很深的學問,如有興趣自行深入;
