diff --git a/9_Firmware/9_2_FPGA/chirp_scheduler.v b/9_Firmware/9_2_FPGA/chirp_scheduler.v new file mode 100644 index 0000000..a749ae8 --- /dev/null +++ b/9_Firmware/9_2_FPGA/chirp_scheduler.v @@ -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 diff --git a/9_Firmware/9_2_FPGA/formal/fv_radar_mode_controller.v b/9_Firmware/9_2_FPGA/formal/fv_radar_mode_controller.v deleted file mode 100644 index 6636b76..0000000 --- a/9_Firmware/9_2_FPGA/formal/fv_radar_mode_controller.v +++ /dev/null @@ -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 diff --git a/9_Firmware/9_2_FPGA/matched_filter_multi_segment.v b/9_Firmware/9_2_FPGA/matched_filter_multi_segment.v index 7ab4f95..71ce80e 100644 --- a/9_Firmware/9_2_FPGA/matched_filter_multi_segment.v +++ b/9_Firmware/9_2_FPGA/matched_filter_multi_segment.v @@ -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 diff --git a/9_Firmware/9_2_FPGA/mti_canceller.v b/9_Firmware/9_2_FPGA/mti_canceller.v index c483fd5..c54ea3c 100644 --- a/9_Firmware/9_2_FPGA/mti_canceller.v +++ b/9_Firmware/9_2_FPGA/mti_canceller.v @@ -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; diff --git a/9_Firmware/9_2_FPGA/radar_mode_controller.v b/9_Firmware/9_2_FPGA/radar_mode_controller.v deleted file mode 100644 index 38a918a..0000000 --- a/9_Firmware/9_2_FPGA/radar_mode_controller.v +++ /dev/null @@ -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 diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index 2b68ff2..5f49eee 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -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) ); diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index 17b7dfd..b5bb872 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -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; diff --git a/9_Firmware/9_2_FPGA/run_regression.sh b/9_Firmware/9_2_FPGA/run_regression.sh index 8683de1..7735f16 100755 --- a/9_Firmware/9_2_FPGA/run_regression.sh +++ b/9_Firmware/9_2_FPGA/run_regression.sh @@ -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 "" # =========================================================================== diff --git a/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl b/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl index 0a183b7..5fa250f 100644 --- a/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl +++ b/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl @@ -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" \ diff --git a/9_Firmware/9_2_FPGA/tb/tb_mti_canceller.v b/9_Firmware/9_2_FPGA/tb/tb_mti_canceller.v index dfd4060..9082322 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_mti_canceller.v +++ b/9_Firmware/9_2_FPGA/tb/tb_mti_canceller.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 diff --git a/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v b/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v index 953e83e..4208fb6 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v +++ b/9_Firmware/9_2_FPGA/tb/tb_multiseg_cosim.v @@ -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"); diff --git a/9_Firmware/9_2_FPGA/tb/tb_radar_mode_controller.v b/9_Firmware/9_2_FPGA/tb/tb_radar_mode_controller.v deleted file mode 100644 index 0b0f829..0000000 --- a/9_Firmware/9_2_FPGA/tb/tb_radar_mode_controller.v +++ /dev/null @@ -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 diff --git a/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v b/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v index 2747c30..f6bbd3f 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v @@ -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 diff --git a/9_Firmware/9_2_FPGA/tb/tb_rxb_fullchain_latency.v b/9_Firmware/9_2_FPGA/tb/tb_rxb_fullchain_latency.v index c72a3ca..3813132 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_rxb_fullchain_latency.v +++ b/9_Firmware/9_2_FPGA/tb/tb_rxb_fullchain_latency.v @@ -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; diff --git a/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v b/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v index 1a22e21..529f9c5 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v +++ b/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v @@ -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