🎉欢迎来到FPGA专栏~按键消抖模块设计与验证
🎉 目录-按键消抖模块设计与验证
一、效果演示
🥝模块设计:
🥝按键消抖模块的完整代码,可直接使用:
c
//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
/
module KeyFilter(
input Clk,
input Rst_n,
input key_in,
output reg key_flag,
output reg key_state
);
//按键的四个状态
localparam
IDLE = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
//状态寄存器
reg [3:0] curr_st;
//边沿检测输出上升沿或下降沿
wire pedge;
wire nedge;
//计数寄存器
reg [19:0]cnt;
//使能计数寄存器
reg en_cnt;
//计数满标志信号
reg cnt_full;//计数满寄存器
//------<边沿检测电路的实现>------
//边沿检测电路寄存器
reg key_tmp0;
reg key_tmp1;
//边沿检测
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);
assign pedge = (key_tmp0) & (!key_tmp1);
//------<状态机主程序>------
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
//------<20ms计数器>------
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
🥝RTL视图:
🥝状态转移:
🥝仿真结果:
二、模块设计
🥝模块设计:
信号 | 作用 |
---|---|
clk | 时钟信号输入 |
rst_n | 复位信号输入 |
key_in | 按键信号输入 |
key_flag | 消抖结束之后的标志位 |
key_state | 消抖结束之后按键的状态 |
🥝上升沿检测电路:
🥝下降沿检测电路:
🥝边沿检测电路的实现:
检测到下降沿,nedge输出高电平;检测到上升沿,pedge输出高电平。
c
//------<边沿检测电路的实现>------
//边沿检测电路寄存器
reg key_tmp0;
reg key_tmp1;
//边沿检测
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);//检测到下降沿,nedge输出高电平
assign pedge = (key_tmp0) & (!key_tmp1);//检测到上升沿,pedge输出高电平
🥝一段式状态机设计:
按键的四种状态:
c
//按键的四个状态
localparam
IDLE = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
计数器:
c
//------<20ms计数器>------
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次
//计数寄存器
reg [19:0]cnt;
//使能计数寄存器
reg en_cnt;
//计数满标志信号
reg cnt_full;//计数满寄存器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
状态机主程序:
c
//------<状态机主程序>------
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
三、仿真测试
3.1 常规编写
c
`timescale 1ns/1ns
`define clock_period 20
module KeyFilter_tb;
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
KeyFilter KeyFilter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clock_period*10);
Rst_n = 1'b1;
#(`clock_period*10 + 1);
key_in = 0;#1000;
key_in = 1;#2000;
key_in = 0;#1400;
key_in = 1;#2600;
key_in = 0;#1300;
key_in = 1;#200;
key_in = 0;#20000100;
#50000000;
key_in = 1;#2600;
key_in = 0;#1000;
key_in = 1;#2000;
key_in = 0;#1400;
key_in = 1;#2600;
key_in = 0;#1300;
key_in = 1;#200;
key_in = 1;#20000100;
#50000000;
$stop;
end
endmodule
仿真结果:
3.2 task编写
c
`timescale 1ns/1ns
`define clock_period 20
module KeyFilter_tb;
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
KeyFilter KeyFilter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clock_period*10);
Rst_n = 1'b1;
#(`clock_period*10 + 1);
#30000;
PressKey; #10000;
PressKey; #10000;
PressKey; #10000;
$stop;
end
reg [15:0]myrand;
task PressKey;
begin
//50次随机时间按下抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key_in = ~key_in;
end
key_in = 0;
#50_000_000;//按下稳定
//50次随机时间释放抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key_in = ~key_in;
end
key_in = 1;
#50_000_000;//释放稳定
end
endtask
endmodule
注意$random
随机函数的用法:
$random
这一系统函数可以产生一个有符号的32bit
随机整数。一般的用法是$random%b
,其中b>0
。这样就会生成一个范围在(-b+1):(b-1)
中的随机数。如果只得到正数的随机数,可采用{$random}%b
来产生。
c
myrand = {$random}%65536;//0~65535
上述语句的作用即是产生了0~65535
之间的随机数。
通过repeat语句
循环50
次,就产生了50次不同的延时效果:
c
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key_in = ~key_in;
end
仿真结果:
四、仿真模型
编写key_model并添加到测试激励文件中:
c
`timescale 1ns/1ns
module key_model(key);
output reg key;
reg [15:0]myrand;
initial begin
key = 1'b1;
PressKey; #10000;
PressKey; #10000;
PressKey; #10000;
$stop;
end
task PressKey;
begin
//50次随机时间按下抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key = ~key;
end
key = 0;
#50_000_000;//按下稳定
//50次随机时间释放抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key = ~key;
end
key = 1;
#50_000_000;//释放稳定
end
endtask
endmodule
修改KeyFilter_tb:
c
`timescale 1ns/1ns
`define clock_period 20
module KeyFilter_tb;
reg Clk;
reg Rst_n;
wire key_in;
wire key_flag;
wire key_state;
KeyFilter KeyFilter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
key_model key_model0(.key(key_in));
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
#(`clock_period*10);
Rst_n = 1'b1;
#(`clock_period*10 + 1);
end
endmodule
整个激励文件的内部结构:
仿真结果:
🧸结尾