Synchronize, debounce, detect

Tell apart the three small circuits that sit behind a slow input, put them in the right order, and count one press for one bouncing button.

Lesson
R2.5
Time
25 min
Runs on
Your browser
Check
5 questions

A button press was counted three times. The input had a two-flop synchronizer and an edge detector, and the engineer believed the synchronizer cleaned the signal. It did not: the contact bounced, the synchronizer passed three clean pulses, and the edge detector counted every one.

One bouncing press through the synchronizer and the edge detectorEdges 3 to 11 of the lab bench. btn is the pin, s2 the synchronizer output, rise the edge detector, count the presses counted.
012345678clkbtns2risecount0123
  1. Cycle 1

    Edge 4: the contact has closed and s2 shows it. s2 is 1 and was 0, so rise is 1.

  2. Cycle 4

    Edge 7: the contact bounced open for one cycle and closed again. The synchronizer passed both changes, and rise is 1 a second time.

  3. Cycle 7

    Edge 10: a two-cycle bounce, then the contact stays closed. rise fires a third time.

  4. Cycle 8

    Edge 11: one press, three counts. Every pulse out of the synchronizer was clean and correctly timed.

Measured on the lab bench. s2 repeats btn one row later: the first flop takes it at the edge of that row, the second at the next edge.

Three jobs, three circuits

All three circuits are a few flops in a row, which is why they get confused. Each removes one thing, and only that.

From the pin to one pulse per pressArrows carry the signal from the pin to the counter. Each block names what it removes.
From the pin to one pulse per pressArrows carry the signal from the pin to the counter. Each block names what it removes. input btn pin no timing to clk;bounces block Synchronizer 2 flops: removestiming uncertainty block Debouncer counter: removesshort pulses block Edge detector 1 flop and a gate:level to pulse block Press counter adds 1 per pulse btns2levelrise
  1. Synchronize

    Two flops in series. If the first flop samples btn as it changes it may go metastable, hovering between 0 and 1; it has a cycle to settle before anything reads it. Every pulse still gets through.

  2. Debounce

    The new level counts only after s2 has stayed at it for STABLE cycles in a row. A bounce shorter than that never reaches level.

  3. Detect

    rise = level & ~level_q: 1 for one cycle when the level goes from 0 to 1. One clean level gives one pulse.

Read the diagram as text
  • btn pin: no timing to clk; bounces
  • Synchronizer: 2 flops: removes timing uncertainty
  • Debouncer: counter: removes short pulses
  • Edge detector: 1 flop and a gate: level to pulse
  • Press counter: adds 1 per pulse
  • btn pin → Synchronizer: btn
  • Synchronizer → Debouncer: s2
  • Debouncer → Edge detector: level
  • Edge detector → Press counter: rise

Measured on this flow: the synchronizer is 2 flops, the edge detector 1 flop and 1 gate (2 cells, 1 level of logic), the debouncer 24 cells with 5 flops.

Synchronizing gives a signal from a pin or another clock a known timing to this clock. It removes timing uncertainty; R6.2 has the theory. Debouncing removes the pulses a mechanical contact makes as it settles, by waiting for a quiet interval. Edge detecting turns a level into a one-cycle pulse per rising edge, and it counts bounces as edges.

The order is always synchronize, then debounce, then detect. The debouncer and the detector are clocked logic, so they must only ever see a synchronized input.

The debouncer waits for quiet

A debouncer is a counter that restarts whenever its input changes back. While s2 equals level it holds at 0. When they differ it counts, and after STABLE cycles in a row it copies s2 into level. A bounce back to the old value restarts it.

Level straight from the synchronizer

assign level = s2;

Every bounce is a level change: three presses counted for one.

Level from a debouncer

localparam int STABLE = 4;               // cycles of quiet before a new level counts
logic [3:0] quiet;                       // cycles s2 has differed from level
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n)                       begin level <= 1'b0; quiet <= '0; end
  else if (s2 == level)             quiet <= '0;          // no change, or a bounce back: restart
  else if (quiet == 4'(STABLE - 1)) begin level <= s2; quiet <= '0; end
  else                              quiet <= quiet + 1'b1;

The bounces never last four cycles: one press counted.

The same press through the debouncer, STABLE = 4Edges 3 to 15 of the lab bench with the debouncer inserted.
0123456789101112clkbtns2levelrisecount
  1. Cycle 2

    Edges 4 to 9: s2 keeps changing, so the quiet counter keeps restarting and level stays 0.

  2. Cycle 7

    Edge 10: s2 is 1 and stays 1 from here on. The counter starts its four quiet cycles.

  3. Cycle 11

    Edge 14: four quiet cycles have passed. level becomes 1 and rise fires once.

Measured on the lab bench. The cost is latency: level follows the last change of s2 four edges later, on the press and on the release.

STABLE sets both what is removed and what it costs. On this bench the longest bounce lasts two cycles. Measured: STABLE = 4 or 3 counts 1 press, STABLE = 2 counts 2, and STABLE = 1 counts 3. A real contact bounces for milliseconds. At 50 MHz, 10 ms is 500,000 cycles, so a real STABLE needs a 19-bit counter.

The lab

Design brief
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active-low
btninput1from a pin; no timing relation to clk
countoutput[7:0]adds 1 per press of the button, however much the contact bounces
Latency
a press is counted some cycles after it settles
Parameters
STABLE, the quiet cycles before a new level counts: 4 here, up to 16 with a 4-bit counter
Corner cases
bounces of one and two cycles; a press held for many cycles; the release

The lab ships the synchronizer and the edge detector with assign level = s2;. Run it: the last line reads presses=3. Replace that one line with the debouncer from the compare above and run again: presses=1. Then set STABLE to 2 and watch the count climb. Synthesize both versions: 27 cells with 11 flops, then 49 cells with 16 flops.

Asked in interviews
  1. Does a synchronizer debounce?

    No. It gives the signal a known timing to the clock and passes every pulse, bounces included. A bounce becomes a clean, synchronized pulse, which is worse, because the logic after it trusts it.

  2. How do you debounce in RTL?

    Use a counter that runs while the input differs from the accepted level and restarts when the input returns. Once the input has held the new level for a set number of cycles, the counter accepts it. That number is also the latency.

  3. Write an edge detector, and where does it go?

    Register the level and AND it with the inverse of the registered copy: rise = level & ~level_q. It goes last: synchronize, then debounce, then detect, so it sees one clean level change per press.

Try it

Each line is printed just after an edge. rise is 1 at edges 4, 7 and 10, once per pulse that came out of the synchronizer, and count takes each one at the following edge. One press, three counts.

Replace assign level = s2; with the debouncer from the compare block. level then rises once, at edge 14, and falls at edge 25, four edges after s2 settles each time; the last line reads presses=1.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: e0 btn=0 s2=0 level=0 rise=0 count=0 e1 btn=0 s2=0 level=0 rise=0 count=0 e2 btn=0 s2=0 level=0 rise=0 count=0 e3 btn=1 s2=0 level=0 rise=0 count=0 e4 btn=1 s2=1 level=1 rise=1 count=0 e5 btn=0 s2=1 level=1 rise=0 count=1 e6 btn=1 s2=0 level=0 rise=0 count=1 e7 btn=0 s2=1 level=1 rise=1 count=1 e8 btn=0 s2=0 level=0 rise=0 count=2 e9 btn=1 s2=0 level=0 rise=0 count=2 e10 btn=1 s2=1 level=1 rise=1 count=2 e11 btn=1 s2=1 level=1 rise=0 count=3 e12 btn=1 s2=1 level=1 rise=0 count=3 e13 btn=1 s2=1 level=1 rise=0 count=3 e14 btn=1 s2=1 level=1 rise=0 count=3 e15 btn=1 s2=1 level=1 rise=0 count=3 e16 btn=1 s2=1 level=1 rise=0 count=3 e17 btn=1 s2=1 level=1 rise=0 count=3 e18 btn=1 s2=1 level=1 rise=0 count=3 e19 btn=1 s2=1 level=1 rise=0 count=3 e20 btn=0 s2=1 level=1 rise=0 count=3 e21 btn=0 s2=0 level=0 rise=0 count=3 e22 btn=0 s2=0 level=0 rise=0 count=3 e23 btn=0 s2=0 level=0 rise=0 count=3 e24 btn=0 s2=0 level=0 rise=0 count=3 e25 btn=0 s2=0 level=0 rise=0 count=3 e26 btn=0 s2=0 level=0 rise=0 count=3 e27 btn=0 s2=0 level=0 rise=0 count=3 presses=3

Original design · 27 cells · 11 flip-flops · 0 latches · 7 logic levels. Synthesis uses Yosys; counts depend on the toolchain.

Read included file: tb.sv
`timescale 1ns/1ps
module tb;
  logic clk = 0;
  always #5 clk = ~clk;                   // rising edges at 5, 15, 25, ...
  logic rst_n = 1;                        // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
  int edge_n = -1;
  always @(posedge clk) edge_n = edge_n + 1;
  logic       btn = 0;
  logic [7:0] count;
  logic [8:0] bounce = 9'b111001011;      // btn for nine cycles, bit 0 first: 1 1 0 1 0 0 1 1 1
  presses dut (.clk(clk), .rst_n(rst_n), .btn(btn), .count(count));
  // Advance to the next rising edge, wait for the design to settle, then print one line.
  task automatic tick();
    @(posedge clk); #1;
    $display("e%0d btn=%b s2=%b level=%b rise=%b count=%0d", edge_n, btn, dut.s2, dut.level, dut.rise, count);
  endtask
  initial begin
    #1 rst_n = 0; tick(); tick(); rst_n = 1;   // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
    tick();
    for (int i = 0; i < 9; i++) begin btn = bounce[i]; tick(); end   // the press, bouncing
    repeat (8) tick();                                               // held
    btn = 0;                                                         // released, cleanly
    repeat (8) tick();
    $display("presses=%0d", count);
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule

Check yourself

Predict the outputThe lab design, with no debouncer, sees two presses. Each closes for two cycles, bounces open for one, then closes for six; each release is clean. What does the program print?
// presses: counts presses of a push button. btn comes from a pin: it is not
// timed to clk, and a mechanical contact bounces for a while when pressed.
module presses (
  input  logic       clk,
  input  logic       rst_n,
  input  logic       btn,
  output logic [7:0] count
);
  // 1. Synchronize: two flops in series give btn a known timing to clk.
  logic s1, s2;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin s1 <= 1'b0; s2 <= 1'b0; end
    else begin s1 <= btn; s2 <= s1; end

  // 2. Detect the rising edge: a flop keeps last cycle's value.
  logic level, level_q;
  assign level = s2;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) level_q <= 1'b0;
    else        level_q <= level;
  wire rise = level & ~level_q;

  // 3. Count one press per rising edge.
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n)    count <= '0;
    else if (rise) count <= count + 1'b1;
endmodule

module tb;
  logic clk = 0, rst_n = 1, btn = 0;
  logic [7:0] count;
  presses dut (.clk(clk), .rst_n(rst_n), .btn(btn), .count(count));
  task automatic cycles(input logic v, input int n);
    btn = v;
    repeat (n) begin #1 clk = 1; #1 clk = 0; end
  endtask
  initial begin
    #1 rst_n = 0;
    #1 rst_n = 1;
    repeat (2) begin                       // two presses
      cycles(1, 2); cycles(0, 1); cycles(1, 6);   // closes, bounces open, closes and stays
      cycles(0, 6);                              // released
    end
    $display("presses=%0d", count);
    $finish(0);
  end
endmodule
Spot the bugEach option drives rise from d and its registered copy d_q. The bench applies one clean pulse of three cycles and expects exactly one cycle with rise = 1. Which option fails?
ConceptA pin feeds a two-flop synchronizer. Which statement describes what the synchronizer removes?
ConceptWhich block in the chain is the one that keeps a bouncing contact from being counted several times?
Read the waveformThis is the lab bench without a debouncer, edges 3 to 11. What causes the pulse on rise at edge 7?
The bouncing press
012345678btns2rise

One value per edge, from edge 3 on the left to edge 11 on the right.

Remember

  • A synchronizer gives a pin a known timing to the clock; it passes every pulse, bounces included.
  • A debouncer is a counter that accepts a new level only after it has lasted a set number of cycles; that number is also its latency.
  • An edge detector, level & ~level_q, turns each rising change into a one-cycle pulse, so it must see a clean level.
  • The order is synchronize, debounce, detect: everything clocked after the pin must see a synchronized input.