A Moku Cloud Compile implementation | Published December 15,2022
Reducing noise and transients with custom real-time digital filtering – Liquid Instruments
In this application note, we utilize Moku Cloud Compile and Multi-instrument Mode to explain the development of a commonly used moving average filter. We use both the Oscilloscope and Frequency Response Analyzer to examine this basic finite impulse response (FIR) filter. Then we develop, deploy, and examine a five-point median filter using Moku:Pro or Moku:Go devices. Combining a linear and nonlinear filter in this way can be used to reject transients and reduce noise in many control or sensing applications.
Moku Cloud Compile
Moku Cloud Compile (MCC) is a Liquid Instruments service that allows you to quickly compile and deploy custom hardware description language (HDL) code to a Moku device. MCC opens the FPGA within the Moku to custom code and allows specific functions and features. We provide a range of examples and support to help you to deploy custom functions.
Moving average filter
A moving average filter is an average of a number (n) of consecutive signal samples. The equation is:
Where x(t) is the discrete time series input signal and y(t) is the output signal. For example, when n = 4:
This simple filter has a very useful application in the reduction of noise in a signal. For uncorrelated, random (white) noise, this moving average function is optimal for rejecting the noise and retains a sharp step response, while having poor stopband performance. Implementing this in hardware requires only adders and one division, and is thus useful with limited hardware resources. Division by an arbitrary number in hardware is not simple in an FPGA. Typically, this filter is implemented by ensuring n is a power of 2 (i.e., n = 2^N), reducing the division to a right shift of N binary bits.
A direct hardware implementation would look like Figure 2.
This implementation would require 2^N adders and is expensive in terms of hardware.
The deep series of adders would also likely require clocked registers to meet reasonable timing performance. We can improve upon this by realizing:
Thus, more generally illustrated in Figure 3:
This recognizes that each output depends upon the prior output and the current input. We have now reduced the moving average function to an accumulator, one subtraction function, and an n-stage shift register followed by a bitwise right shift for the 2^N division (Figure 3). When N > 4, this hardware saving becomes significant, and the limiting factor is the 2^N stage shifter register. Additionally, no further clocked elements are needed to meet timing constraints.
Figure 4 shows the core of the VHDL implementation. The core of this filter is very simple and has just 12 lines of code. The p_moving_average is the time history of the last N samples, where line 8 is prepending the newest input and dropping the oldest. On line 9, the accumulator r_acc is adding the new input, while line 10 is generating the bitwise shift (divider) needed for the output.
Compile and deploy
It’s easy to compile and synthesize this VHDL code. To get started, visit compile.liquidinstruments.com, upload the code, and select build. The Liquid Instruments server will produce a file or bitstream that defines the hardware configuration needed on the FPGA to implement the code. For Moku:Go, compilation takes approximately 5 minutes; for Moku:Pro, it’s closer to 20 minutes due to the much larger size of the FPGA.
Detailed instructions to guide you through the compilation and deployment are available here.
Testing the MCC moving average filter
To test this moving average filter, we use Multi-instrument Mode (MiM) for Moku:Go as shown in Figure 5. In this mode, we can deploy two instruments with a sample rate of 31.25 MHz. We could also test this filter on Moku:Pro, which allows for four simultaneous instruments in MiM and an input sample rate of 312.5 MHz.
Slot 1 contains our Cloud Compile moving average filter, while Slot 2 contains the Oscilloscope instrument. We use the Oscilloscope to observe the filtered and unfiltered signals fed from analog-to-digital (ADC) Input 1. The Oscilloscope also has an integrated waveform generator, used to generate the test signals. In this case, we generated a square wave using the Oscilloscope’s built-in waveform generator at 2 kHz and drove it to Output 1. We attenuated the signal by 60 dB externally, driving it close toward the noise floor of Moku:Go. We then routed this signal back into Input 1.
In Figure 6, we can see the noisy square wave after attenuation in the blue trace. The red trace shows the moving averager’s output with a significantly cleaner square wave. This is a useful noise reduction technique that has been enabled using one slot of MCC and MiM.
Turning our attention to the noise power, we know that this average filter reduces the noise power by a factor of 2^N; the noise amplitude is reduced by √2^N. Our implementation uses N=8, so noise amplitude should be reduced to 6.25% (1/16) of the original.
This simplest of filters has thus been useful in reducing noise. It is also very computationally light, requiring only an accumulator, subtractor, and bit-wise shift. This means it can run at very high speed, at 312.5 MSa/s on Moku:Pro or 31.25 MSa/s on Moku:Go.
Figure 7 shows the Moku:Go input noise (blue trace) and the moving average signal (red trace) with amplitudes of 161.2 mV and 9.162 mV, respectively. From this, we can see that the amplitude of the noise after the filter is close to the expected factor of 1/16 the original noise, or 9.162 / 161.2 mV = 0.057. This filter is operating and meets our expectations.
We can readily determine the frequency response of the moving average filter using the Moku Frequency Response Analyzer (FRA) instrument. The FRA drives a frequency-swept sine wave on its output and measures the resulting amplitude and phase on its input. Figure 8 shows the test setup:
Figure 9 shows the resulting frequency response of the MCC filter. Comparing this with Figure 10, a MATLAB plot of an ideal moving average filter, we see that the moving average filter does not provide a particularly good stopband attenuation.
Figure 11 shows the implementation of a five-point median function in VHDL. At each rising edge of the clock signal, the function in Figure 11 sorts the five input samples from low to high values. This sorting occurs in the two nested “for” loops from lines 12 to 20. Thus, the median is the third sample in the sorted window; this is assigned to the output on line 22.
We can analyze the time domain performance of the median filter in the same manner as the moving average filter by using the Oscilloscope and Cloud Compiler slots with the Waveform Generator of the Oscilloscope.
Figure 12 shows significantly reduced noise peaks, lowering the peak-to-peak measurement of unfiltered noise from 3.66 mV to 305 μV after filtering. This is a reduction of 1/12, which is not as effective as the moving average filter (1/16).
Since a key function of the median filter is to remove impulse noise, we also examine its performance with a square wave with added impulses. Figure 13 shows a square wave with a leading-edge spike and a spike halfway through the low level (blue channel B); the filtered signal shows the square wave after the median filter has removed the spikes, but retained the sharp lead and trailing edges (red channel A).
We compiled and tested this median filter on Moku:Go, which has an MCC clock rate of 31.25 MHz. However, when testing this example for Moku:Pro, we needed to adjust our implementation due to an increased clock rate of 312.5 MHz. The implementation in Figure 11 uses a nested-for loop with variables. This synthesizes to a large and deep combinatorial logic net with a propagation delay (Figure 14) that exceeds the 3.2 ns period of the Moku:Pro clock rate. To meet timing, the propagation delay of the logic between clocked elements must be less than the clock period.
We need to break the large logic block into segments separated by registers or clocked elements. In VHDL, we achieve this by using signals as opposed to variables. In this case, we break the logic into five stages for ease of coding. This means that there is an input-to-output latency of around five clock cycles, which is suitable for our application.
Figure 15 shows one stage of this five-stage pipelined median algorithm. Find the full VHDL in the project files available for download here.
Moku: Pro median filter testing
We use the Moku:Pro in MiM with an Arbitrary Waveform Generator (AWG) to create a square wave with noise spikes. We then connect the output of the AWG to the MCC median filter and observe the effects with an Oscilloscope.
This MiM setup is shown in Figure 16. We configured the AWG as shown in Figure 17. Its output is driving an analog signal to Output 3 of the Moku:Pro, which in turn is looped via a co-ax cable to Input 3. The median filter is deployed in the Cloud Compile slot and the Oscilloscope is used to observe the performance.
Finally, we observe the median filter’s performance, shown in Figure 18. The median filter has removed the impulses while retaining the sharp edges of the square wave. The processing delay caused by the insertion of the staged, clocked pipeline leads to approximately 44 ns latency.
In this application note, we discussed the implementation of a moving average filter and a median filter. To implement these, we utilized Moku Cloud Compile to build our filters and deployed them to a Moku:Go. We then modified our design to ensure compatibility with the increased Moku:Pro clock rate. To validate the MCC filter behaviors, we used Multi-instrument Mode to connect the fully customizable filters, an Oscilloscope, and an Arbitrary Waveform Generator. This implementation enables efficient noise reduction while preserving signal edges in digital signal processing applications.
The code for the application note is available for download here.