# Verilog实现数字钟（带校时、按键消抖）并求教一个bug

## Verilog实现数字钟以及一个bug的讨论

### 分频模块

module clk_div(cr,clk_50M,clk_1k,clk_5,clk_1);
parameter N_1k=50_000;            //1kHz分频常数
parameter N_5=1000_0000;          //500Hz分频常数
parameter N_1=5000_0000;          //1Hz分频常数
input cr,clk_50M;
output reg clk_5,clk_1k,clk_1;
reg [14:0]cnt_1k;
reg [24:0]cnt_5;
reg [24:0]cnt_1;

always @(posedge clk_50M or negedge cr)
begin
if(~cr) begin
clk_1<=0;
clk_1k<=0;
clk_5<=0;
cnt_1<=0;
cnt_1k<=0;
cnt_5<=0;
end
else begin
//1kHz输出
if(cnt_1k<(N_1k/2-1)) cnt_1k<=cnt_1k+1'b1;
else begin
cnt_1k<=0;
clk_1k<=~clk_1k;
end

//5Hz输出
if(cnt_5<(N_5/2-1)) cnt_5<=cnt_5+1'b1;
else begin
cnt_5<=0;
clk_5<=~clk_5;
end

//1Hz输出
if(cnt_1<(N_1/2-1)) cnt_1<=cnt_1+1'b1;
else begin
cnt_1<=0;
clk_1<=~clk_1;
end
end
end
endmodule


### 时钟模块

//模10计数器
module cnt10(cp,cr,en,q);
input cp,
cr,
en;
output reg[3:0]q;
always @(posedge cp or negedge cr) begin
if(~cr) q<=4'b0000;         //cr置低清零
else if(en) begin
if(q>=9) q<=4'b0000;
else q<=q+1;
end
else q<=q;
end
endmodule

//模6计数器
module cnt6(cp,cr,en,q);
input cp,
cr,
en;
output reg[3:0]q;
always @(posedge cp or negedge cr) begin
if(~cr) q<=4'b0000;         //cr置低清零
else if(en) begin
if(q>=5) q<=4'b0000;
else q<=q+1;
end
else q<=q;
end
endmodule

//模60计数器
module cnt60(cp,cr,en,q);
input cp,cr,en;
output wire[7:0]q;                  //输出8位BCD码
wire ENP;                           //内部进位信号

cnt10 u0(.cp(cp),.cr(cr),.en(en),.q(q[3:0]));  //引用模10计数器模块
cnt6 u1(.cp(cp),.cr(cr),.en(ENP),.q(q[7:4]));
assign ENP=(q[3:0]==4'b1001);
endmodule

//模24计数器
module cnt24(cp,cr,en,q);
input cp,cr,en;
output reg[7:0]q;

always @(posedge cp or negedge cr)
begin
//个位模10计数，十位模3计数，终态23
if(~cr) q<=8'h00;                    //清零
else if(~en) q<=q;                   //保持
else if(q[7:4]==2&&q[3:0]>=3||q[7:4]>2||q[3:0]>9) q[7:0]<=8'h00;         //错误处理
else if(q[7:4]==2&&q[3:0]<3) begin q[7:4]<=q[7:4]; q[3:0]<=q[3:0]+1; end //20~23计数
else if(q[3:0]==9) begin q[7:4]<=q[7:4]+1; q[3:0]<=4'b0000; end  //十位进位，各位清零
else begin q[3:0]<=q[3:0]+1; q[7:4]<=q[7:4]; end                 //其他情况个位加一
end
endmodule


#### 时钟顶层模块

module clock(
input clk_1,clk_5,cr,en,
input key_2,             //调分信号
input key_3,             //调时信号
output [7:0]hour,        //时信号
output [7:0]min,         //分信号
output [7:0]sec          //秒信号
);
wire sec_min;               //秒-分进位信号
wire min_hour;              //分-时进位信号

//引用模块
cnt60 u_sec(.cp(clk_1),.cr(cr),.en(en),
.q(sec));
cnt60 u_min(.cp(~sec_min),.cr(cr),.en(en),
.q(min));
cnt24 u_hour(.cp(~min_hour),.cr(cr),.en(en),
.q(hour));
assign sec_min=key_2?(sec==8'h59):clk_5;
assign min_hour=key_3?({min,sec}==16'h5959):clk_5;

endmodule


### 显示译码模块

//六位数显管，1ms频率扫描，使用1kHz频率
//此处为共阳极数码管，低电平有效
//片选信号低电平有效
module dig_display(cp,dig,sel,seg);
input cp;
input [23:0]dig;                     //输入24位BCD码
output reg [5:0]sel;                 //选择六个数显管
output reg [7:0]seg;                 //输出8位译码（h为dp）
reg [2:0]cnt;
reg [3:0]dig_r;              //实际转换的4位BCD码

initial cnt<=3'd0;
//片选，1ms刷新
always @(posedge cp)
begin
if(cnt<6) cnt<=cnt+1;
else cnt<=3'd0;
case(cnt)
0:begin dig_r<=dig[3:0];sel<=6'b111110; end        //Q6，右一
1:begin dig_r<=dig[7:4];sel<=6'b111101; end        //Q5，右二
2:begin dig_r<=dig[11:8];sel<=6'b111011; end       //Q4，右三
3:begin dig_r<=dig[15:12];sel<=6'b110111; end      //Q3，左三
4:begin dig_r<=dig[19:16];sel<=6'b101111; end      //Q2，左二
5:begin dig_r<=dig[23:20];sel<=6'b011111; end      //Q1，左一
default:sel<=6'b111111;                            //默认下均不选中
endcase
end

//译码
always @(dig_r)
begin
case(dig_r)
4'h0: seg=8'b1100_0000;        //abcdef
4'h1: seg=8'b1111_1001;        //bc
4'h2: seg=8'b1010_0100;        //abdeg
4'h3: seg=8'b1011_0000;        //abcdg
4'h4: seg=8'b1001_1001;        //bcfg
4'h5: seg=8'b1001_0010;        //acdfg
4'h6: seg=8'b1000_0010;        //acdefg
4'h7: seg=8'b1111_1000;        //abc
4'h8: seg=8'b1000_0000;        //all
4'h9: seg=8'b1001_0000;        //abcdfg
default:seg=8'b0111_1111;      //出现错误显示小数点
endcase
end
endmodule


### 按键消抖模块

1. 带自锁的原版方案
//源码来自：https://blog.csdn.net/zhoutaopower/article/details/103180042
module keyscan_sl(
input clk,
input cr,
input key_in,
output reg key_out
);

//上升沿/下降沿检测模块
reg key_in_0;           //按键抽样现态
reg key_in_1;           //按键上次抽样状态
always @(posedge clk or negedge cr)
begin
if(~cr) begin
key_in_0<=1'b1;
key_in_1<=1'b1;
end
else begin
key_in_0<=key_in;
key_in_1<=key_in_0;
end
end

//下降沿和上升沿的检测方案，记下
wire key_posedge;
wire key_negedge;
assign key_posedge=key_in_0 & (~key_in_1);      //上次检测为0，现态为1时key_posedge置1
assign key_negedge=key_in_1 & (~key_in_0);

//状态机
reg[2:0]state;           //三个状态
parameter IDLE = 3'b001;
parameter SAMPLING = 3'b010;
parameter DOWN = 3'b100;
reg en_cnt;                 //计数使能
reg key_pressed;

always @(posedge clk or negedge cr)
begin
if(~cr) begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
end
else begin
case(state)
IDLE:begin
if(key_negedge) begin
state<=SAMPLING;
en_cnt<=1'b1;
key_pressed<=1'b0;
end
else begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
end
end
SAMPLING:begin
if(key_posedge) begin
state <=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
end
else begin
if(cnt_full) begin
state<=DOWN;
en_cnt<=1'b0;
key_pressed<=1'b0;
end
else begin
state<=SAMPLING;
en_cnt<=1'b1;
key_pressed<=1'b0;
end
end
end
DOWN:begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b1;
end
default:begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
end
endcase
end
end

//Counter
reg [19:0]cnt;
always @(posedge clk or negedge cr)
begin
if(~cr) cnt<=20'h00000;
else if(en_cnt) cnt<=cnt+1;
else cnt<=20'h00000;
end

//Counter_full logic
parameter _20ms=100_0000;    //50M晶振T=20ns，计数1000000次即为20ms
reg cnt_full;
always @(posedge clk or negedge cr)
begin
if(~cr) cnt_full<=1'b0;
else if(cnt==_20ms) cnt_full<=1'b1;
else cnt_full<=1'b0;
end

//保持按键输出状态（自锁）
always @(posedge clk or negedge cr)
begin
if(~cr) key_out<=1'b1;
else if(key_pressed) key_out<=~key_out;
end

endmodule


1. 取消自锁的改进方案
//仅当判定按键松开（key_loose=1）时才进行下降沿检测，当判定按键按下（key_pressed）才进行上升沿检测
//根据以下源码改进：https://blog.csdn.net/zhoutaopower/article/details/103180042

module keyscan(
input clk,
input cr,
input key_in,
output reg key_out
);

//上升沿/下降沿检测模块
reg key_in_0;           //按键抽样现态
reg key_in_1;           //按键上次抽样状态
always @(posedge clk or negedge cr)
begin
if(~cr) begin
key_in_0<=1'b1;
key_in_1<=1'b1;
end
else begin
key_in_0<=key_in;
key_in_1<=key_in_0;
end
end

//下降沿和上升沿的检测方案，记下
wire key_posedge;
wire key_negedge;
assign key_posedge=key_in_0 & (~key_in_1);      //上次检测为0，现态为1时key_posedge置1
assign key_negedge=key_in_1 & (~key_in_0);

//状态机
reg[2:0]state;           //三个状态
parameter IDLE = 3'b001;
parameter SAMPLING = 3'b010;
parameter CONFIRM = 3'b100;
reg en_cnt;                 //计数使能
reg key_loose;              //按键松开
reg key_pressed;            //按键按下

always @(posedge clk or negedge cr)
begin
if(~cr) begin
state<=IDLE;
en_cnt<=1'b0;
key_loose<=1'b1;
key_pressed<=1'b0;
end
else begin
if(key_loose) begin          //按键松开，检测下降沿
case(state)
IDLE:begin
if(key_negedge) begin
state<=SAMPLING;
en_cnt<=1'b1;
key_pressed<=1'b0;
key_loose<=1'b1;
end
else begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
key_loose<=1'b1;
end
end
SAMPLING:begin
if(key_posedge) begin
state <=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
key_loose<=1'b1;
end
else begin
if(cnt_full) begin
state<=CONFIRM;
en_cnt<=1'b0;
key_pressed<=1'b0;
key_loose<=1'b1;
end
else begin
state<=SAMPLING;
en_cnt<=1'b1;
key_pressed<=1'b0;
key_loose<=1'b1;
end
end
end
CONFIRM:begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b1;
key_loose<=1'b0;
end
default:begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
key_loose<=1'b1;
end
endcase
end
else if(key_pressed) begin        //按键按下，检测上升沿
case(state)
IDLE:begin
if(key_posedge) begin
state<=SAMPLING;
en_cnt<=1'b1;
key_pressed<=1'b1;
key_loose<=1'b0;
end
else begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b1;
key_loose<=1'b0;
end
end
SAMPLING:begin
if(key_negedge) begin
state <=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b1;
key_loose<=1'b0;
end
else begin
if(cnt_full) begin
state<=CONFIRM;
en_cnt<=1'b0;
key_pressed<=1'b1;
key_loose<=1'b0;
end
else begin
state<=SAMPLING;
en_cnt<=1'b1;
key_pressed<=1'b1;
key_loose<=1'b0;
end
end
end
CONFIRM:begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b0;
key_loose<=1'b1;
end
default:begin
state<=IDLE;
en_cnt<=1'b0;
key_pressed<=1'b1;
key_loose<=1'b0;
end
endcase
end
end
end

//Counter
reg [19:0]cnt;
always @(posedge clk or negedge cr)
begin
if(~cr) cnt<=20'h00000;
else if(en_cnt) cnt<=cnt+1;
else cnt<=20'h00000;
end

//Counter_full logic
parameter _20ms=100_0000;    //50M晶振T=20ns，计数1000000次即为20ms
reg cnt_full;
always @(posedge clk or negedge cr)
begin
if(~cr) cnt_full<=1'b0;
else if(cnt==_20ms) cnt_full<=1'b1;
else cnt_full<=1'b0;
end

always @(posedge clk or negedge cr)
begin
if(~cr) key_out<=1'b0;
else if(key_pressed) key_out<=1'b1;
else if(key_loose) key_out<=1'b0;
end

endmodule


### 顶层模块

module display_clock(
input cp,cr,key_1,
input key_2,key_3,
output [5:0]sel,
output [7:0]seg
);

wire clk_1;             //1Hz信号
wire clk_1k;            //1kHz信号
wire clk_5;             //5Hz调时专用信号
wire key_min,key_sec;
wire [7:0]hour;
wire [7:0]min;
wire [7:0]sec;

//引用模块
clk_div u0(.cr(cr),.clk_50M(cp),
.clk_1k(clk_1k),.clk_5(clk_5),.clk_1(clk_1));
clock u1(.clk_1(clk_1),.clk_5(clk_5),
.cr(cr),.en(key_en),
.key_2(key_min),.key_3(key_sec),
.hour(hour),.min(min),.sec(sec));
dig_display u2(.cp(clk_1k),.dig({hour,min,sec}),
.sel(sel),.seg(seg));

//key_2、key_3不带自锁按键消抖
keyscan u3(.clk(cp),.cr(cr),.key_in(key_2),.key_out(key_min));
keyscan u4(.clk(cp),.cr(cr),.key_in(key_3),.key_out(key_sec));

//key_en带自锁
keyscan_sl u5(.clk(cp),.cr(cr),.key_in(key_1),.key_out(key_en));
endmodule


RTL结构图：

1. cp：50MHz信号输入
2. cr：复位信号输入（Key0）
3. key_1：计数使能信号输入（Key1）
4. key_2：分校时信号（Key2）
5. key_3：时校时信号（Key3）

• 11
点赞
• 60
收藏
觉得还不错? 一键收藏
• 打赏
• 7
评论
07-11 2万+
02-15 5486
08-10 3万+
10-31 4947
04-27 1481
09-10 8587

### “相关推荐”对你有帮助么？

• 非常没帮助
• 没帮助
• 一般
• 有帮助
• 非常有帮助

Gravellllll

¥1 ¥2 ¥4 ¥6 ¥10 ¥20

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、付费专栏及课程。