feat: CI test suite phases A+B, WaveformConfig separation, dead golden code cleanup

- Phase A: Remove self-blessing golden test from FPGA regression, wire
  MF co-sim (4 scenarios) into run_regression.sh, add opcode count guards
  to cross-layer tests (+3 tests)
- Phase B: Add radar_params.vh parser and architectural param consistency
  tests (+7 tests), add banned stale-value pattern scanner (+1 test)
- Separate WaveformConfig.range_resolution_m (physical, bandwidth-dependent)
  from bin_spacing_m (sample-rate dependent); rename all callers
- Remove 151 lines of dead golden generate/compare code from
  tb_radar_receiver_final.v; testbench now runs structural + bounds only
- Untrack generated MF co-sim CSV files, gitignore tb/golden/ directory

CI: 256 tests total (168 python + 40 cross-layer + 27 FPGA + 21 MCU), all green
This commit is contained in:
Jason
2026-04-15 12:45:41 +05:45
parent 05d1f8c26b
commit e8b495ce6f
17 changed files with 466 additions and 8410 deletions
+18 -6
View File
@@ -105,11 +105,11 @@ class RadarSettings:
tab and Opcode enum in radar_protocol.py. This dataclass holds only
host-side display/map settings and physical-unit conversion factors.
range_resolution and velocity_resolution should be calibrated to
range_bin_spacing and velocity_resolution should be calibrated to
the actual waveform parameters.
"""
system_frequency: float = 10.5e9 # Hz (PLFM TX LO, verified from ADF4382 config)
range_resolution: float = 24.0 # Meters per decimated range bin (c/(2*100MSPS)*16)
range_bin_spacing: float = 24.0 # Meters per decimated range bin (c/(2*100MSPS)*16)
velocity_resolution: float = 2.67 # m/s per Doppler bin (lam/(2*32*167us))
max_distance: float = 1536 # Max detection range (m) -- 64 bins x 24 m (3 km mode)
map_size: float = 1536 # Map display size (m)
@@ -216,18 +216,30 @@ class WaveformConfig:
decimation_factor: int = 16 # 1024 → 64
@property
def range_resolution_m(self) -> float:
def bin_spacing_m(self) -> float:
"""Meters per decimated range bin (matched-filter receiver).
For matched-filter pulse compression: bin spacing = c / (2 * fs).
After decimation the bin spacing grows by *decimation_factor*.
This is independent of chirp bandwidth (BW affects resolution, not
bin spacing).
This is independent of chirp bandwidth (BW affects physical
resolution, not bin spacing).
"""
c = 299_792_458.0
raw_bin = c / (2.0 * self.sample_rate_hz)
return raw_bin * self.decimation_factor
@property
def range_resolution_m(self) -> float:
"""Physical range resolution in meters, set by chirp bandwidth.
range_resolution = c / (2 * BW).
At 20 MHz BW → 7.5 m; at 30 MHz BW → 5.0 m.
This is distinct from bin_spacing_m (which depends on sample rate
and decimation factor, not bandwidth).
"""
c = 299_792_458.0
return c / (2.0 * self.bandwidth_hz)
@property
def velocity_resolution_mps(self) -> float:
"""m/s per Doppler bin. lambda / (2 * n_doppler * PRI)."""
@@ -238,7 +250,7 @@ class WaveformConfig:
@property
def max_range_m(self) -> float:
"""Maximum unambiguous range in meters."""
return self.range_resolution_m * self.n_range_bins
return self.bin_spacing_m * self.n_range_bins
@property
def max_velocity_mps(self) -> float:
+4 -4
View File
@@ -490,7 +490,7 @@ def polar_to_geographic(
def extract_targets_from_frame(
frame,
range_resolution: float = 1.0,
bin_spacing: float = 1.0,
velocity_resolution: float = 1.0,
gps: GPSData | None = None,
) -> list[RadarTarget]:
@@ -503,8 +503,8 @@ def extract_targets_from_frame(
----------
frame : RadarFrame
Frame with populated ``detections``, ``magnitude``, ``range_doppler_i/q``.
range_resolution : float
Meters per range bin.
bin_spacing : float
Meters per range bin (bin spacing, NOT bandwidth-limited resolution).
velocity_resolution : float
m/s per Doppler bin.
gps : GPSData | None
@@ -525,7 +525,7 @@ def extract_targets_from_frame(
mag = float(frame.magnitude[rbin, dbin])
snr = 10.0 * math.log10(max(mag, 1.0)) if mag > 0 else 0.0
range_m = float(rbin) * range_resolution
range_m = float(rbin) * bin_spacing
velocity_ms = float(dbin - doppler_center) * velocity_resolution
lat, lon, azimuth, elevation = 0.0, 0.0, 0.0, 0.0
+3 -3
View File
@@ -169,7 +169,7 @@ class RadarDataWorker(QThread):
The FPGA already does: FFT, MTI, CFAR, DC notch.
Host-side DSP adds: clustering, tracking, geo-coordinate mapping.
Bin-to-physical conversion uses RadarSettings.range_resolution
Bin-to-physical conversion uses RadarSettings.range_bin_spacing
and velocity_resolution (should be calibrated to actual waveform).
"""
targets: list[RadarTarget] = []
@@ -180,7 +180,7 @@ class RadarDataWorker(QThread):
# Extract detections from FPGA CFAR flags
det_indices = np.argwhere(frame.detections > 0)
r_res = self._settings.range_resolution
r_res = self._settings.range_bin_spacing
v_res = self._settings.velocity_resolution
for idx in det_indices:
@@ -559,7 +559,7 @@ class ReplayWorker(QThread):
# Target extraction
targets = self._extract_targets(
frame,
range_resolution=self._waveform.range_resolution_m,
bin_spacing=self._waveform.bin_spacing_m,
velocity_resolution=self._waveform.velocity_resolution_mps,
gps=self._gps,
)