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).
897 lines
36 KiB
Verilog
897 lines
36 KiB
Verilog
`timescale 1ns / 1ps
|
||
|
||
module tb_cdc_modules;
|
||
|
||
// ── Clock periods (reflecting real system) ─────────────────
|
||
localparam SRC_CLK_PERIOD = 2.5; // 400 MHz (ADC domain)
|
||
localparam DST_CLK_PERIOD = 10.0; // 100 MHz (processing domain)
|
||
// For handshake tests, use different ratio
|
||
localparam HS_SRC_PERIOD = 10.0; // 100 MHz
|
||
localparam HS_DST_PERIOD = 7.0; // ~143 MHz (non-integer ratio)
|
||
|
||
// ── Test bookkeeping ───────────────────────────────────────
|
||
integer pass_count;
|
||
integer fail_count;
|
||
integer test_num;
|
||
integer i;
|
||
|
||
// ── Check task ─────────────────────────────────────────────
|
||
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
|
||
|
||
// ════════════════════════════════════════════════════════════
|
||
// MODULE 1: cdc_adc_to_processing (Gray-code multi-bit CDC)
|
||
// ════════════════════════════════════════════════════════════
|
||
reg m1_src_clk;
|
||
reg m1_dst_clk;
|
||
reg m1_src_reset_n;
|
||
reg m1_dst_reset_n;
|
||
reg [7:0] m1_src_data;
|
||
reg m1_src_valid;
|
||
wire [7:0] m1_dst_data;
|
||
wire m1_dst_valid;
|
||
|
||
always #(SRC_CLK_PERIOD/2) m1_src_clk = ~m1_src_clk;
|
||
always #(DST_CLK_PERIOD/2) m1_dst_clk = ~m1_dst_clk;
|
||
|
||
cdc_adc_to_processing #(
|
||
.WIDTH(8),
|
||
.STAGES(3)
|
||
) uut_m1 (
|
||
.src_clk (m1_src_clk),
|
||
.dst_clk (m1_dst_clk),
|
||
.src_reset_n (m1_src_reset_n),
|
||
.dst_reset_n (m1_dst_reset_n),
|
||
.src_data (m1_src_data),
|
||
.src_valid (m1_src_valid),
|
||
.dst_data (m1_dst_data),
|
||
.dst_valid (m1_dst_valid)
|
||
);
|
||
|
||
// ════════════════════════════════════════════════════════════
|
||
// MODULE 2: cdc_single_bit
|
||
// ════════════════════════════════════════════════════════════
|
||
reg m2_src_clk;
|
||
reg m2_dst_clk;
|
||
reg m2_reset_n;
|
||
reg m2_src_signal;
|
||
wire m2_dst_signal;
|
||
|
||
always #(SRC_CLK_PERIOD/2) m2_src_clk = ~m2_src_clk;
|
||
always #(DST_CLK_PERIOD/2) m2_dst_clk = ~m2_dst_clk;
|
||
|
||
cdc_single_bit #(
|
||
.STAGES(3)
|
||
) uut_m2 (
|
||
.src_clk (m2_src_clk),
|
||
.dst_clk (m2_dst_clk),
|
||
.reset_n (m2_reset_n),
|
||
.src_signal(m2_src_signal),
|
||
.dst_signal(m2_dst_signal)
|
||
);
|
||
|
||
// ════════════════════════════════════════════════════════════
|
||
// MODULE 3: cdc_handshake
|
||
// ════════════════════════════════════════════════════════════
|
||
reg m3_src_clk;
|
||
reg m3_dst_clk;
|
||
reg m3_reset_n;
|
||
reg [31:0] m3_src_data;
|
||
reg m3_src_valid;
|
||
wire m3_src_ready;
|
||
wire [31:0] m3_dst_data;
|
||
wire m3_dst_valid;
|
||
reg m3_dst_ready;
|
||
|
||
always #(HS_SRC_PERIOD/2) m3_src_clk = ~m3_src_clk;
|
||
always #(HS_DST_PERIOD/2) m3_dst_clk = ~m3_dst_clk;
|
||
|
||
cdc_handshake #(
|
||
.WIDTH(32)
|
||
) uut_m3 (
|
||
.src_clk (m3_src_clk),
|
||
.dst_clk (m3_dst_clk),
|
||
.reset_n (m3_reset_n),
|
||
.src_data (m3_src_data),
|
||
.src_valid(m3_src_valid),
|
||
.src_ready(m3_src_ready),
|
||
.dst_data (m3_dst_data),
|
||
.dst_valid(m3_dst_valid),
|
||
.dst_ready(m3_dst_ready)
|
||
);
|
||
|
||
// ── Main test sequence ─────────────────────────────────────
|
||
initial begin
|
||
$dumpfile("tb_cdc_modules.vcd");
|
||
$dumpvars(0, tb_cdc_modules);
|
||
|
||
// Init all clocks and signals
|
||
m1_src_clk = 0; m1_dst_clk = 0;
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
m1_src_data = 0; m1_src_valid = 0;
|
||
m2_src_clk = 0; m2_dst_clk = 0; m2_reset_n = 0;
|
||
m2_src_signal = 0;
|
||
m3_src_clk = 0; m3_dst_clk = 0; m3_reset_n = 0;
|
||
m3_src_data = 0; m3_src_valid = 0; m3_dst_ready = 0;
|
||
pass_count = 0; fail_count = 0; test_num = 0;
|
||
|
||
// ════════════════════════════════════════════════════════
|
||
// SECTION A: cdc_adc_to_processing tests
|
||
// ════════════════════════════════════════════════════════
|
||
$display("\n=== Section A: cdc_adc_to_processing (Gray-code CDC) ===");
|
||
|
||
// ── A1: Reset behaviour ────────────────────────────────
|
||
$display("\n--- A1: Reset Behaviour (split-domain reset) ---");
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
#100; // let both clocks run
|
||
check(m1_dst_valid === 1'b0, "M1: dst_valid = 0 during reset");
|
||
check(m1_dst_data === 8'd0, "M1: dst_data = 0 during reset");
|
||
|
||
// Release reset
|
||
@(posedge m1_dst_clk);
|
||
m1_src_reset_n = 1; m1_dst_reset_n = 1;
|
||
@(posedge m1_src_clk);
|
||
|
||
// ── A2: Single value transfer ──────────────────────────
|
||
$display("\n--- A2: Single Value Transfer ---");
|
||
m1_src_data = 8'hA5;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
// Wait for CDC propagation (3 stages × dst_clk + margin)
|
||
begin : a2_wait
|
||
integer wait_cycles;
|
||
reg saw_valid;
|
||
saw_valid = 0;
|
||
for (wait_cycles = 0; wait_cycles < 20; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) begin
|
||
saw_valid = 1;
|
||
disable a2_wait;
|
||
end
|
||
end
|
||
end
|
||
check(m1_dst_valid === 1'b1, "M1: dst_valid asserts after CDC");
|
||
check(m1_dst_data === 8'hA5, "M1: data 0xA5 transferred correctly");
|
||
|
||
// ── A3: Multiple sequential values ─────────────────────
|
||
$display("\n--- A3: Multiple Sequential Values ---");
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
#100;
|
||
m1_src_reset_n = 1; m1_dst_reset_n = 1;
|
||
@(posedge m1_src_clk);
|
||
|
||
begin : a3_block
|
||
reg [7:0] received_values [0:31];
|
||
integer rx_count;
|
||
integer tx_count;
|
||
integer total_wait;
|
||
reg all_received;
|
||
|
||
rx_count = 0;
|
||
|
||
// Send 8 values, one per src_clk cycle
|
||
// At 400:100 ratio, src sends 4x faster than dst can sample
|
||
// Gray-code CDC may miss intermediate values — that's expected.
|
||
// What matters is that received values are VALID (not corrupted).
|
||
for (tx_count = 0; tx_count < 8; tx_count = tx_count + 1) begin
|
||
m1_src_data = tx_count * 37 + 10; // 10, 47, 84, 121, 158, 195, 232, 13
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
end
|
||
m1_src_valid = 0;
|
||
|
||
// Collect outputs
|
||
for (total_wait = 0; total_wait < 40; total_wait = total_wait + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) begin
|
||
received_values[rx_count] = m1_dst_data;
|
||
rx_count = rx_count + 1;
|
||
end
|
||
end
|
||
|
||
$display(" Sent 8 values at 400MHz, received %0d valid values at 100MHz",
|
||
rx_count);
|
||
check(rx_count > 0, "M1: At least one value received from burst");
|
||
|
||
// Verify last received value matches last sent value
|
||
// (The CDC should eventually stabilize to the last written value)
|
||
if (rx_count > 0) begin
|
||
// Check that every received value is one of the sent values
|
||
begin : verify_received
|
||
reg [7:0] sent_vals [0:7];
|
||
reg found;
|
||
integer j, k;
|
||
reg all_valid;
|
||
|
||
for (j = 0; j < 8; j = j + 1) begin
|
||
sent_vals[j] = j * 37 + 10;
|
||
end
|
||
|
||
all_valid = 1;
|
||
for (j = 0; j < rx_count; j = j + 1) begin
|
||
found = 0;
|
||
for (k = 0; k < 8; k = k + 1) begin
|
||
if (received_values[j] === sent_vals[k]) found = 1;
|
||
end
|
||
if (!found) begin
|
||
all_valid = 0;
|
||
$display(" [WARN] Received value 0x%02x not in sent set",
|
||
received_values[j]);
|
||
end
|
||
end
|
||
check(all_valid, "M1: All received values are valid (no corruption)");
|
||
end
|
||
end else begin
|
||
check(1'b0, "M1: All received values are valid (no corruption)");
|
||
end
|
||
end
|
||
|
||
// ── A4: Slow sender (one value every 4 dst_clk cycles) ─
|
||
$display("\n--- A4: Slow Sender ---");
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
#100;
|
||
m1_src_reset_n = 1; m1_dst_reset_n = 1;
|
||
@(posedge m1_src_clk);
|
||
|
||
begin : a4_block
|
||
reg [7:0] expected_vals [0:7];
|
||
reg [7:0] got_vals [0:7];
|
||
integer tx_idx, rx_idx, wait_cnt;
|
||
reg all_match;
|
||
|
||
rx_idx = 0;
|
||
|
||
for (tx_idx = 0; tx_idx < 4; tx_idx = tx_idx + 1) begin
|
||
expected_vals[tx_idx] = (tx_idx + 1) * 50; // 50, 100, 150, 200
|
||
m1_src_data = expected_vals[tx_idx];
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
// Wait long enough for CDC to propagate
|
||
for (wait_cnt = 0; wait_cnt < 15; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid && rx_idx < 8) begin
|
||
got_vals[rx_idx] = m1_dst_data;
|
||
rx_idx = rx_idx + 1;
|
||
end
|
||
end
|
||
end
|
||
|
||
$display(" Slow send: sent 4, received %0d", rx_idx);
|
||
check(rx_idx == 4, "M1: All 4 slow-sent values received");
|
||
|
||
all_match = 1;
|
||
for (i = 0; i < rx_idx && i < 4; i = i + 1) begin
|
||
if (got_vals[i] !== expected_vals[i]) begin
|
||
all_match = 0;
|
||
$display(" [WARN] Slow rx[%0d]: got 0x%02x, exp 0x%02x",
|
||
i, got_vals[i], expected_vals[i]);
|
||
end
|
||
end
|
||
check(all_match, "M1: Slow-sent values match exactly");
|
||
end
|
||
|
||
// ── A5: Split-Domain Reset — Src resets while dst stays active ──
|
||
$display("\n--- A5: Split-Domain Reset (src resets, dst active) ---");
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
m1_src_data = 0; m1_src_valid = 0;
|
||
#100;
|
||
|
||
// Release dst_reset_n first, src stays in reset
|
||
m1_dst_reset_n = 1;
|
||
begin : a5_dst_idle
|
||
integer wait_cycles;
|
||
reg saw_valid;
|
||
saw_valid = 0;
|
||
for (wait_cycles = 0; wait_cycles < 10; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) saw_valid = 1;
|
||
end
|
||
check(!saw_valid, "M1: dst_valid stays 0 while src is in reset");
|
||
end
|
||
|
||
// Now release src_reset_n
|
||
m1_src_reset_n = 1;
|
||
@(posedge m1_src_clk);
|
||
|
||
// Send data and verify transfer works after staggered reset
|
||
m1_src_data = 8'h3C;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
begin : a5_wait
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 20; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) disable a5_wait;
|
||
end
|
||
end
|
||
check(m1_dst_valid === 1'b1, "M1: dst_valid asserts after staggered src reset release");
|
||
check(m1_dst_data === 8'h3C, "M1: data 0x3C correct after staggered src reset");
|
||
|
||
// ── A6: Split-Domain Reset — Dst resets while src stays active ──
|
||
// KEY test: catches the original P0 bug where a single reset from
|
||
// the src domain was used to reset dst-domain registers.
|
||
$display("\n--- A6: Split-Domain Reset (dst resets, src active) ---");
|
||
m1_src_reset_n = 1; m1_dst_reset_n = 1;
|
||
m1_src_data = 0; m1_src_valid = 0;
|
||
@(posedge m1_src_clk);
|
||
|
||
// Send data and verify it arrives (baseline)
|
||
m1_src_data = 8'hF0;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
begin : a6_baseline
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 20; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) disable a6_baseline;
|
||
end
|
||
end
|
||
check(m1_dst_data === 8'hF0, "M1: Baseline data 0xF0 received before dst-only reset");
|
||
|
||
// Assert ONLY dst_reset_n (src keeps running)
|
||
m1_dst_reset_n = 0;
|
||
begin : a6_check_reset
|
||
integer wait_cycles;
|
||
reg dst_cleared;
|
||
dst_cleared = 0;
|
||
for (wait_cycles = 0; wait_cycles < 10; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_data === 8'd0 && m1_dst_valid === 1'b0)
|
||
dst_cleared = 1;
|
||
end
|
||
check(dst_cleared, "M1: dst_data=0 and dst_valid=0 after dst-only reset");
|
||
end
|
||
|
||
// Deassert dst_reset_n
|
||
m1_dst_reset_n = 1;
|
||
repeat (3) @(posedge m1_dst_clk);
|
||
|
||
// Send new data from src, verify it arrives correctly
|
||
m1_src_data = 8'h55;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
begin : a6_recovery
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 20; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) disable a6_recovery;
|
||
end
|
||
end
|
||
check(m1_dst_valid === 1'b1, "M1: dst_valid asserts after dst-only reset recovery");
|
||
check(m1_dst_data === 8'h55, "M1: data 0x55 correct after dst-only reset recovery");
|
||
|
||
// ── A7: Staggered Reset Deassertion ────────────────────
|
||
$display("\n--- A7: Staggered Reset Deassertion ---");
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
m1_src_data = 0; m1_src_valid = 0;
|
||
#100;
|
||
|
||
// Release src_reset_n first, start sending data immediately
|
||
m1_src_reset_n = 1;
|
||
@(posedge m1_src_clk);
|
||
m1_src_data = 8'hBB;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
// Wait 50ns with dst_reset_n still asserted
|
||
#50;
|
||
|
||
// Release dst_reset_n
|
||
m1_dst_reset_n = 1;
|
||
|
||
// Let sync chain clear through a few dst_clk cycles first
|
||
repeat (5) @(posedge m1_dst_clk);
|
||
|
||
// Src sends another value so dst can capture it fresh
|
||
@(posedge m1_src_clk);
|
||
m1_src_data = 8'hCC;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
begin : a7_wait
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 40; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) disable a7_wait;
|
||
end
|
||
end
|
||
check(m1_dst_valid === 1'b1, "M1: dst_valid asserts after staggered deassertion");
|
||
// Accept either 0xBB (if pipeline retained) or 0xCC (if fresh capture)
|
||
check(m1_dst_data === 8'hBB || m1_dst_data === 8'hCC,
|
||
"M1: Data not corrupted after staggered deassertion");
|
||
|
||
// ── A8: Port Connectivity Check ────────────────────────
|
||
$display("\n--- A8: Port Connectivity Check ---");
|
||
m1_src_reset_n = 0; m1_dst_reset_n = 0;
|
||
m1_src_data = 0; m1_src_valid = 0;
|
||
#100;
|
||
m1_src_reset_n = 1; m1_dst_reset_n = 1;
|
||
repeat (5) @(posedge m1_dst_clk); #1;
|
||
|
||
// After reset deassertion, outputs should not be X or Z
|
||
check(m1_dst_data !== 8'bxxxxxxxx, "M1: dst_data is not X after reset");
|
||
check(m1_dst_data !== 8'bzzzzzzzz, "M1: dst_data is not Z after reset");
|
||
check(m1_dst_valid !== 1'bx, "M1: dst_valid is not X after reset");
|
||
check(m1_dst_valid !== 1'bz, "M1: dst_valid is not Z after reset");
|
||
|
||
// After a transfer, check again
|
||
m1_src_data = 8'h99;
|
||
m1_src_valid = 1;
|
||
@(posedge m1_src_clk); #1;
|
||
m1_src_valid = 0;
|
||
|
||
begin : a8_wait
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 20; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m1_dst_clk); #1;
|
||
if (m1_dst_valid) disable a8_wait;
|
||
end
|
||
end
|
||
check(m1_dst_data !== 8'bxxxxxxxx, "M1: dst_data is not X after transfer");
|
||
check(m1_dst_data !== 8'bzzzzzzzz, "M1: dst_data is not Z after transfer");
|
||
check(m1_dst_valid !== 1'bx, "M1: dst_valid is not X after transfer");
|
||
check(m1_dst_valid !== 1'bz, "M1: dst_valid is not Z after transfer");
|
||
|
||
// ════════════════════════════════════════════════════════
|
||
// SECTION B: cdc_single_bit tests
|
||
// ════════════════════════════════════════════════════════
|
||
$display("\n=== Section B: cdc_single_bit ===");
|
||
|
||
// ── B1: Reset behaviour ────────────────────────────────
|
||
$display("\n--- B1: Reset Behaviour ---");
|
||
m2_reset_n = 0;
|
||
m2_src_signal = 0;
|
||
#100;
|
||
check(m2_dst_signal === 1'b0, "M2: dst_signal = 0 during reset");
|
||
|
||
// ── B2: Signal propagation (low to high) ───────────────
|
||
$display("\n--- B2: Low-to-High Propagation ---");
|
||
m2_reset_n = 1;
|
||
@(posedge m2_dst_clk);
|
||
|
||
m2_src_signal = 1;
|
||
begin : b2_wait
|
||
integer wait_cycles;
|
||
reg saw_high;
|
||
saw_high = 0;
|
||
for (wait_cycles = 0; wait_cycles < 10; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m2_dst_clk); #1;
|
||
if (m2_dst_signal === 1'b1) begin
|
||
saw_high = 1;
|
||
$display(" Signal propagated after %0d dst_clk cycles", wait_cycles + 1);
|
||
disable b2_wait;
|
||
end
|
||
end
|
||
end
|
||
check(m2_dst_signal === 1'b1, "M2: 0->1 propagates through sync chain");
|
||
|
||
// ── B3: Signal propagation (high to low) ───────────────
|
||
$display("\n--- B3: High-to-Low Propagation ---");
|
||
m2_src_signal = 0;
|
||
begin : b3_wait
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 10; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m2_dst_clk); #1;
|
||
if (m2_dst_signal === 1'b0) begin
|
||
$display(" Signal de-propagated after %0d dst_clk cycles", wait_cycles + 1);
|
||
disable b3_wait;
|
||
end
|
||
end
|
||
end
|
||
check(m2_dst_signal === 1'b0, "M2: 1->0 propagates through sync chain");
|
||
|
||
// ── B4: Minimum pulse width ────────────────────────────
|
||
$display("\n--- B4: Minimum Pulse Width ---");
|
||
m2_reset_n = 0;
|
||
#100;
|
||
m2_reset_n = 1;
|
||
@(posedge m2_dst_clk);
|
||
|
||
// A single src_clk pulse may or may not be captured
|
||
// At 400:100 ratio, 1 src_clk pulse = 2.5ns, dst_clk period = 10ns
|
||
// A single src_clk pulse might be missed — that's expected behavior
|
||
m2_src_signal = 1;
|
||
@(posedge m2_src_clk); #1;
|
||
m2_src_signal = 0;
|
||
|
||
begin : b4_wait
|
||
integer wait_cycles;
|
||
reg saw_pulse;
|
||
saw_pulse = 0;
|
||
for (wait_cycles = 0; wait_cycles < 10; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m2_dst_clk); #1;
|
||
if (m2_dst_signal) saw_pulse = 1;
|
||
end
|
||
// Single src_clk pulse at 400MHz might be too short for 100MHz dst
|
||
// This is a known limitation of single-bit synchronizers
|
||
$display(" Single src_clk pulse captured: %b (may miss — expected for narrow pulse)",
|
||
saw_pulse);
|
||
check(1'b1, "M2: Narrow pulse test completed (miss is acceptable)");
|
||
end
|
||
|
||
// ── B5: Long pulse always captured ─────────────────────
|
||
$display("\n--- B5: Long Pulse Capture ---");
|
||
m2_reset_n = 0;
|
||
#100;
|
||
m2_reset_n = 1;
|
||
@(posedge m2_dst_clk);
|
||
|
||
// Pulse held for 8 src_clk cycles = 20ns (> 2× dst_clk period)
|
||
m2_src_signal = 1;
|
||
repeat (8) @(posedge m2_src_clk);
|
||
m2_src_signal = 0;
|
||
|
||
begin : b5_wait
|
||
integer wait_cycles;
|
||
reg saw_pulse;
|
||
saw_pulse = 0;
|
||
for (wait_cycles = 0; wait_cycles < 10; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m2_dst_clk); #1;
|
||
if (m2_dst_signal) saw_pulse = 1;
|
||
end
|
||
check(saw_pulse, "M2: Long pulse (8 src_clk) always captured");
|
||
end
|
||
|
||
// ── B6: Reset clears sync chain ────────────────────────
|
||
$display("\n--- B6: Reset Clears Sync Chain ---");
|
||
m2_src_signal = 1;
|
||
repeat (10) @(posedge m2_dst_clk);
|
||
// dst_signal should be 1 now
|
||
m2_reset_n = 0;
|
||
repeat (2) @(posedge m2_dst_clk); #1;
|
||
check(m2_dst_signal === 1'b0, "M2: Reset clears sync chain to 0");
|
||
m2_reset_n = 1;
|
||
m2_src_signal = 0;
|
||
|
||
// ── B7: Port Connectivity ──────────────────────────────
|
||
$display("\n--- B7: Port Connectivity ---");
|
||
m2_reset_n = 0;
|
||
m2_src_signal = 0;
|
||
#100;
|
||
m2_reset_n = 1;
|
||
repeat (5) @(posedge m2_dst_clk); #1;
|
||
|
||
check(m2_dst_signal !== 1'bx, "M2: dst_signal is not X after reset");
|
||
check(m2_dst_signal !== 1'bz, "M2: dst_signal is not Z after reset");
|
||
|
||
// Drive signal high and verify after propagation
|
||
m2_src_signal = 1;
|
||
repeat (8) @(posedge m2_dst_clk); #1;
|
||
check(m2_dst_signal !== 1'bx, "M2: dst_signal is not X after propagation");
|
||
check(m2_dst_signal !== 1'bz, "M2: dst_signal is not Z after propagation");
|
||
m2_src_signal = 0;
|
||
repeat (8) @(posedge m2_dst_clk);
|
||
|
||
// ════════════════════════════════════════════════════════
|
||
// SECTION C: cdc_handshake tests
|
||
// ════════════════════════════════════════════════════════
|
||
$display("\n=== Section C: cdc_handshake ===");
|
||
|
||
// ── C1: Reset behaviour ────────────────────────────────
|
||
$display("\n--- C1: Reset Behaviour ---");
|
||
m3_reset_n = 0;
|
||
m3_src_valid = 0;
|
||
m3_dst_ready = 0;
|
||
#200;
|
||
check(m3_src_ready === 1'b1, "M3: src_ready = 1 during reset (not busy)");
|
||
check(m3_dst_valid === 1'b0, "M3: dst_valid = 0 during reset");
|
||
|
||
// Release reset
|
||
m3_reset_n = 1;
|
||
@(posedge m3_src_clk);
|
||
|
||
// ── C2: Single data transfer ───────────────────────────
|
||
$display("\n--- C2: Single Data Transfer ---");
|
||
m3_dst_ready = 1; // destination always ready
|
||
|
||
m3_src_data = 32'hDEADBEEF;
|
||
m3_src_valid = 1;
|
||
@(posedge m3_src_clk); #1;
|
||
m3_src_valid = 0;
|
||
|
||
// Wait for data to appear at destination
|
||
begin : c2_wait
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 30; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m3_dst_clk); #1;
|
||
if (m3_dst_valid) begin
|
||
disable c2_wait;
|
||
end
|
||
end
|
||
end
|
||
check(m3_dst_valid === 1'b1, "M3: dst_valid asserts for single transfer");
|
||
check(m3_dst_data === 32'hDEADBEEF, "M3: data 0xDEADBEEF transferred correctly");
|
||
|
||
// Wait for handshake to complete (src_ready goes back to 1)
|
||
begin : c2_wait_ready
|
||
integer wait_cycles;
|
||
for (wait_cycles = 0; wait_cycles < 30; wait_cycles = wait_cycles + 1) begin
|
||
@(posedge m3_src_clk); #1;
|
||
if (m3_src_ready) disable c2_wait_ready;
|
||
end
|
||
end
|
||
check(m3_src_ready === 1'b1, "M3: src_ready reasserts after transfer");
|
||
|
||
// ── C3: Consecutive transfers ──────────────────────────
|
||
$display("\n--- C3: Consecutive Transfers ---");
|
||
m3_reset_n = 0;
|
||
#200;
|
||
m3_reset_n = 1;
|
||
@(posedge m3_src_clk);
|
||
m3_dst_ready = 1;
|
||
|
||
begin : c3_block
|
||
reg [31:0] sent_data [0:7];
|
||
reg [31:0] recv_data [0:7];
|
||
integer tx_idx, rx_idx;
|
||
integer wait_cnt;
|
||
reg all_match;
|
||
reg src_wait_done, dst_wait_done;
|
||
|
||
tx_idx = 0;
|
||
rx_idx = 0;
|
||
|
||
// Send 4 values with proper handshaking
|
||
for (tx_idx = 0; tx_idx < 4; tx_idx = tx_idx + 1) begin
|
||
sent_data[tx_idx] = (tx_idx + 1) * 32'h11111111;
|
||
|
||
// Wait for src_ready
|
||
src_wait_done = 0;
|
||
for (wait_cnt = 0; wait_cnt < 50 && !src_wait_done; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_src_clk); #1;
|
||
if (m3_src_ready) begin
|
||
src_wait_done = 1;
|
||
end
|
||
end
|
||
|
||
m3_src_data = sent_data[tx_idx];
|
||
m3_src_valid = 1;
|
||
@(posedge m3_src_clk); #1;
|
||
m3_src_valid = 0;
|
||
|
||
// Wait for dst_valid
|
||
dst_wait_done = 0;
|
||
for (wait_cnt = 0; wait_cnt < 50 && !dst_wait_done; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_dst_clk); #1;
|
||
if (m3_dst_valid) begin
|
||
recv_data[rx_idx] = m3_dst_data;
|
||
rx_idx = rx_idx + 1;
|
||
dst_wait_done = 1;
|
||
end
|
||
end
|
||
end
|
||
|
||
$display(" Consecutive: sent %0d, received %0d", tx_idx, rx_idx);
|
||
check(rx_idx == 4, "M3: All 4 consecutive transfers received");
|
||
|
||
all_match = 1;
|
||
for (i = 0; i < rx_idx && i < 4; i = i + 1) begin
|
||
if (recv_data[i] !== sent_data[i]) begin
|
||
all_match = 0;
|
||
$display(" [WARN] Consec rx[%0d]: got 0x%08x, exp 0x%08x",
|
||
i, recv_data[i], sent_data[i]);
|
||
end
|
||
end
|
||
check(all_match, "M3: All consecutive values match exactly");
|
||
end
|
||
|
||
// ── C4: Backpressure (dst_ready = 0) ───────────────────
|
||
$display("\n--- C4: Backpressure ---");
|
||
m3_reset_n = 0;
|
||
#200;
|
||
m3_reset_n = 1;
|
||
@(posedge m3_src_clk);
|
||
m3_dst_ready = 0; // NOT ready
|
||
|
||
// Send data
|
||
m3_src_data = 32'hCAFEBABE;
|
||
m3_src_valid = 1;
|
||
@(posedge m3_src_clk); #1;
|
||
m3_src_valid = 0;
|
||
|
||
// Wait — dst_valid should assert but data shouldn't be consumed
|
||
begin : c4_wait_valid
|
||
integer wait_cnt;
|
||
for (wait_cnt = 0; wait_cnt < 30; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_dst_clk); #1;
|
||
if (m3_dst_valid) disable c4_wait_valid;
|
||
end
|
||
end
|
||
check(m3_dst_valid === 1'b1, "M3: dst_valid asserts even without dst_ready");
|
||
check(m3_dst_data === 32'hCAFEBABE, "M3: Correct data held during backpressure");
|
||
|
||
// src should NOT be ready (busy with in-flight transfer)
|
||
check(m3_src_ready === 1'b0, "M3: src_ready = 0 during backpressure");
|
||
|
||
// Now assert dst_ready — transfer should complete
|
||
m3_dst_ready = 1;
|
||
begin : c4_wait_done
|
||
integer wait_cnt;
|
||
for (wait_cnt = 0; wait_cnt < 30; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_src_clk); #1;
|
||
if (m3_src_ready) disable c4_wait_done;
|
||
end
|
||
end
|
||
check(m3_src_ready === 1'b1, "M3: src_ready reasserts after backpressure release");
|
||
|
||
// ── C5: Data integrity with edge-case values ───────────
|
||
$display("\n--- C5: Edge-Case Values ---");
|
||
m3_reset_n = 0;
|
||
#200;
|
||
m3_reset_n = 1;
|
||
@(posedge m3_src_clk);
|
||
m3_dst_ready = 1;
|
||
|
||
begin : c5_block
|
||
reg [31:0] edge_vals [0:3];
|
||
reg [31:0] edge_recv [0:3];
|
||
integer tx_idx, rx_idx, wait_cnt;
|
||
reg all_match;
|
||
reg src_wait_done, dst_wait_done;
|
||
|
||
edge_vals[0] = 32'h00000000;
|
||
edge_vals[1] = 32'hFFFFFFFF;
|
||
edge_vals[2] = 32'h80000000;
|
||
edge_vals[3] = 32'h00000001;
|
||
|
||
rx_idx = 0;
|
||
|
||
for (tx_idx = 0; tx_idx < 4; tx_idx = tx_idx + 1) begin
|
||
// Wait for src_ready
|
||
src_wait_done = 0;
|
||
for (wait_cnt = 0; wait_cnt < 50 && !src_wait_done; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_src_clk); #1;
|
||
if (m3_src_ready) src_wait_done = 1;
|
||
end
|
||
|
||
m3_src_data = edge_vals[tx_idx];
|
||
m3_src_valid = 1;
|
||
@(posedge m3_src_clk); #1;
|
||
m3_src_valid = 0;
|
||
|
||
dst_wait_done = 0;
|
||
for (wait_cnt = 0; wait_cnt < 50 && !dst_wait_done; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_dst_clk); #1;
|
||
if (m3_dst_valid) begin
|
||
edge_recv[rx_idx] = m3_dst_data;
|
||
rx_idx = rx_idx + 1;
|
||
dst_wait_done = 1;
|
||
end
|
||
end
|
||
end
|
||
|
||
all_match = 1;
|
||
for (i = 0; i < 4; i = i + 1) begin
|
||
if (i < rx_idx && edge_recv[i] !== edge_vals[i]) begin
|
||
all_match = 0;
|
||
$display(" [WARN] Edge val[%0d]: got 0x%08x, exp 0x%08x",
|
||
i, edge_recv[i], edge_vals[i]);
|
||
end
|
||
end
|
||
check(rx_idx == 4, "M3: All 4 edge-case values received");
|
||
check(all_match, "M3: Edge-case values (0x0, 0xFFFF, 0x8000, 0x1) correct");
|
||
end
|
||
|
||
// ── C6: Port Connectivity + Reset Recovery ─────────────
|
||
$display("\n--- C6: Port Connectivity + Reset Recovery ---");
|
||
m3_reset_n = 0;
|
||
m3_src_valid = 0;
|
||
m3_dst_ready = 0;
|
||
#200;
|
||
m3_reset_n = 1;
|
||
repeat (5) @(posedge m3_src_clk); #1;
|
||
|
||
// Port connectivity: outputs should not be X or Z after reset
|
||
check(m3_src_ready !== 1'bx, "M3: src_ready is not X after reset");
|
||
check(m3_src_ready !== 1'bz, "M3: src_ready is not Z after reset");
|
||
check(m3_dst_valid !== 1'bx, "M3: dst_valid is not X after reset");
|
||
check(m3_dst_valid !== 1'bz, "M3: dst_valid is not Z after reset");
|
||
check(m3_dst_data !== 32'hxxxxxxxx, "M3: dst_data is not X after reset");
|
||
check(m3_dst_data !== 32'hzzzzzzzz, "M3: dst_data is not Z after reset");
|
||
|
||
// Reset during active transfer: start a transfer, then assert reset mid-flight
|
||
m3_dst_ready = 1;
|
||
|
||
// Wait for src_ready
|
||
begin : c6_wait_ready
|
||
integer wait_cnt;
|
||
for (wait_cnt = 0; wait_cnt < 30; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_src_clk); #1;
|
||
if (m3_src_ready) disable c6_wait_ready;
|
||
end
|
||
end
|
||
|
||
m3_src_data = 32'h12345678;
|
||
m3_src_valid = 1;
|
||
@(posedge m3_src_clk); #1;
|
||
m3_src_valid = 0;
|
||
|
||
// Wait a few cycles for transfer to be in-flight, then reset
|
||
repeat (3) @(posedge m3_dst_clk);
|
||
m3_reset_n = 0;
|
||
#200;
|
||
|
||
// Verify outputs are clean during reset
|
||
check(m3_dst_valid === 1'b0, "M3: dst_valid = 0 after mid-transfer reset");
|
||
|
||
// Release reset and verify recovery
|
||
m3_reset_n = 1;
|
||
@(posedge m3_src_clk);
|
||
m3_dst_ready = 1;
|
||
|
||
// Wait for src_ready to reassert (module recovered)
|
||
begin : c6_recovery_wait
|
||
integer wait_cnt;
|
||
reg recovered;
|
||
recovered = 0;
|
||
for (wait_cnt = 0; wait_cnt < 50; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_src_clk); #1;
|
||
if (m3_src_ready) begin
|
||
recovered = 1;
|
||
disable c6_recovery_wait;
|
||
end
|
||
end
|
||
check(recovered, "M3: src_ready reasserts after mid-transfer reset recovery");
|
||
end
|
||
|
||
// Verify a new transfer works after recovery
|
||
m3_src_data = 32'hABCD0000;
|
||
m3_src_valid = 1;
|
||
@(posedge m3_src_clk); #1;
|
||
m3_src_valid = 0;
|
||
|
||
begin : c6_post_reset_xfer
|
||
integer wait_cnt;
|
||
for (wait_cnt = 0; wait_cnt < 30; wait_cnt = wait_cnt + 1) begin
|
||
@(posedge m3_dst_clk); #1;
|
||
if (m3_dst_valid) disable c6_post_reset_xfer;
|
||
end
|
||
end
|
||
check(m3_dst_valid === 1'b1, "M3: dst_valid asserts for post-recovery transfer");
|
||
check(m3_dst_data === 32'hABCD0000, "M3: data 0xABCD0000 correct after reset recovery");
|
||
|
||
// ════════════════════════════════════════════════════════
|
||
// Summary
|
||
// ════════════════════════════════════════════════════════
|
||
$display("");
|
||
$display("========================================");
|
||
$display(" CDC MODULES TESTBENCH RESULTS");
|
||
$display(" PASSED: %0d / %0d", pass_count, test_num);
|
||
$display(" FAILED: %0d / %0d", fail_count, test_num);
|
||
if (fail_count == 0)
|
||
$display(" ** ALL TESTS PASSED **");
|
||
else
|
||
$display(" ** SOME TESTS FAILED **");
|
||
$display("========================================");
|
||
$display("");
|
||
|
||
#100;
|
||
$finish;
|
||
end
|
||
|
||
endmodule
|