forked from laforest/FPGADesignElements
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDebouncer_Low_Latency.html
307 lines (271 loc) · 12.2 KB
/
Debouncer_Low_Latency.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
<html>
<head>
<link rel="shortcut icon" href="./favicon.ico">
<link rel="stylesheet" type="text/css" href="./style.css">
<link rel="canonical" href="./Debouncer_Low_Latency.html">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A digital debouncer for mechanical inputs (e.g.: mechanical and optical switches) which can run on the system clock, introduces only 4 or 5 clock cycles of latency, and whose rising and falling debouncing delays can be altered dynamically (i.e.: if the clock frequency changes, or the mechanical input changes).">
<title>Debouncer Low Latency</title>
</head>
<body>
<p class="inline bordered"><b><a href="./Debouncer_Low_Latency.v">Source</a></b></p>
<p class="inline bordered"><b><a href="./legal.html">License</a></b></p>
<p class="inline bordered"><b><a href="./index.html">Index</a></b></p>
<h1>Debouncer (Low Latency)</h1>
<p>A digital debouncer for mechanical inputs (e.g.: mechanical and optical
switches) which can run on the system clock, introduces only 4 or 5 clock
cycles of latency, and whose rising and falling debouncing delays can be
altered dynamically (i.e.: if the clock frequency changes, or the
mechanical input changes).</p>
<h2>Background</h2>
<ul>
<li>For discussion and some experimental data on switch bounce, see Jack
Ganssle's <a href="http://www.ganssle.com/debouncing.htm">"A Guide to Debouncing, or, How to Debounce a Contact in Two
Easy Pages"</a>.</li>
<li>Horowitz' and Hill's <em>"The Art of Electronics"</em> also has some information
on signal integrity, measurement, and debouncing scattered throughout.</li>
</ul>
<h2>A Warning About External Noise</h2>
<p><strong>This debouncer depends on the assumption that any change on the input is
caused solely by the connected mechanism.</strong> It will <strong>NOT</strong> reliably filter
out random spikes and glitches from other sources such as EMI
(ElectroMagnetic Interference). If a glitch gets captured by the debouncer,
it will be interpreted as a switch event (rising or falling).</p>
<p>On the other hand, this debouncer is very tolerant of slow signal edges and
does not require digital-like quick input transitions. This debouncer only
requires that the input eventually reaches and stays at a valid high or low
logic level. Thus, you can liberally use analog filtering at the input if
your electrical environment is noisy.</p>
<h2>Theory of Operation</h2>
<p>In the absence of external noise, a switch in a steady opened or closed
state will provide a steady logic level to the input (high or low,
depending on the setup). Thus, when we open a closed switch or close an
open switch, the logic level at the input (high or low) can only change to
the other level via a positive (low to high) or negative (high to low)
edge. The switch may then bounce for a while, causing a number of
alternating edges, but the first edge is always valid. (The last edge is
also valid, but we can't know when it will happen, which is another way to
state the problem.)</p>
<p>We detect this initial positive or negative edge and use it to immediately
set the output of the debouncer so it matches the new logic level the
switch will eventually provide once it settles. This initial edge also
starts a counter which freezes the internal state of the debouncer
until the switch has settled. Thus, we can report a switch opening or
closing after only a few clock cycles, and push the wait time to the
interval <em>between</em> the switch opening and closing, in parallel with
whatever action the switch starts. </p>
<h2>Calibration</h2>
<p>Normally, for normal human-scale operation of a switch, a single
conservative bounce time estimate suffices to filter out both closing and
opening switch bounce. However, all switches are different, and may be used
with a very uneven duty cycle (e.g.: rapid short pulses), so a separate
wait time for both rising and falling transitions can be set.</p>
<p>To calibrate the delay, observe the <code>diag_synchronized_input</code> and
<code>diag_ignoring_input</code> signals on a logic analyzer or scope while you
operate the switch. The first will show you the behaviour of the input
bounce and its duration, and if your switch is suitable for your
application. The second will show you if the Debouncer is ignoring
the input bounce long enough for reliable operation, and if valid inputs
are being lost (e.g.: multiple valid switch events inside a single delay).</p>
<p><em>Note that if the input reliably returns to the previous state before the
delay is done, the <code>input_clean</code> will not reflect the change until the
delay is done.</em> This does put a rate limit on the input.</p>
<pre>
`default_nettype none
module <a href="./Debouncer_Low_Latency.html">Debouncer_Low_Latency</a>
#(
parameter COUNTER_WIDTH = 0, // Wide enough to hold largest delay.
parameter INITIAL_INPUT = 1'b0, // 1'b0 or 1'b1. The input rest state.
parameter EXTRA_CDC_STAGES = 0 // Must be 0 or greater.
)
(
input wire clock,
// No reset or enable, as that could cause artificial input events.
input wire [COUNTER_WIDTH-1:0] delay_cycles_rising,
input wire [COUNTER_WIDTH-1:0] delay_cycles_falling,
input wire input_raw,
output wire input_falling,
output wire input_rising,
output wire input_clean,
// For calibration and testing (see notes above)
output reg diag_synchronized_input,
output reg diag_ignoring_input
);
initial begin
diag_synchronized_input = INITIAL_INPUT;
diag_ignoring_input = 1'b0;
end
</pre>
<p>Let's generate counter values of the correct width.</p>
<pre>
localparam COUNTER_ONE = {{COUNTER_WIDTH-1{1'b0}},1'b1};
localparam COUNTER_ZERO = {COUNTER_WIDTH{1'b0}};
</pre>
<p>First, capture the input into an I/O register to filter out glitches and
random sub-threshold analog noise between clock edges, and to convert slow
input level changes (due to input capacitance and resistance) into
a single, sharper edge (or at least a short train of cleaner pulses). All
of this cleanup will make the CDC Synchronizer's job easier and more
reliable.</p>
<p>We also give the CAD tool some attributes to place this register into an
I/O register if possible. This is useful if this Debouncer is fed by
a regional I/O clock, and to make sure we don't let analog noise into the
logic fabric. It shouldn't matter, but in extreme cases the noise could
couple to other logic lines. Analog noise is outside the expected behaviour
of signals inside the FPGA, so let's avoid it.</p>
<p><em>NOTE: The output of this register will still end up briefly metastable or
otherwise marginal sometimes, and must be properly synchronized to the
clock later on.</em></p>
<p>We don't use a <a href="./Register.html">Register</a> module here since we can't apply
the necessary I/O attributes to a module, and we don't want to have any
enable or reset logic on this register, which could cause false input
events.</p>
<pre>
(* IOB = "true" *) // Vivado
(* useioff = 1 *) // Quartus
reg captured_input = INITIAL_INPUT;
always @(posedge clock) begin
captured_input <= input_raw;
end
</pre>
<p>Then, we synchronize the captured, de-noised input to the clock to filter
out any remaining marginal or metastable signals, which will either get
dropped, or converted to proper synchronous logic pulses. Add stages if
necessary, likely for very high clock speeds. From here on, the input is
a clean digital signal we can debounce.</p>
<pre>
wire synchronized_input;
<a href="./CDC_Bit_Synchronizer.html">CDC_Bit_Synchronizer</a>
#(
.EXTRA_DEPTH (EXTRA_CDC_STAGES) // Must be 0 or greater
)
input_synchronizer
(
.receiving_clock (clock),
.bit_in (captured_input),
.bit_out (synchronized_input)
);
always @(*) begin
diag_synchronized_input = synchronized_input;
end
</pre>
<p>Separately detect rising and falling edges on the input. This allows us to
react immediately when the switch opens or closes. The premise is that if
a switch is stable in one state, and no external noise exists, then the
first observed edge can only signify a change in switch state and we can
report it immediately.</p>
<p>The rest of the switching time, with its bouncing transitions, is not
important. We only need to wait it out in parallel by loading the counter
with a non-zero value at that instant and ignoring the input while the
counter counts down to zero.</p>
<pre>
reg input_rising_internal = 1'b0;
reg input_falling_internal = 1'b0;
reg counter_load = 1'b0;
reg counter_run = 1'b0;
always @(*) begin
input_rising_internal = (synchronized_input == 1'b1) && (input_clean == 1'b0) && (counter_run == 1'b0);
input_falling_internal = (synchronized_input == 1'b0) && (input_clean == 1'b1) && (counter_run == 1'b0);
counter_load = (input_rising_internal == 1'b1) || (input_falling_internal == 1'b1);
end
always @(*) begin
diag_ignoring_input = (counter_run == 1'b1);
end
</pre>
<p>Capture the gated edge detection pulses in a latch, where each type of
pulse sets the latch to mirror the state the input is entering, based on
the first detected edge.</p>
<pre>
<a href="./Pulse_Latch.html">Pulse_Latch</a>
#(
.RESET_VALUE (INITIAL_INPUT)
)
input_mirror
(
.clock (clock),
.clear (input_falling_internal),
.pulse_in (input_rising_internal),
.level_out (input_clean)
);
</pre>
<p>And register the rising/falling pulses so they arrive in sync with
<code>input_clean</code>.</p>
<pre>
<a href="./Register.html">Register</a>
#(
.WORD_WIDTH (1),
.RESET_VALUE (1'b0)
)
sync_input_rising
(
.clock (clock),
.clock_enable (1'b1),
.clear (1'b0),
.data_in (input_rising_internal),
.data_out (input_rising)
);
<a href="./Register.html">Register</a>
#(
.WORD_WIDTH (1),
.RESET_VALUE (1'b0)
)
sync_input_falling
(
.clock (clock),
.clock_enable (1'b1),
.clear (1'b0),
.data_in (input_falling_internal),
.data_out (input_falling)
);
</pre>
<p>Finally, when a rising or falling edge is detected, load (and start) the
delay counter, which disables edge detection while running down to zero.
A different value may be loaded for rising and falling edges.</p>
<pre>
wire [COUNTER_WIDTH-1:0] delay_cycles;
<a href="./Multiplexer_One_Hot.html">Multiplexer_One_Hot</a>
#(
.WORD_WIDTH (COUNTER_WIDTH),
.WORD_COUNT (2),
.OPERATION ("OR"),
.IMPLEMENTATION ("AND")
)
delay_select
(
.selectors ({input_falling_internal, input_rising_internal}),
.words_in ({delay_cycles_falling, delay_cycles_rising}),
.word_out (delay_cycles)
);
wire [COUNTER_WIDTH-1:0] count;
<a href="./Counter_Binary.html">Counter_Binary</a>
#(
.WORD_WIDTH (COUNTER_WIDTH),
.INCREMENT (COUNTER_ONE),
.INITIAL_COUNT (COUNTER_ZERO)
)
delay_counter
(
.clock (clock),
.clear (1'b0),
.up_down (1'b1), // 0/1 --> up/down
.run (counter_run),
.load (counter_load),
.load_count (delay_cycles),
.carry_in (1'b0),
// verilator lint_off PINCONNECTEMPTY
.carry_out (),
.carries (),
.overflow (),
// verilator lint_on PINCONNECTEMPTY
.count (count)
);
always @(*) begin
counter_run = (count != COUNTER_ZERO);
end
endmodule
</pre>
<hr>
<p><a href="./index.html">Back to FPGA Design Elements</a>
<center><a href="https://fpgacpu.ca/">fpgacpu.ca</a></center>
</body>
</html>