AUDIT-C11: replace Gray-CDC at CIC→FIR with home-grown async FIFO

cdc_adc_to_processing carries multi-bit data across 400→100 MHz via
TWO independent synchronizer chains (data Gray-encoded + a separate
2-bit toggle). Under metastability, the chains can resolve on
different cycles, letting the destination latch a half-resolved Gray
word that decodes to an arbitrary value. Audit C-11. Practical MTBF
is years per event but the design is non-conformant for arbitrary
multi-bit data — Gray code's single-bit-flip protection only holds
for ±1 transitions, not for CIC samples that can change by hundreds
of LSBs.

Replace with cdc_async_fifo, a Cummings SNUG-2002 style #2 async
FIFO. Data does NOT cross domains; it sits in dual-clock distRAM
(write port src_clk, read port dst_clk). Only the read/write
Gray-coded POINTERS cross — and pointers genuinely change ±1 per
increment, so Gray code's protection is correct by construction.
Home-grown rather than XPM_FIFO_ASYNC: vendor-neutral (iverilog can
simulate it directly, no SIM stub), keeps the project's existing
home-grown CDC convention (3 sibling primitives in cdc_modules.v),
and avoids XPM library version skew.

Port shape is preserved (same WIDTH=18, same dst_data/dst_valid/
overrun semantics — 1-cycle pulse per read in steady state) so the
swap is local to two instantiations in ddc_400m.v. Sticky-overrun
aggregation downstream is unchanged.

XDC: project already has blanket set_false_path on
clk_100m ↔ adc_dco_p, which covers both new pointer crossings.
Synchronizer FFs carry ASYNC_REG="TRUE" for placement-aware MTBF.
No XDC change needed.

New TB tb_cdc_async_fifo.v exercises 7 groups (28 checks): reset,
single-sample passthrough, multi-Gray-bit-flip (0x00000 ↔ 0x3FFFF —
audit's recommended coverage point, asserts NO intermediate values
appear at dst_data), matched-rate continuous stream, sustained-burst
overrun, drain-to-empty, and mid-stream reset.

Resource: 8 LUTRAMs per instance × 2 instances = 16 LUTRAMs (~0.05%
of XC7A50T budget).

Verified: full FPGA regression 42/42 PASS (was 41/41; +1 new test,
0 regressions in DDC Chain / Doppler Co-Sim / Full-Chain Real-Data
/ Receiver Integration / System Top / System E2E / MF Co-Sim — all
of which exercise the swap path through the production signal
chain). 0 lint errors.
This commit is contained in:
Jason
2026-04-30 10:47:31 +05:45
parent bf63d64533
commit 58d2e1ba10
4 changed files with 537 additions and 6 deletions
+11 -4
View File
@@ -643,9 +643,16 @@ wire [17:0] fir_d_in_i, fir_d_in_q;
wire cdc_fir_i_overrun;
wire cdc_fir_q_overrun;
cdc_adc_to_processing #(
// AUDIT-C11: replaced cdc_adc_to_processing (Gray-CDC anti-pattern: independent
// data and toggle synchronizer chains can skew under metastability and let the
// destination capture a half-resolved Gray word) with cdc_async_fifo (Cummings
// SNUG-2002 design data sits in dual-clock distRAM; only ±1-incrementing
// Gray-coded read/write pointers cross domains). Port shape is preserved so
// the swap is local to these two instantiations. Sticky-overrun aggregation
// at line ~680 is unchanged.
cdc_async_fifo #(
.WIDTH(18),
.STAGES(3)
.DEPTH(16)
)CDC_FIR_i(
.src_clk(clk_400m),
.dst_clk(clk_100m),
@@ -658,9 +665,9 @@ cdc_adc_to_processing #(
.overrun(cdc_fir_i_overrun)
);
cdc_adc_to_processing #(
cdc_async_fifo #(
.WIDTH(18),
.STAGES(3)
.DEPTH(16)
)CDC_FIR_q(
.src_clk(clk_400m),
.dst_clk(clk_100m),