Diseño y verificación antirrebote de clave FPGA
¿Por qué tenemos que eliminar el rebote de los botones?
La siguiente figura muestra la aplicación del botón en el circuito real. Cuando no hay ningún botón presionado, las teclas [2: 0] lo subirán a un nivel alto. Primero, hay un resorte de reacción en la estructura de hardware del Cuando se presiona o se suelta A veces, se generan vibraciones físicas adicionales, lo que hace que el nivel del botón tiemble simultáneamente.
Durante el proceso desde presionar hasta soltar la tecla, los cambios ideales y reales del nivel se muestran en la siguiente figura.
Puede verse que el número y el intervalo de la fluctuación se generan aleatoriamente. La única certeza es que, en circunstancias normales, el tiempo de presionar la fluctuación y liberar la fluctuación durará entre 5 ms ~ 10 ms.
Máquina de estado más contador para realizar el rebote de clave
Estado de transición de la máquina de estado
En el cambio real del nivel de señal del botón en la figura anterior, hay cuatro estados:
1. Período estable de alto nivel, el llamado estado inactivo;
2. Presione jitter. Este estado indica que durante el proceso de cambio de nivel alto a nivel bajo, después de que se detecta el borde descendente, el borde ascendente se detecta dentro de los 10 ms, luego se determina que el botón sigue temblando y vuelve al estado inactivo inactivo; llamado filter0 ;
3. En el período estable de nivel bajo, después de detectar el borde descendente, el nivel bajo es estable dentro de los 10 ms, y se determina que se presiona el botón, denominado como estado hacia abajo;
4. Suelte la fluctuación. Este estado indica que durante el proceso de cambio de nivel bajo a nivel alto, después de que se detecta el borde ascendente y descendente, el borde ascendente descendente se detecta dentro de los 10 ms, luego se considera que el botón todavía está fluctuando, y vuelve al estado inactivo; llamado filter1.
Procesamiento síncrono de señales asíncronas
¿Por qué el procesamiento síncrono de señales asíncronas?
Primero, la señal clave es una señal asíncrona para la señal interna de la FPGA (cuando una señal cruza un cierto dominio de reloj, es una señal asíncrona para el circuito del nuevo dominio de reloj, y el circuito que recibe la señal necesita sincronizarla ), Es decir, el estado de la señal del botón no depende del reloj del dispositivo FPGA. Si no se procesa, conducirá fácilmente a un estado metaestable (el estado de la señal no se puede predecir). El proceso de sincronización puede evitar que el estado metaestable se extendiéndose en el nuevo dominio de reloj.
Un sincronizador simple se compone de dos registros en serie sin ningún otro circuito combinacional en el medio. Como se muestra en la figura anterior, este diseño puede garantizar que cuando el siguiente flip-flop obtenga la salida del flip-flop anterior, el flip-flop anterior -flop ha salido del estado metaestable y la salida es estable.
Para este diseño experimental, esta parte es solo un proceso de sincronización para una señal de cambio de clave para evitar que la clave interfiera con otras señales en el nuevo dominio de reloj y no juzga si es un flanco ascendente o descendente.
Diseño de circuito de detección de bordes
¿Por qué la detección de bordes?
Primero, la parte de procesamiento de sincronización de señal asíncrona no juzga el borde ascendente y el borde descendente, que es otra señal importante para la transición de estado de la máquina de estado.
Por tanto, la función del circuito de detección de flanco es detectar la transición de la señal de entrada o la señal lógica interna, es decir, la detección del flanco ascendente o del flanco descendente.
¿Cómo entender el circuito de detección de bordes?
Sabemos que en la lista de señales sensibles del bloque always, puede usar directamente posedge y negedge para extraer directamente los bordes ascendentes y descendentes, pero ¿qué sucede si desea detectar los bordes ascendentes y descendentes dentro del bloque always? ¿Todavía usas posedge y negedge? Obviamente, esto no es posible. Es imposible sintetizar tal declaración, y se informará un error durante la compilación. De hecho, posedge y negedge solo se pueden usar en la lista de señales sensibles o en el banco de pruebas del bloque always, por lo que la detección de bordes Se genera una aplicación de circuito.
La figura anterior es un circuito de detección de bordes de un registro de dos niveles que se usa ampliamente. Generalmente, para evitar la fluctuación de la señal de disparo, puede agregar algunos niveles más de flip-flops para eliminar la fluctuación y hacer que la señal mas estable.
El principio de detección de flancos es comparar y juzgar utilizando la característica de que el estado de entrada es el estado de salida en el momento siguiente bajo el control de la señal de reloj del registro.
¿Cómo detecta el circuito anterior el flanco ascendente o descendente?
Detección de borde ascendente
1. Antes del flanco ascendente, la señal trigger_r ha mantenido un nivel bajo durante un período de tiempo, los registros trigger_rf y trigger_rs mantienen el nivel bajo de salida y una de las señales se somete a una operación AND (&) con la otra señal a través de la puerta NOT, pos_edge y Both neg_edge generan un nivel bajo.
2. Cuando la señal trigger_r cambia de 0 a 1, que es el flanco ascendente, cuando llega el primer flanco del reloj, la salida del primer registro trigger_rf es 1, y el segundo registro trigger_rs debe serlo cuando llega el segundo flanco del reloj. Salida 1, por lo que en el primer flanco del reloj, la salida 0 del segundo registro trigger_rs pasa a través de la puerta NOT y la salida 1 del primer registro trigger_rf realiza una operación Y (&), en este momento pos_edge genera un nivel alto, y neg_edge sigue siendo el mismo Nivel bajo de salida, por lo tanto, use pos_edge para detectar el flanco ascendente.
3. Cuando llega el segundo reloj de clk, trigger_r todavía mantiene un nivel alto en este momento, pero en este momento los registros trigger_rf y trigger_rs siguen emitiendo un nivel alto, lo que hace que pos_edge y neg_edge sigan emitiendo un nivel bajo, lo que explica cuándo hay Cuando el aumento edge llega, pos_edge también genera un nivel alto de un ciclo de reloj.Si no hay ningún cambio de flanco ascendente o descendente, tanto pos_edge como neg_edge permanecen bajos.
Detección de borde descendente
1. Antes del flanco ascendente, la señal trigger_r ha mantenido un nivel alto durante un período de tiempo, los registros trigger_rf y trigger_rs mantienen el nivel alto de salida y una de las señales se somete a una operación AND (&) con la otra señal a través de la puerta NOT, pos_edge y Both neg_edge generan un nivel bajo.
2. Cuando la señal trigger_r cambia de 1 a 0, es decir, el flanco descendente, cuando llega el primer flanco del reloj, la salida del primer registro trigger_rf es 0, y el segundo registro trigger_rs debe serlo cuando llega el segundo flanco del reloj. .Salida 0, por lo que en el primer flanco del reloj, la salida 0 del primer registro trigger_rf pasa a través de la puerta NOT y la salida 1 del segundo registro trigger_rs para la operación Y (&), en este momento neg_edge salidas de nivel alto, pos_edge permanece igual La salida es baja, por lo que neg_edge se usa para detectar el flanco descendente.
3. Cuando llega el segundo reloj de clk, trigger_r todavía se mantiene en un nivel bajo en este momento, pero en este momento los registros trigger_rf y trigger_rs mantienen la salida en un nivel bajo, lo que da como resultado que pos_edge y neg_edge todavía emitan un nivel bajo, lo que explica cuándo hay Cuando la caída Cuando llega el flanco ascendente, neg_edge también genera un nivel alto de un ciclo de reloj No hay cambio de flanco ascendente o descendente, por lo que tanto pos_edge como neg_edge permanecen bajos.
La idea lógica importante aquí es que no importa que el circuito de flanco detecte el flanco ascendente o el flanco descendente, generará un pulso de nivel alto después de detectar el flanco requerido. El flanco ascendente emite un nivel alto durante un ciclo de reloj en pos_edge, y el flanco descendente El flanco también genera un nivel alto durante un ciclo de reloj en neg_edge.
Módulo contador
Esta parte es un signo de juicio importante para la transición de estado de la máquina de estado. Debido a que el tiempo de fluctuación del botón está entre 5 ms y 10 ms, el contador es 10 ms. Cuando el contador está contando y el estado del botón es estable, se determina que el botón ingresa al período estable.
Parte del código Verilog
//--------------------------------------------------------------------------------------------
// 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
Prueba de incentivos y simulación
Esta parte continuará acumulando habilidades de escritura en el banco de pruebas e ideas de simulación en el aprendizaje de proyectos pequeños, y se agregará después de una comprensión profunda de los modelos de simulación del banco de pruebas.
Se adjunta un código de banco de pruebas simple como referencia solo para verificación
//--------------------------------------------------------------------------------------------
// 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