verilog常用模块1——按键消抖模块详解

按键消抖模块key_filter

1. 原理介绍

在这里插入图片描述
在这里插入图片描述

如图,按键未按下时keys信号为高电平,按下则为低电平;通过检测keys信号电平,就可以判断按键状态。

但反作用弹簧会导致抖动现象,电平信号出现一段不确定波形

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZ30ZhLP-1588400946083)(C:%5CUsers%5Ccw199%5CDesktop%5CBlog%5C%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97%5Cimage-20200501160250296.png)]
一般情况下,抖动的电平信号为1的持续时间不会超过20ms,只要通过对抖动波形的发生时长计时(计数),当为1的时长超过20ms,则可判定 进入了发生了按键按下/释放动作,并确定按键状态。

也可以采用一些硬件电路进行消抖,如RS触发器,555定时器组成的单稳态触发器等

(原理:《小梅哥-FPGA系统设计与验证实战指南_V24》P164)

2. 模块及接口设计在这里插入图片描述在这里插入图片描述
3. 状态机设计

由时序图分析可知,存在这样四种状态:

  • IDLE 未按下时的空闲状态
  • FILTER0 按下抖动滤除状态
  • DOWN 按下稳定状态
  • FILTER1 释放抖动滤除状态
状态转移图

状态转移条件
当前状态下一状态转移条件输出
IDLEFILTER0nedge==1(检测到下降沿)(随后开始计数直至20ms)key_state=1
key_flag=1??
FILTER0IDLEpedge==1(在按下消抖状态中,(未计数至20ms)如果检测到上升沿,则说明是一个抖动,仍回到初始态重新等待下降沿到来)
FILTER0DOWNCnt_full(检测到下降沿后即开始计时,判断按键电平是否在计数值满20ms时仍为低电平,若满足则认为确实按下,进入按下状态)
DOWNFILTER1pedge==1(按下稳定状态中,如果检测到上升沿,则启动释放消抖)(随后开始计数直至20ms)
FILTER1DOWNnedge==1(在释放消抖状态中,如果检测到下降沿且记数值未满,说明是个抖动,回到DOWN状态重新等待上升沿到来)
FILTER1IDLECnt_full(若最后一次抖动后,处于高电平的时间计数满20ms,则认为确实释放完成,回到IDLE状态)
4. 模块
(1)单bit异步信号同步化设计(两级DFF消除亚稳态模块)

按键输入信号key_in对于FPGA内部信号而言是一个异步输入的信号,具有随机性,不依赖于系统时钟Clk,如不处理直接作为输入使用,将会引其时序违例,导致亚稳态。因此需要用两级DFF将key_in打慢两拍,依赖于Clk,消除亚稳态。在这里插入图片描述

异步信号采样时出现不定态,即出现亚稳态

在这里插入图片描述

key_in_b即为打慢两拍同步后的信号。

两级寄存器消除亚稳态具体原理解释

解释1

亚稳态在异步电路中是不可避免的,不管用什么同步电路去同步异步信号,都会出现亚稳态。那同步电路的作用是什么呢?其实是为了不让亚稳态传播。 两级同步电路的思想其实是为了给亚稳态留出足够的时间,好让亚稳态变成稳定态。

一般情况下亚稳态持续的时间很短,所以两级DFF同步之后的信号基本上不会出现亚稳态,可以给后级使用。

这里用两级DFF的目的就是给亚稳态留出一个DFF的工作时钟周期,如果第一级DF的输出出现亚稳态,那么在一个时钟周期内,让亚稳态变成稳定态。

这样第二级DFF采样第一级DFF的输出时,第一级DFF的输出早就稳定了,因此不会出现亚稳态的传播。所以在用两级同步电路对异步信号做了处理后,降低同步电路的工作时钟,就可以给亚稳态留更多的时间让它变成稳定态,因此可以起到使电路更稳定安全的效果。

解释2

假设第一级触发器的输入不满足其建立保持时间,它在第一个脉冲沿到来后输出的数据就为亚稳态,那么在下一个脉冲沿到来之前,其输出的亚稳态数据在一段恢复时间后必须稳定下来,而且稳定的数据必须满足第二级触发器的建立时间,如果都满足了,在下一个脉冲沿到来时,第二级触发器将不会出现亚稳态,因为其输入端的数据满足其建立保持时间。同步器有效的条件:第一级触发器进入亚稳态后的恢复时间 + 第二级触发器的建立时间 < = 时钟周期。

更确切地说,输入脉冲宽度必须大于同步时钟周期与第一级触发器所需的保持时间之和。最保险的脉冲宽度是两倍同步时钟周期。所以,这样的同步电路对于从较慢的时钟域来的异步信号进入较快的时钟域比较有效,对于进入一个较慢的时钟域,可能根本采不到异步信号。

但是一级FF稳定后的out是随机的,到第二级FF后大概率没有亚稳态了,但是得到的数据0/1都有可能,仅适用于对错误不敏感的地方。对于敏感的电路,可以采用双口RAM或FIFO同步。

https://blog.csdn.net/weixin_41458037/article/details/94383906?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4

异步信号同步化代码实现
reg key_in_a,key_in_b;//声明同步寄存器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
    begin
		key_in_a <= 1'b0;//第一个寄存器
		key_in_sb <= 1'b0;//第二个寄存器
	end
else 
    begin
		key_in_a <= key_in;
		key_in_b <= key_in_a;
	end

在一些高速设计中,也有使用三级触发器进行单bit信号同步的设计,此方法只是为了提高平均故障时间( Mean Time Between Failure,MTBF),其电路为在两级触发器后再接一级触发器,代码为再加一个key_in_c。

(2)边沿检测模块设计
边沿检测电路原理图

在这里插入图片描述
在这里插入图片描述

边沿检测代码
reg key_tmpa,key_tmpb;
wire pedge,nedge;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
    begin
		key_tmpa <= 1'b0;
		key_tmpb <= 1'b0;
	end
else 
    begin
        key_tmpa <= key_in_sb;
        key_tmpb <= key_tmpa;
    end

assign nedge = (~key_tmpa) && key_tmpb;
assign pedge = key_tmpa && (~key_tmpb);

在c语言中,!和~均表示取反,这两个的区别在于:

  1. ! :代表逻辑取反,即:把非0的数值变为0,0变为1;

  2. ~ :表示按位取反,即在数值的二进制表示方式上,将0变为1,将1变为0;

    按位取反“~”:按位取反1变0,0变1

    逻辑非“!”:逻辑取反, false变true,true变false,在C中,只要不是0就是真

在这里,对于1bit二进制数来说,!和 ~ 都是一样的

在这里插入图片描述

消亚稳态模块+边沿检测模块RTL视图
(3)20ms计数器模块

一般推荐一个always块只对一个信号进行操作!!使逻辑清晰易修改

reg [19:0]cnt;
reg en_cnt; //使能计数寄存器

//计数模块	
always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		cnt <= 20'd0;
	else if(en_cnt)
		cnt <= cnt + 1'b1;
	else
		cnt <= 20'd0;

//计数满信号产生模块
always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		cnt_full <= 1'b0;
	else if(cnt == 20'd999_999)
		cnt_full <= 1'b1;
	else
		cnt_full <= 1'b0;
(4)状态机模块
reg [3:0]state;
localparam
		IDEL		= 4'b0001,
		FILTER0		= 4'b0010,
		DOWN		= 4'b0100,
		FILTER1 	= 4'b1000;

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		en_cnt <= 1'b0;
		state <= IDEL;
		key_flag <= 1'b0;
		key_state <= 1'b1;
	end
	else begin
		case(state)
			IDEL :
				begin
					key_flag <= 1'b0;
					if(nedge)begin
						state <= FILTER0;
						en_cnt <= 1'b1;
					end
					else
						state <= IDEL;
				end
					
			FILTER0:
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b0;
					en_cnt <= 1'b0;
					state <= DOWN;
				end
				else if(pedge)begin
					state <= IDEL;
					en_cnt <= 1'b0;
				end
				else
					state <= FILTER0;
					
			DOWN:  
                //通过判断 key_flag && !key_state 来确定按键的状态,为 1 发生按下动作
				begin
					key_flag <= 1'b0;
					if(pedge)begin
						state <= FILTER1;
						en_cnt <= 1'b1;
					end
					else
						state <= DOWN;
				end
			
			FILTER1:
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b1;
					state <= IDEL;
					en_cnt <= 1'b0;
				end
				else if(nedge)begin
					en_cnt <= 1'b0;
					state <= DOWN;
				end
				else
					state <= FILTER1;
			
			default:
				begin 
					state <= IDEL; 
					en_cnt <= 1'b0;		
					key_flag <= 1'b0;
					key_state <= 1'b1;
				end
				
		endcase	
	end

通过判断 (key_flag && !key_state==1) 来确定按键的状态,为 TRUE 则按下动作发生在这里插入图片描述

(5)仿真
仿真方法1 定时发生型
`timescale 1ns/1ns

`define clk_period 20

module key_filter_tb;

	reg Clk;
	reg Rst_n;
	reg key_in;
	
	wire key_flag;
	wire key_state;
	
	key_filter key_filter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
	initial Clk= 1;
	always#(`clk_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		key_in = 1'b1;
		#(`clk_period*10) Rst_n = 1'b1;
        #(`clk_period*10 + 1); //与时钟边沿稍微延后1ns,减少门级仿真中亚稳态的产生
        
        //模拟按下抖动 20ms 内
		key_in = 1'b0;#1000;
		key_in = 1'b1;#2000;
		key_in = 1'b0;#1400;
		key_in = 1'b1;#2600;
		key_in = 1'b0;#1300;
		key_in = 1'b1;#200;
		key_in = 1'b0;#2000100;
		#50000100;
		//产生一个低电平大于 20ms,代表按下稳定
        
        //模拟释放抖动 20ms 内
		key_in = 1'b1;#2000;
		key_in = 1'b0;#1000;
		key_in = 1'b1;#2000;
		key_in = 1'b0;#1400;
		key_in = 1'b1;#2600;
		key_in = 1'b0;#1300;
		key_in = 1'b1;#2000100;
		#50000100;
		//产生一个高电平大于 20ms,代表释放稳定
        
        //模拟按下抖动 20ms 内
		key_in = 1'b0;#1000;
		key_in = 1'b1;#2000;
		key_in = 1'b0;#1400;
		key_in = 1'b1;#2600;
		key_in = 1'b0;#1300;
		key_in = 1'b1;#200;
		key_in = 1'b0;#2000100;
		#50000100;
		//产生一个低电平大于 20ms,代表按下稳定
        
        //模拟释放抖动 20ms 内
		key_in = 1'b1;#2000;
		key_in = 1'b0;#1000;
		key_in = 1'b1;#2000;
		key_in = 1'b0;#1400;
		key_in = 1'b1;#2600;
		key_in = 1'b0;#1300;
		key_in = 1'b1;#2000100;
		#50000100;
        //产生一个高电平大于 20ms,代表释放稳定
        
		$stop;		
	end
	
endmodule

仿真1波形图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

仿真方法2 press_task型
reg [15:0]myrand;//随机数

task press_key;
    begin
        
        repeat(50) 
            begin //五十次随机时间按下抖动
                myrand = {$random}%65536;
                #myrand key_in = ~key_in;
            end
        key_in = 0;
        #50_000_000; //按下稳定

        repeat(50) 
            begin //50 次随机时间释放抖动
                myrand = {$random}%65536;
                #myrand key_in = ~key_in;
            end
        key_in = 1;
        #50_000_000; //释放稳定
        
    end
endtask

initial 
    begin
        Rst_n = 1'b0;
        key_in = 1'b1;
        #(`clk_period*10) Rst_n = 1'b1;
        #30000;
        
        press_key; #10000;
        press_key; #10000;
        press_key; #10000;
        
        $stop;
	end
仿真2波形图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

仿真方法3 无外部按键型key_model1

按键设置在task press_key内部已设置好

在这里插入图片描述

key_filter_top_tb(1)代码

`timescale 1ns/1ns

`define clk_period 20

module key_filter_top_tb;

	reg Clk;
	reg Rst_n;
	wire key_in;
	
	wire key_flag;
	wire key_state;
	
	key_filter key_filter0(
		.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#(`clk_period/2) Clk = ~Clk;
	
	initial 
        begin
		Rst_n = 1'b0;
		#(`clk_period*10) Rst_n = 1'b1;
		#(`clk_period*10 + 1);		
		end

endmodule

key_model1代码

`timescale 1ns/1ns

module key_model(key);

	output reg key;
	
	reg [15:0]myrand;
	
	initial 
        begin
		key = 1'b1;
			press_key;
			#10000;
			press_key;
			#10000;
			press_key;
			#10000;
			$stop;
		end
	
	task press_key;
		begin
			repeat(50)
                begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
				end
			key = 0;
			#25000000;
			
			repeat(50)
                begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
				end
			key = 1;
			#25000000;		
		end	
	endtask

endmodule
仿真3波形图

在这里插入图片描述
在这里插入图片描述

仿真方法4 有外部按键型key_model2

按键设置在task press_key内部未设置好,按键动作为外部人为输入
在这里插入图片描述

keu_filter_top代码

module key_filter_top;

	input Clk;
	input Rst_n;
    input press;
    
	reg  key_in;
	
	output key_flag;
	output key_state;
	
	key_filter key_filter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
        .key_in(key_in),
        
        .key_flag(key_flag),
        .key_state(key_state)
	);
	
	key_model key_model0(
		.press(press),
		.key(key_in)
	);

endmodule

key_model2代码:按键动作由外部人为键入

`timescale 1ns/1ns

module key_model(press,key);
	
	input press;
	output reg key;
	
	reg [15:0]myrand;
	
    initial 
	begin
        key = 1'b1;     
    end
    
    always@(posedge press)
        press_key;

	task press_key;
		begin
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 0;
			#25000000;
			
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 1;
			#25000000;		
		end	
	endtask

endmodule
5. 边沿D触发器&电平D触发器&锁存器 波形图对比

部人为键入

`timescale 1ns/1ns

module key_model(press,key);
	
	input press;
	output reg key;
	
	reg [15:0]myrand;
	
    initial 
	begin
        key = 1'b1;     
    end
    
    always@(posedge press)
        press_key;

	task press_key;
		begin
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 0;
			#25000000;
			
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 1;
			#25000000;		
		end	
	endtask

endmodule
5. 边沿D触发器&电平D触发器&锁存器 波形图对比在这里插入图片描述
  • 22
    点赞
  • 114
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Verilog按键消抖模块是一种用于消除按键抖动的电路模块。在Verilog代码中,通常会使用状态机来实现按键消抖功能。引用\[1\]中的代码展示了一个名为key_filter_top的模块,该模块包含一个时钟信号Clk、一个复位信号Rst_n和一个按键输入信号press。该模块还输出了一个按键标志信号key_flag和一个按键状态信号key_state。在该模块中,使用了名为key_filter的子模块和名为key_model的子模块。其中,key_filter模块用于实现按键消抖功能,key_model模块用于模拟按键输入。引用\[2\]中的代码展示了一个名为key_filter_top_tb的测试模块,该模块用于对key_filter_top模块进行仿真测试。在该测试模块中,使用了一个时钟信号Clk、一个复位信号Rst_n和一个按键输入信号press。通过对时钟信号进行周期性翻转,模拟了时钟信号的工作。引用\[3\]中的代码展示了一个名为key_filter_tb2的仿真模块,该模块用于模拟按键的抖动现象。在该仿真模块中,使用了一个时钟信号Clk、一个复位信号Reset_n和一个按键输入信号Key。通过使用随机函数模拟按键的抖动现象,模拟了按键的按下和释放过程。整体来说,Verilog按键消抖模块通过消除按键抖动现象,确保按键信号的稳定性和可靠性。 #### 引用[.reference_title] - *1* *2* [verilog常用模块1——按键消抖模块详解](https://blog.csdn.net/m0_37921318/article/details/105890194)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [verilog功能模块——按键消抖](https://blog.csdn.net/m0_70935984/article/details/130803240)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值