按键消抖试验及一个数码管电子时钟的设计

时间:2022-12-20 23:26:19

由于本文的工程相对较大,文件的代码压缩后传到CSDN,其中本文的设计源码为test9,所用quartus版本好位quartus15.1,链接如下http://download.csdn.net/detail/noticeable/9909850

 

设计目的:

      选择四个按键, 通过按键使数码管数据可以稳定进行加1减1的操作,KEY3可以对数码管的显示位进行位选,key0可以进行复位操作,通过数码管位数和led显示的稳定加一,可以验证按键是否成功消抖。

 

 

知识点:1、什么是按键消抖

    2、testbench 中随机数发生函数$random的使用。

 

 

进行按键消抖的原因和方法

  对于机械按键,其按下后,由于其机械特性,会由于弹性产生一个抖动,其在电平输出上表现的就是一个如下的前沿抖动核后延抖动,对于这个 抖动,对于电平敏感的数据接收端来说是不希望出现的,因为其会导致接收端错误的计算按键按下的次数,使项目出现不确定的现象,所以对于按键的抖动,一般需要进行一个消抖的过程。按键消抖试验及一个数码管电子时钟的设计

   对于按键消抖,一般有两种方法,(1)经验表明,按键抖动一般持续的时间在5ms~10ms之间,所以理论上可以在检测到跳变后延时20ms再观察是否真的产生了跳变,这个方法被称之为延时重采样法,这个方法在单片机中用到的比较普遍。(2)第二章方法是当检测到按键处于某电平时,在之后的N个时钟周期内连续检测此按键的电平,如果一直不变,则读出此按键的电平值,这种方法叫做持续采样法。理论上来讲,后者的采样率相对于前者来说是大大增加了的,所以在准确性上来说,后者的准确性更高。这次设计,我们即采用持续采样法进行数据滤波。

 

 

 

设计过程:

首先,建立工程,编辑滤波文件key_filter.v

按键消抖试验及一个数码管电子时钟的设计

  1 /*
2 系统上电后默认进入空闲IDEL状态,key_flag和key_state一起表示消抖的状态
3 key_flag key_state
4 0 1 平常状态
5 0 0 按下状态
6 1 0 确定是否抖动状态
7 */
8 module key_filter(clk,rst_n,key_in,key_flag,key_state);
9
10 input clk;
11 input rst_n;
12 input key_in;
13
14 output reg key_flag;
15 output reg key_state;
16
17
18 localparam
19 IDEL = 4'b0001, //空闲状态
20 FILTER0 =4'b0010,//前抖动滤波
21 DOWN =4'b0100,//按键稳定时态
22 FILTER1 =4'b1000;//后抖动滤波
23
24 reg [3:0]state;
25 reg [19:0]cnt;
26
27 reg key_temp0,key_temp1;
28 reg en_cnt;
29 reg cnt_full;
30 wire pedge,nedge;
31
32
33
34 //边沿检测通过,俩个寄存器进行前后高低状态进行采样后对比
35 always@(posedge clk or negedge rst_n)
36 if(!rst_n)
37 begin
38 key_temp0<=1'b0;
39 key_temp1<=1'b0;
40 end
41 else begin
42 key_temp0<=key_in;
43 key_temp1<=key_temp0;
44 end
45
46 //反相器加一个与门,对寄存器里的两个值进行比较
47 assign nedge=!key_temp0&key_temp1;
48 assign pedge=key_temp0&(!key_temp1);//(!key_temp1)逻辑取反,按位取反(~key_temp1)
49
50
51 always@(posedge clk or negedge rst_n)
52 if (!rst_n) begin
53
54 state<=IDEL;
55 en_cnt=1'b0;
56 key_flag<=1'b0;
57 key_state<=1'b1;
58 end
59 else begin
60 case (state)
61 IDEL:
62 begin
63 key_flag<=1'b0;
64 if(nedge) begin
65 state=FILTER0;
66 en_cnt<=1'b1;//打开计数器开始计数
67 end
68 else
69 state <=IDEL;
70 end
71 FILTER0:
72 if(cnt_full)begin //就是按键事件是否满了20ms,且在这个过程中没有发生数据跳变,是的话说明没有问题,此时是稳定状态,可以进行下一个状态
73 en_cnt<=1'b0;//计数满了也需要关闭计数
74 key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生
75 key_state<=1'b0;
76 state<=DOWN;
77 end
78 else if(pedge) begin
79 state <=IDEL;//检测到上升沿,说明是抖动状态,回到上一状态,并关闭计数器。
80 en_cnt<=1'b0;
81 end
82 else
83 state<=FILTER0;
84
85
86 DOWN:
87 begin
88 key_flag<=1'b0;//到了这个状态说明可以确定是按键电平
89 key_state<=1'b0;
90 if(pedge)begin //等待释放的上升沿信号,进入释放滤波状态
91 state <=FILTER1;
92 en_cnt<=1'b1;
93 end
94 else state<=DOWN;
95 end
96
97
98
99 FILTER1:
100 if(cnt_full)begin //20ms计数满了,没有发生数据跳变(即抖动),说明没有问题,可以释放了
101
102 key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生
103 key_state<=1'b1;
104 state<=IDEL;
105 en_cnt<=0;
106 end
107 else if(nedge) begin
108 state <=DOWN;//检测到下降沿,说明是抖动状态,回到上一状态,并关闭计数器。
109 en_cnt<=1'b0;
110 end
111 else
112 state<=FILTER1;
113 default:begin
114
115 state<=IDEL;
116 en_cnt<=1'b0;
117 key_flag<=1'b0;
118 key_state <=1'b1;
119 end
120 endcase
121 end
122
123
124
125 //定义一个计数器, 已知50M系统时钟,周期即为20ns 需要计数20_000_000/20-1=999_999 //20MS/周期-1的数据,需要20位的寄存器。
126 always@(posedge clk or negedge rst_n)
127 if (!rst_n)
128 cnt<=20'd0;
129 else if (en_cnt)
130 cnt<=cnt+1'b1;
131 else
132 cnt<=20'd0;
133
134 always@(posedge clk or negedge rst_n)
135 if (!rst_n)
136 cnt_full <=1'd0;
137 else if(cnt==999_999)
138 cnt_full<=1'b1;
139 else
140 cnt_full <= 1'b0;
141
142 endmodule

 

 

程序编写完成后,可以通过state machine viewer 观察自己编写的状态机是否合理。

 按键消抖试验及一个数码管电子时钟的设计

本次状态机的状态切换如下

按键消抖试验及一个数码管电子时钟的设计

 

 编写testbench文件,并设定路径

按键消抖试验及一个数码管电子时钟的设计

`timescale 1ns/1ns
`define clk_period
20
module key_filter_tb;
reg clk;
reg rst_n;
reg key_in;
wire key_flag;
wire key_state;

key_filter key_filter0(
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);

initial
clk
=1;
always #(`clk_period/2) clk=~clk;

initial begin
rst_n
<=1'b0;
key_in=1'b1;
#(`clk_period*10)
rst_n
<=1'b1;

key_in
=1;
#(`clk_period
*10)
//模拟按键抖动
key_in=0;
#
1000;
key_in
=1;
#
1400
key_in
=0;
#
1340;
key_in
=1;
#
2600;
key_in
=0;
#
1600;
key_in
=1;
#
2340;
key_in
=0;
#
1300;
key_in
=1;
#
2000;
key_in
=0;
#
1000;
key_in
=1;
#
2050;
key_in
=0;
#20_000_100;
#
100000;//保持稳定一段式时间。

key_in
=1;//释放抖动
#2600;
key_in
=0;
#
1600;
key_in
=1;
#
2340;
key_in
=0;
#
1300;
key_in
=1;
#
20000;
key_in
=0;
#
1000;
key_in
=1;
#
2050;
key_in
=1;
#20_100_000;
#
1000000;
$stop;

end



endmodule

 

点击仿真按钮进行仿真(仿真时可把clk从wave显示中删掉,这样可以大大缩短仿真时间),仿真波形如图,说明整个设计是合理的。

按键消抖试验及一个数码管电子时钟的设计

 

 通过观察-模拟抖动的testbench可以看出我们自己模拟抖动的过程又臭又长,毫无美感和技术可言,对此,我们可以采用task任务和随机数发生函数$random来进行精简。

对testbench修改后的文件如下

`timescale 1ns/1ns
`define clk_period
20
module key_filter_tb;
reg clk;
reg rst_n;
reg key_in;
wire key_flag;
wire key_state;


reg [15:0]myrand;

key_filter key_filter0(
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);

initial
clk
=1;
always #(`clk_period/2) clk=~clk;

initial begin
rst_n
<=1'b0;
key_in=1'b1;
#(`clk_period*10)
rst_n
<=1'b1;
press_key;//直接调用press_key即可模拟一次按键过程了
#100000;
press_key;
#
10000;
$stop;

end

task press_key;
begin
//按下抖动过程
repeat(50)begin
myrand
=($random)%65536;
//random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。
//加过好即为0~65535,不加就是-65535~65535

#myrand key_in
=~key_in;
end
key_in
=0;
#20_500_500
//释放抖动过程
repeat(50)begin
myrand
=($random)%65536;
//random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。
//加过好即为0~65535,不加就是-65535~65535

#myrand key_in
=~key_in;
end
key_in
=1;
#20_500_500;
end
endtask

endmodule

 

仿真结果相对来说也更加明显清晰了许多。

 按键消抖试验及一个数码管电子时钟的设计

仔细观察代码,感觉还是有些缀余,这里添加一个文件,将按键模型写到文件中,

`timescale 1ns/1ns

`define clk_period
20
//模拟一个按键模型

module key_module(key);
output reg key;

reg [15:0]myrand;

initial begin
#(`clk_period
*10)
press_key;
//直接调用press_key即可模拟一次按键过程了
#100000;
press_key;
#
10000;
$stop;
end


task press_key;
begin
//按下抖动过程
repeat(50)begin
myrand
=($random)%65536;
//random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。
//加过好即为0~65535,不加就是-65535~65535

#myrand key
=~key;
end
key
=0;
#20_500_500
//释放抖动过程
repeat(50)begin
myrand
=($random)%65536;
//random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。
//加过好即为0~65535,不加就是-65535~65535

#myrand key
=~key;
end
key
=1;
#20_500_500;
end
endtask

endmodule

 

 然后通过tb文件对模型进行调用(这里是想通过这个testbench的编写,了解到大型项目的文件分级的方法,使得项目程序更富有层次性。)

`timescale 1ns/1ns
`define clk_period
20
module key_filter_tb;
reg clk;
reg rst_n;
wire key_in;
wire key_flag;
wire key_state;



key_filter key_filter0(
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);

key_module key_module1(.key(key_in));

initial
clk
=1;
always #(`clk_period/2) clk=~clk;

initial begin
rst_n
<=1'b0;
#(`clk_period*10)
rst_n
<=1'b1;

end

endmodule

 

 

将key_module添加到路径中

按键消抖试验及一个数码管电子时钟的设计

再点击仿真,能看到仿真波形也是正确的。

按键消抖试验及一个数码管电子时钟的设计

 

之前只是编写的是按键滤波程序,为了使完成项目,还需要很多其他的内容来进行补充。

对输入入的异步信号key_In,当采样点在不定态(即两个输入都在跳变得过程中),那么两个信号的输出就是不确定的,这时候就需要对异步信号进行处理,将其进行同步,为了使输出同步,需要加上两级的D触发器。

/*
系统上电后默认进入空闲IDEL状态,key_flag和key_state一起表示消抖的状态
key_flag key_state
0 1 平常状态
1 0 20ms没有发生跳变,说明按键事件已经可以确认发生了
0 1 说明现在保持着一直按下的状态
1 1 按键已经从按下状态释放了
*/

module key_filter(clk,rst_n,key_in,key_flag,key_state);

input clk;
input rst_n;
input key_in;

output reg key_flag;
output reg key_state;


localparam
IDEL
= 4'b0001, //空闲状态
FILTER0 =4'b0010,//前抖动滤波
DOWN =4'b0100,//按键稳定时态
FILTER1 =4'b1000;//后抖动滤波

reg [3:0]state;
reg [19:0]cnt;

reg key_temp0,key_temp1;
reg en_cnt;
reg cnt_full;
wire pedge,nedge;

reg key_in_s0,key_in_s1;

//key_in异步信号进行同步处理。
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
key_in_s0
<=1'b0;
key_in_s1<=1'b0;
end
else begin
key_in_s0
<=key_in;
key_in_s1
<=key_in_s0;

end


//边沿检测通过,俩个寄存器进行前后高低状态进行采样后对比
always@(posedge clk or negedge rst_n)
if(!rst_n)
begin
key_temp0
<=1'b0;
key_temp1<=1'b0;
end
else begin
key_temp0
<=key_in_s1;
key_temp1
<=key_temp0;
end

//反相器加一个与门,对寄存器里的两个值进行比较
assign nedge=!key_temp0&key_temp1;
assign pedge=key_temp0&(!key_temp1);//(!key_temp1)逻辑取反,按位取反(~key_temp1)


always@(posedge clk or negedge rst_n)
if (!rst_n) begin

state
<=IDEL;
en_cnt
=1'b0;
key_flag<=1'b0;
key_state<=1'b1;
end
else begin
case (state)
IDEL:
begin
key_flag
<=1'b0;
if(nedge) begin
state
=FILTER0;
en_cnt
<=1'b1;//打开计数器开始计数
end
else
state
<=IDEL;
end
FILTER0:
if(cnt_full)begin //就是按键事件是否满了20ms,且在这个过程中没有发生数据跳变,是的话说明没有问题,此时是稳定状态,可以进行下一个状态
en_cnt<=1'b0;//计数满了也需要关闭计数
key_flag<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生
key_state<=1'b0;
state<=DOWN;
end
else if(pedge) begin
state
<=IDEL;//检测到上升沿,说明是抖动状态,回到上一状态,并关闭计数器。
en_cnt<=1'b0;
end
else
state
<=FILTER0;


DOWN:
begin
key_flag
<=1'b0;//到了这个状态说明可以确定是按键电平
key_state<=1'b0;
if(pedge)begin //等待释放的上升沿信号,进入释放滤波状态
state <=FILTER1;
en_cnt
<=1'b1;
end
else state<=DOWN;
end



FILTER1:
if(cnt_full)begin //20ms计数满了,没有发生数据跳变(即抖动),说明没有问题,可以释放了

key_flag
<=1'b1;//产生一个key_flag 的高脉冲,通知外部按键事件发生
key_state<=1'b1;
state<=IDEL;
en_cnt
<=0;
end
else if(nedge) begin
state
<=DOWN;//检测到下降沿,说明是抖动状态,回到上一状态,并关闭计数器。
en_cnt<=1'b0;
end
else
state
<=FILTER1;
default:begin

state
<=IDEL;
en_cnt
<=1'b0;
key_flag<=1'b0;
key_state <=1'b1;
end
endcase
end



//定义一个计数器, 已知50M系统时钟,周期即为20ns 需要计数20_000_000/20-1=999_999 //20MS/周期-1的数据,需要20位的寄存器。
always@(posedge clk or negedge rst_n)
if (!rst_n)
cnt
<=20'd0;
else if (en_cnt)
cnt
<=cnt+1'b1;
else
cnt
<=20'd0;

always@(posedge clk or negedge rst_n)
if (!rst_n)
cnt_full
<=1'd0;
else if(cnt==999_999)
cnt_full
<=1'b1;
else
cnt_full
<= 1'b0;

endmodule

 

驱动程序写完后就需要补充应用程序了。

顶层文件的框图应该如图所示,在后面对其进行添加

按键消抖试验及一个数码管电子时钟的设计

图中rst_n和clk没连,意思是每个文件都需要连上。

下面,首先编写led_ctrl.v文件。

module led_ctrl(clk,rst_n,key_flag1,key_flag2,key_state1,key_state2,led);


input clk,rst_n,key_flag1,key_flag2,key_state1,key_state2;
output reg [3:0]led;



always@(posedge clk or negedge rst_n)
if(!rst_n)
led
<=4'b0000;
else if(key_flag1&&!key_state1)
led
<=led+1'b1;
else if(key_flag2&&!key_state2)
led
<=led-1'b1;

else

led
<=led;

endmodule

 


 

然后,根据上篇文章对segment的了解,在工程中添加文件segment.v对数码管显示进行控制:

这里为了设计的代码整洁及逻辑性,将文件进行分层,首先将segment的译码器segment_data单独写一个文件

module segment_data(clk,rst_n,data_temp,seg) ;
input clk,rst_n;
input [3:0]data_temp;
output reg[6:0]seg;

always@(posedge clk or negedge rst_n)
if(!rst_n)
seg
<=7'b0000000;
else begin
case (data_temp)
4'h0: seg<=7'b1000000;
4'h1: seg<=7'b1111001;
4'h2: seg<=7'b0100100;
4'h3: seg<=7'b0110000;
4'h4: seg<=7'b0011001;
4'h5: seg<=7'b0010010;
4'h6: seg<=7'b0000010;
4'h7: seg<=7'b1111000;
4'h8: seg<=7'b0000000;
4'h9: seg<=7'b0010000;
4'ha: seg<=7'b0001000;
4'hb: seg<=7'b0000011;
4'hc: seg<=7'b1000110;
4'hd: seg<=7'b0100001;
4'he: seg<=7'b0000110;
4'hf: seg<=7'b0001110;
endcase
end
endmodule

 

 

然后编写segment的显示控制主文件,要求显示时间的跳动,并且可以控制单加,并且能够通过按键快速调节显示,主文件segment_2.v文件如下

 

module segment_2(rst_n,clk,en ,key_flag1,key_flag2,key_flag3,key_state1,key_state2,key_state3,data_out);
input clk;//50M
input rst_n;
input en; //使能端口,确定按键是否有效
reg cnt_full,second,miniter,hour;
reg [5:0]sel; //位选(选择控制的哪个数码)
reg [23:0]data;
reg [25:0]cnt;
reg [3:0]data_temp0,data_temp1,data_temp2,data_temp3,data_temp4,data_temp5;//待显示数据缓存
wire [6:0] seg_temp0,seg_temp1,seg_temp2,seg_temp3,seg_temp4,seg_temp5,seg_temp;
input key_flag1,key_flag2,key_flag3,key_state1,key_state2,key_state3;
output reg[41:0]data_out;


segment_data segment_data0(.clk(clk),
.rst_n(rst_n),
.data_temp(data_temp0),
.seg(seg_temp0)) ;
segment_data segment_data1(.clk(clk),
.rst_n(rst_n),
.data_temp(data_temp1),
.seg(seg_temp1)) ;
segment_data segment_data2(.clk(clk),
.rst_n(rst_n),
.data_temp(data_temp2),
.seg(seg_temp2)) ;

segment_data segment_data3(.clk(clk),
.rst_n(rst_n),
.data_temp(data_temp3),
.seg(seg_temp3)) ;
segment_data segment_data4(.clk(clk),
.rst_n(rst_n),
.data_temp(data_temp4),
.seg(seg_temp4)) ;
segment_data segment_data5(.clk(clk),
.rst_n(rst_n),
.data_temp(data_temp5),
.seg(seg_temp5))
;



always@(posedge clk or negedge rst_n)
if (!rst_n)begin //按下key0键
data<=0;
sel
<=0;

end
else if(key_flag1&&!key_state1)begin//按下key1键
if(data==24'd235959)
data<=0;
else begin
case(sel)
0:
data
=data+1'b1;
1: if(data[3:0]==4'b1001)//最大显示位为9
data[3:0]<=0;
else
data[
3:0]<= data[3:0]+1'b1;
2: if(data[7:4]==4'b0101)//最大显示位为5
data[7:4]<=0;
else
data[
7:4]<= data[7:4]+1'b1;

3:
if(data[11:8]==4'b1001)//最大显示位为9
data[11:8]<=0;
else
data[
11:8]<=data[11:8]+1'b1;


4:if(data[15:12]==4'b0101)//最大显示位为5
data[15:12]<=0;
else
data[
15:12]<= data[15:12]+1'b1;


5:
if(data[19:16]==4'b1001)//最大显示位为9
data[19:16]<=0;
else
data[
19:16]<= data[19:16]+1'b1;

6:
if(data[19:16]>=4'b0011) begin // 第5位大于3时
if(data[23:20]==4'b0001)//最大显示位为1
data[23:20]<=0;
else if(data[23:20]==4'b0010)//最大显示位为2
data[23:20]<=0;

end
else data[23:20]=data[23:20]+1'b1;

default
sel
<=0;
endcase
end end
else if(key_flag2&&!key_state2) begin//按下key2键
if(data==0)
data
<=0;
else begin
case(sel)
0:
data
<=data-1'b1;
1: if(data[3:0]==0)
data[
3:0]<=0;
else
data[
3:0]<= data[3:0]-1'b1;

2:
if(data[7:4]==0)
data[
7:4]<=0;
else
data[
7:4]<= data[7:4]-1'b1;
3:
if(data[11:8]==0)
data[
11:8]<=0;
else
data[
11:8]<= data[11:8]-1'b1;

4: if(data[15:12]==0)
data[
15:12]<=0;
else
data[
15:12]<= data[15:12]-1'b1;
5: if(data[19:16]==0)
data[
19:16]<=0;
else
data[
19:16]<= data[19:16]-1'b1;
6: if(data[23:20]==0)
data[
23:20]<=0;
else
data[
23:20]<= data[23:20]-1'b1;
default
sel
<=0;
endcase
end end


else if(key_flag3&&!key_state3) begin //按下key3键,可以通过这个按键改固定位数
if(sel>=6)
sel
<=0;
else begin
sel
<=sel+1;
end

end

//时钟信号,每秒更换一次显示
else if(cnt==26'd49_999_999) //时钟跳变及跳变逻辑
begin

if(data>=24'b0010_0011_0101_1001_0101_1001) //时钟到了最大时间23小时59分59秒?
data<=0;
else if(data<=24'b0010_0011_0101_1001_0101_1001)
begin
if(data[7:0]>=8'b0101_1001)
begin //秒针到了59
data[7:0]<=0;
if(data[15:8]>=8'b0101_1001) begin //分钟到了59
data[15:8]<=0;
if(data[19:16]>=4'b0011)begin
data[19:16]<=0;
data[
23:16]<=data[23:16]+1'b1;end
else
data[
19:16]<=data[19:16]+1'b1;
end


else begin
if(data[11:8]>=4'b1001) begin
data[11:8]<=0;
data[
15:12]<=data[15:12]+1'b1; end
else
data[
11:8]<=data[11:8]+1'b1; end
end

else if(data[7:0]<=8'b0101_1001) begin
if(data[3:0]>=4'b1001)begin
data[3:0]<=0;
data[
7:4]<=data[7:4]+1'b1;end
else
data[
3:0]<=data[3:0]+1'b1; end
end

end




//译码器
always@(*)
begin

data_temp0
<=data[3:0];
data_temp1
<=data[7:4];
data_temp2
<=data[11:8];
data_temp3
<=data[15:12];
data_temp4
<=data[19:16];
data_temp5
<=data[23:20];
data_out[
6:0]<=seg_temp0;
data_out[
13:7]<=seg_temp1;
data_out[
20:14]<=seg_temp2;
data_out[
27:21]<=seg_temp3;
data_out[
34:28]<=seg_temp4;
data_out[
41:35]<=seg_temp5 ;
end


//定义一个计数器, 已知50M系统时钟,周期即为20ns 需要计数1000_000_000/20-1=49_999_999 //20MS/周期-1的数据,需要26位的寄存器。
always@(posedge clk or negedge rst_n)
if (!rst_n)
cnt
<=26'd0;
else if (en)begin
if(cnt>26'd49_999_999)
cnt<=0;
else
cnt
<=cnt+1'b1;end
else
cnt
<=26'd0;


endmodule

 

 

 

 

 

 

 

 

 

 

编写key_top_tb文件,并进行链接,链接时需要把module模块添加到路径中。

 

`timescale 1ns/1ns
`define clk_period
20

module key_top_tb;

reg clk;
reg rst_n;
wire key_in1,key_in2;
wire [3:0]led;
reg press1,press2;
key_top key_top0(.clk(clk),
.rst_n(rst_n),
.key_in1(key_in1),
.key_in2(key_in2),
.led(led)
);

key_module key_module3(
.press(press1),
.key(key_in1)
);

key_module key_module4(
.press(press2),
.key(key_in2)
);
initial
clk
=1;
always #(`clk_period/2) clk=~clk;

initial begin
rst_n
= 1'b0;
press1 = 0;
press2
= 0;
#(`clk_period
*10) rst_n = 1'b1;
#(`clk_period*10 + 1);

press1
= 1;
#(`clk_period
*3)
press1
= 0;

#80_000_000;

press1
= 1;
#(`clk_period
*3)
press1
= 0;

#80_000_000;

press2
= 1;
#(`clk_period
*3)
press2
= 0;

#80_000_000;

press2
= 1;
#(`clk_period
*3)
press2
= 0;

#80_000_000;
$stop;
end
endmodule

 

 

 按键消抖试验及一个数码管电子时钟的设计

编写过程中发现需要对module.v文件进行修改,否者无法对按键是key_in?进行区分,修改后如下

 

`timescale 1ns/1ns
`define clk_period
20
//模拟一个按键模型

module key_module(press,key);
output reg key;
input press;
reg [15:0]myrand;

// initial begin
// #(`clk_period*10)
// press_key;//直接调用press_key即可模拟一次按键过程了
// #100000;
// press_key;
// #10000;
// $stop;
// end
initial begin
key
=1'b1;
end

always@(posedge press)
press_key;



task press_key;
begin
//按下抖动过程
repeat(50)begin
myrand
=($random)%65536;
//random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。
//加过好即为0~65535,不加就是-65535~65535

#myrand key
=~key;
end
key
=0;
#20_500_500
//释放抖动过程
repeat(50)begin
myrand
=($random)%65536;
//random 是个32位的函数,要想其产生一个固定的范围的数据,就将其除以我们希望产生范围的数据的最大值。
//加过好即为0~65535,不加就是-65535~65535

#myrand key
=~key;
end
key
=1;
#20_500_500;
end
endtask

endmodule

 

 

仿真波形如下,按照仿真文件,led数据变化为1111~1110~1101~1110~1111,加减操作都成功完成了

 按键消抖试验及一个数码管电子时钟的设计

 

 设定引脚。

 按键消抖试验及一个数码管电子时钟的设计

烧写程序,查看现象,将程序烧写到友晶的开发板中,可以观察到最后两个数码管按照秒钟的规律跳变,并且通过默认的按键KEY1,KEY2可以加减计数6位的数码管,数码管可以正常进位,按下key3后,可以通过按键次数选择要调节的数码管的位数。

按键消抖试验及一个数码管电子时钟的设计

按键消抖试验及一个数码管电子时钟的设计