002 – ZedBoard Signal Debouncer


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.

Zedboard Debouncer

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.


Leave a Reply

Your email address will not be published. Required fields are marked *