In this post we implement a signal debouncer module for the mechanical inputs of our ZedBoard Audio Processor. The Debouncer is the first of the four major components in our ZedBoard Audio Processor design’s top level, as mentioned in a previous post.
Bouncing Inputs
When working with mechanical inputs like buttons and switches to our FPGA designs, we need to be aware of the issue of bouncing. When activated, a switch or button will not cleanly transition from high to low or low to high. Instead, it will bounce between the two states for some time before settling into the one the user has selected. Though this bouncing is too fast to be perceived by human operators, it can be easily detected by high-speed digital circuits. The logic in our FPGA will identify this bouncing as intentional inputs and might react in unexpected ways.
Debouncing Logic
The solution to this problem is to use a debouncing logic that a) detects a change in the physical inputs to the FPGA pins, b) waits until the bouncing period is finished, and the input signal has settled into its correct value, and c) generates an output with the same value as the stable input signal. We can then use the output of the debouncing logic as the input to our system.
Debounce FSM
One way to debounce signals is by using a 4-state Finite State Machine (FSM), which works as follows:
- The IDLE state is reached after power-up. Here the FSM resets the counter and the signal output, and then transitions to the EDGE_DETECTED state.
- The EDGE_DETECTED state is reached after the IDLE state at power-up or whenever the output of the debouncing logic is different than its input. In the EDGE_DETECTED state the FSM counts to a programmable number of clock cycles, after which the input signal is assumed to be stable and the FSM transitions to the corresponding state (DEBOUNCED_HIGH/LOW). The bouncing period can vary depending on the type of mechanical element and the analog circuitry used to interface with the FPGA inputs, 10 – 20 ms seems to be the accepted rule of thumb.
- The DEBOUNCED_HIGH/LOW state is reached after the input signal has settled in a high/low state, here the output of the debouncing logic is set to the value to which the input signal has settled. The FSM remains in the DEBOUNCED_HIGH/LOW state until the input signal changes, in which case it will transition to the EDGE_DETECTED state.
The Debounce FSM module also includes clock-domain-crossing logic for synchronizing the switch and button inputs to the clock domain in our design, thus avoiding metastability issues. The RTL description of the Debounce FSM is shown below.
module debounce_fsm # (
parameter integer DEBOUNCE_COUNTER_WIDTH = 16
) (
// Clock
input logic i_clock,
// Debounce counter
input logic [DEBOUNCE_COUNTER_WIDTH-1 : 0] i_debounce_counter,
// Bouncing signal
input logic i_bouncing_signal,
// Debounced signal
output logic o_debounced_signal
);
timeunit 1ns;
timeprecision 1ps;
logic bouncing_signal_meta;
logic bouncing_signal_stable;
logic [DEBOUNCE_COUNTER_WIDTH-1 : 0] debounce_counter;
// FSM States Definition
enum logic [1:0] { IDLE,
EDGE_DETECTED,
DEBOUNCED_HIGH,
DEBOUNCED_LOW } fsm_state = IDLE;
// Main FSM
always_ff @(posedge i_clock) begin
bouncing_signal_meta <= i_bouncing_signal;
bouncing_signal_stable <= bouncing_signal_meta;
case (fsm_state)
IDLE : begin
o_debounced_signal <= 1\'b0;
debounce_counter <= \'b0;
fsm_state <= EDGE_DETECTED;
end
EDGE_DETECTED : begin
debounce_counter <= debounce_counter + 1;
if (debounce_counter == i_debounce_counter) begin
debounce_counter <= \'b0;
if (bouncing_signal_stable == 1\'b1) begin
fsm_state <= DEBOUNCED_HIGH;
end else begin
fsm_state <= DEBOUNCED_LOW;
end
end
end
DEBOUNCED_HIGH : begin
o_debounced_signal <= 1\'b1;
if (bouncing_signal_stable == 1\'b0) begin
fsm_state <= EDGE_DETECTED;
end
end
DEBOUNCED_LOW : begin
o_debounced_signal <= 1\'b0;
if (bouncing_signal_stable == 1\'b1) begin
fsm_state <= EDGE_DETECTED;
end
end
default : begin
fsm_state <= IDLE;
end
endcase
end // always_ff
endmodule
‘Debouncer ZedBoard’ Module
The Debounce FSM is a generic, parameterizable core that can be used for debouncing the inputs of any FPGA on any development or custom board. It makes sense to create a hierarchical level that instantiates as many Debounce FSM cores as required for a specific hardware and connects them properly. That is the job of the Zed Debouncer module.
The Debouncer ZedBoard module receives the signals from the physical switches and buttons on the ZedBoard and generates a debounced version of each of them, which can then be used as inputs to our logic. Though the number of switches and buttons on the ZedBoard is fixed, the number of Debounce FSM instances remains parameterizable, which maximizes reusability for other target hardware. The number of switches and buttons can be set independently, and so can the width of the Debounce Counter, in case they need to be different. The Zed Debouncer module is shown below.
module debouncer_zedboard # (
parameter SWITCH_COUNT = 8,
parameter BUTTON_COUNT = 5,
parameter DEBOUNCE_COUNTER_WIDTH = 16
) (
// Clock
input logic i_clock,
// Debounce counter values
input logic [DEBOUNCE_COUNTER_WIDTH-1 : 0] i_switch_debounce_counter,
input logic [DEBOUNCE_COUNTER_WIDTH-1 : 0] i_button_debounce_counter,
// Input switches
input logic i_sw0,
input logic i_sw1,
input logic i_sw2,
input logic i_sw3,
input logic i_sw4,
input logic i_sw5,
input logic i_sw6,
input logic i_sw7,
// Input buttons
input logic i_btnu,
input logic i_btnd,
input logic i_btnl,
input logic i_btnr,
input logic i_btnc,
// Debounced switch outputs
output logic o_sw0,
output logic o_sw1,
output logic o_sw2,
output logic o_sw3,
output logic o_sw4,
output logic o_sw5,
output logic o_sw6,
output logic o_sw7,
// Debounced button outputs
output logic o_btnu,
output logic o_btnd,
output logic o_btnl,
output logic o_btnr,
output logic o_btnc
);
timeunit 1ns;
timeprecision 1ps;
logic [SWITCH_COUNT-1 : 0] bouncing_switch_array;
logic [SWITCH_COUNT-1 : 0] debounced_switch_array;
logic [BUTTON_COUNT-1 : 0] bouncing_button_array;
logic [BUTTON_COUNT-1 : 0] debounced_button_array;
assign bouncing_switch_array = {i_sw7, i_sw6, i_sw5, i_sw4, i_sw3, i_sw2, i_sw1, i_sw0};
assign debounced_switch_array = {o_sw7, o_sw6, o_sw5, o_sw4, o_sw3, o_sw2, o_sw1, o_sw0};
assign bouncing_button_array = {i_btnu, i_btnd, i_btnl, i_btnr, i_btnc};
assign debounced_button_array = {o_btnu, o_btnd, o_btnl, o_btnr, o_btnc};
genvar i;
generate
for (i = 0; i < SWITCH_COUNT; i++) begin : switch_debounce_fsm_gen
debounce_fsm
# (
.DEBOUNCE_COUNTER_WIDTH (DEBOUNCE_COUNTER_WIDTH)
)
switch_debounce_fsm_inst (
.i_clock (i_clock),
.i_debounce_counter (i_switch_debounce_counter),
.i_bouncing_signal (bouncing_switch_array[i]),
.o_debounced_signal (debounced_switch_array[i])
);
end
endgenerate
genvar j;
generate
for (j = 0; j < BUTTON_COUNT; j++) begin : button_debounce_fsm_gen
debounce_fsm
# (
.DEBOUNCE_COUNTER_WIDTH (DEBOUNCE_COUNTER_WIDTH)
)
button_debounce_fsm_inst (
.i_clock (i_clock),
.i_debounce_counter (i_button_debounce_counter),
.i_bouncing_signal (bouncing_button_array[j]),
.o_debounced_signal (debounced_button_array[j])
);
end
endgenerate
endmodule
The figure below shows all the instances of the Debounce FSM within the Debouncer ZedBoard module in the elaborated design. The first one has been expanded to show its internal components.
In the next post we will discuss how to generate the Master Clock for the audio codec using the clocking and IO resources in the Zynq.
Cheers,
Isaac
UPDATE (1st April 2022): updated the Debounce FSM module to include the CDC synchronizer.