test(fpga): wire 4 orphan TBs; add nightly DDC fuzz CI job

Regression coverage additions in run_regression.sh:
  - Phase 2: tb_ddc_400m (standalone DDC unit, 7 checks)
  - Phase 3: tb_freq_matched_filter (14 checks)
  - Phase 4: tb_ddc_input_interface (26 checks), tb_latency_buffer
    (13 checks)

All four existed in tb/ but had no regression runner entry; now gate
every push/PR. Deletes tb/tb_multiseg_cosim.v — stale against the
current RP_FFT_SIZE=2048 / RP_LONG_SEGMENTS_3KM=2 (TB hardcoded 1024/4,
15/32 checks fail on current RTL). Re-add when the multi-segment TB
is reworked for the 2048-point pipeline.

CI: new fpga-fuzz job running test_ddc_cosim_fuzz.py -m slow
(100-seed sweep). Gated to schedule (07:00 UTC daily) +
workflow_dispatch so PRs stay fast.
This commit is contained in:
Jason
2026-04-23 06:31:26 +05:45
parent 72a897f4fc
commit ae61cf5dc5
3 changed files with 51 additions and 666 deletions
+34
View File
@@ -5,6 +5,9 @@ on:
branches: [main, develop]
push:
branches: [main, develop]
schedule:
- cron: "0 7 * * *" # Nightly 07:00 UTC — slow-path fuzz
workflow_dispatch:
jobs:
# ===========================================================================
@@ -114,3 +117,34 @@ jobs:
uv run pytest
9_Firmware/tests/cross_layer/test_cross_layer_contract.py
-v --tb=short
# ===========================================================================
# DDC Co-Sim Fuzz (slow, nightly + manual dispatch only)
# 100 random seeds through the full DDC pipeline vs Python radar_scene model.
# Gated on schedule/workflow_dispatch to keep PRs fast; skipped on push/PR.
# ===========================================================================
fpga-fuzz:
name: FPGA DDC Fuzz (slow)
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --group dev
- name: Install Icarus Verilog
run: sudo apt-get update && sudo apt-get install -y iverilog
- name: Run DDC fuzz (100-seed sweep)
run: >
uv run pytest -m slow
9_Firmware/tests/cross_layer/test_ddc_cosim_fuzz.py
-v --tb=short
+17
View File
@@ -544,6 +544,11 @@ run_test "DDC Chain (NCO→CIC→FIR)" \
tb/tb_ddc_cosim.v ddc_400m.v nco_400m_enhanced.v \
cic_decimator_4x_enhanced.v fir_lowpass.v cdc_modules.v
run_test "DDC 400M (standalone unit)" \
tb/tb_ddc_400m_reg.vvp \
tb/tb_ddc_400m.v ddc_400m.v nco_400m_enhanced.v \
cic_decimator_4x_enhanced.v cdc_modules.v fir_lowpass.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)
# that self-blessing golden-generate/compare tests cannot detect.
@@ -634,6 +639,10 @@ run_test "Matched Filter Chain" \
tb/tb_matched_filter_processing_chain.v matched_filter_processing_chain.v \
fft_engine.v chirp_memory_loader_param.v
run_test "Frequency Matched Filter" \
tb/tb_fmf_reg.vvp \
tb/tb_freq_matched_filter.v frequency_matched_filter.v
echo ""
# ===========================================================================
@@ -661,6 +670,14 @@ run_test "Radar Mode Controller" \
tb/tb_rmc_reg.vvp \
tb/tb_radar_mode_controller.v radar_mode_controller.v
run_test "DDC Input Interface (18→16 round/sat)" \
tb/tb_ddc_in_reg.vvp \
tb/tb_ddc_input_interface.v ddc_input_interface.v
run_test "Latency Buffer" \
tb/tb_latbuf_reg.vvp \
tb/tb_latency_buffer.v latency_buffer.v
echo ""
# ===========================================================================
-666
View File
@@ -1,666 +0,0 @@
`timescale 1ns / 1ps
/**
* tb_multiseg_cosim.v
*
* Co-simulation testbench for matched_filter_multi_segment.v
*
* Tests the overlap-save segmented convolution wrapper:
* - Long chirp: 4 segments with 128-sample overlap
* - Short chirp: 1 segment with zero-padding
*
* Validates:
* 1. FSM state transitions (IDLE -> COLLECT -> WAIT_REF -> PROCESSING -> WAIT_FFT -> OUTPUT -> NEXT)
* 2. Per-segment output count (1024 per segment)
* 3. Buffer contents at processing time (what the MF chain actually sees)
* 4. Overlap-save carry between segments
* 5. Short chirp zero-padding
* 6. Edge cases: chirp trigger, no-trigger idle
*
* Compile (SIMULATION branch):
* iverilog -g2001 -DSIMULATION -o tb/tb_multiseg_cosim.vvp \
* tb/tb_multiseg_cosim.v matched_filter_multi_segment.v \
* matched_filter_processing_chain.v
*/
module tb_multiseg_cosim;
// ============================================================================
// Parameters
// ============================================================================
localparam CLK_PERIOD = 10.0; // 100 MHz
localparam FFT_SIZE = 1024;
localparam SEGMENT_ADVANCE = 896; // 1024 - 128
localparam OVERLAP_SAMPLES = 128;
localparam LONG_SEGMENTS = 4;
localparam SHORT_SAMPLES = 50;
localparam LONG_CHIRP_SAMPLES = 3000;
localparam TIMEOUT = 500000; // Max clocks per operation
// ============================================================================
// Clock and reset
// ============================================================================
reg clk;
reg reset_n;
initial clk = 0;
always #(CLK_PERIOD / 2) clk = ~clk;
// ============================================================================
// DUT signals
// ============================================================================
reg signed [17:0] ddc_i;
reg signed [17:0] ddc_q;
reg ddc_valid;
reg use_long_chirp;
reg [5:0] chirp_counter;
reg mc_new_chirp;
reg mc_new_elevation;
reg mc_new_azimuth;
reg [15:0] ref_chirp_real;
reg [15:0] ref_chirp_imag;
reg mem_ready;
wire signed [15:0] pc_i_w;
wire signed [15:0] pc_q_w;
wire pc_valid_w;
wire [1:0] segment_request;
wire [9:0] sample_addr_out;
wire mem_request;
wire [3:0] status;
// ============================================================================
// DUT instantiation
// ============================================================================
matched_filter_multi_segment dut (
.clk(clk),
.reset_n(reset_n),
.ddc_i(ddc_i),
.ddc_q(ddc_q),
.ddc_valid(ddc_valid),
.use_long_chirp(use_long_chirp),
.chirp_counter(chirp_counter),
.mc_new_chirp(mc_new_chirp),
.mc_new_elevation(mc_new_elevation),
.mc_new_azimuth(mc_new_azimuth),
.ref_chirp_real(ref_chirp_real),
.ref_chirp_imag(ref_chirp_imag),
.segment_request(segment_request),
.sample_addr_out(sample_addr_out),
.mem_request(mem_request),
.mem_ready(mem_ready),
.pc_i_w(pc_i_w),
.pc_q_w(pc_q_w),
.pc_valid_w(pc_valid_w),
.status(status)
);
// ============================================================================
// Reference chirp memory model
// ============================================================================
// Generate simple reference: each segment is a known pattern
// Segment N: ref[k] = {segment_number, sample_index} packed into I, Q=0
// This makes it easy to verify which segment's reference was used
//
// For the SIMULATION behavioral chain, exact ref values don't matter for
// structural testing we just need to verify the wrapper feeds them correctly.
reg [15:0] ref_mem_i [0:4095]; // 4 segments x 1024
reg [15:0] ref_mem_q [0:4095];
integer ref_init_idx;
initial begin
for (ref_init_idx = 0; ref_init_idx < 4096; ref_init_idx = ref_init_idx + 1) begin
// Simple ramp per segment: distinguishable patterns
ref_mem_i[ref_init_idx] = (ref_init_idx % 1024) * 4; // 0..4092 ramp
ref_mem_q[ref_init_idx] = 16'd0;
end
end
always @(posedge clk) begin
if (mem_request) begin
if (use_long_chirp) begin
ref_chirp_real <= ref_mem_i[{segment_request, sample_addr_out}];
ref_chirp_imag <= ref_mem_q[{segment_request, sample_addr_out}];
end else begin
ref_chirp_real <= ref_mem_i[sample_addr_out];
ref_chirp_imag <= ref_mem_q[sample_addr_out];
end
mem_ready <= 1'b1;
end else begin
mem_ready <= 1'b0;
end
end
// One-hot bitmap of distinct segment_request values observed during the run.
// Used by TEST 4 to turn a tautological check into a real coverage assertion.
reg [3:0] seg_request_seen;
initial seg_request_seen = 4'b0000;
always @(posedge clk) begin
if (reset_n && mem_request)
seg_request_seen <= seg_request_seen | (4'b0001 << segment_request);
end
// ============================================================================
// Output capture
// ============================================================================
reg signed [15:0] cap_out_i [0:4095];
reg signed [15:0] cap_out_q [0:4095];
integer cap_count;
integer cap_file;
// ============================================================================
// Test infrastructure
// ============================================================================
integer pass_count;
integer fail_count;
integer test_count;
task check;
input cond;
input [511:0] label;
begin
test_count = test_count + 1;
if (cond) begin
$display("[PASS] %0s", label);
pass_count = pass_count + 1;
end else begin
$display("[FAIL] %0s", label);
fail_count = fail_count + 1;
end
end
endtask
task apply_reset;
begin
reset_n <= 1'b0;
ddc_i <= 18'd0;
ddc_q <= 18'd0;
ddc_valid <= 1'b0;
use_long_chirp <= 1'b0;
chirp_counter <= 6'd0;
mc_new_chirp <= 1'b0;
mc_new_elevation <= 1'b0;
mc_new_azimuth <= 1'b0;
ref_chirp_real <= 16'd0;
ref_chirp_imag <= 16'd0;
mem_ready <= 1'b0;
repeat(10) @(posedge clk);
reset_n <= 1'b1;
repeat(5) @(posedge clk);
end
endtask
// ============================================================================
// Task: Feed N samples and wait for processing to complete
// ============================================================================
// The multi_segment FSM is blocking: it only accepts data in ST_COLLECT_DATA
// state, and processes each segment before accepting more data.
// This task feeds data respecting the FSM flow.
task feed_and_wait_segment;
input integer start_idx;
input integer num_samples;
input integer seg_num;
output integer output_count;
integer i;
integer wait_cnt;
begin
output_count = 0;
// Feed samples one per clock (only accepted when FSM is in ST_COLLECT_DATA)
for (i = 0; i < num_samples; i = i + 1) begin
@(posedge clk);
// Use a simple ramp pattern: value = sample index (easy to verify)
ddc_i <= (start_idx + i) & 18'h3FFFF;
ddc_q <= ((start_idx + i) * 3 + 100) & 18'h3FFFF; // Different pattern for Q
ddc_valid <= 1'b1;
end
@(posedge clk);
ddc_valid <= 1'b0;
ddc_i <= 18'd0;
ddc_q <= 18'd0;
// Wait for processing to complete and capture output
wait_cnt = 0;
while (output_count < FFT_SIZE && wait_cnt < TIMEOUT) begin
@(posedge clk);
#1;
if (pc_valid_w) begin
cap_out_i[cap_count] = pc_i_w;
cap_out_q[cap_count] = pc_q_w;
cap_count = cap_count + 1;
output_count = output_count + 1;
end
wait_cnt = wait_cnt + 1;
end
$display(" Segment %0d: fed %0d samples (from idx %0d), got %0d outputs, waited %0d clks",
seg_num, num_samples, start_idx, output_count, wait_cnt);
end
endtask
// ============================================================================
// Main test sequence
// ============================================================================
integer i, j;
integer wait_count;
integer seg_out;
integer total_outputs;
integer errors_i, errors_q;
reg [3:0] prev_state;
// Buffer content probes (access DUT internal signals)
wire signed [15:0] buf_probe_i_0 = dut.input_buffer_i[0];
wire signed [15:0] buf_probe_i_127 = dut.input_buffer_i[127];
wire signed [15:0] buf_probe_i_128 = dut.input_buffer_i[128];
wire signed [15:0] buf_probe_i_895 = dut.input_buffer_i[895];
wire signed [15:0] buf_probe_i_896 = dut.input_buffer_i[896];
wire signed [15:0] buf_probe_i_1023 = dut.input_buffer_i[1023];
wire [10:0] buf_wptr = dut.buffer_write_ptr;
wire [10:0] buf_rptr = dut.buffer_read_ptr;
wire [2:0] cur_seg = dut.current_segment;
wire [2:0] tot_seg = dut.total_segments;
wire [3:0] fsm_state = dut.state;
wire [15:0] chirp_cnt = dut.chirp_samples_collected;
initial begin
// VCD dump
$dumpfile("tb_multiseg_cosim.vcd");
$dumpvars(0, tb_multiseg_cosim);
pass_count = 0;
fail_count = 0;
test_count = 0;
cap_count = 0;
$display("============================================================");
$display("Multi-Segment Matched Filter Co-Sim Testbench");
$display("============================================================");
// ====================================================================
// TEST 1: Reset and Idle behavior
// ====================================================================
$display("\n=== TEST 1: Reset and Idle ===");
apply_reset;
check(fsm_state == 4'd0, "FSM state is ST_IDLE after reset");
check(cur_seg == 3'd0, "Current segment is 0 after reset");
check(chirp_cnt == 16'd0, "Chirp sample count is 0 after reset");
// Feed data without chirp trigger should stay idle
ddc_i <= 18'h1000;
ddc_q <= 18'h2000;
ddc_valid <= 1'b1;
repeat(20) @(posedge clk);
ddc_valid <= 1'b0;
check(fsm_state == 4'd0, "Stays in IDLE without chirp trigger");
// ====================================================================
// TEST 2: Short chirp (1 segment, zero-padded)
// ====================================================================
$display("\n=== TEST 2: Short Chirp (1 segment, zero-padded) ===");
apply_reset;
use_long_chirp <= 1'b0;
chirp_counter <= 6'd0;
@(posedge clk);
// Trigger chirp start (rising edge on mc_new_chirp)
mc_new_chirp <= 1'b1;
@(posedge clk);
@(posedge clk);
// Verify FSM transitioned to ST_COLLECT_DATA
check(fsm_state == 4'd1, "Short chirp: entered ST_COLLECT_DATA");
// Feed 50 short chirp samples
for (i = 0; i < SHORT_SAMPLES; i = i + 1) begin
@(posedge clk);
ddc_i <= (i * 100 + 500) & 18'h3FFFF; // Identifiable values
ddc_q <= (i * 50 + 200) & 18'h3FFFF;
ddc_valid <= 1'b1;
end
@(posedge clk);
ddc_valid <= 1'b0;
// Should transition to ST_ZERO_PAD
@(posedge clk);
@(posedge clk);
check(fsm_state == 4'd2, "Short chirp: entered ST_ZERO_PAD");
// Wait for zero-padding + processing + output
cap_count = 0;
wait_count = 0;
while (cap_count < FFT_SIZE && wait_count < TIMEOUT) begin
@(posedge clk);
#1;
if (pc_valid_w) begin
cap_out_i[cap_count] = pc_i_w;
cap_out_q[cap_count] = pc_q_w;
cap_count = cap_count + 1;
end
wait_count = wait_count + 1;
end
$display(" Short chirp: captured %0d outputs (waited %0d clks)", cap_count, wait_count);
check(cap_count == FFT_SIZE, "Short chirp: got 1024 outputs");
// Verify the buffer was zero-padded correctly
// After zero-padding, positions 50-1023 should be zero
// We can check this via the output a partially zero buffer
// should produce a specific FFT pattern
// Write short chirp CSV
cap_file = $fopen("tb/cosim/rtl_multiseg_short.csv", "w");
if (cap_file != 0) begin
$fwrite(cap_file, "bin,rtl_i,rtl_q\n");
for (i = 0; i < cap_count; i = i + 1) begin
$fwrite(cap_file, "%0d,%0d,%0d\n", i, cap_out_i[i], cap_out_q[i]);
end
$fclose(cap_file);
end
// ====================================================================
// TEST 3: Long chirp (4 segments, overlap-save)
// ====================================================================
$display("\n=== TEST 3: Long Chirp (4 segments, overlap-save) ===");
apply_reset;
use_long_chirp <= 1'b1;
chirp_counter <= 6'd0;
@(posedge clk);
// Trigger chirp start
mc_new_chirp <= 1'b1;
@(posedge clk);
@(posedge clk);
check(fsm_state == 4'd1, "Long chirp: entered ST_COLLECT_DATA");
check(tot_seg == 3'd4, "total_segments = 4");
// Track cumulative input index
total_outputs = 0;
cap_count = 0;
// ------ SEGMENT 0 ------
$display("\n --- Segment 0 ---");
// Feed BUFFER_SIZE (1024) samples to fill the entire buffer
// (overlap-save fix: seg 0 must fill the full 1024-sample buffer)
for (i = 0; i < FFT_SIZE; i = i + 1) begin
@(posedge clk);
ddc_i <= (i + 1) & 18'h3FFFF; // Non-zero, identifiable: 1, 2, 3, ...
ddc_q <= ((i + 1) * 2) & 18'h3FFFF;
ddc_valid <= 1'b1;
end
@(posedge clk);
ddc_valid <= 1'b0;
// Verify segment 0 transition
@(posedge clk);
@(posedge clk);
$display(" After feeding 1024 samples: state=%0d, segment=%0d, chirp_cnt=%0d",
fsm_state, cur_seg, chirp_cnt);
check(cur_seg == 3'd0, "Seg 0: current_segment=0");
// Verify buffer contents for segment 0 all 1024 positions written
$display(" Buffer[0]=%0d, Buffer[1]=%0d, Buffer[127]=%0d",
buf_probe_i_0, dut.input_buffer_i[1], buf_probe_i_127);
$display(" Buffer[895]=%0d, Buffer[896]=%0d, Buffer[1023]=%0d",
buf_probe_i_895, buf_probe_i_896, buf_probe_i_1023);
// Buffer[896:1023] should now be WRITTEN with data (overlap-save fix)
check(buf_probe_i_896 != 16'd0, "Seg 0: buffer[896]!=0 (written with data)");
check(buf_probe_i_1023 != 16'd0, "Seg 0: buffer[1023]!=0 (written with data)");
// Wait for segment 0 processing to complete
seg_out = 0;
wait_count = 0;
while (seg_out < FFT_SIZE && wait_count < TIMEOUT) begin
@(posedge clk);
#1;
if (pc_valid_w) begin
cap_out_i[cap_count] = pc_i_w;
cap_out_q[cap_count] = pc_q_w;
cap_count = cap_count + 1;
seg_out = seg_out + 1;
end
wait_count = wait_count + 1;
end
total_outputs = total_outputs + seg_out;
$display(" Seg 0 output: %0d samples (waited %0d clks)", seg_out, wait_count);
check(seg_out == FFT_SIZE, "Seg 0: got 1024 outputs");
// After segment 0 output, FSM goes to ST_NEXT_SEGMENT then ST_COLLECT_DATA
// Wait for it to settle
wait_count = 0;
while (fsm_state != 4'd1 && wait_count < 100) begin
@(posedge clk);
wait_count = wait_count + 1;
end
$display(" After seg 0 complete: state=%0d, segment=%0d", fsm_state, cur_seg);
check(fsm_state == 4'd1, "Seg 0 done: back to ST_COLLECT_DATA");
check(cur_seg == 3'd1, "Seg 0 done: current_segment=1");
// Verify overlap-save: buffer[0:127] should now contain
// what was in buffer[896:1023] of segment 0 (real data, not zeros)
$display(" Overlap check: buffer[0]=%0d (from seg0 pos 896, expect non-zero)",
buf_probe_i_0);
check(buf_probe_i_0 != 16'd0, "Overlap-save: buffer[0]!=0 (real data from seg0[896])");
// buffer_write_ptr should be 128 (OVERLAP_SAMPLES)
check(buf_wptr == 11'd128, "Overlap-save: write_ptr=128");
// ------ SEGMENT 1 ------
$display("\n --- Segment 1 ---");
// Need to fill from ptr=128 to ptr=1024 -> 896 new samples
for (i = 0; i < SEGMENT_ADVANCE; i = i + 1) begin
@(posedge clk);
ddc_i <= ((FFT_SIZE + i + 1) * 5) & 18'h3FFFF; // Different pattern
ddc_q <= ((FFT_SIZE + i + 1) * 7) & 18'h3FFFF;
ddc_valid <= 1'b1;
end
@(posedge clk);
ddc_valid <= 1'b0;
@(posedge clk);
@(posedge clk);
$display(" After feeding 896 samples: state=%0d, segment=%0d, chirp_cnt=%0d",
fsm_state, cur_seg, chirp_cnt);
// Wait for segment 1 processing
seg_out = 0;
wait_count = 0;
while (seg_out < FFT_SIZE && wait_count < TIMEOUT) begin
@(posedge clk);
#1;
if (pc_valid_w) begin
cap_out_i[cap_count] = pc_i_w;
cap_out_q[cap_count] = pc_q_w;
cap_count = cap_count + 1;
seg_out = seg_out + 1;
end
wait_count = wait_count + 1;
end
total_outputs = total_outputs + seg_out;
$display(" Seg 1 output: %0d samples (waited %0d clks)", seg_out, wait_count);
check(seg_out == FFT_SIZE, "Seg 1: got 1024 outputs");
// Wait for FSM to return to COLLECT_DATA
wait_count = 0;
while (fsm_state != 4'd1 && wait_count < 100) begin
@(posedge clk);
wait_count = wait_count + 1;
end
check(cur_seg == 3'd2, "Seg 1 done: current_segment=2");
check(buf_wptr == 11'd128, "Seg 1 done: write_ptr=128 (overlap ready)");
// ------ SEGMENT 2 ------
$display("\n --- Segment 2 ---");
// Feed 896 new samples (ptr 128 -> 1024)
for (i = 0; i < SEGMENT_ADVANCE; i = i + 1) begin
@(posedge clk);
ddc_i <= ((FFT_SIZE + SEGMENT_ADVANCE + i + 1) * 3) & 18'h3FFFF;
ddc_q <= ((FFT_SIZE + SEGMENT_ADVANCE + i + 1) * 9) & 18'h3FFFF;
ddc_valid <= 1'b1;
end
@(posedge clk);
ddc_valid <= 1'b0;
seg_out = 0;
wait_count = 0;
while (seg_out < FFT_SIZE && wait_count < TIMEOUT) begin
@(posedge clk);
#1;
if (pc_valid_w) begin
cap_out_i[cap_count] = pc_i_w;
cap_out_q[cap_count] = pc_q_w;
cap_count = cap_count + 1;
seg_out = seg_out + 1;
end
wait_count = wait_count + 1;
end
total_outputs = total_outputs + seg_out;
$display(" Seg 2 output: %0d samples (waited %0d clks)", seg_out, wait_count);
check(seg_out == FFT_SIZE, "Seg 2: got 1024 outputs");
wait_count = 0;
while (fsm_state != 4'd1 && wait_count < 100) begin
@(posedge clk);
wait_count = wait_count + 1;
end
check(cur_seg == 3'd3, "Seg 2 done: current_segment=3");
// ------ SEGMENT 3 (final partial, zero-padded) ------
$display("\n --- Segment 3 (final, partial + zero-pad) ---");
// Total consumed so far: 1024 + 896 + 896 = 2816
// Remaining: 3000 - 2816 = 184 new samples
// After feeding 184 samples, chirp_complete fires and zero-padding begins
for (i = 0; i < (LONG_CHIRP_SAMPLES - FFT_SIZE - 2 * SEGMENT_ADVANCE); i = i + 1) begin
@(posedge clk);
ddc_i <= ((FFT_SIZE + 2 * SEGMENT_ADVANCE + i + 1) * 11) & 18'h3FFFF;
ddc_q <= ((FFT_SIZE + 2 * SEGMENT_ADVANCE + i + 1) * 13) & 18'h3FFFF;
ddc_valid <= 1'b1;
end
@(posedge clk);
ddc_valid <= 1'b0;
// Wait a few clocks for chirp_complete to fire and zero-padding to begin
repeat(5) @(posedge clk);
$display(" After feeding %0d samples: state=%0d, segment=%0d, chirp_cnt=%0d",
LONG_CHIRP_SAMPLES - FFT_SIZE - 2 * SEGMENT_ADVANCE,
fsm_state, cur_seg, chirp_cnt);
seg_out = 0;
wait_count = 0;
while (seg_out < FFT_SIZE && wait_count < TIMEOUT) begin
@(posedge clk);
#1;
if (pc_valid_w) begin
cap_out_i[cap_count] = pc_i_w;
cap_out_q[cap_count] = pc_q_w;
cap_count = cap_count + 1;
seg_out = seg_out + 1;
end
wait_count = wait_count + 1;
end
total_outputs = total_outputs + seg_out;
$display(" Seg 3 output: %0d samples (waited %0d clks)", seg_out, wait_count);
check(seg_out == FFT_SIZE, "Seg 3: got 1024 outputs");
// After last segment, FSM should return to IDLE
wait_count = 0;
while (fsm_state != 4'd0 && wait_count < 100) begin
@(posedge clk);
wait_count = wait_count + 1;
end
check(fsm_state == 4'd0, "After all segments: returned to ST_IDLE");
$display("\n Total long chirp outputs: %0d (expected %0d)",
total_outputs, LONG_SEGMENTS * FFT_SIZE);
check(total_outputs == LONG_SEGMENTS * FFT_SIZE,
"Long chirp: total 4096 outputs across 4 segments");
// Write CSV
cap_file = $fopen("tb/cosim/rtl_multiseg_long.csv", "w");
if (cap_file != 0) begin
$fwrite(cap_file, "segment,bin,rtl_i,rtl_q\n");
for (i = 0; i < total_outputs; i = i + 1) begin
$fwrite(cap_file, "%0d,%0d,%0d,%0d\n",
i / FFT_SIZE, i % FFT_SIZE,
cap_out_i[i], cap_out_q[i]);
end
$fclose(cap_file);
$display(" Long chirp output written to tb/cosim/rtl_multiseg_long.csv");
end
// ====================================================================
// TEST 4: Verify segment_request output
// ====================================================================
$display("\n=== TEST 4: Segment Request Tracking ===");
// Verify that segment_request actually took all 4 values (0..3) during
// the long-chirp run, using the bitmap captured by the always-block above.
// A stuck segment_request would previously pass silently.
$display(" segment_request bitmap: %b (bit k = value k seen)", seg_request_seen);
check(seg_request_seen == 4'b1111,
"Segment request visited all 4 values (0,1,2,3) during long-chirp run");
// ====================================================================
// TEST 5: Non-zero output energy check
// ====================================================================
$display("\n=== TEST 5: Output Energy Check ===");
begin : energy_check
integer seg;
integer bin;
integer seg_energy;
integer max_energy;
for (seg = 0; seg < LONG_SEGMENTS; seg = seg + 1) begin
seg_energy = 0;
max_energy = 0;
for (bin = 0; bin < FFT_SIZE; bin = bin + 1) begin
j = seg * FFT_SIZE + bin;
seg_energy = seg_energy +
((cap_out_i[j] > 0) ? cap_out_i[j] : -cap_out_i[j]) +
((cap_out_q[j] > 0) ? cap_out_q[j] : -cap_out_q[j]);
if (((cap_out_i[j] > 0) ? cap_out_i[j] : -cap_out_i[j]) +
((cap_out_q[j] > 0) ? cap_out_q[j] : -cap_out_q[j]) > max_energy) begin
max_energy = ((cap_out_i[j] > 0) ? cap_out_i[j] : -cap_out_i[j]) +
((cap_out_q[j] > 0) ? cap_out_q[j] : -cap_out_q[j]);
end
end
$display(" Seg %0d: total_energy=%0d, peak_mag=%0d", seg, seg_energy, max_energy);
check(seg_energy > 0, "Seg non-zero output energy");
end
end
// ====================================================================
// TEST 6: Re-trigger capability
// ====================================================================
$display("\n=== TEST 6: Re-trigger After Complete ===");
// Verify we can start a new chirp after the previous one completed
check(fsm_state == 4'd0, "In IDLE before re-trigger");
// Toggle mc_new_chirp (it was left high, so toggle low then high)
mc_new_chirp <= 1'b0;
repeat(3) @(posedge clk);
mc_new_chirp <= 1'b1;
@(posedge clk);
@(posedge clk);
@(posedge clk);
check(fsm_state == 4'd1, "Re-trigger: entered ST_COLLECT_DATA");
// Clean up
ddc_valid <= 1'b0;
// ====================================================================
// Summary
// ====================================================================
$display("\n============================================================");
$display("Results: %0d/%0d PASS", pass_count, test_count);
if (fail_count == 0)
$display("ALL TESTS PASSED");
else
$display("SOME TESTS FAILED");
$display("============================================================");
$finish;
end
endmodule