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
WaveformConfigfor 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.pyand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →