FPGA Decimation Filter Design for Range Bin Reduction in the PLFM RADAR
The PLFM RADAR project implements a dedicated FPGA decimator in 9_Firmware/9_2_FPGA/range_bin_decimator.v that compresses 1024 matched-filter range bins into 64 bins using configurable simple decimation, peak detection, or averaging modes.
The FPGA decimation filter design for range bin reduction is a critical stage in the PLFM RADAR signal-processing chain. Developed in the NawfalMotii79/PLFM_RADAR repository, this Verilog module shrinks the high-resolution range profile before Doppler processing, cutting downstream FPGA resource usage and data bandwidth. It sits immediately after the matched filter and feeds directly into the MTI and Doppler FFT stages.
Architecture of the FPGA Decimation Filter for Range Bin Reduction
The decimator is parameterized with INPUT_BINS = 1024, OUTPUT_BINS = 64, and a fixed DECIMATION_FACTOR = 16. All core logic resides in 9_Firmware/9_2_FPGA/range_bin_decimator.v, where a five-state finite-state machine (FSM) consumes validated complex samples and emits one decimated bin for every 16 input samples.
Decimation Modes
The decimation_mode input selects how each 16-sample group is collapsed into a single output bin. According to lines 9–13 of range_bin_decimator.v, the supported modes are:
- Simple decimation (
2'b00) – Takes the centre sample of each 16-sample group. - Peak detection (
2'b01) – Selects the sample with the largest magnitude, approximated as`|I| + |Q|`, within the group. - Averaging (
2'b10) – Sums the 16 signed I/Q samples and right-shifts by 4 to divide by 16. - Reserved (
2'b11) – Currently unused.
Configurable Region of Interest
The start_bin parameter, described in lines 22–27, lets the operator skip an initial portion of the 1024-sample range profile. This enables region-of-interest processing; for example, discarding far-range bins to focus only on near-range targets.
State Machine and Watchdog Protection
A five-state FSM—ST_IDLE, ST_SKIP, ST_PROCESS, ST_EMIT, and ST_DONE—orchestrates the pipeline (lines 85–92). To prevent indefinite stalls, a watchdog counter with WATCHDOG_LIMIT = 256 clock cycles monitors the input stream. If no valid sample arrives within that window, the logic forces a return to ST_IDLE, as implemented in lines 69–73 and 246–255.
Output Interface
The module exposes the following outputs on lines 46–51:
range_i_outandrange_q_out– The decimated complex sample.range_valid_out– A one-cycle pulse signalling a new valid bin.range_bin_index– A 6-bit counter ranging from 0 to 63.
How the Decimation Filter Processes Samples
Input Handling and Start-Bin Skipping
Each validated sample from the matched filter advances the FSM. When start_bin is greater than zero, the state machine enters ST_SKIP and discards samples until the programmed offset is reached, effectively shifting the processing window.
Group Processing
Once in ST_PROCESS, the module handles groups of exactly 16 samples. Depending on the selected mode, it either latches the centre sample, tracks the maximum-magnitude sample using running registers (peak_i, peak_q, peak_mag), or accumulates signed I/Q values in sum_i and sum_q.
Emission and Frame Completion
After the 16th sample of a group is processed, the FSM transitions to ST_EMIT. It asserts range_valid_out, drives the selected I/Q data onto range_i_out and range_q_out, and updates range_bin_index. Once 64 output bins have been emitted, the state machine moves to ST_DONE and then returns to ST_IDLE, preparing for the next chirp acquisition.
Integration in the Radar Signal Chain
As documented in 9_Firmware/9_3_GUI/v7/software_fpga.py at lines 190–194, the decimator fits into the FPGA data-flow between the range and Doppler domains:
quantize → range_fft → decimator → MTI → doppler_fft → ...
This placement ensures that the Doppler FFT engine operates on a manageable 64-bin range profile rather than the full 1024-bin matched-filter output.
Verilog and Python Code Examples
Verilog Instantiation
You can instantiate the decimator in a top-level receiver module such as radar_receiver_final.v as follows:
range_bin_decimator #(
.INPUT_BINS (1024),
.OUTPUT_BINS (64),
.DECIMATION_FACTOR (16)
) u_range_bin_decimator (
.clk (clk_400m),
.reset_n (reset_n),
.range_i_in (mf_i_out),
.range_q_in (mf_q_out),
.range_valid_in (mf_valid_out),
.range_i_out (decim_i_out),
.range_q_out (decim_q_out),
.range_valid_out (decim_valid_out),
.range_bin_index (decim_bin_index),
.decimation_mode (decim_mode), // 2'b00, 2'b01, or 2'b10
.start_bin (10'd0), // No skip by default
.watchdog_timeout (watchdog_timeout)
);
Python GUI Configuration
The host software configures the decimator and retrieves results through the STM32-mediated register map, as shown in software_fpga.py:
# Configure decimation mode (0 = simple, 1 = peak, 2 = average)
self.fpga.write_register('range_decim_mode', 0b01) # peak detection
# Trigger a new chirp and wait for decimated output
self.fpga.start_acquisition()
# Collect the 64 decimated bins (I/Q pairs)
dec_i = []
dec_q = []
while len(dec_i) < 64:
resp = self.fpga.read_register('range_decim_data')
if resp['valid']:
dec_i.append(resp['i'])
dec_q.append(resp['q'])
The range_decim_mode register maps directly to the decimation_mode input of the Verilog module, while range_decim_data returns the decimated complex samples.
Verification Testbenches
The design is validated by two dedicated testbenches in the 9_Firmware/9_2_FPGA/tb/ directory:
tb_range_bin_decimator.v– A unit-level testbench that exercises all four decimation modes using synthetic ramps, injected peaks, and known averaging patterns.tb_fullchain_realdata.v– A system-level integration testbench that verifies the decimator inside the complete radar chain. The check call on line 613 confirms that exactly 64 decimated range bins are produced per frame before Doppler processing begins.
Both simulations report pass/fail status based on whether the range bin decimator emits the expected 64-bin sequence.
Summary
- The PLFM RADAR decimator in
range_bin_decimator.vreduces 1024 matched-filter range bins to 64 using a fixed decimation factor of 16. - Three active modes are available: simple centre-sample selection, peak detection based on
`|I| + |Q|`, and arithmetic averaging via right-shift division. - A five-state FSM with a 256-cycle watchdog prevents pipeline lockups when input data stalls.
- The block is inserted between the matched filter and the Doppler FFT, with runtime configuration handled through the Python driver in
software_fpga.py. - Dedicated testbenches verify both standalone behaviour and full-chain integration.
Frequently Asked Questions
What decimation factor does the PLFM RADAR FPGA decimator use?
The module decimates by a factor of 16, converting 1024 input range bins into 64 output bins. These values are set by the INPUT_BINS, OUTPUT_BINS, and DECIMATION_FACTOR parameters in range_bin_decimator.v.
How does the range bin decimator prevent pipeline lockups?
A watchdog timer with a WATCHDOG_LIMIT of 256 clock cycles monitors the input stream and forces the state machine back to ST_IDLE if no valid sample arrives within that window. This safety mechanism is implemented in lines 69–73 and 246–255 of range_bin_decimator.v.
Which decimation mode should I use for capturing strong targets versus uniform power estimation?
Select peak detection (2'b01) to retain the sample with the largest magnitude within each 16-sample group, which preserves strong localized returns. Choose averaging (2'b10) when you need the arithmetic mean of the group, computed by summing signed I/Q values and right-shifting by four.
How is the decimator configured and monitored from the host software?
The Python GUI driver in software_fpga.py writes the range_decim_mode register to select the operating mode and reads the range_decim_data register to capture the 64 decimated I/Q bins per chirp. This register map connects through the STM32-mediated FPGA interface, allowing real-time configuration without re-synthesizing the bitstream.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →