[FPGA]Verilog 60s秒表計時器
1.引述
這次的實驗來自於本人本科課程數電結課時的自選題目。由於這次上傳是后知后覺,學校已將小腳丫板子回收,所以在這篇文章中沒法貼出代碼結果的效果圖了,但最終效果已經過測試,可放心食用。那么下面就貼上代碼並略加講解供大家參考。
2.分頻模塊
我們要實現一個秒表,自然要將實驗板中的時鍾脈沖clk分頻為一個周期為1s的脈沖,已知小腳丫板子的晶振為12MHz。下面貼上分頻模塊的代碼。
module divide # ( //parameter是verilog里參數定義 parameter WIDTH = 24, //計數器的位數,計數的最大值為 2**(WIDTH-1) parameter N = 12_000_000 //分頻系數,請確保 N<2**(WIDTH-1),否則計數會溢出 ) ( input clk, //clk頻率為12MHz input rst_n, //復位信號,低有效, output clkout //輸出信號,可以連接到LED觀察分頻的時鍾 ); reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p為上升沿觸發時的計數器,cnt_n為下降沿觸發時的計數器 reg clk_p,clk_n; //clk_p為上升沿觸發時分頻時鍾,clk_n為下降沿觸發時分頻時鍾 //上升沿觸發時計數器的控制 always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_p <= 1'b0; else if(cnt_p == (N-1)) cnt_p <= 1'b0; else cnt_p <= cnt_p + 1'b1; //計數器一直計數,當計數到N-1的時候清零,這是一個模N的計數器 end //上升沿觸發的分頻時鍾輸出,如果N為奇數得到的時鍾占空比不是50%;如果N為偶數得到的時鍾占空比為50% always @(posedge clk or negedge rst_n) begin if(!rst_n) clk_p <= 1'b0; else if(cnt_p < (N>>1)) //N>>1表示右移一位,相當於除以2取商 clk_p <= 1'b0; else clk_p <= 1'b1; //得到的分頻時鍾正周期比負周期多一個clk時鍾 end //下降沿觸發時計數器的控制 always @(negedge clk or negedge rst_n) begin if(!rst_n) cnt_n <= 1'b0; else if(cnt_n == (N-1)) cnt_n <= 1'b0; else cnt_n <= cnt_n + 1'b1; end //下降沿觸發的分頻時鍾輸出,和clk_p相差半個clk時鍾 always @(negedge clk or negedge rst_n) begin if(!rst_n) clk_n <= 1'b0; else if(cnt_n < (N>>1)) clk_n <= 1'b0; else clk_n <= 1'b1; //得到的分頻時鍾正周期比負周期多一個clk時鍾 end wire clk1 = clk; //當N=1時,直接輸出clk wire clk2 = clk_p; //當N為偶數也就是N的最低位為0,N[0]=0,輸出clk_p wire clk3 = clk_p & clk_n; //當N為奇數也就是N最低位為1,N[0]=1,輸出clk_p&clk_n。正周期多所以是相與 assign clkout = (N==1)? clk1:(N[0]? clk3:clk2); //條件判斷表達式 endmodule
3.八位數碼管顯示模塊
小腳丫板子上有兩個八位數碼管顯示,本實驗中用來顯示從00s到59s的顯示。下面貼上數碼管顯示模塊的代碼。
module segment ( input wire [3:0] seg_data_1, //四位輸入數據信號 input wire [3:0] seg_data_2, //四位輸入數據信號 output wire [8:0] segment_led_1, //數碼管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A output wire [8:0] segment_led_2 //數碼管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); reg[8:0] seg [15:0]; //存儲7段數碼管譯碼數據 initial begin seg[0] = 9'h3f; // 0 seg[1] = 9'h06; // 1 seg[2] = 9'h5b; // 2 seg[3] = 9'h4f; // 3 seg[4] = 9'h66; // 4 seg[5] = 9'h6d; // 5 seg[6] = 9'h7d; // 6 seg[7] = 9'h07; // 7 seg[8] = 9'h7f; // 8 seg[9] = 9'h6f; // 9 seg[10]= 9'h77; // A seg[11]= 9'h7C; // b seg[12]= 9'h39; // C seg[13]= 9'h5e; // d seg[14]= 9'h79; // E seg[15]= 9'h71; // F end assign segment_led_1 = seg[seg_data_1]; assign segment_led_2 = seg[seg_data_2]; endmodule
4.功能講解
在主模塊中除了要例化上述的兩個模塊之外,還需給這個秒表添磚加瓦一下!標題中提到這是一個60s秒表,而我們數碼管顯示只從00到59,但最大計時量程卻達到了9min,這是怎么辦到的呢?這里我們就用到了小腳丫上的一排八位LED燈,每當計到59s時,下一秒數碼管顯示回到00,點亮八位LED燈中的一個,達到表示已計過了1min的作用。一共有八位LED燈,當八個燈都被點亮后,數碼管還有一次從00到59的顯示機會,這樣我們就的得到了一個最大計時量程為9min的秒表啦!下面貼上八位LED燈顯示部分的代碼。
always@(posedge clk) if(cnt1==4'b0) LED[7:0]<=8'b11111111; else if(cnt1==4'b0001) LED[7:0]<=8'b11111110; else if(cnt1==4'b0010) LED[7:0]<=8'b11111100; else if(cnt1==4'b0011) LED[7:0]<=8'b11111000; else if(cnt1==4'b0100) LED[7:0]<=8'b11110000; else if(cnt1==4'b0101) LED[7:0]<=8'b11100000; else if(cnt1==4'b0110) LED[7:0]<=8'b11000000; else if(cnt1<=4'b0111) LED[7:0]<=8'b10000000; else if(cnt1<=4'b1000) LED[7:0]<=8'b00000000;
此外作為一個秒表自然就要有暫停和開始計時的功能(當然清零功能也是有噠!主模塊中就用rst復位鍵來實現,這里不多贅述。)暫停和開始計時這里我就用同一個按鍵實現。小腳丫板子上還有兩個RGB三色燈,既然有這么好的資源存在,我們就要物盡其用!按下開始計時鍵時,秒表開始計時,數碼管顯示開始變化,此處我們讓RGB三色燈中的一個等亮綠燈,表示處於正常計時狀態中;當再次按鍵開啟鍵時,秒表暫停,我們讓另一個RGB三色燈亮紅色,表示處於暫停狀態。下面貼上包含三色燈點亮的部分代碼。
always @(posedge clk1h or negedge rst) //產生60進制計數器 begin //數碼管顯示要按照十進制的方式顯示 if(!rst) begin cnt <= 8'h00; //復位初值顯示00 cnt1<=4'b0; end else if(flag) begin G_LED2<=1'b0; R_LED1<=1'b1; if(cnt[3:0] == 4'd9) //個位滿九? begin cnt[3:0] <= 4'd0; //個位清零 if(cnt[7:4] == 4'd5 ) //十位滿五? begin cnt[7:4] <= 4'd0; //十位清零 cnt1<=cnt1+1; end else begin cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一 cnt1<=cnt1; end end else cnt[3:0] <= cnt[3:0] + 1'b1; //個位加一 end else begin cnt <= cnt; G_LED2<=1'b1; R_LED1<=1'b0; end end
5.主模塊
最后貼上主模塊的代碼,完成整個秒表的實現。
module counter60 ( input clk,rst, //時鍾和復位輸入 input key, //啟動暫停按鍵 output wire [8:0] segment_led_1,segment_led_2, //數碼管輸出 output reg [7:0] LED, //八位LED燈 output reg R_LED1,G_LED2 //RGB三色燈,此處用紅色表示處於暫停狀態中,綠色表示處於正常計時中 ); wire clk1h; //1秒時鍾 reg [7:0] cnt; //計時計數器 reg [3:0] cnt1; //分鍾計數器 reg flag; //啟動暫停標志 divide # //例化分頻器產生1秒時鍾信號 ( .WIDTH(24), .N(12_000_000) ) u1 ( .clk(clk), .rst_n(rst), .clkout(clk1h) ); segment u2 //例化數碼管顯示模塊 ( .seg_data_1 (cnt[7:4]), //seg_data input .seg_data_2 (cnt[3:0]), //seg_data input .segment_led_1 (segment_led_1), //MSB~LSB = SEG,DP,G,F,E,D,C,B,A .segment_led_2 (segment_led_2) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); always @(posedge clk or negedge rst) //產生標志信號 begin if(!rst) flag = 1'b0; else if(!key) begin flag = ~flag; end else begin flag = flag; end end always @(posedge clk1h or negedge rst) //產生60進制計數器 begin //數碼管顯示要按照十進制的方式顯示 if(!rst) begin cnt <= 8'h00; //復位初值顯示00 cnt1<=4'b0; end else if(flag) begin G_LED2<=1'b0; R_LED1<=1'b1; if(cnt[3:0] == 4'd9) //個位滿九? begin cnt[3:0] <= 4'd0; //個位清零 if(cnt[7:4] == 4'd5 ) //十位滿五? begin cnt[7:4] <= 4'd0; //十位清零 cnt1<=cnt1+1; end else begin cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一 cnt1<=cnt1; end end else cnt[3:0] <= cnt[3:0] + 1'b1; //個位加一 end else begin cnt <= cnt; G_LED2<=1'b1; R_LED1<=1'b0; end end always@(posedge clk) if(cnt1==4'b0) LED[7:0]<=8'b11111111; else if(cnt1==4'b0001) LED[7:0]<=8'b11111110; else if(cnt1==4'b0010) LED[7:0]<=8'b11111100; else if(cnt1==4'b0011) LED[7:0]<=8'b11111000; else if(cnt1==4'b0100) LED[7:0]<=8'b11110000; else if(cnt1==4'b0101) LED[7:0]<=8'b11100000; else if(cnt1==4'b0110) LED[7:0]<=8'b11000000; else if(cnt1<=4'b0111) LED[7:0]<=8'b10000000; else if(cnt1<=4'b1000) LED[7:0]<=8'b00000000; endmodule module divide # ( //parameter是verilog里參數定義 parameter WIDTH = 24, //計數器的位數,計數的最大值為 2**(WIDTH-1) parameter N = 12_000_000 //分頻系數,請確保 N<2**(WIDTH-1),否則計數會溢出 ) ( input clk, //clk頻率為12MHz input rst_n, //復位信號,低有效, output clkout //輸出信號,可以連接到LED觀察分頻的時鍾 ); reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p為上升沿觸發時的計數器,cnt_n為下降沿觸發時的計數器 reg clk_p,clk_n; //clk_p為上升沿觸發時分頻時鍾,clk_n為下降沿觸發時分頻時鍾 //上升沿觸發時計數器的控制 always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_p <= 1'b0; else if(cnt_p == (N-1)) cnt_p <= 1'b0; else cnt_p <= cnt_p + 1'b1; //計數器一直計數,當計數到N-1的時候清零,這是一個模N的計數器 end //上升沿觸發的分頻時鍾輸出,如果N為奇數得到的時鍾占空比不是50%;如果N為偶數得到的時鍾占空比為50% always @(posedge clk or negedge rst_n) begin if(!rst_n) clk_p <= 1'b0; else if(cnt_p < (N>>1)) //N>>1表示右移一位,相當於除以2取商 clk_p <= 1'b0; else clk_p <= 1'b1; //得到的分頻時鍾正周期比負周期多一個clk時鍾 end //下降沿觸發時計數器的控制 always @(negedge clk or negedge rst_n) begin if(!rst_n) cnt_n <= 1'b0; else if(cnt_n == (N-1)) cnt_n <= 1'b0; else cnt_n <= cnt_n + 1'b1; end //下降沿觸發的分頻時鍾輸出,和clk_p相差半個clk時鍾 always @(negedge clk or negedge rst_n) begin if(!rst_n) clk_n <= 1'b0; else if(cnt_n < (N>>1)) clk_n <= 1'b0; else clk_n <= 1'b1; //得到的分頻時鍾正周期比負周期多一個clk時鍾 end wire clk1 = clk; //當N=1時,直接輸出clk wire clk2 = clk_p; //當N為偶數也就是N的最低位為0,N[0]=0,輸出clk_p wire clk3 = clk_p & clk_n; //當N為奇數也就是N最低位為1,N[0]=1,輸出clk_p&clk_n。正周期多所以是相與 assign clkout = (N==1)? clk1:(N[0]? clk3:clk2); //條件判斷表達式 endmodule module segment ( input wire [3:0] seg_data_1, //四位輸入數據信號 input wire [3:0] seg_data_2, //四位輸入數據信號 output wire [8:0] segment_led_1, //數碼管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A output wire [8:0] segment_led_2 //數碼管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); reg[8:0] seg [15:0]; //存儲7段數碼管譯碼數據 initial begin seg[0] = 9'h3f; // 0 seg[1] = 9'h06; // 1 seg[2] = 9'h5b; // 2 seg[3] = 9'h4f; // 3 seg[4] = 9'h66; // 4 seg[5] = 9'h6d; // 5 seg[6] = 9'h7d; // 6 seg[7] = 9'h07; // 7 seg[8] = 9'h7f; // 8 seg[9] = 9'h6f; // 9 seg[10]= 9'h77; // A seg[11]= 9'h7C; // b seg[12]= 9'h39; // C seg[13]= 9'h5e; // d seg[14]= 9'h79; // E seg[15]= 9'h71; // F end assign segment_led_1 = seg[seg_data_1]; assign segment_led_2 = seg[seg_data_2]; endmodule
6.總結
到這里整個秒表就完成啦。最后再次向讀者們道歉,不能貼上實驗效果圖了。身邊有實驗板的讀者們可以將代碼燒錄進板子觀察現象。本人編程水平、時間有限,這篇文章到這里就要結束啦,歡迎廣大讀者評論留言,更歡迎大家指出本人的不足,希望能通過交流自身得到提高。最后感謝大家的耐心閱讀!