It’s been some time now since we introduced the implementation of a pair of generic filters. The first was a fairly generic FIR filter implementation, and the second was a “cheaper” implementation of the same logic. Both filters filters were presented as though they worked, but without any test bench to prove it. Further, these prior posts hinted at other filters which might be better, but I haven’t come back to present them (yet). (As an example, I’ve got a nice symmetric filter implementation waiting to be present in due time.)
Since that time, we’ve presented a test harness that can be used to prove whether a generic FIR filter even works. We’ve also presented a component of that harness which measures the frequency response of any given FIR filter. Both of these were designed to demonstrate how easy it could be to determine if an HDL filter “works” when using Verilator as our simulator.
Given all coefficients of zero save one, is the impulse response appropriate?
Given all maximally valued coefficients and maximally valued inputs, will the filter under test overflow?
Given a set of identical and maximal coefficients, does the frequency response cutoff where we expect?
These will be the question’s we’ll try to answer in the next section.
Testing the Generic filter
We’ll start by working through the test bench for this filter. We’ve already presented most of the difficult logic (here too), so all that remains now is to create a series of tests and apply them to the filter through our generic test harness.
The first step, though, is to define the constant parameters that the filter was verilated with. We’ve touched on the fact, over time, that I haven’t found a good way to get module parameters into our test bench. Instead, we’ll declare them up top and require the user to remember to keep them in synch with the verilog filter’s synthesized parameters.
These parameters are: the number of taps in implemented within the
NTAPS, the number of bits in an inputs sample,
IW, the number of bits in
each filter coefficient,
TW, and the number of output bits. The last parameter,
DELAY, is the number
of clocks that need to take place from the time an input is presented to
until the first response due to the
We’ll want to test below whether or not the filter’s impulse response is as we think it should be. To do this, we’ll pick values that are the absolute maximum values. We’ll also attempt to turn this filter into a moving averaging filter later to see if we can overflow it. Both of these require maximum constant values.
As a last step before beginning, we’ll allocate some memory for test vectors, and for filter coefficients we might wish to apply as part of a test. We’ll also (optionally) open a VCD file to record the internals of anything that happens. As a final step before beginning, we’ll issue a reset to the unit under test.
Now for our first test set: let’s walk through all coefficients, setting a
single coefficient to
TAPVALUE at a time, and let’s see if we get the
we are expecting.
You may remember from
when we built our
testload function not only loads coefficients into the
but it also applies sufficient test vectors to
to know whether or not the coefficients were validly loaded as desired. In a
similar fashion, the
test_overflow() method runs maximum (negative) values
to see if it can be caused to overflow. Both routines will end in an
if something fails.
For our second test, let’s set all of the coefficients to a maximum value. This will create a block average filter.
We can then set our filter’s coefficients to be these values, and double check that what we loaded was what we wanted.
One of the neat parts of using a block filter is that the filter’s response is easy to predict. Let’s apply a rectangle function to the input of this filter and verify that we get the response we are expecting.
These filter coefficients are also the most likely coefficients to overflow–since they are all maximum valued integers for their bit-widths. Let’s see if we can cause this filter to overflow by using these same maximal coefficients as inputs.
As you may recall, the overflow test places inputs into the filter with maximum integer values. It returns true if the filter passes, or false otherwise. (Actually, it fails on an assert error if the filter doesn’t pass here.)
A quick check of the results, shows us that we are in the range we are expecting.
This will lead to the output,
among other outputs.
This looks much better than our prior set of coefficients, and indeed it is.
We now have about a
depth. Further, our
are both centered around 1/4, just as you would expect for any
Even better, the distance from the middle of the band to the
is exactly the same as the distance from the middle of the band to the
You can see the frequency response measured by this routine in the charts within our last post on measuring the frequency response of a filter, or below in Fig 1 showing the frequency response in linear units,
Time for more filters!
Now that we’ve finished discussing how to test/verify a digital filter, it’s time to build some more! Let’s see … I’ve wanted to demonstrate a symmetric filter that’s been waiting on this post, the Hilbert transform should be trivial following the symmetric filter, I’ve got a few slower filters (fewer multiplies) that I can demo … indeed, this might only be the beginning.
On the other hand, there are other CPU based topics that I’ve been ignoring, so it might still take us some time to get to all of these new digital filter implementations.
When your fathers tempted me, proved me, and saw my work. (Ps 95:9)