mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-09 06:57:15 +00:00
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:
@@ -0,0 +1,181 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
// ============================================================================
|
||||
// cdc_async_fifo — Cummings-style asynchronous FIFO (SNUG 2002, style #2)
|
||||
// ============================================================================
|
||||
// Replaces cdc_adc_to_processing for multi-bit data crossings where the data
|
||||
// can change by arbitrary amounts between src_valid events (e.g. CIC samples
|
||||
// at the 400→100 MHz boundary). The Gray-CDC anti-pattern that
|
||||
// cdc_adc_to_processing exposes — independent data and toggle synchronizer
|
||||
// chains that can skew under metastability and let the destination capture a
|
||||
// half-resolved Gray word — does not apply here, because:
|
||||
//
|
||||
// - Data does NOT cross domains; it sits in a dual-clock distRAM
|
||||
// (write port: src_clk, read port: dst_clk).
|
||||
// - Only the read/write Gray-coded POINTERS cross between domains. Pointer
|
||||
// counters genuinely change ±1 per increment, so Gray code's single-bit-
|
||||
// flip metastability protection holds by construction.
|
||||
//
|
||||
// Reference: Clifford Cummings, "Simulation and Synthesis Techniques for
|
||||
// Asynchronous FIFO Design", SNUG 2002 (style #2, registered empty/full).
|
||||
//
|
||||
// Output semantics — drop-in compatible with cdc_adc_to_processing:
|
||||
// - dst_valid pulses HIGH for one dst_clk cycle per FIFO read.
|
||||
// - In matched-rate steady state (src_rate ≤ dst_rate) dst_valid is HIGH
|
||||
// every dst_clk cycle while data is flowing — same level shape that
|
||||
// cdc_adc_to_processing produced when the toggle changed every cycle.
|
||||
// - When the FIFO is empty, dst_valid stays LOW.
|
||||
//
|
||||
// Overrun semantics:
|
||||
// - overrun pulses HIGH for one src_clk cycle whenever src_valid arrives
|
||||
// while the FIFO is full. The write is dropped (no stomp on data already
|
||||
// in the FIFO). External logic latches/counts as needed (matches the
|
||||
// audit-F-1.2 sticky-overrun pattern in ddc_400m.v).
|
||||
//
|
||||
// XDC timing constraints:
|
||||
// The project XDC files (xc7a50t_ftg256.xdc, xc7a200t_fbg484.xdc) already
|
||||
// contain a blanket `set_false_path` between `clk_100m` and `adc_dco_p`
|
||||
// (the 100/400 MHz domains). This automatically covers both pointer
|
||||
// crossings here (wptr Gray src→dst and rptr Gray dst→src) — no XDC change
|
||||
// is needed. The 2-stage synchronizers carry ASYNC_REG="TRUE" so Vivado
|
||||
// places them in the same slice for MTBF; placement is unaffected by the
|
||||
// blanket false-path. This matches the project convention already used by
|
||||
// cdc_adc_to_processing and other CDC primitives in cdc_modules.v.
|
||||
//
|
||||
// Resource estimate: distributed-RAM FIFO, depth 16 × width 18 → 8 LUTRAMs
|
||||
// per instance. Two instances on the CIC→FIR boundary = 16 LUTRAMs (~0.05%
|
||||
// of XC7A50T LUT budget).
|
||||
//
|
||||
// Reset semantics: src and dst sides reset independently (async-reset on
|
||||
// negedge of each domain's reset_n). The FIFO comes out of reset in the
|
||||
// EMPTY state from both sides; writes are gated on `~full` so a write
|
||||
// arriving before the dst side has come out of reset is safely held in the
|
||||
// FIFO and drained once dst_reset_n deasserts.
|
||||
// ============================================================================
|
||||
module cdc_async_fifo #(
|
||||
parameter WIDTH = 18,
|
||||
parameter DEPTH = 16 // must be a power of 2
|
||||
)(
|
||||
input wire src_clk,
|
||||
input wire dst_clk,
|
||||
input wire src_reset_n,
|
||||
input wire dst_reset_n,
|
||||
input wire [WIDTH-1:0] src_data,
|
||||
input wire src_valid,
|
||||
output reg [WIDTH-1:0] dst_data,
|
||||
output reg dst_valid,
|
||||
output reg overrun
|
||||
);
|
||||
|
||||
localparam ADDR_W = $clog2(DEPTH);
|
||||
|
||||
// ---------- Storage (dual-clock distRAM; Vivado infers SLICEM LUTRAM) ----------
|
||||
// Note: no reset on `mem` — distRAM has no reset semantics, and forcing one
|
||||
// would block LUTRAM inference. Reads are gated on `~empty`, so a cell is
|
||||
// never read before it has been written; X-propagation is impossible in
|
||||
// sim by construction. The `initial` block zeroes cells purely for
|
||||
// simulator cleanliness; synthesis honors it as LUTRAM init values.
|
||||
reg [WIDTH-1:0] mem [0:DEPTH-1];
|
||||
|
||||
integer init_i;
|
||||
initial begin
|
||||
for (init_i = 0; init_i < DEPTH; init_i = init_i + 1)
|
||||
mem[init_i] = {WIDTH{1'b0}};
|
||||
end
|
||||
|
||||
// ---------- Source domain registers ----------
|
||||
reg [ADDR_W:0] wptr_bin; // ADDR_W+1 bits: extra MSB enables full detect
|
||||
reg [ADDR_W:0] wptr_gray;
|
||||
reg full;
|
||||
wire [ADDR_W-1:0] waddr = wptr_bin[ADDR_W-1:0];
|
||||
|
||||
// ---------- Destination domain registers ----------
|
||||
reg [ADDR_W:0] rptr_bin;
|
||||
reg [ADDR_W:0] rptr_gray;
|
||||
reg empty;
|
||||
wire [ADDR_W-1:0] raddr = rptr_bin[ADDR_W-1:0];
|
||||
|
||||
// ---------- CDC: Gray pointer crossings (the only domain-crossing signals) ----------
|
||||
(* ASYNC_REG = "TRUE" *) reg [ADDR_W:0] wptr_gray_dst [0:1];
|
||||
(* ASYNC_REG = "TRUE" *) reg [ADDR_W:0] rptr_gray_src [0:1];
|
||||
|
||||
// ---------- Pointer-next combinational ----------
|
||||
wire do_write = src_valid & ~full;
|
||||
wire do_read = ~empty;
|
||||
wire [ADDR_W:0] wptr_bin_next = wptr_bin + do_write;
|
||||
wire [ADDR_W:0] wptr_gray_next = wptr_bin_next ^ (wptr_bin_next >> 1);
|
||||
wire [ADDR_W:0] rptr_bin_next = rptr_bin + do_read;
|
||||
wire [ADDR_W:0] rptr_gray_next = rptr_bin_next ^ (rptr_bin_next >> 1);
|
||||
|
||||
// ---------- Cummings full/empty conditions (style #2: registered) ----------
|
||||
// full: next-write-Gray equals synchronized-read-Gray with the two MSBs
|
||||
// inverted. This is the canonical "Gray pointer match with MSB twist"
|
||||
// detection that distinguishes "wrote one full lap and caught up" from
|
||||
// "wptr == rptr because both are at 0".
|
||||
wire wfull_val = (wptr_gray_next ==
|
||||
{~rptr_gray_src[1][ADDR_W:ADDR_W-1],
|
||||
rptr_gray_src[1][ADDR_W-2:0]});
|
||||
wire rempty_val = (rptr_gray_next == wptr_gray_dst[1]);
|
||||
|
||||
// ============================================================================
|
||||
// SOURCE DOMAIN
|
||||
// ============================================================================
|
||||
always @(posedge src_clk or negedge src_reset_n) begin
|
||||
if (!src_reset_n) begin
|
||||
wptr_bin <= {(ADDR_W+1){1'b0}};
|
||||
wptr_gray <= {(ADDR_W+1){1'b0}};
|
||||
full <= 1'b0;
|
||||
overrun <= 1'b0;
|
||||
end else begin
|
||||
if (do_write) mem[waddr] <= src_data;
|
||||
wptr_bin <= wptr_bin_next;
|
||||
wptr_gray <= wptr_gray_next;
|
||||
full <= wfull_val;
|
||||
overrun <= src_valid & full; // 1-cycle pulse on dropped write
|
||||
end
|
||||
end
|
||||
|
||||
// Synchronize destination read pointer (Gray) into source domain
|
||||
always @(posedge src_clk or negedge src_reset_n) begin
|
||||
if (!src_reset_n) begin
|
||||
rptr_gray_src[0] <= {(ADDR_W+1){1'b0}};
|
||||
rptr_gray_src[1] <= {(ADDR_W+1){1'b0}};
|
||||
end else begin
|
||||
rptr_gray_src[0] <= rptr_gray;
|
||||
rptr_gray_src[1] <= rptr_gray_src[0];
|
||||
end
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// DESTINATION DOMAIN
|
||||
// ============================================================================
|
||||
always @(posedge dst_clk or negedge dst_reset_n) begin
|
||||
if (!dst_reset_n) begin
|
||||
rptr_bin <= {(ADDR_W+1){1'b0}};
|
||||
rptr_gray <= {(ADDR_W+1){1'b0}};
|
||||
empty <= 1'b1;
|
||||
dst_data <= {WIDTH{1'b0}};
|
||||
dst_valid <= 1'b0;
|
||||
end else begin
|
||||
if (do_read) begin
|
||||
dst_data <= mem[raddr]; // capture the read data
|
||||
rptr_bin <= rptr_bin_next;
|
||||
rptr_gray <= rptr_gray_next;
|
||||
end
|
||||
empty <= rempty_val;
|
||||
dst_valid <= do_read; // 1-cycle pulse per read
|
||||
end
|
||||
end
|
||||
|
||||
// Synchronize source write pointer (Gray) into destination domain
|
||||
always @(posedge dst_clk or negedge dst_reset_n) begin
|
||||
if (!dst_reset_n) begin
|
||||
wptr_gray_dst[0] <= {(ADDR_W+1){1'b0}};
|
||||
wptr_gray_dst[1] <= {(ADDR_W+1){1'b0}};
|
||||
end else begin
|
||||
wptr_gray_dst[0] <= wptr_gray;
|
||||
wptr_gray_dst[1] <= wptr_gray_dst[0];
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -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),
|
||||
|
||||
@@ -59,6 +59,7 @@ PROD_RTL=(
|
||||
nco_400m_enhanced.v
|
||||
cic_decimator_4x_enhanced.v
|
||||
cdc_modules.v
|
||||
cdc_async_fifo.v
|
||||
fir_lowpass.v
|
||||
ddc_input_interface.v
|
||||
chirp_memory_loader_param.v
|
||||
@@ -99,7 +100,7 @@ RECEIVER_RTL=(
|
||||
radar_mode_controller.v
|
||||
tb/ad9484_interface_400m_stub.v
|
||||
ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v
|
||||
cdc_modules.v fir_lowpass.v ddc_input_interface.v
|
||||
cdc_modules.v cdc_async_fifo.v fir_lowpass.v ddc_input_interface.v
|
||||
chirp_memory_loader_param.v
|
||||
matched_filter_multi_segment.v matched_filter_processing_chain.v
|
||||
range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v
|
||||
@@ -585,7 +586,7 @@ echo "--- PHASE 2: Integration Tests ---"
|
||||
run_test "DDC Chain (NCO→CIC→FIR)" \
|
||||
tb/tb_ddc_reg.vvp \
|
||||
tb/tb_ddc_cosim.v ddc_400m.v nco_400m_enhanced.v \
|
||||
cic_decimator_4x_enhanced.v fir_lowpass.v cdc_modules.v
|
||||
cic_decimator_4x_enhanced.v fir_lowpass.v cdc_modules.v cdc_async_fifo.v
|
||||
|
||||
# Real-data co-simulation: committed golden hex vs RTL (exact match required).
|
||||
# These catch architecture mismatches (e.g. 32-pt → dual 16-pt Doppler FFT)
|
||||
@@ -705,6 +706,10 @@ run_test "CDC Modules (3 variants)" \
|
||||
tb/tb_cdc_reg.vvp \
|
||||
tb/tb_cdc_modules.v cdc_modules.v
|
||||
|
||||
run_test "CDC Async FIFO (AUDIT-C11)" \
|
||||
tb/tb_cdc_async_fifo_reg.vvp \
|
||||
tb/tb_cdc_async_fifo.v cdc_async_fifo.v
|
||||
|
||||
run_test "Edge Detector" \
|
||||
tb/tb_edge_reg.vvp \
|
||||
tb/tb_edge_detector.v edge_detector.v
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
// ============================================================================
|
||||
// tb_cdc_async_fifo — exercises the home-grown Cummings async FIFO that
|
||||
// replaces cdc_adc_to_processing on the CIC→FIR (400→100 MHz) crossing.
|
||||
//
|
||||
// Coverage objectives (audit C-11 recommendation):
|
||||
// 1. Reset behaviour on both domains.
|
||||
// 2. Single sample passthrough (data integrity).
|
||||
// 3. Continuous stream at matched src/dst rate (steady-state bandwidth).
|
||||
// 4. Multi-Gray-bit-flip stimulus — alternate 0x00000 ↔ 0x3FFFF: each
|
||||
// transition flips ALL Gray bits at once, which is exactly the input
|
||||
// pattern that exposes the cdc_adc_to_processing skew hazard. This
|
||||
// FIFO must never present an intermediate value at dst_data — only
|
||||
// the alternating extremes.
|
||||
// 5. Sustained burst: src_valid every src_clk cycle (4× the dst drain
|
||||
// rate). FIFO fills, overrun pulses, no data corruption on cells
|
||||
// already in flight.
|
||||
// 6. Drain to empty: after src stops, dst_valid pulses exactly N times
|
||||
// for N writes that fit in the FIFO, then dst_valid stays LOW.
|
||||
//
|
||||
// Clock ratio mirrors production: src=400 MHz, dst=100 MHz (4:1).
|
||||
// ============================================================================
|
||||
module tb_cdc_async_fifo;
|
||||
|
||||
localparam SRC_CLK_PERIOD = 2.5; // 400 MHz
|
||||
localparam DST_CLK_PERIOD = 10.0; // 100 MHz
|
||||
localparam WIDTH = 18;
|
||||
localparam DEPTH = 16;
|
||||
|
||||
integer pass_count;
|
||||
integer fail_count;
|
||||
integer test_num;
|
||||
|
||||
task check;
|
||||
input cond;
|
||||
input [511:0] label;
|
||||
begin
|
||||
test_num = test_num + 1;
|
||||
if (cond) begin
|
||||
$display("[PASS] Test %0d: %0s", test_num, label);
|
||||
pass_count = pass_count + 1;
|
||||
end else begin
|
||||
$display("[FAIL] Test %0d: %0s", test_num, label);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
// ── DUT signals ───────────────────────────────────────────────
|
||||
reg src_clk;
|
||||
reg dst_clk;
|
||||
reg src_reset_n;
|
||||
reg dst_reset_n;
|
||||
reg [WIDTH-1:0] src_data;
|
||||
reg src_valid;
|
||||
wire [WIDTH-1:0] dst_data;
|
||||
wire dst_valid;
|
||||
wire overrun;
|
||||
|
||||
always #(SRC_CLK_PERIOD/2) src_clk = ~src_clk;
|
||||
always #(DST_CLK_PERIOD/2) dst_clk = ~dst_clk;
|
||||
|
||||
cdc_async_fifo #(
|
||||
.WIDTH(WIDTH),
|
||||
.DEPTH(DEPTH)
|
||||
) dut (
|
||||
.src_clk (src_clk),
|
||||
.dst_clk (dst_clk),
|
||||
.src_reset_n (src_reset_n),
|
||||
.dst_reset_n (dst_reset_n),
|
||||
.src_data (src_data),
|
||||
.src_valid (src_valid),
|
||||
.dst_data (dst_data),
|
||||
.dst_valid (dst_valid),
|
||||
.overrun (overrun)
|
||||
);
|
||||
|
||||
// ── Captured-output queue (collected on every dst_valid pulse) ────
|
||||
reg [WIDTH-1:0] capture_q [0:1023];
|
||||
integer capture_n;
|
||||
always @(posedge dst_clk) begin
|
||||
if (!dst_reset_n) begin
|
||||
capture_n <= 0;
|
||||
end else if (dst_valid) begin
|
||||
capture_q[capture_n] <= dst_data;
|
||||
capture_n <= capture_n + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// ── Overrun pulse counter (src domain) ────────────────────────────
|
||||
integer overrun_count;
|
||||
always @(posedge src_clk) begin
|
||||
if (!src_reset_n) overrun_count <= 0;
|
||||
else if (overrun) overrun_count <= overrun_count + 1;
|
||||
end
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────
|
||||
task drive_sample;
|
||||
input [WIDTH-1:0] v;
|
||||
begin
|
||||
@(posedge src_clk);
|
||||
src_data <= v;
|
||||
src_valid <= 1'b1;
|
||||
@(posedge src_clk);
|
||||
src_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Drive src_valid HIGH every src_clk cycle for n cycles, with
|
||||
// a counter pattern as data so we can verify ordering.
|
||||
task drive_burst;
|
||||
input integer n;
|
||||
input [WIDTH-1:0] base;
|
||||
integer k;
|
||||
begin
|
||||
for (k = 0; k < n; k = k + 1) begin
|
||||
@(posedge src_clk);
|
||||
src_data <= base + k;
|
||||
src_valid <= 1'b1;
|
||||
end
|
||||
@(posedge src_clk);
|
||||
src_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task wait_dst_cycles;
|
||||
input integer n;
|
||||
integer k;
|
||||
begin
|
||||
for (k = 0; k < n; k = k + 1) @(posedge dst_clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
// ── Main ──────────────────────────────────────────────────────────
|
||||
integer i, expected_overrun;
|
||||
integer alt_count, intermediate_count;
|
||||
|
||||
initial begin
|
||||
$dumpfile("tb_cdc_async_fifo.vcd");
|
||||
$dumpvars(0, tb_cdc_async_fifo);
|
||||
|
||||
src_clk = 0;
|
||||
dst_clk = 0;
|
||||
src_reset_n = 0;
|
||||
dst_reset_n = 0;
|
||||
src_data = {WIDTH{1'b0}};
|
||||
src_valid = 1'b0;
|
||||
pass_count = 0;
|
||||
fail_count = 0;
|
||||
test_num = 0;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 1: Reset
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 1: Reset ===");
|
||||
#100;
|
||||
check(dst_valid === 1'b0, "G1.1: dst_valid LOW during reset");
|
||||
check(dst_data === {WIDTH{1'b0}}, "G1.2: dst_data 0 during reset");
|
||||
check(overrun === 1'b0, "G1.3: overrun LOW during reset");
|
||||
|
||||
// Release dst first (out-of-order reset deassertion is allowed)
|
||||
@(posedge dst_clk); dst_reset_n = 1'b1;
|
||||
wait_dst_cycles(2);
|
||||
check(dst_valid === 1'b0, "G1.4: dst_valid LOW (src still in reset)");
|
||||
|
||||
@(posedge src_clk); src_reset_n = 1'b1;
|
||||
wait_dst_cycles(4);
|
||||
check(dst_valid === 1'b0, "G1.5: dst_valid LOW after both resets release (FIFO empty)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 2: Single sample passthrough
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 2: Single sample passthrough ===");
|
||||
capture_n = 0;
|
||||
drive_sample(18'h12345);
|
||||
wait_dst_cycles(8);
|
||||
check(capture_n === 1, "G2.1: exactly 1 sample emitted");
|
||||
check(capture_q[0] === 18'h12345, "G2.2: data integrity (0x12345)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 3: Multi-Gray-bit-flip stimulus (audit C-11 coverage)
|
||||
// Alternate 0x00000 ↔ 0x3FFFF — these two values differ by ALL
|
||||
// 18 bits in binary, so Gray code also flips many bits between
|
||||
// them. cdc_adc_to_processing's data/toggle skew failure mode
|
||||
// would manifest as an intermediate value (anything other than
|
||||
// these two). The FIFO must NEVER produce an intermediate.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 3: Multi-Gray-bit-flip (0x00000 <-> 0x3FFFF) ===");
|
||||
capture_n = 0;
|
||||
for (i = 0; i < 32; i = i + 1) begin
|
||||
drive_sample((i & 1) ? 18'h3FFFF : 18'h00000);
|
||||
// small inter-sample gap to let dst drain so FIFO doesn't fill
|
||||
wait_dst_cycles(2);
|
||||
end
|
||||
wait_dst_cycles(16);
|
||||
check(capture_n === 32, "G3.1: 32 samples emitted (no drops)");
|
||||
|
||||
intermediate_count = 0;
|
||||
alt_count = 0;
|
||||
for (i = 0; i < capture_n; i = i + 1) begin
|
||||
if (capture_q[i] === 18'h00000 || capture_q[i] === 18'h3FFFF) begin
|
||||
alt_count = alt_count + 1;
|
||||
end else begin
|
||||
intermediate_count = intermediate_count + 1;
|
||||
end
|
||||
end
|
||||
check(intermediate_count === 0,
|
||||
"G3.2: NO intermediate values (every sample is 0x00000 or 0x3FFFF)");
|
||||
check(alt_count === 32,
|
||||
"G3.3: all 32 samples are one of the two extremes");
|
||||
|
||||
// Verify ordering: even index → 0x00000, odd index → 0x3FFFF
|
||||
// (sampling order matches src_valid order because FIFO is FIFO)
|
||||
for (i = 0; i < capture_n; i = i + 1) begin
|
||||
if ((i & 1) ? (capture_q[i] !== 18'h3FFFF)
|
||||
: (capture_q[i] !== 18'h00000)) begin
|
||||
$display(" [trace] G3 ordering mismatch at index %0d: 0x%h", i, capture_q[i]);
|
||||
end
|
||||
end
|
||||
// Just one summary check on ordering:
|
||||
check((capture_q[0] === 18'h00000) && (capture_q[1] === 18'h3FFFF) &&
|
||||
(capture_q[30] === 18'h00000) && (capture_q[31] === 18'h3FFFF),
|
||||
"G3.4: alternation order preserved across the run");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 4: Continuous stream — counter pattern, no overrun
|
||||
// src writes one sample every 4 src cycles (100 MHz src valid
|
||||
// rate, matched to dst drain rate)
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 4: Matched-rate continuous stream ===");
|
||||
capture_n = 0;
|
||||
overrun_count = 0;
|
||||
// Drive 64 samples with 3-cycle gap → 100 MHz effective src rate
|
||||
for (i = 0; i < 64; i = i + 1) begin
|
||||
@(posedge src_clk); src_data <= i[WIDTH-1:0]; src_valid <= 1'b1;
|
||||
@(posedge src_clk); src_valid <= 1'b0;
|
||||
@(posedge src_clk);
|
||||
@(posedge src_clk);
|
||||
end
|
||||
wait_dst_cycles(32);
|
||||
check(overrun_count === 0, "G4.1: no overrun at matched src/dst rate");
|
||||
check(capture_n === 64, "G4.2: all 64 samples passed through");
|
||||
check(capture_q[0] === 18'd0, "G4.3: first sample = 0");
|
||||
check(capture_q[63] === 18'd63, "G4.4: last sample = 63");
|
||||
// Spot-check ordering
|
||||
check(capture_q[10] === 18'd10 && capture_q[42] === 18'd42,
|
||||
"G4.5: monotonic counter pattern preserved");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 5: Overrun — sustained burst, src_valid every cycle
|
||||
// FIFO has DEPTH=16, drain is 4× slower than fill, so after
|
||||
// ~21 src writes the FIFO fills and overrun starts pulsing.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 5: Burst overrun ===");
|
||||
capture_n = 0;
|
||||
overrun_count = 0;
|
||||
// Wait for dst to fully drain anything pending
|
||||
wait_dst_cycles(8);
|
||||
@(posedge src_clk);
|
||||
|
||||
drive_burst(64, 18'h10000); // src_valid HIGH for 64 src cycles
|
||||
wait_dst_cycles(48); // let FIFO drain
|
||||
|
||||
check(overrun_count > 0, "G5.1: overrun fired during sustained burst");
|
||||
// 64 writes; FIFO drains 1 entry per 4 src cycles = 16 drained during burst.
|
||||
// So writes that succeed = 16 (in FIFO) + 16 (drained) = ~32; drops = 64-32 = ~32.
|
||||
check(capture_n >= 16 && capture_n <= 48,
|
||||
"G5.2: capture count in expected range (FIFO depth + drained)");
|
||||
// Whatever made it through must be a contiguous prefix-suffix of the
|
||||
// counter pattern — first sample MUST be 0x10000 (the burst base);
|
||||
// the FIFO never reorders.
|
||||
check(capture_q[0] === 18'h10000, "G5.3: first captured sample = burst base");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 6: Drain to empty + idle behaviour
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 6: Drain + idle ===");
|
||||
wait_dst_cycles(64);
|
||||
capture_n = 0;
|
||||
overrun_count = 0;
|
||||
wait_dst_cycles(32);
|
||||
check(capture_n === 0, "G6.1: no spurious dst_valid while idle");
|
||||
check(dst_valid === 1'b0, "G6.2: dst_valid LOW after drain");
|
||||
check(overrun_count === 0, "G6.3: no overrun while idle");
|
||||
|
||||
// Single-shot post-idle write should still work
|
||||
drive_sample(18'h2AAAA);
|
||||
wait_dst_cycles(8);
|
||||
check(capture_n === 1, "G6.4: post-idle single sample emitted");
|
||||
check(capture_q[0] === 18'h2AAAA, "G6.5: post-idle data integrity");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Group 7: Reset mid-stream — both pointers must zero, dst_valid LOW
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n=== Group 7: Reset mid-stream ===");
|
||||
wait_dst_cycles(8);
|
||||
// Pre-load a few samples
|
||||
drive_sample(18'h11111);
|
||||
drive_sample(18'h22222);
|
||||
// Assert reset on both before they drain
|
||||
@(posedge dst_clk); dst_reset_n = 1'b0;
|
||||
@(posedge src_clk); src_reset_n = 1'b0;
|
||||
capture_n = 0;
|
||||
wait_dst_cycles(8);
|
||||
check(dst_valid === 1'b0, "G7.1: dst_valid LOW under mid-stream reset");
|
||||
check(capture_n === 0, "G7.2: no captures during reset");
|
||||
|
||||
// Release
|
||||
@(posedge dst_clk); dst_reset_n = 1'b1;
|
||||
@(posedge src_clk); src_reset_n = 1'b1;
|
||||
wait_dst_cycles(8);
|
||||
check(dst_valid === 1'b0, "G7.3: dst_valid LOW after reset release (FIFO empty)");
|
||||
|
||||
// Post-reset write
|
||||
drive_sample(18'h3CCCC);
|
||||
wait_dst_cycles(8);
|
||||
check(capture_q[0] === 18'h3CCCC, "G7.4: post-reset write succeeds");
|
||||
|
||||
// ── Final summary ─────────────────────────────────────────
|
||||
$display("\n============================================");
|
||||
$display(" RESULTS: %0d passed / %0d failed / %0d total",
|
||||
pass_count, fail_count, test_num);
|
||||
$display("============================================");
|
||||
if (fail_count == 0) $display(" STATUS: ALL TESTS PASSED");
|
||||
else $display(" STATUS: FAILURES DETECTED");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
// Watchdog
|
||||
initial begin
|
||||
#200000;
|
||||
$display("[FAIL] WATCHDOG: simulation hung");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user