debounce

Aug 17, 2026 Last reply: 1 month ago 20 Replies

We are making a 4-channel tachometer module. After a bunch of analog stuff, four logic signals run into an FPGA to be processed. The fpga will use a 50 MHz clock for the tach logic.



I used OPA197 opamps as the comparators. Their outputs run through a short ribbon cable from the analog board to the FPGA board. The edges are kinda slow, > 1 us, and might pick up noise, so I figure we should debounce them digitally in the FPGA.



Suggestions?



One could push the signal into a long shift register clocked at 50 MHz. All 1's could set a flop and all 0's could clear it. Maybe 32 flops long? Clumsy.



Or some sort of up/down counter?


Courtesy of Google AI. Carefull it doesn't burn your house down and run away with your wife.

library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL;

entity debouncer is generic ( -- For a 50 MHz clock, 1,000,000 cycles = 20 ms BOUNCE_LIMIT : integer := 1000000 ); port ( clk : in std_logic; rst : in std_logic; raw_in : in std_logic; clean_out: out std_logic ); end entity debouncer;

architecture rtl of debouncer is -- Synchronizer stages signal sync_0 : std_logic := '0'; signal sync_1 : std_logic := '0'; -- Debounce counter and internal state signal count : integer range 0 to BOUNCE_LIMIT := 0; signal stable_state: std_logic := '0'; begin

-- Output assignment clean_out <= stable_state;

process(clk, rst) begin if rst = '1' then sync_0 <= '0'; sync_1 <= '0'; count <= 0; stable_state <= '0'; elsif rising_edge(clk) then -- 2-stage synchronizer to prevent metastability sync_0 <= raw_in; sync_1 <= sync_0; -- Debounce logic if sync_1 /= stable_state then -- Input changed, start/continue counting if count < BOUNCE_LIMIT then count <= count + 1; else -- Input remained stable long enough stable_state <= sync_1; count <= 0; end if; else -- Input matches current stable state, reset counter count <= 0; end if; end if; Process;

end architecture rtl;

The "Not Too Shifty!" section of

formatting link
similar to what you propose.

Or use an up/down counter on the 50 MHz with the analog input controlling up/down. On state 0, reset the output, and on state N-1, set it.

Load the counter appropriately if it tries to wrap. I could uncoil my rusty Verilog, but it’s a pretty simple gizmo, and probably saves space vs a long register.

Cheers

Phil Hobbs

8-stage is sufficient. Just don't forget to poison it on startup. Trivial to implement in an FPGA.

If it is news, the shift register thing is a digital LPF.

Here is a many decades old classic, still in production and readily available:

formatting link

Naw, I think it's got a crush on Claude...

A smarter move is usually to signal the change and THEN debounce. I.e., ignore bouncing AFTER the initial transition

Otherwise, your signal transitions are delayed by the debounce interval.

Read the state of the contact and log, wait a debounce time, say

20mS, then read again to verify, Play with time delay and > 1 verification cycles to make more robust.

In software, like this, many projects:

Interrupt handler fragment, from system clock tick isr.

Sorry, cut and paste, lost the formatting.

Most FPGA specify max rise/fall edges of 10ns. You're well in excess of that by a factor of 100, risking false triggering internal oscillation due to thermal noise, and metastability. Some FPGA have programmable Schmitt Trigger Mode on their I/O banks for this reason. Keywords to look for: SCHMITT_TRIGGER = TRUE or HYSTERESIS = ENABLE in the EDA. If you don't have that, then a 74LVC1G17 is in order. 74LVC14A is a multi-gate package Schmitt, but it inverts. Most of these high speed buffer types have delta-t/ delta-V input transition max times of 10ns/V don't help the situation. The 74LVC1G17 and 74LVC14A, don't have that limitation. For maximum preservation of transition times, the Schmitt should be placed at the FPGA location. The old way of preserving signal integrity was to place a series R with C shunt to COM at the Schmitt input. Might as well make that a transient match to the line with a time constant equal to the analog signal rise time. So R about 100R and C 10nF.

Is the location of the initial contact important, or is it sufficient not to chatter?

My old analog scheme was a RC lowpass filter feeding a schmitt trigger. Always got exactly one pulse per switch closure.

A fancier version uised the schmitt output to discharge the C faster.

Thee above is easily implemented in a 50 MHz controller.

Joe

The tach design is done and it works. I want to add the debounce logic just in case we get any crosstalk in traces or ribbon cables.

As far as I can tell, the T20 FPGA has zero internal ground bounce or crosstalk.

Actually, google AI tells me that the T20 has selectable schmitt triggers, at least on some inputs. Maybe we got lucky and used them.

John Larkin Highland Tech Glen Canyon Design Center Lunatic Fringe Electronics

If you're inputs are Schmitt, then the RC will annihilate any crosstalk. Because the interwire coupling is pF's , the crosstalk will be a few nsec duration max. Not going to budge the 1usec rise time on the input, the talk gets attenuated by factor of 1000 minimum. A 3V spike becomes a 3mV something at the inputs.

Yes, the Efinix Trion T20 FPGA supports a selectable Schmitt trigger mode on its I/O pins, but with important bank exceptions:

  1. General-Purpose I/O (GPIO) Banks Standard input I/O buffers on the T20 support an optional Schmitt trigger mode. This can be configured pin-by-pin via the Efinity® Interface Designer software.

  1. LVDS Banks (Exception) You can configure the LVDS transmit (TX) and receive (RX) channels to function as standard 3.3 V single-ended GPIO pins. However, when used as single-ended pins, they do not support Schmitt triggers or variable drive strength. (???)

  2. Dedicated Configuration Pins All dedicated configuration pins on the Trion T20 automatically feature a built-in Schmitt trigger buffer.

For a digital bus generated via some analogue circuitry? Thats the worst thing you could possibly do.

You need to compare the current bus state to the registered state and only update when the new state is settled for an appropriate time.

Only if there is noise present -- in which case, you're not just debouncing but also filtering. A slow rising signal that has risen enough to cross a "reasonably chosen" threshold would have to be encountering a lot of noise to NOT end up where it appeared to be headed. In which case, you've got another problem to solve, first.

In the absence of noise, the initial departure from the "registered state" is indicative of where the signal will settle. So, you can make that declaration, NOW, and then debounce the transition that follows until that state is achieved.

Putting the debounce interval after the claimed transition lets you lengthen it as conditions change (worsen?) WITHOUT adding more delay to the signal being perceived.

You're assuming valid sequential states on the input bus differ by only one bit.

It was a tachometer, wasn't it? Not a syncro encoder...

How does YOUR solution handle multiple bits on a single signal??

The OP will have to evaluate the particular application. I offered my approach as it is surprising how often folks miss this possibility and end up adding more delay to their signals as the "bounce" worsens or is found (in production) to be worse than planned.

No idea what it is. If it's 4 seperate 1 bit streams then of course he can register the first bit change and then just have a blanking period.

I never offered a solution. If people here write HDL code for him John will be shipping his own crew off to the soylent green factory in short order.

"We are making a 4-channel tachometer module. After a bunch of analog stuff, four logic signals run into an FPGA to be processed. The fpga will use a 50 MHz clock for the tach logic."

"Courtesy of Google AI. Carefull it doesn't burn your house down and run away with your wife."

And your quotes are supposed to indicate what exactly?

References to your first post in this thread.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required