A debounce circuit filters the noisy signal from a mechanical button or switch so that one press produces exactly one clean transition. In Verilog, a debouncer synchronizes the raw input to the clock, then changes its output only after the input has stayed at a new level for a fixed time, typically a few milliseconds.

12 min read | Beginner Level

What You'll Learn

  • Why mechanical contacts bounce, and what bouncing does to digital logic
  • Write a counter-based debouncer with an input synchronizer
  • Write a shift register debouncer and compare the two approaches
  • Size the debounce time for real hardware and test the debouncer efficiently in simulation

What is debouncing ?

Mechanical buttons and switches do not make a clean transition from 0 to 1. When the contacts close, they bounce against each other, making and breaking contact many times before settling. A typical bounce lasts about 5 to 20 ms, which is hundreds of thousands of cycles of a 50 MHz clock.

Timing diagram: an ideal press rises once, the raw button_in bounces several times before settling, and button_out rises once, a debounce time after the input settles

Digital logic sees every bounce as a separate edge. Without debouncing, one press can register as several presses: a counter increments several times, a state machine skips states, or an action runs repeatedly. Debouncing replaces the burst of edges with a single clean change.

Counter Based Debouncer

The most common and reliable approach waits until the input has been stable for a set time before passing it on:

Counter-based debouncer block diagram: button_in passes through two synchronizer flip-flops, a comparator checks it against button_out, a counter fires at COUNTER_MAX and loads the output register

Key Components

  • Input synchronizer: two flip-flops in series. The button is not synchronized to the clock and can change just as a flip-flop samples it, which can make that flip-flop metastable. The second flip-flop gives the value time to settle.
  • Counter logic: compares the synchronized input with the current output. While they differ, it counts clock cycles. If the input changes back before the count reaches the threshold, the counter restarts. Only when the input has been stable for the whole debounce period does the output change.
  • Output register: holds the debounced value, which changes only after the debounce period has passed.

Verilog Code


module debouncer #(
    parameter CLK_FREQ = 50_000_000,    // Clock frequency in Hz
    parameter DEBOUNCE_TIME_MS = 20     // Debounce time in milliseconds
)(
    input wire clk,           // System clock
    input wire rst_n,         // Active low reset
    input wire button_in,     // Raw button input (noisy)
    output reg button_out     // Debounced button output
);

    // Calculate counter value for debounce time
    localparam COUNTER_MAX = (CLK_FREQ / 1000) * DEBOUNCE_TIME_MS;
    localparam COUNTER_WIDTH = $clog2(COUNTER_MAX + 1);

    // Internal registers
    reg [COUNTER_WIDTH-1:0] counter;
    reg button_sync_0, button_sync_1;

    // Double-flop synchronizer to avoid metastability
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            button_sync_0 <= 1'b0;
            button_sync_1 <= 1'b0;
        end else begin
            button_sync_0 <= button_in;
            button_sync_1 <= button_sync_0;
        end
    end

    // Debounce logic
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            counter <= 0;
            button_out <= 1'b0;
        end else begin
            if (button_sync_1 != button_out) begin
                // Input differs from output, start/continue counting
                counter <= counter + 1;
                if (counter >= COUNTER_MAX) begin
                    button_out <= button_sync_1;
                    counter <= 0;
                end
            end else begin
                // Input matches output, reset counter
                counter <= 0;
            end
        end
    end

endmodule

With the default parameters, COUNTER_MAX is 50,000 x 20 = 1,000,000 cycles, which is 20 ms at 50 MHz. Every bounce makes the synchronized input match the output again for a moment, which resets the counter, so the count only completes once the bouncing has stopped for a full 20 ms.

Synthesis result: The tool builds two synchronizer flip-flops, a COUNTER_WIDTH-bit counter (20 bits for the default values), a comparator for counter >= COUNTER_MAX, an XOR that detects button_sync_1 != button_out, and the output flip-flop. The constant COUNTER_MAX is computed during elaboration and costs no logic.

Quick Check

Q: For a 100 MHz clock and a 10 ms debounce time, what are COUNTER_MAX and COUNTER_WIDTH?

Show Answer

COUNTER_MAX = (100,000,000 / 1000) x 10 = 1,000,000, and COUNTER_WIDTH = $clog2(1,000,001) = 20 bits. The same width as the default, because 100 MHz for 10 ms is the same number of cycles as 50 MHz for 20 ms.

Where are Debouncers Used?

Push buttons on development boards, appliances and industrial control panels all bounce. Every button that a design reads as an event, such as a reset button, a mode select or a menu key, passes through a debouncer, usually followed by an edge detector that turns each clean press into a single one-cycle pulse. See Verilog Positive Edge Detector.

Shift Register Debouncer

This approach samples the synchronized button at a slow, fixed rate, for example once per millisecond, and shifts each sample into an 8-bit register. The output goes high only when all eight samples are 1, goes low only when all eight are 0, and otherwise keeps its previous value.

Shift register debouncer block diagram: a sample counter produces sample_tick, which shifts button_sync_1 into an 8-bit register; all-ones and all-zeros detectors set or clear the output register

Advantages:

  • Predictable timing: the output needs exactly 8 matching samples to change
  • Can detect stuck-at faults: a register that never fills with 1s or 0s shows a contact that is not switching cleanly
  • Slightly lower resource usage when one sample tick is shared by many buttons

Trade-offs:

  • Needs a sample-rate generator
  • Very short glitches between samples are not seen at all, which is usually desirable but means fast signals cannot be debounced this way
  • Less intuitive to tune than a single debounce time

Verilog Code


module debouncer_shift #(
    parameter CLK_FREQ = 50_000_000,    // Clock frequency in Hz
    parameter SAMPLE_RATE_MS = 1        // Sample rate in milliseconds
)(
    input wire clk,           // System clock
    input wire rst_n,         // Active low reset
    input wire button_in,     // Raw button input
    output reg button_out     // Debounced output
);

    // Calculate sampling period
    localparam SAMPLE_PERIOD = (CLK_FREQ / 1000) * SAMPLE_RATE_MS;
    localparam SAMPLE_WIDTH = $clog2(SAMPLE_PERIOD + 1);

    reg [SAMPLE_WIDTH-1:0] sample_counter;
    reg sample_tick;
    reg [7:0] shift_reg;  // 8-bit shift register
    reg button_sync_0, button_sync_1;

    // Synchronizer
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            button_sync_0 <= 1'b0;
            button_sync_1 <= 1'b0;
        end else begin
            button_sync_0 <= button_in;
            button_sync_1 <= button_sync_0;
        end
    end

    // Sample rate generator: one-cycle tick every SAMPLE_PERIOD clocks
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sample_counter <= 0;
            sample_tick <= 0;
        end else begin
            if (sample_counter >= SAMPLE_PERIOD - 1) begin
                sample_counter <= 0;
                sample_tick <= 1;
            end else begin
                sample_counter <= sample_counter + 1;
                sample_tick <= 0;
            end
        end
    end

    // Shift register debouncer
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            shift_reg <= 8'h00;
            button_out <= 1'b0;
        end else if (sample_tick) begin
            shift_reg <= {shift_reg[6:0], button_sync_1};

            // Output is high if all bits are 1, low if all bits are 0
            if (shift_reg == 8'hFF)
                button_out <= 1'b1;
            else if (shift_reg == 8'h00)
                button_out <= 1'b0;
        end
    end

endmodule

The sample-rate generator is a Mod-N counter that produces a one-cycle sample_tick, and the sampled values move through an 8-bit shift register. The comparison uses the register value from before the current sample is shifted in, so the output changes on the tick after the eighth matching sample.

Synthesis result: Two synchronizer flip-flops, a 16-bit sample counter for the default values (50,000 cycles per millisecond), an 8-bit shift register, and two 8-input comparisons (an AND for all-ones and a NOR for all-zeros) feeding the output flip-flop.

Counter basedShift register
Stability checkEvery clock cycleOnce per sample tick
Response timeExactly the debounce time after bouncing stops8 to 9 sample periods after bouncing stops
Main storageOne wide counter per button8-bit register per button, one shared tick counter
Best forA single button, simple tuningMany buttons sharing one sample tick

Quick Check

Q: With SAMPLE_RATE_MS = 2, about how long must the button be stable before button_out changes?

Show Answer

About 16 to 18 ms. The shift register needs eight consecutive matching samples, taken 2 ms apart, and the output updates on the tick after the register has filled. That should be longer than the bounce time of the switch.

Rotary Encoder Knobs

Volume and menu knobs on audio equipment and instruments are often mechanical rotary encoders with two switch contacts. Both contacts bounce as the knob turns, and without debouncing the decoding logic counts extra steps or even steps in the wrong direction. Each contact is debounced before the two signals are decoded into direction and count.

Testbench

A debounce time of several milliseconds is millions of clock cycles at a typical clock frequency, which makes simulation slow. The testbench therefore uses a 1 MHz clock and a 1 ms debounce time, so COUNTER_MAX is only 1000 cycles. A task simulates a press or release by bouncing the input randomly for a while before settling at the final level, then holds that level for longer than the debounce time.


`timescale 1ns/1ps

module debouncer_tb;
    reg clk, rst_n;
    reg button_in;
    wire button_out;
    integer i;

    debouncer #(
        .CLK_FREQ(1_000_000),     // Must match the clock generated below
        .DEBOUNCE_TIME_MS(1)      // 1 ms = 1000 clock cycles
    ) dut (
        .clk(clk),
        .rst_n(rst_n),
        .button_in(button_in),
        .button_out(button_out)
    );

    always #500 clk = ~clk;       // 1 us period = 1 MHz

    // Bounce the input randomly, then settle at final_level
    task bounce_to (input final_level);
        reg [3:0] delay;
        begin
            for (i = 0; i <= 10; i = i + 1) begin
                button_in = $random;
                delay = $random;
                #(delay * 5_000);     // 0 to 75 us per bounce
            end
            button_in = final_level;
        end
    endtask

    initial begin
        rst_n = 0;
        clk = 0;
        button_in = 0;
        #5_000 rst_n = 1;

        bounce_to(1);             // Press
        #3_000_000;               // Hold for 3 ms, longer than the debounce time
        bounce_to(0);             // Release
        #3_000_000;
        bounce_to(1);             // Press again
        #3_000_000;

        $finish;
    end

    initial begin
        $monitor("Time=%0t button_in=%b button_out=%b", $time, button_in, button_out);
    end
endmodule

In the output, button_in changes many times during each bounce burst, while button_out changes only once per press or release, about 1 ms after the input settles. Each burst of 11 bounces lasts at most 825 us, less than the 1 ms debounce time, so no part of a burst can reach the threshold, however the random levels fall.

Limit Switches and Relay Contacts

Industrial controllers read limit switches on moving machinery and the auxiliary contacts of relays and contactors. These contacts bounce like buttons, and a false edge can stop a conveyor or trigger an alarm. Controllers debounce each input, often with longer times than for push buttons, because heavy contacts can bounce for tens of milliseconds.

Common Beginner Mistakes

Mistake #1: Skipping the synchronizer

Wrong: if (button_in != button_out) counter <= counter + 1; using the raw input
Right: Pass button_in through two flip-flops and use button_sync_1

Why? The button can change at any time relative to the clock. The flip-flops in the counter logic that sample it directly can go metastable, and different flip-flops can see different values in the same cycle. Debouncing removes bounce, but only the synchronizer makes the signal safe to sample.

Mistake #2: Debounce time shorter than the bounce

Wrong: DEBOUNCE_TIME_MS = 1 for a switch that bounces for 10 ms
Right: Choose a debounce time longer than the worst-case bounce, typically 10 to 20 ms for push buttons

Why? A gap between bounces that lasts longer than the debounce time passes as a real change, so one press can still produce two or more output transitions. Too long a time makes the button feel slow, so check the switch's datasheet or measure its bounce.

Mistake #3: CLK_FREQ not matching the actual clock

Wrong: .CLK_FREQ(1_000_000) while the testbench or board clock runs at 100 MHz
Right: Set CLK_FREQ to the frequency of the clock actually connected to clk

Why? The debounce time is calculated from CLK_FREQ. If the real clock is 100 times faster, the debounce time is 100 times shorter than intended, and bouncing passes straight through. In simulation, a mismatch makes the expected and observed timing disagree.

Mistake #4: Testbench pulses shorter than the debounce time

Wrong: Holding the button for 1000 ns in a testbench where the debounce time is 5 ms
Right: Hold each level for longer than the debounce time, and shrink the debounce time for simulation

Why? If the input never stays stable for the full debounce period, button_out never changes, and the test shows nothing. Reducing CLK_FREQ and DEBOUNCE_TIME_MS in the testbench keeps the number of cycles small, so the simulation runs fast while the logic is unchanged.

Practice Exercise

Challenge: Debounced Press Pulse

Write a module button_press that instantiates debouncer and adds an output pressed, a pulse exactly one clock cycle wide each time the debounced button goes from 0 to 1. Holding the button down must produce only one pulse.

Hint

Keep a copy of button_out delayed by one clock in a register. A rising edge is the cycle in which the current value is 1 and the delayed value is 0.

Solution

module button_press #(
    parameter CLK_FREQ = 50_000_000,
    parameter DEBOUNCE_TIME_MS = 20
)(
    input  wire clk,
    input  wire rst_n,
    input  wire button_in,     // Raw, bouncy button
    output wire pressed        // One-cycle pulse per press
);

    wire btn_db;               // Debounced level
    reg  btn_db_dly;           // Debounced level, one clock older

    debouncer #(
        .CLK_FREQ(CLK_FREQ),
        .DEBOUNCE_TIME_MS(DEBOUNCE_TIME_MS)
    ) u_db (
        .clk(clk), .rst_n(rst_n),
        .button_in(button_in), .button_out(btn_db)
    );

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) btn_db_dly <= 1'b0;
        else        btn_db_dly <= btn_db;
    end

    // Rising edge of the debounced signal
    assign pressed = btn_db & ~btn_db_dly;

endmodule

Both inputs of the AND come from flip-flops on the same clock, so pressed is high for exactly one cycle per press, however long the button is held. A counter or state machine can now use pressed as an enable and advance once per press.

Summary

In this tutorial, you learned:

  • Mechanical contacts bounce for several milliseconds, which logic sees as many separate edges
  • Every button input needs a two-flip-flop synchronizer before any other logic uses it
  • A counter-based debouncer changes its output only after the input has been stable for the full debounce time
  • A shift register debouncer samples slowly and changes its output after eight matching samples
  • The debounce time must be longer than the switch's bounce, and CLK_FREQ must match the real clock
  • Testbenches should scale the debounce time down so the simulation runs in a reasonable time
  • A debouncer followed by an edge detector turns each press into a single one-cycle pulse
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5