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:
Jason
2026-04-30 20:52:32 +05:45
parent 4238eb1b99
commit 8e8f3e60c4
15 changed files with 666 additions and 1718 deletions

View File

@@ -0,0 +1,440 @@
`timescale 1ns / 1ps
`include "radar_params.vh"
/**
* chirp_scheduler.v (chirp-v2 PR-D, replaces radar_mode_controller.v)
*
* Single source of truth for waveform identity and inter-chirp timing on the
* RX side. Drives `wave_sel[1:0]` and `chirp_pulse` natively; downstream
* modules (chirp_reference_rom, matched_filter_multi_segment, mti_canceller)
* consume those without 1-bit shims.
*
* Operating modes (host_radar_mode, opcode 0x01):
* 2'b00 STM32 pass-through STM32 owns chirp timing; we follow stm32_*
* toggles and announce the wave_sel that matches
* the current sub-frame index.
* 2'b01 Auto-scan Internal FSM cycles SHORT, MEDIUM, LONG sub-
* frames in order (host_subframe_enable masks
* individual waveforms out without recompiling).
* Each sub-frame fires `host_chirps_per_subframe`
* chirps at the per-waveform timing.
* 2'b10 Single-chirp debug One chirp per host_trigger pulse, waveform
* from host_debug_wave_sel.
* 2'b11 Track Host-cued dwell on one beam + one waveform
* for host_track_chirp_count chirps. A watchdog
* falls back to mode 01 after
* RP_DEF_TRACK_WATCHDOG_FRAMES idle frames so a
* USB-yank does not silently drop coverage.
*
* Pulse outputs (chirp_pulse, subframe_pulse, frame_pulse) are 1-cycle
* positive pulses, not toggles. The legacy mc_new_*-style toggles are gone.
*
* Clock domain: clk (100 MHz), async-low reset.
*/
module chirp_scheduler (
input wire clk,
input wire reset_n,
// Top-level mode and 3-bit sub-frame enable mask (LONG|MEDIUM|SHORT)
input wire [1:0] host_mode,
input wire [2:0] host_subframe_enable,
// 3-ladder timing (100 MHz cycles). host_*_listen sums with host_guard
// to define the inter-chirp PRI. Each waveform has independent chirp/
// listen so SHORT can run faster while LONG covers full eclipse.
input wire [15:0] host_short_chirp_cycles,
input wire [15:0] host_short_listen_cycles,
input wire [15:0] host_medium_chirp_cycles,
input wire [15:0] host_medium_listen_cycles,
input wire [15:0] host_long_chirp_cycles,
input wire [15:0] host_long_listen_cycles,
input wire [15:0] host_guard_cycles,
// Frame structure (chirps per Doppler sub-frame, default 16)
input wire [5:0] host_chirps_per_subframe,
// Single-chirp debug (mode 10)
input wire host_trigger,
input wire [1:0] host_debug_wave_sel,
// Track mode (mode 11)
input wire host_track_request,
input wire [1:0] host_track_wave_sel,
input wire [8:0] host_track_chirp_count,
input wire [5:0] host_track_beam_az,
input wire [5:0] host_track_beam_el,
// STM32 pass-through (mode 00) toggle inputs (CDC-synced upstream)
input wire stm32_new_chirp,
input wire stm32_new_subframe,
input wire stm32_new_frame,
// ====== Outputs ======
output reg [1:0] wave_sel, // canonical waveform identity
output reg chirp_pulse, // 1-cycle pulse: chirp begins this clk
output reg subframe_pulse, // 1-cycle pulse: sub-frame complete
output reg frame_pulse, // 1-cycle pulse: frame complete
output reg [5:0] chirp_counter, // chirp index inside current frame
output reg [1:0] subframe_id, // 0=SHORT, 1=MEDIUM, 2=LONG
// Currently selected timing for the in-flight chirp (PR-E TX async FIFO)
output wire [15:0] cfg_chirp_cycles,
output wire [15:0] cfg_listen_cycles,
output wire [15:0] cfg_guard_cycles,
// Track-mode beam pointer (latched on host_track_request rising edge)
output reg track_mode_active,
output reg [5:0] track_beam_az,
output reg [5:0] track_beam_el
);
// ============================================================================
// Edge / pulse detection on async inputs
// ============================================================================
reg trigger_prev;
reg track_request_prev;
reg stm32_new_chirp_prev;
reg stm32_new_subframe_prev;
reg stm32_new_frame_prev;
wire trigger_pulse = host_trigger & ~trigger_prev;
wire track_request_pulse = host_track_request & ~track_request_prev;
wire stm32_chirp_edge = stm32_new_chirp ^ stm32_new_chirp_prev;
wire stm32_subframe_edge = stm32_new_subframe ^ stm32_new_subframe_prev;
wire stm32_frame_edge = stm32_new_frame ^ stm32_new_frame_prev;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
trigger_prev <= 1'b0;
track_request_prev <= 1'b0;
stm32_new_chirp_prev <= 1'b0;
stm32_new_subframe_prev <= 1'b0;
stm32_new_frame_prev <= 1'b0;
end else begin
trigger_prev <= host_trigger;
track_request_prev <= host_track_request;
stm32_new_chirp_prev <= stm32_new_chirp;
stm32_new_subframe_prev <= stm32_new_subframe;
stm32_new_frame_prev <= stm32_new_frame;
end
end
// ============================================================================
// Sub-frame helpers pure functions of (subframe, mask)
// ============================================================================
function [1:0] first_enabled_subframe;
input [2:0] mask;
begin
if (mask[0]) first_enabled_subframe = 2'd0; // SHORT
else if (mask[1]) first_enabled_subframe = 2'd1; // MEDIUM
else if (mask[2]) first_enabled_subframe = 2'd2; // LONG
else first_enabled_subframe = 2'd0; // mask=000 fallback
end
endfunction
function [1:0] next_enabled_subframe;
input [1:0] cur;
input [2:0] mask;
reg [1:0] try0, try1, try2;
begin
// Walk forward from cur+1, wrapping at 3, find first enabled bit.
try0 = (cur == 2'd2) ? 2'd0 : (cur + 2'd1);
try1 = (try0 == 2'd2) ? 2'd0 : (try0 + 2'd1);
try2 = (try1 == 2'd2) ? 2'd0 : (try1 + 2'd1);
if (mask[try0]) next_enabled_subframe = try0;
else if (mask[try1]) next_enabled_subframe = try1;
else if (mask[try2]) next_enabled_subframe = try2;
else next_enabled_subframe = cur; // mask=000 fallback
end
endfunction
function [1:0] subframe_to_wave;
input [1:0] sf;
begin
case (sf)
2'd0: subframe_to_wave = `RP_WAVE_SHORT;
2'd1: subframe_to_wave = `RP_WAVE_MEDIUM;
2'd2: subframe_to_wave = `RP_WAVE_LONG;
default: subframe_to_wave = `RP_WAVE_SHORT;
endcase
end
endfunction
// ============================================================================
// Track watchdog count frames since last host_track_request rising edge.
// effective_mode collapses to scan once the watchdog expires so a USB stall
// does not silently freeze coverage on one beam.
// ============================================================================
reg [7:0] track_idle_frames;
wire watchdog_expired = (track_idle_frames >= `RP_DEF_TRACK_WATCHDOG_FRAMES);
wire [1:0] effective_mode = (host_mode == `RP_MODE_TRACK && watchdog_expired)
? `RP_MODE_AUTO_3KM
: host_mode;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
track_idle_frames <= 8'd0;
end else if (track_request_pulse) begin
track_idle_frames <= 8'd0;
end else if (frame_pulse && track_idle_frames != 8'hFF) begin
track_idle_frames <= track_idle_frames + 8'd1;
end
end
// Latch beam pointer at the start of every track dwell.
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
track_beam_az <= 6'd0;
track_beam_el <= 6'd0;
end else if (track_request_pulse) begin
track_beam_az <= host_track_beam_az;
track_beam_el <= host_track_beam_el;
end
end
// ============================================================================
// Output mux for selected timing wave_sel drives chirp/listen window length.
// guard is shared across waveforms.
// ============================================================================
reg [15:0] sel_chirp_cycles;
reg [15:0] sel_listen_cycles;
always @(*) begin
case (wave_sel)
`RP_WAVE_SHORT: begin
sel_chirp_cycles = host_short_chirp_cycles;
sel_listen_cycles = host_short_listen_cycles;
end
`RP_WAVE_MEDIUM: begin
sel_chirp_cycles = host_medium_chirp_cycles;
sel_listen_cycles = host_medium_listen_cycles;
end
`RP_WAVE_LONG: begin
sel_chirp_cycles = host_long_chirp_cycles;
sel_listen_cycles = host_long_listen_cycles;
end
default: begin
sel_chirp_cycles = host_short_chirp_cycles;
sel_listen_cycles = host_short_listen_cycles;
end
endcase
end
assign cfg_chirp_cycles = sel_chirp_cycles;
assign cfg_listen_cycles = sel_listen_cycles;
assign cfg_guard_cycles = host_guard_cycles;
// ============================================================================
// Main FSM
// ============================================================================
localparam S_IDLE = 3'd0;
localparam S_CHIRP = 3'd1;
localparam S_LISTEN = 3'd2;
localparam S_GUARD = 3'd3;
localparam S_ADVANCE = 3'd4;
reg [2:0] state;
reg [16:0] timer; // 17 bits cover LONG+listen+guard worst case
reg [5:0] track_remaining; // saturated copy of host_track_chirp_count
// Pre-computed wires used inside the FSM advance logic so non-blocking
// updates to subframe_id / wave_sel see the correct next value in the same
// clock edge as the bookkeeping update.
wire [1:0] first_sf = first_enabled_subframe(host_subframe_enable);
wire [1:0] next_sf = next_enabled_subframe(subframe_id, host_subframe_enable);
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
state <= S_IDLE;
timer <= 17'd0;
wave_sel <= `RP_WAVE_SHORT;
chirp_pulse <= 1'b0;
subframe_pulse <= 1'b0;
frame_pulse <= 1'b0;
chirp_counter <= 6'd0;
subframe_id <= 2'd0;
track_mode_active <= 1'b0;
track_remaining <= 6'd0;
end else begin
// Pulses default low — set high for one cycle on relevant transitions.
chirp_pulse <= 1'b0;
subframe_pulse <= 1'b0;
frame_pulse <= 1'b0;
case (effective_mode)
// --------------------------------------------------------------------
// MODE 00 — STM32 pass-through. STM32 owns chirp timing; we walk
// sub-frames in step with stm32_chirp_edge so wave_sel always matches
// the chirp the firmware just fired.
// --------------------------------------------------------------------
`RP_MODE_STM32_PASSTHROUGH: begin
state <= S_IDLE;
timer <= 17'd0;
track_mode_active <= 1'b0;
if (stm32_chirp_edge) begin
chirp_pulse <= 1'b1;
if (chirp_counter < host_chirps_per_subframe - 6'd1) begin
chirp_counter <= chirp_counter + 6'd1;
end else begin
chirp_counter <= 6'd0;
subframe_pulse <= 1'b1;
subframe_id <= next_sf;
wave_sel <= subframe_to_wave(next_sf);
if (next_sf == first_sf)
frame_pulse <= 1'b1;
end
end
// STM32 firmware can pulse subframe/frame toggles directly when it
// wants to force-advance (e.g. abort current sub-frame). These
// override the chirp-driven walk above.
if (stm32_subframe_edge) subframe_pulse <= 1'b1;
if (stm32_frame_edge) frame_pulse <= 1'b1;
end
// --------------------------------------------------------------------
// MODE 01 — Auto-scan over enabled sub-frames.
// --------------------------------------------------------------------
`RP_MODE_AUTO_3KM: begin
track_mode_active <= 1'b0;
case (state)
S_IDLE: begin
timer <= 17'd0;
chirp_counter <= 6'd0;
subframe_id <= first_sf;
wave_sel <= subframe_to_wave(first_sf);
chirp_pulse <= 1'b1;
state <= S_CHIRP;
end
S_CHIRP: begin
if (timer + 17'd1 >= {1'b0, sel_chirp_cycles}) begin
timer <= 17'd0;
state <= S_LISTEN;
end else timer <= timer + 17'd1;
end
S_LISTEN: begin
if (timer + 17'd1 >= {1'b0, sel_listen_cycles}) begin
timer <= 17'd0;
state <= S_GUARD;
end else timer <= timer + 17'd1;
end
S_GUARD: begin
if (timer + 17'd1 >= {1'b0, host_guard_cycles}) begin
timer <= 17'd0;
state <= S_ADVANCE;
end else timer <= timer + 17'd1;
end
S_ADVANCE: begin
if (chirp_counter < host_chirps_per_subframe - 6'd1) begin
chirp_counter <= chirp_counter + 6'd1;
chirp_pulse <= 1'b1;
state <= S_CHIRP;
end else begin
chirp_counter <= 6'd0;
subframe_pulse <= 1'b1;
subframe_id <= next_sf;
wave_sel <= subframe_to_wave(next_sf);
if (next_sf == first_sf)
frame_pulse <= 1'b1;
chirp_pulse <= 1'b1;
state <= S_CHIRP;
end
end
default: state <= S_IDLE;
endcase
end
// --------------------------------------------------------------------
// MODE 10 — Single-chirp debug. One chirp per host_trigger.
// --------------------------------------------------------------------
`RP_MODE_SINGLE_DEBUG: begin
track_mode_active <= 1'b0;
case (state)
S_IDLE: begin
timer <= 17'd0;
if (trigger_pulse) begin
wave_sel <= host_debug_wave_sel;
chirp_pulse <= 1'b1;
state <= S_CHIRP;
end
end
S_CHIRP: begin
if (timer + 17'd1 >= {1'b0, sel_chirp_cycles}) begin
timer <= 17'd0;
state <= S_LISTEN;
end else timer <= timer + 17'd1;
end
S_LISTEN: begin
if (timer + 17'd1 >= {1'b0, sel_listen_cycles}) begin
timer <= 17'd0;
state <= S_IDLE;
end else timer <= timer + 17'd1;
end
default: state <= S_IDLE;
endcase
end
// --------------------------------------------------------------------
// MODE 11 — Track dwell. Watchdog fallback handled by effective_mode.
// --------------------------------------------------------------------
`RP_MODE_TRACK: begin
track_mode_active <= 1'b1;
case (state)
S_IDLE: begin
timer <= 17'd0;
if (track_request_pulse) begin
wave_sel <= host_track_wave_sel;
// chirp_counter is 6 bits; clip the dwell length to
// avoid wrapping inside a single dwell.
track_remaining <= (host_track_chirp_count > 9'd63)
? 6'd63
: host_track_chirp_count[5:0];
chirp_counter <= 6'd0;
chirp_pulse <= 1'b1;
state <= S_CHIRP;
end
end
S_CHIRP: begin
if (timer + 17'd1 >= {1'b0, sel_chirp_cycles}) begin
timer <= 17'd0;
state <= S_LISTEN;
end else timer <= timer + 17'd1;
end
S_LISTEN: begin
if (timer + 17'd1 >= {1'b0, sel_listen_cycles}) begin
timer <= 17'd0;
state <= S_GUARD;
end else timer <= timer + 17'd1;
end
S_GUARD: begin
if (timer + 17'd1 >= {1'b0, host_guard_cycles}) begin
timer <= 17'd0;
state <= S_ADVANCE;
end else timer <= timer + 17'd1;
end
S_ADVANCE: begin
if (chirp_counter < track_remaining) begin
chirp_counter <= chirp_counter + 6'd1;
chirp_pulse <= 1'b1;
state <= S_CHIRP;
end else begin
// Dwell complete = one track frame. Watchdog ticks
// here on every dwell; host re-pulsing track_request
// resets it.
frame_pulse <= 1'b1;
chirp_counter <= 6'd0;
state <= S_IDLE;
end
end
default: state <= S_IDLE;
endcase
end
endcase
end
end
endmodule

View File

@@ -1,232 +0,0 @@
`timescale 1ns / 1ps
// ============================================================================
// Formal Verification Wrapper: radar_mode_controller
// AERIS-10 Radar FPGA 7-state beam scan FSM
// Target: SymbiYosys with smtbmc/z3
//
// Single-clock design: clk is an input wire, async2sync handles async reset.
// Each formal step = one clock edge.
//
// Timer parameters reduced to small values to keep BMC tractable.
// ============================================================================
module fv_radar_mode_controller (
input wire clk
);
// Reduced parameters for tractable BMC
localparam LONG_CHIRP_CYCLES = 5;
localparam LONG_LISTEN_CYCLES = 5;
localparam GUARD_CYCLES = 5;
localparam SHORT_CHIRP_CYCLES = 3;
localparam SHORT_LISTEN_CYCLES = 3;
localparam CHIRPS_PER_ELEVATION = 3;
localparam ELEVATIONS_PER_AZIMUTH = 2;
localparam AZIMUTHS_PER_SCAN = 2;
// Maximum timer value across all phases
localparam MAX_TIMER = LONG_CHIRP_CYCLES; // 5 (largest)
// State encoding (mirrors DUT localparams)
localparam S_IDLE = 3'd0;
localparam S_LONG_CHIRP = 3'd1;
localparam S_LONG_LISTEN = 3'd2;
localparam S_GUARD = 3'd3;
localparam S_SHORT_CHIRP = 3'd4;
localparam S_SHORT_LISTEN = 3'd5;
localparam S_ADVANCE = 3'd6;
`ifdef FORMAL
// ================================================================
// Clock is an input wire smtbmc drives it automatically.
// async2sync (in .sby, default) converts async reset to sync.
// ================================================================
// ================================================================
// Past-valid tracker (for guarding $past usage)
// ================================================================
reg f_past_valid;
initial f_past_valid = 1'b0;
always @(posedge clk) f_past_valid <= 1'b1;
// ================================================================
// Reset: asserted (low) for cycle 0, deasserted from cycle 1
// ================================================================
reg reset_n;
initial reset_n = 1'b0;
always @(posedge clk) reset_n <= 1'b1;
// ================================================================
// DUT inputs solver-driven each cycle
// ================================================================
(* anyseq *) wire [1:0] mode;
(* anyseq *) wire stm32_new_chirp;
(* anyseq *) wire stm32_new_elevation;
(* anyseq *) wire stm32_new_azimuth;
(* anyseq *) wire trigger;
// Runtime config inputs are pinned to the reduced localparams so this
// wrapper proves one tractable configuration. It does not sweep the full
// runtime-configurable cfg_* space.
wire [15:0] cfg_long_chirp_cycles = LONG_CHIRP_CYCLES;
wire [15:0] cfg_long_listen_cycles = LONG_LISTEN_CYCLES;
wire [15:0] cfg_guard_cycles = GUARD_CYCLES;
wire [15:0] cfg_short_chirp_cycles = SHORT_CHIRP_CYCLES;
wire [15:0] cfg_short_listen_cycles = SHORT_LISTEN_CYCLES;
wire [5:0] cfg_chirps_per_elev = CHIRPS_PER_ELEVATION;
// ================================================================
// DUT outputs
// ================================================================
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;
wire [2:0] scan_state;
wire [17:0] timer;
// ================================================================
// DUT instantiation
// ================================================================
radar_mode_controller #(
.CHIRPS_PER_ELEVATION (CHIRPS_PER_ELEVATION),
.ELEVATIONS_PER_AZIMUTH (ELEVATIONS_PER_AZIMUTH),
.AZIMUTHS_PER_SCAN (AZIMUTHS_PER_SCAN),
.LONG_CHIRP_CYCLES (LONG_CHIRP_CYCLES),
.LONG_LISTEN_CYCLES (LONG_LISTEN_CYCLES),
.GUARD_CYCLES (GUARD_CYCLES),
.SHORT_CHIRP_CYCLES (SHORT_CHIRP_CYCLES),
.SHORT_LISTEN_CYCLES (SHORT_LISTEN_CYCLES)
) dut (
.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),
// Runtime-configurable timing ports, bound here to the reduced wrapper
// constants for tractable proof depth.
.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),
.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),
.fv_scan_state (scan_state),
.fv_timer (timer)
);
// scan_state and timer are now accessed via formal output ports
// ================================================================
// PROPERTY 1: State encoding state 7 is unreachable
// ================================================================
always @(posedge clk) begin
if (reset_n)
assert(scan_state <= 3'd6);
end
// ================================================================
// PROPERTY 2: Counter bounds
// ================================================================
always @(posedge clk) begin
if (reset_n) begin
assert(chirp_count < CHIRPS_PER_ELEVATION);
assert(elevation_count < ELEVATIONS_PER_AZIMUTH);
assert(azimuth_count < AZIMUTHS_PER_SCAN);
end
end
// ================================================================
// PROPERTY 3: Timer bound
// Timer must never reach or exceed the maximum timer parameter.
// The timer counts from 0 to (PARAM - 1) before resetting.
// ================================================================
always @(posedge clk) begin
if (reset_n)
assert(timer < MAX_TIMER);
end
// ================================================================
// PROPERTY 4: Mode coherence
// In S_LONG_CHIRP / S_LONG_LISTEN, use_long_chirp must be 1.
// In S_SHORT_CHIRP / S_SHORT_LISTEN, use_long_chirp must be 0.
// ================================================================
always @(posedge clk) begin
if (reset_n) begin
if (scan_state == S_LONG_CHIRP || scan_state == S_LONG_LISTEN)
assert(use_long_chirp == 1'b1);
if (scan_state == S_SHORT_CHIRP || scan_state == S_SHORT_LISTEN)
assert(use_long_chirp == 1'b0);
end
end
// ================================================================
// PROPERTY 5: Single-chirp returns to idle
// In mode 2'b10, after S_LONG_LISTEN completes (timer reaches
// max), scan_state returns to S_IDLE.
// ================================================================
always @(posedge clk) begin
if (reset_n && f_past_valid) begin
if ($past(mode) == 2'b10 && mode == 2'b10 &&
$past(scan_state) == S_LONG_LISTEN &&
$past(timer) == LONG_LISTEN_CYCLES - 1)
assert(scan_state == S_IDLE);
end
end
// ================================================================
// PROPERTY 6: Auto-scan never stalls in S_IDLE
// In mode 2'b01, if the FSM is in S_IDLE on one cycle it must
// leave S_IDLE on the very next cycle.
// ================================================================
always @(posedge clk) begin
if (reset_n && f_past_valid) begin
if ($past(mode) == 2'b01 && $past(scan_state) == S_IDLE &&
$past(reset_n) && mode == 2'b01)
assert(scan_state != S_IDLE);
end
end
// ================================================================
// COVER 1: Full auto-scan completes
// ================================================================
always @(posedge clk) begin
if (reset_n)
cover(scan_complete && mode == 2'b01);
end
// ================================================================
// COVER 2: Each state is reachable
// ================================================================
always @(posedge clk) begin
if (reset_n) begin
cover(scan_state == S_IDLE);
cover(scan_state == S_LONG_CHIRP);
cover(scan_state == S_LONG_LISTEN);
cover(scan_state == S_GUARD);
cover(scan_state == S_SHORT_CHIRP);
cover(scan_state == S_SHORT_LISTEN);
cover(scan_state == S_ADVANCE);
end
end
`endif // FORMAL
endmodule

View File

@@ -12,16 +12,16 @@ module matched_filter_multi_segment (
input wire signed [17:0] ddc_q,
input wire ddc_valid,
// Chirp control (from sequence controller)
input wire use_long_chirp, //
input wire [5:0] chirp_counter, //
// Microcontroller sync signals
input wire mc_new_chirp, // Toggle for new chirp (32)
input wire mc_new_elevation, // Toggle for new elevation (32)
input wire mc_new_azimuth, // Toggle for new azimuth (50)
// Reference chirp (upstream memory loader selects long/short via use_long_chirp)
// Chirp control (from chirp_scheduler chirp-v2 wave_sel rail)
input wire [1:0] wave_sel, // 00=SHORT, 01=MEDIUM, 10=LONG
input wire [5:0] chirp_counter,
// Chirp boundary 1-cycle pulse from chirp_scheduler. Replaces the old
// mc_new_chirp toggle + XOR edge detector; mc_new_elevation/azimuth are
// gone (they were dead no consumer in this module).
input wire chirp_pulse,
// Reference chirp (chirp_reference_rom selects waveform via wave_sel)
input wire [15:0] ref_chirp_real,
input wire [15:0] ref_chirp_imag,
@@ -42,17 +42,21 @@ module matched_filter_multi_segment (
// ========== FIXED PARAMETERS ==========
parameter BUFFER_SIZE = `RP_FFT_SIZE; // 2048
parameter LONG_CHIRP_SAMPLES = 3000; // Still 3000 samples total
parameter SHORT_CHIRP_SAMPLES = 50; // 0.5 us @ 100MHz
parameter LONG_CHIRP_SAMPLES = 3000; // 30 us @ 100 MHz
parameter MEDIUM_CHIRP_SAMPLES = 500; // 5 us @ 100 MHz (chirp-v2)
parameter SHORT_CHIRP_SAMPLES = 100; // 1 us @ 100 MHz (chirp-v2; was 50)
parameter OVERLAP_SAMPLES = `RP_OVERLAP_SAMPLES; // 128
parameter SEGMENT_ADVANCE = `RP_SEGMENT_ADVANCE; // 2048 - 128 = 1920 samples
parameter DEBUG = 1; // Debug output control
// Calculate segments needed with overlap
// For 3000 samples with 128 overlap:
// Segments = ceil((3000 - 2048) / 1920) + 1 = ceil(952/1920) + 1 = 2
parameter LONG_SEGMENTS = `RP_LONG_SEGMENTS_3KM; // 2 segments
parameter SHORT_SEGMENTS = 1; // 50 samples padded to 2048
// Segment counts (overlap-save). LONG spans 2 segments; SHORT and MEDIUM
// both fit in a single 2048 buffer with zero-pad.
parameter LONG_SEGMENTS = `RP_LONG_SEGMENTS_3KM; // 2 segments (30 us / 2048-128 overlap)
parameter SHORT_SEGMENTS = 1; // SHORT or MEDIUM, single segment
// Convenience nets so the FSM body reads cleanly.
wire is_long = (wave_sel == `RP_WAVE_LONG);
wire is_medium = (wave_sel == `RP_WAVE_MEDIUM);
// ========== FIXED INTERNAL SIGNALS ==========
reg signed [31:0] pc_i, pc_q;
@@ -107,12 +111,6 @@ reg ov_we;
reg [6:0] ov_waddr;
reg signed [15:0] ov_wdata_i, ov_wdata_q;
// Microcontroller sync detection
reg mc_new_chirp_prev, mc_new_elevation_prev, mc_new_azimuth_prev;
wire chirp_start_pulse = mc_new_chirp ^ mc_new_chirp_prev; // Toggle-to-pulse (any edge)
wire elevation_change_pulse = mc_new_elevation ^ mc_new_elevation_prev; // Toggle-to-pulse
wire azimuth_change_pulse = mc_new_azimuth ^ mc_new_azimuth_prev; // Toggle-to-pulse
// Processing chain signals
wire [15:0] fft_pc_i, fft_pc_q;
wire fft_pc_valid;
@@ -126,19 +124,6 @@ reg fft_start;
// ========== SAMPLE ADDRESS OUTPUT ==========
assign sample_addr_out = buffer_read_ptr[10:0];
// ========== MICROCONTROLLER SYNC ==========
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
mc_new_chirp_prev <= 1'b0;
mc_new_elevation_prev <= 1'b0;
mc_new_azimuth_prev <= 1'b0;
end else begin
mc_new_chirp_prev <= mc_new_chirp;
mc_new_elevation_prev <= mc_new_elevation;
mc_new_azimuth_prev <= mc_new_azimuth;
end
end
// ========== BUFFER INITIALIZATION ==========
integer buf_init;
integer ov_init;
@@ -227,15 +212,15 @@ always @(posedge clk or negedge reset_n) begin
chirp_complete <= 0;
saw_chain_output <= 0;
// Wait for chirp start from microcontroller
if (chirp_start_pulse) begin
// Wait for chirp start (1-cycle pulse from chirp_scheduler)
if (chirp_pulse) begin
state <= ST_COLLECT_DATA;
total_segments <= use_long_chirp ? LONG_SEGMENTS[2:0] : SHORT_SEGMENTS[2:0];
total_segments <= is_long ? LONG_SEGMENTS[2:0] : SHORT_SEGMENTS[2:0];
`ifdef SIMULATION
$display("[MULTI_SEG_FIXED] Starting %s chirp, segments: %d",
use_long_chirp ? "LONG" : "SHORT",
use_long_chirp ? LONG_SEGMENTS : SHORT_SEGMENTS);
is_long ? "LONG" : (is_medium ? "MEDIUM" : "SHORT"),
is_long ? LONG_SEGMENTS : SHORT_SEGMENTS);
$display("[MULTI_SEG_FIXED] Overlap: %d samples, Advance: %d samples",
OVERLAP_SAMPLES, SEGMENT_ADVANCE);
`endif
@@ -269,13 +254,18 @@ always @(posedge clk or negedge reset_n) begin
`endif
end
// SHORT CHIRP: Only 50 samples, then zero-pad
if (!use_long_chirp) begin
if (chirp_samples_collected >= SHORT_CHIRP_SAMPLES - 1) begin
// SHORT/MEDIUM single-segment path: collect waveform-specific
// sample count then zero-pad to 2048. SHORT=100 (1 us),
// MEDIUM=500 (5 us). LONG path falls through to the
// multi-segment overlap-save block below.
if (!is_long) begin
if (( is_medium && chirp_samples_collected >= MEDIUM_CHIRP_SAMPLES - 1) ||
(!is_medium && chirp_samples_collected >= SHORT_CHIRP_SAMPLES - 1)) begin
state <= ST_ZERO_PAD;
chirp_complete <= 1; // Bug A fix: mark chirp done so ST_OUTPUT exits to IDLE
`ifdef SIMULATION
$display("[MULTI_SEG_FIXED] Short chirp: collected %d samples, starting zero-pad",
$display("[MULTI_SEG_FIXED] %s chirp: collected %d samples, starting zero-pad",
is_medium ? "Medium" : "Short",
chirp_samples_collected + 1);
`endif
end
@@ -291,19 +281,19 @@ always @(posedge clk or negedge reset_n) begin
// processing. For segment 0 this means FFT_SIZE fresh samples.
// For segments 1+, write_ptr starts at OVERLAP_SAMPLES (128)
// so we collect 896 new samples to fill the buffer.
if (use_long_chirp) begin
if (is_long) begin
if (buffer_write_ptr >= BUFFER_SIZE) begin
buffer_has_data <= 1;
state <= ST_WAIT_REF;
segment_request <= current_segment[1:0];
mem_request <= 1;
`ifdef SIMULATION
$display("[MULTI_SEG_FIXED] Segment %d ready: %d samples collected",
current_segment, chirp_samples_collected);
`endif
end
if (chirp_samples_collected >= LONG_CHIRP_SAMPLES && !chirp_complete) begin
chirp_complete <= 1;
`ifdef SIMULATION
@@ -335,7 +325,7 @@ always @(posedge clk or negedge reset_n) begin
buffer_has_data <= 1;
buffer_write_ptr <= 0;
state <= ST_WAIT_REF;
segment_request <= use_long_chirp ? current_segment[1:0] : 2'd0;
segment_request <= is_long ? current_segment[1:0] : 2'd0;
mem_request <= 1;
`ifdef SIMULATION
$display("[MULTI_SEG_FIXED] Zero-pad complete, buffer full");
@@ -463,7 +453,7 @@ always @(posedge clk or negedge reset_n) begin
current_segment <= current_segment + 1;
segment_done <= 0;
if (use_long_chirp) begin
if (is_long) begin
// OVERLAP-SAVE: Write cached tail samples back to BRAM [0..127]
overlap_copy_count <= 0;
state <= ST_OVERLAP_COPY;
@@ -473,7 +463,7 @@ always @(posedge clk or negedge reset_n) begin
OVERLAP_SAMPLES);
`endif
end else begin
// Short chirp: only one segment
// SHORT or MEDIUM: only one segment, no overlap-save.
buffer_write_ptr <= 0;
if (!chirp_complete) begin
state <= ST_COLLECT_DATA;
@@ -514,8 +504,9 @@ always @(posedge clk or negedge reset_n) begin
end
endcase
// Update status
status <= {state[2:0], use_long_chirp};
// Update status bit 0 echoes is_long for legacy probes; full
// wave_sel is consumed at the module boundary.
status <= {state[2:0], is_long};
end
end

View File

@@ -74,13 +74,13 @@ module mti_canceller #(
// ========== CONFIGURATION ==========
input wire mti_enable, // 1=MTI active, 0=pass-through
// Current chirp's waveform selector (from radar_mode_controller). Used
// to mute MTI output across the longshort chirp boundary in range
// mode 01 (long-range interleave) without this, the first chirp of
// a new waveform subtracts the previous waveform's range profile,
// injecting a per-range-bin impulse into slow-time sample 0 of the
// new Doppler sub-frame that spreads across all Doppler bins.
input wire use_long_chirp,
// Current chirp's waveform selector (from chirp_scheduler). Used to
// mute MTI output across waveform transitions in scan-mode 3-sub-frame
// sequencing without this, the first chirp of a new waveform would
// subtract the previous waveform's range profile, injecting a per-bin
// impulse into slow-time sample 0 of the new Doppler sub-frame that
// spreads across all Doppler bins.
input wire [1:0] wave_sel,
// ========== STATUS ==========
output reg mti_first_chirp, // 1 during first chirp (output muted)
@@ -109,23 +109,23 @@ reg signed [DATA_WIDTH-1:0] range_i_d1, range_q_d1;
reg range_valid_d1;
reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_d1;
reg mti_enable_d1;
reg use_long_chirp_d1;
reg [1:0] wave_sel_d1;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
range_i_d1 <= {DATA_WIDTH{1'b0}};
range_q_d1 <= {DATA_WIDTH{1'b0}};
range_valid_d1 <= 1'b0;
range_bin_d1 <= {`RP_RANGE_BIN_WIDTH_MAX{1'b0}};
mti_enable_d1 <= 1'b0;
use_long_chirp_d1 <= 1'b0;
range_i_d1 <= {DATA_WIDTH{1'b0}};
range_q_d1 <= {DATA_WIDTH{1'b0}};
range_valid_d1 <= 1'b0;
range_bin_d1 <= {`RP_RANGE_BIN_WIDTH_MAX{1'b0}};
mti_enable_d1 <= 1'b0;
wave_sel_d1 <= `RP_WAVE_SHORT;
end else begin
range_i_d1 <= range_i_in;
range_q_d1 <= range_q_in;
range_valid_d1 <= range_valid_in;
range_bin_d1 <= range_bin_in;
mti_enable_d1 <= mti_enable;
use_long_chirp_d1 <= use_long_chirp;
range_i_d1 <= range_i_in;
range_q_d1 <= range_q_in;
range_valid_d1 <= range_valid_in;
range_bin_d1 <= range_bin_in;
mti_enable_d1 <= mti_enable;
wave_sel_d1 <= wave_sel;
end
end
@@ -160,14 +160,14 @@ end
reg has_previous;
// Waveform of the chirp whose profile currently lives in prev_i/prev_q.
// Latched on every range_valid_d1 (use_long_chirp_d1 is constant within a
// chirp, so this stays consistent inside a chirp; at the first sample of
// the *next* chirp the OLD value is still present for the combinational
// Latched on every range_valid_d1 (wave_sel_d1 is constant within a chirp,
// so this stays consistent inside a chirp; at the first sample of the
// *next* chirp the OLD value is still present for the combinational
// `waveform_changed` compare, then updates this cycle to the new value).
// Updating per-cycle (rather than only at the last bin) keeps the tag
// correct when range_bin_decimator early-terminates a chirp before
// `range_bin_d1` ever reaches NUM_RANGE_BINS - 1 (RX-F).
reg prev_chirp_was_long;
reg [1:0] prev_chirp_wave_sel;
// ============================================================================
// CHIRP BOUNDARY DETECTION (RX-F: end-of-chirp without depending on the
@@ -192,7 +192,7 @@ wire chirp_boundary = range_valid_d1
wire effective_has_previous = has_previous || chirp_boundary;
wire waveform_changed = effective_has_previous
&& (use_long_chirp_d1 != prev_chirp_was_long);
&& (wave_sel_d1 != prev_chirp_wave_sel);
// ============================================================================
// MTI PROCESSING (operates on d1 pipeline stage + BRAM read data)
@@ -237,7 +237,7 @@ always @(posedge clk or negedge reset_n) begin
range_bin_out <= {`RP_RANGE_BIN_WIDTH_MAX{1'b0}};
has_previous <= 1'b0;
mti_first_chirp <= 1'b1;
prev_chirp_was_long <= 1'b0;
prev_chirp_wave_sel <= `RP_WAVE_SHORT;
mti_saturation_count <= 8'd0;
saw_nonzero_bin_in_chirp <= 1'b0;
end else begin
@@ -261,7 +261,7 @@ always @(posedge clk or negedge reset_n) begin
// still visible to the combinational `waveform_changed` compare
// (read-before-write semantics), then updates this cycle to the
// new chirp's value.
prev_chirp_was_long <= use_long_chirp_d1;
prev_chirp_wave_sel <= wave_sel_d1;
// Arm has_previous on either the original last-bin trigger OR a
// chirp_boundary (RX-F). After this cycle, prev_i/prev_q holds
@@ -290,11 +290,12 @@ always @(posedge clk or negedge reset_n) begin
saw_nonzero_bin_in_chirp <= 1'b0;
end else if (!effective_has_previous || waveform_changed) begin
// No valid previous chirp to subtract from either the very
// first chirp after reset/enable, or the longshort boundary
// in range_mode=01 where the prev buffer holds a different
// waveform's profile. Mute output (emit zeros with valid=1
// so Doppler still sees the expected chirp count), overwrite
// prev_i/prev_q as this chirp streams through the write port.
// first chirp after reset/enable, or a sub-frame waveform
// transition (SHORT->MEDIUM, MEDIUM->LONG, etc.) where the
// prev buffer holds a different waveform's profile. Mute
// output (emit zeros with valid=1 so Doppler still sees the
// expected chirp count), overwrite prev_i/prev_q as this
// chirp streams through the write port.
range_i_out <= {DATA_WIDTH{1'b0}};
range_q_out <= {DATA_WIDTH{1'b0}};
range_valid_out <= 1'b1;

View File

@@ -1,499 +0,0 @@
`timescale 1ns / 1ps
`include "radar_params.vh"
/**
* radar_mode_controller.v
*
* Generates beam scanning and chirp mode control signals for the AERIS-10
* receiver processing chain. This module drives:
* - use_long_chirp : selects long (30us) or short (0.5us) chirp mode
* - mc_new_chirp : toggle signal indicating new chirp start
* - mc_new_elevation : toggle signal indicating elevation step
* - mc_new_azimuth : toggle signal indicating azimuth step
*
* These signals are consumed by matched_filter_multi_segment and
* chirp_reference_rom in the receiver path. (chirp-v2 PR-D will replace
* this entire module with chirp_scheduler.)
*
* The controller mirrors the transmitter's chirp sequence defined in
* plfm_chirp_controller_enhanced:
* - 32 chirps per elevation
* - 31 elevations per azimuth
* - 50 azimuths per full scan
*
* Chirp sequence depends on range_mode (host_range_mode, opcode 0x20):
* range_mode 2'b00 (3 km): All short chirps only. Long chirp blind zone
* (4500 m) exceeds 3 km max range, so long chirps are useless.
* range_mode 2'b01 (long-range): Dual chirp Long chirp Listen Guard
* Short chirp Listen. First half of chirps_per_elev are long, second
* half are short (blind-zone fill).
*
* Modes of operation (host_radar_mode, opcode 0x01):
* mode[1:0]:
* 2'b00 = STM32-driven (pass through stm32 toggle signals)
* 2'b01 = Free-running auto-scan (internal timing, short chirps only)
* 2'b10 = Single-chirp (fire one chirp per trigger, for debug)
* 2'b11 = Reserved
*
* Clock domain: clk (100 MHz)
*/
module radar_mode_controller #(
parameter CHIRPS_PER_ELEVATION = `RP_DEF_CHIRPS_PER_ELEV,
parameter ELEVATIONS_PER_AZIMUTH = 31,
parameter AZIMUTHS_PER_SCAN = 50,
// Timing in 100 MHz clock cycles
// Long chirp: 30us = 3000 cycles at 100 MHz
// Long listen: 137us = 13700 cycles
// Guard: 175.4us = 17540 cycles
// Short chirp: 0.5us = 50 cycles
// Short listen: 174.5us = 17450 cycles
parameter LONG_CHIRP_CYCLES = `RP_DEF_LONG_CHIRP_CYCLES,
parameter LONG_LISTEN_CYCLES = `RP_DEF_LONG_LISTEN_CYCLES,
parameter GUARD_CYCLES = `RP_DEF_GUARD_CYCLES,
parameter SHORT_CHIRP_CYCLES = `RP_DEF_SHORT_CHIRP_CYCLES,
parameter SHORT_LISTEN_CYCLES = `RP_DEF_SHORT_LISTEN_CYCLES
) (
input wire clk,
input wire reset_n,
// Mode selection (host_radar_mode, opcode 0x01)
input wire [1:0] mode, // 00=STM32, 01=auto, 10=single, 11=rsvd
// Range mode (host_range_mode, opcode 0x20)
// Determines chirp type selection in pass-through and auto-scan modes.
// 2'b00 = 3 km (all short chirps long blind zone > max range)
// 2'b01 = Long-range (dual chirp: first half long, second half short)
input wire [1:0] range_mode,
// STM32 pass-through inputs (active in mode 00)
input wire stm32_new_chirp,
input wire stm32_new_elevation,
input wire stm32_new_azimuth,
// Single-chirp trigger (active in mode 10)
input wire trigger,
// Runtime-configurable timing inputs from host USB commands.
// When connected, these override the compile-time parameters.
input wire [15:0] cfg_long_chirp_cycles,
input wire [15:0] cfg_long_listen_cycles,
input wire [15:0] cfg_guard_cycles,
input wire [15:0] cfg_short_chirp_cycles,
input wire [15:0] cfg_short_listen_cycles,
input wire [5:0] cfg_chirps_per_elev,
// Outputs to receiver processing chain
output reg use_long_chirp,
output reg mc_new_chirp,
output reg mc_new_elevation,
output reg mc_new_azimuth,
// Beam position tracking
output reg [5:0] chirp_count,
output reg [5:0] elevation_count,
output reg [5:0] azimuth_count,
// Status
output wire scanning, // 1 = scan in progress
output wire scan_complete // pulse when full scan done
`ifdef FORMAL
,
output wire [2:0] fv_scan_state,
output wire [17:0] fv_timer
`endif
);
// ============================================================================
// INTERNAL STATE
// ============================================================================
// Auto-scan state machine
reg [2:0] scan_state;
localparam S_IDLE = 3'd0;
localparam S_LONG_CHIRP = 3'd1;
localparam S_LONG_LISTEN = 3'd2;
localparam S_GUARD = 3'd3;
localparam S_SHORT_CHIRP = 3'd4;
localparam S_SHORT_LISTEN = 3'd5;
localparam S_ADVANCE = 3'd6;
// Timing counter
reg [17:0] timer; // enough for up to 262143 cycles (~2.6ms at 100 MHz)
`ifdef FORMAL
assign fv_scan_state = scan_state;
assign fv_timer = timer;
`endif
// Edge detection for STM32 pass-through
reg stm32_new_chirp_prev;
reg stm32_new_elevation_prev;
reg stm32_new_azimuth_prev;
// Trigger edge detection (for single-chirp mode)
reg trigger_prev;
wire trigger_pulse = trigger & ~trigger_prev;
// Scan completion
reg scan_done_pulse;
// ============================================================================
// EDGE DETECTION
// ============================================================================
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
stm32_new_chirp_prev <= 1'b0;
stm32_new_elevation_prev <= 1'b0;
stm32_new_azimuth_prev <= 1'b0;
trigger_prev <= 1'b0;
end else begin
stm32_new_chirp_prev <= stm32_new_chirp;
stm32_new_elevation_prev <= stm32_new_elevation;
stm32_new_azimuth_prev <= stm32_new_azimuth;
trigger_prev <= trigger;
end
end
wire stm32_chirp_toggle = stm32_new_chirp ^ stm32_new_chirp_prev;
wire stm32_elevation_toggle = stm32_new_elevation ^ stm32_new_elevation_prev;
wire stm32_azimuth_toggle = stm32_new_azimuth ^ stm32_new_azimuth_prev;
// ============================================================================
// MAIN STATE MACHINE
// ============================================================================
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
scan_state <= S_IDLE;
timer <= 18'd0;
use_long_chirp <= 1'b0; // Default short chirp (safe for 3 km mode)
mc_new_chirp <= 1'b0;
mc_new_elevation <= 1'b0;
mc_new_azimuth <= 1'b0;
chirp_count <= 6'd0;
elevation_count <= 6'd0;
azimuth_count <= 6'd0;
scan_done_pulse <= 1'b0;
end else begin
// Clear one-shot signals
scan_done_pulse <= 1'b0;
case (mode)
// ================================================================
// MODE 00: STM32-driven pass-through
// The STM32 firmware controls timing; we just detect toggle edges
// and forward them to the receiver chain. Chirp type is determined
// by range_mode:
// range_mode 00 (3 km): ALL chirps are short (long blind zone
// 4500 m exceeds 3072 m max range, so long chirps are useless).
// range_mode 01 (long-range): First half of chirps_per_elev are
// long, second half are short (blind-zone fill).
// ================================================================
2'b00: begin
// Reset auto-scan state
scan_state <= S_IDLE;
timer <= 18'd0;
// Pass through toggle signals
if (stm32_chirp_toggle) begin
mc_new_chirp <= ~mc_new_chirp; // Toggle output
// Determine chirp type based on range_mode
case (range_mode)
`RP_RANGE_MODE_3KM: begin
// 3 km mode: all short chirps
use_long_chirp <= 1'b0;
end
`RP_RANGE_MODE_LONG: begin
// Long-range: first half long, second half short.
// chirps_per_elev is typically 32 (16 long + 16 short).
// Use cfg_chirps_per_elev[5:1] as the halfway point.
if (chirp_count < {1'b0, cfg_chirps_per_elev[5:1]})
use_long_chirp <= 1'b1;
else
use_long_chirp <= 1'b0;
end
default: begin
// Reserved modes: default to short chirp (safe)
use_long_chirp <= 1'b0;
end
endcase
// Track chirp count
if (chirp_count < cfg_chirps_per_elev - 1)
chirp_count <= chirp_count + 1;
else
chirp_count <= 6'd0;
end
if (stm32_elevation_toggle) begin
mc_new_elevation <= ~mc_new_elevation;
chirp_count <= 6'd0;
if (elevation_count < ELEVATIONS_PER_AZIMUTH - 1)
elevation_count <= elevation_count + 1;
else
elevation_count <= 6'd0;
end
if (stm32_azimuth_toggle) begin
mc_new_azimuth <= ~mc_new_azimuth;
elevation_count <= 6'd0;
if (azimuth_count < AZIMUTHS_PER_SCAN - 1)
azimuth_count <= azimuth_count + 1;
else begin
azimuth_count <= 6'd0;
scan_done_pulse <= 1'b1;
end
end
end
// ================================================================
// MODE 01: Free-running auto-scan
// Internally generates chirp timing matching the transmitter.
// For 3 km mode (range_mode 00): short chirps only. The long chirp
// blind zone (4500 m) exceeds the 3072 m max range, making long
// chirps useless. State machine skips S_LONG_CHIRP/LISTEN/GUARD.
// For long-range mode (range_mode 01): full dual-chirp sequence.
// NOTE: Auto-scan is primarily for bench testing without STM32.
// ================================================================
2'b01: begin
case (scan_state)
S_IDLE: begin
// Start first chirp immediately
timer <= 18'd0;
chirp_count <= 6'd0;
elevation_count <= 6'd0;
azimuth_count <= 6'd0;
mc_new_chirp <= ~mc_new_chirp; // Toggle to start chirp
// For 3 km mode, skip directly to short chirp
if (range_mode == `RP_RANGE_MODE_3KM) begin
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end else begin
scan_state <= S_LONG_CHIRP;
use_long_chirp <= 1'b1;
end
`ifdef SIMULATION
$display("[MODE_CTRL] Auto-scan starting, range_mode=%0d", range_mode);
`endif
end
S_LONG_CHIRP: begin
use_long_chirp <= 1'b1;
if (timer < cfg_long_chirp_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_LONG_LISTEN;
end
end
S_LONG_LISTEN: begin
if (timer < cfg_long_listen_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_GUARD;
end
end
S_GUARD: begin
if (timer < cfg_guard_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end
end
S_SHORT_CHIRP: begin
use_long_chirp <= 1'b0;
if (timer < cfg_short_chirp_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_SHORT_LISTEN;
end
end
S_SHORT_LISTEN: begin
if (timer < cfg_short_listen_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_ADVANCE;
end
end
S_ADVANCE: begin
// Advance chirp/elevation/azimuth counters
if (chirp_count < cfg_chirps_per_elev - 1) begin
// Next chirp in current elevation
chirp_count <= chirp_count + 1;
mc_new_chirp <= ~mc_new_chirp;
// For 3 km mode: short chirps only, skip long phases
if (range_mode == `RP_RANGE_MODE_3KM) begin
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end else begin
scan_state <= S_LONG_CHIRP;
use_long_chirp <= 1'b1;
end
end else begin
chirp_count <= 6'd0;
if (elevation_count < ELEVATIONS_PER_AZIMUTH - 1) begin
// Next elevation
elevation_count <= elevation_count + 1;
mc_new_chirp <= ~mc_new_chirp;
mc_new_elevation <= ~mc_new_elevation;
if (range_mode == `RP_RANGE_MODE_3KM) begin
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end else begin
scan_state <= S_LONG_CHIRP;
use_long_chirp <= 1'b1;
end
end else begin
elevation_count <= 6'd0;
if (azimuth_count < AZIMUTHS_PER_SCAN - 1) begin
// Next azimuth
azimuth_count <= azimuth_count + 1;
mc_new_chirp <= ~mc_new_chirp;
mc_new_elevation <= ~mc_new_elevation;
mc_new_azimuth <= ~mc_new_azimuth;
if (range_mode == `RP_RANGE_MODE_3KM) begin
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end else begin
scan_state <= S_LONG_CHIRP;
use_long_chirp <= 1'b1;
end
end else begin
// Full scan complete restart
azimuth_count <= 6'd0;
scan_done_pulse <= 1'b1;
mc_new_chirp <= ~mc_new_chirp;
mc_new_elevation <= ~mc_new_elevation;
mc_new_azimuth <= ~mc_new_azimuth;
if (range_mode == `RP_RANGE_MODE_3KM) begin
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end else begin
scan_state <= S_LONG_CHIRP;
use_long_chirp <= 1'b1;
end
`ifdef SIMULATION
$display("[MODE_CTRL] Full scan complete, restarting");
`endif
end
end
end
end
default: scan_state <= S_IDLE;
endcase
end
// ================================================================
// MODE 10: Single-chirp (debug mode)
// Fire one chirp per trigger pulse, no scanning.
// Chirp type depends on range_mode:
// 3 km: short chirp only
// Long-range: long chirp (for testing long-chirp path)
// ================================================================
2'b10: begin
case (scan_state)
S_IDLE: begin
if (trigger_pulse) begin
timer <= 18'd0;
mc_new_chirp <= ~mc_new_chirp;
if (range_mode == `RP_RANGE_MODE_3KM) begin
// 3 km: fire short chirp
scan_state <= S_SHORT_CHIRP;
use_long_chirp <= 1'b0;
end else begin
// Long-range: fire long chirp
scan_state <= S_LONG_CHIRP;
use_long_chirp <= 1'b1;
end
end
end
S_LONG_CHIRP: begin
if (timer < cfg_long_chirp_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_LONG_LISTEN;
end
end
S_LONG_LISTEN: begin
if (timer < cfg_long_listen_cycles - 1)
timer <= timer + 1;
else begin
// Single long chirp done, return to idle
timer <= 18'd0;
scan_state <= S_IDLE;
end
end
S_SHORT_CHIRP: begin
use_long_chirp <= 1'b0;
if (timer < cfg_short_chirp_cycles - 1)
timer <= timer + 1;
else begin
timer <= 18'd0;
scan_state <= S_SHORT_LISTEN;
end
end
S_SHORT_LISTEN: begin
if (timer < cfg_short_listen_cycles - 1)
timer <= timer + 1;
else begin
// Single short chirp done, return to idle
timer <= 18'd0;
scan_state <= S_IDLE;
end
end
default: scan_state <= S_IDLE;
endcase
end
// ================================================================
// MODE 11: Reserved idle
// ================================================================
2'b11: begin
scan_state <= S_IDLE;
timer <= 18'd0;
end
endcase
end
end
// ============================================================================
// OUTPUT ASSIGNMENTS
// ============================================================================
assign scanning = (scan_state != S_IDLE);
assign scan_complete = scan_done_pulse;
endmodule

View File

@@ -122,16 +122,18 @@ module radar_receiver_final (
);
// ========== INTERNAL SIGNALS ==========
wire use_long_chirp;
// NOTE: chirp_counter is now an input port (was undriven internal wire bug NEW-1)
wire chirp_start;
wire azimuth_change;
wire elevation_change;
// Mode controller outputs matched_filter_multi_segment
wire mc_new_chirp;
wire mc_new_elevation;
wire mc_new_azimuth;
// chirp_counter is an input port (driven by the transmitter bug NEW-1).
// chirp_scheduler emits the canonical wave_sel rail and 1-cycle chirp_pulse;
// no use_long_chirp shim and no mc_new_*-toggle XOR converters.
wire [1:0] wave_sel;
wire chirp_pulse;
wire subframe_pulse; // unused on RX in PR-D; doppler picks up in PR-F
wire frame_pulse; // unused on RX in PR-D; PR-F doppler driver
wire [5:0] sched_chirp_counter;
wire [1:0] sched_subframe_id;
wire [15:0] sched_cfg_chirp_cycles, sched_cfg_listen_cycles, sched_cfg_guard_cycles;
wire sched_track_mode_active;
wire [5:0] sched_track_beam_az, sched_track_beam_el;
wire [1:0] segment_request;
wire mem_request;
@@ -152,12 +154,6 @@ wire [3:0] gc_current_gain; // Diagnostic: effective gain_shift
// depending on wave_sel selected by chirp_reference_rom).
wire [15:0] ref_chirp_real, ref_chirp_imag;
// chirp-v2 PR-C: wave_sel shim. radar_mode_controller still produces a
// 1-bit use_long_chirp output (replaced in PR-D by chirp_scheduler whose
// native output is wave_sel[1:0]). MEDIUM is unreachable through the
// 1-bit path; the rom serves SHORT or LONG only until PR-D lands.
wire [1:0] wave_sel = use_long_chirp ? `RP_WAVE_LONG : `RP_WAVE_SHORT;
// ========== DOPPLER PROCESSING SIGNALS ==========
wire [31:0] range_data_32bit;
wire range_data_valid;
@@ -206,42 +202,53 @@ wire mti_range_valid;
wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] mti_range_bin; // 9-bit
wire mti_first_chirp;
// ========== RADAR MODE CONTROLLER SIGNALS ==========
wire rmc_scanning;
wire rmc_scan_complete;
wire [5:0] rmc_chirp_count;
wire [5:0] rmc_elevation_count;
wire [5:0] rmc_azimuth_count;
// ========== MODULE INSTANTIATIONS ==========
// 0. Radar Mode Controller drives chirp/elevation/azimuth timing signals
// Default mode: auto-scan (2'b01). Change to 2'b00 for STM32 pass-through.
radar_mode_controller rmc (
// 0. Chirp scheduler (chirp-v2 PR-D) single source of truth for waveform
// identity and inter-chirp timing on the RX side. Replaces the legacy
// radar_mode_controller. SHORT/MEDIUM/LONG ladders + sub-frame walking
// + host-cued track dwell with watchdog scan-fallback.
//
// Several inputs (medium/track/subframe-enable/debug) are pinned to
// radar_params defaults until PR-G plumbs the new USB opcodes through
// radar_system_top. host_chirps_per_elev (legacy) is intentionally not
// wired here the V2 sub-frame structure uses RP_DEF_CHIRPS_PER_SUBFRAME
// (16) and PR-G renames the host register.
chirp_scheduler sched (
.clk(clk),
.reset_n(reset_n),
.mode(host_mode), // Controlled by host via USB (default: 2'b01 auto-scan)
.range_mode(host_range_mode), // Range mode: 00=3km, 01=long-range (drives chirp type)
.stm32_new_chirp(stm32_new_chirp_rx),
.stm32_new_elevation(stm32_new_elevation_rx),
.stm32_new_azimuth(stm32_new_azimuth_rx),
.trigger(host_trigger), // Single-chirp trigger from host via USB
// Gap 2: Runtime-configurable timing from host USB commands
.cfg_long_chirp_cycles(host_long_chirp_cycles),
.cfg_long_listen_cycles(host_long_listen_cycles),
.cfg_guard_cycles(host_guard_cycles),
.cfg_short_chirp_cycles(host_short_chirp_cycles),
.cfg_short_listen_cycles(host_short_listen_cycles),
.cfg_chirps_per_elev(host_chirps_per_elev),
.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(rmc_chirp_count),
.elevation_count(rmc_elevation_count),
.azimuth_count(rmc_azimuth_count),
.scanning(rmc_scanning),
.scan_complete(rmc_scan_complete)
.host_mode(host_mode),
.host_subframe_enable(`RP_DEF_SUBFRAME_ENABLE),
.host_short_chirp_cycles (host_short_chirp_cycles),
.host_short_listen_cycles(host_short_listen_cycles),
.host_medium_chirp_cycles (16'd`RP_DEF_MEDIUM_CHIRP_CYCLES),
.host_medium_listen_cycles(16'd`RP_DEF_MEDIUM_LISTEN_CYCLES),
.host_long_chirp_cycles (host_long_chirp_cycles),
.host_long_listen_cycles(host_long_listen_cycles),
.host_guard_cycles(host_guard_cycles),
.host_chirps_per_subframe(6'd`RP_DEF_CHIRPS_PER_SUBFRAME),
.host_trigger(host_trigger),
.host_debug_wave_sel(`RP_WAVE_SHORT),
.host_track_request(1'b0),
.host_track_wave_sel(`RP_WAVE_SHORT),
.host_track_chirp_count(`RP_DEF_TRACK_CHIRP_COUNT),
.host_track_beam_az(6'd0),
.host_track_beam_el(6'd0),
.stm32_new_chirp (stm32_new_chirp_rx),
.stm32_new_subframe(stm32_new_elevation_rx),
.stm32_new_frame (stm32_new_azimuth_rx),
.wave_sel(wave_sel),
.chirp_pulse(chirp_pulse),
.subframe_pulse(subframe_pulse),
.frame_pulse(frame_pulse),
.chirp_counter(sched_chirp_counter),
.subframe_id(sched_subframe_id),
.cfg_chirp_cycles (sched_cfg_chirp_cycles),
.cfg_listen_cycles(sched_cfg_listen_cycles),
.cfg_guard_cycles (sched_cfg_guard_cycles),
.track_mode_active(sched_track_mode_active),
.track_beam_az(sched_track_beam_az),
.track_beam_el(sched_track_beam_el)
);
wire clk_400m;
@@ -461,11 +468,9 @@ matched_filter_multi_segment mf_dual (
.ddc_i({{2{gc_i[15]}}, gc_i}),
.ddc_q({{2{gc_q[15]}}, gc_q}),
.ddc_valid(gc_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), // 1-FF aligned ref (RX-B fix)
.ref_chirp_imag(ref_chirp_imag),
.segment_request(segment_request),
@@ -517,7 +522,7 @@ mti_canceller #(
.range_valid_out(mti_range_valid),
.range_bin_out(mti_range_bin),
.mti_enable(host_mti_enable),
.use_long_chirp(use_long_chirp),
.wave_sel(wave_sel),
.mti_first_chirp(mti_first_chirp),
.mti_saturation_count(mti_saturation_count_out)
);

View File

@@ -251,9 +251,9 @@ reg [5:0] host_stream_control;
reg [3:0] host_gain_shift;
// Gap 2: Host-configurable chirp timing registers
// These override the compile-time defaults in radar_mode_controller when
// written via USB command. Defaults match the parameter values in
// radar_mode_controller.v so behavior is unchanged until the host writes them.
// chirp_scheduler (chirp-v2 PR-D) consumes these directly no parameter
// overrides. Defaults match the legacy values so behavior is unchanged
// until the host writes them. PR-G adds the v2 medium/track/subframe regs.
reg [15:0] host_long_chirp_cycles; // Opcode 0x10 (default 3000)
reg [15:0] host_long_listen_cycles; // Opcode 0x11 (default 13700)
reg [15:0] host_guard_cycles; // Opcode 0x12 (default 17540)
@@ -589,9 +589,8 @@ radar_receiver_final rx_inst (
.host_agc_attack(host_agc_attack),
.host_agc_decay(host_agc_decay),
.host_agc_holdoff(host_agc_holdoff),
// STM32 toggle signals for RX mode controller (mode 00 pass-through).
// These are the raw GPIO inputs the RX mode controller's edge detectors
// (inside radar_mode_controller) handle debouncing/edge detection.
// STM32 toggle signals for the RX scheduler (mode 00 pass-through).
// Raw GPIO inputs chirp_scheduler's edge detectors handle debouncing.
.stm32_new_chirp_rx(stm32_new_chirp),
.stm32_new_elevation_rx(stm32_new_elevation),
.stm32_new_azimuth_rx(stm32_new_azimuth),
@@ -1003,12 +1002,13 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
host_detect_threshold <= 16'd10000; // Default threshold
host_stream_control <= `RP_STREAM_CTRL_DEFAULT; // Default: all streams, mag-only mode
host_gain_shift <= 4'd0; // Default: pass-through (no gain change)
// Gap 2: chirp timing defaults (match radar_mode_controller parameters)
// Gap 2: chirp timing defaults (forwarded to chirp_scheduler).
// SHORT bumped to 100 cycles (1 us) for chirp-v2 SHORT waveform.
host_long_chirp_cycles <= 16'd3000;
host_long_listen_cycles <= 16'd13700;
host_guard_cycles <= 16'd17540;
host_short_chirp_cycles <= 16'd50;
host_short_listen_cycles <= 16'd17450;
host_short_chirp_cycles <= 16'd100;
host_short_listen_cycles <= 16'd17400;
host_chirps_per_elev <= 6'd32;
host_status_request <= 1'b0;
chirps_mismatch_error <= 1'b0;

View File

@@ -75,7 +75,7 @@ PROD_RTL=(
usb_data_interface.v
usb_data_interface_ft2232h.v
edge_detector.v
radar_mode_controller.v
chirp_scheduler.v
rx_gain_control.v
cfar_ca.v
mti_canceller.v
@@ -97,7 +97,7 @@ EXTRA_RTL=(
# Receiver chain (used by golden generate/compare tests)
RECEIVER_RTL=(
radar_receiver_final.v
radar_mode_controller.v
chirp_scheduler.v
tb/ad9484_interface_400m_stub.v
ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v
cdc_modules.v cdc_async_fifo.v fir_lowpass.v ddc_input_interface.v
@@ -722,10 +722,6 @@ run_test "Range Bin Decimator" \
tb/tb_rbd_reg.vvp \
tb/tb_range_bin_decimator.v range_bin_decimator.v
run_test "Radar Mode Controller" \
tb/tb_rmc_reg.vvp \
tb/tb_radar_mode_controller.v radar_mode_controller.v
echo ""
# ===========================================================================

View File

@@ -68,7 +68,7 @@ set rtl_files [list \
"${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \
"${rtl_dir}/plfm_chirp_controller.v" \
"${rtl_dir}/radar_mode_controller.v" \
"${rtl_dir}/chirp_scheduler.v" \
"${rtl_dir}/radar_receiver_final.v" \
"${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \

View File

@@ -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

View File

@@ -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");

View File

@@ -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 (01, hold at 1)
stm32_new_chirp = 1'b1;
// Wait 2 cycles: 1 for prev register update, 1 for XORmain 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 (10, 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 (01, 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 (01, 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 longguardshort 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_IDLES_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

View File

@@ -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

View File

@@ -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;

View File

@@ -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