目錄
-
實驗 9 FPGA數字鐘
-
-
-
實驗分析:
-
實作思路:
-
硬體支持:
-
-
硬體描述語言代碼撰寫:
-
1 頂層模塊
-
2 時鐘分頻,(正/倒)計時器模塊
-
3 輸入處理模塊
in_out.v -
5 24小時時鐘,計時,秒表模塊
-
6 鬧鐘
-
7 時間設定
-
-
-
實驗 9 FPGA數字鐘
??請使用SystemVerilog/Verilog實作一個數字鐘,
要求:
(1)能夠顯示時分秒;
(2)能夠設定開始時間;
(3)使用你自己的7段數碼管顯示譯碼電路實作;
(4)可以使用動態顯示方法實作;
(5)依據實作的其他附加功能,酌情加分:秒表、倒計時、鬧鐘、…
(6)需要在Basys3 FPGA開發板上實作,并通過驗收
實驗分析:
該實驗主要考察對使用硬體設計語言(HDL)進行編程設計,鍛煉硬體設計能力,
實作思路:
采用模塊化設計的思想,將整個數字鐘分解為若干功能模塊:
-
埠映射:約束檔案(代碼埠映射到硬體埠)
-
處理按鍵輸入的io模塊:消抖,判斷按下
-
特定功能模塊:時鐘分頻,24小時時鐘,正/倒計時,秒表,鬧鐘,秒表,時間設定
-
顯示模塊:譯碼,選擇輸出
硬體支持:
FPGA開發板:Artix-7 Basys3 from Xilinx

學習步驟:
-
閱讀手冊,了解結構與功能:
Basys 3 Reference Manual - Digilent Reference -
若需要更詳細的資訊,則閱讀所用模塊的原理圖
Basys 3 Schematic - Digilent



硬體描述語言代碼撰寫:
1 頂層模塊
輸入:板載時鐘CLK,各種按鍵
輸出:按鍵LED燈信號,數碼管12位信號(4位用于位置選擇,7位用于7段數碼管顯示,1位用于小數點顯示)
內部:
1 分線接線,設定緩沖區等
2 例化各種子模塊
3 選擇數碼管輸出
-
digital_clk.vmodule digital_clk( input CLK, input BTNU, BTND, BTNL, BTNR, BTNC, input SW0, SW1, SW2, SW3, SW4, SW5, SW6, input SW13, SW14, SW15, output reg[11:0] display_out, output LD0, LD1, LD2, LD3, LD4, LD5, LD6, output LD13, LD14, LD15 ); //時鐘分頻 wire ms_clk; wire clock_clk; //1Hz wire scan_clk; //1kHZ wire Reset, reset, set; //按鍵接線 wire btnu_down, btnd_down, btnl_down, btnr_down, btnc_down; wire sw0, sw1, sw2, sw3, sw4, sw5, sw6; wire sw13, sw14, sw15; //各模塊數碼管輸出區 wire [11:0]clock_seg ; wire [11:0]countup_seg; wire [11:0]countdown_seg; wire [11:0]alarm_seg; wire [11:0]reset_seg; wire [11:0]sw_seg; //時間設定接線 wire [3:0] sethour1; wire [3:0] sethour2; wire [3:0] setminute1; wire [3:0] setminute2; wire [3:0] setsecond1; wire [3:0] setsecond2; wire [3:0] hour1; wire [3:0] hour2; wire [3:0] minute1; wire [3:0] minute2; wire [3:0] second1; wire [3:0] second2; //wire [5:0] setbit; wire beep; assign Reset = sw0; assign reset = sw13; assign set = sw1; //LD assign LD0 = sw0; assign LD1 = sw1; assign LD2 = sw2; assign LD3 = sw3; assign LD4 = sw4; assign LD5 = sw5; assign LD6 = sw6; assign LD13 =sw13; assign LD14 = sw14; assign LD15 = sw15; div_n32p #(42950) myscan_clk( //輸出1000Hz掃描時鐘 .CLK(CLK), .reset(Reset), .clk(scan_clk) ); div_n32p #(43) myclock_clk( //輸出1Hz計時時鐘 .CLK(CLK), .reset(Reset), .clk(clock_clk) ); div_n32p #(2577) myms_clk( .CLK(CLK), .reset(Reset), .clk(ms_clk) ); // io模塊 in_out myio( .CLK(CLK), .BTNU(BTNU), .BTND(BTND), .BTNL(BTNL), .BTNR(BTNR), .BTNC(BTNC), .SW0(SW0), .SW1(SW1), .SW2(SW2), .SW3(SW3), .SW4(SW4), .SW5(SW5), .SW6(SW6), .SW13(SW13), .SW14(SW14), .SW15(SW15), .btnu_down(btnu_down), .btnd_down(btnd_down), .btnl_down(btnl_down), .btnr_down(btnr_down), .btnc_down(btnc_down), .sw0(sw0), .sw1(sw1), .sw2(sw2), .sw3(sw3), .sw4(sw4), .sw5(sw5), .sw6(sw6), .sw13(sw13), .sw14(sw14), .sw15(sw15) ); // 時鐘模塊 clock myclock( .CLK(CLK), .clock_clk(clock_clk), //1Hz .scan_clk(scan_clk), //1kHZ .Reset(Reset), .reset(reset && sw2), .set(set && sw2), .start_pause(btnc_down && sw2), .display_h(sw15), .sethour1(sethour1), .sethour2(sethour2), .setminute1(setminute1), .setminute2(setminute2), .setsecond1(setsecond1), .setsecond2(setsecond2), .hour1 (hour1), .hour2 (hour2), .minute1(minute1), .minute2(minute2), .second1(second1), .second2(second2), .clock_seg(clock_seg[7:0]),//8 .seln(clock_seg[11:8])//4 ); //正計時模塊 countup mycountup( .CLK(CLK), .clock_clk(clock_clk), //1Hz .scan_clk(scan_clk), //1kHZ .Reset(Reset), .reset(reset && sw3), .set(set && sw3), .display_h(sw15), .start_pause(btnc_down && sw3), .sethour1(sethour1), .sethour2(sethour2), .setminute1(setminute1), .setminute2(setminute2), .setsecond1(setsecond1), .setsecond2(setsecond2), .clock_seg(countup_seg[7:0]),//8 .seln(countup_seg[11:8])//4 ); //倒計時模塊 countdown mycountdown( .CLK(CLK), .clock_clk(clock_clk), //1Hz .scan_clk(scan_clk), //1kHZ .Reset(Reset), .reset(reset && sw4), .set(set && sw4), .display_h(sw15), .start_pause(btnc_down && sw4), .sethour1(sethour1), .sethour2(sethour2), .setminute1(setminute1), .setminute2(setminute2), .setsecond1(setsecond1), .setsecond2(setsecond2), .clock_seg(countdown_seg[7:0]),//8 .seln(countdown_seg[11:8])//4 ); //鬧鐘模塊 alarm myalarm( .CLK(CLK), .clock_clk(clock_clk), //1Hz .scan_clk(scan_clk), //1kHZ .Reset(Reset), .reset(reset && sw5), .set(set && sw5), .start(sw14), .display_h(sw15), .hour1(hour1), .hour2(hour2), .minute1(minute1), .minute2(minute2), .second1(second1), .second2(second2), .sethour1(sethour1), .sethour2(sethour2), .setminute1(setminute1), .setminute2(setminute2), .setsecond1(setsecond1), .setsecond2(setsecond2), .clock_seg(alarm_seg[7:0]),//8 .seln(alarm_seg[11:8]),//4 .beep(beep) ); //秒表 stopwatch mystopwatch( .CLK(CLK), .ms_clk(ms_clk), .scan_clk(scan_clk), .Reset(Reset),//全域復 .reset(reset && sw6),//功能復 .start_pause(btnc_down && sw6), .display_h(display_h), .clock_seg(sw_seg[7:0]),//8 .seln(sw_seg[11:8])//4 ); button_set_time myset( .CLK(CLK), .Reset(Reset), // .reset(1'b0), .reset(reset && set), .set(set), .btnu_down(btnu_down), .btnd_down(btnd_down), .btnl_down(btnl_down), .btnr_down(btnr_down), .btnc_down(btnc_down), //out .sethour1(sethour1), .sethour2(sethour2), .setminute1(setminute1), .setminute2(setminute2), .setsecond1(setsecond1), .setsecond2(setsecond2)); reset_module myreset( .CLK(CLK), .Reset(Reset), .scan_clk(scan_clk), .clock_clk(clock_clk), .clock_seg(reset_seg[7:0]), .seln(reset_seg[11:8])); //選擇輸出 always@(posedge CLK, negedge Reset)begin if(~Reset) display_out <= reset_seg; else if(sw2) begin//時鐘 if(beep && sw14)display_out <=alarm_seg;//鬧鐘響了 else display_out <= clock_seg; end else if(sw3)display_out <=countup_seg; //正計時 else if(sw4)display_out <=countdown_seg;//倒計時 else if(sw5)display_out <=alarm_seg; //鬧鐘 else if(sw6)display_out <=sw_seg; //秒表 else display_out <= reset_seg; end endmodule
2 時鐘分頻,(正/倒)計時器模塊
時鐘分頻
將板載輸入100MHz分頻為所需頻率:
1000Hz用于掃描
1Hz用于時鐘計時
60Hz用于秒表計時
由分頻公式\(f=\frac{p}{2^{N}} f_{0}\),得\(p=\frac{2^{N} f}{f_{0}}\),采用32位計數器即可求得對應分頻器的加數\(p\)
-
div_n32p.vmodule div_n32p #(parameter p = 43)(//1Hz input CLK, reset, output clk); reg [31:0] counter; always@(posedge CLK, negedge reset)begin if(!reset) counter<=32'b0; else begin counter = counter + p; end end assign clk = counter[31]; endmodule
1位計時器
通過輸入的進位計時增加,滿量程后進位,帶有設定時間和開始/暫停功能,
-
counter.vmodule counter #(parameter base = 10)( input CLK, input cin, input reset, input set, input start_pause, input [3:0] scount, output reg[3:0] count, output reg cout ); reg [2:0] cin_reg; wire cin_p; reg mode;//1開始 0暫停 always@(posedge CLK, negedge reset)begin if(!reset) mode<=1'b0; else if(start_pause) mode<=~mode; else begin mode<=mode; end end always@(posedge CLK, negedge reset)begin if(!reset) begin cin_reg <=3'b0; end else begin cin_reg <={cin_reg[1:0], cin}; end end assign cin_p = ~cin_reg[2] && cin_reg[1]; always@(posedge CLK, negedge reset)begin if(!reset) begin //復位 count <= 4'b0; cout <= 0; end else if(set==1'b1)begin//設定時間 count <= scount; cout <=0; end else begin//自增 if(count >= base)begin cout <= 1; count <=4'b0; end else if(cin_p && mode==1'b1) begin count <=count+1; cout<=0; end else begin count<=count; cout<=0; end end end endmodule
1位倒計時器
與計時器相仿,減一個數等價于加上其補碼,-1→+15
-
decounter.v`timescale 1ns / 1ps module decounter #(parameter base = 10)( input CLK, input cin, input reset, input set, input start_pause, input [3:0] scount, output reg[3:0] count, output reg cout); reg [2:0] cin_reg; wire cin_p; reg mode;//1開始 0暫停 always@(posedge CLK, negedge reset)begin if(!reset) mode<=1'b0; else if(start_pause) mode<=~mode; else begin mode<=mode; end end always@(posedge CLK, negedge reset)begin if(!reset) begin cin_reg <=3'b0; end else begin cin_reg <={cin_reg[1:0], cin}; end end assign cin_p = ~cin_reg[2] && cin_reg[1]; always@(posedge CLK, negedge reset)begin if(!reset) begin //復位 count <= 4'b0; cout <= 0; end else if(set==1'b1)begin//設定時間 count <= scount; end else begin//自增 if(cin_p && mode==1'b1) begin if(count !=0 ) count <=count + 4'd15; else begin count <=base-1; cout <= 1; end end else begin count<=count; cout<=0; end end end endmodule
3 輸入處理模塊in_out.v
輸入:未處理的按鍵接線
輸出:處理后按鍵接線
-
按鍵消抖
-
判斷Button按下
按鍵消抖
由于按鍵輸入可能存在的抖動與對板載時鐘的異步輸入,所以要進行消抖與同步處理,
這里采用了簡單的串聯觸發器構成的同步器,通過三級或更多級觸發器,使得最終采樣到的信號為亞穩態的概率盡可能小,即輸出趨于穩定,

-
module debouncemodule debounce( input CLK, input key_in, output key_out ); reg delay1; reg delay2; reg delay3; always@(posedge CLK)begin delay1 <= key_in; delay2 <= delay1; delay3 <= delay2; end assign key_out = delay3; endmodule
判斷Button按下
按下,電位由低變高(或相反),存在一個上升沿,能不能用posedge檢測上升沿來檢測按鍵按下?
本著老師所說的控制信號與其他信號分開處理的要求,這里采取了連續采樣寄存多次Button信號,通過判斷臨近兩位是否出現相反的電位,來確定是否出現了上升沿,(相當于又做了一次同步??,感覺這里處理的不簡練)
-
btn_downwire btnu; reg [2:0] btnu_reg; assign btnu_down = ~btnu_reg[2]&(btnu_reg[1]); always@(posedge CLK)begin btnu_reg<= {btnu_reg[1:0], btnu}; end
5 24小時時鐘,計時,秒表模塊
??輸入:
各時鐘與復位信號:CLK,clock_clk,scan_clk,Reset
設定時間,開始/暫停,高位顯示信號
??輸出:
設定時間值,輸出時間值,數碼管信號
-
例化counter,采用逐級進位
counter #(6) s1_counter( .CLK(CLK), .reset(Reset&&~reset), .set(set), .start_pause(start_pause), .cin(cout[0]), .scount(setsecond1), .count(second1), .cout(cout[1]) ); -
掃描
reg [2:0] sel; always@(posedge scan_clk, negedge Reset)begin if(!Reset) sel<=0; else if(sel==3) sel<=0; else sel <= sel+1; end always@(posedge scan_clk, negedge Reset)begin if(~Reset || full)begin {seln, clock_seg} <= 12'b1101_0000001_1; end else if(~display_h)begin case(sel) 3'd0:{seln, clock_seg} <= {4'b0111, encoder(minute1), 1'b1}; 3'd1:{seln, clock_seg} <= {4'b1011, encoder(minute2), dot }; 3'd2:{seln, clock_seg} <= {4'b1101, encoder(second1), 1'b1}; 3'd3:{seln, clock_seg} <= {4'b1110, encoder(second2), dot }; default:{seln, clock_seg} <= 12'b1011_0000000_0; endcase end else begin case(sel) 3'd0:{seln, clock_seg} <= {4'b0111, encoder(hour1), 1'b1}; 3'd1:{seln, clock_seg} <= {4'b1011, encoder(hour2), dot }; 3'd2:{seln, clock_seg} <= {4'b1101, encoder(minute1), 1'b1}; 3'd3:{seln, clock_seg} <= {4'b1110, encoder(minute2), dot }; default:{seln, clock_seg} <= 12'b0111_0000000_0; endcase end end -
譯碼器(函式)
function [6:0]encoder; input [3:0] dec; begin case(dec) 4'd0: encoder = 7'b0000_001; 4'd1: encoder = 7'b1001_111; 4'd2: encoder = 7'b0010_010; 4'd3: encoder = 7'b0000_110; 4'd4: encoder = 7'b1001_100; 4'd5: encoder = 7'b0100_100; 4'd6: encoder = 7'b0100_000; 4'd7: encoder = 7'b0001_111; 4'd8: encoder = 7'b0000_000; 4'd9: encoder = 7'b0000_100; default: encoder = 7'b1000_000; endcase end endfunction
正倒計時模塊:正計時與24小時時鐘模塊相同,倒計時采用decounter即可
秒表:接入ms_clk即可,其他與時鐘相同
6 鬧鐘
輸入同上,另增一鬧鐘開關輸入
輸出另增一
beep輸出,用于響鈴
思路:記錄所設定的鬧鐘時間,與時鐘時間比較,判斷是否觸發響鈴,
-
響鈴邏輯
beepoutput reg beep; always@(posedge clock_clk, negedge Reset)begin if(~Reset)begin count <= 4'b0; pre <= 1'b0; count <=4'b0; end else if(onclk && start)begin //到時間 pre <=1; count<=0; beep <=1; end else if(pre==1 && count <4'd5)begin count<= count+1; end else if(pre==1 && count>=4'd5)begin pre<=0; count<=0; beep<=0; end else begin pre<=pre; count<=count; beep<=beep; end end
7 時間設定
輸入:各控制信號,按鍵信號
輸出:所設定的時間
維護reg [5:0] setbit;通過左右按鍵調整設定哪一位,上下按鍵增減數值,
按鍵功能:
SW15顯示高位
SW14鬧鐘開關
SW13功能歸零reset高有效
SW6秒表
SW5鬧鐘
SW4倒計時
SW3正計時
SW2時鐘
SW1設定時間
SW0全域復位
按鍵去抖動:
(154條訊息) FPGA中的按鍵消抖_Super-fei的博客-CSDN博客_fpga按鍵消抖
Verilog——FPGA按鍵去抖操作_Footprints明軒的博客-CSDN博客
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/538815.html
標籤:其他
