1、key_filter.v
//按键消抖模块,延时法
//延时法:由于机械按键按下时,机械按键会产生抖动,抖动时间一般在5-10ms左右
//所以我们可以采用直接检测到按键被按下时,让延时计数器开始计数、计数20ms后再对按键信号进行采样
//1.如何检测按键被按下了?因为按键初值为高电平,按下后变为低电平,所以当按键按下时必然会出现下降沿
//设计如何检测下降沿?对输入信号进行同步打拍,同步打拍的作用:①消除亚稳态,
//②由于同步信号key_r0和打拍信号key_r1有一个时钟周期的延迟,所以我们可以采用打拍信号key_r1不变&对同步信号取反
//我们要将下降沿nedge信号作为判断条件,因此我们需要nedge信号高电平保持一个时钟周期
//检测到按键被按下,让延时计数器开始计数,延时计数20ms
//延时计数器如何才能开始工作,因为代码中我们采用的是下降沿nedge信号作为按键按下的的判断条件
//直接把nedge信号作为计数器开始累加的条件是不行的,我们需要持续的有效信号让计数器开始计数
//因此就产生了标志信号flag信号(当下降沿有效时把flag信号拉高,当计数完成时flag信号拉低,其他情况保持不变)
//这样flag信号就可以作为计数器的开启条件
module key_filter(
input wire clk ,//系统时钟
input wire rst_n ,//复位信号
input wire key ,//输入的按键信号
output reg key_flag //输出按键被按下时的标志信号
);
parameter CNT_MAX_20MS = 20'd999_999;
reg [19:0] cnt_20ms;
wire add_cnt_20ms;
wire end_cnt_20ms;
reg key_r0;//同步
reg key_r1;//打拍
reg flag ;//标志信号定义
wire nedge ;//下降沿标志信号
//20ms延时计数器模块
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 20'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'd1;
end
end
else begin
cnt_20ms <= cnt_20ms;
end
end
assign add_cnt_20ms = flag;//开始计数的条件
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == CNT_MAX_20MS;
//进行同步打拍检测下降沿
//同步和打拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= 1'b1;
key_r1 <= 1'b1;
end
else begin
key_r0 <= key ;
key_r1 <= key_r0;
end
end
assign nedge = key_r1 & (~key_r0) ;//下降沿信号的描述
//出现下降沿,让延时计数器开始计数
//flag信号赋值
//用flag信号对nedge信号进行寄存
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(nedge)begin
flag <= 1'b1;
end
else if(end_cnt_20ms)begin
flag <= 1'b0;
end
else begin
flag <= flag;
end
end
//描述输出,时序逻辑
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_flag <= 1'b0;
end
else if(end_cnt_20ms)begin
key_flag <= ~key;
end
else begin
key_flag <= 1'b0;
end
end
endmodule
2、d_key_led.v
module d_key_led (
input wire clk ,
input wire rst_n,
input wire [1:0] key ,
output reg [3:0] led
);
parameter CNT_MAX = 25'd24_999_999;//0.5s计数器最大值
reg [24:0] cnt_500ms ;
wire add_cnt_500ms;
wire end_cnt_500ms;
wire [1:0] key_flag;
reg [7:0] led_r;
reg [3:0] led_w;//流水灯信号寄存
reg [1:0] flag ;
key_filter u1_key_filter(
. clk (clk),//系统时钟
. rst_n (rst_n),//复位信号
. key (key[0]),//输入的按键信号
. key_flag(key_flag[0]) //输出按键被按下时的标志信号
);
key_filter u2_key_filter(
. clk (clk),//系统时钟
. rst_n (rst_n),//复位信号
. key (key[1]),//输入的按键信号
. key_flag(key_flag[1]) //输出按键被按下时的标志信号
);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_500ms <= 25'd0;
end
else if(add_cnt_500ms)begin
if(end_cnt_500ms)begin
cnt_500ms <= 25'd0;
end
else begin
cnt_500ms <= cnt_500ms + 1'd1;
end
end
else begin
cnt_500ms <= cnt_500ms;
end
end
assign add_cnt_500ms = 1'b1;
assign end_cnt_500ms = add_cnt_500ms && cnt_500ms == CNT_MAX ;
//流水灯模块的实现
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_w <= 4'b0001;
end
else if(end_cnt_500ms)begin
led_w <= {led_w[2:0],led_w[3]};
end
else begin
led_w <= led_w;
end
end
//跑马灯模块的实现
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_r <= 8'b1111_0000;
end
else if(end_cnt_500ms)begin
led_r <= {led_r[0],led_r[7:1]};
end
else begin
led_r <= led_r;
end
end
//flag信号,对按键输出标志信号进行寄存,持续保持高电平
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 2'b00;
end
else if(key_flag)begin
flag <= key_flag;
end
else begin
flag <= flag;
end
end
//组合逻辑选择不同的类型
always @(*)begin
case(flag)
2'b01: led = led_w;//key1按下流水灯模式
2'b10: led = led_r[3:0];//key2按下跑马灯模式
default:led = 4'b0000;
endcase
end
endmodule
3、key_filter_tb.v
//按键消抖模块仿真
//模拟按键抖动
`timescale 1ns/1ns
module key_filter_tb();
reg clk ;
reg rst_n ;
reg key ;
wire key_flag;
initial begin
clk = 1'b1;
rst_n = 1'b0;
key = 1'b1;
#15
rst_n = 1'b1;
#50000
//模拟按键前抖动
key = 1'b0;
#50
key = 1'b1;
#45
key = 1'b0;
#35
key = 1'b1;
#5
key = 1'b0;
#20000
//模拟按键后抖动
key = 1'b1;
#15
key = 1'b0;
#15
key = 1'b1;
#5
key = 1'b0;
#25
key = 1'b1;
#50000
$stop;
end
always #10 clk = ~clk;
defparam u_key_filter.CNT_MAX_20MS = 20'd99;
key_filter u_key_filter(
. clk (clk),
. rst_n (rst_n),
. key (key),
. key_flag(key_flag)
);
endmodule