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

由于本文的工程相对较大,文件的代码压缩后传到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后,可以通过按键次数选择要调节的数码管的位数。

 

posted @ 2017-07-24 10:46  noticeable  阅读(2372)  评论(0编辑  收藏  举报