Drop the chirp-v1 1-bit use_long_chirp memory loader and its 6 .mem files;
introduce chirp_reference_rom — wave_sel-native, single 8192x16 BRAM array
per Q15 lane, 4-region init (SHORT, MEDIUM, LONG seg0/seg1) loaded from the
PR-B mem files. Same 1-clk read latency as the legacy module so the RX-B
autocorrelation alignment fix carries through unchanged.
Receiver-side wave_sel shim added in radar_receiver_final.v:
wire [1:0] wave_sel = use_long_chirp ? RP_WAVE_LONG : RP_WAVE_SHORT;
This is a 1-line transitional bridge while radar_mode_controller still
emits 1-bit use_long_chirp; PR-D deletes the shim and wires chirp_scheduler
straight through. MEDIUM is loaded into the ROM but unreachable through
the production path until PR-D.
BRAM cost: 8 RAMB18 (was 6 in chirp-v1). +2 BRAM is the cost of adding
MEDIUM to the waveform set; not avoidable.
Files added:
- chirp_reference_rom.v
Files removed:
- chirp_memory_loader_param.v
- long_chirp_seg{0,1}_{i,q}.mem (4 files)
- short_chirp_{i,q}.mem (2 files)
- tb/cosim/validate_mem_files.py (legacy file-set validator; replaced by
gen_chirp_mem.py's internal verify_phase_match)
- tb/cosim/analyze_short_chirp_mismatch.py (one-shot tool from the
chirp-v1 TX-I investigation; finding incorporated, references the
deleted short_chirp_*.mem files)
Files updated for module rename:
- radar_receiver_final.v — instance, comments, wave_sel shim
- radar_mode_controller.v — header comment
- matched_filter_processing_chain.v — header comment
- scripts/200t/build_200t.tcl — explicit RTL list
- run_regression.sh — 5 spots
- tb/tb_rxb_fullchain_latency.v — instance, wave_sel shim, mem filenames,
SHORT_LEN 50 → 100 (1 µs at 100 MHz)
- tb/tb_system_e2e.v — header comment
Verification:
- chirp_reference_rom standalone iverilog compile: clean
- Full receiver chain compile (21 RTL files): clean
- tb_rxb_fullchain_latency runs end-to-end with new ROM + new mem files
+ 100-sample SHORT chirp; autocorrelation peak at bin 0, peak |I|+|Q|
= 15115. Confirms 1-clk ROM read latency is preserved and the RX-B
direct-wire-with-1-FF alignment still holds.
- 50T build script (scripts/50t/build_50t.tcl) uses glob *.v — no edit
needed; it picks up the new file automatically.
500 lines
18 KiB
Verilog
500 lines
18 KiB
Verilog
`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
|