Files
NawfalMotii79-PLFM_RADAR/9_Firmware/9_2_FPGA/chirp_scheduler.v
T
Jason 1b2a21d55b PR-AB.b expanded commit 1: RTL strip (dead modes + counters + range_mode)
Flatten chirp_scheduler.v to single-FSM auto-scan. Mode 00 (STM32 pass-through),
mode 10 (single-chirp debug) and mode 11 (track dwell) FSM branches were all
half-implemented and unreachable in production: MCU dispatcher was deleted in
F-2.1; mode 11 inputs were tied to constants in radar_receiver_final; mode 10
debug_wave_sel was hardcoded to SHORT. The case-switch wrapper, watchdog,
effective_mode mux, and all host_track_* / host_debug_wave_sel / host_trigger
plumbing are removed.

Strip host_radar_mode (opcode 0x01), host_trigger_pulse (opcode 0x02), and
host_range_mode (opcode 0x20). The mode register had no consumer after the
single-mode flatten; the range_mode register was already write-only telemetry
(declared as input in radar_receiver_final but never read). The runtime 3km vs
20km presentation on a 200T build is driven by host_subframe_enable (0x19) +
per-waveform chirp/listen cycles (0x10-0x18) — no separate mode field needed.

Strip stm32_new_elevation / stm32_new_azimuth GPIOs and the elevation_counter /
azimuth_counter regs in plfm_chirp_controller_v2. The FPGA-side counters had no
consumer (status pack never carried them; on 50T they went to _nc; on 200T to
unconstrained outputs). MCU software counters n/y reach the GUI via USB-CDC
on a separate channel.

USB status word 0 bits [23:22] (was radar_mode) and word 4 bits [1:0] (was
range_mode) are now reserved zeros — host parser keeps the same byte offsets.

Files modified:
  chirp_scheduler.v               - flatten to single FSM (~155 LOC delta)
  plfm_chirp_controller_v2.v      - strip counter blocks + ports
  radar_transmitter.v             - strip elev/azim CDC + edge detectors + ports
  radar_receiver_final.v          - strip host_mode/range_mode/trigger + STM32 toggle ports
  radar_system_top.v              - strip regs, opcodes 0x01/0x02/0x20, status_*_mode wiring, top-level ports
  radar_system_top_50t.v          - strip _nc wires + stm32_new_elev/azim + tie-offs
  radar_system_top_te0713_umft601x_dev.v - strip status_radar_mode/range_mode ties
  usb_data_interface.v            - drop status_*_mode ports, reserve word 0 [23:22] + word 4 [1:0]
  usb_data_interface_ft2232h.v    - same as above
  radar_params.vh                 - strip RP_MODE_* / RP_RANGE_MODE_* / RP_OP_RADAR_MODE / RP_OP_TRIGGER_PULSE / RP_OP_RANGE_MODE / RP_DEF_TRACK_*

Regression will fail at this commit due to TB references to deleted signals
(host_radar_mode, status_range_mode, etc.) — TB cleanup follows in commit 2.
2026-05-11 10:24:20 +05:45

234 lines
9.0 KiB
Verilog

`timescale 1ns / 1ps
`include "radar_params.vh"
/**
* chirp_scheduler.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.
*
* Operation: FPGA-paced auto-scan over the enabled sub-frames (SHORT, MEDIUM,
* LONG). `host_subframe_enable[2:0]` masks individual waveforms out without
* recompiling. Each sub-frame fires `host_chirps_per_subframe` chirps at the
* per-waveform chirp/listen-cycle setpoints.
*
* The legacy multi-mode field (STM32 pass-through / single-chirp debug /
* track dwell) was retired in PR-AB.b expanded (2026-05-11). All three
* dead branches plus their host_* inputs (host_mode, host_trigger,
* host_debug_wave_sel, host_track_*) and the track watchdog were stripped
* — see project_aeris10_mode_strip_2026-05-11.md for rationale.
*
* Pulse outputs (chirp_pulse, subframe_pulse, frame_pulse) are 1-cycle
* positive pulses, not toggles.
*
* Clock domain: clk (100 MHz), async-low reset.
*/
module chirp_scheduler (
input wire clk,
input wire reset_n,
// 3-bit sub-frame enable mask (LONG|MEDIUM|SHORT)
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,
// Master enable (PR-E). When low, the scheduler holds in S_IDLE and
// emits no chirp_pulse — the FSM resumes on the next clock edge after
// mixers_enable returns high. Keeps the radar quiet between operator
// commands and prevents stale chirp_pulses from being buffered by the
// TX-side cdc_async_fifo before mixers come up.
input wire mixers_enable,
// ====== 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
);
// ============================================================================
// 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
// ============================================================================
// 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 — auto-scan over enabled sub-frames.
// ============================================================================
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
// 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;
end else if (!mixers_enable) begin
// Master disable — quiesce the FSM so chirp_pulse never asserts and
// the TX side stays at idle.
state <= S_IDLE;
timer <= 17'd0;
chirp_pulse <= 1'b0;
subframe_pulse <= 1'b0;
frame_pulse <= 1'b0;
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 (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
end
endmodule