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.
btn is the pin, s2 the synchronizer output, rise the edge detector, count the presses counted.- Cycle 1
Edge 4: the contact has closed and
s2shows it.s2is 1 and was 0, soriseis 1. - Cycle 4
Edge 7: the contact bounced open for one cycle and closed again. The synchronizer passed both changes, and
riseis 1 a second time. - Cycle 7
Edge 10: a two-cycle bounce, then the contact stays closed.
risefires a third time. - 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.
- Synchronize
Two flops in series. If the first flop samples
btnas 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. - Debounce
The new level counts only after
s2has stayed at it for STABLE cycles in a row. A bounce shorter than that never reacheslevel. - 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.
- Cycle 2
Edges 4 to 9:
s2keeps changing, so the quiet counter keeps restarting andlevelstays 0. - Cycle 7
Edge 10:
s2is 1 and stays 1 from here on. The counter starts its four quiet cycles. - Cycle 11
Edge 14: four quiet cycles have passed.
levelbecomes 1 andrisefires 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
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| rst_n | input | 1 | reset, asynchronous, active-low |
| btn | input | 1 | from a pin; no timing relation to clk |
| count | output | [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
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.
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.
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.
Synthesize the design to measure its cells and inspect the mapped gates.
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=3Original 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
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.