Using a debug-bus to Measure Bouncing
As we’ve discussed in our two previous posts, buttons and switches on FPGAs can be very useful to work with. They can be very useful tools when debugging as well. The problem is that these forms of inputs often “bounce”, and produce multiple edges when only one is desired. This can be a problem when you wish to depend or rely upon these inputs as being a dependable part of a debugging solution–before you’ve managed to ensure their dependability.
Making contact bounce particularly problematic is the fact that it is difficult to simulate, since the bounces tend to appear quite random.
Our first post on this topic looked into how this sort of bouncing manifested, showing several examples and traces of contact bounces. The pictures weren’t very pretty, as many buttons from several different boards and even a keypad all manifested this problem.
Our second post discussed how to create the digital logic necessary to debounce a user input.
We then created some simple test points that could be used to measure button press duration and the number of times a particular button press bounced.
Today, our discussion is going to focus on how to connect these two components to the wishbone bus present within our debugging-bus testing logic. We’re also going to add a compressed wishbone scope to the design in order to get the traces out of our design such as were presented in the initial post.
Instantiating the two components
Over the course of the last two posts, we’ve created two components that we would now like to test: a debouncer, and an unbounced module. We are going to test these two components by pressing a button, and then reading the results produced by these two components as registers on a bus. Once read, we’ll use the bus to reset these components.
We’ll start by assuming we have NBTNS
buttons, in a vector named i_button
coming into our top level design. This vector, together with our system
clock, can then be used as inputs to these two modules, as in:
The result of the
debouncer
is a NBTNS
-bit vector debounced
. This vector is a copy of i_button
,
save only that it has been both synchronized to the clock and any bounces
have been removed.
The two results of the
unbounced
vector are two 31-bit vectors: transitions
which holds the number of
transitions, and max_clock
which holds the number of clock ticks from the
first transition to the last.
The other two items are the unbounce_reset
and the debounce_dbg
.
The unbounce_reset
line clears the counters. We’ll hook that up in the
next section. The debounce_dbg
lines, however, we’ll come back to when
we discuss hooking the debouncer up to the wishbone
scope.
Connecting the components to the debugging bus
We’d like to hook these two peripherals to our wishbone debugging bus, testbus.v. In particular, we’d like to be able to read these three values from an external UART. Since reading the values requires no logic handshake with the modules, they can be hooked up just as simply as any of the other simple registers within the testbus.v.
To hook this new peripheral up, just assign it an address among our simple data peripheral’s address map:
Don’t forget when using these addresses, that each address references a
32-bit quantity, and hence they need to be multiplied by four to get the
offset into the address map.
Hence, address offsets of 24, 28, and 32 from the simple data peripheral
now contain our new values. Given that we placed our simple data peripheral
at an address offset of 0x2040
, that means these new registers will have
final addresses 0x2058
, 0x205c
, and 0x2060
.
One other change is required to the bus, we need to create our reset signal,
unbounce_reset
. We’re going to set this reset signal up so that any time
we write to either of these registers, either the transitions
or the
max_clock
register, we reset our counters:
That’s it! That’s all the changes that are necessary in order to connect these two new peripherals to our bus. Gosh, if it’s that easy, why not try your own peripherals?
Before moving to the next section, there’s one other difference. If you compare this new top level design with the initial top level design, you’ll notice that the block RAM has been removed. This was to provide extra block RAM space for the wishbone scope—space that was never really needed in the end.
Switching to a compressed wishbone scope
There are three RTL steps to setting up a wishbone scope within your design.
The first step is to create a trigger. You can find the trigger I created in the debouncer.
This trigger is designed to stop the scope any time the debouncing logic is complete, and the button’s logic value(s) has returned to zero. In practice, it didn’t work very well for testing the keypad, since the keypad uses negative logic (a button release sends the signal high, not low). The end result was that I often needed to trigger the scope manually from the debugging bus, but we’ll come back to this in a moment.
The next step in connecting the scope is to select the data lines you wish
to record. Here, let’s connect to our synchronized inputs, the debounced
result, as well as the ztimer
and trigger
logic from within the design.
Because buttons take place over a very long period of time, we’ll need
to use the compressed wishbone scope. That’s going to limit our debugging
width to 31-bits instead of 32. (The last bit is used for run length
encoding.)
While this approach limits the number of inputs to 13-bits in a 14-bit field, the logic within the actual debouncer code makes it possible to record up to 14-bits, and to process any number of buttons up to an arbitrary width.
The final change is to adjust the scope itself. Switching from an
uncompressed wishbone scope to a
compressed wishbone scope
is almost as easy as changing wbscope
to wbscopc
. Indeed, the two share
an almost identical interface standard. The only more substantial change
beyond changing the name is adjusting the input data width from 32-bits down
to the 31-bits of the compressed scope. We’ll also pull the scope_trigger
from its bit position within the debug
vector coming from the debouncer
code.
Setting up the design for this purpose is really just that easy.
Adjusting the register definition files
Finally, I adjusted the
regdefs.h and
regdefs.cpp
files to reflect that there are two new registers
in our design, giving them names that I can use to access them both from
my own C code
(regdefs.h),
as well as from the wbregs
command line
(regdefs.cpp).
The first change was to regdefs.h, where we needed to define our three new registers:
You may remember from our discussion above that these are the addresses of these three new registers within our simple data peripheral’s address space.
I then gave these registers user-friendly names by modifying regdefs.cpp:
This only modifies a structure providing a mapping from register values to user register names.
The final change was to create a piece of C++ code to read from the wishbone scope. You can see what this new scope code looks like here, although how to develop this code is discussed in general here. Build and run this new btnscope program, and you’ll have a VCD file that you can then use with GTKWave to view traces showing how your own buttons bounce–just like the traces I presented when we first started.
That’s it! We can now grab two registers from our button press, and read
their results via wbregs duration
and wbregs bounces
. We can also reset
our count by writing to either of these registers via something like
wbregs duration 0
. Using
btnscope,
we can also read and create VCD
files.
Scripts to access the design
After using this design over and over several times, I got tired of typing the same commands over and over. As a result, I wrote two scripts to make things easier.
The first script, getinfo.sh, simple reads the two registers and dumps them into a file record for later analysis. It also manually triggers the wishbone scope in case it hadn’t triggered yet.
The second script, reset.sh, resets the logic for another button press. Specifically, it resets the scope (i.e. writes to it with the high data clear), and then attempts to write to the bounce count. As with any write to either of the unbounced registers, this will set the reset line to the unbounced module for one clock, and thus reset the counters. Finally, just to be certain that the two registers actually cleared as desired, they are read after reset.
Conclusion
That’s it! You now have all the information you need to either build and measure your own debouncer, or to just measure the bounces on your own device. Further, all of the debouncing logic, design, and software files have been placed into their own GitHub repo for your own reference.
As cold waters to a thirsty soul, so is good news from a far country. (Prov 25:25)