09_按键消抖模块的设计与验证
1. 实验目标
利用所学知识,设计并实现一个按键消抖模块,将外部输入的单比特按键信号做消抖处理后输出,输出信号正常可被其他模块调用。按照 cnt_20ms 计数器计数到 999_999 时清零来分析。如果在0-20ms内没有毛刺抖动的产生。也就是0-999_999计数器内没有毛刺现象,那么按键就是被按下,有一个脉冲。
2. 波形图
3. RTL
module key_filter
#(
// 50mhz 20ms的计数
parameter CNT_MAX = 20'd1_000_000
/*
这是我们第一次使用参数的方式定义常量
使用参数的方式定义常量有很多好处,如:我们在 RTL 代码中实例化该模块时,如果需要两个
不同计数值的计数器我们不必设计两个模块,而是直接修改参数的值即可;另一个好处是在编
写 Testbench 进行仿真时我们也需要实例化该模块,但是我们需要仿真至少 0.5s 的时间才
能够看出到 led_out 效果,这会让仿真时间很长,也会导致产生的仿真文件很大,所以我们
可以通过直接修改参数的方式来缩短仿真的时间而看到相同的效果,且不会影响到 RTL 代码模
块中的实际值,因为 parameter 定义的是局部参数,所以只在本模块中有效。为了更好的区
分,参数名我们习惯上都要大写。
*/
)
// 端口的定义
(
input wire sys_clk, //系统时钟 50Mh
//根据频率的转换 一个时钟周期20ns 20ms 就是 1_000_000
input wire sys_rst_n,
input wire key_in,
output reg key_flag //脉冲信号
);
reg [19:0] cnt_20ms; // 定义计数器
//cnt:上升沿开始从 0 到 CNT_MAX 循环计数
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt_20ms <= 20'd0;
end
else if(key_in == 1'b1)
begin
cnt_20ms <= 20'd0;
end
else if((key_in == 1'b0) && (cnt_20ms == CNT_MAX-1'd1))
begin
cnt_20ms <= cnt_20ms;
end
else
begin
cnt_20ms <= cnt_20ms + 20'd1;
end
end
//key_flag:当计数满 20ms 后产生按键有效标志位
//且 key_flag 在 999_999 时拉高,维持一个时钟的高电平
//cnt:上升沿开始从 0 到 CNT_MAX 循环计数
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
key_flag <= 1'b0;
end
else if(cnt_20ms == CNT_MAX-1'd1-1'd1)
begin
key_flag <= 1'b1;
end
else
begin
key_flag <= 1'b0;
end
end
endmodule
4. testbench
`timescale 1ns/1ns
// 在这里面不需要对端口进行定义
module tb_key_filter();
//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量
//parameter define
//为了缩短仿真时间,我们将参数化的时间值改小
//但位宽依然定义和参数名的值保持一致
//也可以将这些参数值改成和参数名的值一致
parameter CNT_1MS = 20'd19 ,
CNT_11MS = 21'd69 ,
CNT_41MS = 22'd149 ,
CNT_51MS = 22'd199 ,
CNT_60MS = 22'd249 ;
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
reg sys_clk;
reg sys_rst_n;
reg key_in;
reg [21:0] tb_cnt ; //模拟按键抖动计数器
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire key_flag;
//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)
initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别 -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
begin
sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
//其他信号的赋值都是用“<=”
sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
#20 //延时20ns
sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位
end
// always语句 一直在执行
sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50Mhz
always #10 sys_clk = ~sys_clk;//取模求余数,产生随机数 1'b0、1'b1//每隔 10ns 产生一次随机数
//tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tb_cnt <= 22'd0;
else if(tb_cnt == CNT_60MS)
//计数器计数到 CNT_60MS 完成一次按键从按下到释放的整个过程
// 也就是250次
tb_cnt <= 22'd0;
else
tb_cnt <= tb_cnt + 22'd1;
//key_in:产生输入随机数,模拟按键的输入情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1;
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS) || (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2;
else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
key_in <= 1'b0;
else
key_in <= 1'b1;
//------------------------------------------------------------
//待测试 RTL 模块的实例化,相当于将待测试模块放到测试模块中,并将输入输出对应连接上
//测试模块中产生激励信号给待测试模块的输入,以观察待测试模块的输出信号是否正确
//------------------------------------------------
key_filter //第一个是被实例化模块的名子,第二个是我们自己定义的在另一个
#(
.CNT_MAX (25'd25)
)
//模块中实例化后的名字。同一个模块可以在另一个模块中或不同的
//另外模块中被多次实例化,第一个名字相同,第二个名字不同
key_filter_inst
(
//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
//“.”可以理解为将这两个信号连接在一起
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_in (key_in ),
.key_flag (key_flag )
);
endmodule