mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-08-07 19:23:11 +00:00
chirp-v2 PR-D: chirp_scheduler replaces radar_mode_controller; MF/MTI wave_sel-native
Single 100 MHz scheduler emits wave_sel[1:0] and chirp_pulse natively. Modes 00 (STM32 pass-through), 01 (auto-scan over SHORT/MEDIUM/LONG sub-frames), 10 (single-chirp debug), 11 (track dwell with watchdog scan-fallback after RP_DEF_TRACK_WATCHDOG_FRAMES=5 idle frames). Sub-frame mask lets ops drop a waveform without recompiling. Drops the receiver_final wave_sel shim added in PR-C: wave_sel comes straight from the scheduler; chirp_pulse replaces the old mc_new_chirp toggle + XOR edge converter. matched_filter_multi_segment and mti_canceller take wave_sel[1:0] and chirp_pulse directly — no parallel paths. multi_segment also bumped: SHORT_CHIRP_SAMPLES 50 -> 100 (V2 1 us SHORT) and MEDIUM_CHIRP_SAMPLES = 500 (5 us). LONG path unchanged. Dead mc_new_elevation/azimuth XOR converters removed. Deletes radar_mode_controller.v, formal/fv_radar_mode_controller.v, and tb/tb_radar_mode_controller.v. Build manifests (run_regression.sh, scripts/200t/build_200t.tcl) updated. Receiver_final pins medium/track/ subframe_enable inputs to RP_DEF_* defaults until PR-G plumbs USB opcodes. Verification: - tb_rxb_fullchain_latency: peak |I|+|Q|=24033 at bin 0, ~80x peak/mean (up from PR-C's 15115 since matched filter now uses full 100 SHORT samples) - tb_mti_canceller: 43/43 PASS with new wave_sel[1:0] input - tb_radar_receiver_final: 8/8 PASS, ALL TESTS PASSED - tb_system_e2e: 34/49 PASS - identical to pre-PR-D baseline (15 failures are pre-existing matched-filter cycle-budget skips); G8.2/G8.3 chirp_scheduler probes PASS - tb_multiseg_cosim: 16/32 - same as pre-PR-D baseline
This commit is contained in:
@@ -34,7 +34,7 @@ reg signed [DATA_W-1:0] range_q_in;
|
||||
reg range_valid_in;
|
||||
reg [5:0] range_bin_in;
|
||||
reg mti_enable;
|
||||
reg tb_use_long_chirp;
|
||||
reg [1:0] tb_wave_sel;
|
||||
|
||||
wire signed [DATA_W-1:0] range_i_out;
|
||||
wire signed [DATA_W-1:0] range_q_out;
|
||||
@@ -65,7 +65,7 @@ mti_canceller #(
|
||||
.range_valid_out(range_valid_out),
|
||||
.range_bin_out(range_bin_out),
|
||||
.mti_enable(mti_enable),
|
||||
.use_long_chirp(tb_use_long_chirp), // driven by TB; T12 exercises boundary
|
||||
.wave_sel(tb_wave_sel), // driven by TB; T12 exercises boundary
|
||||
.mti_first_chirp(mti_first_chirp)
|
||||
);
|
||||
|
||||
@@ -94,7 +94,7 @@ task do_reset;
|
||||
range_q_in = 0;
|
||||
range_valid_in = 0;
|
||||
range_bin_in = 0;
|
||||
tb_use_long_chirp = 1'b0; // default homogeneous waveform
|
||||
tb_wave_sel = 2'b00; // default homogeneous waveform (SHORT)
|
||||
repeat (5) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
@@ -485,7 +485,7 @@ initial begin
|
||||
mti_enable = 1'b1;
|
||||
|
||||
// Chirp A (long, val=1000) — first chirp, muted by first-chirp path.
|
||||
tb_use_long_chirp = 1'b1;
|
||||
tb_wave_sel = 2'b10; // RP_WAVE_LONG
|
||||
fork
|
||||
feed_chirp_const(16'sd1000, 16'sd500);
|
||||
capture_chirp;
|
||||
@@ -496,7 +496,7 @@ initial begin
|
||||
cap_q[0] == 16'sd0);
|
||||
|
||||
// Chirp B (long, val=2000) — same waveform: 2000 - 1000 = 1000.
|
||||
tb_use_long_chirp = 1'b1;
|
||||
tb_wave_sel = 2'b10; // RP_WAVE_LONG
|
||||
cap_count = 0;
|
||||
fork
|
||||
feed_chirp_const(16'sd2000, 16'sd1500);
|
||||
@@ -511,7 +511,7 @@ initial begin
|
||||
// prev buffer must be overwritten with THIS chirp (not subtracted
|
||||
// against the long-waveform chirp B). If R-1 regresses, we'd see
|
||||
// 5000 - 2000 = 3000 here instead of 0.
|
||||
tb_use_long_chirp = 1'b0;
|
||||
tb_wave_sel = 2'b00; // RP_WAVE_SHORT
|
||||
cap_count = 0;
|
||||
fork
|
||||
feed_chirp_const(16'sd5000, 16'sd3000);
|
||||
@@ -525,7 +525,7 @@ initial begin
|
||||
// Chirp D (short, val=5500) — same waveform as C: 5500 - 5000 = 500.
|
||||
// This proves the prev buffer was correctly overwritten with C,
|
||||
// not stuck on B's long-waveform profile.
|
||||
tb_use_long_chirp = 1'b0;
|
||||
tb_wave_sel = 2'b00; // RP_WAVE_SHORT
|
||||
cap_count = 0;
|
||||
fork
|
||||
feed_chirp_const(16'sd5500, 16'sd3250);
|
||||
@@ -538,7 +538,7 @@ initial begin
|
||||
|
||||
// Chirp E (short -> long) — another boundary, reverse direction,
|
||||
// confirms muting is symmetric.
|
||||
tb_use_long_chirp = 1'b1;
|
||||
tb_wave_sel = 2'b10; // RP_WAVE_LONG
|
||||
cap_count = 0;
|
||||
fork
|
||||
feed_chirp_const(16'sd9000, 16'sd4000);
|
||||
@@ -566,7 +566,7 @@ initial begin
|
||||
// ================================================================
|
||||
do_reset;
|
||||
mti_enable = 1'b1;
|
||||
tb_use_long_chirp = 1'b1;
|
||||
tb_wave_sel = 2'b10; // RP_WAVE_LONG
|
||||
|
||||
// Chirp 1: early-terminate at bin 31 (only 32/64 bins). I=1000, Q=500.
|
||||
begin : t13_partial_chirp
|
||||
|
||||
@@ -32,7 +32,7 @@ localparam FFT_SIZE = 1024;
|
||||
localparam SEGMENT_ADVANCE = 896; // 1024 - 128
|
||||
localparam OVERLAP_SAMPLES = 128;
|
||||
localparam LONG_SEGMENTS = 4;
|
||||
localparam SHORT_SAMPLES = 50;
|
||||
localparam SHORT_SAMPLES = 100; // chirp-v2 SHORT = 1 us (was 0.5 us / 50 samples)
|
||||
localparam LONG_CHIRP_SAMPLES = 3000;
|
||||
localparam TIMEOUT = 500000; // Max clocks per operation
|
||||
|
||||
@@ -51,15 +51,17 @@ always #(CLK_PERIOD / 2) clk = ~clk;
|
||||
reg signed [17:0] ddc_i;
|
||||
reg signed [17:0] ddc_q;
|
||||
reg ddc_valid;
|
||||
reg use_long_chirp;
|
||||
reg [1:0] wave_sel;
|
||||
reg [5:0] chirp_counter;
|
||||
reg mc_new_chirp;
|
||||
reg mc_new_elevation;
|
||||
reg mc_new_azimuth;
|
||||
reg chirp_pulse;
|
||||
reg [15:0] ref_chirp_real;
|
||||
reg [15:0] ref_chirp_imag;
|
||||
reg mem_ready;
|
||||
|
||||
// chirp-v2 PR-D: legacy use_long_chirp removed. Tests pick a waveform via
|
||||
// wave_sel directly; helper alias kept for legibility in test bodies.
|
||||
wire use_long_chirp = (wave_sel == 2'b10); // RP_WAVE_LONG
|
||||
|
||||
wire signed [15:0] pc_i_w;
|
||||
wire signed [15:0] pc_q_w;
|
||||
wire pc_valid_w;
|
||||
@@ -77,11 +79,9 @@ matched_filter_multi_segment dut (
|
||||
.ddc_i(ddc_i),
|
||||
.ddc_q(ddc_q),
|
||||
.ddc_valid(ddc_valid),
|
||||
.use_long_chirp(use_long_chirp),
|
||||
.wave_sel(wave_sel),
|
||||
.chirp_counter(chirp_counter),
|
||||
.mc_new_chirp(mc_new_chirp),
|
||||
.mc_new_elevation(mc_new_elevation),
|
||||
.mc_new_azimuth(mc_new_azimuth),
|
||||
.chirp_pulse(chirp_pulse),
|
||||
.ref_chirp_real(ref_chirp_real),
|
||||
.ref_chirp_imag(ref_chirp_imag),
|
||||
.segment_request(segment_request),
|
||||
@@ -176,11 +176,9 @@ task apply_reset;
|
||||
ddc_i <= 18'd0;
|
||||
ddc_q <= 18'd0;
|
||||
ddc_valid <= 1'b0;
|
||||
use_long_chirp <= 1'b0;
|
||||
wave_sel <= 2'b00; // RP_WAVE_SHORT
|
||||
chirp_counter <= 6'd0;
|
||||
mc_new_chirp <= 1'b0;
|
||||
mc_new_elevation <= 1'b0;
|
||||
mc_new_azimuth <= 1'b0;
|
||||
chirp_pulse <= 1'b0;
|
||||
ref_chirp_real <= 16'd0;
|
||||
ref_chirp_imag <= 16'd0;
|
||||
mem_ready <= 1'b0;
|
||||
@@ -301,18 +299,19 @@ initial begin
|
||||
$display("\n=== TEST 2: Short Chirp (1 segment, zero-padded) ===");
|
||||
|
||||
apply_reset;
|
||||
use_long_chirp <= 1'b0;
|
||||
wave_sel <= 2'b00; // RP_WAVE_SHORT
|
||||
chirp_counter <= 6'd0;
|
||||
@(posedge clk);
|
||||
|
||||
// Trigger chirp start (rising edge on mc_new_chirp)
|
||||
mc_new_chirp <= 1'b1;
|
||||
// Trigger chirp start (1-cycle chirp_pulse from chirp_scheduler)
|
||||
chirp_pulse <= 1'b1;
|
||||
@(posedge clk);
|
||||
chirp_pulse <= 1'b0;
|
||||
@(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
|
||||
// Feed SHORT_SAMPLES (100) short chirp samples
|
||||
for (i = 0; i < SHORT_SAMPLES; i = i + 1) begin
|
||||
@(posedge clk);
|
||||
ddc_i <= (i * 100 + 500) & 18'h3FFFF; // Identifiable values
|
||||
@@ -365,13 +364,14 @@ initial begin
|
||||
$display("\n=== TEST 3: Long Chirp (4 segments, overlap-save) ===");
|
||||
|
||||
apply_reset;
|
||||
use_long_chirp <= 1'b1;
|
||||
wave_sel <= 2'b10; // RP_WAVE_LONG
|
||||
chirp_counter <= 6'd0;
|
||||
@(posedge clk);
|
||||
|
||||
// Trigger chirp start
|
||||
mc_new_chirp <= 1'b1;
|
||||
// Trigger chirp start (1-cycle chirp_pulse)
|
||||
chirp_pulse <= 1'b1;
|
||||
@(posedge clk);
|
||||
chirp_pulse <= 1'b0;
|
||||
@(posedge clk);
|
||||
check(fsm_state == 4'd1, "Long chirp: entered ST_COLLECT_DATA");
|
||||
check(tot_seg == 3'd4, "total_segments = 4");
|
||||
@@ -637,11 +637,11 @@ initial begin
|
||||
// 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;
|
||||
// Re-trigger via 1-cycle chirp_pulse
|
||||
repeat(3) @(posedge clk);
|
||||
mc_new_chirp <= 1'b1;
|
||||
chirp_pulse <= 1'b1;
|
||||
@(posedge clk);
|
||||
chirp_pulse <= 1'b0;
|
||||
@(posedge clk);
|
||||
@(posedge clk);
|
||||
check(fsm_state == 4'd1, "Re-trigger: entered ST_COLLECT_DATA");
|
||||
|
||||
@@ -1,750 +0,0 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module tb_radar_mode_controller;
|
||||
|
||||
// ── Parameters ─────────────────────────────────────────────
|
||||
localparam CLK_PERIOD = 10.0; // 100 MHz
|
||||
|
||||
// Use much shorter timing for simulation (100x faster)
|
||||
localparam SIM_LONG_CHIRP = 30;
|
||||
localparam SIM_LONG_LISTEN = 137;
|
||||
localparam SIM_GUARD = 175;
|
||||
localparam SIM_SHORT_CHIRP = 5;
|
||||
localparam SIM_SHORT_LISTEN = 175;
|
||||
|
||||
// Use small scan size for simulation
|
||||
localparam SIM_CHIRPS = 4;
|
||||
localparam SIM_ELEVATIONS = 3;
|
||||
localparam SIM_AZIMUTHS = 2;
|
||||
|
||||
// ── Signals ────────────────────────────────────────────────
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
reg [1:0] mode;
|
||||
reg stm32_new_chirp;
|
||||
reg stm32_new_elevation;
|
||||
reg stm32_new_azimuth;
|
||||
reg trigger;
|
||||
|
||||
// Gap 2: Runtime-configurable timing inputs
|
||||
reg [15:0] cfg_long_chirp_cycles;
|
||||
reg [15:0] cfg_long_listen_cycles;
|
||||
reg [15:0] cfg_guard_cycles;
|
||||
reg [15:0] cfg_short_chirp_cycles;
|
||||
reg [15:0] cfg_short_listen_cycles;
|
||||
reg [5:0] cfg_chirps_per_elev;
|
||||
reg [1:0] range_mode;
|
||||
|
||||
wire use_long_chirp;
|
||||
wire mc_new_chirp;
|
||||
wire mc_new_elevation;
|
||||
wire mc_new_azimuth;
|
||||
wire [5:0] chirp_count;
|
||||
wire [5:0] elevation_count;
|
||||
wire [5:0] azimuth_count;
|
||||
wire scanning;
|
||||
wire scan_complete;
|
||||
|
||||
// ── Test bookkeeping ───────────────────────────────────────
|
||||
integer pass_count;
|
||||
integer fail_count;
|
||||
integer test_num;
|
||||
integer csv_file;
|
||||
integer i;
|
||||
|
||||
// Edge detection helpers for auto-scan counting
|
||||
reg mc_new_chirp_prev;
|
||||
reg mc_new_elevation_prev;
|
||||
reg mc_new_azimuth_prev;
|
||||
integer chirp_toggles;
|
||||
integer elevation_toggles;
|
||||
integer azimuth_toggles;
|
||||
integer scan_completes;
|
||||
|
||||
// Saved values for toggle checks
|
||||
reg saved_mc_new_chirp;
|
||||
reg saved_mc_new_elevation;
|
||||
reg saved_mc_new_azimuth;
|
||||
|
||||
// ── Clock ──────────────────────────────────────────────────
|
||||
always #(CLK_PERIOD/2) clk = ~clk;
|
||||
|
||||
// ── DUT ────────────────────────────────────────────────────
|
||||
radar_mode_controller #(
|
||||
.CHIRPS_PER_ELEVATION (SIM_CHIRPS),
|
||||
.ELEVATIONS_PER_AZIMUTH(SIM_ELEVATIONS),
|
||||
.AZIMUTHS_PER_SCAN (SIM_AZIMUTHS),
|
||||
.LONG_CHIRP_CYCLES (SIM_LONG_CHIRP),
|
||||
.LONG_LISTEN_CYCLES (SIM_LONG_LISTEN),
|
||||
.GUARD_CYCLES (SIM_GUARD),
|
||||
.SHORT_CHIRP_CYCLES (SIM_SHORT_CHIRP),
|
||||
.SHORT_LISTEN_CYCLES (SIM_SHORT_LISTEN)
|
||||
) uut (
|
||||
.clk (clk),
|
||||
.reset_n (reset_n),
|
||||
.mode (mode),
|
||||
.stm32_new_chirp (stm32_new_chirp),
|
||||
.stm32_new_elevation(stm32_new_elevation),
|
||||
.stm32_new_azimuth (stm32_new_azimuth),
|
||||
.trigger (trigger),
|
||||
// Gap 2: Runtime-configurable timing inputs
|
||||
.cfg_long_chirp_cycles (cfg_long_chirp_cycles),
|
||||
.cfg_long_listen_cycles (cfg_long_listen_cycles),
|
||||
.cfg_guard_cycles (cfg_guard_cycles),
|
||||
.cfg_short_chirp_cycles (cfg_short_chirp_cycles),
|
||||
.cfg_short_listen_cycles(cfg_short_listen_cycles),
|
||||
.cfg_chirps_per_elev (cfg_chirps_per_elev),
|
||||
.range_mode (range_mode),
|
||||
// Outputs
|
||||
.use_long_chirp (use_long_chirp),
|
||||
.mc_new_chirp (mc_new_chirp),
|
||||
.mc_new_elevation (mc_new_elevation),
|
||||
.mc_new_azimuth (mc_new_azimuth),
|
||||
.chirp_count (chirp_count),
|
||||
.elevation_count (elevation_count),
|
||||
.azimuth_count (azimuth_count),
|
||||
.scanning (scanning),
|
||||
.scan_complete (scan_complete)
|
||||
);
|
||||
|
||||
// ── 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
|
||||
|
||||
// ── Helper: apply reset ────────────────────────────────────
|
||||
task apply_reset;
|
||||
begin
|
||||
reset_n = 0;
|
||||
mode = 2'b11; // reserved = safe idle
|
||||
stm32_new_chirp = 0;
|
||||
stm32_new_elevation = 0;
|
||||
stm32_new_azimuth = 0;
|
||||
trigger = 0;
|
||||
// Gap 2: Set cfg_* to simulation parameter defaults
|
||||
cfg_long_chirp_cycles = SIM_LONG_CHIRP;
|
||||
cfg_long_listen_cycles = SIM_LONG_LISTEN;
|
||||
cfg_guard_cycles = SIM_GUARD;
|
||||
cfg_short_chirp_cycles = SIM_SHORT_CHIRP;
|
||||
cfg_short_listen_cycles = SIM_SHORT_LISTEN;
|
||||
cfg_chirps_per_elev = SIM_CHIRPS;
|
||||
range_mode = 2'b00; // 3 km short-chirp mode
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ── Stimulus ───────────────────────────────────────────────
|
||||
initial begin
|
||||
$dumpfile("tb_radar_mode_controller.vcd");
|
||||
$dumpvars(0, tb_radar_mode_controller);
|
||||
|
||||
clk = 0;
|
||||
pass_count = 0;
|
||||
fail_count = 0;
|
||||
test_num = 0;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 1: Reset behaviour
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 1: Reset Behaviour ---");
|
||||
apply_reset;
|
||||
|
||||
reset_n = 0;
|
||||
repeat (4) @(posedge clk); #1;
|
||||
check(use_long_chirp === 1'b0, "use_long_chirp=0 after reset");
|
||||
check(mc_new_chirp === 1'b0, "mc_new_chirp=0 after reset");
|
||||
check(mc_new_elevation === 1'b0, "mc_new_elevation=0 after reset");
|
||||
check(mc_new_azimuth === 1'b0, "mc_new_azimuth=0 after reset");
|
||||
check(chirp_count === 6'd0, "chirp_count=0 after reset");
|
||||
check(elevation_count === 6'd0, "elevation_count=0 after reset");
|
||||
check(azimuth_count === 6'd0, "azimuth_count=0 after reset");
|
||||
check(scanning === 1'b0, "scanning=0 after reset");
|
||||
check(scan_complete === 1'b0, "scan_complete=0 after reset");
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 2: STM32 pass-through mode (mode 00)
|
||||
// The DUT uses XOR toggle detection: when stm32_new_chirp
|
||||
// changes from its previous value, the DUT detects it.
|
||||
// We toggle-and-hold (don't pulse) to get exactly one detection.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 2: STM32 Pass-through (mode 00) ---");
|
||||
apply_reset;
|
||||
mode = 2'b00;
|
||||
@(posedge clk); #1;
|
||||
|
||||
// Save current mc_new_chirp
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
|
||||
// Toggle stm32_new_chirp (0→1, hold at 1)
|
||||
stm32_new_chirp = 1'b1;
|
||||
// Wait 2 cycles: 1 for prev register update, 1 for XOR→main FSM
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"mc_new_chirp toggles on stm32 chirp change");
|
||||
check(chirp_count === 6'd1, "chirp_count incremented to 1");
|
||||
|
||||
// Toggle again (1→0, hold at 0) — second chirp
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
stm32_new_chirp = 1'b0;
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"mc_new_chirp toggles again");
|
||||
check(chirp_count === 6'd2, "chirp_count incremented to 2");
|
||||
|
||||
// Toggle stm32_new_elevation (0→1, hold)
|
||||
saved_mc_new_elevation = mc_new_elevation;
|
||||
stm32_new_elevation = 1'b1;
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
|
||||
check(mc_new_elevation !== saved_mc_new_elevation,
|
||||
"mc_new_elevation toggles on stm32 elevation change");
|
||||
check(chirp_count === 6'd0,
|
||||
"chirp_count resets on elevation toggle");
|
||||
check(elevation_count === 6'd1,
|
||||
"elevation_count incremented to 1");
|
||||
|
||||
// Toggle stm32_new_azimuth (0→1, hold)
|
||||
saved_mc_new_azimuth = mc_new_azimuth;
|
||||
stm32_new_azimuth = 1'b1;
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
|
||||
check(mc_new_azimuth !== saved_mc_new_azimuth,
|
||||
"mc_new_azimuth toggles on stm32 azimuth change");
|
||||
check(elevation_count === 6'd0,
|
||||
"elevation_count resets on azimuth toggle");
|
||||
check(azimuth_count === 6'd1,
|
||||
"azimuth_count incremented to 1");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 3: Auto-scan mode (mode 01) — full scan
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 3: Auto-scan (mode 01) — Full Scan ---");
|
||||
apply_reset;
|
||||
mode = 2'b01;
|
||||
|
||||
csv_file = $fopen("rmc_autoscan.csv", "w");
|
||||
$fwrite(csv_file, "cycle,chirp,elevation,azimuth,long_chirp,scanning,scan_complete\n");
|
||||
|
||||
mc_new_chirp_prev = 0;
|
||||
mc_new_elevation_prev = 0;
|
||||
mc_new_azimuth_prev = 0;
|
||||
chirp_toggles = 0;
|
||||
elevation_toggles = 0;
|
||||
azimuth_toggles = 0;
|
||||
scan_completes = 0;
|
||||
|
||||
// Check: scanning starts immediately
|
||||
@(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Scanning starts immediately in auto mode");
|
||||
|
||||
// Run for enough cycles to complete one full scan
|
||||
for (i = 0; i < 15000; i = i + 1) begin
|
||||
@(posedge clk); #1;
|
||||
|
||||
if (mc_new_chirp !== mc_new_chirp_prev)
|
||||
chirp_toggles = chirp_toggles + 1;
|
||||
if (mc_new_elevation !== mc_new_elevation_prev)
|
||||
elevation_toggles = elevation_toggles + 1;
|
||||
if (mc_new_azimuth !== mc_new_azimuth_prev)
|
||||
azimuth_toggles = azimuth_toggles + 1;
|
||||
if (scan_complete)
|
||||
scan_completes = scan_completes + 1;
|
||||
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
mc_new_elevation_prev = mc_new_elevation;
|
||||
mc_new_azimuth_prev = mc_new_azimuth;
|
||||
|
||||
if (i % 100 == 0) begin
|
||||
$fwrite(csv_file, "%0d,%0d,%0d,%0d,%0d,%0d,%0d\n",
|
||||
i, chirp_count, elevation_count, azimuth_count,
|
||||
use_long_chirp, scanning, scan_complete);
|
||||
end
|
||||
end
|
||||
|
||||
$fclose(csv_file);
|
||||
|
||||
$display(" Chirp toggles: %0d (expected %0d)",
|
||||
chirp_toggles, SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS);
|
||||
$display(" Elevation toggles: %0d", elevation_toggles);
|
||||
$display(" Azimuth toggles: %0d", azimuth_toggles);
|
||||
$display(" Scan completes: %0d", scan_completes);
|
||||
|
||||
check(chirp_toggles >= SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS,
|
||||
"At least 24 chirp toggles in full scan");
|
||||
check(scan_completes >= 1,
|
||||
"At least 1 scan completion detected");
|
||||
check(elevation_toggles >= SIM_AZIMUTHS,
|
||||
"Elevation toggles >= number of azimuths");
|
||||
check(azimuth_toggles >= 1,
|
||||
"Azimuth toggles >= 1");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 4: Auto-scan chirp timing (3 km mode, short chirps only)
|
||||
// With range_mode=0, auto-scan skips long chirp and goes directly
|
||||
// to short chirp. No long→guard→short transition.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 4: Chirp Timing Sequence (3km short-only) ---");
|
||||
apply_reset;
|
||||
mode = 2'b01;
|
||||
|
||||
@(posedge clk); #1;
|
||||
check(use_long_chirp === 1'b0, "3km: starts with short chirp");
|
||||
|
||||
repeat (SIM_SHORT_CHIRP / 2) @(posedge clk);
|
||||
#1;
|
||||
check(use_long_chirp === 1'b0, "3km: still short chirp midway");
|
||||
|
||||
// Wait through short chirp + short listen
|
||||
repeat (SIM_SHORT_CHIRP / 2 + SIM_SHORT_LISTEN) @(posedge clk);
|
||||
#1;
|
||||
|
||||
// Next chirp should also be short
|
||||
repeat (2) @(posedge clk); #1;
|
||||
check(use_long_chirp === 1'b0, "3km: next chirp is also short");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 5: Single-chirp mode (mode 10)
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 5: Single-chirp Mode (mode 10) ---");
|
||||
apply_reset;
|
||||
mode = 2'b10;
|
||||
|
||||
repeat (10) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Single mode: idle without trigger");
|
||||
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
|
||||
// Pulse trigger (rising edge detection)
|
||||
trigger = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
trigger = 1'b0;
|
||||
|
||||
repeat (2) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Single mode: scanning after trigger");
|
||||
check(use_long_chirp === 1'b0, "Single mode: uses short chirp (3km)");
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"Single mode: mc_new_chirp toggled");
|
||||
|
||||
// Wait for chirp to complete (short chirp in 3km mode)
|
||||
repeat (SIM_SHORT_CHIRP + SIM_SHORT_LISTEN + 10) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Single mode: returns to idle after chirp");
|
||||
|
||||
// No activity without trigger
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
repeat (100) @(posedge clk); #1;
|
||||
check(mc_new_chirp === saved_mc_new_chirp,
|
||||
"Single mode: no activity without trigger");
|
||||
|
||||
// Second trigger
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
trigger = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
trigger = 1'b0;
|
||||
repeat (3) @(posedge clk); #1;
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"Single mode: 2nd trigger works");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 6: Reserved mode (mode 11) — stays idle
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 6: Reserved Mode (mode 11) ---");
|
||||
apply_reset;
|
||||
mode = 2'b11;
|
||||
|
||||
repeat (200) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Reserved mode: stays idle");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 7: Mode switching
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 7: Mode Switching ---");
|
||||
apply_reset;
|
||||
mode = 2'b01; // Auto-scan
|
||||
|
||||
repeat (100) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Auto mode: scanning");
|
||||
|
||||
mode = 2'b11;
|
||||
repeat (10) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Switching to reserved: stops scanning");
|
||||
|
||||
mode = 2'b10;
|
||||
repeat (10) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Single mode after switch: idle");
|
||||
|
||||
trigger = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
trigger = 1'b0;
|
||||
repeat (3) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Single mode after switch: triggers OK");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 8: STM32 mode — chirp count wrapping
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 8: STM32 Chirp Count Wrapping ---");
|
||||
apply_reset;
|
||||
mode = 2'b00;
|
||||
@(posedge clk); #1;
|
||||
|
||||
// Toggle chirp SIM_CHIRPS times (toggle-and-hold each time)
|
||||
for (i = 0; i < SIM_CHIRPS; i = i + 1) begin
|
||||
stm32_new_chirp = ~stm32_new_chirp; // toggle and hold
|
||||
@(posedge clk); @(posedge clk); #1; // wait for detection
|
||||
end
|
||||
|
||||
$display(" chirp_count after %0d toggles: %0d (expect 0)",
|
||||
SIM_CHIRPS, chirp_count);
|
||||
check(chirp_count === 6'd0,
|
||||
"chirp_count wraps after CHIRPS_PER_ELEVATION toggles");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 9: STM32 mode — full scan completion
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 9: STM32 Full Scan Completion ---");
|
||||
apply_reset;
|
||||
mode = 2'b00;
|
||||
@(posedge clk); #1;
|
||||
|
||||
scan_completes = 0;
|
||||
|
||||
// Toggle azimuth SIM_AZIMUTHS times
|
||||
for (i = 0; i < SIM_AZIMUTHS; i = i + 1) begin
|
||||
stm32_new_azimuth = ~stm32_new_azimuth;
|
||||
@(posedge clk); #1;
|
||||
if (scan_complete) scan_completes = scan_completes + 1;
|
||||
@(posedge clk); #1;
|
||||
if (scan_complete) scan_completes = scan_completes + 1;
|
||||
end
|
||||
|
||||
$display(" scan_complete pulses: %0d (expect 1)", scan_completes);
|
||||
check(scan_completes == 1, "scan_complete pulses once after full azimuth sweep");
|
||||
check(azimuth_count === 6'd0, "azimuth_count wraps to 0 after full scan");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 10: Reset Mid-Scan
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 10: Reset Mid-Scan ---");
|
||||
apply_reset;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
// Wait ~200 cycles (partway through first chirp)
|
||||
repeat (200) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Mid-scan: scanning=1 before reset");
|
||||
|
||||
// Assert reset for 4 cycles
|
||||
reset_n = 0;
|
||||
repeat (4) @(posedge clk); #1;
|
||||
|
||||
// Verify state during reset
|
||||
check(scanning === 1'b0, "Mid-scan reset: scanning=0");
|
||||
check(chirp_count === 6'd0, "Mid-scan reset: chirp_count=0");
|
||||
check(elevation_count === 6'd0, "Mid-scan reset: elevation_count=0");
|
||||
check(azimuth_count === 6'd0, "Mid-scan reset: azimuth_count=0");
|
||||
check(use_long_chirp === 1'b0, "Mid-scan reset: use_long_chirp=0");
|
||||
check(mc_new_chirp === 1'b0, "Mid-scan reset: mc_new_chirp=0");
|
||||
check(mc_new_elevation === 1'b0, "Mid-scan reset: mc_new_elevation=0");
|
||||
check(mc_new_azimuth === 1'b0, "Mid-scan reset: mc_new_azimuth=0");
|
||||
|
||||
// Release reset
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 11: Mode-Switch State Leakage
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 11: Mode-Switch State Leakage ---");
|
||||
apply_reset;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
// Run for ~500 cycles
|
||||
repeat (500) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Leakage: scanning=1 during auto-scan");
|
||||
|
||||
// Switch to reserved mode (11) — forces scan_state=S_IDLE
|
||||
mode = 2'b11;
|
||||
repeat (10) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Leakage: scanning=0 in reserved mode");
|
||||
|
||||
// Switch back to auto-scan (01)
|
||||
mode = 2'b01;
|
||||
// Auto-scan S_IDLE transitions to S_LONG_CHIRP on the next clock
|
||||
// so after 1 cycle scan_state != S_IDLE => scanning=1
|
||||
@(posedge clk); #1;
|
||||
// The first cycle in mode 01 hits S_IDLE and transitions out
|
||||
// scanning should be 1 now (scan_state moved to S_LONG_CHIRP)
|
||||
check(scanning === 1'b1, "Leakage: auto-scan restarts cleanly (scanning=1)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 12: Simultaneous STM32 Toggle Events
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 12: Simultaneous STM32 Toggle Events ---");
|
||||
apply_reset;
|
||||
mode = 2'b00;
|
||||
@(posedge clk); #1;
|
||||
|
||||
// Save current toggle outputs
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
saved_mc_new_elevation = mc_new_elevation;
|
||||
|
||||
// Toggle BOTH stm32_new_chirp AND stm32_new_elevation at the same time
|
||||
stm32_new_chirp = 1'b1;
|
||||
stm32_new_elevation = 1'b1;
|
||||
// Wait 2 cycles for XOR detection
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"Simultaneous: mc_new_chirp toggled");
|
||||
check(mc_new_elevation !== saved_mc_new_elevation,
|
||||
"Simultaneous: mc_new_elevation toggled");
|
||||
// Elevation toggle resets chirp_count (last-write-wins in RTL)
|
||||
check(chirp_count === 6'd0,
|
||||
"Simultaneous: chirp_count=0 (elevation resets it)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 13: Single-Chirp Mode — Multiple Rapid Triggers
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 13: Single-Chirp Multiple Rapid Triggers ---");
|
||||
apply_reset;
|
||||
mode = 2'b10;
|
||||
@(posedge clk); #1;
|
||||
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
|
||||
// First trigger — should start a chirp
|
||||
trigger = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
trigger = 1'b0;
|
||||
repeat (2) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Rapid trigger: first trigger starts chirp");
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"Rapid trigger: mc_new_chirp toggled on first trigger");
|
||||
|
||||
// Save chirp state after first trigger
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
|
||||
// Send another trigger while chirp is still active (FSM not in S_IDLE)
|
||||
trigger = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
trigger = 1'b0;
|
||||
repeat (2) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Rapid trigger: still scanning (didn't restart)");
|
||||
check(mc_new_chirp === saved_mc_new_chirp,
|
||||
"Rapid trigger: second trigger ignored (mc_new_chirp unchanged)");
|
||||
|
||||
// Wait for chirp to complete (short_chirp + short_listen for range_mode=0)
|
||||
repeat (SIM_SHORT_CHIRP + SIM_SHORT_LISTEN + 20) @(posedge clk); #1;
|
||||
check(scanning === 1'b0, "Rapid trigger: chirp completed, back to idle");
|
||||
|
||||
// Now trigger again — this should work
|
||||
saved_mc_new_chirp = mc_new_chirp;
|
||||
trigger = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
trigger = 1'b0;
|
||||
repeat (2) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Rapid trigger: third trigger works after idle");
|
||||
check(mc_new_chirp !== saved_mc_new_chirp,
|
||||
"Rapid trigger: mc_new_chirp toggled on third trigger");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 14: Auto-Scan Counter Verification
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 14: Auto-Scan Counter Verification ---");
|
||||
apply_reset;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
mc_new_chirp_prev = 0;
|
||||
chirp_toggles = 0;
|
||||
scan_completes = 0;
|
||||
|
||||
// The first chirp toggle happens on the S_IDLE→S_SHORT_CHIRP transition.
|
||||
// We need to capture it. Sample after the first posedge so we get the
|
||||
// initial state right.
|
||||
@(posedge clk); #1;
|
||||
// After this clock, scan_state has moved to S_LONG_CHIRP and
|
||||
// mc_new_chirp has already toggled once. Record its value as prev
|
||||
// so we can count from here.
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
chirp_toggles = 1; // count the initial toggle
|
||||
|
||||
// Run until first scan_complete
|
||||
// Total chirps = 4*3*2 = 24, each chirp ~523 cycles
|
||||
// 24*523 = 12552, add margin
|
||||
// NOTE: When scan_complete fires (S_ADVANCE full-scan branch), the DUT
|
||||
// simultaneously toggles mc_new_chirp for the NEXT scan's first chirp.
|
||||
// We must check scan_complete before counting the toggle so we don't
|
||||
// include that restart toggle in our count of the current scan's chirps.
|
||||
for (i = 0; i < 14000; i = i + 1) begin
|
||||
@(posedge clk); #1;
|
||||
if (scan_complete)
|
||||
scan_completes = scan_completes + 1;
|
||||
// Stop BEFORE counting the toggle that coincides with scan_complete
|
||||
// (that toggle starts the next scan, not the current one)
|
||||
if (scan_completes >= 1)
|
||||
i = 14000; // break
|
||||
else begin
|
||||
if (mc_new_chirp !== mc_new_chirp_prev)
|
||||
chirp_toggles = chirp_toggles + 1;
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
end
|
||||
end
|
||||
|
||||
$display(" Total chirp toggles: %0d (expected 24)", chirp_toggles);
|
||||
$display(" Scan completes: %0d (expected 1)", scan_completes);
|
||||
|
||||
// At scan_complete, the DUT wraps all counters and immediately starts
|
||||
// a new chirp (transitions to S_LONG_CHIRP, not S_IDLE). The counters
|
||||
// are reset to 0 in the full-scan-complete branch of S_ADVANCE.
|
||||
check(scan_completes == 1, "Counter verify: exactly 1 scan_complete");
|
||||
// The full-scan-complete branch resets all counters to 0:
|
||||
check(chirp_count === 6'd0, "Counter verify: chirp_count=0 at scan_complete");
|
||||
check(elevation_count === 6'd0, "Counter verify: elevation_count=0 at scan_complete");
|
||||
check(azimuth_count === 6'd0, "Counter verify: azimuth_count=0 at scan_complete");
|
||||
check(chirp_toggles == SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS,
|
||||
"Counter verify: exactly 24 chirp toggles");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 15: STM32 Mode — Counter Persistence
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 15: STM32 Mode Counter Persistence ---");
|
||||
apply_reset;
|
||||
mode = 2'b00;
|
||||
@(posedge clk); #1;
|
||||
|
||||
// Toggle chirp 3 times
|
||||
for (i = 0; i < 3; i = i + 1) begin
|
||||
stm32_new_chirp = ~stm32_new_chirp;
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
end
|
||||
|
||||
$display(" chirp_count after 3 toggles: %0d (expect 3)", chirp_count);
|
||||
check(chirp_count === 6'd3, "Persistence: chirp_count=3 after 3 toggles");
|
||||
|
||||
// Switch to reserved mode (11) — does NOT reset counters
|
||||
mode = 2'b11;
|
||||
repeat (10) @(posedge clk); #1;
|
||||
|
||||
$display(" chirp_count in reserved mode: %0d (expect 3)", chirp_count);
|
||||
check(chirp_count === 6'd3, "Persistence: chirp_count=3 in reserved mode");
|
||||
|
||||
// Switch back to STM32 mode (00)
|
||||
mode = 2'b00;
|
||||
@(posedge clk); #1;
|
||||
|
||||
$display(" chirp_count after returning to STM32: %0d (expect 3)", chirp_count);
|
||||
check(chirp_count === 6'd3, "Persistence: chirp_count=3 after mode roundtrip");
|
||||
|
||||
// Toggle chirp once more — should wrap (3+1=4=CHIRPS, wraps to 0)
|
||||
stm32_new_chirp = ~stm32_new_chirp;
|
||||
@(posedge clk); @(posedge clk); #1;
|
||||
|
||||
$display(" chirp_count after 4th toggle: %0d (expect 0)", chirp_count);
|
||||
check(chirp_count === 6'd0, "Persistence: chirp_count wraps to 0 at 4th toggle");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 16: Runtime Timing Reconfiguration (Gap 2)
|
||||
// Verify that changing cfg_* mid-simulation changes timing.
|
||||
// We halve the long chirp duration and verify the chirp
|
||||
// completes in fewer cycles.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 16: Runtime Timing Reconfiguration (Gap 2) ---");
|
||||
apply_reset;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
// Let the first chirp start (S_IDLE -> S_SHORT_CHIRP in 3km mode)
|
||||
@(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Reconfig: auto-scan started");
|
||||
check(use_long_chirp === 1'b0, "Reconfig: starts with short chirp (3km)");
|
||||
|
||||
// Wait ~half the default short chirp time to confirm we're still in S_SHORT_CHIRP
|
||||
// S_SHORT_CHIRP state index: check via scan_state
|
||||
repeat (SIM_SHORT_CHIRP / 2) @(posedge clk); #1;
|
||||
|
||||
// Now change cfg_short_chirp_cycles to a much shorter value mid-scan.
|
||||
// The timer is already at ~SIM_SHORT_CHIRP/2, so setting cycles to 1
|
||||
// means the FSM will advance on the next cycle.
|
||||
cfg_short_chirp_cycles = 1;
|
||||
repeat (2) @(posedge clk); #1;
|
||||
|
||||
// Restore default and verify scan continues
|
||||
cfg_short_chirp_cycles = SIM_SHORT_CHIRP;
|
||||
repeat (10) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Reconfig: scan continues after restoring default");
|
||||
|
||||
// Test runtime chirps_per_elev change:
|
||||
// Reset and set chirps_per_elev to 2 (instead of default 4)
|
||||
apply_reset;
|
||||
cfg_chirps_per_elev = 6'd2;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
mc_new_chirp_prev = 0;
|
||||
chirp_toggles = 0;
|
||||
elevation_toggles = 0;
|
||||
|
||||
@(posedge clk); #1;
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
mc_new_elevation_prev = mc_new_elevation;
|
||||
chirp_toggles = 1; // initial toggle
|
||||
|
||||
// Run enough cycles for a few chirps + elevation advance
|
||||
// With 2 chirps/elev: each chirp ~180 cycles (5+175) for short-only 3km mode
|
||||
// 2 chirps = ~684 cycles, then elevation advance
|
||||
for (i = 0; i < 2000; i = i + 1) begin
|
||||
@(posedge clk); #1;
|
||||
if (mc_new_chirp !== mc_new_chirp_prev)
|
||||
chirp_toggles = chirp_toggles + 1;
|
||||
if (mc_new_elevation !== mc_new_elevation_prev)
|
||||
elevation_toggles = elevation_toggles + 1;
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
mc_new_elevation_prev = mc_new_elevation;
|
||||
end
|
||||
|
||||
$display(" chirp_toggles=%0d elevation_toggles=%0d (cfg_chirps_per_elev=2)",
|
||||
chirp_toggles, elevation_toggles);
|
||||
// With 2 chirps/elev, we should get elevation toggles at every 2 chirps
|
||||
check(elevation_toggles >= 1,
|
||||
"Reconfig: elevation advances with cfg_chirps_per_elev=2");
|
||||
// Verify the ratio: chirp_toggles should be ~2x elevation_toggles
|
||||
// (first elevation has 2 chirps, then toggle. Second has 2 chirps, then toggle, etc.)
|
||||
check(chirp_toggles >= 2 * elevation_toggles,
|
||||
"Reconfig: chirp/elevation ratio consistent with cfg_chirps_per_elev=2");
|
||||
|
||||
// Restore defaults
|
||||
cfg_chirps_per_elev = SIM_CHIRPS;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Summary
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("");
|
||||
$display("========================================");
|
||||
$display(" RADAR MODE CONTROLLER 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
|
||||
@@ -41,7 +41,7 @@
|
||||
//
|
||||
// Strategy:
|
||||
// - Uses behavioral stub for ad9484_interface_400m (no Xilinx primitives)
|
||||
// - Overrides radar_mode_controller timing params for fast simulation
|
||||
// - Drives chirp_scheduler timing via host_* inputs for fast simulation
|
||||
// - Feeds 120 MHz tone at ADC input (IF frequency -> DDC passband)
|
||||
// - Verifies structural correctness + golden comparison + bounds checks
|
||||
//
|
||||
@@ -103,6 +103,9 @@ reg mc_new_chirp_prev;
|
||||
reg tx_frame_start;
|
||||
reg [5:0] rmc_chirp_prev;
|
||||
|
||||
// chirp-v2 PR-D: chirp_scheduler emits chirp_pulse (1-cycle pulse) and
|
||||
// sched_chirp_counter directly. mc_new_chirp toggle / rmc_chirp_count are
|
||||
// gone. The probe just rides those pulses to drive the TB-side counters.
|
||||
always @(posedge clk_100m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
chirp_counter <= 6'd0;
|
||||
@@ -110,17 +113,16 @@ always @(posedge clk_100m or negedge reset_n) begin
|
||||
tx_frame_start <= 1'b0;
|
||||
rmc_chirp_prev <= 6'd0;
|
||||
end else begin
|
||||
mc_new_chirp_prev <= dut.mc_new_chirp;
|
||||
if (dut.mc_new_chirp != mc_new_chirp_prev) begin
|
||||
if (dut.chirp_pulse) begin
|
||||
chirp_counter <= chirp_counter + 1;
|
||||
end
|
||||
|
||||
// Detect when the internal mode controller's chirp_count wraps to 0
|
||||
|
||||
// Detect when the scheduler's chirp_counter wraps to 0
|
||||
tx_frame_start <= 1'b0;
|
||||
if (dut.rmc_chirp_count == 6'd0 && rmc_chirp_prev != 6'd0) begin
|
||||
if (dut.sched_chirp_counter == 6'd0 && rmc_chirp_prev != 6'd0) begin
|
||||
tx_frame_start <= 1'b1;
|
||||
end
|
||||
rmc_chirp_prev <= dut.rmc_chirp_count;
|
||||
rmc_chirp_prev <= dut.sched_chirp_counter;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,13 +165,16 @@ radar_receiver_final dut (
|
||||
.host_range_mode(2'b01), // long-range mode (dual chirp); was missing -> z
|
||||
.host_trigger(1'b0),
|
||||
|
||||
// Gap 2: Host-configurable chirp timing — match defparam overrides below
|
||||
// chirp-v2 PR-D: chirp_scheduler is host-input driven. SHORT chirp bumped
|
||||
// to 100 cycles (1 µs V2). Host_chirps_per_elev is still wired to keep
|
||||
// the parent port list intact, but the scheduler inside the receiver
|
||||
// pins chirps_per_subframe to RP_DEF (16) — PR-G renames the host reg.
|
||||
.host_long_chirp_cycles(16'd500),
|
||||
.host_long_listen_cycles(16'd2000),
|
||||
.host_guard_cycles(16'd500),
|
||||
.host_short_chirp_cycles(16'd50),
|
||||
.host_short_chirp_cycles(16'd100),
|
||||
.host_short_listen_cycles(16'd1000),
|
||||
.host_chirps_per_elev(6'd32),
|
||||
.host_chirps_per_elev(6'd16),
|
||||
|
||||
// Fix 3: digital gain control — pass-through for golden reference
|
||||
.host_gain_shift(4'd0),
|
||||
@@ -180,19 +185,13 @@ radar_receiver_final dut (
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// OVERRIDE TIMING PARAMETERS via defparam
|
||||
// ============================================================================
|
||||
// Reduce radar_mode_controller timing to keep simulation tractable.
|
||||
// SIM TIMING — driven via host_* inputs above (chirp-v2 PR-D).
|
||||
// chirp_scheduler is host-input driven; no defparam overrides needed.
|
||||
// Real values: LONG_CHIRP=3000, LONG_LISTEN=13700, GUARD=17540,
|
||||
// SHORT_CHIRP=50, SHORT_LISTEN=17450 (total ~51740 per chirp)
|
||||
// Need enough DDC samples to fill MF buffer (896) plus latency buffer (3187).
|
||||
// At ~1 DDC sample per sys_clk, we need at least ~5000 sys_clk per chirp.
|
||||
// Use moderately reduced values: ~5000 cycles per chirp pair
|
||||
defparam dut.rmc.LONG_CHIRP_CYCLES = 500;
|
||||
defparam dut.rmc.LONG_LISTEN_CYCLES = 2000;
|
||||
defparam dut.rmc.GUARD_CYCLES = 500;
|
||||
defparam dut.rmc.SHORT_CHIRP_CYCLES = 50;
|
||||
defparam dut.rmc.SHORT_LISTEN_CYCLES = 1000;
|
||||
// SHORT_CHIRP=100 (V2), SHORT_LISTEN=17400 (~31040 per chirp).
|
||||
// The host_* assignments above feed the same compressed timing the legacy
|
||||
// defparams used.
|
||||
// ============================================================================
|
||||
|
||||
// ============================================================================
|
||||
// TEST INFRASTRUCTURE
|
||||
|
||||
@@ -34,15 +34,13 @@ module tb_rxb_fullchain_latency;
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
|
||||
// multi_segment inputs
|
||||
// multi_segment inputs (chirp-v2 PR-D wave_sel + chirp_pulse contract)
|
||||
reg signed [17:0] ddc_i;
|
||||
reg signed [17:0] ddc_q;
|
||||
reg ddc_valid;
|
||||
reg use_long_chirp;
|
||||
reg [1:0] wave_sel_r; // SHORT/MEDIUM/LONG selector
|
||||
reg [5:0] chirp_counter;
|
||||
reg mc_new_chirp;
|
||||
reg mc_new_elevation;
|
||||
reg mc_new_azimuth;
|
||||
reg chirp_pulse; // 1-cycle pulse on chirp start
|
||||
|
||||
// multi_segment <-> chirp_reference_rom interconnect
|
||||
wire [1:0] segment_request;
|
||||
@@ -60,8 +58,9 @@ module tb_rxb_fullchain_latency;
|
||||
wire pc_valid;
|
||||
wire [3:0] ms_status;
|
||||
|
||||
// wave_sel shim — matches radar_receiver_final.v PR-C transitional wiring.
|
||||
wire [1:0] wave_sel = use_long_chirp ? `RP_WAVE_LONG : `RP_WAVE_SHORT;
|
||||
// wave_sel drives both the ROM and the matched filter (chirp-v2 PR-D
|
||||
// contract — no use_long_chirp shim).
|
||||
wire [1:0] wave_sel = wave_sel_r;
|
||||
|
||||
// ----- Chirp reference ROM (chirp-v2 PR-C) -----
|
||||
chirp_reference_rom chirp_rom (
|
||||
@@ -97,11 +96,9 @@ module tb_rxb_fullchain_latency;
|
||||
.ddc_i (ddc_i),
|
||||
.ddc_q (ddc_q),
|
||||
.ddc_valid (ddc_valid),
|
||||
.use_long_chirp (use_long_chirp),
|
||||
.wave_sel (wave_sel),
|
||||
.chirp_counter (chirp_counter),
|
||||
.mc_new_chirp (mc_new_chirp),
|
||||
.mc_new_elevation (mc_new_elevation),
|
||||
.mc_new_azimuth (mc_new_azimuth),
|
||||
.chirp_pulse (chirp_pulse),
|
||||
.ref_chirp_real (ref_i_d),
|
||||
.ref_chirp_imag (ref_q_d),
|
||||
.segment_request (segment_request),
|
||||
@@ -225,11 +222,9 @@ module tb_rxb_fullchain_latency;
|
||||
ddc_i = 0;
|
||||
ddc_q = 0;
|
||||
ddc_valid = 0;
|
||||
use_long_chirp = 1'b0; // use SHORT chirp path so loader uses short_chirp_*.mem
|
||||
wave_sel_r = `RP_WAVE_SHORT; // SHORT path → rx_short_*.mem
|
||||
chirp_counter = 6'd0;
|
||||
mc_new_chirp = 1'b0;
|
||||
mc_new_elevation = 1'b0;
|
||||
mc_new_azimuth = 1'b0;
|
||||
chirp_pulse = 1'b0;
|
||||
|
||||
// Load the same short-chirp samples the ROM will serve as ref,
|
||||
// so signal == ref → autocorrelation. Peak should be at bin 0 if
|
||||
@@ -248,12 +243,12 @@ module tb_rxb_fullchain_latency;
|
||||
$display("FFT_SIZE: %0d, SHORT_LEN: %0d", FFT_SIZE, SHORT_LEN);
|
||||
$display("");
|
||||
|
||||
// Pulse mc_new_chirp
|
||||
$display("[T=%0t] Pulsing mc_new_chirp HIGH...", $time);
|
||||
// Pulse chirp_pulse for one cycle (chirp-v2 PR-D contract)
|
||||
$display("[T=%0t] Pulsing chirp_pulse HIGH...", $time);
|
||||
@(posedge clk);
|
||||
#1 mc_new_chirp = 1'b1;
|
||||
repeat (4) @(posedge clk);
|
||||
#1 mc_new_chirp = 1'b0;
|
||||
#1 chirp_pulse = 1'b1;
|
||||
@(posedge clk);
|
||||
#1 chirp_pulse = 1'b0;
|
||||
|
||||
// Feed signal samples (same as ref → autocorrelation)
|
||||
feed_short_chirp_signal;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
* chirp_reference_rom.v \
|
||||
* matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||
* range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
|
||||
* usb_data_interface.v edge_detector.v radar_mode_controller.v
|
||||
* usb_data_interface.v edge_detector.v chirp_scheduler.v
|
||||
*
|
||||
* Run:
|
||||
* vvp tb/tb_system_e2e.vvp
|
||||
@@ -873,14 +873,16 @@ initial begin
|
||||
check(obs_range_valid_count > saved_range_count,
|
||||
"G8.1: Auto-scan generated range profile output autonomously");
|
||||
|
||||
// G8.2: Receiver mode controller chirp counter advanced
|
||||
// Access the RX-side mode controller chirp count directly.
|
||||
check(dut.rx_inst.rmc_chirp_count > 0 || dut.rx_inst.rmc_elevation_count > 0,
|
||||
"G8.2: RX mode controller chirp/elevation counters advanced");
|
||||
// G8.2: chirp_scheduler chirp counter advanced (chirp-v2 PR-D)
|
||||
// Sub-frame id replaces "elevation" in the v2 contract.
|
||||
check(dut.rx_inst.sched_chirp_counter > 0 || dut.rx_inst.sched_subframe_id > 0,
|
||||
"G8.2: chirp_scheduler chirp/sub-frame counters advanced");
|
||||
|
||||
// G8.3: RX-side elevation counter incremented (4 chirps/elev)
|
||||
check(dut.rx_inst.rmc_elevation_count >= 1,
|
||||
"G8.3: RX elevation counter incremented in auto-scan");
|
||||
// G8.3: sub-frame index incremented (auto-scan walks SHORT->MEDIUM->LONG).
|
||||
// chirps_per_subframe = 16 in PR-D, so a sub-frame transition implies the
|
||||
// first 16 chirps completed.
|
||||
check(dut.rx_inst.sched_subframe_id >= 1 || dut.rx_inst.sched_chirp_counter >= 6'd1,
|
||||
"G8.3: sub-frame counter or chirp counter advanced in auto-scan");
|
||||
|
||||
// G8.4: Switch to single-chirp mode — auto-scan stops
|
||||
bfm_send_cmd(8'h01, 8'h00, 16'h0002); // mode = 10 = single chirp
|
||||
|
||||
Reference in New Issue
Block a user