FPGA-key debounce design and verification
Why do we need to debounce the buttons?
The following figure shows the application of the button in the actual circuit. When there is no button pressed, the keys[2:0] will pull it up to a high level. First, there is a reaction spring in the hardware structure of the button. When pressed or released At times, additional physical vibrations are generated, causing the button level to jitter simultaneously.
During the process from pressing to releasing the key, the ideal and actual changes of the level are shown in the figure below.
It can be seen that the number and interval of the jitter are randomly generated. The only certainty is that under normal circumstances, the time of pressing the jitter and releasing the jitter will last within 5ms~10ms.
State machine plus counter to realize key debounce
State machine transition state
In the actual change of the button signal level in the above figure, there are four states:
1. High level stable period, the so-called idle state;
2. Press jitter. This state indicates that during the process of changing from high level to low level, after the falling edge is detected, the rising edge is detected within 10ms, then the button is determined to be still jittering and return to idle state idle; named filter0 ;
3. In the low-level stable period, after detecting the falling edge, the low-level is stable within 10ms, and it is determined that the button is pressed; it is named as the down state;
4. Release jitter. This state indicates that during the process of changing from low level to high level, after the rising and falling edge is detected, the falling rising edge is detected within 10ms, then the button is considered to be still jittering, and it returns to the down state; named filter1 .
Asynchronous signal synchronous processing
Why do asynchronous signal synchronous processing?
First, the key signal is an asynchronous signal for the internal signal of the FPGA (when a signal crosses a certain clock domain, it is an asynchronous signal for the circuit of the new clock domain, and the circuit receiving the signal needs to synchronize it), That is, the button signal state does not depend on the FPGA device clock. If it is not processed, it will easily lead to a metastable state (the signal state cannot be predicted). The synchronization process can prevent the metastable state from spreading in the new clock domain.
A simple synchronizer is made up of two registers in series with no other combinational circuit in between. As shown in the figure above, this design can ensure that when the following flip-flop obtains the output of the previous flip-flop, the previous flip-flop has exited metastable State, and the output is stable.
For this experimental design, this part is only a synchronization process for a key change signal to prevent the key from interfering with other signals in the new clock domain, and does not judge whether it is a rising edge or a falling edge.
Edge detection circuit design
Why do edge detection?
First, the asynchronous signal synchronization processing part does not judge the rising edge and the falling edge, which is another important signal for the state transition of the state machine.
Therefore, the function of the edge detection circuit is to detect the transition of the input signal or the internal logic signal, that is, the detection of the rising edge or the falling edge.
How to understand the edge detection circuit?
We know that in the sensitive signal list of the always block, you can directly use posedge and negedge to directly extract the rising and falling edges, but what if you want to detect the rising and falling edges inside the always block? Still use posedge and negedge? Obviously this is not possible. It is impossible to synthesize such a statement, and an error will be reported during compilation. In fact, posedge and negedge can only be used in the sensitive signal list or testbench of the always block, so the edge detection circuit application is generated.
The above figure is an edge detection circuit of a two-level register that is widely used. Generally, in order to prevent the fluctuation of the trigger signal, you can add a few more levels of flip-flops to eliminate jitter and make the signal more stable.
The principle of edge detection is to use the characteristic that the input state is the output state at the next moment of the register under the control of the clock signal for comparison and judgment.
How does the above circuit detect the rising or falling edge?
Rising edge detection
1. Before the rising edge, the trigger_r signal has maintained a low level for a period of time, the trigger_rf and trigger_rs registers both maintain the output low level, and one of the signals undergoes an AND (&) operation with the other signal through the NOT gate, pos_edge and Both neg_edge output low level.
2. When the trigger_r signal changes from 0 to 1, which is the rising edge, when the first clock edge arrives, the output of the first register trigger_rf is 1, and the second register trigger_rs needs to be when the second clock edge arrives. Output 1, so at the first clock edge, the output 0 of the second register trigger_rs passes through the NOT gate and the output 1 of the first register trigger_rf performs an AND (&) operation, at this time pos_edge outputs a high level, and neg_edge remains Output low level, therefore, use pos_edge to detect the rising edge.
3. When the second clock of clk comes, trigger_r still keeps high level at this time, but at this time trigger_rf and trigger_rs registers keep outputting high level, causing pos_edge and neg_edge to still output low level, which explains when there is When the rising edge arrives, pos_edge also generates a high level of one clock cycle. If there is no rising or falling edge change, both pos_edge and neg_edge remain low.
Falling edge detection
1. Before the rising edge, the trigger_r signal has maintained a high level for a period of time, the trigger_rf and trigger_rs registers both maintain the output high level, and one of the signals passes through the NOT gate and performs an AND (&) operation with the other signal, pos_edge and Both neg_edge output low level.
2. When the trigger_r signal changes from 1 to 0, that is, the falling edge, when the first clock edge arrives, the output of the first register trigger_rf is 0, and the second register trigger_rs needs to be when the second clock edge arrives. Output 0, so at the first clock edge, the output 0 of the first register trigger_rf passes through the NOT gate and the output 1 of the second register trigger_rs for the AND (&) operation, at this time neg_edge outputs high level, pos_edge remains the same The output is low, so neg_edge is used to detect the falling edge.
3. When the second clock of clk comes, trigger_r still remains low at this time, but at this time trigger_rf and trigger_rs registers keep output low, resulting in pos_edge and neg_edge still output low, which explains when there is When the falling rising edge arrives, neg_edge also generates a high level of one clock cycle. There is no rising or falling edge change, so both pos_edge and neg_edge remain low.
The important logical idea here is that no matter the edge circuit detects a rising edge or a falling edge, it will generate a high-level pulse after detecting the required edge, and the rising edge outputs a high level for one clock cycle at pos_edge, and the falling edge The edge also outputs a high level for one clock cycle at neg_edge.
Counter module
This part is an important judgment sign for the state transition of the state machine. Because the jitter time of the button is between 5ms and 10ms, the counter is 10ms. When the counter is counting and the button state is stable, it is determined that the button enters the stable period.
Verilog code part
//--------------------------------------------------------------------------------------------
// Component name : key_filter
// Author : 硬件嘟嘟嘟
// time : 2020.04.17
// Description : 按键消抖设计
// src : FPGA系统设计与验证实战指南_V1.2
//--------------------------------------------------------------------------------------------
module key_filter(
clk, //50m时钟
rst_n, //复位信号
key_in, //按键输入
key_flag, //按键状态切换标志
key_state //按键状态
);
input clk,rst_n;
input key_in;
output reg key_flag,key_state;
//对key_in作异步信号同步处理
reg key_in_a,key_in_b; //定义两个寄存器
always@(posedge clk,negedge rst_n)
if(!rst_n)begin
key_in_a <= 1'b1;
key_in_b <= 1'b1;
end
else begin //input : key_in -->output :key_in_b
key_in_a <= key_in;
key_in_b <=key_in_a;
end
//对key_in_b(异步信号同步处理输出)进行边沿检测
reg trigger_rf,trigger_rs;
wire pos_edge,neg_edge;
always@(posedge clk,negedge rst_n)
if(!rst_n) begin
trigger_rf <= 1'b0;
trigger_rs <= 1'b0;
end
else begin
trigger_rf <= key_in_b;
trigger_rs <= trigger_rf;
end
assign pos_edge = trigger_rf & (!trigger_rs);
assign neg_edge = (!trigger_rf) & trigger_rs;
//定义一个10ms计数器
reg [18:0]cnt; //10_000_000 ns /20ns =500000
reg en_cnt ; //定义使能寄存器,要让在按键抖动器件才开始计数
//使能信号产生,计数器开始计数
always@(posedge clk,negedge rst_n)
if(!rst_n)
cnt <= 19'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 19'd0;
// 计数器计满10ms后产生计满标志
reg cnt_full; //计满标志
always@(posedge clk,negedge rst_n)
if(!rst_n)
cnt_full <= 1'b0;
else if(cnt == 19'd499_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
//状态机状态转移
//localparam定义四种状态
localparam key_idle = 3'd1,
key_filter0 = 3'd2,
key_down = 3'd3,
key_filter1 = 3'd4;
reg [2:0] current_state; //定义当前态
//描述状态转移及输出
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
en_cnt <= 1'b0;
current_state <= key_idle;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(current_state)
key_idle :
begin
key_flag <= 1'b0;
if(neg_edge)begin
current_state <= key_filter0;
en_cnt <= 1'b1;
end
else
current_state <= key_idle;
end
key_filter0:
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
en_cnt <= 1'b0;
current_state <= key_down;
end
else if(pos_edge)begin
current_state <= key_idle;
en_cnt <= 1'b0;
end
else
current_state <= key_filter0;
key_down:
begin
key_flag <= 1'b0;
if(pos_edge)begin
current_state <= key_filter1;
en_cnt <= 1'b1;
end
else
current_state <= key_down;
end
key_filter1:
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
current_state <= key_idle;
en_cnt <= 1'b0;
end
else if(neg_edge)begin
en_cnt <= 1'b0;
current_state <= key_down;
end
else
current_state <= key_filter1;
default:
begin
current_state <= key_idle;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
endmodule
Incentive and simulation test
This part will continue to accumulate testbench writing skills and simulation ideas in learning small projects, and will be added after in-depth understanding of testbench simulation models.
A simple testbench code is attached for reference only for verification
//--------------------------------------------------------------------------------------------
// src : FPGA系统设计与验证实战指南_V1.2
//--------------------------------------------------------------------------------------------
`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);
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;
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;
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;
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;
$stop;
end
endmodule