mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-10 15:31:21 +00:00
38d6dd0719d7c731ba8d86ea1936b0ae3fa23439
57 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4989c33aa6 |
PR-AB.b expanded commit 5: Beam-ready handshake (RTL + MCU + GUI)
Wire a per-frame MCU→FPGA "beam pattern ready" handshake so the chirp scheduler can stall between 48-chirp frames until the MCU finishes writing the next ADAR1000 pattern. The legacy unused stm32_new_chirp input on PD8 is repurposed as stm32_beam_ready; chirp_scheduler.v gets a new S_BEAM_WAIT state entered after each frame_pulse and an 80 ms watchdog so a missed MCU toggle degrades to wall-clock cadence with a sticky telemetry bit rather than stalling the radar. Cold-reset defaults the handshake off (host_handshake_enable=0, new opcode 0x1A); the GUI opts in once the MCU PD8 wiring is verified on the bench. Both the FT601 and FT2232H status word 4 paths get the new beam_handshake_watchdog_fired sticky at bit [1] (reclaimed from the range_mode retirement in commit 1). RTL: - chirp_scheduler.v: 2-FF ASYNC_REG sync on beam_ready_async; 1-cycle edge detect (any transition, MCU side uses HAL_GPIO_TogglePin); new S_BEAM_WAIT state entered at frame_pulse when host_handshake_enable=1; 23-bit beam_watchdog counter with BEAM_WATCHDOG_MAX = 8_000_000 (~80 ms at 100 MHz, ~10 nominal frames); beam_handshake_watchdog_fired output sticky across mixers_enable cycles, cleared only by reset_n; mid-wait disable releases the FSM so dropping the opcode never strands the radar between frames. - radar_receiver_final.v: thread stm32_beam_ready_async + host_handshake_enable + beam_handshake_watchdog_fired through the scheduler instance. - radar_system_top.v: rename input port stm32_new_chirp → stm32_beam_ready; add host_handshake_enable register (cold-reset = 1'b0); opcode 0x1A dispatch (value[0]); add rx_beam_handshake_watchdog wire; pack into status_words[4][1] in both USB paths. - radar_system_top_50t.v: rename wrapper port + sub-instance wiring. - usb_data_interface.v + usb_data_interface_ft2232h.v: add status_beam_handshake_watchdog input + 2-FF level CDC (same convention as F-6.4 / F-1.2 stickies); refresh word-4 layout doc comment; pack beam_handshake_wd_sync_1 into status_words[4][1]. XDC: - xc7a50t_ftg256.xdc + xc7a200t_fbg484.xdc: rename stm32_new_chirp port references to stm32_beam_ready (same PD8 pin, F13 on 50T / L18 on 200T). MCU: - main.h: add FPGA_BEAM_READY_Pin = GPIO_PIN_8 + FPGA_BEAM_READY_GPIO_Port = GPIOD alongside the existing FPGA_FRAME_PULSE alias. - main.cpp:runRadarPulseSequence: insert HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8) after each setCustomBeamPattern16(RX) — once after the per-azimuth broadside (vector_0), once after matrix1, once after matrix2 — between the SPI burst completion and waitForFramePulse. GUI: - radar_protocol.py: Opcode.HANDSHAKE_ENABLE = 0x1A; StatusResponse.beam_handshake_watchdog = 0 default; parse word 4 bit [1] in parse_status_packet; update word-4 layout comment. - test_GUI_V65_Tk.py: add beam_handshake_watchdog kwarg to _make_status_packet (sets bit [1] of word 4); refresh test_parse_status_word4_layout_co_spec to cover the new bit (used+9=32); add test_parse_status_beam_handshake_watchdog round-trip; test_handshake_enable_opcode pins 0x1A; defaults / chirps_mismatch / agc-coexist tests gain a watchdog==0 assertion; bump test_all_rtl_opcodes_present expected set to include 0x17/0x18/0x19/0x1A. TB: - new tb_chirp_scheduler_handshake.v (16 checks): legacy open-loop, edge exit (rising + falling), 200-cycle idle hold, watchdog auto-advance via force on dut.beam_watchdog, sticky-survives-mixers_disable, mid-wait disable release, reset_n clears sticky. - run_regression.sh: register the new TB in PHASE 1. - tb_radar_receiver_final.v: tie the 3 new receiver ports off (beam_ready_async=0, handshake_enable=0, watchdog unconnected). - tb_system_mechanics.v / tb_system_opcodes.v: explicit .stm32_beam_ready(1'b0) connection (the cold-reset host_handshake_enable=0 keeps the FSM out of S_BEAM_WAIT). - tb_usb_data_interface.v / tb_usb_protocol_v2.v / tb_e2e_dsp_to_host.v / tb_ft2232h_frame_drop.v: tie .status_beam_handshake_watchdog(1'b0). Ride-along ruff sweep (14 → 0 across the repo): - tb/cosim/compare_independent.py: RUF003 — '5×' → 'at least 5x'. - tb/cosim/gen_e2e_expected.py: noqa: E402 on the post-sys.path import; drop unused EXPECTED_RANGE_BIN + EXPECTED_DOPPLER_BIN_PER_SF imports; fold the detect-class slot if/else into a ternary (SIM108). - tb/cosim/gen_e2e_stimulus.py: drop int() wrapping round() at four call sites (RUF046 — round() already returns int in Python 3); rewrite the range-bin derivation comment block from code-like `# range_bin = ...` to prose (ERA001); strip stray f from placeholder-free error string (F541). - tb/cosim/tb_e2e_dsp_to_host_parse.py: open(path, 'r') → open(path) (UP015). - v7/dashboard.py: '3×' → '3x' (RUF003); drop quotes from 'StatusResponse | None' annotation (UP037, file already has `from __future__ import annotations`). CI summary (all suites green pre-commit): - ruff: All checks passed! - FPGA regression (iverilog): 43 / 0 / 0 (incl. new handshake TB 16/16). - MCU tests: 51 / 0 + 34 / 0 + 13 / 13 ADAR1000_AGC. - GUI Tk (test_GUI_V65_Tk): 120 / 0. - GUI v7 (test_v7): 152 / 0. Production rollout note: bitstream cold-resets with host_handshake_enable=0 so existing flashes keep their open-loop cadence until the GUI sends opcode 0x1A=1. Once enabled, the per-pattern dwell tracks both the chirp ladder (PD14 frame_pulse from commit-3 work) and the MCU pattern-write completion (PD8 toggle from this commit), eliminating drift from the SPI burst timing. |
||
|
|
98ec9cb6a5 |
fix(fpga): PR-AA — doppler_mag 1-cell shift in usb emit FSM
The WR_DOPPLER_DATA emit advanced mag_rd_addr at end of phase 1 (LSB byte) but BRAM has 1-cycle read latency, so phase 0 of the next pair re-read the prior cell. Result: wire pair K = (HIGH(bram[K-1]), LOW(bram[K])) — adjacent cells silently swapped their high bytes whenever the high byte differed. Footprint was 30 of 24576 cells (peak rows + high-byte transitions in the noise floor); max diff 6656 LSB on the target row. Fix: advance the BRAM read address at end of phase 0 (MSB) so BRAM has 2 cycles between addr-set and the next pair's MSB read. Same pattern existed in WR_RANGE_DATA — silently broken (regression skips range stream); fixed for symmetry. After fix, both iverilog and remote Vivado 2025.2 xsim emit a bit-exact match against the Python golden. Tighten E12.14 / E12.6.b to strict zero tolerance and rename the "PR-AA pending" notes to indicate the fix has landed. Target-cell window check (E12.15) now points at the exact (rb, db) bin. Verification: * iverilog A6 in-TB: doppler_mismatches=0/24576 (16/16 PASS) * iverilog A6 parse strict: 28/28 PASS * Vivado 2025.2 xsim A6 in-TB: doppler_mismatches=0/24576 (16/16 PASS) * Vivado 2025.2 xsim A6 parse strict: 28/28 PASS * Full regression: 41 passed, 0 failed, 0 skipped / 41 total |
||
|
|
d9e7a5becf |
fix(fpga): PR-Y.1 + PR-X.3 — DC notch boundary, audit cleanups, formal retarget
Bundles audit items unblocked by the AERIS-10 end-to-end audit:
S-1 (radar_system_top.v) — DC notch off-by-one at width=7
Audit S-1: ±W around DC in a 16-bin FFT covers bins {0..W, 16-W..15}
(2W+1 total, bin 8 the only one excluded at W=7). The previous form
`< W || > 15-W+1` missed both boundaries: at W=1 it notched only {0}
(skipping 1 and 15); at W=7 it missed 7 and 9. Replaced with inclusive
comparators against 5-bit limits (`<= notch_lo || >= notch_hi`) which
hit the intended set for all W ∈ {1..7}. Cosim DC golden
(tb/cosim/rtl_bb_dc.csv) regenerated against the corrected behaviour.
S-7 (rx_gain_control.v) — reg→wire for combinational helpers
`wire_frame_sat_incr` / `wire_frame_peak_update` were declared `reg`
and blocking-assigned inside the clocked always block. They are pure
combinational functions of the registered inputs — promoted to
module-scope continuous assigns. Behaviour is bit-identical (the read
inside the always still reflects the prior-cycle latched values) but
the iverilog warnings disappear and the sim/synth correspondence is
unambiguous.
M-9 (formal/fv_radar_mode_controller.sby) — delete orphan
radar_mode_controller.v was retired in PR-D in favour of
chirp_scheduler.v; the .sby was never updated and pointed at a
non-existent module. Deleted.
M-10 (radar_receiver_final.v) — document `data_sync_error` unconnected
In production AD9484 produces a single 8-bit stream that the DDC mixes
into matched I/Q paths with symmetric pipelines, so `ddc_valid_i` and
`ddc_valid_q` rise on the same cycle and `data_sync_error` cannot
fire by construction. The check is retained inside
ddc_input_interface for the standalone tb_ddc_input_interface
unit-test (which intentionally drives valid_i ≠ valid_q). Adds
comments explaining the unconnected port at both call sites; no
functional change.
M-11 (radar_receiver_final.v) — `force_saturation_pulse` symmetric hook
The DDC has a `force_saturation` debug input that previously was tied
1'b0 directly. Routed through a new `force_saturation_pulse` wire
alongside the existing `clear_monitors_pulse` so a future host opcode
surface for "diagnostic force/clear" lands both at the same dispatch
point. Still tied 1'b0 today — RTL change is a placeholder for the
opcode plumbing.
PR-X.3 F-7.5 (formal/fv_cdc_adc.{v,sby}) — retarget to cdc_async_fifo
Prior wrapper instantiated `cdc_adc_to_processing`, retired by
AUDIT-C11 in favour of `cdc_async_fifo` (the production CIC→FIR
boundary CDC, see ddc_400m.v line 646). Wrapper rewritten with
FIFO-shaped equivalents of the original Gray-CDC properties:
P1 reset behaviour, P2 no spurious dst_valid, P3 overrun semantics,
P4 data integrity (cooldown-spaced, FIFO-equivalent of the
original single-element latch property),
P5 bounded liveness (depth 100 gclk),
P6 cover sequences for the basic write→read pipeline.
P4's true multi-in-flight FIFO order proof is left as Option B work;
for the AERIS-10 use case the upstream ddc_400m CIC→FIR consumer
operates below FIFO-fill rate by design, so the cooldown-spacing
assumption is a tight model.
Verification: full FPGA regression 41 / 0 / 0.
|
||
|
|
9c231d85db |
fix(fpga): PR-Z A6 — usb cfar dense bug end-to-end fix + e2e test
The PR-Z A6 e2e test (tb_e2e_dsp_to_host) exposed that the wire-format
cfar_dense map emitted by usb_data_interface_ft2232h was all-zero for
our deterministic single-target stimulus, even though cfar_ca's
in-flight outputs showed CONFIRMED at the expected cells (verified via
in-TB capture, E5/E6 PASS).
Deep instrumented debug (BRAM-WRITE, BRAM-READ, EGRESS-CAP probes)
revealed THREE independent bugs that combined to produce the all-zero
wire output. Each bug alone would have been visible; the way they
compounded made the symptom look like a single coarse failure.
Bug A — stale write address (radar_system_top.v):
usb_inst.range_bin_in/doppler_bin_in were tied to notched_*_bin
(= rx_*_bin = doppler_processor outputs). After doppler returns to
S_IDLE its `output reg`s hold their last-driven values (511, 47).
cfar_ca's CMP-phase emit (cycles ~520..73520 after frame_complete)
fires cfar_valid with detect_range/detect_doppler set to its own
per-cell scan counters, but those outputs were dangling — usb's
RMW saw the doppler stale (511, 47) and slammed every cfar write
to byte_addr {511, 47[5:2]} = bram[8187], past the 6144-byte wire
range entirely.
Fix: register cfar_detect_range/doppler in lockstep with the existing
rx_detect_valid/rx_detect_class registration block (clk_100m_buf
domain), then mux them into usb_inst.range_bin_in/doppler_bin_in on
rx_detect_valid. doppler-magnitude write path is unaffected because
doppler_valid and rx_detect_valid are mutually exclusive (BUFFER vs
CMP phases of cfar_ca).
Bug B — BRAM read pipeline lag (usb_data_interface_ft2232h.v):
The detect_rd_data <= detect_bram[detect_rd_addr] BRAM read port has
1-cycle latency. WR_DETECT_DATA's emit FSM advanced detect_rd_addr
and read detect_rd_data in the SAME edge — so cycle K read bram[K-2]
(the addr from cycle K-1's commit) instead of bram[K-1]. Result:
every cfar wire byte = bram[N-1] instead of bram[N], shifting the
entire 6144-byte detect section +1 byte = +4 doppler bins. Doppler
hides this naturally because its 2-byte-per-cell rhythm gives BRAM a
free settling cycle between addr-set and emit-read.
Fix: pre-load detect_rd_addr <= 1 and det_doppler_byte_idx <= 1 at
every WR_DETECT_DATA entry transition (HDR direct, RANGE direct,
DOPPLER → DETECT). BRAM produces bram[0] for the first emit cycle
(settled since reset because detect_rd_addr was 0 throughout the
preceding section) while the addr advance schedules bram[1] for the
second emit cycle — and from then on the FSM's natural advance
pattern keeps the pipeline aligned, including across the per-range
boundary (det_doppler_byte_idx == DET_BYTE_LAST_PER_RANGE).
Bug C — detect_clearing window overlaps cfar's first 4 columns:
detect_clearing fired 1 cycle after frame_complete and ran for 8192
clk cycles (1 byte/cycle). cfar_valid writes were gated on
`!detect_clearing` (line 512). cfar's CMP-phase emits start at
frame_complete + ~520 cycles and run for ~73000 cycles, so the
first ~7672 cycles (≈ 4 doppler columns) of cfar pulses were
silently dropped. Test stimulus lit (67, 2/3) for sub-frame 0, all
inside the clearing window → bytes lost. (67, 18/19) and (67, 34/35)
for SF1/SF2 fell after clearing → captured correctly. Visible as
one-byte mismatch (0x0A expected, 0x00 captured) at offset 49965
(= cfar byte 804 = range 67, doppler 0..3) once Bugs A and B were
fixed.
Fix: move detect_clearing trigger from "1 cycle after frame_complete"
to wr_done_pulse (USB-transfer-complete edge already CDC'd into clk
via the AUDIT-C12 wr_done_sync chain). Clearing now runs in the dead
zone after USB has finished reading frame N's BRAM, well before
frame N+1's cfar starts CMP (~480k cycles of margin at 178 fps).
First frame after reset relies on BRAM init=0 — added explicit
initial block under `ifdef SIMULATION so iverilog matches Vivado's
synthesis default.
Test infrastructure:
- tb/tb_e2e_dsp_to_host.v new — deterministic single-target stimulus
fed through the back-half of the radar pipeline (range_decim → MTI
→ doppler → DC-notch → cfar → registered sync → usb), 16 in-TB
asserts + bit-exact byte capture.
- tb/cosim/gen_e2e_stimulus.py / gen_e2e_expected.py new — Python
deterministic stim + bit-exact frame golden.
- tb/cosim/tb_e2e_dsp_to_host_parse.py new — parses captured frame
via radar_protocol, runs 12 strict-bit-equality checks plus 16
semantic checks (target == CONFIRMED, neighbors == NONE,
DC-notched bins == NONE, etc).
- run_regression.sh — A6 hookup + retired the two zero-assertion
radar_system_tb USB_MODE=0/1 smoke runs and the 3-liveness-only
tb_system_dataflow (subsumed by A6's stronger checks). Saves
~7 min wall.
Verification:
- Local iverilog: in-TB 16/16 PASS, parser strict 28/28 PASS.
- Remote Vivado 2025.2 xsim (Artix-7 target): in-TB 16/16 PASS,
parser strict 28/28 PASS.
- Full regression: 41 / 0 / 0.
The MODEL_USB_CFAR_BUG bug-model flag (used to keep the regression
green during development against buggy production) is removed — the
test is now strict bit-exact against the post-fix wire format.
|
||
|
|
bf83d35917 |
test(fpga): PR-M.4 — redesign T-6 drift invariants for scaled-FFT chain
Three sub-checks in compare_independent.py were red because the test
inputs assumed an UNSCALED FFT but PR-O moved both the RTL xFFT path
and fpga_model.FFTEngine to LogiCORE v9.1 Scaled mode (one >>>1 per
butterfly stage with conv-rounding, total /N applied across LOG2N
stages). At small input amplitudes the per-bin output rounded to zero
and the test invariants no longer described meaningful behaviour.
The fpga_reference.py side already mirrors the scaled-mode convention
(np.fft.fft(x)/n on forward, ifft on inverse — see line 104, 137-138,
207). The fix is purely in the test inputs:
- FFT-2048(impulse): amp 1000 → 32000 (≈ Q15 max). Expected per-bin
is now round(amp/N) = round(32000/2048) = 16 (banker's). The
impulse has b=0 at every butterfly so there is no twiddle
interaction; banker's rounding keeps every bin within ±1 LSB.
Tightened tolerance from 5 to 2.
- MF peak position + MF peak-to-median: amp 200 → 4000. Chain
output peak under scaled-mode is correlation/N² ≈ pulse_len*amp²
/N² = amp²/16384 (256 / 4_194_304). At amp=200 the peak collapsed
to 2.4 (mostly Q15 quantization noise — argmax wandered to bin 2);
at amp=4000 the peak rises to ≈ 977 with sidelobes near LSB
floor. Peak-to-median ratio observed: 974 vs threshold 5.
Runtime verification: compare_independent.py 13/13 PASS standalone.
Full FPGA regression: 36/1/6 → 37/0/6 (the single FAIL was this test;
no other tests touched).
|
||
|
|
0728d931c4 |
chore(repo): PR-H — G-series close-out (regression infra + lint sweep)
Closeout pass for the G-series 3-ladder chirp + adaptive-escalation work.
Cleanup, watchdog/fallback, lint, full regression — final sign-off.
Cleanup + watchdog/fallback: already wired during earlier audit waves
(track watchdog in chirp_scheduler RP_DEF_TRACK_WATCHDOG_FRAMES, RESERVED
fallback in plfm_chirp_controller_v2, range-decim watchdog in
radar_system_top with gpio_dig7 surfacing, F-3.* MCU error path).
Verified — no residual TODO/FIXME in production RTL or MCU.
Regression infra: tb/cosim/compare_independent.py SKIP-detection bug —
importlib.util.find_spec("scipy.signal") raises ModuleNotFoundError when
the parent scipy package is itself absent (instead of returning None as
the surrounding logic assumed). Wrap in try/except so the regression
runner gets the intended rc=2 SKIP marker rather than a crash that masks
the rest of the script.
Lint sweep: ruff full-repo → 0 errors. Two changes:
- pyproject.toml broadens 5_Simulations/Antenna/**.py exemption from
just T20+ERA to the full set of script-ergonomics rules
(RUF001/002/003 Greek µ/λ/π/θ in physical-units strings, E501 long
matplotlib/numpy lines, RUF005/015/046, E70x one-line setup, B007
tuple-unpack loop vars, B905, BLE001 diag try/except, C401, RET504,
SIM118, PERF40x, ARG001, E402). These are sim/analysis scripts, not
production code — keep substantive bug rules (F unused, B core
bugbears) but drop stylistic noise.
- Auto-fix sweep: 31x F541 (f-string-no-placeholder), 3x F401 (unused
sys import), 2x F841 (dead leftover ref_pat / phases_quant in
array_factor_adar1000_aeris10.py).
.gitignore: cover 9_Firmware/9_2_FPGA/tb/cosim/mf_chain_autocorr.csv
(matched_filter cosim writes here now; was already covered for tb/ but
not tb/cosim/).
Regression baseline (radar_venv):
FPGA : 42/43 — 1 pre-existing T-6 drift cosim fail surfaced by the
SKIP fix above. Three sub-checks now red because PR-O moved
xFFT/MF chain to LogiCORE v9.1 *Scaled* mode (1/2 per stage,
1/2^11 total for N=2048) but compare_independent.py's invariants
(FFT-impulse uniform-spectrum, MF peak-at-injected-delay, MF
peak/median ≥ 5) were written assuming UNSCALED FFT. Not
introduced by this PR — was hidden by the SKIP-detection crash.
Defer to PR-M.4: redesign T-6 invariants (or input amplitudes)
to match scaled-mode arithmetic.
MCU : 34/34 binary suites pass.
GUI : test_v7 150/150 pass.
uv.lock: scipy resolution catch-up (declared in pyproject dev group all
along; lock just hadn't been refreshed after pyproject edits landed).
Bench-side checks: none — this PR is repo hygiene, no firmware/RTL
behaviour change.
|
||
|
|
8ebb7016de |
chore(repo): PR-S — m-1..m-9 hygiene sweep (audit cleanup)
Bundled minor-tier fixes from project_aeris10_audit_2026-05-02. No
behavioural changes to the production happy path; mostly stale comments,
defaults, and one new emit-path (m-9) that lets cosim_dir replay show
detections instead of an empty mask.
m-1 — processing.py:59 RadarProcessor.range_doppler_map placeholder
shape (1024, 32) -> (NUM_RANGE_BINS, NUM_DOPPLER_BINS) imported
from radar_protocol so the legacy literal stops leaking to
anything reading the attribute before frame 0.
m-2 — radar_receiver_final.v:596 stale "// 32" comment for
RP_CHIRPS_PER_FRAME -> "// 48 (PR-F: 3 sub-frames * 16)".
m-4 — radar_protocol.py "16384 x 2 = 32768" arithmetic comment was
already corrected by an earlier edit; verified clean.
m-5 — usb_data_interface_ft2232h.v:961 "Frame header: 8 bytes"
comment -> "9 bytes (PR-G: added version byte at offset 1)".
m-6 — radar_system_top.v cold-reset host_chirps_per_elev 32 -> 48
+ status doc-comment so any sanity-checking parser sees the
value matching RP_CHIRPS_PER_FRAME instead of latching a
chirps_mismatch_error.
m-7 — radar_receiver_final.v:370 RX DDC mixers_enable(1'b1)
annotated: documented as intentional asymmetry vs TX (counter-
UAS RX has no quiesce scenario; CDC would add cost without
operational benefit).
m-8 — RadarSettings range_resolution / velocity_resolution flagged
inline as PLACEHOLDER (docstring already explains; inline
marker makes it visible at the field).
m-9 — gen_realdata_hex.py now also emits fullchain_cfar_flags.npy
(uint8 detection mask) and fullchain_cfar_mag.npy (|I|+|Q|),
produced by run_cfar_ca() with the FPGA cold-reset defaults
(guard=2 train=8 alpha=0x30 mode=CA). Replays through
v7.replay's COSIM_DIR loader: 22 detections on the synthetic
scene (was 0). The hex/ directory's two new .npy files are
included in this commit.
Regression: 247/247 (test_v7 130 + test_GUI_V65_Tk 117). Ruff clean.
|
||
|
|
3d2ffc3f2c |
chore(repo): cosim_dir replay revival + ruff lint cleanup
cosim_dir revival:
- gen_realdata_hex.py: also emit decimated_range_{i,q}.npy (48x512)
and doppler_map_{i,q}.npy (512x48) at production dimensions; the
same Python pipeline that produces the RTL .hex stimuli now writes
the .npy intermediates v7.replay COSIM_DIR loads. Replaces the
workflow lost when golden_reference.py was deleted in
|
||
|
|
54627bbbe3 |
fix(gui): software_fpga revival post-e8b495c — port chain helpers to fpga_model
Restore SoftwareFPGA's process_chirps() pipeline by porting the missing
chain stages (MTI canceller, DC notch, CFAR, threshold detection) plus
thin wrappers (range FFT, decimator, Doppler FFT) to fpga_model.py and
swapping software_fpga.py's import target from the deleted
golden_reference.py to fpga_model.
History: golden_reference.py was deleted in
|
||
|
|
7ed4d5d405 |
test(fpga): PR-Q.2 — align cosim T_PRI_MEDIUM 175->161 us + regen goldens
Mirror the PR-Q.1 PRI stagger (MEDIUM 175 us -> 161 us) into the cosim scenario generator and regenerate all 12 affected golden hex/csv files. Without this, the Doppler co-sim TBs would diverge from the RTL on every MEDIUM sub-frame bin. - tb/cosim/radar_scene.py: T_PRI_MEDIUM = 161e-6 - tb/cosim/gen_doppler_golden.py: comment update for MEDIUM bin map - 12 regenerated hex/csv files (doppler + real_data + fullchain_realdata) Regression: 42/0/1 (PR-Q.1 baseline preserved; T-6 SKIP is scipy-missing). |
||
|
|
8541443c64 |
fix(fpga): PR-O — xFFT scaled mode + 32-bit MF chain widening
Resolves AUDIT-C10 (xFFT scaling sim/silicon mismatch) by replacing the
LogiCORE FFT v9.1 BFP setting with deterministic Scaled mode. Schedule
[1,1,…,1] (= /N total) is encoded in radar_params.vh and applied in
both the Xilinx IP via cfg_tdata SCALE_SCH bits and the iverilog
fft_engine fallback via per-stage convergent-rounding >>>1 at every
butterfly write. Output magnitudes now match between sim and silicon —
CFAR alpha calibration is portable.
The /N switch exposed a pre-existing dynamic-range hole in the matched-
filter chain (project_mf_chain_dynrange_defect_2026-05-02): the
frequency_matched_filter.v Q30→Q15 truncation was calibrated for the
BFP-normalized FFT outputs of the BFP era. Under deterministic /N,
chirp energy spreads across bins so each FFT bin is well below Q15
full-scale, and the >>15+saturate crushed chirp / DC / impulse
autocorrelations to zero.
Fix: widen the path between conjugate-multiply and IFFT to 32-bit Q30.
One 32-bit FFT engine instance, AXIS data 64-bit packed
{Q[31:0], I[31:0]}. FWD passes sign-extend their 16-bit ADC/ref
samples; FWD outputs sat-truncate back to 16-bit into sig_buf/ref_buf;
conj-mult emits raw Q30 into a 32-bit prod_buf; IFFT consumes Q30; the
chain saturates 32→16 onto range_profile_*.
bb_mf_test_*.hex regenerated with realistic AGC scaling (peak filled to
~½ ADC range = 16384 LSB) so the cosim chirp scenario exercises the
chain at production-equivalent levels — the bare radar-physics output
sat ~5 LSB below the FFT's per-bin LSB floor.
Test 19 (orthogonal cross-correlation) corrected: under deterministic
/N the cross-correlation of two integer-bin tones is mathematically
zero; the previous "non-zero output" assertion only passed under BFP
because BFP renormalized the noise floor. tb_rxb_fullchain_latency.v
peak-bin gating relaxed to recognize the iverilog fft_engine RX-NEW-1
mirror (peak at bin 2047 instead of 0) as PASS when peak/mean is
healthy.
compare_mf.py "both produce output" gate dropped: zero-but-matching is
valid sim/silicon parity, and the remaining metrics (energy ratio,
magnitude correlation, peak overlap, I/Q correlation) already handle
the zero case via the py_energy == 0 and rtl_energy == 0 → 1.0 clause.
Regression: 42 PASS / 0 FAIL / 1 skip (was 37 PASS / 5 FAIL):
- MF Co-Sim chirp/dc/impulse: PASS (was FAIL on dynamic-range floor)
- MF Co-Sim chirp peak: 4917 at bin 271, peak/mean ~3.4x
- Matched Filter Chain unit: 40/40 PASS (was 34/40)
- RX-B Full-Chain Autocorrelation: PASS, peak/mean ~166x (was 0)
- tb_fft_engine: 12/12 PASS (Parseval, scaling, roundtrip)
The Xilinx IP DCP must be regenerated on the remote Vivado box for
synth and XSim — gen_xfft_2048_ip.tcl + xfft_2048_ip.xci are updated
for input_width=32 / 64-bit AXIS but the .dcp is still pre-PR-O.
|
||
|
|
db6b220f92 |
ci(fpga): PR-M.3 — wire T-6 drift cosim into regression + CI deps
Adds the T-6 independent reference drift cosim (PR-M.1,
|
||
|
|
36234fe0e3 |
fix(doppler): PR-M.2 — Dolph-Chebyshev 60 dB window replaces Hamming-ish LUT
T-6 drift cosim (PR-M.1,
|
||
|
|
c30be89dbe |
test(cosim): PR-M.1 — independent fpga_reference.py + drift cosim (T-6)
Adds tb/cosim/fpga_reference.py: numpy/scipy implementation of NCO,
FFT, matched filter, and Doppler. Unlike fpga_model.py — which is a
bit-exact PORT of the RTL (same NCO_SINE_LUT, same twiddle .mem files,
same Q15 quantization) — this reference computes the algorithm from
analytical formulas with no LUT or quantization. It is the third leg
of the cosim triangle so transcription bugs that exist identically in
both the Python twin AND the RTL no longer hide.
Adds tb/cosim/compare_independent.py: runs canonical stimulus through
both twin and reference and reports drift. Bytewise LUT spot-checks
(NCO_SINE_LUT, fft_twiddle_16.mem, fft_twiddle_2048.mem,
HAMMING_WINDOW) plus end-to-end peak/roundtrip invariants for NCO,
FFT-2048, MF, Doppler.
Findings (12/13 drift checks pass):
* NCO_SINE_LUT, fft_twiddle_16.mem, fft_twiddle_2048.mem all match
their analytical Q15 values bytewise (max dev = 0 LSB) — the two
biggest hand-transcribed LUTs are clean.
* HAMMING_WINDOW [FAIL] — max 740 LSB drift from documented formula
0.54-0.46*cos(2*pi*n/15) at n=5 (LUT=25971, ideal=25231). The
same wrong values appear in fpga_model.HAMMING_WINDOW and
doppler_processor.v lines 99-114; both share the drift, which is
why every existing Doppler cosim has been passing bit-exactly. To
resolve: either regen the LUTs to match the documented formula
and re-bless Doppler goldens, or update the comments to describe
the actual values (no clean closed-form match yet identified).
Not wired into run_regression.sh in this commit so the drift gating
decision (fix vs document) can be made deliberately.
|
||
|
|
237e74ceba |
test(realdata): PR-K — synthetic regen of doppler/fullchain realdata fixtures
Replaces the legacy ADI CN0566 .npy capture flow with a synthetic radar
scene generated by tb/cosim/real_data/gen_realdata_hex.py via the
existing radar_scene + fpga_model bit-accurate Python models.
Dimensions now match production radar_params.vh:
RP_FFT_SIZE=2048, RP_DECIMATION_FACTOR=4, RP_NUM_RANGE_BINS=512,
CHIRPS_PER_FRAME=48, NUM_DOPPLER_BINS=48 (3 sub-frames x 16-pt FFT).
Previously both TBs were pinned to legacy 32-chirp / 2-subframe / 1024->64
DECIM=16 dimensions. range_bin_decimator.v's 2-bit comparisons against
DECIMATION_FACTOR/2 only behave correctly for small DECIM, so the old
DECIM=16 path no longer worked even though the TBs compiled — that is
why Full-Chain Real-Data was reporting pass=0/fail=3.
Changes:
tb/cosim/real_data/gen_realdata_hex.py (new) - synthesises 6 fixture
files from a 2-target scene via DopplerProcessor (3-subframe) and
RangeBinDecimator (peak, 2048->512). Reproducible (fixed seed 42).
tb/cosim/real_data/golden_reference.py (deleted, 1436 lines) - the
legacy generator depended on out-of-tree ADI .npy captures and
modelled only the 2-subframe / 32-chirp path.
tb/cosim/real_data/hex/ - 43 orphan artifacts deleted (CFAR / MTI /
notched / detection / range-FFT debug dumps that nothing in the
active TB or regression was loading); 6 fixtures regenerated at
production dimensions:
doppler_input_realdata.hex 24576 packed lines (was 2048)
doppler_ref_{i,q}.hex 24576 lines each (was 2048)
fullchain_range_input.hex 98304 packed lines (was 32768)
fullchain_doppler_ref_{i,q}.hex 24576 lines each (was 2048)
tb/tb_doppler_realdata.v - CHIRPS 32->48, RANGE_BINS 64->512,
DOPPLER_FFT 32->48, MAX_CYCLES bumped.
tb/tb_fullchain_realdata.v - same + INPUT_BINS 1024->2048,
DECIM_FACTOR 16->4, fixed
decim_bin_index width to
RP_RANGE_BIN_WIDTH_MAX, fixed
start_bin width 10->11.
run_regression.sh - "Doppler Real-Data" label updated
(no longer "ADI CN0566"); both
realdata tests get explicit
--timeout values (300 / 600 s).
Standalone results:
tb_doppler_realdata - 24584/24584 PASS (3.36 s sim, ~50 s wall)
tb_fullchain_realdata - 24585/24585 PASS (4.10 s sim, ~5 min wall)
Full regression now: 41 passed / 1 failed (only remaining FAIL is
FFT Engine, pre-existing pre-PR-K regex-reveal — unrelated).
|
||
|
|
b7a841a32c |
test(cosim): T-7 strict MF thresholds + T-8 doppler 32->48 (3 sub-frames)
T-7 (compare_mf.py): replace "energy ratio 0.001-1000" cargo-cult bounds with strict Parseval/correlation gates — energy 0.95-1.05, mag_corr >= 0.95, peak_overlap_10 >= 0.90, corr_i/corr_q >= 0.90. All four MF cosim scenarios still pass (energy=1.000 mag_corr=1.000 peak=1.000) but the script now bites on any drift instead of rubber-stamping. T-8 (doppler cosim 32->48): bump cosim/TBs/Python model to production 3-subframe / 48-bin config (PR-F). DopplerProcessor parameterised over NUM_SUBFRAMES (default 3, legacy 2 still callable). radar_scene now uses SHORT/MEDIUM/LONG slow-time matching chirp_scheduler.v. Goldens regenerated; tb_doppler_cosim drops the legacy CHIRPS_PER_FRAME=32 override; all 3 doppler scenarios pass bit-exact (energy=1.0000 peak_agree=1.000 mag_corr=1.000) at production config. tb_doppler_realdata kept on the legacy override — its goldens are bit-exact ADI CN0566 captures (32 chirps x 64 range bins) and the 3-subframe regen needs new hardware captures + golden_reference.py rewrite, deferred to PR-I. Full regression: 37/41 (same 4 pre-existing T-2..T-5 failures, no new regressions). |
||
|
|
4238eb1b99 |
chirp-v2 PR-C: chirp_reference_rom replaces chirp_memory_loader_param
Drop the chirp-v1 1-bit use_long_chirp memory loader and its 6 .mem files;
introduce chirp_reference_rom — wave_sel-native, single 8192x16 BRAM array
per Q15 lane, 4-region init (SHORT, MEDIUM, LONG seg0/seg1) loaded from the
PR-B mem files. Same 1-clk read latency as the legacy module so the RX-B
autocorrelation alignment fix carries through unchanged.
Receiver-side wave_sel shim added in radar_receiver_final.v:
wire [1:0] wave_sel = use_long_chirp ? RP_WAVE_LONG : RP_WAVE_SHORT;
This is a 1-line transitional bridge while radar_mode_controller still
emits 1-bit use_long_chirp; PR-D deletes the shim and wires chirp_scheduler
straight through. MEDIUM is loaded into the ROM but unreachable through
the production path until PR-D.
BRAM cost: 8 RAMB18 (was 6 in chirp-v1). +2 BRAM is the cost of adding
MEDIUM to the waveform set; not avoidable.
Files added:
- chirp_reference_rom.v
Files removed:
- chirp_memory_loader_param.v
- long_chirp_seg{0,1}_{i,q}.mem (4 files)
- short_chirp_{i,q}.mem (2 files)
- tb/cosim/validate_mem_files.py (legacy file-set validator; replaced by
gen_chirp_mem.py's internal verify_phase_match)
- tb/cosim/analyze_short_chirp_mismatch.py (one-shot tool from the
chirp-v1 TX-I investigation; finding incorporated, references the
deleted short_chirp_*.mem files)
Files updated for module rename:
- radar_receiver_final.v — instance, comments, wave_sel shim
- radar_mode_controller.v — header comment
- matched_filter_processing_chain.v — header comment
- scripts/200t/build_200t.tcl — explicit RTL list
- run_regression.sh — 5 spots
- tb/tb_rxb_fullchain_latency.v — instance, wave_sel shim, mem filenames,
SHORT_LEN 50 → 100 (1 µs at 100 MHz)
- tb/tb_system_e2e.v — header comment
Verification:
- chirp_reference_rom standalone iverilog compile: clean
- Full receiver chain compile (21 RTL files): clean
- tb_rxb_fullchain_latency runs end-to-end with new ROM + new mem files
+ 100-sample SHORT chirp; autocorrelation peak at bin 0, peak |I|+|Q|
= 15115. Confirms 1-clk ROM read latency is preserved and the RX-B
direct-wire-with-1-FF alignment still holds.
- 50T build script (scripts/50t/build_50t.tcl) uses glob *.v — no edit
needed; it picks up the new file automatically.
|
||
|
|
f5b8e7a20b |
chirp-v2 PR-B: 3-waveform mem generator + 11 new .mem files
Rewrite gen_chirp_mem.py to emit the SHORT (1 µs), MEDIUM (5 µs), and LONG
(30 µs) waveform set on both TX and RX paths. The script is now the single
source for every chirp .mem file; the legacy 6-file set on disk
(long_chirp_lut.mem, long_chirp_seg{0,1}_{i,q}.mem, short_chirp_{i,q}.mem)
is no longer regenerated and gets deleted in PR-C/PR-E when its consumer
modules are removed.
Generated artifacts (committed):
TX (8-bit unsigned offset-binary, fs_dac = 120 MHz):
tx_short_lut.mem 120 lines
tx_medium_lut.mem 600 lines
tx_long_lut.mem 3600 lines
RX (Q15 I/Q hex, fs_sys = 100 MHz, all 2048 lines for uniform BRAM sizing):
rx_short_i.mem / rx_short_q.mem 100 active + 1948 zero-pad
rx_medium_i.mem / rx_medium_q.mem 500 active + 1548 zero-pad
rx_long_seg0_i.mem / rx_long_seg0_q.mem 2048 (samples [0..2047])
rx_long_seg1_i.mem / rx_long_seg1_q.mem 952 active + 1096 zero-pad
Phase model unchanged from chirp-v1: phi(n) = 2π·F_BASEBAND_LOW·t +
π·(BW/T)·t² with F_BASEBAND_LOW=10 MHz and BW=20 MHz. The same formula now
runs three durations and two sample rates from one helper.
rx_long_seg0_i.mem is bit-exact to the legacy long_chirp_seg0_i.mem on disk
(diff -q reports identical) — proves the SHORT/MEDIUM additions did not
perturb the LONG path.
Verification:
- all 11 files have correct line counts (above)
- script is idempotent (re-run produces byte-identical output)
- ruff clean (one E501 line-length + two RUF046 redundant-int casts fixed)
- phase regression at long-seg0 against pre-chirp-v2 reference: bit-exact
No RTL or testbench changes. The legacy .mem files remain on disk for the
existing chirp_memory_loader_param.v / plfm_chirp_controller.v consumers
until PR-C and PR-E delete those modules. No module references the new
files yet.
|
||
|
|
1f307f77a9 |
cosim: refresh stale baselines (FFT-2048 + chirp realign)
Two stale-baseline events were never captured in earlier commits: 1. The FFT-1024 -> FFT-2048 merge ( |
||
|
|
5ff5671fe2 |
fix(fpga): TX-I — align matched-filter reference with actual post-DDC band
The DAC short/long chirp LUTs are 10..30 MHz upchirps (Hilbert-confirmed). With TX_LO=10.500 GHz, RX_LO=10.380 GHz (adf4382a_manager.h) and the 120 MHz DDC NCO (ddc_400m.v), high-side mixing places the post-DDC echo at 10..30 MHz baseband. The matched-filter reference (gen_chirp_mem.py) was generating 0..20 MHz, implicitly assuming the chirp's low edge mixed to DC. This caused a 10 MHz spectral offset and ~5 dB matched-filter loss. Adds F_BASEBAND_LOW=10e6 in both gen_chirp_mem.py and radar_scene.py, with phase formula 2*pi*F_BASEBAND_LOW*t + pi*rate*t^2 in all chirp generators. Regenerates the 6 .mem files. Adds analyze_short_chirp_mismatch.py for the Hilbert-based diagnosis. Fixes the misleading "30MHz to 10MHz" comment in plfm_chirp_controller.v and adds an end-to-end frequency plan in the LUT header. Sideband orientation (high-side at both mixers) is the conventional choice and consistent with antenna match (10.25..10.75 GHz, 8x16 patch designed at 10.5 GHz). Loopback capture would settle definitively; if either mixer is low-side the F_BASEBAND_LOW sign flips and/or chirp direction reverses. |
||
|
|
b7ac2de1a4 |
chore: delete dead latency_buffer; doc cleanup for two stale comments
latency_buffer.v has had zero non-tb instantiations since RX-B (2026-04-23)
replaced its hookup in radar_receiver_final with a 1-FF alignment register.
The module was being kept "for potential future use" — exactly the kind of
dead weight the codebase does not need. Deleted, along with all build /
test infrastructure that dragged it along:
- 9_Firmware/9_2_FPGA/latency_buffer.v
- 9_Firmware/9_2_FPGA/tb/tb_latency_buffer.v
- run_regression.sh: removed from RTL_FILES and RECEIVER_RTL
- scripts/200t/build_200t.tcl: removed from synthesis source list
- tb/tb_system_e2e.v: removed from header compile-string example
- tb/cosim/validate_mem_files.py: deleted test_latency_buffer() (~75 lines),
its call site, and the corresponding entry in the module docstring
Historical RX-B comments referencing latency_buffer in radar_receiver_final.v,
tb_rxb_fullchain_latency.v, and tb_rxb_latency_measure.v are kept — they
explain WHY the module was removed, which is still useful design archaeology.
Two doc-only housekeeping touches bundled in:
- plfm_chirp_controller.v: replaced two empty "CRITICAL FIX: Generate
valid signal" labels at LONG_CHIRP and SHORT_CHIRP with one shared
chirp_valid policy comment block above LONG_CHIRP that explains the
actual rationale (downstream FIFO underrun on trailing samples).
- v7/models.py: replaced the "range_resolution and velocity_resolution
should be calibrated" docstring (sounded like an open TODO but was a
documented placeholder) with a clear pointer to the GUI-C3 fix in
workers.py:RadarDataWorker so future readers know the live path
derives correct values from WaveformConfig.
FPGA quick regression unchanged: 28/29 (1 fail is the unrelated iverilog/
Xilinx-IP RX-NEW-3 gap). GUI suite 180/180. Ruff clean.
|
||
|
|
f39a78cb1e |
chore(fpga): untrack TB-generated CSV, ignore a.out
rx_final_doppler_out.csv is written by tb_radar_receiver_final.v on every run via $fopen — it is a test-run artifact, not an oracle. It was mistakenly tracked in an earlier commit, causing unnecessary churn on every sim. Remove from the index and ignore going forward. Also ignore stray a.out from iverilog one-shot compiles. Golden references (.hex, .mem, doppler_golden_py_*.csv) remain tracked — they are load-bearing oracles used by MF / Doppler / receiver cosim testbenches. |
||
|
|
8865e9a0ef |
fix(fpga): pre-bringup RTL hardening + test-suite hardening
RTL (P0 pre-bringup findings R-1/R-2/R-3/R-5/R-6): - mti_canceller: add use_long_chirp input and waveform-boundary mute so the long->short transition in mode 01 no longer subtracts across heterogeneous waveforms (R-1). Prev buffer is overwritten in-flight at the boundary so the next same-waveform chirp subtracts cleanly. - ad9484_interface_400m: 2FF sync of mmcm_locked into the 400 MHz domain before gating reset_n_gated (R-6). - cic_decimator_4x_enhanced: correct max_fanout narrative (R-3). - ad9484_interface_400m: strip stale pblock comment, note 3.0 ns max_delay instead (R-2). - mti_canceller / doppler_processor: 200T-20km WARNING banners flagging the broken 4096-bin path (R-5). 9-bit BRAM address aliases silently until rewritten. - adc_clk_mmcm.xdc: relax set_max_delay from 2.700 -> 3.000 ns, closes WNS with headroom on 50T build. - radar_receiver_final: wire use_long_chirp into mti_inst. Architecture-bump finalization (2048-pt range FFT, 512 range bins, 32 Doppler bins -> 16384 output cells per frame): - tb/cosim/radar_scene.py: FFT_SIZE 1024 -> 2048, RANGE_BINS 64 -> 512. - tb/gen_mf_golden_ref.py: N 1024 -> 2048. - Regenerate all affected hex goldens (MF cases 1-4, Doppler inputs + py goldens, receiver integration golden_doppler.mem 2048 -> 16384). - tb_radar_receiver_final: widen range_bin_out 6 -> 9 bits, bump GOLDEN_ENTRIES 2048 -> 16384, expand bitmaps/arrays to 512 bins, update all check messages and thresholds. - tb_mti_canceller, tb_fullchain_mti_cfar_realdata: tie/pass use_long_chirp so compile still works after RTL port add. Test-suite hardening (coverage audit findings): - tb_mti_canceller T12: 10 new assertions exercising R-1 waveform- boundary mute across a long/long/short/short/long sequence. Catches a regression that re-enables subtraction across the boundary. - tb_fir_lowpass: replace tautological check(1'b1, ...) on coefficient symmetry with a real hierarchical check coeff[k]===coeff[31-k]; replace always-pass overflow check with a well-driven (not X/Z) assertion on filter_overflow. - tb_matched_filter_processing_chain: replace three always-pass peak- bin placeholders with peak-to-mean-|out| > 2x ratio checks (catches flat/zero output that the old tautologies silently accepted). - tb_cdc_modules M2: replace always-pass narrow-pulse check with a well-defined-output assertion on the synchronizer. - tb_nco_400m: replace always-pass freq-switch check with a swing + no-X assertion across 200 post-switch samples. - tb_system_e2e G12.1: replace check(1, ...) with test_num > 20 so it catches a stalled TB that skipped prior groups. - tb_multiseg_cosim TEST 4: replace always-pass placeholder with a bitmap that asserts segment_request visited all 4 values. - tb_mf_chain_synth and tb_fullchain_mti_cfar_realdata: add DEPRECATED headers plus \$fatal guards (ifndef ALLOW_STALE_*) so they cannot be silently re-enabled in CI with stale 1024-bin goldens against current 2048-pt RTL. Regression: 32 passed, 0 failed. MTI TB grew 30 -> 39 checks; receiver integration grew 17 -> 18 checks with 16384/16384 golden match at tolerance +/- 2 LSB. |
||
|
|
c668652ba8 |
merge(wave3/tier2): port testbenches and cosim goldens for fft-2048
Regression goes from 21/32 -> 27/32 passing.
TB files updated from feat/fft-2048-upgrade (FFT_SIZE=2048 / 512 range
bins / Manhattan magnitude / 2-segment matched filter):
- tb/tb_mf_cosim.v (range_profile_{i,q} port names)
- tb/tb_matched_filter_processing_chain.v (long_chirp port names)
- tb/tb_range_bin_decimator.v (new 2048->512 DUT)
- tb/tb_radar_mode_controller.v (XOR edge detector)
- tb/tb_doppler_cosim.v (2048-deep inputs)
- tb/tb_multiseg_cosim.v
- tb/tb_mf_chain_synth.v
Cosim infrastructure regenerated with FFT_SIZE=2048:
- tb/cosim/gen_mf_cosim_golden.py
- tb/cosim/gen_doppler_golden.py
- tb/cosim/compare_mf.py, compare_doppler.py
- tb/cosim/fpga_model.py
- All mf_* and doppler_* goldens/inputs regenerated
Deliberately NOT taken:
- tb/tb_radar_receiver_final.v — kept p0's version because the merged
radar_receiver_final requires tx_frame_start + adc_or_p/n inputs
that fft's TB does not drive. Its 3 failures (G1 golden mismatch,
B3/B5 hardcoded 64-bin limits) are tracked as known issues; TB
needs a 64->512 bin rewrite + golden regen against merged RTL.
Known remaining failures (5/32):
- Doppler Co-Sim x3: python compare mismatch — goldens generated
against fft's reset/DDC behavior; merged RTL uses p0's reset
strategy. Needs golden regen against merged RTL.
- Receiver Integration: TB has stale 64-bin localparams/widths.
- Matched Filter Chain: 3/40 "peak magnitude > 0" checks fail on
behavioral-FFT cases. Pre-existing on fft branch (known brittle).
|
||
|
|
60e49c7da6 |
feat(fpga): integrate 2048-pt FFT upgrade — non-conflicting RTL (wave 1/3)
File-scoped cherry-pick from feat/fft-2048-upgrade ( |
||
|
|
d0b3a4c969 |
fix(fpga): registered reset fan-out at 400 MHz; default USB to FT2232H
Replace direct !reset_n async sense with a registered active-high reset_h (max_fanout=50) in nco_400m_enhanced, cic_decimator_4x_enhanced, and ddc_400m. The prior single-LUT1 / 700+ load net was the root cause of WNS=-0.626 ns in the 400 MHz clock domain on the xc7a50t build. Vivado replicates the constrained register into ≈14 regional copies, each driving ≤50 loads, closing timing at 2.5 ns. Change radar_system_top default USB_MODE from 0 (FT601) to 1 (FT2232H). FT601 remains available for the 200T premium board via explicit parameter override; the 50T production wrapper already hard-codes USB_MODE=1. Regression: add usb_data_interface_ft2232h.v to PROD_RTL lint list and both system-top TB compile commands; fix legacy radar_system_tb hierarchical probe from gen_ft601.usb_inst to gen_ft2232h.usb_inst. Golden reference files (rtl_bb_dc.csv, rx_final_doppler_out.csv, golden_doppler.mem) regenerated to reflect the +1-cycle registered-reset boundary behaviour; Receiver golden-compare passes 18/18 checks. All 25 regression tests pass (0 failures, 0 skipped). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
15a9cde274 |
review(cosim): fix stale comment and wrong docstring derivation
golden_reference.py: update comment from 'Simplified' to 'Exact' to
match shaun0927's corrected formula.
fpga_model.py: fix adc_to_signed docstring that incorrectly derived
0x7F80 instead of 0xFF00. Verilog '/' binds tighter than '-', so
{1'b0,8'hFF,9'b0}/2 = 0x1FE00/2 = 0xFF00, not 0xFF<<8 = 0x7F80.
|
||
|
|
a9ceb3c851 |
fix(cosim): align golden_reference ADC sign conversion with RTL
The golden reference used (adc_val - 128) << 9 which subtracts 65536,
but the Verilog RTL computes {1'b0,adc,9'b0} - {1'b0,8'hFF,9'b0}/2
which subtracts 0xFF00 = 65280. This creates a constant 256-LSB DC
offset between the golden reference and RTL for all 256 ADC values.
The bit-accurate model in fpga_model.py already uses the correct RTL
formula. This aligns golden_reference.py to match.
Verified: all 256 ADC input values now produce zero offset against
fpga_model.py.
|
||
|
|
063fa081fe |
fix: FPGA timing margins (WNS +0.002→+0.080ns) + 11 bug fixes from code review
FPGA timing (400MHz domain WNS: +0.339ns, was +0.002ns): - DONT_TOUCH on BUFG to prevent AggressiveExplore cascade replication - NCO→mixer pipeline registers break critical 1.5ns route - Clock uncertainty reduced 200ps→100ps (adequate guardband) - Updated golden/cosim references for +1 cycle pipeline latency STM32 bug fixes: - Guard uint32_t underflow in processStartFlag (length<4) - Replace unbounded strcat in getSystemStatusForGUI with snprintf - Early-return error masking in checkSystemHealth - Add HAL_Delay in emergency blink loop GUI bug fixes: - Remove 0x03 from _HARDWARE_ONLY_OPCODES (was in both sets) - Wire real error count in V7 diagnostics panel - Fix _stop_demo showing 'Live' label during replay mode FPGA comment fixes + CI: add test_v7.py to pytest command Vivado build 50t passed: 0 failing endpoints, WHS=+0.056ns |
||
|
|
2106e24952 |
fix: enforce strict ruff lint (17 rule sets) across entire repo
- Expand ruff config from E/F to 17 rule sets (B, RUF, SIM, PIE, T20, ARG, ERA, A, BLE, RET, ISC, TCH, UP, C4, PERF) - Fix 907 lint errors across all Python files (GUI, FPGA cosim, schematics scripts, simulations, utilities, tools) - Replace all blind except-Exception with specific exception types - Remove commented-out dead code (ERA001) from cosim/simulation files - Modernize typing: deprecated typing.List/Dict/Tuple to builtins - Fix unused args/loop vars, ambiguous unicode, perf anti-patterns - Delete legacy GUI files V1-V4 - Add V7 test suite, requirements files - All CI jobs pass: ruff (0 errors), py_compile, pytest (92/92), MCU tests (20/20), FPGA regression (25/25) |
||
|
|
519c95f452 |
fix: regenerate golden hex for dual-16pt Doppler and add real-data TBs to regression
Regenerate all real-data golden reference hex files against the current dual 16-point FFT Doppler architecture (staggered-PRI sub-frames). The old hex files were generated against the previous 32-point single-FFT architecture and caused 2048/2048 mismatches in both strict real-data TBs. Changes: - Regenerate doppler_ref_i/q.hex, fullchain_doppler_ref_i/q.hex, and all downstream golden files (MTI, DC notch, CFAR) via golden_reference.py - Add tb_doppler_realdata (exact-match, ADI CN0566 data) to regression - Add tb_fullchain_realdata (exact-match, decim->Doppler chain) to regression - Both TBs now pass: 2048/2048 bins exact match, MAX_ERROR=0 - Update CI comment: 23 -> 25 testbenches - Fill in STALE_NOTICE.md with regeneration instructions Regression: 25/25 pass, 0 fail, 0 skip. ruff check: 0 errors. |
||
|
|
11aa590cf2 |
fix: full-repo ruff lint cleanup and CI migration to uv
Resolve all 374 ruff errors across 36 Python files (E501, E702, E722, E741, F821, F841, invalid-syntax) bringing `ruff check .` to zero errors repo-wide with line-length=100. Rewrite CI workflow to use uv for dependency management, whole-repo `ruff check .`, py_compile syntax gate, and merged python-tests job. Add pyproject.toml with ruff config and uv dependency groups. CI structure proposed by hcm444. |
||
|
|
57de32b172 |
fix: resolve all ruff lint errors across V6+ GUIs, v7 module, and FPGA cosim scripts
Fixes 25 remaining manual lint errors after auto-fix pass (94 auto-fixed earlier): - GUI_V6.py: noqa on availability imports, bare except, unused vars, F811 redefs - GUI_V6_Demo.py: unused app variable - v7/models.py: noqa F401 on 8 try/except availability-check imports - FPGA cosim: unused header/status/span vars, ambiguous 'l' renamed to 'line', E701 while-on-one-line split, F841 padding vars annotated Also adds v7/ module, GUI_PyQt_Map.py, and GUI_V7_PyQt.py to version control. Expands CI lint job to cover all 21 maintained Python files (was 4). All 58 Python tests pass. Zero ruff errors on all target files. |
||
|
|
1e284767cd |
fix(test,docs): remove dead xfft_32 files, update test infra for dual-16 FFT, add regression guide
- Remove xfft_32.v, tb_xfft_32.v, and fft_twiddle_32.mem (dead code since PR #33 moved Doppler to dual 16-pt FFT architecture) - Update run_regression.sh: xfft_16 in PROD_RTL, remove xfft_32 from EXTRA_RTL and all compile commands - Update tb_fft_engine.v to test with N=16 / fft_twiddle_16.mem - Update validate_mem_files.py: validate fft_twiddle_16.mem instead of 32 - Update testbenches and golden data from main_cleanup branch to match dual-16 architecture (tb_doppler_cosim, tb_doppler_realdata, tb_fullchain_realdata, tb_fullchain_mti_cfar_realdata, tb_system_e2e, radar_receiver_final, golden_doppler.mem) - Update CONTRIBUTING.md with full regression test instructions covering FPGA, MCU, GUI, co-simulation, and formal verification Regression: 23/23 FPGA, 20/20 MCU, 57/58 GUI, 56/56 mem validation, all co-sim scenarios PASS. |
||
|
|
ffc89f0bbd |
fix(rtl,gui,cosim,formal): adapt surrounding files for dual 16-pt FFT (follow-up to PR #33)
- radar_system_top.v: DC notch now masks to dop_bin[3:0] per sub-frame so both sub-frames get their DC zeroed correctly; rename DOPPLER_FFT_SIZE → DOPPLER_FRAME_CHIRPS to avoid confusion with the per-FFT size (now 16) - radar_dashboard.py: remove fftshift (crosses sub-frame boundary), display raw Doppler bins, remove dead velocity constants - golden_reference.py: model dual 16-pt FFT with per-sub-frame Hamming window, update DC notch and CFAR to match RTL - fv_doppler_processor.sby: reference xfft_16.v / fft_twiddle_16.mem, raise BMC depth to 512 and cover to 1024 - fv_radar_mode_controller.sby: raise cover depth to 600 - fv_radar_mode_controller.v: pin cfg_* to reduced constants (documented as single-config proof), fix Property 5 mode guard, strengthen Cover 1 - STALE_NOTICE.md: document that real-data hex files are stale and need regeneration with external dataset Closes #39 |
||
|
|
a577b7628b | Fix staggered-PRF Doppler processing with dual 16-point FFTs | ||
|
|
4985eccbae |
Wire self-test results (0x31) to USB status readback path, add fpga_self_test to regression
- usb_data_interface.v: Add 3 self-test status inputs, expand status packet
from 7 words (header + 5 data + footer) to 8 words (header + 6 data + footer).
New status_words[5] carries {busy, detail[7:0], flags[4:0]}.
- radar_system_top.v: Wire self_test_flags_latched, self_test_detail_latched,
self_test_busy to usb_data_interface ports. Add opcode 0x31 as status
readback alias so host can read self-test results.
- tb_usb_data_interface.v: Add self-test port connections, verify word 5 in
Group 16, add Group 18 (busy flag + partial failure variant). 81 checks pass.
- run_regression.sh: Add fpga_self_test.v to PROD_RTL lint list and system-
level compile lists. Add tb_fpga_self_test as Phase 1 unit test.
- 24/24 regression tests pass, lint clean (0 errors, 4 advisory warnings).
|
||
|
|
f8d80cc96e | Add radar dashboard GUI with replay mode for real ADI CN0566 data visualization, FPGA self-test module, and co-sim npy arrays | ||
|
|
7a44f19432 | Full-chain MTI+CFAR real-data co-simulation: bit-exact match across all 10247 checkpoints (decim->MTI->Doppler->DC notch->CFAR) using ADI CN0566 data | ||
|
|
e93bc33c6c |
Production fixes 1-7: detection bugs, cfar→threshold rename, digital gain control, Doppler mismatch protection, decimator watchdog, bypass_mode dead code removal, range-mode register (21/21 regression PASS)
Fix 1: Combinational magnitude + non-sticky detection flag (tb: 23/23) Fix 2: Rename all cfar_* signals to detect_*/threshold_* (honest naming) Fix 3: New rx_gain_control.v between DDC and FFT, opcode 0x16 (tb: 33/33) Fix 4: Clamp host_chirps_per_elev to DOPPLER_FFT_SIZE, error flag (E2E: 54/54) Fix 5: Decimator watchdog timeout, 256-cycle limit (tb: 63/63) Fix 6: Remove bypass_mode dead code from ddc_400m.v (DDC tb: 21/21) Fix 7: Range-mode register 0x20 with status readback (USB tb: 77/77) |
||
|
|
0b0643619c |
Real-data co-simulation: range FFT, Doppler, full-chain all 2048/2048 exact match
ADI CN0566 Phaser 10.525 GHz X-band radar data validation: - golden_reference.py: bit-accurate Python model with range_bin_decimator - tb_range_fft_realdata.v: 1024/1024 exact match - tb_doppler_realdata.v: 2048/2048 exact match - tb_fullchain_realdata.v: decimator+Doppler 2048/2048 exact match - 19/19 FPGA regression unaffected |
||
|
|
0773001708 |
E2E integration test + RTL fixes: mixer sequencing, USB data-pending flags, receiver toggle wiring (19/19 FPGA)
RTL fixes discovered via new end-to-end testbench: - plfm_chirp_controller: TX/RX mixer enables now mutually exclusive by FSM state (Fix #4), preventing simultaneous TX+RX activation - usb_data_interface: stream control reset default 3'b001 (range-only), added doppler/cfar data_pending sticky flags, write FSM triggers on range_valid only — eliminates startup deadlock (Fix #5) - radar_receiver_final: STM32 toggle signals wired through for mode-00 pass-through, dynamic frame detection via host_chirps_per_elev - radar_system_top: STM32 toggle signal wiring to receiver instance - chirp_memory_loader_param: explicit readmemh range for short chirp Test infrastructure: - New tb_system_e2e.v: 46 checks across 12 groups (reset, TX, safety, RX, USB R/W, CDC, beam scanning, reset recovery, stream control, latency budgets, watchdog) - tb_usb_data_interface: Tests 21/22/56 updated for data_pending architecture (preload flags, verify consumption instead of state) - tb_chirp_controller: mixer tests T7.1/T7.2 updated for Fix #4 - run_regression.sh: PASS/FAIL regex fixed to match only [PASS]/[FAIL] markers, added E2E test entry - Updated rx_final_doppler_out.csv golden data |
||
|
|
c6103b37de |
Gap 7 MMCM jitter cleaner + CIC comb CREG pipeline + XDC clock-name fix
MMCM (Gap 7): - Add adc_clk_mmcm.v: MMCME2_ADV wrapper (VCO=800MHz, CLKOUT0=400MHz) - Modify ad9484_interface_400m.v: replace BUFG with MMCM path, gate reset on mmcm_locked - Add adc_clk_mmcm.xdc: CDC false paths for clk_mmcm_out0 <-> clk_100m XDC Fix (Build 19 WNS=-0.011 root cause): - Remove conflicting create_generated_clock -name clk_400m_mmcm - Replace all clk_400m_mmcm references with Vivado auto-generated clk_mmcm_out0 - CDC false paths now correctly apply to actual timing paths CIC CREG Pipeline (Build 18 critical path fix): - Explicit DSP48E1 for comb[0] with CREG=1/AREG=1/BREG=1/PREG=1 - Absorbs integrator_sampled_comb fabric FDRE into DSP48 C-port register - Eliminates 0.643ns fabric->DSP routing delay (Build 18 tightest path) - +1 cycle comb latency via data_valid_comb_0_out pipeline - Move shared register declarations above ifndef SIMULATION (iverilog fix) - Update golden data for +1 cycle CIC pipeline shift Build scripts: build19_mmcm.tcl, build20_mmcm_creg.tcl Regression: 18/18 FPGA pass, 20/20 MCU pass Build 20 launched on remote Vivado (pending results) |
||
|
|
94ffdb8f77 |
Add Phase 0 Vivado-style lint to regression runner, update golden data
Adds two-layer lint pass (iverilog -Wall + custom static checks) that catches part-select OOB errors and case-without-default warnings before pushing to remote Vivado. Catches the exact Synth 8-524 class error that broke Build 18 initial attempt. Lint errors abort regression; warnings are advisory. Regenerated golden data for BRAM-migrated matched filter. |
||
|
|
ed6f79c6d3 |
FIR DSP48 pipelining (BREG+MREG) + matched filter BRAM migration with overlap cache
FIR: Add coeff_reg/mult_reg pipeline stages to fix 68 DPIP-1 + 35 DPOP-2 DRC warnings. Valid pipeline widened 7→9 bits (+2 cycle latency). Matched filter: Migrate input_buffer_i/q from register arrays to BRAM (~33K FF savings). Overlap-save uses register cache captured during ST_PROCESSING to avoid BRAM read/write conflicts during overlap copy. New ST_OVERLAP_COPY state writes cached tail samples back sequentially. Both changes pass 18/18 FPGA regression. Golden data regenerated for +2 FIR latency baseline. |
||
|
|
463ebef554 |
CIC comb pipeline registers, BUFG sim guard, system TB fix, regression runner
- cic_decimator_4x_enhanced.v: Add integrator_sampled_comb and data_valid_comb_pipe pipeline stages between integrator sampling and comb computation to break the critical path (matches remote 40cda0f) - radar_system_top.v: Wrap 3 BUFG instances in ifdef SIMULATION guard with pass-through assigns for iverilog compatibility - radar_system_tb.v: Convert generate_radar_echo function to task and move sin_lut declaration before task (iverilog declaration-order fix), add modular index clamping to prevent LUT out-of-bounds - run_regression.sh: Automated regression runner for all 18 FPGA testbenches with --quick mode. Results: 17 pass, 1 pre-existing fail - .gitignore: Exclude *.vvp, *.vcd simulation artifacts |
||
|
|
f6877aab64 |
Phase 1 hardware bring-up prep: ILA debug probes, CDC waivers, programming scripts
- Rename latency_buffer_2159 -> latency_buffer (module + file + all refs) - Add CDC waivers for 5 verified false-positive criticals to XDC - Add ILA debug probe insertion script (4 cores, 126 probe bits, 2 clock domains) - Add FPGA programming script (7-step flow with DONE pin verification) - Add ILA capture script (4 scenarios + health check, CSV export) - Add debug_ila.xdc with MARK_DEBUG fallback attributes - Full regression clean: 13/13 suites, 266/266 checks, 2048/2048 golden match |
||
|
|
fcf3999e39 |
Fix CDC reset domain bug (P0), strengthen testbenches with 31 structural assertions
Split cdc_adc_to_processing reset_n into src_reset_n/dst_reset_n so source and destination clock domains use correctly-synchronized resets. Previously cdc_chirp_counter's destination-side sync chain (100MHz) was reset by sys_reset_120m_n (120MHz domain), causing 30 CDC critical warnings. RTL changes: - cdc_modules.v: split reset port, source logic uses src_reset_n, destination sync chains + output logic use dst_reset_n - radar_system_top.v: cdc_chirp_counter gets proper per-domain resets - ddc_400m.v: CDC_FIR_i/q use reset_n_400m (src) and reset_n (dst) - formal/fv_cdc_adc.v: updated wrapper for new port interface Build 7 fixes (previously untouched): - radar_transmitter.v: SPI level-shifter assigns, STM32 GPIO CDC sync - latency_buffer_2159.v: BRAM read registration - constraints: ft601 IOB -quiet fix - tb_latency_buffer.v: updated for BRAM changes Testbench hardening (tb_cdc_modules.v, +31 new assertions): - A5-A7: split-domain reset tests (staggered deassertion, independent dst reset while src active — catches the P0 bug class) - A8: port connectivity (no X/Z on outputs) - B7: cdc_single_bit port connectivity - C6: cdc_handshake reset recovery + port connectivity Full regression: 13/13 test suites pass (257 total assertions). |
||
|
|
6fc5a10785 | Fix range_bin_decimator overflow guard priority bug: group completion now takes precedence over overflow guard in ST_PROCESS, ensuring all OUTPUT_BINS outputs are emitted when sufficient input samples exist. Split formal property 5 into 5a (upper bound) and 5b (exact count when start_bin=0), added Cover 4 for overflow guard path, reduced BMC depth to 50. | ||
|
|
5fd632bc47 |
Fix all 10 CDC bugs from report_cdc audit, add overflow guard in range_bin_decimator
CDC fixes across 6 RTL files based on post-implementation report_cdc analysis:
- P0: sync stm32_mixers_enable and new_chirp_pulse to clk_120m via toggle CDC
in radar_transmitter, add ft601 reset synchronizer and USB holding
registers with proper edge detection in usb_data_interface
- P1: add ASYNC_REG to edge_detector, convert new_chirp_frame to toggle CDC,
fix USB valid edge detect to use fully-synced signal
- P2: register Gray encoding in cdc_adc_to_processing source domain, sync
ft601_txe and stm32_mixers_enable for status_reg in radar_system_top
- Safety: add in_bin_count overflow guard in range_bin_decimator to prevent
downstream BRAM corruption
All 13 regression test suites pass (159 individual tests).
|