From ae61cf5dc5f1cea84a423a9bd86aa3b36e669773 Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Thu, 23 Apr 2026 06:31:26 +0545 Subject: [PATCH] test(fpga): wire 4 orphan TBs; add nightly DDC fuzz CI job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/ci-tests.yml | 34 ++ 9_Firmware/9_2_FPGA/run_regression.sh | 17 + 9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v | 666 --------------------- 3 files changed, 51 insertions(+), 666 deletions(-) delete mode 100644 9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index be7637a..6f35b89 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -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 diff --git a/9_Firmware/9_2_FPGA/run_regression.sh b/9_Firmware/9_2_FPGA/run_regression.sh index 6b12ac3..bd9e926 100755 --- a/9_Firmware/9_2_FPGA/run_regression.sh +++ b/9_Firmware/9_2_FPGA/run_regression.sh @@ -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 "" # =========================================================================== diff --git a/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v b/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v deleted file mode 100644 index 953e83e..0000000 --- a/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v +++ /dev/null @@ -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