Bug 1 (FPGA): status_words[0] was 37 bits (8+3+2+5+3+16), silently
truncated to 32. Restructured to {0xFF, mode[1:0], stream[2:0],
3'b000, threshold[15:0]} = 32 bits exactly. Fixed in both
usb_data_interface_ft2232h.v and usb_data_interface.v.
Bug 2 (Python): radar_mode extracted at bit 21 but was actually at
bit 24 after truncation — always returned 0. Updated shift/mask in
parse_status_packet() to match new layout (mode>>22, stream>>19).
Bug 3 (STM32): parseFromUSB() minimum size check was 74 bytes but
9 doubles + uint32 + markers = 82 bytes. Buffer overread on last
fields when 74-81 bytes passed.
All 166 tests pass (29 cross-layer, 92 GUI, 20 MCU, 25 FPGA).
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.
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.
Accidentally included SSH key path, hostname, port, and internal server
paths in the build quick-reference section. Replaced with generic
instructions.
Add USB Interface Architecture section documenting the USB_MODE parameter,
generate block mechanism, per-target wrapper pattern, FT2232H pin map, and
build quick-reference. Update top modules table (50T now uses
radar_system_top_50t), bank voltage tables, and signal differences to
reflect the FT2232H/FT601 dual-interface design.
- Add set_false_path -hold for source-synchronous ADC IDDR paths in
adc_clk_mmcm.xdc (eliminates 8 hold violations from build 12)
- Add DDR falling-edge input delay constraints to xc7a50t_ftg256.xdc
(parity with 200T XDC)
- Reorganize scripts/ into target subdirectories: 50t/, 200t/, te0712/,
te0713/, utils/ so users can run the correct build for their hardware
- Delete obsolete build scripts (build17-20) superseded by build_50t/200t
- Update project_root paths in all moved scripts (.. -> ../..)
Add (* USE_DSP = "no" *) attribute to FIR lowpass adder tree registers
(add_l1, add_l2, add_l3, accumulator_reg) to prevent Vivado from
inferring DSP48E1 slices for pure addition operations.
Each fir_lowpass_parallel_enhanced instance was using 47 DSPs (32 for
multiply + 15 for the adder tree). The 15 adder-tree DSPs per instance
(30 total for I/Q pair) performed only PCIN+A:B additions with no
multiplier usage. On the XC7A50T with only 120 DSP48E1 slices, this
caused 100% DSP utilization and forced FFT butterfly complex multipliers
to spill into 18-level fabric carry chains (WNS=-1.103ns).
Moving these 36-bit additions to fabric CARRY4 chains (~9 CARRY4 per
add, ~2ns propagation) is well within the 10ns clock period and frees
~30 DSPs for the FFT engine to use native DSP48E1 multipliers.
Regression: 23/23 FPGA tests PASS (attribute is synthesis-only).
Build attempt 10 produced a valid bitstream but with only 315 LUTs and
15 DSPs — opt_design removed all logic feeding unconnected _nc wires.
Adding (* DONT_TOUCH = "TRUE" *) on the u_core instance prevents
Vivado from optimizing away the internal radar pipeline logic.
The XC7A50T-FTG256 has only 69 usable IO pins but radar_system_top
declares 182 port bits. Previous attempts to remove unconstrained
ports via TCL caused opt_design to cascade-remove all driving logic.
New approach: radar_system_top_50t.v is a thin wrapper that:
- Exposes only the 64 physically-connected ports (ADC, DAC, SPI, clocks)
- Instantiates radar_system_top internally with full logic preserved
- Ties off unused inputs (FT601 bus, ext trigger) to safe defaults
- Leaves unused outputs internally connected (no IOBs created)
Updated build_50t_test.tcl to use radar_system_top_50t as top module
and removed the now-unnecessary port removal TCL code.
remove_port fails on connected ports with [Coretcl 2-28]. Add
disconnect_net step before remove_port to properly detach the
port from its driving/driven nets in the synthesized netlist.
The 50T FTG256 has only 69 usable IO pins but the RTL declares 182 port
bits. launch_runs spawns a child process that cannot remove ports.
Switch to direct opt_design/place_design/route_design flow so we can
remove 118 unconstrained ports (FT601 USB, dac_clk, status/debug) from
the netlist before placement, avoiding [Place 30-58] IO overflow.
The placer enforces a single VCCO per bank. LVDS_25 forces Bank 14
to VCCO=2.5V, which conflicts with LVCMOS33 (needs 3.3V). Changing
adc_pwdn to LVCMOS25 resolves [Place 30-372] bank incompatibility.
The AD9484 PWDN pin has CMOS-level thresholds (~0.8V), so 2.5V
output drives it correctly.
set_property SEVERITY in the parent Vivado process does not propagate
to the child process spawned by launch_runs. Write a drc_waivers_50t.tcl
hook and attach it via STEPS.OPT_DESIGN.TCL.PRE so BIVC-1, NSTD-1,
and UCIO-1 are demoted to warnings inside the impl_1 run context.
Three issues prevented the 50T (FTG256) build from completing:
1. LVDS standard: LVDS_33 and LVDS do not exist on 7-series HR banks.
Changed to LVDS_25 (the only valid differential input standard).
IBUFDS inputs are VCCO-independent, so LVDS_25 works correctly even
with Bank 14 VCCO=3.3V.
2. BIVC-1 DRC: Bank 14 has LVDS_25 (needs 2.5V) and LVCMOS33 adc_pwdn
(needs 3.3V). Since all LVDS ports are inputs (IBUFDS only), the
voltage conflict does not affect functionality. Demoted to warning.
3. Pin overflow: 113 ports vs 69 available FTG256 pins. The 118
unconstrained port bits (FT601 unwired, status/debug unrouted,
dac_clk unconnected) cause NSTD-1/UCIO-1 DRC errors. Demoted to
warnings since these ports have no physical connections on this board.
Also added: CFGBVS/CONFIG_VOLTAGE settings, build_50t_test.tcl to repo.
LVDS_33 is not a valid I/O standard on 7-series FPGAs. The correct
standard for LVDS inputs in HR banks with VCCO != 2.5V is LVDS, which
works with any VCCO for input-only buffers (IBUFDS). LVDS_25 requires
VCCO=2.5V exactly.
Note: the 50T FTG256 build still fails at placement due to pin overflow
(113 ports vs 69 available pins) — this is a pre-existing package
limitation unrelated to this fix.
The IBUFDS primitives in ad9484_interface_400m.v hardcoded LVDS_25 and
DIFF_TERM TRUE, which overrode XDC constraints. On the XC7A50T (Bank 14
VCCO=3.3V), this caused a BIVC-1 DRC error: LVDS_25 requires VCCO=2.5V,
conflicting with adc_pwdn (LVCMOS33, VCCO=3.3V) in the same bank.
Changes:
- ad9484_interface_400m.v: IBUFDS parameters changed from LVDS_25/DIFF_TERM
TRUE to DEFAULT/DIFF_TERM FALSE, delegating control to XDC per target
- xc7a50t_ftg256.xdc: Re-enable DIFF_TERM TRUE (safe now that RTL does not
hardcode LVDS_25), update DRC Fix History with correct root cause
Build scripts (17-21): STATS.WNS/TNS/WHS/THS/TPWS from get_property can
return empty strings in Vivado 2025.2 after write_bitstream auto-launch.
Wrap in catch with N/A fallback. Guard all expr delta calculations and
signoff comparisons with [string is double -strict] checks.
XDC (xc7a50t_ftg256): Fix PLIO-9 by moving clk_120m_dac from C13 (N-type)
to D13 (P-type MRCC) — clock inputs require P-type MRCC pin. Fix BIVC-1 by
disabling DIFF_TERM on Bank 14 LVDS pairs to resolve VCCO conflict with
single-ended adc_pwdn (LVCMOS33) on T5 — requires external termination.
- Escape [extra] → \[extra\] to prevent TCL interpreting it as a command
(Vivado resolved 'extra' to 'extract_files' causing ERROR [Common 17-163])
- Fix implementation status check: accept 'write_bitstream' status as success
(Vivado auto-proceeds to write_bitstream, making status != '*Complete*')
- Wrap bitstream launch_runs in catch{} to handle already-running case
Fixes applied to: build17, build18, build19, build20, build21
- 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.
- 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
- 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).
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.
Split ST_CFAR_THR into two pipeline stages (THR + MUL) to fix Build 23
timing violation (WNS = -0.309 ns). The combinational path from
leading_sum through GO/SO cross-multiply into alpha*noise DSP was too
long for 10 ns.
New pipeline:
ST_CFAR_THR: register noise_sum_comb (mode select + cross-multiply)
ST_CFAR_MUL: compute alpha * noise_sum_reg in DSP
ST_CFAR_CMP: compare + update window (unchanged)
3 cycles per CUT instead of 2 (~85 us vs 70 us per frame, negligible).
All detection results identical: 23/23 CFAR standalone, 22/22 full
regression, 3/3 real-data co-sim (5137/5137 exact match) PASS.
usb_data_interface.v: doppler_data_pending and cfar_data_pending were
driven by two always blocks (CDC sync block set them, write FSM cleared
them). Vivado DRC MDRV-1 flagged this as multiple drivers. Moved all
set/clear logic into the write FSM always block using doppler_valid_ft
and cfar_valid_ft edge wires.
adc_clk_mmcm.xdc: changed set_false_path -from to -through for MMCM
LOCKED pin (not a valid timing startpoint). Eliminates CRITICAL WARNING
from Builds 19/20/21.
19/19 FPGA regression 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
Opt 1: Eliminated ST_BF_SHIFT state — arithmetic right-shift is pure
bit-selection (zero logic levels), merged into BF_WRITE combinational
add/subtract. Saves LOG2N * N/2 = 5120 cycles per 1024-pt FFT.
Opt 2: Replaced idx_val * tw_stride_reg general multiply with
idx_val << (LOG2N-1-stage) barrel shift. tw_stride_reg is always a
power of 2, so this is mathematically identical and frees a multiplier.
Regression: 18/18 FPGA pass (bit-exact results).
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.
Vivado 2025.2 (Synth 8-524): overlap_copy_count is 8-bit but [9:0]
part-select was 10-bit. Changed to explicit zero-extend concat.
Added default case to FSM to suppress non-full case warning.