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.
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.
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.
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.
STM32-006: Remove blocking do-while loop that waited for legacy GUI start
flag — production V7 PyQt GUI never sends it, hanging the MCU at boot.
STM32-004: Check ad9523_init() return code and call Error_Handler() on
failure, matching the pattern used by all other hardware init calls.
FPGA-001: Simplify frame boundary detection to only trigger on
chirp_counter wrap-to-zero. Previous conditions checking == N and == 2N
were unreachable dead code (counter wraps at N-1). Now correct for any
chirps_per_elev value.
- 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.
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.
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
P0-1: nco_400m_enhanced.v — DSP48E1 OPMODE corrected from PCIN to P
feedback (was routing stale cascade data into accumulator)
P0-2: radar_receiver_final.v — removed same-clock CDC that corrupted
ADC data path between ad9484_interface and DDC
P1-5: fir_lowpass.v — fixed zero replication count in coefficient
symmetric extension ({0{1'b0}} is empty, now uses explicit 0)
Also updates .gitignore to exclude debug/scratch artifacts.
All 30+ testbenches pass (unit, co-sim, integration).
RTL fix: matched_filter_multi_segment.v ST_WAIT_FFT now waits for
processing chain to complete ALL 1024 outputs and return to IDLE
before advancing to next segment. Previously, it transitioned on the
first fft_pc_valid edge, causing the chain to still be outputting
while multi-seg started collecting data for the next segment. This
broke the handshake and caused permanent deadlock after segment 0.
Also fixes forward reference of sample_addr_from_chain in
radar_receiver_final.v (declaration moved before first use).
New files:
- tb/tb_radar_receiver_final.v: P0 integration test for full RX
pipeline (ADC->DDC->MF->range_bin_decimator->doppler), 10 checks
- tb/ad9484_interface_400m_stub.v: behavioral ADC stub for iverilog
All existing tests still pass: multi-seg 32/32, MF co-sim 3/3,
Doppler co-sim 14/14.