LFM Chirp Timing Parameters and Pulse Repetition Interval Configuration in PLFM_RADAR

The PLFM_RADAR system implements a 167 µs pulse repetition interval using coordinated FPGA Verilog timing constants and Python configuration dataclasses that define long (30 µs) and short (0.5 µs) LFM chirps with interleaved listen and guard periods.

The NawfalMotii79/PLFM_RADAR repository implements a coherent radar waveform generator that pairs linear-frequency-modulated (LFM) chirp generation in FPGA firmware with host-side Python configuration. Understanding the LFM chirp timing parameters and pulse repetition interval configuration is essential for modifying system behavior or adapting the design to different radar requirements.

FPGA-Side Timing Implementation in Verilog

The core timing logic resides in 9_Firmware/9_2_FPGA/plfm_chirp_controller.v. This module implements a finite state machine (FSM) that sequences through five distinct timing states to complete one full PRI cycle.

Timing Constants Defined in plfm_chirp_controller.v

Parameter Value Duration Purpose
T1_SAMPLES 3600 30 µs Long chirp transmission at 120 MHz clock
T1_RADAR_LISTENING 16440 137 µs Listen period after long chirp
GUARD_SAMPLES 21048 175.4 µs Guard interval between chirp types
T2_SAMPLES 60 0.5 µs Short chirp transmission
T2_RADAR_LISTENING 20940 174.5 µs Listen period after short chirp

The pulse repetition interval (PRI) derives from the sum of all active states:


PRI = T1_SAMPLES + T1_RADAR_LISTENING + GUARD_SAMPLES + T2_SAMPLES + T2_RADAR_LISTENING
PRI = 3600 + 16440 + 21048 + 60 + 20940 = 62088 samples
PRI = 62088 / 120 MHz = 167 µs

FSM State Transitions

The get_max_counter helper function (lines 42-55) determines when each state expires. The state machine cycles through:


LONG_CHIRP → LONG_LISTEN → GUARD_TIME → SHORT_CHIRP → SHORT_LISTEN → (repeat)

Each state assertion controls DAC output enable and ADC sample gating.

Chirp Count Configuration

The constant CHIRP_MAX = 32 defines total chirps per beam position. The design alternates long and short chirps, yielding 16 long and 16 short chirps per coherent processing interval (CPI).

Host-Side Configuration Using WaveformConfig

The Python dataclass WaveformConfig in 9_Firmware/9_3_GUI/v7/models.py mirrors the FPGA timing for consistent physical-unit calculations.

Core Timing Fields

from models import WaveformConfig

cfg = WaveformConfig()

print(f"chirp_duration_s: {cfg.chirp_duration_s}")  # 30e-6

print(f"pri_s: {cfg.pri_s}")                        # 167e-6

print(f"sample_rate_hz: {cfg.sample_rate_hz}")      # 100e6
Field Default Value Physical Meaning
chirp_duration_s 30e-6 s Long chirp ramp time
pri_s 167e-6 s Complete PRI (matches FPGA)
sample_rate_hz 100e6 Hz Post-DDC baseband rate
bandwidth_hz 20e6 Hz LFM sweep bandwidth
chirps_per_subframe 16 Chirps per Doppler sub-frame
n_doppler_bins 32 Total Doppler bins (2 sub-frames × 16)

Derived Resolution Properties

Lines 22-41 of models.py implement resolution calculations:

cfg = WaveformConfig()

# Range resolution from bandwidth

print(f"range_resolution_m: {cfg.range_resolution_m}")  # ~7.5 m

# Velocity resolution from PRI and chirp count

print(f"velocity_resolution_mps: {cfg.velocity_resolution_mps}")

The velocity resolution uses the standard formula:

$$v_{\text{res}} = \frac{\lambda}{2 \cdot N_{\text{chirps}} \cdot \text{PRI}}$$

Where N_chirps = 16 (per sub-frame) and λ derives from the carrier frequency.

Practical Code Examples

Verify Configuration Consistency

from models import WaveformConfig

wf = WaveformConfig()

# Assert FPGA-Python timing alignment

assert wf.chirp_duration_s == 30e-6, "Long chirp mismatch with T1_SAMPLES"
assert wf.pri_s == 167e-6, "PRI mismatch with FPGA sum"

# Calculate expected FPGA samples

clock_hz = 120e6
expected_t1 = int(wf.chirp_duration_s * clock_hz)  # 3600

expected_pri = int(wf.pri_s * clock_hz)            # 62088

print(f"T1 samples: {expected_t1}, PRI samples: {expected_pri}")

Simulate Complete Chirp Sequence

import numpy as np

# Load FPGA LUTs

long_lut = np.loadtxt('9_Firmware/9_2_FPGA/long_chirp_lut.mem', 
                      dtype=np.uint16, base=16)

# Build timing segments (matching Verilog parameters)

t1 = long_lut[:3600]                           # long chirp

listen1 = np.zeros(16440, dtype=np.int16)      # T1 listening

guard = np.zeros(21048, dtype=np.int16)        # guard interval

t2 = long_lut[:60]  # placeholder: actual short LUT differs

listen2 = np.zeros(20940, dtype=np.int16)      # T2 listening

full_pri = np.concatenate([t1, listen1, guard, t2, listen2])
print(f"Simulated PRI: {len(full_pri)} samples = {len(full_pri)/120e6*1e6:.1f} µs")

Access Register-Level Timing Updates

The radar_protocol.py module handles host-to-FPGA communication. While timing constants are typically fixed at compile time, the protocol supports runtime register access:


# From 9_Firmware/9_3_GUI/v7/radar_protocol.py structure

# (Actual opcode values defined in source)

class RadarProtocol:
    REG_T1_SAMPLES = 0x10      # hypothetical; verify in source

    REG_PRI_HIGH = 0x11
    REG_PRI_LOW = 0x12
    
    def set_timing(self, t1_samples: int, pri_samples: int):
        self.write_register(self.REG_T1_SAMPLES, t1_samples)
        self.write_register(self.REG_PRI_HIGH, pri_samples >> 16)
        self.write_register(self.REG_PRI_LOW, pri_samples & 0xFFFF)

Key Source Files for Timing Configuration

Path Function
9_Firmware/9_2_FPGA/plfm_chirp_controller.v Verilog FSM implementing chirp sequencing and PRI timing
9_Firmware/9_3_GUI/v7/models.py WaveformConfig dataclass with SI-unit timing and resolutions
9_Firmware/9_3_GUI/v7/radar_protocol.py Register access protocol for FPGA communication
9_Firmware/9_3_GUI/v7/test_v7.py Unit tests validating default timing values
9_Firmware/9_2_FPGA/long_chirp_lut.mem Waveform lookup table for DAC playback

Summary

  • PRI = 167 µs derives from five sequential timing blocks in plfm_chirp_controller.v
  • FPGA timing uses sample counts at 120 MHz; Python uses WaveformConfig for physical units
  • Long chirp: 30 µs / 3600 samples; short chirp: 0.5 µs / 60 samples
  • 32 total chirps per CPI (16 long, 16 short) with alternating pattern
  • Consistency between FPGA and host is enforced through matching default values in models.py and explicit unit tests

Frequently Asked Questions

How is the 167 µs PRI calculated in PLFM_RADAR?

The PRI equals the sum of all state durations in plfm_chirp_controller.v: T1_SAMPLES (3600) + T1_RADAR_LISTENING (16440) + GUARD_SAMPLES (21048) + T2_SAMPLES (60) + T2_RADAR_LISTENING (20940) = 62088 samples. At the 120 MHz system clock, this yields 62088 / 120e6 = 167 µs exactly.

Why does the design use both long and short LFM chirps?

The long chirp (30 µs) provides fine range resolution for target detection, while the short chirp (0.5 µs) enables unambiguous velocity measurement and mitigates range-Doppler coupling. The guard interval prevents cross-interference between chirp types.

Where are the timing parameters validated between FPGA and Python?

The test_v7.py module contains assertions that WaveformConfig.chirp_duration_s == 30e-6 and pri_s == 167e-6, ensuring the Python configuration matches the Verilog compile-time constants before system operation.

Can the PRI be modified without recompiling the FPGA bitstream?

No—the timing constants in plfm_chirp_controller.v are compile-time parameters used for state machine counter limits. Changing PRI requires modifying T1_SAMPLES, T1_RADAR_LISTENING, and related constants, then regenerating the bitstream. The host-side WaveformConfig must be updated to match.

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 →