目录
-
实验 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.v
module 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.v
module 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.v
module 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 debounce
module 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_down
wire 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
输出,用于响铃
思路:记录所设置的闹钟时间,与时钟时间比较,判断是否触发响铃。
-
响铃逻辑
beep
output 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博客