当前位置: 首页 > news >正文

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
http://www.lryc.cn/news/602382.html

相关文章:

  • css 实现虚线效果的多种方式
  • Kubernetes 存储入门
  • 【自动化运维神器Ansible】Ansible常用模块之unarchive模块详解
  • 快速入门Linux操作系统(二)
  • 腾讯AI IDE
  • 《红色脉络:一部PLMN在中国的演进史诗 (1G-6G)》 第3篇 | 2G:GSM一统江湖?——移动、联通的“分家”与双轨并行
  • windows平台计划任务批处理实现定时任务
  • 零基础学习性能测试第九章:全链路追踪-系统中间件节点监控
  • DDD领域驱动中瘦模型与富态模型的核心区别
  • FastGPT本地构建工作流高级编排(最新4.11.0)
  • 火狐浏览器中国特供版关闭,如何下载 Firefox 国际版?如何备份数据?
  • App Inventor 2 使用 MaterialIcons 图标字体,快捷展示专业图标
  • NAS远程访问新解法:OMV与cpolar的技术协同价值
  • CentOS7 安装和配置教程
  • nvim tagbar安装
  • VUE2 学习笔记11 脚手架
  • 架构实战——互联网架构模板(“存储层”技术)
  • 黑马商城微服务-下
  • 云服务器以域名形式访问机房Kubernetes集群服务之解决方案
  • 国产化PDF处理控件Spire.PDF教程:Java 提取 PDF 图片,高质量提取与图片过滤技巧
  • 【设计模式】状态模式 (状态对象(Objects for States))
  • Spring AI 1.0 提供简单的 AI 系统和服务
  • claude code
  • LeetCode 85. 最大矩形
  • 剑指“CPU飙高”问题
  • FFmpeg 安装与使用
  • kafka开启Kerberos使用方式
  • 【三桥君】如何解决后端Agent和前端UI之间的交互问题?——解析AG-UI协议的神奇作用
  • 2025年7月28日训练日志
  • Android 解析 TrafficDescriptor 的 OSAPP 信息