mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-08 14:44:56 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user