改自wolai笔记FPGA数字钟(Verilog)
项目源代码已上传至github:houhuawei23/DDCA_2022
实验 9 FPGA数字钟
实验分析:
实现思路:
硬件支持:
硬件描述语言代码编写:
1 顶层模块
2 时钟分频,(正/倒)计时器模块
3 输入处理模块in_out.v
5 24小时时钟,计时,秒表模块
6 闹钟
7 时间设置
请使用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
输入:板载时钟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
时钟分频
将板载输入100MHz
分频为所需频率:
1000Hz
用于扫描
1Hz
用于时钟计时
60Hz
用于秒表计时
由分频公式 f = p 2 N f 0 f=\frac{p}{2^{N}} f_{0} f=2Npf0,得 p = 2 N f f 0 p=\frac{2^{N} f}{f_{0}} p=f02Nf。采用32位计数器即可求得对应分频器的加数 p p 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
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
输入:
各时钟与复位信号: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
即可,其他与时钟相同
输入同上,另增一闹钟开关输入
输出另增一
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
输入:各控制信号,按键信号
输出:所设置的时间
维护reg [5:0] setbit;
通过左右按键调整设置哪一位,上下按键增减数值。
按键功能:
SW15显示高位
SW14闹钟开关
SW13功能归零reset高有效
SW6秒表
SW5闹钟
SW4倒计时
SW3正计时
SW2时钟
SW1设置时间
SW0全局复位
按键去抖动:
FPGA中的按键消抖_Super-fei的博客-CSDN博客_fpga按键消抖
Verilog——FPGA按键去抖操作_Footprints明轩的博客-CSDN博客