MTI (Moving Target Indication) Canceller Implementation for Clutter Suppression in Radar Systems

The AERIS‑10 radar implements a two‑stage MTI canceller—combining a hardware 2‑pulse FPGA module with a host‑side Python processor—to remove stationary clutter before Doppler processing.

Modern pulsed radar systems face severe performance degradation from ground clutter—returns from stationary objects like buildings, terrain, and vegetation. The MTI (Moving Target Indication) canceller is the standard digital signal processing technique that solves this by subtracting successive pulse returns, exploiting the fact that stationary clutter remains correlated while moving targets shift in phase. This article examines the complete MTI implementation in the NawfalMotii79/PLFM_RADAR open‑source repository, which provides both real‑time FPGA hardware and flexible host‑side reprocessing capabilities.

Hardware MTI Canceller in Verilog FPGA

The primary MTI canceller runs in the FPGA fabric between the range‑bin decimator and the Doppler processor. Located at 9_Firmware/9_2_FPGA/mti_canceller.v, this module implements a first‑order 2‑pulse canceller with transfer function H(z) = 1 − z⁻¹.

2‑Pulse Canceller Algorithm

For each range bin, the module subtracts the previous chirp's I/Q sample from the current sample:


# Equivalent Python representation of the hardware operation

y_i[n] = x_i[n] - x_i[n-1]   # In-phase component

y_q[n] = x_q[n] - x_q[n-1]   # Quadrature component

In the Verilog source, this appears as direct subtraction between current and delayed samples across the line range 9_Firmware/9_2_FPGA/mti_canceller.v#L17-L20. The delay element stores one complete range profile (all range bins) from the previous transmitted chirp.

First‑Chirp Handling and Latency Management

A critical edge case occurs when MTI is first enabled: no previous chirp exists. The hardware handles this by outputting zeros for the entire first chirp—effectively muting the output until valid history accumulates. This behavior spans 9_Firmware/9_2_FPGA/mti_canceller.v#L24-L25 and L38-L50, ensuring clean initialization without transient artifacts.

The module also provides pass‑through mode: when the MTI_ENABLE register (opcode 0x26) is cleared, input data routes directly to output with zero additional latency. This is implemented at 9_Firmware/9_2_FPGA/mti_canceller.v#L30-L33.

FPGA Resource Footprint

The hardware MTI canceller is extremely lightweight:

  • Two BRAM instances: 64 × 16‑bit for I and Q delay lines
  • ≈30 LUTs for control logic and arithmetic
  • Zero DSP48 blocks (subtraction uses fabric logic)

This resource profile at 9_Firmware/9_2_FPGA/mti_canceller.v#L30-L34 makes the canceller suitable for even small FPGAs while maintaining real‑time throughput.

Software Control and Host‑Side Reprocessing

The FPGA MTI is controlled through Python register maps, while captured data can be re‑processed with enhanced flexibility.

FPGA Register Interface

The RadarFPGA class in 9_Firmware/9_3_GUI/v7/software_fpga.py exposes MTI control:

from 9_Firmware.9_3_GUI.v7.software_fpga import RadarFPGA

fpga = RadarFPGA()
fpga.set_mti_enable(True)   # Writes opcode 0x26 = 1

The register mapping appears at 9_Firmware/9_3_GUI/v7/software_fpga.py#L86-L88, where self.mti_enable corresponds to opcode 0x26. The dashboard GUI in dashboard.py (lines 672–683) provides toggle buttons for "Enable MTI" and "Disable MTI".

Host-Side MTI Filter with Variable Order

The Python processor in 9_Firmware/9_3_GUI/v7/processing.py extends MTI capability with configurable filter orders (1st through 3rd), useful for offline analysis and algorithm experimentation.

Configuration is managed through ProcessingConfig at 9_Firmware/9_3_GUI/v7/models.py#L49-L52:

Parameter Type Description
mti_enabled bool Enable/disable MTI filtering
mti_order int (1–3) Canceller order: 1, 2, or 3 pulses

Filter Equations and Implementation

The RadarProcessor.mti_filter method (9_Firmware/9_3_GUI/v7/processing.py#L33-L60) implements three canceller types:

Order Difference Equation Clutter Roll‑Off
1 y[n] = x[n] − x[n−1] 6 dB/octave
2 y[n] = x[n] − 2x[n−1] + x[n−2] 12 dB/octave
3 y[n] = x[n] − 3x[n−1] + 3x[n−2] − x[n−3] 18 dB/octave

Higher orders provide steeper clutter attenuation but require more history frames and reduce usable Doppler bandwidth. The processor maintains up to three previous frames in _mti_history (9_Firmware/9_3_GUI/v7/processing.py#L66-L68) to support this flexibility.

History Initialization Handling

Matching hardware behavior, the Python implementation returns zero frames when insufficient history exists (9_Firmware/9_3_GUI/v7/processing.py#L49-L52). This ensures bit‑exact reproducibility when validating FPGA operation against host‑side replay.

Signal Processing Pipeline Integration

MTI occupies a specific position in the end‑to‑end radar processing chain:


Raw ADC → Range-bin Decimator → [MTI Canceller] → Doppler FFT
                                            ↓
DC Notch → Windowing → [MTI (host replay)] → Power Computation → CFAR → Detection

In live operation, the FPGA MTI runs immediately after range compression. During host‑side replay, MTI in process_frame (9_Firmware/9_3_GUI/v7/processing.py#L53-L56) applies after DC‑notch and windowing, before power computation and CFAR detection.

Practical Configuration Examples

Enable Second‑Order MTI for Offline Analysis

from 9_Firmware.9_3_GUI.v7.models import ProcessingConfig
from 9_Firmware.9_3_GUI.v7.processing import RadarProcessor

cfg = ProcessingConfig()
cfg.mti_enabled = True      # Enable host-side filtering

cfg.mti_order = 2           # 2-pulse canceller (steeper than FPGA default)

proc = RadarProcessor()
proc.set_config(cfg)        # Resets internal MTI history

# Process recorded frame: shape [range_bins, doppler_bins]

filtered_rdm, detection_mask = proc.process_frame(raw_frame)

Disable MTI for Clutter Analysis

cfg.mti_enabled = False     # Pass-through mode

proc.set_config(cfg)

# Compare with/without to quantify clutter suppression

raw_rdm, _ = proc.process_frame(raw_frame)

Key Design Files Reference

File Purpose Critical Lines
9_Firmware/9_2_FPGA/mti_canceller.v Hardware 2‑pulse canceller 14–21 (architecture), 24–25, 38–50 (initialization)
9_Firmware/9_3_GUI/v7/software_fpga.py FPGA register control 86–88 (MTI enable mapping)
9_Firmware/9_3_GUI/v7/models.py Configuration dataclasses 49–52 (ProcessingConfig)
9_Firmware/9_3_GUI/v7/processing.py Host-side filter implementation 33–60 (mti_filter), 53–56 (pipeline placement)
9_Firmware/9_3_GUI/v7/dashboard.py GUI controls 672–683 (toggle buttons)

Summary

  • Dual implementation: The AERIS‑10 radar provides MTI in both FPGA hardware (real‑time, fixed 1st‑order) and Python software (replay, orders 1–3)
  • Hardware location: mti_canceller.v sits between range decimation and Doppler FFT with H(z) = 1 − z⁻¹
  • Resource efficiency: FPGA implementation uses only 2 BRAMs and ~30 LUTs, no DSP blocks
  • Control interface: Opcode 0x26 enables/disables hardware MTI via software_fpga.py
  • Extended capability: Host processor supports 2nd and 3rd‑order cancellers for enhanced clutter suppression during offline analysis
  • Initialization safety: Both implementations mute output when history is unavailable, preventing transient artifacts

Frequently Asked Questions

What is the difference between hardware and software MTI in this radar?

The FPGA MTI (mti_canceller.v) runs in real‑time with fixed 1st‑order 2‑pulse cancellation, optimized for latency and resource usage. The Python MTI (processing.py) operates during replay with configurable orders 1–3, enabling algorithm experimentation and hardware validation. Both share identical initialization behavior (muted first chirp) but the software version offers steeper clutter roll‑off at the cost of reduced Doppler bandwidth.

Why does the first chirp produce zero output when MTI is enabled?

The 2‑pulse canceller requires a previous chirp reference to compute the difference. With no history available, the hardware and software implementations intentionally output zeros rather than pass unprocessed data. This prevents transient clutter leakage and maintains predictable system behavior. Valid MTI output begins with the second transmitted chirp.

How much FPGA resources does the MTI canceller consume?

According to mti_canceller.v lines 30–34, the implementation uses two 64×16‑bit BRAMs (one each for I and Q delay lines) and approximately 30 LUTs for address generation, subtraction, and control logic. No DSP48 blocks are required, making the design portable to cost‑sensitive FPGA families.

Can I use higher-order MTI filters in real-time operation?

The current FPGA implementation supports only 1st‑order cancellation. Higher‑order filters (2nd or 3rd) require host‑side post‑processing via RadarProcessor in Python. To deploy 2nd‑order MTI in hardware, the Verilog module would need extension to additional BRAM stages and binomial coefficient multiplication, which would increase latency and resource usage proportionally.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →