Verilog简易的按键消抖模块
这是一个简单的 Verilog 按键消抖模块,通过计时判断按键是否稳定按下,并输出一个时钟周期的脉冲作为响应。
时序图
i_key 按下一段时间,o_key_pulse 输出一个单周期脉冲
仿真图
key_debounce.v
module key_debounce
#(parameter P_CLK_FREQ_MHZ = 50, // 时钟频率,单位MHz,默认50MHzparameter P_DEBOUNCE_MS = 20, // 消抖时间,单位ms,默认20msparameter L_CNT_WIDTH = 32 // 需要外部计算后传入
)
(input wire i_clk , //系统时钟50Mhzinput wire i_rst_n , //全局复位input wire i_key , //按键输入信号output reg o_key_pulse //消抖后的脉冲信号
);// 根据时钟频率和消抖时间计算需要计数的最大值
localparam L_MAX_CNT = P_CLK_FREQ_MHZ * 1000 * P_DEBOUNCE_MS;
reg [L_CNT_WIDTH-1:0] r_cnt ;// ==================================================
// r_cnt :记录按键按键的时间
// ==================================================
always@(posedge i_clk or negedge i_rst_n)if(i_rst_n == 1'b0)r_cnt <= {(L_CNT_WIDTH+1){1'b0}};//按键松开,计数器清零else if(i_key == 1)r_cnt <= {(L_CNT_WIDTH+1){1'b0}};//按键按下时,计数器计数else if(i_key == 1'b0 && r_cnt < L_MAX_CNT-1)r_cnt <= r_cnt + 1'b1;else//如果按键一直不释放,则r_cnt维持最大值,防止多次触发r_cnt <= r_cnt;// ==================================================
// o_key_pulse :输出脉冲信号,当按键稳定按下超过设定时间,输出一个时钟周期的脉冲
// ==================================================
always@(posedge i_clk or negedge i_rst_n)if(i_rst_n == 1'b0)o_key_pulse <= 1'b0;//计数快满时,产生一个时钟周期的脉冲,因为按住不松手时,计数器会维持在L_MAX_CNT-1else if(r_cnt == L_MAX_CNT-3)o_key_pulse <= 1'b1;elseo_key_pulse <= 1'b0;
endmodule
tb.v
`timescale 1ns / 1psmodule tb();//为了快速测试,消抖1msparameter P_MAX_CNT = 20'd1000; //1m// 信号声明reg i_clk;reg i_rst_n;reg i_din;wire o_dout;// 实例化被测模块key_debounce #(.P_CLK_FREQ_MHZ(1),.P_DEBOUNCE_MS(1)) uut (.i_clk(i_clk),.i_rst_n(i_rst_n),.i_key(i_din),.o_key_pulse(o_dout));// 生成50MHz时钟(周期20ns)initial begini_clk = 0;forever #10 i_clk = ~i_clk; // 50MHz时钟end// 测试序列initial begin// 初始化信号i_rst_n = 0;i_din = 1;#20;// 释放复位i_rst_n = 1;#20;// 测试用例1:短按抖动(<20ms),应被过滤$display("Test Case 1: Short press with jitter (<20ms), should be filtered");generate_key_press(500); // 500个时钟周期(10us)的短按#1000;// 测试用例2:长按稳定(>20ms),应产生脉冲$display("Test Case 2: Long stable press (>20ms), should generate a pulse");generate_key_press(P_MAX_CNT + 100); // 长按超过20ms#1000;// 测试用例3:带抖动的长按,应忽略抖动并在稳定20ms后触发$display("Test Case 3: Long press with jitter, should ignore jitter and trigger after stable 20ms");generate_key_press_with_chatter(P_MAX_CNT + 100, 10); // 带抖动的长按#1000;// 测试用例4:多次按下释放$display("Test Case 4: Multiple press and release");repeat(3) begingenerate_key_press(P_MAX_CNT + 100);#1000;end// 测试用例5:边界条件 - 刚好20ms$display("Test Case 5: Edge case - exactly 20ms");generate_key_press(P_MAX_CNT);#1000;// 新增测试用例6:按键一直按下,验证是否重复触发$display("Test Case 6: Key held down, check for repeated triggers");i_din = 0; // 按下按键#(P_MAX_CNT * 20 * 5); // 保持按下状态60ms(3个20ms周期)#1000;// 结束仿真$display("All test cases completed");$finish;end// 任务:生成按键按下(无抖动)task generate_key_press;input [31:0] press_cycles;begin// 按下按键i_din = 0;#(press_cycles * 20); // 保持低电平// 释放按键i_din = 1;#1000; // 释放后等待一段时间endendtask// 任务:生成带抖动的按键按下task generate_key_press_with_chatter;input [31:0] press_cycles;input [31:0] chatter_times; // 抖动次数begininteger i;// 初始按下(带抖动)for (i = 0; i < chatter_times; i = i + 1) begini_din = 0;#(20 * ({$random} % 41 + 10)); // 随机低电平时间 (10-50 cycles)i_din = 1;#(20 * ({$random} % 41 + 10)); // 随机高电平时间 (10-50 cycles)end// 稳定按下i_din = 0;#(press_cycles * 20); // 保持低电平// 释放按键(带抖动)for (i = 0; i < chatter_times; i = i + 1) begini_din = 1;#(20 * ({$random} % 41 + 10)); // 随机高电平时间 (10-50 cycles)i_din = 0;#(20 * ({$random} % 41 + 10)); // 随机低电平时间 (10-50 cycles)end// 稳定释放i_din = 1;#1000; // 释放后等待一段时间endendtask// 监测输出并验证结果reg [19:0] pulse_cnt;reg [19:0] trigger_count; // 记录触发次数initial beginpulse_cnt = 0;trigger_count = 0;// 监测o_dout信号,验证脉冲宽度forever @(posedge i_clk) beginif (o_dout) beginpulse_cnt = pulse_cnt + 1;if (pulse_cnt == 0) begin // 新脉冲开始trigger_count = trigger_count + 1;$display("Time %0t: Detected trigger #%0d", $time, trigger_count);endend else if (pulse_cnt > 0) begin$display("Time %0t: o_dout goes low, pulse width = %0d clock cycles", $time, pulse_cnt);// 验证脉冲宽度是否符合预期if (pulse_cnt == 4) begin$display("PASS: Pulse width correct (4 cycles)");end else begin$display("FAIL: Pulse width should be 4, actual = %0d", pulse_cnt);endpulse_cnt = 0;endendend// 自动验证按键消抖功能reg [63:0] key_press_time;initial beginkey_press_time = 0;wait(i_rst_n == 1);#100; // 等待复位完成// 监测i_din和o_dout信号,验证消抖功能forever begin// 等待按键按下(下降沿)@(negedge i_din);key_press_time = $time;$display("Time %0t: Key press detected", $time);// 等待可能的o_dout脉冲@(posedge o_dout);$display("Time %0t: o_dout pulse detected", $time);// 验证i_din是否稳定低电平至少20msif ($time - key_press_time < (P_MAX_CNT * 20)) begin$display("FAIL: Key press duration less than 20ms but pulse generated");end else begin$display("PASS: Key held low for more than 20ms before pulse");endendend// 新增:验证持续按下时的触发次数initial begin// 等待测试用例6开始#5000;wait(i_din == 0);// 等待3个20ms周期#(P_MAX_CNT * 20 * 3);// 验证触发次数if (trigger_count >= 3) begin$display("FAIL: Key held down triggered %0d times", trigger_count);end else begin$display("PASS: Key held down only triggered once");endendendmodule