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
+440
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
@@ -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
@@ -12,16 +12,16 @@ module matched_filter_multi_segment (
input wire signed [17:0] ddc_q, input wire signed [17:0] ddc_q,
input wire ddc_valid, input wire ddc_valid,
// Chirp control (from sequence controller) // Chirp control (from chirp_scheduler — chirp-v2 wave_sel rail)
input wire use_long_chirp, // input wire [1:0] wave_sel, // 00=SHORT, 01=MEDIUM, 10=LONG
input wire [5:0] chirp_counter, // input wire [5:0] chirp_counter,
// Microcontroller sync signals // Chirp boundary — 1-cycle pulse from chirp_scheduler. Replaces the old
input wire mc_new_chirp, // Toggle for new chirp (32) // mc_new_chirp toggle + XOR edge detector; mc_new_elevation/azimuth are
input wire mc_new_elevation, // Toggle for new elevation (32) // gone (they were dead — no consumer in this module).
input wire mc_new_azimuth, // Toggle for new azimuth (50) input wire chirp_pulse,
// Reference chirp (upstream memory loader selects long/short via use_long_chirp) // Reference chirp (chirp_reference_rom selects waveform via wave_sel)
input wire [15:0] ref_chirp_real, input wire [15:0] ref_chirp_real,
input wire [15:0] ref_chirp_imag, input wire [15:0] ref_chirp_imag,
@@ -42,17 +42,21 @@ module matched_filter_multi_segment (
// ========== FIXED PARAMETERS ========== // ========== FIXED PARAMETERS ==========
parameter BUFFER_SIZE = `RP_FFT_SIZE; // 2048 parameter BUFFER_SIZE = `RP_FFT_SIZE; // 2048
parameter LONG_CHIRP_SAMPLES = 3000; // Still 3000 samples total parameter LONG_CHIRP_SAMPLES = 3000; // 30 us @ 100 MHz
parameter SHORT_CHIRP_SAMPLES = 50; // 0.5 us @ 100MHz 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 OVERLAP_SAMPLES = `RP_OVERLAP_SAMPLES; // 128
parameter SEGMENT_ADVANCE = `RP_SEGMENT_ADVANCE; // 2048 - 128 = 1920 samples parameter SEGMENT_ADVANCE = `RP_SEGMENT_ADVANCE; // 2048 - 128 = 1920 samples
parameter DEBUG = 1; // Debug output control parameter DEBUG = 1; // Debug output control
// Calculate segments needed with overlap // Segment counts (overlap-save). LONG spans 2 segments; SHORT and MEDIUM
// For 3000 samples with 128 overlap: // both fit in a single 2048 buffer with zero-pad.
// Segments = ceil((3000 - 2048) / 1920) + 1 = ceil(952/1920) + 1 = 2 parameter LONG_SEGMENTS = `RP_LONG_SEGMENTS_3KM; // 2 segments (30 us / 2048-128 overlap)
parameter LONG_SEGMENTS = `RP_LONG_SEGMENTS_3KM; // 2 segments parameter SHORT_SEGMENTS = 1; // SHORT or MEDIUM, single segment
parameter SHORT_SEGMENTS = 1; // 50 samples padded to 2048
// 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 ========== // ========== FIXED INTERNAL SIGNALS ==========
reg signed [31:0] pc_i, pc_q; reg signed [31:0] pc_i, pc_q;
@@ -107,12 +111,6 @@ reg ov_we;
reg [6:0] ov_waddr; reg [6:0] ov_waddr;
reg signed [15:0] ov_wdata_i, ov_wdata_q; 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 // Processing chain signals
wire [15:0] fft_pc_i, fft_pc_q; wire [15:0] fft_pc_i, fft_pc_q;
wire fft_pc_valid; wire fft_pc_valid;
@@ -126,19 +124,6 @@ reg fft_start;
// ========== SAMPLE ADDRESS OUTPUT ========== // ========== SAMPLE ADDRESS OUTPUT ==========
assign sample_addr_out = buffer_read_ptr[10:0]; 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 ========== // ========== BUFFER INITIALIZATION ==========
integer buf_init; integer buf_init;
integer ov_init; integer ov_init;
@@ -227,15 +212,15 @@ always @(posedge clk or negedge reset_n) begin
chirp_complete <= 0; chirp_complete <= 0;
saw_chain_output <= 0; saw_chain_output <= 0;
// Wait for chirp start from microcontroller // Wait for chirp start (1-cycle pulse from chirp_scheduler)
if (chirp_start_pulse) begin if (chirp_pulse) begin
state <= ST_COLLECT_DATA; 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 `ifdef SIMULATION
$display("[MULTI_SEG_FIXED] Starting %s chirp, segments: %d", $display("[MULTI_SEG_FIXED] Starting %s chirp, segments: %d",
use_long_chirp ? "LONG" : "SHORT", is_long ? "LONG" : (is_medium ? "MEDIUM" : "SHORT"),
use_long_chirp ? LONG_SEGMENTS : SHORT_SEGMENTS); is_long ? LONG_SEGMENTS : SHORT_SEGMENTS);
$display("[MULTI_SEG_FIXED] Overlap: %d samples, Advance: %d samples", $display("[MULTI_SEG_FIXED] Overlap: %d samples, Advance: %d samples",
OVERLAP_SAMPLES, SEGMENT_ADVANCE); OVERLAP_SAMPLES, SEGMENT_ADVANCE);
`endif `endif
@@ -269,13 +254,18 @@ always @(posedge clk or negedge reset_n) begin
`endif `endif
end end
// SHORT CHIRP: Only 50 samples, then zero-pad // SHORT/MEDIUM single-segment path: collect waveform-specific
if (!use_long_chirp) begin // sample count then zero-pad to 2048. SHORT=100 (1 us),
if (chirp_samples_collected >= SHORT_CHIRP_SAMPLES - 1) begin // 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; state <= ST_ZERO_PAD;
chirp_complete <= 1; // Bug A fix: mark chirp done so ST_OUTPUT exits to IDLE chirp_complete <= 1; // Bug A fix: mark chirp done so ST_OUTPUT exits to IDLE
`ifdef SIMULATION `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); chirp_samples_collected + 1);
`endif `endif
end end
@@ -291,7 +281,7 @@ always @(posedge clk or negedge reset_n) begin
// processing. For segment 0 this means FFT_SIZE fresh samples. // processing. For segment 0 this means FFT_SIZE fresh samples.
// For segments 1+, write_ptr starts at OVERLAP_SAMPLES (128) // For segments 1+, write_ptr starts at OVERLAP_SAMPLES (128)
// so we collect 896 new samples to fill the buffer. // 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 if (buffer_write_ptr >= BUFFER_SIZE) begin
buffer_has_data <= 1; buffer_has_data <= 1;
state <= ST_WAIT_REF; state <= ST_WAIT_REF;
@@ -335,7 +325,7 @@ always @(posedge clk or negedge reset_n) begin
buffer_has_data <= 1; buffer_has_data <= 1;
buffer_write_ptr <= 0; buffer_write_ptr <= 0;
state <= ST_WAIT_REF; 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; mem_request <= 1;
`ifdef SIMULATION `ifdef SIMULATION
$display("[MULTI_SEG_FIXED] Zero-pad complete, buffer full"); $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; current_segment <= current_segment + 1;
segment_done <= 0; segment_done <= 0;
if (use_long_chirp) begin if (is_long) begin
// OVERLAP-SAVE: Write cached tail samples back to BRAM [0..127] // OVERLAP-SAVE: Write cached tail samples back to BRAM [0..127]
overlap_copy_count <= 0; overlap_copy_count <= 0;
state <= ST_OVERLAP_COPY; state <= ST_OVERLAP_COPY;
@@ -473,7 +463,7 @@ always @(posedge clk or negedge reset_n) begin
OVERLAP_SAMPLES); OVERLAP_SAMPLES);
`endif `endif
end else begin end else begin
// Short chirp: only one segment // SHORT or MEDIUM: only one segment, no overlap-save.
buffer_write_ptr <= 0; buffer_write_ptr <= 0;
if (!chirp_complete) begin if (!chirp_complete) begin
state <= ST_COLLECT_DATA; state <= ST_COLLECT_DATA;
@@ -514,8 +504,9 @@ always @(posedge clk or negedge reset_n) begin
end end
endcase endcase
// Update status // Update status — bit 0 echoes is_long for legacy probes; full
status <= {state[2:0], use_long_chirp}; // wave_sel is consumed at the module boundary.
status <= {state[2:0], is_long};
end end
end end
+23 -22
View File
@@ -74,13 +74,13 @@ module mti_canceller #(
// ========== CONFIGURATION ========== // ========== CONFIGURATION ==========
input wire mti_enable, // 1=MTI active, 0=pass-through input wire mti_enable, // 1=MTI active, 0=pass-through
// Current chirp's waveform selector (from radar_mode_controller). Used // Current chirp's waveform selector (from chirp_scheduler). Used to
// to mute MTI output across the long↔short chirp boundary in range // mute MTI output across waveform transitions in scan-mode 3-sub-frame
// mode 01 (long-range interleave) — without this, the first chirp of // sequencing — without this, the first chirp of a new waveform would
// a new waveform subtracts the previous waveform's range profile, // subtract the previous waveform's range profile, injecting a per-bin
// injecting a per-range-bin impulse into slow-time sample 0 of the // impulse into slow-time sample 0 of the new Doppler sub-frame that
// new Doppler sub-frame that spreads across all Doppler bins. // spreads across all Doppler bins.
input wire use_long_chirp, input wire [1:0] wave_sel,
// ========== STATUS ========== // ========== STATUS ==========
output reg mti_first_chirp, // 1 during first chirp (output muted) output reg mti_first_chirp, // 1 during first chirp (output muted)
@@ -109,7 +109,7 @@ reg signed [DATA_WIDTH-1:0] range_i_d1, range_q_d1;
reg range_valid_d1; reg range_valid_d1;
reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_d1; reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_d1;
reg mti_enable_d1; reg mti_enable_d1;
reg use_long_chirp_d1; reg [1:0] wave_sel_d1;
always @(posedge clk or negedge reset_n) begin always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin if (!reset_n) begin
@@ -118,14 +118,14 @@ always @(posedge clk or negedge reset_n) begin
range_valid_d1 <= 1'b0; range_valid_d1 <= 1'b0;
range_bin_d1 <= {`RP_RANGE_BIN_WIDTH_MAX{1'b0}}; range_bin_d1 <= {`RP_RANGE_BIN_WIDTH_MAX{1'b0}};
mti_enable_d1 <= 1'b0; mti_enable_d1 <= 1'b0;
use_long_chirp_d1 <= 1'b0; wave_sel_d1 <= `RP_WAVE_SHORT;
end else begin end else begin
range_i_d1 <= range_i_in; range_i_d1 <= range_i_in;
range_q_d1 <= range_q_in; range_q_d1 <= range_q_in;
range_valid_d1 <= range_valid_in; range_valid_d1 <= range_valid_in;
range_bin_d1 <= range_bin_in; range_bin_d1 <= range_bin_in;
mti_enable_d1 <= mti_enable; mti_enable_d1 <= mti_enable;
use_long_chirp_d1 <= use_long_chirp; wave_sel_d1 <= wave_sel;
end end
end end
@@ -160,14 +160,14 @@ end
reg has_previous; reg has_previous;
// Waveform of the chirp whose profile currently lives in prev_i/prev_q. // 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 // Latched on every range_valid_d1 (wave_sel_d1 is constant within a chirp,
// chirp, so this stays consistent inside a chirp; at the first sample of // so this stays consistent inside a chirp; at the first sample of the
// the *next* chirp the OLD value is still present for the combinational // *next* chirp the OLD value is still present for the combinational
// `waveform_changed` compare, then updates this cycle to the new value). // `waveform_changed` compare, then updates this cycle to the new value).
// Updating per-cycle (rather than only at the last bin) keeps the tag // Updating per-cycle (rather than only at the last bin) keeps the tag
// correct when range_bin_decimator early-terminates a chirp before // correct when range_bin_decimator early-terminates a chirp before
// `range_bin_d1` ever reaches NUM_RANGE_BINS - 1 (RX-F). // `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 // 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 effective_has_previous = has_previous || chirp_boundary;
wire waveform_changed = effective_has_previous 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) // 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}}; range_bin_out <= {`RP_RANGE_BIN_WIDTH_MAX{1'b0}};
has_previous <= 1'b0; has_previous <= 1'b0;
mti_first_chirp <= 1'b1; mti_first_chirp <= 1'b1;
prev_chirp_was_long <= 1'b0; prev_chirp_wave_sel <= `RP_WAVE_SHORT;
mti_saturation_count <= 8'd0; mti_saturation_count <= 8'd0;
saw_nonzero_bin_in_chirp <= 1'b0; saw_nonzero_bin_in_chirp <= 1'b0;
end else begin end else begin
@@ -261,7 +261,7 @@ always @(posedge clk or negedge reset_n) begin
// still visible to the combinational `waveform_changed` compare // still visible to the combinational `waveform_changed` compare
// (read-before-write semantics), then updates this cycle to the // (read-before-write semantics), then updates this cycle to the
// new chirp's value. // 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 // Arm has_previous on either the original last-bin trigger OR a
// chirp_boundary (RX-F). After this cycle, prev_i/prev_q holds // 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; saw_nonzero_bin_in_chirp <= 1'b0;
end else if (!effective_has_previous || waveform_changed) begin end else if (!effective_has_previous || waveform_changed) begin
// No valid previous chirp to subtract from — either the very // No valid previous chirp to subtract from — either the very
// first chirp after reset/enable, or the long↔short boundary // first chirp after reset/enable, or a sub-frame waveform
// in range_mode=01 where the prev buffer holds a different // transition (SHORT->MEDIUM, MEDIUM->LONG, etc.) where the
// waveform's profile. Mute output (emit zeros with valid=1 // prev buffer holds a different waveform's profile. Mute
// so Doppler still sees the expected chirp count), overwrite // output (emit zeros with valid=1 so Doppler still sees the
// prev_i/prev_q as this chirp streams through the write port. // expected chirp count), overwrite prev_i/prev_q as this
// chirp streams through the write port.
range_i_out <= {DATA_WIDTH{1'b0}}; range_i_out <= {DATA_WIDTH{1'b0}};
range_q_out <= {DATA_WIDTH{1'b0}}; range_q_out <= {DATA_WIDTH{1'b0}};
range_valid_out <= 1'b1; range_valid_out <= 1'b1;
-499
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
+58 -53
View File
@@ -122,16 +122,18 @@ module radar_receiver_final (
); );
// ========== INTERNAL SIGNALS ========== // ========== INTERNAL SIGNALS ==========
wire use_long_chirp; // chirp_counter is an input port (driven by the transmitter — bug NEW-1).
// NOTE: chirp_counter is now an input port (was undriven internal wire — bug NEW-1) // chirp_scheduler emits the canonical wave_sel rail and 1-cycle chirp_pulse;
wire chirp_start; // no use_long_chirp shim and no mc_new_*-toggle XOR converters.
wire azimuth_change; wire [1:0] wave_sel;
wire elevation_change; wire chirp_pulse;
wire subframe_pulse; // unused on RX in PR-D; doppler picks up in PR-F
// Mode controller outputs → matched_filter_multi_segment wire frame_pulse; // unused on RX in PR-D; PR-F doppler driver
wire mc_new_chirp; wire [5:0] sched_chirp_counter;
wire mc_new_elevation; wire [1:0] sched_subframe_id;
wire mc_new_azimuth; 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 [1:0] segment_request;
wire mem_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). // depending on wave_sel — selected by chirp_reference_rom).
wire [15:0] ref_chirp_real, ref_chirp_imag; 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 ========== // ========== DOPPLER PROCESSING SIGNALS ==========
wire [31:0] range_data_32bit; wire [31:0] range_data_32bit;
wire range_data_valid; 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 [`RP_RANGE_BIN_WIDTH_MAX-1:0] mti_range_bin; // 9-bit
wire mti_first_chirp; 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 ========== // ========== MODULE INSTANTIATIONS ==========
// 0. Radar Mode Controller — drives chirp/elevation/azimuth timing signals // 0. Chirp scheduler (chirp-v2 PR-D) — single source of truth for waveform
// Default mode: auto-scan (2'b01). Change to 2'b00 for STM32 pass-through. // identity and inter-chirp timing on the RX side. Replaces the legacy
radar_mode_controller rmc ( // 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), .clk(clk),
.reset_n(reset_n), .reset_n(reset_n),
.mode(host_mode), // Controlled by host via USB (default: 2'b01 auto-scan) .host_mode(host_mode),
.range_mode(host_range_mode), // Range mode: 00=3km, 01=long-range (drives chirp type) .host_subframe_enable(`RP_DEF_SUBFRAME_ENABLE),
.stm32_new_chirp(stm32_new_chirp_rx), .host_short_chirp_cycles (host_short_chirp_cycles),
.stm32_new_elevation(stm32_new_elevation_rx), .host_short_listen_cycles(host_short_listen_cycles),
.stm32_new_azimuth(stm32_new_azimuth_rx), .host_medium_chirp_cycles (16'd`RP_DEF_MEDIUM_CHIRP_CYCLES),
.trigger(host_trigger), // Single-chirp trigger from host via USB .host_medium_listen_cycles(16'd`RP_DEF_MEDIUM_LISTEN_CYCLES),
// Gap 2: Runtime-configurable timing from host USB commands .host_long_chirp_cycles (host_long_chirp_cycles),
.cfg_long_chirp_cycles(host_long_chirp_cycles), .host_long_listen_cycles(host_long_listen_cycles),
.cfg_long_listen_cycles(host_long_listen_cycles), .host_guard_cycles(host_guard_cycles),
.cfg_guard_cycles(host_guard_cycles), .host_chirps_per_subframe(6'd`RP_DEF_CHIRPS_PER_SUBFRAME),
.cfg_short_chirp_cycles(host_short_chirp_cycles), .host_trigger(host_trigger),
.cfg_short_listen_cycles(host_short_listen_cycles), .host_debug_wave_sel(`RP_WAVE_SHORT),
.cfg_chirps_per_elev(host_chirps_per_elev), .host_track_request(1'b0),
.use_long_chirp(use_long_chirp), .host_track_wave_sel(`RP_WAVE_SHORT),
.mc_new_chirp(mc_new_chirp), .host_track_chirp_count(`RP_DEF_TRACK_CHIRP_COUNT),
.mc_new_elevation(mc_new_elevation), .host_track_beam_az(6'd0),
.mc_new_azimuth(mc_new_azimuth), .host_track_beam_el(6'd0),
.chirp_count(rmc_chirp_count), .stm32_new_chirp (stm32_new_chirp_rx),
.elevation_count(rmc_elevation_count), .stm32_new_subframe(stm32_new_elevation_rx),
.azimuth_count(rmc_azimuth_count), .stm32_new_frame (stm32_new_azimuth_rx),
.scanning(rmc_scanning), .wave_sel(wave_sel),
.scan_complete(rmc_scan_complete) .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; wire clk_400m;
@@ -461,11 +468,9 @@ matched_filter_multi_segment mf_dual (
.ddc_i({{2{gc_i[15]}}, gc_i}), .ddc_i({{2{gc_i[15]}}, gc_i}),
.ddc_q({{2{gc_q[15]}}, gc_q}), .ddc_q({{2{gc_q[15]}}, gc_q}),
.ddc_valid(gc_valid), .ddc_valid(gc_valid),
.use_long_chirp(use_long_chirp), .wave_sel(wave_sel),
.chirp_counter(chirp_counter), .chirp_counter(chirp_counter),
.mc_new_chirp(mc_new_chirp), .chirp_pulse(chirp_pulse),
.mc_new_elevation(mc_new_elevation),
.mc_new_azimuth(mc_new_azimuth),
.ref_chirp_real(ref_chirp_real), // 1-FF aligned ref (RX-B fix) .ref_chirp_real(ref_chirp_real), // 1-FF aligned ref (RX-B fix)
.ref_chirp_imag(ref_chirp_imag), .ref_chirp_imag(ref_chirp_imag),
.segment_request(segment_request), .segment_request(segment_request),
@@ -517,7 +522,7 @@ mti_canceller #(
.range_valid_out(mti_range_valid), .range_valid_out(mti_range_valid),
.range_bin_out(mti_range_bin), .range_bin_out(mti_range_bin),
.mti_enable(host_mti_enable), .mti_enable(host_mti_enable),
.use_long_chirp(use_long_chirp), .wave_sel(wave_sel),
.mti_first_chirp(mti_first_chirp), .mti_first_chirp(mti_first_chirp),
.mti_saturation_count(mti_saturation_count_out) .mti_saturation_count(mti_saturation_count_out)
); );
+9 -9
View File
@@ -251,9 +251,9 @@ reg [5:0] host_stream_control;
reg [3:0] host_gain_shift; reg [3:0] host_gain_shift;
// Gap 2: Host-configurable chirp timing registers // Gap 2: Host-configurable chirp timing registers
// These override the compile-time defaults in radar_mode_controller when // chirp_scheduler (chirp-v2 PR-D) consumes these directly — no parameter
// written via USB command. Defaults match the parameter values in // overrides. Defaults match the legacy values so behavior is unchanged
// radar_mode_controller.v so behavior is unchanged until the host writes them. // 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_chirp_cycles; // Opcode 0x10 (default 3000)
reg [15:0] host_long_listen_cycles; // Opcode 0x11 (default 13700) reg [15:0] host_long_listen_cycles; // Opcode 0x11 (default 13700)
reg [15:0] host_guard_cycles; // Opcode 0x12 (default 17540) 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_attack(host_agc_attack),
.host_agc_decay(host_agc_decay), .host_agc_decay(host_agc_decay),
.host_agc_holdoff(host_agc_holdoff), .host_agc_holdoff(host_agc_holdoff),
// STM32 toggle signals for RX mode controller (mode 00 pass-through). // STM32 toggle signals for the RX scheduler (mode 00 pass-through).
// These are the raw GPIO inputs — the RX mode controller's edge detectors // Raw GPIO inputs — chirp_scheduler's edge detectors handle debouncing.
// (inside radar_mode_controller) handle debouncing/edge detection.
.stm32_new_chirp_rx(stm32_new_chirp), .stm32_new_chirp_rx(stm32_new_chirp),
.stm32_new_elevation_rx(stm32_new_elevation), .stm32_new_elevation_rx(stm32_new_elevation),
.stm32_new_azimuth_rx(stm32_new_azimuth), .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_detect_threshold <= 16'd10000; // Default threshold
host_stream_control <= `RP_STREAM_CTRL_DEFAULT; // Default: all streams, mag-only mode host_stream_control <= `RP_STREAM_CTRL_DEFAULT; // Default: all streams, mag-only mode
host_gain_shift <= 4'd0; // Default: pass-through (no gain change) 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_chirp_cycles <= 16'd3000;
host_long_listen_cycles <= 16'd13700; host_long_listen_cycles <= 16'd13700;
host_guard_cycles <= 16'd17540; host_guard_cycles <= 16'd17540;
host_short_chirp_cycles <= 16'd50; host_short_chirp_cycles <= 16'd100;
host_short_listen_cycles <= 16'd17450; host_short_listen_cycles <= 16'd17400;
host_chirps_per_elev <= 6'd32; host_chirps_per_elev <= 6'd32;
host_status_request <= 1'b0; host_status_request <= 1'b0;
chirps_mismatch_error <= 1'b0; chirps_mismatch_error <= 1'b0;
+2 -6
View File
@@ -75,7 +75,7 @@ PROD_RTL=(
usb_data_interface.v usb_data_interface.v
usb_data_interface_ft2232h.v usb_data_interface_ft2232h.v
edge_detector.v edge_detector.v
radar_mode_controller.v chirp_scheduler.v
rx_gain_control.v rx_gain_control.v
cfar_ca.v cfar_ca.v
mti_canceller.v mti_canceller.v
@@ -97,7 +97,7 @@ EXTRA_RTL=(
# Receiver chain (used by golden generate/compare tests) # Receiver chain (used by golden generate/compare tests)
RECEIVER_RTL=( RECEIVER_RTL=(
radar_receiver_final.v radar_receiver_final.v
radar_mode_controller.v chirp_scheduler.v
tb/ad9484_interface_400m_stub.v tb/ad9484_interface_400m_stub.v
ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.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 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_rbd_reg.vvp \
tb/tb_range_bin_decimator.v range_bin_decimator.v 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 "" echo ""
# =========================================================================== # ===========================================================================
@@ -68,7 +68,7 @@ set rtl_files [list \
"${rtl_dir}/matched_filter_processing_chain.v" \ "${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \ "${rtl_dir}/nco_400m_enhanced.v" \
"${rtl_dir}/plfm_chirp_controller.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_receiver_final.v" \
"${rtl_dir}/radar_system_top.v" \ "${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \ "${rtl_dir}/radar_transmitter.v" \
+9 -9
View File
@@ -34,7 +34,7 @@ reg signed [DATA_W-1:0] range_q_in;
reg range_valid_in; reg range_valid_in;
reg [5:0] range_bin_in; reg [5:0] range_bin_in;
reg mti_enable; 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_i_out;
wire signed [DATA_W-1:0] range_q_out; wire signed [DATA_W-1:0] range_q_out;
@@ -65,7 +65,7 @@ mti_canceller #(
.range_valid_out(range_valid_out), .range_valid_out(range_valid_out),
.range_bin_out(range_bin_out), .range_bin_out(range_bin_out),
.mti_enable(mti_enable), .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) .mti_first_chirp(mti_first_chirp)
); );
@@ -94,7 +94,7 @@ task do_reset;
range_q_in = 0; range_q_in = 0;
range_valid_in = 0; range_valid_in = 0;
range_bin_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); repeat (5) @(posedge clk);
reset_n = 1; reset_n = 1;
repeat (2) @(posedge clk); repeat (2) @(posedge clk);
@@ -485,7 +485,7 @@ initial begin
mti_enable = 1'b1; mti_enable = 1'b1;
// Chirp A (long, val=1000) — first chirp, muted by first-chirp path. // 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 fork
feed_chirp_const(16'sd1000, 16'sd500); feed_chirp_const(16'sd1000, 16'sd500);
capture_chirp; capture_chirp;
@@ -496,7 +496,7 @@ initial begin
cap_q[0] == 16'sd0); cap_q[0] == 16'sd0);
// Chirp B (long, val=2000) — same waveform: 2000 - 1000 = 1000. // 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; cap_count = 0;
fork fork
feed_chirp_const(16'sd2000, 16'sd1500); feed_chirp_const(16'sd2000, 16'sd1500);
@@ -511,7 +511,7 @@ initial begin
// prev buffer must be overwritten with THIS chirp (not subtracted // prev buffer must be overwritten with THIS chirp (not subtracted
// against the long-waveform chirp B). If R-1 regresses, we'd see // against the long-waveform chirp B). If R-1 regresses, we'd see
// 5000 - 2000 = 3000 here instead of 0. // 5000 - 2000 = 3000 here instead of 0.
tb_use_long_chirp = 1'b0; tb_wave_sel = 2'b00; // RP_WAVE_SHORT
cap_count = 0; cap_count = 0;
fork fork
feed_chirp_const(16'sd5000, 16'sd3000); 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. // Chirp D (short, val=5500) — same waveform as C: 5500 - 5000 = 500.
// This proves the prev buffer was correctly overwritten with C, // This proves the prev buffer was correctly overwritten with C,
// not stuck on B's long-waveform profile. // 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; cap_count = 0;
fork fork
feed_chirp_const(16'sd5500, 16'sd3250); feed_chirp_const(16'sd5500, 16'sd3250);
@@ -538,7 +538,7 @@ initial begin
// Chirp E (short -> long) — another boundary, reverse direction, // Chirp E (short -> long) — another boundary, reverse direction,
// confirms muting is symmetric. // confirms muting is symmetric.
tb_use_long_chirp = 1'b1; tb_wave_sel = 2'b10; // RP_WAVE_LONG
cap_count = 0; cap_count = 0;
fork fork
feed_chirp_const(16'sd9000, 16'sd4000); feed_chirp_const(16'sd9000, 16'sd4000);
@@ -566,7 +566,7 @@ initial begin
// ================================================================ // ================================================================
do_reset; do_reset;
mti_enable = 1'b1; 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. // Chirp 1: early-terminate at bin 31 (only 32/64 bins). I=1000, Q=500.
begin : t13_partial_chirp begin : t13_partial_chirp
+23 -23
View File
@@ -32,7 +32,7 @@ localparam FFT_SIZE = 1024;
localparam SEGMENT_ADVANCE = 896; // 1024 - 128 localparam SEGMENT_ADVANCE = 896; // 1024 - 128
localparam OVERLAP_SAMPLES = 128; localparam OVERLAP_SAMPLES = 128;
localparam LONG_SEGMENTS = 4; 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 LONG_CHIRP_SAMPLES = 3000;
localparam TIMEOUT = 500000; // Max clocks per operation 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_i;
reg signed [17:0] ddc_q; reg signed [17:0] ddc_q;
reg ddc_valid; reg ddc_valid;
reg use_long_chirp; reg [1:0] wave_sel;
reg [5:0] chirp_counter; reg [5:0] chirp_counter;
reg mc_new_chirp; reg chirp_pulse;
reg mc_new_elevation;
reg mc_new_azimuth;
reg [15:0] ref_chirp_real; reg [15:0] ref_chirp_real;
reg [15:0] ref_chirp_imag; reg [15:0] ref_chirp_imag;
reg mem_ready; 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_i_w;
wire signed [15:0] pc_q_w; wire signed [15:0] pc_q_w;
wire pc_valid_w; wire pc_valid_w;
@@ -77,11 +79,9 @@ matched_filter_multi_segment dut (
.ddc_i(ddc_i), .ddc_i(ddc_i),
.ddc_q(ddc_q), .ddc_q(ddc_q),
.ddc_valid(ddc_valid), .ddc_valid(ddc_valid),
.use_long_chirp(use_long_chirp), .wave_sel(wave_sel),
.chirp_counter(chirp_counter), .chirp_counter(chirp_counter),
.mc_new_chirp(mc_new_chirp), .chirp_pulse(chirp_pulse),
.mc_new_elevation(mc_new_elevation),
.mc_new_azimuth(mc_new_azimuth),
.ref_chirp_real(ref_chirp_real), .ref_chirp_real(ref_chirp_real),
.ref_chirp_imag(ref_chirp_imag), .ref_chirp_imag(ref_chirp_imag),
.segment_request(segment_request), .segment_request(segment_request),
@@ -176,11 +176,9 @@ task apply_reset;
ddc_i <= 18'd0; ddc_i <= 18'd0;
ddc_q <= 18'd0; ddc_q <= 18'd0;
ddc_valid <= 1'b0; ddc_valid <= 1'b0;
use_long_chirp <= 1'b0; wave_sel <= 2'b00; // RP_WAVE_SHORT
chirp_counter <= 6'd0; chirp_counter <= 6'd0;
mc_new_chirp <= 1'b0; chirp_pulse <= 1'b0;
mc_new_elevation <= 1'b0;
mc_new_azimuth <= 1'b0;
ref_chirp_real <= 16'd0; ref_chirp_real <= 16'd0;
ref_chirp_imag <= 16'd0; ref_chirp_imag <= 16'd0;
mem_ready <= 1'b0; mem_ready <= 1'b0;
@@ -301,18 +299,19 @@ initial begin
$display("\n=== TEST 2: Short Chirp (1 segment, zero-padded) ==="); $display("\n=== TEST 2: Short Chirp (1 segment, zero-padded) ===");
apply_reset; apply_reset;
use_long_chirp <= 1'b0; wave_sel <= 2'b00; // RP_WAVE_SHORT
chirp_counter <= 6'd0; chirp_counter <= 6'd0;
@(posedge clk); @(posedge clk);
// Trigger chirp start (rising edge on mc_new_chirp) // Trigger chirp start (1-cycle chirp_pulse from chirp_scheduler)
mc_new_chirp <= 1'b1; chirp_pulse <= 1'b1;
@(posedge clk); @(posedge clk);
chirp_pulse <= 1'b0;
@(posedge clk); @(posedge clk);
// Verify FSM transitioned to ST_COLLECT_DATA // Verify FSM transitioned to ST_COLLECT_DATA
check(fsm_state == 4'd1, "Short chirp: entered 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 for (i = 0; i < SHORT_SAMPLES; i = i + 1) begin
@(posedge clk); @(posedge clk);
ddc_i <= (i * 100 + 500) & 18'h3FFFF; // Identifiable values 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) ==="); $display("\n=== TEST 3: Long Chirp (4 segments, overlap-save) ===");
apply_reset; apply_reset;
use_long_chirp <= 1'b1; wave_sel <= 2'b10; // RP_WAVE_LONG
chirp_counter <= 6'd0; chirp_counter <= 6'd0;
@(posedge clk); @(posedge clk);
// Trigger chirp start // Trigger chirp start (1-cycle chirp_pulse)
mc_new_chirp <= 1'b1; chirp_pulse <= 1'b1;
@(posedge clk); @(posedge clk);
chirp_pulse <= 1'b0;
@(posedge clk); @(posedge clk);
check(fsm_state == 4'd1, "Long chirp: entered ST_COLLECT_DATA"); check(fsm_state == 4'd1, "Long chirp: entered ST_COLLECT_DATA");
check(tot_seg == 3'd4, "total_segments = 4"); 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 // Verify we can start a new chirp after the previous one completed
check(fsm_state == 4'd0, "In IDLE before re-trigger"); check(fsm_state == 4'd0, "In IDLE before re-trigger");
// Toggle mc_new_chirp (it was left high, so toggle low then high) // Re-trigger via 1-cycle chirp_pulse
mc_new_chirp <= 1'b0;
repeat(3) @(posedge clk); repeat(3) @(posedge clk);
mc_new_chirp <= 1'b1; chirp_pulse <= 1'b1;
@(posedge clk); @(posedge clk);
chirp_pulse <= 1'b0;
@(posedge clk); @(posedge clk);
@(posedge clk); @(posedge clk);
check(fsm_state == 4'd1, "Re-trigger: entered ST_COLLECT_DATA"); check(fsm_state == 4'd1, "Re-trigger: entered ST_COLLECT_DATA");
@@ -1,750 +0,0 @@
`timescale 1ns / 1ps
module tb_radar_mode_controller;
// ── Parameters ─────────────────────────────────────────────
localparam CLK_PERIOD = 10.0; // 100 MHz
// Use much shorter timing for simulation (100x faster)
localparam SIM_LONG_CHIRP = 30;
localparam SIM_LONG_LISTEN = 137;
localparam SIM_GUARD = 175;
localparam SIM_SHORT_CHIRP = 5;
localparam SIM_SHORT_LISTEN = 175;
// Use small scan size for simulation
localparam SIM_CHIRPS = 4;
localparam SIM_ELEVATIONS = 3;
localparam SIM_AZIMUTHS = 2;
// ── Signals ────────────────────────────────────────────────
reg clk;
reg reset_n;
reg [1:0] mode;
reg stm32_new_chirp;
reg stm32_new_elevation;
reg stm32_new_azimuth;
reg trigger;
// Gap 2: Runtime-configurable timing inputs
reg [15:0] cfg_long_chirp_cycles;
reg [15:0] cfg_long_listen_cycles;
reg [15:0] cfg_guard_cycles;
reg [15:0] cfg_short_chirp_cycles;
reg [15:0] cfg_short_listen_cycles;
reg [5:0] cfg_chirps_per_elev;
reg [1:0] range_mode;
wire use_long_chirp;
wire mc_new_chirp;
wire mc_new_elevation;
wire mc_new_azimuth;
wire [5:0] chirp_count;
wire [5:0] elevation_count;
wire [5:0] azimuth_count;
wire scanning;
wire scan_complete;
// ── Test bookkeeping ───────────────────────────────────────
integer pass_count;
integer fail_count;
integer test_num;
integer csv_file;
integer i;
// Edge detection helpers for auto-scan counting
reg mc_new_chirp_prev;
reg mc_new_elevation_prev;
reg mc_new_azimuth_prev;
integer chirp_toggles;
integer elevation_toggles;
integer azimuth_toggles;
integer scan_completes;
// Saved values for toggle checks
reg saved_mc_new_chirp;
reg saved_mc_new_elevation;
reg saved_mc_new_azimuth;
// ── Clock ──────────────────────────────────────────────────
always #(CLK_PERIOD/2) clk = ~clk;
// ── DUT ────────────────────────────────────────────────────
radar_mode_controller #(
.CHIRPS_PER_ELEVATION (SIM_CHIRPS),
.ELEVATIONS_PER_AZIMUTH(SIM_ELEVATIONS),
.AZIMUTHS_PER_SCAN (SIM_AZIMUTHS),
.LONG_CHIRP_CYCLES (SIM_LONG_CHIRP),
.LONG_LISTEN_CYCLES (SIM_LONG_LISTEN),
.GUARD_CYCLES (SIM_GUARD),
.SHORT_CHIRP_CYCLES (SIM_SHORT_CHIRP),
.SHORT_LISTEN_CYCLES (SIM_SHORT_LISTEN)
) uut (
.clk (clk),
.reset_n (reset_n),
.mode (mode),
.stm32_new_chirp (stm32_new_chirp),
.stm32_new_elevation(stm32_new_elevation),
.stm32_new_azimuth (stm32_new_azimuth),
.trigger (trigger),
// Gap 2: Runtime-configurable timing inputs
.cfg_long_chirp_cycles (cfg_long_chirp_cycles),
.cfg_long_listen_cycles (cfg_long_listen_cycles),
.cfg_guard_cycles (cfg_guard_cycles),
.cfg_short_chirp_cycles (cfg_short_chirp_cycles),
.cfg_short_listen_cycles(cfg_short_listen_cycles),
.cfg_chirps_per_elev (cfg_chirps_per_elev),
.range_mode (range_mode),
// Outputs
.use_long_chirp (use_long_chirp),
.mc_new_chirp (mc_new_chirp),
.mc_new_elevation (mc_new_elevation),
.mc_new_azimuth (mc_new_azimuth),
.chirp_count (chirp_count),
.elevation_count (elevation_count),
.azimuth_count (azimuth_count),
.scanning (scanning),
.scan_complete (scan_complete)
);
// ── Check task ─────────────────────────────────────────────
task check;
input cond;
input [511:0] label;
begin
test_num = test_num + 1;
if (cond) begin
$display("[PASS] Test %0d: %0s", test_num, label);
pass_count = pass_count + 1;
end else begin
$display("[FAIL] Test %0d: %0s", test_num, label);
fail_count = fail_count + 1;
end
end
endtask
// ── Helper: apply reset ────────────────────────────────────
task apply_reset;
begin
reset_n = 0;
mode = 2'b11; // reserved = safe idle
stm32_new_chirp = 0;
stm32_new_elevation = 0;
stm32_new_azimuth = 0;
trigger = 0;
// Gap 2: Set cfg_* to simulation parameter defaults
cfg_long_chirp_cycles = SIM_LONG_CHIRP;
cfg_long_listen_cycles = SIM_LONG_LISTEN;
cfg_guard_cycles = SIM_GUARD;
cfg_short_chirp_cycles = SIM_SHORT_CHIRP;
cfg_short_listen_cycles = SIM_SHORT_LISTEN;
cfg_chirps_per_elev = SIM_CHIRPS;
range_mode = 2'b00; // 3 km short-chirp mode
repeat (4) @(posedge clk);
reset_n = 1;
@(posedge clk); #1;
end
endtask
// ── Stimulus ───────────────────────────────────────────────
initial begin
$dumpfile("tb_radar_mode_controller.vcd");
$dumpvars(0, tb_radar_mode_controller);
clk = 0;
pass_count = 0;
fail_count = 0;
test_num = 0;
// ════════════════════════════════════════════════════════
// TEST GROUP 1: Reset behaviour
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 1: Reset Behaviour ---");
apply_reset;
reset_n = 0;
repeat (4) @(posedge clk); #1;
check(use_long_chirp === 1'b0, "use_long_chirp=0 after reset");
check(mc_new_chirp === 1'b0, "mc_new_chirp=0 after reset");
check(mc_new_elevation === 1'b0, "mc_new_elevation=0 after reset");
check(mc_new_azimuth === 1'b0, "mc_new_azimuth=0 after reset");
check(chirp_count === 6'd0, "chirp_count=0 after reset");
check(elevation_count === 6'd0, "elevation_count=0 after reset");
check(azimuth_count === 6'd0, "azimuth_count=0 after reset");
check(scanning === 1'b0, "scanning=0 after reset");
check(scan_complete === 1'b0, "scan_complete=0 after reset");
reset_n = 1;
@(posedge clk); #1;
// ════════════════════════════════════════════════════════
// TEST GROUP 2: STM32 pass-through mode (mode 00)
// The DUT uses XOR toggle detection: when stm32_new_chirp
// changes from its previous value, the DUT detects it.
// We toggle-and-hold (don't pulse) to get exactly one detection.
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 2: STM32 Pass-through (mode 00) ---");
apply_reset;
mode = 2'b00;
@(posedge clk); #1;
// Save current mc_new_chirp
saved_mc_new_chirp = mc_new_chirp;
// Toggle stm32_new_chirp (0→1, hold at 1)
stm32_new_chirp = 1'b1;
// Wait 2 cycles: 1 for prev register update, 1 for XOR→main FSM
@(posedge clk); @(posedge clk); #1;
check(mc_new_chirp !== saved_mc_new_chirp,
"mc_new_chirp toggles on stm32 chirp change");
check(chirp_count === 6'd1, "chirp_count incremented to 1");
// Toggle again (1→0, hold at 0) — second chirp
saved_mc_new_chirp = mc_new_chirp;
stm32_new_chirp = 1'b0;
@(posedge clk); @(posedge clk); #1;
check(mc_new_chirp !== saved_mc_new_chirp,
"mc_new_chirp toggles again");
check(chirp_count === 6'd2, "chirp_count incremented to 2");
// Toggle stm32_new_elevation (0→1, hold)
saved_mc_new_elevation = mc_new_elevation;
stm32_new_elevation = 1'b1;
@(posedge clk); @(posedge clk); #1;
check(mc_new_elevation !== saved_mc_new_elevation,
"mc_new_elevation toggles on stm32 elevation change");
check(chirp_count === 6'd0,
"chirp_count resets on elevation toggle");
check(elevation_count === 6'd1,
"elevation_count incremented to 1");
// Toggle stm32_new_azimuth (0→1, hold)
saved_mc_new_azimuth = mc_new_azimuth;
stm32_new_azimuth = 1'b1;
@(posedge clk); @(posedge clk); #1;
check(mc_new_azimuth !== saved_mc_new_azimuth,
"mc_new_azimuth toggles on stm32 azimuth change");
check(elevation_count === 6'd0,
"elevation_count resets on azimuth toggle");
check(azimuth_count === 6'd1,
"azimuth_count incremented to 1");
// ════════════════════════════════════════════════════════
// TEST GROUP 3: Auto-scan mode (mode 01) — full scan
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 3: Auto-scan (mode 01) Full Scan ---");
apply_reset;
mode = 2'b01;
csv_file = $fopen("rmc_autoscan.csv", "w");
$fwrite(csv_file, "cycle,chirp,elevation,azimuth,long_chirp,scanning,scan_complete\n");
mc_new_chirp_prev = 0;
mc_new_elevation_prev = 0;
mc_new_azimuth_prev = 0;
chirp_toggles = 0;
elevation_toggles = 0;
azimuth_toggles = 0;
scan_completes = 0;
// Check: scanning starts immediately
@(posedge clk); #1;
check(scanning === 1'b1, "Scanning starts immediately in auto mode");
// Run for enough cycles to complete one full scan
for (i = 0; i < 15000; i = i + 1) begin
@(posedge clk); #1;
if (mc_new_chirp !== mc_new_chirp_prev)
chirp_toggles = chirp_toggles + 1;
if (mc_new_elevation !== mc_new_elevation_prev)
elevation_toggles = elevation_toggles + 1;
if (mc_new_azimuth !== mc_new_azimuth_prev)
azimuth_toggles = azimuth_toggles + 1;
if (scan_complete)
scan_completes = scan_completes + 1;
mc_new_chirp_prev = mc_new_chirp;
mc_new_elevation_prev = mc_new_elevation;
mc_new_azimuth_prev = mc_new_azimuth;
if (i % 100 == 0) begin
$fwrite(csv_file, "%0d,%0d,%0d,%0d,%0d,%0d,%0d\n",
i, chirp_count, elevation_count, azimuth_count,
use_long_chirp, scanning, scan_complete);
end
end
$fclose(csv_file);
$display(" Chirp toggles: %0d (expected %0d)",
chirp_toggles, SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS);
$display(" Elevation toggles: %0d", elevation_toggles);
$display(" Azimuth toggles: %0d", azimuth_toggles);
$display(" Scan completes: %0d", scan_completes);
check(chirp_toggles >= SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS,
"At least 24 chirp toggles in full scan");
check(scan_completes >= 1,
"At least 1 scan completion detected");
check(elevation_toggles >= SIM_AZIMUTHS,
"Elevation toggles >= number of azimuths");
check(azimuth_toggles >= 1,
"Azimuth toggles >= 1");
// ════════════════════════════════════════════════════════
// TEST GROUP 4: Auto-scan chirp timing (3 km mode, short chirps only)
// With range_mode=0, auto-scan skips long chirp and goes directly
// to short chirp. No long→guard→short transition.
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 4: Chirp Timing Sequence (3km short-only) ---");
apply_reset;
mode = 2'b01;
@(posedge clk); #1;
check(use_long_chirp === 1'b0, "3km: starts with short chirp");
repeat (SIM_SHORT_CHIRP / 2) @(posedge clk);
#1;
check(use_long_chirp === 1'b0, "3km: still short chirp midway");
// Wait through short chirp + short listen
repeat (SIM_SHORT_CHIRP / 2 + SIM_SHORT_LISTEN) @(posedge clk);
#1;
// Next chirp should also be short
repeat (2) @(posedge clk); #1;
check(use_long_chirp === 1'b0, "3km: next chirp is also short");
// ════════════════════════════════════════════════════════
// TEST GROUP 5: Single-chirp mode (mode 10)
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 5: Single-chirp Mode (mode 10) ---");
apply_reset;
mode = 2'b10;
repeat (10) @(posedge clk); #1;
check(scanning === 1'b0, "Single mode: idle without trigger");
saved_mc_new_chirp = mc_new_chirp;
// Pulse trigger (rising edge detection)
trigger = 1'b1;
@(posedge clk); #1;
trigger = 1'b0;
repeat (2) @(posedge clk); #1;
check(scanning === 1'b1, "Single mode: scanning after trigger");
check(use_long_chirp === 1'b0, "Single mode: uses short chirp (3km)");
check(mc_new_chirp !== saved_mc_new_chirp,
"Single mode: mc_new_chirp toggled");
// Wait for chirp to complete (short chirp in 3km mode)
repeat (SIM_SHORT_CHIRP + SIM_SHORT_LISTEN + 10) @(posedge clk); #1;
check(scanning === 1'b0, "Single mode: returns to idle after chirp");
// No activity without trigger
saved_mc_new_chirp = mc_new_chirp;
repeat (100) @(posedge clk); #1;
check(mc_new_chirp === saved_mc_new_chirp,
"Single mode: no activity without trigger");
// Second trigger
saved_mc_new_chirp = mc_new_chirp;
trigger = 1'b1;
@(posedge clk); #1;
trigger = 1'b0;
repeat (3) @(posedge clk); #1;
check(mc_new_chirp !== saved_mc_new_chirp,
"Single mode: 2nd trigger works");
// ════════════════════════════════════════════════════════
// TEST GROUP 6: Reserved mode (mode 11) — stays idle
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 6: Reserved Mode (mode 11) ---");
apply_reset;
mode = 2'b11;
repeat (200) @(posedge clk); #1;
check(scanning === 1'b0, "Reserved mode: stays idle");
// ════════════════════════════════════════════════════════
// TEST GROUP 7: Mode switching
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 7: Mode Switching ---");
apply_reset;
mode = 2'b01; // Auto-scan
repeat (100) @(posedge clk); #1;
check(scanning === 1'b1, "Auto mode: scanning");
mode = 2'b11;
repeat (10) @(posedge clk); #1;
check(scanning === 1'b0, "Switching to reserved: stops scanning");
mode = 2'b10;
repeat (10) @(posedge clk); #1;
check(scanning === 1'b0, "Single mode after switch: idle");
trigger = 1'b1;
@(posedge clk); #1;
trigger = 1'b0;
repeat (3) @(posedge clk); #1;
check(scanning === 1'b1, "Single mode after switch: triggers OK");
// ════════════════════════════════════════════════════════
// TEST GROUP 8: STM32 mode — chirp count wrapping
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 8: STM32 Chirp Count Wrapping ---");
apply_reset;
mode = 2'b00;
@(posedge clk); #1;
// Toggle chirp SIM_CHIRPS times (toggle-and-hold each time)
for (i = 0; i < SIM_CHIRPS; i = i + 1) begin
stm32_new_chirp = ~stm32_new_chirp; // toggle and hold
@(posedge clk); @(posedge clk); #1; // wait for detection
end
$display(" chirp_count after %0d toggles: %0d (expect 0)",
SIM_CHIRPS, chirp_count);
check(chirp_count === 6'd0,
"chirp_count wraps after CHIRPS_PER_ELEVATION toggles");
// ════════════════════════════════════════════════════════
// TEST GROUP 9: STM32 mode — full scan completion
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 9: STM32 Full Scan Completion ---");
apply_reset;
mode = 2'b00;
@(posedge clk); #1;
scan_completes = 0;
// Toggle azimuth SIM_AZIMUTHS times
for (i = 0; i < SIM_AZIMUTHS; i = i + 1) begin
stm32_new_azimuth = ~stm32_new_azimuth;
@(posedge clk); #1;
if (scan_complete) scan_completes = scan_completes + 1;
@(posedge clk); #1;
if (scan_complete) scan_completes = scan_completes + 1;
end
$display(" scan_complete pulses: %0d (expect 1)", scan_completes);
check(scan_completes == 1, "scan_complete pulses once after full azimuth sweep");
check(azimuth_count === 6'd0, "azimuth_count wraps to 0 after full scan");
// ════════════════════════════════════════════════════════
// TEST GROUP 10: Reset Mid-Scan
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 10: Reset Mid-Scan ---");
apply_reset;
mode = 2'b01; // auto-scan
// Wait ~200 cycles (partway through first chirp)
repeat (200) @(posedge clk); #1;
check(scanning === 1'b1, "Mid-scan: scanning=1 before reset");
// Assert reset for 4 cycles
reset_n = 0;
repeat (4) @(posedge clk); #1;
// Verify state during reset
check(scanning === 1'b0, "Mid-scan reset: scanning=0");
check(chirp_count === 6'd0, "Mid-scan reset: chirp_count=0");
check(elevation_count === 6'd0, "Mid-scan reset: elevation_count=0");
check(azimuth_count === 6'd0, "Mid-scan reset: azimuth_count=0");
check(use_long_chirp === 1'b0, "Mid-scan reset: use_long_chirp=0");
check(mc_new_chirp === 1'b0, "Mid-scan reset: mc_new_chirp=0");
check(mc_new_elevation === 1'b0, "Mid-scan reset: mc_new_elevation=0");
check(mc_new_azimuth === 1'b0, "Mid-scan reset: mc_new_azimuth=0");
// Release reset
reset_n = 1;
@(posedge clk); #1;
// ════════════════════════════════════════════════════════
// TEST GROUP 11: Mode-Switch State Leakage
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 11: Mode-Switch State Leakage ---");
apply_reset;
mode = 2'b01; // auto-scan
// Run for ~500 cycles
repeat (500) @(posedge clk); #1;
check(scanning === 1'b1, "Leakage: scanning=1 during auto-scan");
// Switch to reserved mode (11) — forces scan_state=S_IDLE
mode = 2'b11;
repeat (10) @(posedge clk); #1;
check(scanning === 1'b0, "Leakage: scanning=0 in reserved mode");
// Switch back to auto-scan (01)
mode = 2'b01;
// Auto-scan S_IDLE transitions to S_LONG_CHIRP on the next clock
// so after 1 cycle scan_state != S_IDLE => scanning=1
@(posedge clk); #1;
// The first cycle in mode 01 hits S_IDLE and transitions out
// scanning should be 1 now (scan_state moved to S_LONG_CHIRP)
check(scanning === 1'b1, "Leakage: auto-scan restarts cleanly (scanning=1)");
// ════════════════════════════════════════════════════════
// TEST GROUP 12: Simultaneous STM32 Toggle Events
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 12: Simultaneous STM32 Toggle Events ---");
apply_reset;
mode = 2'b00;
@(posedge clk); #1;
// Save current toggle outputs
saved_mc_new_chirp = mc_new_chirp;
saved_mc_new_elevation = mc_new_elevation;
// Toggle BOTH stm32_new_chirp AND stm32_new_elevation at the same time
stm32_new_chirp = 1'b1;
stm32_new_elevation = 1'b1;
// Wait 2 cycles for XOR detection
@(posedge clk); @(posedge clk); #1;
check(mc_new_chirp !== saved_mc_new_chirp,
"Simultaneous: mc_new_chirp toggled");
check(mc_new_elevation !== saved_mc_new_elevation,
"Simultaneous: mc_new_elevation toggled");
// Elevation toggle resets chirp_count (last-write-wins in RTL)
check(chirp_count === 6'd0,
"Simultaneous: chirp_count=0 (elevation resets it)");
// ════════════════════════════════════════════════════════
// TEST GROUP 13: Single-Chirp Mode — Multiple Rapid Triggers
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 13: Single-Chirp Multiple Rapid Triggers ---");
apply_reset;
mode = 2'b10;
@(posedge clk); #1;
saved_mc_new_chirp = mc_new_chirp;
// First trigger — should start a chirp
trigger = 1'b1;
@(posedge clk); #1;
trigger = 1'b0;
repeat (2) @(posedge clk); #1;
check(scanning === 1'b1, "Rapid trigger: first trigger starts chirp");
check(mc_new_chirp !== saved_mc_new_chirp,
"Rapid trigger: mc_new_chirp toggled on first trigger");
// Save chirp state after first trigger
saved_mc_new_chirp = mc_new_chirp;
// Send another trigger while chirp is still active (FSM not in S_IDLE)
trigger = 1'b1;
@(posedge clk); #1;
trigger = 1'b0;
repeat (2) @(posedge clk); #1;
check(scanning === 1'b1, "Rapid trigger: still scanning (didn't restart)");
check(mc_new_chirp === saved_mc_new_chirp,
"Rapid trigger: second trigger ignored (mc_new_chirp unchanged)");
// Wait for chirp to complete (short_chirp + short_listen for range_mode=0)
repeat (SIM_SHORT_CHIRP + SIM_SHORT_LISTEN + 20) @(posedge clk); #1;
check(scanning === 1'b0, "Rapid trigger: chirp completed, back to idle");
// Now trigger again — this should work
saved_mc_new_chirp = mc_new_chirp;
trigger = 1'b1;
@(posedge clk); #1;
trigger = 1'b0;
repeat (2) @(posedge clk); #1;
check(scanning === 1'b1, "Rapid trigger: third trigger works after idle");
check(mc_new_chirp !== saved_mc_new_chirp,
"Rapid trigger: mc_new_chirp toggled on third trigger");
// ════════════════════════════════════════════════════════
// TEST GROUP 14: Auto-Scan Counter Verification
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 14: Auto-Scan Counter Verification ---");
apply_reset;
mode = 2'b01; // auto-scan
mc_new_chirp_prev = 0;
chirp_toggles = 0;
scan_completes = 0;
// The first chirp toggle happens on the S_IDLE→S_SHORT_CHIRP transition.
// We need to capture it. Sample after the first posedge so we get the
// initial state right.
@(posedge clk); #1;
// After this clock, scan_state has moved to S_LONG_CHIRP and
// mc_new_chirp has already toggled once. Record its value as prev
// so we can count from here.
mc_new_chirp_prev = mc_new_chirp;
chirp_toggles = 1; // count the initial toggle
// Run until first scan_complete
// Total chirps = 4*3*2 = 24, each chirp ~523 cycles
// 24*523 = 12552, add margin
// NOTE: When scan_complete fires (S_ADVANCE full-scan branch), the DUT
// simultaneously toggles mc_new_chirp for the NEXT scan's first chirp.
// We must check scan_complete before counting the toggle so we don't
// include that restart toggle in our count of the current scan's chirps.
for (i = 0; i < 14000; i = i + 1) begin
@(posedge clk); #1;
if (scan_complete)
scan_completes = scan_completes + 1;
// Stop BEFORE counting the toggle that coincides with scan_complete
// (that toggle starts the next scan, not the current one)
if (scan_completes >= 1)
i = 14000; // break
else begin
if (mc_new_chirp !== mc_new_chirp_prev)
chirp_toggles = chirp_toggles + 1;
mc_new_chirp_prev = mc_new_chirp;
end
end
$display(" Total chirp toggles: %0d (expected 24)", chirp_toggles);
$display(" Scan completes: %0d (expected 1)", scan_completes);
// At scan_complete, the DUT wraps all counters and immediately starts
// a new chirp (transitions to S_LONG_CHIRP, not S_IDLE). The counters
// are reset to 0 in the full-scan-complete branch of S_ADVANCE.
check(scan_completes == 1, "Counter verify: exactly 1 scan_complete");
// The full-scan-complete branch resets all counters to 0:
check(chirp_count === 6'd0, "Counter verify: chirp_count=0 at scan_complete");
check(elevation_count === 6'd0, "Counter verify: elevation_count=0 at scan_complete");
check(azimuth_count === 6'd0, "Counter verify: azimuth_count=0 at scan_complete");
check(chirp_toggles == SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS,
"Counter verify: exactly 24 chirp toggles");
// ════════════════════════════════════════════════════════
// TEST GROUP 15: STM32 Mode — Counter Persistence
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 15: STM32 Mode Counter Persistence ---");
apply_reset;
mode = 2'b00;
@(posedge clk); #1;
// Toggle chirp 3 times
for (i = 0; i < 3; i = i + 1) begin
stm32_new_chirp = ~stm32_new_chirp;
@(posedge clk); @(posedge clk); #1;
end
$display(" chirp_count after 3 toggles: %0d (expect 3)", chirp_count);
check(chirp_count === 6'd3, "Persistence: chirp_count=3 after 3 toggles");
// Switch to reserved mode (11) — does NOT reset counters
mode = 2'b11;
repeat (10) @(posedge clk); #1;
$display(" chirp_count in reserved mode: %0d (expect 3)", chirp_count);
check(chirp_count === 6'd3, "Persistence: chirp_count=3 in reserved mode");
// Switch back to STM32 mode (00)
mode = 2'b00;
@(posedge clk); #1;
$display(" chirp_count after returning to STM32: %0d (expect 3)", chirp_count);
check(chirp_count === 6'd3, "Persistence: chirp_count=3 after mode roundtrip");
// Toggle chirp once more — should wrap (3+1=4=CHIRPS, wraps to 0)
stm32_new_chirp = ~stm32_new_chirp;
@(posedge clk); @(posedge clk); #1;
$display(" chirp_count after 4th toggle: %0d (expect 0)", chirp_count);
check(chirp_count === 6'd0, "Persistence: chirp_count wraps to 0 at 4th toggle");
// ════════════════════════════════════════════════════════
// TEST GROUP 16: Runtime Timing Reconfiguration (Gap 2)
// Verify that changing cfg_* mid-simulation changes timing.
// We halve the long chirp duration and verify the chirp
// completes in fewer cycles.
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 16: Runtime Timing Reconfiguration (Gap 2) ---");
apply_reset;
mode = 2'b01; // auto-scan
// Let the first chirp start (S_IDLE -> S_SHORT_CHIRP in 3km mode)
@(posedge clk); #1;
check(scanning === 1'b1, "Reconfig: auto-scan started");
check(use_long_chirp === 1'b0, "Reconfig: starts with short chirp (3km)");
// Wait ~half the default short chirp time to confirm we're still in S_SHORT_CHIRP
// S_SHORT_CHIRP state index: check via scan_state
repeat (SIM_SHORT_CHIRP / 2) @(posedge clk); #1;
// Now change cfg_short_chirp_cycles to a much shorter value mid-scan.
// The timer is already at ~SIM_SHORT_CHIRP/2, so setting cycles to 1
// means the FSM will advance on the next cycle.
cfg_short_chirp_cycles = 1;
repeat (2) @(posedge clk); #1;
// Restore default and verify scan continues
cfg_short_chirp_cycles = SIM_SHORT_CHIRP;
repeat (10) @(posedge clk); #1;
check(scanning === 1'b1, "Reconfig: scan continues after restoring default");
// Test runtime chirps_per_elev change:
// Reset and set chirps_per_elev to 2 (instead of default 4)
apply_reset;
cfg_chirps_per_elev = 6'd2;
mode = 2'b01; // auto-scan
mc_new_chirp_prev = 0;
chirp_toggles = 0;
elevation_toggles = 0;
@(posedge clk); #1;
mc_new_chirp_prev = mc_new_chirp;
mc_new_elevation_prev = mc_new_elevation;
chirp_toggles = 1; // initial toggle
// Run enough cycles for a few chirps + elevation advance
// With 2 chirps/elev: each chirp ~180 cycles (5+175) for short-only 3km mode
// 2 chirps = ~684 cycles, then elevation advance
for (i = 0; i < 2000; i = i + 1) begin
@(posedge clk); #1;
if (mc_new_chirp !== mc_new_chirp_prev)
chirp_toggles = chirp_toggles + 1;
if (mc_new_elevation !== mc_new_elevation_prev)
elevation_toggles = elevation_toggles + 1;
mc_new_chirp_prev = mc_new_chirp;
mc_new_elevation_prev = mc_new_elevation;
end
$display(" chirp_toggles=%0d elevation_toggles=%0d (cfg_chirps_per_elev=2)",
chirp_toggles, elevation_toggles);
// With 2 chirps/elev, we should get elevation toggles at every 2 chirps
check(elevation_toggles >= 1,
"Reconfig: elevation advances with cfg_chirps_per_elev=2");
// Verify the ratio: chirp_toggles should be ~2x elevation_toggles
// (first elevation has 2 chirps, then toggle. Second has 2 chirps, then toggle, etc.)
check(chirp_toggles >= 2 * elevation_toggles,
"Reconfig: chirp/elevation ratio consistent with cfg_chirps_per_elev=2");
// Restore defaults
cfg_chirps_per_elev = SIM_CHIRPS;
// ════════════════════════════════════════════════════════
// Summary
// ════════════════════════════════════════════════════════
$display("");
$display("========================================");
$display(" RADAR MODE CONTROLLER RESULTS");
$display(" PASSED: %0d / %0d", pass_count, test_num);
$display(" FAILED: %0d / %0d", fail_count, test_num);
if (fail_count == 0)
$display(" ** ALL TESTS PASSED **");
else
$display(" ** SOME TESTS FAILED **");
$display("========================================");
$display("");
#100;
$finish;
end
endmodule
@@ -41,7 +41,7 @@
// //
// Strategy: // Strategy:
// - Uses behavioral stub for ad9484_interface_400m (no Xilinx primitives) // - 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) // - Feeds 120 MHz tone at ADC input (IF frequency -> DDC passband)
// - Verifies structural correctness + golden comparison + bounds checks // - Verifies structural correctness + golden comparison + bounds checks
// //
@@ -103,6 +103,9 @@ reg mc_new_chirp_prev;
reg tx_frame_start; reg tx_frame_start;
reg [5:0] rmc_chirp_prev; 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 always @(posedge clk_100m or negedge reset_n) begin
if (!reset_n) begin if (!reset_n) begin
chirp_counter <= 6'd0; chirp_counter <= 6'd0;
@@ -110,17 +113,16 @@ always @(posedge clk_100m or negedge reset_n) begin
tx_frame_start <= 1'b0; tx_frame_start <= 1'b0;
rmc_chirp_prev <= 6'd0; rmc_chirp_prev <= 6'd0;
end else begin end else begin
mc_new_chirp_prev <= dut.mc_new_chirp; if (dut.chirp_pulse) begin
if (dut.mc_new_chirp != mc_new_chirp_prev) begin
chirp_counter <= chirp_counter + 1; chirp_counter <= chirp_counter + 1;
end 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; 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; tx_frame_start <= 1'b1;
end end
rmc_chirp_prev <= dut.rmc_chirp_count; rmc_chirp_prev <= dut.sched_chirp_counter;
end end
end end
@@ -163,13 +165,16 @@ radar_receiver_final dut (
.host_range_mode(2'b01), // long-range mode (dual chirp); was missing -> z .host_range_mode(2'b01), // long-range mode (dual chirp); was missing -> z
.host_trigger(1'b0), .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_chirp_cycles(16'd500),
.host_long_listen_cycles(16'd2000), .host_long_listen_cycles(16'd2000),
.host_guard_cycles(16'd500), .host_guard_cycles(16'd500),
.host_short_chirp_cycles(16'd50), .host_short_chirp_cycles(16'd100),
.host_short_listen_cycles(16'd1000), .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 // Fix 3: digital gain control — pass-through for golden reference
.host_gain_shift(4'd0), .host_gain_shift(4'd0),
@@ -180,19 +185,13 @@ radar_receiver_final dut (
); );
// ============================================================================ // ============================================================================
// OVERRIDE TIMING PARAMETERS via defparam // SIM TIMING — driven via host_* inputs above (chirp-v2 PR-D).
// ============================================================================ // chirp_scheduler is host-input driven; no defparam overrides needed.
// Reduce radar_mode_controller timing to keep simulation tractable.
// Real values: LONG_CHIRP=3000, LONG_LISTEN=13700, GUARD=17540, // Real values: LONG_CHIRP=3000, LONG_LISTEN=13700, GUARD=17540,
// SHORT_CHIRP=50, SHORT_LISTEN=17450 (total ~51740 per chirp) // SHORT_CHIRP=100 (V2), SHORT_LISTEN=17400 (~31040 per chirp).
// Need enough DDC samples to fill MF buffer (896) plus latency buffer (3187). // The host_* assignments above feed the same compressed timing the legacy
// At ~1 DDC sample per sys_clk, we need at least ~5000 sys_clk per chirp. // defparams used.
// 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;
// ============================================================================ // ============================================================================
// TEST INFRASTRUCTURE // TEST INFRASTRUCTURE
@@ -34,15 +34,13 @@ module tb_rxb_fullchain_latency;
reg clk; reg clk;
reg reset_n; 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_i;
reg signed [17:0] ddc_q; reg signed [17:0] ddc_q;
reg ddc_valid; reg ddc_valid;
reg use_long_chirp; reg [1:0] wave_sel_r; // SHORT/MEDIUM/LONG selector
reg [5:0] chirp_counter; reg [5:0] chirp_counter;
reg mc_new_chirp; reg chirp_pulse; // 1-cycle pulse on chirp start
reg mc_new_elevation;
reg mc_new_azimuth;
// multi_segment <-> chirp_reference_rom interconnect // multi_segment <-> chirp_reference_rom interconnect
wire [1:0] segment_request; wire [1:0] segment_request;
@@ -60,8 +58,9 @@ module tb_rxb_fullchain_latency;
wire pc_valid; wire pc_valid;
wire [3:0] ms_status; wire [3:0] ms_status;
// wave_sel shim — matches radar_receiver_final.v PR-C transitional wiring. // wave_sel drives both the ROM and the matched filter (chirp-v2 PR-D
wire [1:0] wave_sel = use_long_chirp ? `RP_WAVE_LONG : `RP_WAVE_SHORT; // 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-v2 PR-C) -----
chirp_reference_rom chirp_rom ( chirp_reference_rom chirp_rom (
@@ -97,11 +96,9 @@ module tb_rxb_fullchain_latency;
.ddc_i (ddc_i), .ddc_i (ddc_i),
.ddc_q (ddc_q), .ddc_q (ddc_q),
.ddc_valid (ddc_valid), .ddc_valid (ddc_valid),
.use_long_chirp (use_long_chirp), .wave_sel (wave_sel),
.chirp_counter (chirp_counter), .chirp_counter (chirp_counter),
.mc_new_chirp (mc_new_chirp), .chirp_pulse (chirp_pulse),
.mc_new_elevation (mc_new_elevation),
.mc_new_azimuth (mc_new_azimuth),
.ref_chirp_real (ref_i_d), .ref_chirp_real (ref_i_d),
.ref_chirp_imag (ref_q_d), .ref_chirp_imag (ref_q_d),
.segment_request (segment_request), .segment_request (segment_request),
@@ -225,11 +222,9 @@ module tb_rxb_fullchain_latency;
ddc_i = 0; ddc_i = 0;
ddc_q = 0; ddc_q = 0;
ddc_valid = 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; chirp_counter = 6'd0;
mc_new_chirp = 1'b0; chirp_pulse = 1'b0;
mc_new_elevation = 1'b0;
mc_new_azimuth = 1'b0;
// Load the same short-chirp samples the ROM will serve as ref, // Load the same short-chirp samples the ROM will serve as ref,
// so signal == ref → autocorrelation. Peak should be at bin 0 if // 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("FFT_SIZE: %0d, SHORT_LEN: %0d", FFT_SIZE, SHORT_LEN);
$display(""); $display("");
// Pulse mc_new_chirp // Pulse chirp_pulse for one cycle (chirp-v2 PR-D contract)
$display("[T=%0t] Pulsing mc_new_chirp HIGH...", $time); $display("[T=%0t] Pulsing chirp_pulse HIGH...", $time);
@(posedge clk); @(posedge clk);
#1 mc_new_chirp = 1'b1; #1 chirp_pulse = 1'b1;
repeat (4) @(posedge clk); @(posedge clk);
#1 mc_new_chirp = 1'b0; #1 chirp_pulse = 1'b0;
// Feed signal samples (same as ref → autocorrelation) // Feed signal samples (same as ref → autocorrelation)
feed_short_chirp_signal; feed_short_chirp_signal;
+10 -8
View File
@@ -35,7 +35,7 @@
* chirp_reference_rom.v \ * chirp_reference_rom.v \
* matched_filter_multi_segment.v matched_filter_processing_chain.v \ * matched_filter_multi_segment.v matched_filter_processing_chain.v \
* range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.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: * Run:
* vvp tb/tb_system_e2e.vvp * vvp tb/tb_system_e2e.vvp
@@ -873,14 +873,16 @@ initial begin
check(obs_range_valid_count > saved_range_count, check(obs_range_valid_count > saved_range_count,
"G8.1: Auto-scan generated range profile output autonomously"); "G8.1: Auto-scan generated range profile output autonomously");
// G8.2: Receiver mode controller chirp counter advanced // G8.2: chirp_scheduler chirp counter advanced (chirp-v2 PR-D)
// Access the RX-side mode controller chirp count directly. // Sub-frame id replaces "elevation" in the v2 contract.
check(dut.rx_inst.rmc_chirp_count > 0 || dut.rx_inst.rmc_elevation_count > 0, check(dut.rx_inst.sched_chirp_counter > 0 || dut.rx_inst.sched_subframe_id > 0,
"G8.2: RX mode controller chirp/elevation counters advanced"); "G8.2: chirp_scheduler chirp/sub-frame counters advanced");
// G8.3: RX-side elevation counter incremented (4 chirps/elev) // G8.3: sub-frame index incremented (auto-scan walks SHORT->MEDIUM->LONG).
check(dut.rx_inst.rmc_elevation_count >= 1, // chirps_per_subframe = 16 in PR-D, so a sub-frame transition implies the
"G8.3: RX elevation counter incremented in auto-scan"); // 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 // G8.4: Switch to single-chirp mode — auto-scan stops
bfm_send_cmd(8'h01, 8'h00, 16'h0002); // mode = 10 = single chirp bfm_send_cmd(8'h01, 8'h00, 16'h0002); // mode = 10 = single chirp