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