You wire up a button, write five lines of SystemVerilog, load the bitstream — and the LED goes crazy. Every press toggles it three, five, sometimes ten times. You slow down, press more carefully. Still wrong. The hardware looks fine. The code looks fine. The problem is physics.
A mechanical button is a metal contact closing against another metal contact. At the microscopic level, that contact bounces — making and breaking the connection dozens of times in the span of a few milliseconds. Your FPGA clock runs at 100 MHz. It sees every single bounce as a separate, valid press. This post shows you exactly what is happening and builds a reusable debouncer module that eliminates the problem permanently.
1. The Physics of Bounce
When a button is pressed, the contacts do not close cleanly in one motion. The metal flexes, vibrates, and makes intermittent contact for roughly 5 to 20 milliseconds before settling into a stable state. The same thing happens on release. An oscilloscope on the button pin shows a clean-looking press at human timescales but reveals a dense burst of transitions at higher resolution.
The diagram below captures this behavior at clock resolution. What looks like one button press to your finger is a storm of rising and falling edges to the FPGA.
2. Why the FPGA Counts Every Bounce
The fundamental mismatch is one of timescales. Human reaction time is measured in hundreds of milliseconds. The bounce window is measured in milliseconds. The FPGA clock period is measured in nanoseconds. The FPGA operates six orders of magnitude faster than the bounce settles.
The naive approach — connecting the button pin directly to a counter or state machine — looks correct in simulation (where the button is an ideal step function) but fails immediately on hardware. The code below demonstrates the problem: what appears to be a simple counter becomes a bounce counter, incrementing hundreds of times per physical press.
3. The Shift Register Debouncer
The solution is to stop reading the button on every clock cycle and instead sample it at a much lower rate — once every few milliseconds. After each sample, the value is shifted into a small shift register. The output only changes when all N slots in the shift register agree: all ones means the button is definitively pressed, all zeros means it is definitively released. Any mixed pattern — which occurs during a bounce — leaves the output unchanged.
This works because the bounce window (5–20 ms) is much shorter than the debounce window (N × sample period). By the time N consecutive samples have been taken, the contacts have long since settled. The module below is fully parameterized: drop it into any project and adjust CLK_FREQ and DEBOUNCE_MS to match your hardware.
4. Edge Detection: From Level to Pulse
The debouncer outputs a level signal: it is high for as long as the button is held down. For most applications — toggling an LED, triggering a state machine transition, incrementing a counter — you want a single-cycle pulse that fires exactly once per press, regardless of how long the button is held. That requires edge detection.
The edge detector stores the previous cycle’s value of the debounced signal and compares it to the current value. A rising edge — current is high, previous was low — produces a one-cycle pulse on btn_press. The combinational assign ensures the pulse appears in the same cycle the edge is detected, with no added latency.
btn_clean directly to a counter’s enable input, the counter increments on every clock cycle the button is held — potentially thousands of times per press. Edge detection ensures the counter increments exactly once per physical press, no matter how long the button is held.
5. Putting It Together
The top-level module chains the two building blocks: raw button pin → debouncer → edge detector → application logic. The example below toggles an LED on each clean button press. The debouncer handles the electrical noise; the edge detector converts the stable level into a precise trigger. Every project that reads a button should use this exact pattern.
Final Thoughts: One Module, Every Project
Button debouncing is a solved problem. The shift register debouncer above is 25 lines, fully parameterized, and works correctly on any FPGA at any clock frequency. Write it once, add it to your project library, and never think about bounce again.
The deeper lesson is about the gap between simulation and hardware. In simulation, btn_raw is a perfect step function — it transitions once and stays there. On hardware, it is a burst of noise that your 100 MHz clock captures in full detail. Any design that reads a mechanical signal without debouncing is correct in simulation and broken on the board. Debounce every button, every switch, every mechanical contact — no exceptions.
Happy coding.
fpgawizard.com

