47 Commits

Author SHA1 Message Date
Jason
ef32345b26 feat(rtl,gui): PR-U / M-8 — sub-frame enable mask routed end-to-end (C-5 hardening)
The chirp_scheduler had a 3-bit host_subframe_enable input {LONG, MEDIUM, SHORT}
that was tied to the constant RP_DEF_SUBFRAME_ENABLE at the receiver instance,
so the host could neither change it nor know what mask was active. With the
mask not at 3'b111 the scheduler skips a sub-frame at TX but doppler_processor
still writes 48 chirp slots, so the host CRT (`dbin // 16 → {SHORT, MED, LONG}`)
silently mis-attributes the SF axis and unfolds to the wrong velocity.

Plumb the mask through:

- radar_system_top.v: new reg [2:0] host_subframe_enable, cold-reset
  RP_DEF_SUBFRAME_ENABLE, opcode 0x19 setter, wired to rx_inst and usb_inst.
- radar_receiver_final.v: new host_subframe_enable[2:0] input port; the
  chirp_scheduler instance is untied from the constant.
- usb_data_interface_ft2232h.v: new subframe_enable[2:0] input + per-frame
  snapshot reg latched at frame_complete (stable for ft_clk read, same
  pattern as stream_flags_snapshot). Byte 2 emission is now
  {2'b00, subframe_enable[2:0], stream_flags[2:0]} — was {5'b00000, stream}.
- radar_protocol.py: Opcode.SUBFRAME_ENABLE = 0x19; RadarFrame.subframe_enable
  field; parse_bulk_frame surfaces bits[5:3]; reserved-mask 0xF8 → 0xC0.
  Bulk-frame mock encodes the mask in its emit so dashboard replay is correct.
- v7/processing.py: extract_targets_from_frame_crt forces every target to
  AMBIGUOUS when frame.subframe_enable != 0b111. Operator sees the red `?`
  flag in the targets table instead of a silently-wrong velocity.
- v7/software_fpga.py + v7/dashboard.py: subframe_enable mirror + setter, and
  replay dispatch routes 0x19 to set_subframe_enable.

Tests (test_v7.py): TestSubframeEnableRoundTrip (4), TestSoftwareFpgaSubframeEnable
(2), TestCrtSubframeMaskGating (3), 0x19 added to TestOpcodeEnumFillIn and
TestReplayOpcodeDispatch. Existing test_full_frame_round_trip updated to expect
byte 2 = 0x3F (mask 0b111 default + stream 0x07).

Cosim TBs (tb/tb_usb_protocol_v2.v, tb/tb_ft2232h_frame_drop.v) drive the new
input with 3'b111 and assert the new byte-2 layout (T2.3: 0x00 → 0x38).

Regression: test_v7 146/146, test_GUI_V65_Tk 117/117, ruff clean.
iverilog: tb_usb_protocol_v2 27/27 PASS, tb_ft2232h_frame_drop 10/10 PASS.
2026-05-02 17:49:16 +05:45
Jason
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.
2026-05-02 17:13:12 +05:45
Jason
049f7b5d14 fix(fpga): PR-Q.1 — stagger MEDIUM PRI 175→161 µs for 3-PRI Doppler CRT (C-5)
Bumps RP_DEF_MEDIUM_LISTEN_CYCLES 17000 → 15600 so MEDIUM PRI = 161 µs,
distinct from SHORT (175 µs) and LONG (167 µs). Three coprime PRIs let
the host run 3-PRI Chinese-Remainder unfolding on Doppler aliases beyond
the per-sub-frame ±~41 m/s unambiguous range — closes the FPGA half of
audit C-5 (PR-F Doppler ambiguity unfolding).

Stagger choice (proposal B):
  SHORT  175 µs — chirp 1   + listen 174
  MEDIUM 161 µs — chirp 5   + listen 156   (PR-Q, was 175)
  LONG   167 µs — chirp 30  + listen 137

In 3 km mode LONG is blind (4500 m blind zone) → SHORT-vs-MEDIUM
(Δ=14 µs / 8 %) is the operative pair; in 20 km mode MEDIUM-vs-LONG
(Δ=6 µs / 4 %) carries the long-range slice that has SNR for both.
Listens picked to differ by ≥5 % so the alias resolver is robust against
the 5.1 m/s/bin Doppler quantization.

Architecture is unchanged — chirp_scheduler.v already takes per-waveform
host_*_listen_cycles. doppler_processor.v / cfar_ca.v are PRI-agnostic
and just tag Doppler outputs with sub_frame ID; host-side CRT lives in
v7/processing.py (PR-Q.5, follow-on).

Files:
  radar_params.vh:240        RP_DEF_MEDIUM_LISTEN_CYCLES 17000 → 15600
  radar_params.vh:217-228    block comment: stagger rationale + Δ math
  radar_system_top.v:273     port-list comment: default 17000 → 15600
  radar_system_top.v:278-282 staggered-PRI block comment: 3-ladder PRI
  doppler_processor.v:25-30  reference v7/processing.py CRT unfolder
  tb/tb_radar_receiver_final.v:199-202  list MEDIUM=15600 in real-values

Validation: full iverilog regression 42 PASS / 0 FAIL / 1 SKIP (pre-
existing scipy availability) — same baseline as post-PR-O.8. No TB
default-value asserts touched (tb_system_opcodes / tb_usb_protocol_v2
both use literal 16500 for opcode 0x18 round-trip).

Follow-on: PR-Q.2 (cosim T_PRI_MEDIUM align + golden regen), PR-Q.4-7
(v7 GUI 3-PRI CRT unfolder + AMBIGUOUS confidence display), PR-Q.8
(memory close-out). MCU executeChirpSequence is live but PRI-agnostic
in production mode 2'b01 (FPGA auto-scan) — pre-existing 2-ladder
staleness vs chirp-v2 3-ladder, defer to PR-H or dedicated MCU PR.
2026-05-02 13:37:06 +05:45
Jason
58792d0e7d chirp-v2 PR-G: header/body consistency + runtime MEDIUM ladder
G1.5 (FSM trim): doppler section emits NUM_RANGE_BINS*NUM_DOPPLER_BINS
cells (49152 B) and detect emits packed valid bytes (6144 B), matching
the 9-byte header advertisement. Replaces flat counters with nested
range x doppler indices in usb_data_interface_ft2232h.v. Saves ~18.4 kB
per frame on the wire.

G2 (runtime MEDIUM ladder): adds opcodes 0x17/0x18 for medium chirp/
listen cycles with RP_DEF_MEDIUM_* defaults. Plumbed through
radar_system_top -> radar_receiver_final -> chirp_scheduler. SHORT/LONG
were already runtime-tunable; MEDIUM was hardcoded.

TBs: tb_usb_protocol_v2 adds TEST 4 (full-frame egress byte count =
56330) and TEST 5 (MEDIUM opcode round-trip) - 27/27 PASS.
tb_ft2232h_frame_drop updated for new section sizes - 10/10 PASS.

Full regression: 37/41 with 4 pre-existing failures (T-2..T-5,
tracked in PR-Tests-1 / PR-I). Stash test confirmed pre-PR-G HEAD has
identical failures - PR-G introduces zero new test regressions.
2026-05-01 11:10:06 +05:45
Jason
51a94f0baf chirp-v2 PR-F follow-up: doppler OOB read + dead cfar wires
Two issues caught re-reviewing 7862f4d:

1. doppler_processor.v: at sub_frame = NUM_SUBFRAMES-1 (=2 in production),
   the read-ahead pointer was advanced one cycle past the last useful chirp,
   producing an out-of-range mem_read_addr (chirps 48/49 in a 48-chirp frame)
   on the BRAM read port. The result was never consumed — counter > CPS-1
   blocks the multiply — so the OOB read had no functional effect, but it
   still drives mem_mem[OOB_idx] every frame and would trigger Vivado synth
   range warnings. Gate the read_doppler_index advance on
   fft_sample_counter <= CHIRPS_PER_SUBFRAME - 3 so the last NBA at
   counter = CPS-3 schedules the data needed at counter = CPS-1 and no more.
   For sub_frame < NUM_SUBFRAMES-1 this just replaces previously-wasted
   forward reads with redundant reads of the same address; outputs are
   bit-exact.

2. radar_system_top.v: cfar_detect_class, cfar_detect_threshold_soft, and
   cfar_detect_count_cand were declared and connected to cfar_inst but went
   nowhere downstream. They will be wired to USB / telemetry in PR-G; until
   then they show up as dangling wires that Vivado optimises away with
   noisy warnings. Drop the wire decls and leave the cfar_ca output ports
   unconnected. The soft-tier comparison is still synthesized because the
   1-bit detect_flag (which IS wired) depends on noise_product_soft via the
   `else if (cur > thr_soft)` branch, so the candidate logic is preserved
   in the netlist — only the class / soft-thr / cand-count rails are gone.

Tests (parity with the PR-F numbers in 7862f4d):
  - tb_chirp_controller:   43/43 PASS
  - tb_chirp_contract:     10/10 PASS
  - tb_cfar_ca:            24/0  PASS
  - tb_mti_canceller:      43/43 PASS
  - tb_doppler_realdata:   2056/2056 PASS
  - tb_doppler_frame_start_gate: 21/21 PASS
  - tb_system_e2e:         33/49 PASS (PR-F baseline parity)
2026-05-01 04:06:58 +05:45
Jason
7862f4d63c chirp-v2 PR-F: doppler/CFAR widen to 3 sub-frames + 2-class detect
Bumps RP_CHIRPS_PER_FRAME 32 -> 48 (= 3 sub-frames × 16 chirps), widens
doppler_bin from 5 to 6 bits ({sub_frame[1:0], bin[3:0]}), and replaces the
1-bit detect_flag rail with a 2-bit detect_class (NONE / CANDIDATE /
CONFIRMED) sourced from a soft+confirm CFAR threshold pair.

doppler_processor:
  Generalised the 2-subframe FSM to NUM_SUBFRAMES = CHIRPS_PER_FRAME /
  CHIRPS_PER_SUBFRAME (=3 in production, =2 when TBs override). S_OUTPUT
  walks current_sub_frame 0..NUM_SUBFRAMES-1 then advances range_bin;
  the chirp_base * CHIRPS_PER_SUBFRAME formula replaces the if/else split.
  write_chirp_index, read_doppler_index, sub_frame, current_sub_frame all
  widened to 6/2 bits accordingly. doppler_bin packing {current_sub_frame[1:0],
  fft_sample_counter[3:0]} naturally yields 6 bits.

cfar_ca:
  Adds cfg_alpha_soft input + r_alpha_soft register (default
  RP_DEF_CFAR_ALPHA_SOFT = 0x18 ≈ 1.5 in Q4.4 → Pfa_soft ≈ 1e-5). ST_CFAR_MUL
  computes both noise_product (alpha) and noise_product_soft (alpha_soft) in
  parallel DSPs; ST_CFAR_CMP emits detect_class = CONFIRMED when cur > thr,
  CANDIDATE when cur > thr_soft (and not CONFIRMED), NONE otherwise.
  detect_flag is preserved as (class != NONE) for backward compat.
  Address packing now pads doppler axis to next power-of-2 (DOPPLER_PAD =
  1 << ceil(log2(NUM_DOPPLER))) so {range, doppler} packs contiguously
  for both NUM_DOPPLER=32 (legacy TB) and NUM_DOPPLER=48 (production).
  Mag-BRAM grows from ~16 to ~30 RAMB18 on 50T (acceptable on the budget).

usb_data_interface_ft2232h:
  doppler_bin_in widened to 6 bits. FRAME_CELLS pads to next power of two
  (32K) so {range, doppler[5:0]} concatenation lands cleanly. Address regs
  bumped: mag_wr/rd_addr 14→15, detect_byte_addr 11→12, detect_clear bit-
  counter 14→15. Detect-bit BRAM grows 2K→4K bytes. Wire-protocol byte
  counts auto-scale with FRAME_CELLS / DOPPLER_MAG_SECTION_BYTES; PR-G
  bumps the bulk-frame protocol version so the host parser knows.

Other:
  - radar_params.vh: RP_CHIRPS_PER_FRAME 32→48, RP_NUM_DOPPLER_BINS 32→48,
    RP_DOPPLER_MEM_ADDR_W 14→15 (50T) / 17→18 (200T), RP_CFAR_MAG_ADDR_W
    likewise. Other macros (RP_DOPPLER_BIN_WIDTH=6, RP_DETECT_CLASS_WIDTH=2,
    RP_DEF_CFAR_ALPHA_SOFT=0x18, RP_NUM_SUBFRAMES=3) were already in place
    from PR-A.
  - radar_system_top: rx_doppler_bin / dbg_doppler_bin widened. Adds
    host_cfar_alpha_soft register (default RP_DEF_CFAR_ALPHA_SOFT). USB
    opcode mapping deferred to PR-G.
  - radar_system_top_50t: dbg_doppler_bin_nc width.
  - radar_receiver_final: doppler_bin port width.

Test summary:
  - tb_chirp_controller_v2:  43/43 PASS
  - tb_chirp_contract:       10/10 PASS
  - tb_cfar_ca:              24/0 PASS
  - tb_mti_canceller:        43/43 PASS
  - tb_rxb_fullchain:        peak 24033 ~80x (parity with PR-D/E)
  - tb_doppler_realdata:     2056/2056 PASS  (had been broken pre-PR-F due
                             to missing RANGE_BINS=64 override; this PR fixes
                             the parameter override along with the widening)
  - tb_system_e2e:           33/49 PASS — identical to PR-E baseline; the
                             one new fail vs PR-D (G2.2) carries over.
  - tb_radar_receiver_final: still finishing in background (~10 min).
2026-05-01 03:36:03 +05:45
Jason
a1a8fa7107 chirp-v2 PR-E: plfm_chirp_controller_v2 + scheduler-driven TX via async-FIFO
Replaces plfm_chirp_controller_enhanced (5-state FSM with hardcoded
LONG/SHORT timings + 60-entry inline short LUT) with plfm_chirp_controller_v2,
a pure DAC playback driver: IDLE -> CHIRP -> IDLE keyed off a 1-cycle
dst_chirp_valid pulse, with sample count selected by dst_wave_sel
(SHORT=120 / MEDIUM=600 / LONG=3600). Inter-chirp timing (LISTEN, GUARD,
frame boundaries) is now owned exclusively by chirp_scheduler.

Scheduler -> TX bridge: cdc_async_fifo (Cummings style #2, WIDTH=2 DEPTH=4)
crosses {wave_sel} from clk_100m to clk_120m_dac, with chirp_pulse as
src_valid. frame_pulse rides a separate toggle CDC for chirp_counter
clear and the new_chirp_frame status output. mixers_enable now also gates
the scheduler so it stays in S_IDLE while the radar is "off" — without
this gate the first chirp_pulse fires at reset and gets dropped before
mixers come up.

Files:
- NEW  plfm_chirp_controller_v2.v      DAC playback driver (3 LUTs, FSM)
- DEL  plfm_chirp_controller.v         legacy controller (382 lines)
- DEL  long_chirp_lut.mem              legacy LUT (3600 lines), replaced
                                       by tx_long_lut.mem from PR-B
- chirp_scheduler.v       + mixers_enable input (master quiesce)
- radar_receiver_final.v  + sched_*_out output ports + mixers_enable_100m
- radar_system_top.v      wire sched_*_out -> tx_inst.sched_*; pass
                          stm32_mixers_enable_100m to rx_inst
- radar_transmitter.v     full rewrite: drop new_chirp edge detector +
                          toggle CDC, instantiate cdc_async_fifo for
                          {wave_sel}, toggle CDC for frame_pulse,
                          plfm_chirp_controller_v2 in place of _enhanced
- tb/tb_chirp_controller.v  + tb/tb_chirp_contract.v  rewritten for v2
                          contract (43/43 unit + 10/10 contract green)
- tb/tb_radar_receiver_final.v  + .mixers_enable_100m(1'b1) pin
- run_regression.sh, scripts/200t/build_200t.tcl  file-list bumped

Test summary:
- tb_chirp_controller_v2:   43/43 PASS
- tb_chirp_contract:        10/10 contracts upheld
- tb_rxb_fullchain:         peak 24033 ~80x (parity with PR-D)
- tb_mti_canceller:         43/43 PASS
- tb_system_e2e:            33/49 (1 new vs 34/49 PR-D baseline: G2.2
                            new_chirp_frame, intentional v2 frame-pulse
                            semantics — fires once per Doppler frame
                            instead of once per stm32 chirp toggle.
                            TB needs widening in PR-H to wait the full
                            frame.)
2026-04-30 21:51:46 +05:45
Jason
8e8f3e60c4 chirp-v2 PR-D: chirp_scheduler replaces radar_mode_controller; MF/MTI wave_sel-native
Single 100 MHz scheduler emits wave_sel[1:0] and chirp_pulse natively. Modes
00 (STM32 pass-through), 01 (auto-scan over SHORT/MEDIUM/LONG sub-frames),
10 (single-chirp debug), 11 (track dwell with watchdog scan-fallback after
RP_DEF_TRACK_WATCHDOG_FRAMES=5 idle frames). Sub-frame mask lets ops drop a
waveform without recompiling.

Drops the receiver_final wave_sel shim added in PR-C: wave_sel comes
straight from the scheduler; chirp_pulse replaces the old mc_new_chirp
toggle + XOR edge converter. matched_filter_multi_segment and mti_canceller
take wave_sel[1:0] and chirp_pulse directly — no parallel paths.

multi_segment also bumped: SHORT_CHIRP_SAMPLES 50 -> 100 (V2 1 us SHORT)
and MEDIUM_CHIRP_SAMPLES = 500 (5 us). LONG path unchanged. Dead
mc_new_elevation/azimuth XOR converters removed.

Deletes radar_mode_controller.v, formal/fv_radar_mode_controller.v, and
tb/tb_radar_mode_controller.v. Build manifests (run_regression.sh,
scripts/200t/build_200t.tcl) updated. Receiver_final pins medium/track/
subframe_enable inputs to RP_DEF_* defaults until PR-G plumbs USB opcodes.

Verification:
- tb_rxb_fullchain_latency: peak |I|+|Q|=24033 at bin 0, ~80x peak/mean
  (up from PR-C's 15115 since matched filter now uses full 100 SHORT samples)
- tb_mti_canceller: 43/43 PASS with new wave_sel[1:0] input
- tb_radar_receiver_final: 8/8 PASS, ALL TESTS PASSED
- tb_system_e2e: 34/49 PASS - identical to pre-PR-D baseline (15 failures
  are pre-existing matched-filter cycle-budget skips); G8.2/G8.3 chirp_scheduler
  probes PASS
- tb_multiseg_cosim: 16/32 - same as pre-PR-D baseline
2026-04-30 20:52:32 +05:45
Jason
58154a6bf1 fpga: split gpio_dig5/dig7 by fault class (AUDIT-S10)
gpio_dig5 (PD13) previously OR'd six flags — four signal-saturation
classes (AGC, DDC overflow, DDC saturation, MTI saturation) and two
control-fault classes (range-decimator watchdog from F-6.4, CIC->FIR
CDC overrun from F-1.2). The MCU outer-loop AGC reduces RF gain on
PD13 assertion, which is the wrong response to a watchdog or CDC
stall — it just hides the stall behind a quiet receive chain. gpio_dig7
(PD15) was tied 1'b0 as "reserved".

Split:
  gpio_dig5 = signal-saturation only (AGC continues to react correctly)
  gpio_dig7 = control-fault classes

Telemetry: status_words[5][6:5] now exposes the two control-fault
classes in BOTH legacy (FT601) and FT2232H USB variants, with 2-FF
level CDC sync from clk_100m to ft601_clk_in / ft_clk. Bit [7] is
reserved. AUDIT-C12's frame_drop_count at [31:25] is preserved.

50T XDC H12 -> gpio_dig7 pin already assigned (audit AUDIT-C15-era);
no XDC change.

Test: tb/tb_audit_s10_gpio_split.v 17/17 PASS — exercises both the
combinational GPIO split and the CDC status-word packing path.
Regression: 39/39 PASS (was 34/34).
2026-04-29 20:06:52 +05:45
Jason
59f3c82fbb fpga: wire AD9484 PWDN to host opcode 0x32 (AUDIT-S25)
`radar_receiver_final.v:246` had `assign adc_pwdn = 1'b0;` -- the AD9484
PWDN pin was hard-tied LOW with no path for the host or MCU to assert
it. Combined with AUDIT-C13 (CSB hard-tied HIGH on the production board,
no SPI access to the AD9484), the ADC was fully un-recoverable from a
stuck state without dropping main power -- which also drops the
VBAT-backed BKPSRAM persistence (MCU-A4 OCXO warmup, MCU-A7 emergency
flag) and forces a 180 s warmup soak.

Opcode 0x32 was reserved during the AUDIT-C3 fix (commit 24ef5e7) for
exactly this purpose. Wire it through:

  - `radar_system_top.v` adds `reg host_adc_pwdn` next to `host_adc_format`,
    resets to 1'b0 (matches historical hard-tied state -- preserves
    bringup behavior), latches `usb_cmd_value[0]` on opcode 0x32, drives
    the new receiver input port.
  - `radar_receiver_final.v` adds `input wire host_adc_pwdn`, replaces the
    hard-coded `assign adc_pwdn = 1'b0` with `assign adc_pwdn = host_adc_pwdn`.
  - No CDC: `host_adc_pwdn` is a stable single-bit level driven from the
    clk_100m register straight to the I/O pad. AD9484 PWDN is asynchronous
    w.r.t. the ADC clock; the chip re-acquires its DLL on PWDN deassert.

XDC pin assignments were already in place from AUDIT-C15 (50T:T5,
200T:P20, both LVCMOS25 driving the AD9484 PWDN net via the R36/R37
divider on the Main Board).

Verification:
  - new tb/tb_adc_pwdn_opcode.v, 15/15 PASS:
      T1 reset -> host_adc_pwdn=0, adc_pwdn pin=0 (ADC powered up)
      T2 opcode 0x32 val=1 -> host_adc_pwdn=1, pin=1 (PWDN asserted)
      T3 opcode 0x32 val=0 -> cleared
      T4 only bit[0] consumed (upper bits ignored)
      T5 unrelated opcodes (0x33, 0x01) don't disturb host_adc_pwdn
      T6 cmd_valid_100m gating works
  - Quick regression 33/33 PASS (was 32/32; +1 new test, 0 regressions)
  - Lint: 0 errors
2026-04-29 19:37:37 +05:45
Jason
79a9353456 fix(usb): C-9 — GUI bulk-frame parser for FT2232H + clamp inert flag bits
The GUI's radar_protocol.py parsed 11-byte legacy packets only. The
production board (50T, USB_MODE=1) emits ~35 KB bulk frames from
usb_data_interface_ft2232h.v, so the legacy parser saw a random walk
of false 11-byte boundaries through bulk data — no usable display on
production hardware.

Bulk parser added (radar_protocol.py):
- parse_bulk_frame validates header, reserved bits, n_range=512,
  n_doppler=32, footer-at-flag-derived-offset; unpacks range_profile
  / doppler_mag / cfar_dense per the format-flags byte.
- find_bulk_frame_boundaries is the bulk counterpart of
  find_packet_boundaries; status packets (0xBB) handled in the same
  stream since FT2232H emits them too.
- RadarAcquisition dispatches on isinstance(conn, FT2232HConnection):
  bulk path skips the per-sample state machine and fills RadarFrame
  in one shot. FT601 / 200T keeps legacy 11-byte (USB 3.0 has 50x
  bandwidth headroom; per-sample format is correct and already works).
- RadarFrame.mag_only flag carries the wire's mag_only bit so
  downstream consumers can skip I/Q panels cleanly.
- FT2232HConnection._mock_read now emits synthetic bulk frames
  (was misleading legacy 11-byte).

RTL alignment (AUDIT-C9 RTL stub option):
- usb_data_interface_ft2232h.v header no longer promises the
  unimplemented mag_only=0 (full-I/Q) and sparse_det=1 paths;
  explicit INERT FLAGS note distinguishes the two reasons:
    * Full-I/Q is constrained by hardware — needs ~28-BRAM18 I/Q
      buffer (50T currently 78% BRAM utilised after FFT IP) AND
      USB 2.0 bandwidth (12.21 MB/s vs 8 MB/s conservative budget).
    * Sparse-list is feasible — smaller than dense for typical
      scenes (<341 detections), ~1 BRAM18 cost. Just unimplemented
      RTL work (small list BRAM + new WR_DETECT_SPARSE state).
- New SIMULATION-only assertion fires if stream_mag_only ever
  becomes 0 or stream_sparse_det ever becomes 1 — backstop for
  any future regression that bypasses the host-register clamp.
- radar_system_top.v opcode 0x04 force-clamps mag_only=1 and
  sparse_det=0 in host_stream_control when USB_MODE=1, so a
  Custom-Command host write can't push the FPGA into a wire-format
  vs FSM divergence.

Bandwidth math (verified for 27c9c22+):
  Frame rate = 1 / (16x167 us + 175.4 us + 16x175 us) = ~178 fps
  Mag-only frame = 8+1024+32768+2048+1 = 35849 B = 6.38 MB/s
  FT2232H 245-Sync-FIFO sustained budget (FTDI AN_232B-04
  conservative): 8 MB/s. Headroom 20%.

Tests: test_GUI_V65_Tk.py TestBulkFrameParser — 18 new cases covering
round-trip per stream-flag combo, header/footer/n_range/n_doppler/
reserved-bit/truncation rejection, multi-frame boundaries, bulk+status
mixed streams, byte-drop resync, dispatch-by-connection-type,
ingest-to-RadarFrame end-to-end. GUI 117/117 PASS, v7 83/83 PASS,
FPGA quick regression 29/29 PASS, ruff clean.

Refs: AUDIT-C9 (GUI parses legacy 11-byte vs FT2232H bulk).
Follow-ups (separate patches):
  - Sparse-detection write FSM (~1 BRAM18 + ~100 RTL lines).
    Bandwidth- and memory-feasible; just unimplemented work.
  - Full-I/Q write FSM. Constrained: needs ~28-BRAM18 I/Q buffer
    AND USB 2.0 bandwidth headroom (50T post-FFT-IP at 78% BRAM).
2026-04-29 15:12:04 +05:45
Jason
24ef5e7251 fix(fpga): C-3 — parameterize DDC ADC sign-conversion via host opcode 0x33
The DDC hard-coded an offset-binary->2C subtract on the AD9484 path. The
chip's output format is selected by the SCLK/DFS strap (jumper SJ1 on
RADAR_Main_Board.sch), and CSB is hard-tied HIGH so SPI cannot be used
to confirm or change it from firmware. If the board is assembled with
SJ1 on pins 2-3 (two's-complement), the existing RTL silently mis-
converts every sample.

Add a 2-bit adc_format input to ddc_400m_enhanced (2-FF synchronized
clk_100m -> clk_400m, ASYNC_REG attribute), drive it from a new top-
level register host_adc_format written by host opcode 0x33, and wire
it through radar_receiver_final. Default 2'b00 matches the SJ1 default
strap (offset-binary) and preserves pre-patch behavior. Opcode 0x32 is
intentionally left unused; reserved for the future S-25 fix
(host-driven adc_pwdn).

Tests: tb/tb_ddc_400m.v Test Group 5 — 7 new assertions covering
offset-binary at {0x80, 0x00, 0xFF}, two's-complement at
{0x00, 0x80, 0x7F}, and reserved 2'b10 fallback. 14/14 PASS.

Refs: AUDIT-C3 (DDC offset-binary hardcoded).
Schematic ref: RADAR_Main_Board.sch:46719 (CSB on +1V8_CLOCK_F),
:46845 (SCLK/DFS via SJ1).
2026-04-29 14:18:25 +05:45
Jason
ca2b6e527d fix(fpga): TX-G — surface chirps_mismatch_error to host status
`chirps_mismatch_error` was set in radar_system_top when the host
requested chirps_per_elev != Doppler FFT size, but never wired into the
USB status response — a latent silent failure.

Wired the flag through both USB interfaces (FT601 + FT2232H) into bit
[10] of status word 4 (was reserved). GUI parser exposes it as
StatusResponse.chirps_mismatch.

- usb_data_interface*.v: new status_chirps_mismatch input, packed at [10]
- radar_system_top.v: connect chirps_mismatch_error to both USB instances
- radar_protocol.py + test_GUI_V65_Tk.py: parse new bit, +1 round-trip test
- tb_usb_data_interface.v: drive the new port, update word-4 expectation

Tests: GUI 92/92 (was 91), MCU 75/75, USB TB 91/91, ruff clean repo-wide.
The 2 remaining FPGA regression failures (Receiver Integration, MF Chain)
are the pre-existing iverilog-can't-link-Xilinx-IP issue tracked
separately as the open RX-NEW-3 follow-up.
2026-04-24 11:06:26 +05:45
Jason
9d1eb4b11c fix(radar): RX chain corrections, GUI bin alignment, MCU boot ordering
FPGA — RX chain
  matched_filter_multi_segment.v: drop the gratuitous /4 scaling on
    DDC sign-extended input (was ddc_i[17:2] + ddc_i[1]); use
    ddc_i[15:0] directly. fft_engine has INTERNAL_W=32 with
    saturating 16-bit output, so full 16-bit input is safe. Restores
    ~12 dB of MF input dynamic range.
  radar_receiver_final.v: remove latency_buffer (count-N-pulses-then-
    prime FIFO that left frame 1 with all-zero ref). Replaced with
    a single-FF alignment register on ref_i/ref_q that matches the
    1-FF stage multi_segment ST_PROCESSING uses on adc_data.
    Verified by tb/tb_rxb_fullchain_latency.v — autocorrelation peak
    at bin 0 with peak/mean ~88x.
  doppler_processor.v / mti_canceller.v / cfar_ca.v /
    range_bin_decimator.v / radar_receiver_final.v / radar_system_top.v
    / usb_data_interface_ft2232h.v: switch port and parameter widths
    from RP_NUM_RANGE_BINS / RP_RANGE_BIN_BITS (always 512 / 9-bit)
    to RP_MAX_OUTPUT_BINS / RP_RANGE_BIN_WIDTH_MAX (auto-scales:
    50T 512 / 9-bit, 200T 4096 / 12-bit). Unblocks 200T 20 km mode
    at the RX module boundary; USB wire-protocol extension still
    pending.
  radar_receiver_final.v: doppler_frame_done_prev reset value 0 -> 1
    to prevent false done pulse on cycle 1 when level signal is
    HIGH at reset.
  matched_filter_processing_chain.v: delete the broken `ifdef
    SIMULATION inline behavioural FFT (482 lines removed). It
    produced wrong-bin peaks and 100-1000x weak magnitudes. Chain
    now uses production fft_engine.v + frequency_matched_filter.v
    in both iverilog and Vivado. Iverilog tests are ~38x slower per
    chain pass but produce correct results. Misleading "OK with
    Xilinx IP" comments at three test sites updated since the FFT
    is in-house, not an IP placeholder.

FPGA — testbenches
  tb/tb_rxb_latency_measure.v (new): measures chain internal pipeline
    depth (~2057 cycles, chirp-agnostic).
  tb/tb_rxb_fullchain_latency.v (new): full-chain autocorrelation
    verification — drives ddc with the same chirp samples the loader
    serves as ref, finds peak position and peak/mean.
  tb/tb_matched_filter_processing_chain.v: wait timeouts bumped
    50000 -> 500000 cycles to accommodate production FFT pipeline.

MCU
  main.cpp checkSystemHealthStatus: latch system_emergency_state on
    the error_count > 10 path so the SAFE-MODE blink loop in main()
    actually engages (was bypassed because predicate was false).
  main.cpp: move FPGA reset BEFORE the if(PowerAmplifier) block so
    adar_tr_x is driven LOW (RX commanded externally) before PA Vdd
    reaches 22 V. Old reset block at the original location removed.
  main.cpp MX_GPIO_Init: add GPIO_PIN_12 (FPGA reset) to the
    explicit WritePin(LOW) list so the safe initial state is no
    longer implicit.
  main.cpp checkSystemHealth: rate-limit ADAR1000
    verifyDeviceCommunication (HAL_Delay 1ms x 4 devices = 4 ms
    blocking SPI burst per main-loop iteration) from every-loop to
    every 2 s. readTemperature stays per-loop so over-temp
    detection latency is unchanged.
  USBHandler.cpp processSettingsData: dispatch threshold bumped
    74 -> 82 (matches parser minimum); buffer drained after parse
    attempt (slide remaining bytes left) so a false END find no
    longer sticks the buffer until 256-byte overflow.

GUI
  radar_protocol.py: NUM_RANGE_BINS 64 -> 512 (matches FPGA
    RP_NUM_RANGE_BINS); NUM_CELLS 2048 -> 16384.
  radar_protocol.py _ingest_sample: honor FPGA frame_start bit for
    resync after a USB drop; capture range_profile[rbin] once per
    range bin at dbin == 0 (FPGA emits the same range_i/range_q for
    all 32 Doppler cells of a given range bin; previous accumulator
    inflated the profile 32x).
  v7/models.py RadarSettings: range_resolution 24 -> 6 m (matches
    c/(2*100MHz)*4); max_distance and coverage_radius 1536 -> 3072 m;
    map_size 2000 -> 4000.
  v7/models.py WaveformConfig: n_range_bins 64 -> 512, fft_size
    1024 -> 2048, decimation_factor 16 -> 4.
  GUI_V65_Tk.py: _RANGE_PER_BIN math and stale "~24 m / ~1536 m"
    comments updated.
  test_v7.py: assertion values updated to match new defaults.

Tests
  test_ddc_cosim_fuzz.py: remove unused os/tempfile imports, wrap
    three long lines for ruff E501 compliance.
2026-04-23 05:56:52 +05:45
Jason
5f3002a4d1 merge(wave2): manual resolution of 6 shared files — fft-2048 × p0 audit
Hand-merged files modified on both fix/pre-bringup-audit-p0 and
feat/fft-2048-upgrade. Wave 1 (commit 60e49c7) took 20 files from fft
verbatim; this wave resolves the overlap.

- run_regression.sh: 3-way merge. Adopts fft's ${RECEIVER_RTL[@]} array
  refactor and drops the self-blessing golden pair from p0. Skip count
  bumped to 5.

- usb_data_interface.v (FT601/200T): p0 FSM + clock-loss watchdog kept
  wholesale; widened stream_control 3 -> 6 bits to carry fft's extended
  mode bits through the CDC sync chain and the 0xFF status word.

- mti_canceller.v: fft's BRAM-inferred 512-range-bin implementation as
  the base, with p0's F-6.3 saturation counter grafted onto the d1
  pipeline stage. Overflow detection uses the top-two-bits disagreement
  on diff_{i,q}_full (DATA_WIDTH+1 signed).

- radar_receiver_final.v: fft's 2048-pt / 512-bin structure + p0
  diagnostic plumbing (ADC overrange sticky+CDC, DDC diagnostics,
  tx_frame_start edge detector replacing chirp_counter frame sync,
  mti_saturation_count, range_decim_watchdog).

- radar_system_top.v: clean 3-way merge, orthogonal regions
  (+38 / -27).

- usb_data_interface_ft2232h.v (FT2232H/50T): fft's per-frame bulk BRAM
  rewrite kept wholesale. Ported two p0 items that are orthogonal to
  the write FSM:
    * ft_clk-loss watchdog (heartbeat + 2FF ASYNC_REG sync + 16-bit
      timeout) ORed into a 2FF sync'd ft_effective_reset_n for the FSM.
    * rd_cmd_complete flag so RD_DEASSERT can distinguish a legitimate
      3-byte completion from an ft_rxf_n abort that also zeros
      rd_byte_cnt.

Deliberately NOT taken from 2401f5f: cic_decimator_4x_enhanced.v and
ddc_400m.v reset-strategy changes. Those conflict with p0's shipped
registered-sync-reset + max_fanout=25 distribution, which is already
timing-clean on the production build.
2026-04-21 02:12:04 +05:45
Jason
bec578a5e7 Revert "fix(fpga): F-0.9 option A — BUFIO+BUFR for 50T ft_clkout (SRCC pin)"
This reverts commit 30279e8c4d73a53a23615bdb7d8beec5c8a51794.
2026-04-20 21:47:19 +05:45
Jason
3b666ac47f Revert "fix(fpga): move IBUF+BUFIO+BUFR into 50T wrapper (same scope as pad)"
This reverts commit 813ee4c962496ae0ff3c68f34b35dacd6bd8003a.
2026-04-20 21:47:19 +05:45
Jason
813ee4c962 fix(fpga): move IBUF+BUFIO+BUFR into 50T wrapper (same scope as pad)
The previous attempt put BUFIO inside u_core/gen_ft_bufr, but the pad
(ft_clkout) and its inferred IBUF live at the top wrapper level. Vivado
shape-packs IBUF↔BUFIO into the same IOB tile, and it couldn't do that
across the wrapper→u_core hierarchy boundary — producing CRITICAL
WARNING [12-1411] "Illegal to place BUFIO on TIEOFF site" and WNS=-5.737
(worse than the CLOCK_DEDICATED_ROUTE=FALSE baseline).

Fix: instantiate IBUF+BUFIO+BUFR explicitly in radar_system_top_50t.v
and pass the BUFR output into u_core.ft601_clk_in. radar_system_top.v
now does a pass-through wire assign for USB_MODE=1 (no BUFG) so the
clock net doesn't get double-buffered.
2026-04-20 21:02:56 +05:45
Jason
30279e8c4d fix(fpga): F-0.9 option A — BUFIO+BUFR for 50T ft_clkout (SRCC pin)
C4 is an SRCC pin (IS_CLK_CAPABLE=1, IS_MASTER=0 in the Vivado device
model), not an MRCC as earlier comments claimed. SRCC cannot drive BUFG
through dedicated routing, so the previous CLOCK_DEDICATED_ROUTE=FALSE
override forced fabric routing and burned ~5 ns on the ft_clkout path
(WNS -5.362 ns in the d36a4c9 build).

Swap to BUFIO + BUFR for USB_MODE=1 (50T/FT2232H): SRCC → BUFIO → BUFR
is the standard 7-series path for regional clock distribution. All
ft_clkout-domain logic (FT2232H FSM, toggle CDCs, USB FIFO flops) is
contained in bank 35 / one clock region, so regional distribution is
sufficient. USB_MODE=0 (200T/FT601) keeps the BUFG because D17 is a
proper MRCC pin.

Removed CLOCK_DEDICATED_ROUTE=FALSE from both the XDC and the build
script — no longer needed with dedicated BUFIO/BUFR routing.
2026-04-20 20:53:49 +05:45
Jason
70067c6121 fix(fpga): F-0.1 wire AD9484 OR overrange pin into diagnostics
The AD9484 OR (overrange) LVDS pair is routed on the 50T main board to
xc7a50t-ftg256 bank-14 pins M6/N6 but was previously left unconnected at
the top level. Plumb it through the full stack so saturation at the raw
ADC boundary shows up in the existing overflow aggregation:

- ad9484_interface_400m: add adc_or_p/n inputs, IBUFDS + IDDR capture of
  both phases in the BUFIO domain, re-register into the clk_400m BUFG
  domain, OR rise|fall into adc_overrange_400m output.
- radar_receiver_final: stickify adc_overrange_400m in clk_400m, CDC to
  clk_100m via a 2FF ASYNC_REG chain (same reasoning as F-1.2's
  cdc_cic_fir_overrun — single-bit, latched low→high, GPIO-class
  diagnostic), OR into the existing ddc_overflow_any aggregation.
- radar_system_top: expose adc_or_p/n top-level ports and pass through.
- xc7a50t_ftg256.xdc: anchor M6/N6 as LVDS_25 DIFF_TERM, with the same
  DCO-relative input-delay constraints as adc_d_p[*].
- xc7a200t_fbg484.xdc: IOSTANDARD/DIFF_TERM set; PACKAGE_PIN left as a
  documented TODO — the 200T dev-board schematic has not been checked
  and the 200T build will need the anchor filled in before place/route.
2026-04-20 15:48:34 +05:45
Jason
675b1c0015 fix(pre-bringup): second-batch P1/P2/P3 audit findings
Addresses the remaining actionable items from
docs/DEVELOP_AUDIT_2026-04-19.md after commit 3f47d1e.

XDC (dead waivers — F-0.4, F-0.5, F-0.6, F-0.7):
- ft_clkout_IBUF CLOCK_DEDICATED_ROUTE now uses hierarchical filter;
  flat net name did not exist post-synth.
- reset_sync_reg[*] false-path rewritten to walk hierarchy and filter
  on CLR/PRE pins.
- adc_clk_mmcm.xdc ft601_clk_in references replaced with foreach-loop
  over real USB clock names, gated on -quiet existence.
- MMCM LOCKED waiver uses REF_PIN_NAME filter instead of the
  previously-missing u_core/ literal path.

CDC (F-1.1, F-1.2, F-1.3):
- Documented the quasi-static-bus stability invariant above the
  FT601 cmd_valid toggle block.
- cdc_adc_to_processing gains an `overrun` output; the two CIC->FIR
  instances feed a sticky cdc_cic_fir_overrun flag surfaced on
  gpio_dig5 so silent sample drops become visible to the MCU.
- Removed the dead mixers_enable synchronizer in ddc_400m.v; the _sync
  output was unused and every caller ties the port to 1'b1.

Diagnostics (F-6.4):
- range_bin_decimator watchdog_timeout plumbed through receiver
  and top-level, OR'd into gpio_dig5.

ADAR (F-4.7):
- delayUs() replaced with DWT cycle counter; self-initialising
  TRCENA/CYCCNTENA, overflow-safe unsigned subtraction.

Regression: tb_cdc_modules.v 57/57 passes under iverilog after
the cdc_modules.v change. Remote Vivado verification in progress.
2026-04-20 14:28:22 +05:45
Jason
3f47d1ef71 fix(pre-bringup): resolve P0 + quick-win P1 findings from 2026-04-19 audit
Addresses findings from docs/DEVELOP_AUDIT_2026-04-19.md:

P0 source-level:
- F-4.3 ADAR1000_Manager::adarSetTxPhase now writes REG_LOAD_WORKING
  with LD_WRK_REGS_LDTX_OVERRIDE (0x02) instead of 0x01. Previous value
  toggled the LDRX latch on a TX-phase write, so host TX phase updates
  never reached the working registers.
- F-6.1 DDC mixer_saturation / filter_overflow / diagnostics were deleted
  at the receiver boundary. Now plumbed to new outputs on
  radar_receiver_final (ddc_overflow_any, ddc_saturation_count) and
  aggregated into gpio_dig5 in radar_system_top. Added mark_debug
  attributes for ILA visibility. Test/debug inputs tied low explicitly.
- F-0.8 adc_clk_mmcm.xdc set_clock_uncertainty: removed invalid -add
  flag (Vivado silently rejected it, applying zero guardband). Now uses
  absolute 0.150 ns which covers 53 ps jitter + ~100 ps PVT margin.

P1:
- F-4.2 adarSetBit / adarResetBit reject broadcast=ON — the RMW sampled
  a single device but wrote to all four, clobbering the other three's
  state.
- F-4.4 initializeSingleDevice returns false and leaves initialized=false
  when scratchpad verification fails; previously marked the device
  initialized anyway so downstream PA enable could drive a dead bus.
- F-6.2 FIR I/Q filter_overflow ports, previously unconnected, now OR'd
  into the module-level filter_overflow output.
- F-6.3 mti_canceller exposes 8-bit saturation counter. Saturation was
  previously invisible and produces spurious Doppler harmonics.

Verification:
- 27/27 iverilog testbenches pass
- 228/228 pytest pass (cross-layer contract + cosim)
- MCU unit tests 51/51 + 24/24 pass
- Remote Vivado 2025.2 build: bitstream writes; 400 MHz mixer pipeline
  now shows WNS -0.109 ns which MATCHES the audit's F-0.9 prediction
  that the design only closed because F-0.8's guardband was silently
  dropped. ft_clkout F-0.9 remains a show-stopper (requires MRCC pin
  move), tracked separately.

Not addressed in this PR (larger scope, follow-up tickets):
F-0.4, F-0.5, F-0.6, F-0.7, F-0.9, F-1.1, F-1.2, F-2.2, F-3.2, F-4.1,
F-4.7, F-6.4, F-6.5.
2026-04-20 13:48:36 +05:45
NawfalMotii79
d3476139e3
Merge pull request #89 from NawfalMotii79/feat/ft2232h-default-ft601-option
feat: make FT2232H default USB interface, add FT601 premium option, deprecate GUI V6
2026-04-17 22:21:58 +01:00
Jason
658752abb7 fix: propagate FPGA AGC enable to MCU outer loop via DIG_6 GPIO
Resolve cross-layer AGC control mismatch where opcode 0x28 only
controlled the FPGA inner-loop AGC but the STM32 outer-loop AGC
(ADAR1000_AGC) ran independently with its own enable state.

FPGA: Drive gpio_dig6 from host_agc_enable instead of tied low,
making the FPGA register the single source of truth for AGC state.

MCU: Change ADAR1000_AGC constructor default from enabled(true) to
enabled(false) so boot state matches FPGA reset default (AGC off).
Read DIG_6 GPIO every frame with 2-frame confirmation debounce to
sync outerAgc.enabled — prevents single-sample glitch from causing
spurious AGC state transitions.

Tests: Update MCU unit tests for new default, add 6 cross-layer
contract tests verifying the FPGA-MCU-GUI AGC invariant chain.
2026-04-17 00:04:37 +05:45
Jason
f393e96d69 feat(fpga): make FT2232H default USB interface, rewrite FT601 write FSM, add clock-loss watchdog
- Set USB_MODE default to 1 (FT2232H) in radar_system_top.v; 200T build
  overrides to USB_MODE=0 via build_200t.tcl generic property
- Rewrite FT601 write FSM: 4-state architecture with 3-word packed data,
  pending-flag gating, and frame sync counter
- Add FT2232H read FSM rd_cmd_complete flag, stream field zeroing, and
  range_data_ready 1-cycle pipeline delay in both USB modules
- Implement clock-loss watchdog: ft_heartbeat toggle + 16-bit timeout
  counter drives ft_clk_lost, feeding ft_effective_reset_n via 2-stage
  ASYNC_REG synchronizer chain
- Fix sample_counter reset literal width (11'd0 -> 12'd0)
- Add FT2232H I/O timing constraints to 50T XDC; fix dac_clk comments
- Document vestigial ft601_txe_n/rxf_n ports (needed for 200T XDC)
- Tie off AGC ports on TE0713 dev wrapper
- Rewrite tb_usb_data_interface.v for new 4-state FSM (89 checks)
- Add USB_MODE=1 regression runs; remove dead CHECK 5/6 loop
- Update diag_log.h USB interface comment
2026-04-16 16:18:52 +05:45
Jason
db4e73577e fix: use authoritative tx frame signal for frame sync, consistent ad9523 error path
FPGA-001: The previous fix derived frame boundaries from chirp_counter==0,
but that counter comes from plfm_chirp_controller_enhanced which overflows
to N (not wrapping at chirps_per_elev). This caused frame pulses only on
6-bit rollover (every 64 chirps) instead of every N chirps. Now wires the
CDC-synchronized tx_new_chirp_frame_sync signal from the transmitter into
radar_receiver_final, giving correct per-frame timing for any N.

STM32-004: Changed ad9523_init() failure path from Error_Handler() to
return -1, matching the pattern used by ad9523_setup() and ad9523_status()
in the same function. Both halt the system, but return -1 keeps IRQs
enabled for diagnostic output.
2026-04-16 00:33:27 +05:45
Jason
ffba27a10a feat: hybrid AGC (FPGA phases 1-3 + GUI phase 6) with timing fix
FPGA:
- rx_gain_control.v rewritten: per-frame peak/saturation tracking,
  auto-shift AGC with attack/decay/holdoff, signed gain -7 to +7
- New registers 0x28-0x2C (agc_enable/target/attack/decay/holdoff)
- status_words[4] carries AGC metrics (gain, peak, sat_count, enable)
- DIG_5 GPIO outputs saturation flag for STM32 outer loop
- Both USB interfaces (FT601 + FT2232H) updated with AGC status ports

Timing fix (WNS +0.001ns -> +0.045ns, 45x improvement):
- CIC max_fanout 4->16 on valid pipeline registers
- +200ps setup uncertainty on 400MHz domain
- ExtraNetDelay_high placement + AggressiveExplore routing

GUI:
- AGC opcodes + status parsing in radar_protocol.py
- AGC control groups in both tkinter and V7 PyQt dashboards
- 11 new AGC tests (103/103 GUI tests pass)

Cross-layer:
- AGC opcodes/defaults/status assertions added (29/29 pass)
- contract_parser.py: fixed comment stripping in concat parser

All tests green: 25 FPGA + 103 GUI + 29 cross-layer = 157 pass
2026-04-13 19:24:11 +05:45
Jason
408f4d126f feat(usb): add FT2232H USB 2.0 interface for 50T production board
Replace FT601 (USB 3.0, 32-bit) with FT2232H (USB 2.0, 8-bit) on the
50T production board per updated Eagle schematic (commit 0db0e7b).
USB 3.0 via FT601 remains available on the 200T premium board.

RTL changes:
- Add usb_data_interface_ft2232h.v: 245 Sync FIFO interface with toggle
  CDC (3-stage) for reliable 100MHz->60MHz clock domain crossing,
  mux-based byte serialization for 11-byte data packets, 26-byte status
  packets, and 4-byte sequential command read FSM
- Add USB_MODE parameter to radar_system_top.v with generate block:
  USB_MODE=0 selects FT601 (200T), USB_MODE=1 selects FT2232H (50T)
- Wire FT2232H ports in radar_system_top_50t.v with USB_MODE=1 override,
  connect ft_clkout to shared clock input port
- Add post-DSP retiming register in ddc_400m.v to fix marginal 400MHz
  timing path (WNS improved from +0.070ns to +0.088ns)

Constraints:
- Add FT2232H pin assignments for all 15 signals on Bank 35 (LVCMOS33)
- Add 60MHz ft_clkout clock constraint (16.667ns) on MRCC N-type pin C4
- Add CLOCK_DEDICATED_ROUTE FALSE for N-type MRCC workaround
- Add CDC false paths between ft_clkout and clk_100m/clk_120m_dac

Build scripts:
- Add PLIO-9 DRC demotion and CLOCK_DEDICATED_ROUTE property in build_50t.tcl
- Add usb_data_interface_ft2232h.v to build_200t.tcl explicit file list

Python host:
- Add FT2232HConnection class using pyftdi SyncFIFO (VID 0x0403:0x6010)
- Add compact 11-byte packet parser for FT2232H data packets
- Update RadarAcquisition to support both FT601 and FT2232H connections

Test results:
- iverilog regression: 23/23 PASS
- Vivado Build 15 (XC7A50T): WNS=+0.088ns, WHS=+0.059ns, 0 violations
- DSP48E1: 112/120 (93.3%), LUTs: 10,060/32,600 (30.9%)
2026-04-07 19:22:16 +03:00
Serhii
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
2026-04-06 23:15:50 +03:00
Jason
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).
2026-03-20 20:03:11 +02:00
Jason
f8d80cc96e Add radar dashboard GUI with replay mode for real ADI CN0566 data visualization, FPGA self-test module, and co-sim npy arrays 2026-03-20 19:02:06 +02:00
Jason
ed629e7559 Integrate MTI canceller and DC notch filter for ground clutter removal
MTI canceller (2-pulse, H(z)=1-z^{-1}) between range decimator and
Doppler processor. Subtracts previous chirp from current, nulling DC
Doppler (stationary clutter). Pass-through when host_mti_enable=0.

DC notch filter (post-Doppler, pre-CFAR) zeros bins within
+/-host_dc_notch_width of DC. Complements MTI for residual clutter.

New host registers: 0x26 (mti_enable), 0x27 (dc_notch_width).
Both default to 0 (disabled) - fully backward-compatible.

Verification: 23/23 regression, 29/29 MTI standalone, 3/3 real-data
co-sim (5137/5137 exact match) all PASS.
2026-03-20 16:39:17 +02:00
Jason
f71923b67d Integrate CA-CFAR detector: replace fixed-threshold comparator with adaptive sliding-window CFAR engine (22/22 regression PASS)
- Add cfar_ca.v: CA/GO/SO-CFAR with BRAM magnitude buffer, host-configurable
  guard cells, training cells, alpha multiplier, and mode selection
- Replace old threshold detector block in radar_system_top.v with cfar_ca
  instantiation; backward-compatible (cfar_enable defaults to 0)
- Add 5 new host registers: guard (0x21), train (0x22), alpha (0x23),
  mode (0x24), enable (0x25)
- Expose doppler_frame_done_out from radar_receiver_final for CFAR frame sync
- Add tb_cfar_ca.v standalone testbench (14 tests, 24 checks)
- Add Group 14 E2E tests: 13 checks covering range-mode (0x20) and all
  CFAR config registers (0x21-0x25) through full USB command path
- Update run_regression.sh with CFAR in lint, Phase 1, and integration compiles
2026-03-20 04:57:34 +02:00
Jason
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)
2026-03-20 04:38:35 +02:00
Jason
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
2026-03-20 01:45:00 +02:00
Jason
7cdfa486e5 Gap 2 GUI Settings: runtime chirp timing, stream control gating, status readback (18/18 FPGA, 20/20 MCU)
Register map: 0x10-0x15 chirp timing overrides, 0xFF status readback,
0x03 CFAR threshold now wired to actual compare, 0x04 stream control
gates USB write FSM. Status readback sends 7-word packet (0xBB header,
5 status words, 0x55 footer) via toggle CDC.

radar_mode_controller: 6 cfg_* input ports replace hardcoded parameters.
usb_data_interface: stream_control CDC, status_request toggle CDC,
  SEND_STATUS state (3'd7), stream gating in IDLE/HEADER/RANGE/DOPPLER.
radar_system_top: 6 host registers + command decode for 0x10-0x15/0xFF.
radar_receiver_final: 6 host_* timing passthrough ports.

Testbench coverage: RMC 81 checks (+TG16 runtime reconfig), USB 77
checks (+TG15 stream gating, TG16 status readback, TG17 chirp opcodes).
Fixed iverilog 13.0 forward-ref for status_req_toggle_100m.
2026-03-19 23:54:48 +02:00
Jason
e5d1b3cfc3 Gap 4 USB Read Path: wire host-to-FPGA command path with toggle CDC, add read path tests
- usb_data_interface.v: Add FT601 read FSM (RD_IDLE/OE_ASSERT/READING/
  DEASSERT/PROCESS) + cmd_data/valid/opcode/addr/value output ports
- radar_system_top.v: Connect cmd_* ports from usb_inst, add toggle CDC
  for cmd_valid (ft601_clk -> clk_100m), add command decode registers
  (mode/trigger/cfar_threshold/stream_control), wire host_mode and
  host_trigger to rx_inst
- radar_receiver_final.v: Add host_mode[1:0] and host_trigger input
  ports, replace hardcoded mode/trigger in radar_mode_controller instance
- tb_usb_data_interface.v: Connect cmd_* ports, add host data bus driver,
  add Test Groups 12 (single command), 13 (multiple commands), 14
  (read/write interleave). USB TB now has 55 checks.

Regression: 18/18 PASS
2026-03-19 23:16:26 +02:00
Jason
2763b4be91 Fix CFAR blocking assignment (= to <=) in clocked block, add Build 15 analysis report
CFAR magnitude computation in radar_system_top.v used blocking assignment (=)
inside posedge-clocked always block, creating sim/synth mismatch risk. Changed
to non-blocking (<=). Threshold check now reads previous cycle's magnitude,
which is correct sequential behavior. Regression: 15/15 quick + system TB pass.

Build 15 analysis written to 10_docs/reports/ (not tracked — gitignored).
2026-03-19 13:22:15 +02:00
Jason
3fa26c9e4c Wire matched filter range profile to USB, replacing Doppler placeholder 2026-03-19 12:33:40 +02:00
Jason
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
2026-03-19 11:31:46 +02:00
Jason
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).
2026-03-17 19:38:09 +02:00
Jason
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).
2026-03-17 13:48:47 +02:00
Jason
49eb6169b6 Widen ft601_be to [3:0] for 32-bit FT601 mode, fix NCO XSim TB
- Expand ft601_be from [1:0] to [3:0] across RTL, top-level, testbenches,
  and XDC (uncomment be[2:3] pin assignments B21/A21)
- Fix NCO XSim testbench: correct reset check (0x7FFF not 0), add pipeline
  warmup and sample skip for DSP48E1 quadrature test
- All local regression tests pass (39/39 USB, 10/10 integration, all co-sim)
2026-03-16 23:17:38 +02:00
Jason
c983a3c705 Achieve timing closure: DSP48E1 pipelines, 4-stage NCO, 28-bit CIC, ASYNC_REG
Phase 0+ timing optimization (attempts #13-22 + implementation):

NCO (nco_400m_enhanced.v):
- 4-stage pipeline: DSP48E1 accumulate -> LUT read -> negate -> quadrant MUX
- DSP48E1 phase accumulator in P=P+C mode (eliminates 8-stage CARRY4 chain)
- Registered phase_inc_dithered to break cascaded 32-bit add path

DDC (ddc_400m.v):
- Direct DSP48E1 instantiation for I/Q mixers (AREG=1, BREG=1, MREG=1, PREG=1)
- CEP=1, RSTP=!reset_n for proper pipeline control
- 3-stage dsp_valid_pipe for PREG=1 latency
- Behavioral sim model under ifdef SIMULATION for Icarus compatibility

CIC (cic_decimator_4x_enhanced.v):
- 28-bit accumulators (was 36) per CIC width formula: 18 + 5*log2(4) = 28
- Removed integrator/comb saturation (CIC uses wrapping arithmetic by design)
- Pipelined output saturation comparison

CDC/ASYNC_REG:
- ASYNC_REG attribute on all CDC synchronizer registers (cdc_modules.v,
  radar_system_top.v, usb_data_interface.v)
- Sync reset in generate blocks (cdc_modules.v)

Results: Vivado post-implementation WNS=+1.196ns, 0 failing endpoints,
850 LUTs (1.34%), 466 FFs (0.37%), 2 DSP48E1 (0.83%) on xc7a100t.
All testbenches pass: 241/244 (3 known stub failures).
2026-03-16 01:02:07 +02:00
Jason
ffed7c1623 Fix CDC timing violations: add synchronizers for all inter-clock crossings
Resolve all 4 inter-clock timing violations found in Vivado synthesis
attempt #11 (WNS was -2.552 ns). Changes:

- Add reset synchronizer for clk_120m_dac domain (2-FF chain)
- Add Gray-code CDC for chirp_counter (6-bit, 120MHz->100MHz)
- Add single-bit CDC for new_chirp_frame (3-stage, 120MHz->100MHz)
- Add 2-stage input synchronizers for valid signals in USB module
  (clk_100m->ft601_clk_in) with data capture on rising edge
- Fix ft601_clk_out multi-driven net (removed duplicate assignment)
- Update XDC: set_max_delay -datapath_only for CDC, false_path for reset

Result: Vivado attempt #12 passes with 0 errors, 0 timing violations,
and 'All user specified timing constraints are met.' (WNS +0.983 ns)
2026-03-15 17:58:14 +02:00
Jason
76183e2e95 Fix 6 RTL bugs in FPGA signal processing chain
- nco_400m_enhanced.v: Correct sine LUT values (3.7x quadrature error)
- ddc_400m.v: Fix wire forward-declaration (Verilog-2001 compliance)
- plfm_chirp_controller.v: Remove multi-driven chirp_counter (critical)
- radar_system_top.v: Fix CFAR wire-as-reg, connect chirp_counter to receiver
- radar_receiver_final.v: Promote chirp_counter to input port

All fixes verified with Icarus Verilog 13.0 testbenches (144/144 tests pass).
2026-03-15 06:14:04 +02:00
NawfalMotii79
45ad19184c
Add files via upload 2026-03-10 01:31:50 +00:00