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


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

  • 相关阅读:
    Spring AOP 详解
    java 线程的几种状态
    Atomic
    ConcurrentHashMap原理分析
    MySQL存储过程详解 mysql 存储过程
    spring-定时器(2)
    spring-定时器(1)
    spring-线程池(3)
    spring-线程池(2)
    spring-线程池(1)
  • 原文地址:https://www.cnblogs.com/noticeable/p/7220368.html
Copyright © 2020-2023  润新知