【重拾FPGA】由按键消抖观察到的阻塞赋值和非阻塞赋值的区别

按键消抖在单片机上的实现是非常简单的,检测到输入变化后延时一段时间继续检测,在FPGA上也可以运用相似的原理,延时采用计数方式。

注:此消抖方法并不是最优化的,本文并不主讲消抖,而是讲在消抖过程中发现的阻塞赋值和非阻塞赋值的区别

本文使用的按键消抖中,有四个重要的寄存器和一个wire型变量:

output reg key_state;  //寄存新的key状态
output reg key_state_pre;  //寄存上一个key状态
	
output reg key_delay_state;  //寄存计数值满后的key状态
output reg key_delay_state_pre;  //寄存计数值满后的上一个key状态
	
wire key_negedge;   //检测key是否出现下降沿,其值为 key_negedge = key_state_pre&(~key_state)
		    //为1时出现下降沿,即按下,为0时没按下,或为一直按下,或为弹起

消抖原理如下:

首先判断按键是否按下(出现下降沿)

[email protected](posedge clk or negedge rst_n)   //key状态读取模块
begin
    if(!rst_n)
    begin
	key_state <= 1;
	key_state_pre <= 1;
    end
		
    else
    begin
	key_state <= key;  //key_state为当前key值
	key_state_pre <= key_state;   //key_state_pre为上一个周期key值
    end
end
assign key_negedge = key_state_pre & (~key_state);

注:此模块中key_state和key_state_pre寄存器采用非阻塞赋值,特别应注意key_state_pre的值并不是C语言中顺序结构的赋值方式,而是与上一句并行的赋值,即此式的值为上一个时钟周期时的key_state的值

【重拾FPGA】由按键消抖观察到的阻塞赋值和非阻塞赋值的区别

在上升沿1时,key_state变为0,若按照顺序结构赋值,此时key_state_pre应该也为0,但是因为采用的是非阻塞赋值,此时的值为执行key_state <= key之前的值,即上一个时钟周期的key_state值:1

在上升沿2时,key_state没有改变,而key_state_pre要等于上一个时钟周期的key_state值:0

 

当key_negedge变为1时,即key_state为0,而key_state_pre为1,也就是出现了下降沿的时候,计数器清零并开始计数,若出现抖动,也就是反复出现下降沿,则计数器会反复清零,直至稳定没有下降沿为止,开始稳定计数。

[email protected](posedge clk or negedge rst_n)  //计数延时模块
begin
    if(!rst_n)
	cnt <= 4'd0;
    else if(key_negedge)  //当按键按下或抖动产生下降沿时清零
	cnt <= 4'd0;
    else if(cnt == 4'd10)
	cnt <= 4'd0;
    else
	cnt <= cnt + 1'b1;
end

当计数器好不容易稳定计数到最大值后,还需要判断是否是按键按下,因为在按键没按下时,计数器仍能稳定计数到最大值,判断按键是否按下,可根据判断计数值满后的key的状态是否为低电平(按下)

[email protected](posedge clk or negedge rst_n)  //开始消抖判断
begin
    if(!rst_n)
    begin
	key_delay_state <= 1;
	key_delay_state_pre <= 1;
	key_pulse <= 0;
    end
    else if(cnt == 4'd10 || key_pulse == 1)
    begin
	key_delay_state_pre = key_delay_state;
	key_delay_state = key;
	key_pulse = (key_delay_state_pre) & (~key_delay_state);
    end
end

为什么else if里面的条件有一个key_pulse == 1呢,是为了让最终输出key_pulse 只持续一个时钟周期,否则它会持续知道下一次计数器满。

在这里面key_delay_state_pre和 key_delay_state便采用了阻塞赋值,赋值方法便和C语言一样,从上到下依次进行,也就是key_delay_state_pre 是当前周期时key_delay_state的值。

为什么要用阻塞赋值?

首先看一下采用非阻塞赋值时的结果:

key_delay_state_pre <= key_delay_state;
key_delay_state <= key;
key_pulse <= (key_delay_state_pre) & (~key_delay_state);

【重拾FPGA】由按键消抖观察到的阻塞赋值和非阻塞赋值的区别

 在上升沿1的时候,抖动结束,按照我们的设计逻辑,应该在上升沿2的时候产生一个输出信号,但是却又计数了10次后在3的时候才产生一个输出信号。这是因为采用的是非阻塞赋值,所以在2的时候,虽然key_delay_state为0,但key_delay_state_pre为key_delay_state上一个周期的值,即1,此时,因为key_pulse <= (key_delay_state_pre) & (~key_delay_state),因为赋值都是非阻塞,所以参与逻辑运算的变量值为上一个时钟周期的值!key_delay_state_pre=1,key_delay_state=1!因此没有输出,要等到下一个计数满才会重新判断!

下一个计数满时,因为key_pulse <= (key_delay_state_pre) & (~key_delay_state),因为赋值都是非阻塞,所以参与逻辑运算的变量值为上一个时钟周期的值!key_delay_state_pre=1,key_delay_state=0!有输出

 

采用阻塞赋值时:

key_delay_state_pre = key_delay_state;
key_delay_state = key;
key_pulse = (key_delay_state_pre) & (~key_delay_state);

注意赋值顺序,仿真结果为:

【重拾FPGA】由按键消抖观察到的阻塞赋值和非阻塞赋值的区别

可见,在抖动消失后的十个计数值后,便有了输出,这是因为采用阻塞赋值,程序便会顺序执行,于是便可以利用C语言的逻辑来判断输出值了。

在2处,key_delay_state_pre首先等于key_delay_state等于1,然后再让key_delay_state等于当前key状态0,因为key_pulse = (key_delay_state_pre) & (~key_delay_state),因为赋值都是阻塞,顺序赋值,所以参与逻辑运算的变量值为该时钟周期的值!key_delay_state_pre=1,key_delay_state=0!有输出