Input

Button debouncer

debounce

The input passes a two-flop synchronizer; the output follows it only after it has held one value for STABLE_CYCLES clock cycles, so contact bounce and short glitches are ignored. rise and fall pulse for one cycle when the output changes.

Verified by Tapeo97 generic cells at the defaultsbutton inputswitch input

Instantiate it

Paste into your design and connect the signals.

systemverilog
debounce #(.STABLE_CYCLES(1000000)) u_button (
  .clk(clk), .rst(rst),
  .noisy(button_pin), .clean(button), .rise(button_pressed), .fall(button_released)
);

Parameters

NameDefaultMeaning
STABLE_CYCLES1000000Cycles the input must hold before the output changes (1,000,000 is 10 ms at 100 MHz).

Ports

NameDirectionWidthMeaning
clkinput1Clock; everything changes on its rising edge.
rstinput1Synchronous reset, active high.
noisyinput1Button or switch input (asynchronous).
cleanoutput1Debounced level.
riseoutput1Pulses when clean goes high (a press).
falloutput1Pulses when clean goes low (a release).

Add it to a project

It is copied to ip/debounce/debounce.sv with a README (ip/debounce/README.md). When your design needs it, the Architect (new designs) or the Architect (changes) suggests it and asks before using it. Or add it yourself:

How it is verified

Before every release Tapeo checks this block: its self-checking testbench passes in Icarus Verilog, Verilator -Wall reports nothing, and Yosys synthesizes it (97 generic cells at the default parameters). That shows it works for the tested cases; your copy is part of your project, and your own testbench checks it in your design.

Source

debounce.sv
// Button or switch debouncer. The input passes a two-flop synchronizer; the
// output follows it only after it has held one value for STABLE_CYCLES clock
// cycles (for example 1,000,000 for 10 ms at 100 MHz). `rise` and `fall`
// pulse for one cycle when the output changes. Tapeo Hub (verified by Tapeo).
module debounce #(
  parameter integer STABLE_CYCLES = 1000000
) (
  input  logic clk,
  input  logic rst,
  input  logic noisy,
  output logic clean,
  output logic rise,
  output logic fall
);
  localparam integer COUNT_WIDTH = (STABLE_CYCLES > 1) ? $clog2(STABLE_CYCLES) : 1;
  localparam logic [COUNT_WIDTH-1:0] LAST_TICK = COUNT_WIDTH'(STABLE_CYCLES - 1);

  logic [1:0] sync;
  logic [COUNT_WIDTH-1:0] stable;

  always_ff @(posedge clk) begin
    if (rst) sync <= 2'b00;
    else sync <= {sync[0], noisy};
  end

  always_ff @(posedge clk) begin
    if (rst) begin
      stable <= '0;
      clean <= 1'b0;
      rise <= 1'b0;
      fall <= 1'b0;
    end else begin
      rise <= 1'b0;
      fall <= 1'b0;
      if (sync[1] == clean) stable <= '0;
      else if (stable == LAST_TICK) begin
        stable <= '0;
        clean <= sync[1];
        rise <= sync[1];
        fall <= !sync[1];
      end else stable <= stable + 1'b1;
    end
  end
endmodule

← Every block in Tapeo Hub