mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-11 07:51:17 +00:00
chirp-v2 PR-E: plfm_chirp_controller_v2 + scheduler-driven TX via async-FIFO
Replaces plfm_chirp_controller_enhanced (5-state FSM with hardcoded LONG/SHORT timings + 60-entry inline short LUT) with plfm_chirp_controller_v2, a pure DAC playback driver: IDLE -> CHIRP -> IDLE keyed off a 1-cycle dst_chirp_valid pulse, with sample count selected by dst_wave_sel (SHORT=120 / MEDIUM=600 / LONG=3600). Inter-chirp timing (LISTEN, GUARD, frame boundaries) is now owned exclusively by chirp_scheduler. Scheduler -> TX bridge: cdc_async_fifo (Cummings style #2, WIDTH=2 DEPTH=4) crosses {wave_sel} from clk_100m to clk_120m_dac, with chirp_pulse as src_valid. frame_pulse rides a separate toggle CDC for chirp_counter clear and the new_chirp_frame status output. mixers_enable now also gates the scheduler so it stays in S_IDLE while the radar is "off" — without this gate the first chirp_pulse fires at reset and gets dropped before mixers come up. Files: - NEW plfm_chirp_controller_v2.v DAC playback driver (3 LUTs, FSM) - DEL plfm_chirp_controller.v legacy controller (382 lines) - DEL long_chirp_lut.mem legacy LUT (3600 lines), replaced by tx_long_lut.mem from PR-B - chirp_scheduler.v + mixers_enable input (master quiesce) - radar_receiver_final.v + sched_*_out output ports + mixers_enable_100m - radar_system_top.v wire sched_*_out -> tx_inst.sched_*; pass stm32_mixers_enable_100m to rx_inst - radar_transmitter.v full rewrite: drop new_chirp edge detector + toggle CDC, instantiate cdc_async_fifo for {wave_sel}, toggle CDC for frame_pulse, plfm_chirp_controller_v2 in place of _enhanced - tb/tb_chirp_controller.v + tb/tb_chirp_contract.v rewritten for v2 contract (43/43 unit + 10/10 contract green) - tb/tb_radar_receiver_final.v + .mixers_enable_100m(1'b1) pin - run_regression.sh, scripts/200t/build_200t.tcl file-list bumped Test summary: - tb_chirp_controller_v2: 43/43 PASS - tb_chirp_contract: 10/10 contracts upheld - tb_rxb_fullchain: peak 24033 ~80x (parity with PR-D) - tb_mti_canceller: 43/43 PASS - tb_system_e2e: 33/49 (1 new vs 34/49 PR-D baseline: G2.2 new_chirp_frame, intentional v2 frame-pulse semantics — fires once per Doppler frame instead of once per stm32 chirp toggle. TB needs widening in PR-H to wait the full frame.)
This commit is contained in:
@@ -71,6 +71,13 @@ module chirp_scheduler (
|
||||
input wire stm32_new_subframe,
|
||||
input wire stm32_new_frame,
|
||||
|
||||
// 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
|
||||
@@ -256,6 +263,15 @@ always @(posedge clk or negedge reset_n) begin
|
||||
subframe_id <= 2'd0;
|
||||
track_mode_active <= 1'b0;
|
||||
track_remaining <= 6'd0;
|
||||
end else if (!mixers_enable) begin
|
||||
// Master disable — quiesce the FSM so chirp_pulse never asserts and
|
||||
// the TX side stays at idle. Doesn't disturb track_mode_active so
|
||||
// the host can still observe whether track was last requested.
|
||||
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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,382 +0,0 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
`include "radar_params.vh"
|
||||
|
||||
module plfm_chirp_controller_enhanced (
|
||||
input wire clk_120m,
|
||||
input wire clk_100m,
|
||||
input wire reset_n,
|
||||
input wire new_chirp,
|
||||
input wire new_elevation,
|
||||
input wire new_azimuth,
|
||||
input wire mixers_enable,
|
||||
output reg [7:0] chirp_data,
|
||||
output reg chirp_valid,
|
||||
output wire new_chirp_frame,
|
||||
output reg chirp_done,
|
||||
output reg rf_switch_ctrl,
|
||||
output wire rx_mixer_en,
|
||||
output wire tx_mixer_en,
|
||||
output wire adar_tx_load_1,
|
||||
output wire adar_rx_load_1,
|
||||
output wire adar_tx_load_2,
|
||||
output wire adar_rx_load_2,
|
||||
output wire adar_tx_load_3,
|
||||
output wire adar_rx_load_3,
|
||||
output wire adar_tx_load_4,
|
||||
output wire adar_rx_load_4,
|
||||
output reg adar_tr_1,
|
||||
output reg adar_tr_2,
|
||||
output reg adar_tr_3,
|
||||
output reg adar_tr_4,
|
||||
output reg [5:0] chirp_counter,
|
||||
output reg [5:0] elevation_counter,
|
||||
output reg [5:0] azimuth_counter
|
||||
);
|
||||
|
||||
// Chirp parameters
|
||||
parameter F_START = 30000000; // 30 MHz (starting frequency)
|
||||
parameter F_END = 10000000; // 10 MHz (ending frequency)
|
||||
parameter FS = 120000000; // 120 MHz
|
||||
|
||||
// Timing parameters
|
||||
parameter T1_SAMPLES = 3600; // 30us at 120MHz
|
||||
parameter T1_RADAR_LISTENING = 16440; //137us at 120MHz
|
||||
parameter T2_SAMPLES = 60; // 0.5us at 120MHz
|
||||
parameter T2_RADAR_LISTENING = 20940; //174.5us at 120MHz
|
||||
parameter GUARD_SAMPLES = 21048; // 175.4us at 120MHz
|
||||
|
||||
// Chirp and beam parameters
|
||||
parameter CHIRP_MAX = `RP_CHIRPS_PER_FRAME;
|
||||
parameter ELEVATION_MAX = 31;
|
||||
parameter AZIMUTH_MAX = 50;
|
||||
|
||||
// State parameters
|
||||
parameter IDLE = 3'b000;
|
||||
parameter LONG_CHIRP = 3'b001;
|
||||
parameter LONG_LISTEN = 3'b010;
|
||||
parameter GUARD_TIME = 3'b011;
|
||||
parameter SHORT_CHIRP = 3'b100;
|
||||
parameter SHORT_LISTEN = 3'b101;
|
||||
parameter DONE = 3'b110;
|
||||
|
||||
reg [2:0] current_state;
|
||||
reg [2:0] next_state;
|
||||
|
||||
// Control registers
|
||||
reg [15:0] sample_counter;
|
||||
|
||||
// Edge detection for input signals
|
||||
wire chirp__toggling, elevation__toggling, azimuth__toggling;
|
||||
|
||||
// LUTs for chirp waveforms
|
||||
(* ram_style = "block" *) reg [7:0] long_chirp_lut [0:3599]; // T1_SAMPLES-1
|
||||
reg [7:0] short_chirp_lut [0:59]; // T2_SAMPLES-1
|
||||
|
||||
// Registered BRAM read output (sync-only for BRAM inference)
|
||||
reg [7:0] long_chirp_rd_data;
|
||||
|
||||
// Edge detection
|
||||
assign chirp__toggling = new_chirp;
|
||||
assign elevation__toggling = new_elevation;
|
||||
assign azimuth__toggling = new_azimuth;
|
||||
assign new_chirp_frame = (current_state == IDLE && next_state == LONG_CHIRP);
|
||||
|
||||
// Mixer TX/RX sequencing — mutually exclusive based on chirp FSM state.
|
||||
// TX mixer active during chirp transmission, RX mixer during listen.
|
||||
// Both require mixers_enable (STM32 master enable) to be high.
|
||||
assign tx_mixer_en = mixers_enable && (current_state == LONG_CHIRP ||
|
||||
current_state == SHORT_CHIRP);
|
||||
assign rx_mixer_en = mixers_enable && (current_state == LONG_LISTEN ||
|
||||
current_state == SHORT_LISTEN);
|
||||
|
||||
// ADTR1000 pull to ground tx and rx load pins if not used
|
||||
assign adar_tx_load_1 = 1'b0;
|
||||
assign adar_rx_load_1 = 1'b0;
|
||||
assign adar_tx_load_2 = 1'b0;
|
||||
assign adar_rx_load_2 = 1'b0;
|
||||
assign adar_tx_load_3 = 1'b0;
|
||||
assign adar_rx_load_3 = 1'b0;
|
||||
assign adar_tx_load_4 = 1'b0;
|
||||
assign adar_rx_load_4 = 1'b0;
|
||||
|
||||
|
||||
|
||||
|
||||
// LUT Initialization
|
||||
// Long PLFM chirp LUT loaded from .mem file for BRAM inference
|
||||
initial begin
|
||||
$readmemh("long_chirp_lut.mem", long_chirp_lut);
|
||||
end
|
||||
|
||||
// Synchronous-only BRAM read (no async reset) for BRAM inference
|
||||
always @(posedge clk_120m) begin
|
||||
long_chirp_rd_data <= long_chirp_lut[sample_counter];
|
||||
end
|
||||
|
||||
// Short PLFM chirp LUT initialization (too small for BRAM, keep inline)
|
||||
//
|
||||
// TX-I (analyzed 2026-04-28; tb/cosim/analyze_short_chirp_mismatch.py):
|
||||
// 60 samples @ fs_dac=120 MHz over 0.5 us, real-valued passband.
|
||||
// Hilbert analysis confirms an UPCHIRP from ~10 to ~30 MHz baseband
|
||||
// (BW ~19.4 MHz). The old comment "30MHz to 10MHz" had the sweep
|
||||
// direction reversed and is corrected below.
|
||||
//
|
||||
// End-to-end frequency plan (from adf4382a_manager.h + ddc_400m.v):
|
||||
// TX upmix: LO=10.500 GHz, high-side -> RF: 10.510..10.530 GHz
|
||||
// RX downmix: LO=10.380 GHz, high-side -> IF: 130..150 MHz
|
||||
// DDC NCO: 120 MHz exactly -> baseband: 10..30 MHz
|
||||
// The matched-filter reference in tb/cosim/gen_chirp_mem.py was updated
|
||||
// to include the +10 MHz baseband offset to match this band.
|
||||
initial begin
|
||||
// Complete Short PLFM chirp LUT (0.5us, ~10MHz to ~30MHz upchirp)
|
||||
short_chirp_lut[ 0] = 8'd255; short_chirp_lut[ 1] = 8'd237; short_chirp_lut[ 2] = 8'd187; short_chirp_lut[ 3] = 8'd118; short_chirp_lut[ 4] = 8'd 49; short_chirp_lut[ 5] = 8'd 6; short_chirp_lut[ 6] = 8'd 7; short_chirp_lut[ 7] = 8'd 54;
|
||||
short_chirp_lut[ 8] = 8'd132; short_chirp_lut[ 9] = 8'd210; short_chirp_lut[10] = 8'd253; short_chirp_lut[11] = 8'd237; short_chirp_lut[12] = 8'd167; short_chirp_lut[13] = 8'd 75; short_chirp_lut[14] = 8'd 10; short_chirp_lut[15] = 8'd 10;
|
||||
short_chirp_lut[16] = 8'd 80; short_chirp_lut[17] = 8'd180; short_chirp_lut[18] = 8'd248; short_chirp_lut[19] = 8'd237; short_chirp_lut[20] = 8'd150; short_chirp_lut[21] = 8'd 45; short_chirp_lut[22] = 8'd 1; short_chirp_lut[23] = 8'd 54;
|
||||
short_chirp_lut[24] = 8'd167; short_chirp_lut[25] = 8'd249; short_chirp_lut[26] = 8'd228; short_chirp_lut[27] = 8'd118; short_chirp_lut[28] = 8'd 15; short_chirp_lut[29] = 8'd 18; short_chirp_lut[30] = 8'd127; short_chirp_lut[31] = 8'd238;
|
||||
short_chirp_lut[32] = 8'd235; short_chirp_lut[33] = 8'd118; short_chirp_lut[34] = 8'd 10; short_chirp_lut[35] = 8'd 34; short_chirp_lut[36] = 8'd167; short_chirp_lut[37] = 8'd254; short_chirp_lut[38] = 8'd187; short_chirp_lut[39] = 8'd 45;
|
||||
short_chirp_lut[40] = 8'd 8; short_chirp_lut[41] = 8'd129; short_chirp_lut[42] = 8'd248; short_chirp_lut[43] = 8'd201; short_chirp_lut[44] = 8'd 49; short_chirp_lut[45] = 8'd 10; short_chirp_lut[46] = 8'd145; short_chirp_lut[47] = 8'd254;
|
||||
short_chirp_lut[48] = 8'd167; short_chirp_lut[49] = 8'd 17; short_chirp_lut[50] = 8'd 46; short_chirp_lut[51] = 8'd210; short_chirp_lut[52] = 8'd235; short_chirp_lut[53] = 8'd 75; short_chirp_lut[54] = 8'd 7; short_chirp_lut[55] = 8'd155;
|
||||
short_chirp_lut[56] = 8'd253; short_chirp_lut[57] = 8'd118; short_chirp_lut[58] = 8'd 1; short_chirp_lut[59] = 8'd129;
|
||||
end
|
||||
|
||||
// chirp_counter is driven solely by the clk_120m FSM always block (line ~683).
|
||||
// Removed redundant clk_100m driver that caused multi-driven register
|
||||
// (synthesis failure, simulation race condition).
|
||||
// The FSM internally sequences through CHIRP_MAX chirps per beam position,
|
||||
// so external new_chirp edge counting is unnecessary here.
|
||||
|
||||
// Elevation counter
|
||||
|
||||
always @(posedge clk_100m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
elevation_counter <= 6'b1;
|
||||
end else begin
|
||||
if (elevation__toggling) begin
|
||||
if (elevation_counter == ELEVATION_MAX) begin
|
||||
elevation_counter <= 6'b1;
|
||||
end else begin
|
||||
elevation_counter <= elevation_counter + 6'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
// Azimuth counter
|
||||
|
||||
always @(posedge clk_100m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
azimuth_counter <= 6'd1;
|
||||
end else begin
|
||||
if (azimuth__toggling) begin
|
||||
if (azimuth_counter == AZIMUTH_MAX) begin
|
||||
azimuth_counter <= 6'd1;
|
||||
end else begin
|
||||
azimuth_counter <= azimuth_counter + 6'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// State register
|
||||
always @(posedge clk_120m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
current_state <= IDLE;
|
||||
end else begin
|
||||
current_state <= next_state;
|
||||
end
|
||||
end
|
||||
|
||||
// Next state logic
|
||||
always @(*) begin
|
||||
case (current_state)
|
||||
IDLE: begin
|
||||
if (chirp__toggling && mixers_enable)
|
||||
next_state = LONG_CHIRP;
|
||||
else
|
||||
next_state = IDLE;
|
||||
end
|
||||
|
||||
LONG_CHIRP: begin
|
||||
if (sample_counter == T1_SAMPLES-1)
|
||||
next_state = LONG_LISTEN;
|
||||
else
|
||||
next_state = LONG_CHIRP;
|
||||
end
|
||||
|
||||
LONG_LISTEN: begin
|
||||
if (sample_counter == T1_RADAR_LISTENING-1) begin
|
||||
if (chirp_counter == (CHIRP_MAX/2)-1)
|
||||
next_state = GUARD_TIME;
|
||||
else
|
||||
next_state = LONG_CHIRP;
|
||||
end else begin
|
||||
next_state = LONG_LISTEN;
|
||||
end
|
||||
end
|
||||
|
||||
GUARD_TIME: begin
|
||||
if (sample_counter == GUARD_SAMPLES-1)
|
||||
next_state = SHORT_CHIRP;
|
||||
else
|
||||
next_state = GUARD_TIME;
|
||||
end
|
||||
|
||||
SHORT_CHIRP: begin
|
||||
if (sample_counter == T2_SAMPLES-1)
|
||||
next_state = SHORT_LISTEN;
|
||||
else
|
||||
next_state = SHORT_CHIRP;
|
||||
end
|
||||
|
||||
SHORT_LISTEN: begin
|
||||
if (sample_counter == T2_RADAR_LISTENING-1) begin
|
||||
if (chirp_counter == CHIRP_MAX-1)
|
||||
next_state = DONE;
|
||||
else
|
||||
next_state = SHORT_CHIRP;
|
||||
end else begin
|
||||
next_state = SHORT_LISTEN;
|
||||
end
|
||||
end
|
||||
|
||||
DONE: begin
|
||||
next_state = IDLE;
|
||||
end
|
||||
|
||||
default: begin
|
||||
next_state = IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
always @(posedge clk_120m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
sample_counter <= 0;
|
||||
chirp_counter <= 0;
|
||||
chirp_valid <= 0;
|
||||
chirp_done <= 0;
|
||||
chirp_data <= 8'd128;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
adar_tr_1 <= 1'b0;
|
||||
adar_tr_2 <= 1'b0;
|
||||
adar_tr_3 <= 1'b0;
|
||||
adar_tr_4 <= 1'b0;
|
||||
end else if (mixers_enable) begin
|
||||
// Default outputs
|
||||
chirp_valid <= 0;
|
||||
chirp_done <= 0;
|
||||
rf_switch_ctrl <= 0;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000;
|
||||
|
||||
// Sample counter increment logic
|
||||
if (current_state == LONG_CHIRP || current_state == LONG_LISTEN ||
|
||||
current_state == GUARD_TIME || current_state == SHORT_CHIRP ||
|
||||
current_state == SHORT_LISTEN) begin
|
||||
if (sample_counter == get_max_counter(current_state) - 1) begin
|
||||
sample_counter <= 0;
|
||||
// Increment chirp counter at end of listen states
|
||||
if (current_state == LONG_LISTEN || current_state == SHORT_LISTEN) begin
|
||||
chirp_counter <= chirp_counter + 1;
|
||||
end
|
||||
end else begin
|
||||
sample_counter <= sample_counter + 1;
|
||||
end
|
||||
end else begin
|
||||
sample_counter <= 0;
|
||||
end
|
||||
|
||||
// State-specific outputs
|
||||
case (current_state)
|
||||
IDLE: begin
|
||||
chirp_data <= 8'd128;
|
||||
end
|
||||
|
||||
// chirp_valid policy (LONG_CHIRP + SHORT_CHIRP states): assert
|
||||
// chirp_valid HIGH for the entire active-sample window of each
|
||||
// chirp (sample_counter < T?_SAMPLES) so the downstream DAC sees a
|
||||
// continuous data-valid pulse, then ride out the remaining state
|
||||
// duration on idle code 8'd128. Without the per-cycle assert,
|
||||
// downstream FIFOs underrun on the trailing samples of each chirp.
|
||||
LONG_CHIRP: begin
|
||||
rf_switch_ctrl <= 1'b1;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111;
|
||||
|
||||
if (sample_counter < T1_SAMPLES) begin
|
||||
chirp_data <= long_chirp_rd_data;
|
||||
chirp_valid <= 1'b1; // Valid during entire chirp
|
||||
end else begin
|
||||
chirp_data <= 8'd128;
|
||||
end
|
||||
end
|
||||
|
||||
LONG_LISTEN: begin
|
||||
chirp_data <= 8'd128;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
end
|
||||
|
||||
GUARD_TIME: begin
|
||||
chirp_data <= 8'd128;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
end
|
||||
|
||||
SHORT_CHIRP: begin
|
||||
rf_switch_ctrl <= 1'b1;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111;
|
||||
|
||||
/* see chirp_valid policy block above LONG_CHIRP */
|
||||
if (sample_counter < T2_SAMPLES) begin
|
||||
chirp_data <= short_chirp_lut[sample_counter];
|
||||
chirp_valid <= 1'b1; // Valid during entire chirp
|
||||
end else begin
|
||||
chirp_data <= 8'd128;
|
||||
end
|
||||
end
|
||||
|
||||
SHORT_LISTEN: begin
|
||||
chirp_data <= 8'd128;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
end
|
||||
|
||||
DONE: begin
|
||||
// Reset chirp_counter so the next frame restarts at chirp 0.
|
||||
// Without this, frame 2+ starts at chirp_counter == CHIRP_MAX
|
||||
// and the LONG_LISTEN transition guard (== CHIRP_MAX/2-1)
|
||||
// never matches on the correct chirp.
|
||||
chirp_counter <= 0;
|
||||
chirp_done <= 1'b1;
|
||||
chirp_data <= 8'd128;
|
||||
end
|
||||
|
||||
default: begin
|
||||
chirp_data <= 8'd128;
|
||||
end
|
||||
endcase
|
||||
end else begin
|
||||
// Mixers disabled
|
||||
chirp_data <= 8'd128;
|
||||
chirp_valid <= 0;
|
||||
chirp_done <= 0;
|
||||
rf_switch_ctrl <= 0;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000;
|
||||
sample_counter <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
// Helper function to get max counter for each state
|
||||
function [15:0] get_max_counter;
|
||||
input [2:0] state;
|
||||
begin
|
||||
case (state)
|
||||
LONG_CHIRP: get_max_counter = T1_SAMPLES;
|
||||
LONG_LISTEN: get_max_counter = T1_RADAR_LISTENING;
|
||||
GUARD_TIME: get_max_counter = GUARD_SAMPLES;
|
||||
SHORT_CHIRP: get_max_counter = T2_SAMPLES;
|
||||
SHORT_LISTEN: get_max_counter = T2_RADAR_LISTENING;
|
||||
default: get_max_counter = 0;
|
||||
endcase
|
||||
end
|
||||
endfunction
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,294 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
`include "radar_params.vh"
|
||||
|
||||
// ============================================================================
|
||||
// plfm_chirp_controller_v2.v — DAC playback driver for the 3-ladder waveform
|
||||
// set (chirp-v2 PR-E). Replaces plfm_chirp_controller_enhanced.
|
||||
// ============================================================================
|
||||
// chirp-v1 plfm_chirp_controller_enhanced owned an internal 6-state FSM
|
||||
// (LONG_CHIRP / LONG_LISTEN / GUARD_TIME / SHORT_CHIRP / SHORT_LISTEN / DONE)
|
||||
// that duplicated the timing the receiver-side mode controller already had.
|
||||
// chirp-v2 makes the chirp_scheduler (clk_100m, RX side) authoritative for
|
||||
// inter-chirp timing (LISTEN, GUARD, frame boundaries) and the wave_sel rail.
|
||||
//
|
||||
// This module is then a *pure DAC playback driver*:
|
||||
//
|
||||
// IDLE ──(dst_chirp_valid pulse)──> CHIRP ──(N samples played)──> IDLE
|
||||
//
|
||||
// where N depends on dst_wave_sel:
|
||||
//
|
||||
// RP_WAVE_SHORT → 120 samples @ 120 MHz = 1 µs
|
||||
// RP_WAVE_MEDIUM → 600 samples @ 120 MHz = 5 µs
|
||||
// RP_WAVE_LONG → 3600 samples @ 120 MHz = 30 µs
|
||||
//
|
||||
// Sample data sources (.mem files generated by tb/cosim/gen_chirp_mem.py
|
||||
// PR-B; 8-bit unsigned offset-binary, idle code = 8'd128):
|
||||
//
|
||||
// tx_short_lut.mem (120 entries) — inline LUTRAM
|
||||
// tx_medium_lut.mem (600 entries) — inline LUTRAM
|
||||
// tx_long_lut.mem (3600 entries) — inferred BRAM (sync read)
|
||||
//
|
||||
// Inputs from the scheduler (clk_100m → clk_120m crossing handled in the
|
||||
// parent radar_transmitter via cdc_async_fifo for {wave_sel} + toggle CDC
|
||||
// for frame_pulse):
|
||||
//
|
||||
// dst_chirp_valid 1-cycle pulse on clk_120m, "begin a new chirp"
|
||||
// dst_wave_sel[1:0] valid alongside dst_chirp_valid; one of RP_WAVE_*
|
||||
// frame_pulse_120m 1-cycle pulse on clk_120m, "frame boundary"
|
||||
// (clears chirp_counter so frame N+1 starts at 0)
|
||||
//
|
||||
// Mixer / RF-switch / ADAR T-R:
|
||||
// tx_mixer_en, rf_switch_ctrl, adar_tr_* asserted during ST_CHIRP only.
|
||||
// rx_mixer_en asserted whenever mixers_enable=1 and we're NOT in ST_CHIRP
|
||||
// (i.e. the radar is listening). v1 gated rx_mixer_en strictly to
|
||||
// {LONG,SHORT}_LISTEN; v2 also covers the scheduler's GUARD window. The
|
||||
// GUARD window is RF-quiet by construction so the radar sees nothing
|
||||
// useful and the change is benign — revisit in PR-H if a finer gate is
|
||||
// needed.
|
||||
//
|
||||
// Beam steering:
|
||||
// elevation_counter / azimuth_counter remain on clk_100m and are bumped
|
||||
// by the STM32 toggle inputs (independent of the chirp FSM), unchanged
|
||||
// from v1.
|
||||
//
|
||||
// chirp_counter:
|
||||
// Increments by 1 each time we leave ST_CHIRP (i.e. one chirp finished).
|
||||
// Cleared on frame_pulse_120m so it tracks chirp index within the current
|
||||
// frame, matching the chirp_scheduler's chirp_counter.
|
||||
//
|
||||
// new_chirp_frame:
|
||||
// Mirrors frame_pulse_120m. Same 1-cycle pulse semantics as the v1 output.
|
||||
// ============================================================================
|
||||
module plfm_chirp_controller_v2 (
|
||||
input wire clk_120m,
|
||||
input wire clk_100m,
|
||||
input wire reset_n, // 120m-domain reset
|
||||
input wire reset_100m_n, // 100m-domain reset (elev/az counters)
|
||||
|
||||
input wire mixers_enable, // CDC-synced to clk_120m
|
||||
|
||||
// From scheduler via cdc_async_fifo + toggle CDC (clk_120m domain)
|
||||
input wire dst_chirp_valid,
|
||||
input wire [1:0] dst_wave_sel,
|
||||
input wire frame_pulse_120m,
|
||||
|
||||
// STM32 beam-step toggle inputs (clk_100m, edge-detected upstream)
|
||||
input wire new_elevation,
|
||||
input wire new_azimuth,
|
||||
|
||||
// DAC outputs
|
||||
output reg [7:0] chirp_data,
|
||||
output reg chirp_valid,
|
||||
output wire new_chirp_frame,
|
||||
output reg chirp_done,
|
||||
output reg rf_switch_ctrl,
|
||||
output wire rx_mixer_en,
|
||||
output wire tx_mixer_en,
|
||||
|
||||
// ADAR control (loads tied off; T/R pulsed during chirp)
|
||||
output wire adar_tx_load_1,
|
||||
output wire adar_rx_load_1,
|
||||
output wire adar_tx_load_2,
|
||||
output wire adar_rx_load_2,
|
||||
output wire adar_tx_load_3,
|
||||
output wire adar_rx_load_3,
|
||||
output wire adar_tx_load_4,
|
||||
output wire adar_rx_load_4,
|
||||
output reg adar_tr_1,
|
||||
output reg adar_tr_2,
|
||||
output reg adar_tr_3,
|
||||
output reg adar_tr_4,
|
||||
|
||||
// Status counters
|
||||
output reg [5:0] chirp_counter,
|
||||
output reg [5:0] elevation_counter,
|
||||
output reg [5:0] azimuth_counter
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Waveform sample counts (must match tb/cosim/gen_chirp_mem.py)
|
||||
// ----------------------------------------------------------------------------
|
||||
localparam SHORT_SAMPLES = 12'd120;
|
||||
localparam MEDIUM_SAMPLES = 12'd600;
|
||||
localparam LONG_SAMPLES = 12'd3600;
|
||||
|
||||
// Beam ranges
|
||||
parameter ELEVATION_MAX = 31;
|
||||
parameter AZIMUTH_MAX = 50;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// FSM
|
||||
// ----------------------------------------------------------------------------
|
||||
localparam ST_IDLE = 1'b0;
|
||||
localparam ST_CHIRP = 1'b1;
|
||||
|
||||
reg state;
|
||||
reg [1:0] active_wave_sel;
|
||||
reg [11:0] sample_counter;
|
||||
reg [11:0] active_max_samples;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// LUTs — long is BRAM (sync read), short/medium are inline LUTRAM (combinational)
|
||||
// ----------------------------------------------------------------------------
|
||||
(* ram_style = "block" *) reg [7:0] tx_long_lut [0:LONG_SAMPLES-1];
|
||||
reg [7:0] tx_medium_lut [0:MEDIUM_SAMPLES-1];
|
||||
reg [7:0] tx_short_lut [0:SHORT_SAMPLES-1];
|
||||
|
||||
reg [7:0] long_rd_data;
|
||||
always @(posedge clk_120m) begin
|
||||
long_rd_data <= tx_long_lut[sample_counter];
|
||||
end
|
||||
|
||||
initial begin
|
||||
$readmemh("tx_long_lut.mem", tx_long_lut);
|
||||
$readmemh("tx_medium_lut.mem", tx_medium_lut);
|
||||
$readmemh("tx_short_lut.mem", tx_short_lut);
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Combinational helpers
|
||||
// ----------------------------------------------------------------------------
|
||||
function [11:0] wave_to_samples;
|
||||
input [1:0] w;
|
||||
begin
|
||||
case (w)
|
||||
`RP_WAVE_SHORT: wave_to_samples = SHORT_SAMPLES;
|
||||
`RP_WAVE_MEDIUM: wave_to_samples = MEDIUM_SAMPLES;
|
||||
`RP_WAVE_LONG: wave_to_samples = LONG_SAMPLES;
|
||||
default: wave_to_samples = SHORT_SAMPLES; // RESERVED → safe fallback
|
||||
endcase
|
||||
end
|
||||
endfunction
|
||||
|
||||
reg [7:0] active_sample_data;
|
||||
always @(*) begin
|
||||
case (active_wave_sel)
|
||||
`RP_WAVE_SHORT: active_sample_data = tx_short_lut [sample_counter[6:0]];
|
||||
`RP_WAVE_MEDIUM: active_sample_data = tx_medium_lut[sample_counter[9:0]];
|
||||
`RP_WAVE_LONG: active_sample_data = long_rd_data;
|
||||
default: active_sample_data = 8'd128;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Static outputs
|
||||
// ----------------------------------------------------------------------------
|
||||
assign new_chirp_frame = frame_pulse_120m;
|
||||
assign tx_mixer_en = mixers_enable && (state == ST_CHIRP);
|
||||
assign rx_mixer_en = mixers_enable && (state != ST_CHIRP);
|
||||
|
||||
// ADTR1000 load pins are unused on this board — tie low.
|
||||
assign adar_tx_load_1 = 1'b0;
|
||||
assign adar_rx_load_1 = 1'b0;
|
||||
assign adar_tx_load_2 = 1'b0;
|
||||
assign adar_rx_load_2 = 1'b0;
|
||||
assign adar_tx_load_3 = 1'b0;
|
||||
assign adar_rx_load_3 = 1'b0;
|
||||
assign adar_tx_load_4 = 1'b0;
|
||||
assign adar_rx_load_4 = 1'b0;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main FSM (clk_120m)
|
||||
// ----------------------------------------------------------------------------
|
||||
always @(posedge clk_120m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
state <= ST_IDLE;
|
||||
active_wave_sel <= `RP_WAVE_SHORT;
|
||||
sample_counter <= 12'd0;
|
||||
active_max_samples <= 12'd0;
|
||||
chirp_data <= 8'd128;
|
||||
chirp_valid <= 1'b0;
|
||||
chirp_done <= 1'b0;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
adar_tr_1 <= 1'b0;
|
||||
adar_tr_2 <= 1'b0;
|
||||
adar_tr_3 <= 1'b0;
|
||||
adar_tr_4 <= 1'b0;
|
||||
chirp_counter <= 6'd0;
|
||||
end else if (mixers_enable) begin
|
||||
chirp_done <= 1'b0; // default: deassert pulse
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
chirp_data <= 8'd128;
|
||||
chirp_valid <= 1'b0;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000;
|
||||
|
||||
if (dst_chirp_valid) begin
|
||||
state <= ST_CHIRP;
|
||||
active_wave_sel <= dst_wave_sel;
|
||||
active_max_samples <= wave_to_samples(dst_wave_sel);
|
||||
sample_counter <= 12'd0;
|
||||
end
|
||||
end
|
||||
|
||||
ST_CHIRP: begin
|
||||
rf_switch_ctrl <= 1'b1;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111;
|
||||
chirp_data <= active_sample_data;
|
||||
chirp_valid <= 1'b1;
|
||||
|
||||
if (sample_counter == active_max_samples - 12'd1) begin
|
||||
// End of this chirp — return to IDLE, pulse chirp_done,
|
||||
// and bump chirp_counter (frame_pulse will clear it below).
|
||||
state <= ST_IDLE;
|
||||
sample_counter <= 12'd0;
|
||||
chirp_data <= 8'd128;
|
||||
chirp_valid <= 1'b0;
|
||||
chirp_done <= 1'b1;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000;
|
||||
chirp_counter <= chirp_counter + 6'd1;
|
||||
end else begin
|
||||
sample_counter <= sample_counter + 12'd1;
|
||||
end
|
||||
end
|
||||
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
|
||||
// frame_pulse always wins — restart chirp index for the next frame.
|
||||
if (frame_pulse_120m) begin
|
||||
chirp_counter <= 6'd0;
|
||||
end
|
||||
end else begin
|
||||
// mixers_enable LOW: hold quiescent (and reset the FSM so a glitch on
|
||||
// dst_chirp_valid during disable can't sneak us into ST_CHIRP).
|
||||
state <= ST_IDLE;
|
||||
chirp_data <= 8'd128;
|
||||
chirp_valid <= 1'b0;
|
||||
chirp_done <= 1'b0;
|
||||
rf_switch_ctrl <= 1'b0;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000;
|
||||
sample_counter <= 12'd0;
|
||||
end
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Beam steering counters (clk_100m, independent of chirp FSM) — unchanged from v1.
|
||||
// ----------------------------------------------------------------------------
|
||||
always @(posedge clk_100m or negedge reset_100m_n) begin
|
||||
if (!reset_100m_n) begin
|
||||
elevation_counter <= 6'd1;
|
||||
end else if (new_elevation) begin
|
||||
if (elevation_counter == ELEVATION_MAX)
|
||||
elevation_counter <= 6'd1;
|
||||
else
|
||||
elevation_counter <= elevation_counter + 6'd1;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk_100m or negedge reset_100m_n) begin
|
||||
if (!reset_100m_n) begin
|
||||
azimuth_counter <= 6'd1;
|
||||
end else if (new_azimuth) begin
|
||||
if (azimuth_counter == AZIMUTH_MAX)
|
||||
azimuth_counter <= 6'd1;
|
||||
else
|
||||
azimuth_counter <= azimuth_counter + 6'd1;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -69,6 +69,10 @@ module radar_receiver_final (
|
||||
input wire stm32_new_elevation_rx,
|
||||
input wire stm32_new_azimuth_rx,
|
||||
|
||||
// PR-E: master mixers_enable in clk_100m domain — gates the scheduler
|
||||
// so it stays in S_IDLE until the operator turns the radar on.
|
||||
input wire mixers_enable_100m,
|
||||
|
||||
// CFAR integration: expose Doppler frame_complete to top level
|
||||
output wire doppler_frame_done_out,
|
||||
|
||||
@@ -118,7 +122,15 @@ module radar_receiver_final (
|
||||
// silent sample drop between the 400 MHz CIC output and the 100 MHz
|
||||
// FIR input; stays high until the next reset. OR'd into the GPIO
|
||||
// diagnostic bit at the top level.
|
||||
output wire ddc_cic_fir_overrun
|
||||
output wire ddc_cic_fir_overrun,
|
||||
|
||||
// chirp_scheduler outputs exposed for the TX-side CDC bridge (PR-E).
|
||||
// sched_chirp_pulse: 1-cycle pulse on clk that announces "begin chirp now"
|
||||
// sched_wave_sel: waveform identity rail valid alongside chirp_pulse
|
||||
// sched_frame_pulse: 1-cycle pulse on frame boundary (chirp_counter wrap)
|
||||
output wire [1:0] sched_wave_sel_out,
|
||||
output wire sched_chirp_pulse_out,
|
||||
output wire sched_frame_pulse_out
|
||||
);
|
||||
|
||||
// ========== INTERNAL SIGNALS ==========
|
||||
@@ -215,6 +227,7 @@ wire mti_first_chirp;
|
||||
// wired here — the V2 sub-frame structure uses RP_DEF_CHIRPS_PER_SUBFRAME
|
||||
// (16) and PR-G renames the host register.
|
||||
chirp_scheduler sched (
|
||||
.mixers_enable(mixers_enable_100m),
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.host_mode(host_mode),
|
||||
@@ -250,6 +263,14 @@ chirp_scheduler sched (
|
||||
.track_beam_az(sched_track_beam_az),
|
||||
.track_beam_el(sched_track_beam_el)
|
||||
);
|
||||
|
||||
// PR-E: forward scheduler pulses + wave_sel to the TX-side CDC bridge in
|
||||
// radar_system_top. The transmitter does its own clk_100m → clk_120m_dac
|
||||
// crossing via cdc_async_fifo + toggle CDC.
|
||||
assign sched_wave_sel_out = wave_sel;
|
||||
assign sched_chirp_pulse_out = chirp_pulse;
|
||||
assign sched_frame_pulse_out = frame_pulse;
|
||||
|
||||
wire clk_400m;
|
||||
|
||||
// NOTE: lvds_to_cmos_400m removed — ad9484_interface_400m now provides
|
||||
|
||||
@@ -177,6 +177,12 @@ wire [5:0] tx_current_chirp; // In clk_120m_dac domain
|
||||
wire [5:0] tx_current_chirp_sync; // Synchronized to clk_100m domain
|
||||
wire tx_current_chirp_sync_valid;
|
||||
|
||||
// PR-E: scheduler outputs from receiver_final, in clk_100m domain.
|
||||
// Routed directly into radar_transmitter, which owns the 100→120 CDC.
|
||||
wire [1:0] sched_wave_sel;
|
||||
wire sched_chirp_pulse;
|
||||
wire sched_frame_pulse;
|
||||
|
||||
// Receiver internal signals
|
||||
wire [31:0] rx_doppler_output;
|
||||
wire rx_doppler_valid;
|
||||
@@ -489,12 +495,16 @@ radar_transmitter tx_inst (
|
||||
.rx_mixer_en(rx_mixer_en),
|
||||
.tx_mixer_en(tx_mixer_en),
|
||||
|
||||
// STM32 Control Interface
|
||||
.stm32_new_chirp(stm32_new_chirp),
|
||||
// Scheduler bridge (chirp-v2 PR-E): clk_100m signals from receiver_final
|
||||
.sched_wave_sel(sched_wave_sel),
|
||||
.sched_chirp_pulse(sched_chirp_pulse),
|
||||
.sched_frame_pulse(sched_frame_pulse),
|
||||
|
||||
// STM32 Control Interface (chirp moved to scheduler — only beam-step here)
|
||||
.stm32_new_elevation(stm32_new_elevation),
|
||||
.stm32_new_azimuth(stm32_new_azimuth),
|
||||
.stm32_mixers_enable(stm32_mixers_enable),
|
||||
|
||||
|
||||
// RF Switch Control
|
||||
.fpga_rf_switch(fpga_rf_switch),
|
||||
|
||||
@@ -594,6 +604,8 @@ radar_receiver_final rx_inst (
|
||||
.stm32_new_chirp_rx(stm32_new_chirp),
|
||||
.stm32_new_elevation_rx(stm32_new_elevation),
|
||||
.stm32_new_azimuth_rx(stm32_new_azimuth),
|
||||
// PR-E: master enable for the scheduler (CDC-sync'd to clk_100m above)
|
||||
.mixers_enable_100m(stm32_mixers_enable_100m),
|
||||
// CFAR: Doppler frame-complete pulse
|
||||
.doppler_frame_done_out(rx_frame_complete),
|
||||
// Ground clutter removal
|
||||
@@ -618,7 +630,11 @@ radar_receiver_final rx_inst (
|
||||
.mti_saturation_count_out(rx_mti_saturation_count),
|
||||
// Range-bin decimator watchdog (audit F-6.4)
|
||||
.range_decim_watchdog(rx_range_decim_watchdog),
|
||||
.ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
|
||||
.ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun),
|
||||
// PR-E: scheduler outputs forwarded to TX-side CDC bridge (clk_100m).
|
||||
.sched_wave_sel_out(sched_wave_sel),
|
||||
.sched_chirp_pulse_out(sched_chirp_pulse),
|
||||
.sched_frame_pulse_out(sched_frame_pulse)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -1,46 +1,54 @@
|
||||
`timescale 1ns / 1ps
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// Company:
|
||||
// Engineer:
|
||||
//
|
||||
// Create Date: 19:04:35 12/14/2025
|
||||
// Design Name:
|
||||
// Module Name: radar_transmitter
|
||||
// Project Name:
|
||||
// Target Devices:
|
||||
// Tool versions:
|
||||
// Description:
|
||||
//
|
||||
// Dependencies:
|
||||
//
|
||||
// Revision:
|
||||
// Revision 0.01 - File Created
|
||||
// Additional Comments:
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
module radar_transmitter(
|
||||
// System Clocks
|
||||
input wire clk_100m, // System clock
|
||||
input wire clk_120m_dac, // 120MHz DAC clock
|
||||
input wire reset_n, // Reset synchronized to clk_120m_dac
|
||||
`timescale 1ns / 1ps
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// radar_transmitter — DAC-side wrapper around plfm_chirp_controller_v2.
|
||||
//
|
||||
// chirp-v2 PR-E reorganization:
|
||||
// chirp_scheduler (clk_100m, in receiver_final) is now the master timekeeper.
|
||||
// It emits {wave_sel[1:0], chirp_pulse, frame_pulse} on clk_100m. We bridge
|
||||
// them to clk_120m_dac here:
|
||||
// - wave_sel + chirp_pulse → cdc_async_fifo (Cummings style #2). Each
|
||||
// chirp_pulse pushes wave_sel into the FIFO; the dst-side dst_valid
|
||||
// pulse drives plfm_chirp_controller_v2.dst_chirp_valid.
|
||||
// - frame_pulse → toggle CDC → 1-cycle pulse on clk_120m_dac for
|
||||
// chirp_counter clear and the new_chirp_frame status output.
|
||||
//
|
||||
// Beam steering:
|
||||
// stm32_new_elevation / stm32_new_azimuth still run through edge detectors
|
||||
// on clk_100m and feed the controller's internal beam counters. These are
|
||||
// independent of the chirp FSM and unchanged from chirp-v1.
|
||||
//
|
||||
// stm32_new_chirp:
|
||||
// Removed from this module — the scheduler in receiver_final now owns chirp
|
||||
// timing. The top-level GPIO is still wired to receiver_final via
|
||||
// stm32_new_chirp_rx; the transmitter has no separate path.
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
module radar_transmitter(
|
||||
// System Clocks
|
||||
input wire clk_100m, // System clock
|
||||
input wire clk_120m_dac, // 120MHz DAC clock
|
||||
input wire reset_n, // Reset synchronized to clk_120m_dac
|
||||
input wire reset_100m_n, // Reset synchronized to clk_100m (for edge detectors/CDC)
|
||||
|
||||
|
||||
// DAC Interface
|
||||
output wire [7:0] dac_data,
|
||||
output wire dac_clk,
|
||||
output wire dac_sleep,
|
||||
output wire rx_mixer_en,
|
||||
output wire tx_mixer_en,
|
||||
|
||||
// STM32 Control Interface
|
||||
input wire stm32_new_chirp,
|
||||
input wire stm32_new_elevation,
|
||||
output wire tx_mixer_en,
|
||||
|
||||
// Scheduler outputs from receiver_final (clk_100m domain) — PR-E
|
||||
input wire [1:0] sched_wave_sel,
|
||||
input wire sched_chirp_pulse,
|
||||
input wire sched_frame_pulse,
|
||||
|
||||
// STM32 Control Interface (chirp moved to scheduler; beam-step still here)
|
||||
input wire stm32_new_elevation,
|
||||
input wire stm32_new_azimuth,
|
||||
input wire stm32_mixers_enable,
|
||||
|
||||
output wire fpga_rf_switch,
|
||||
|
||||
// ADAR1000 Control Interface
|
||||
input wire stm32_mixers_enable,
|
||||
|
||||
output wire fpga_rf_switch,
|
||||
|
||||
// ADAR1000 Control Interface
|
||||
output wire adar_tx_load_1,
|
||||
output wire adar_rx_load_1,
|
||||
output wire adar_tx_load_2,
|
||||
@@ -53,7 +61,7 @@ module radar_transmitter(
|
||||
output wire adar_tr_2,
|
||||
output wire adar_tr_3,
|
||||
output wire adar_tr_4,
|
||||
|
||||
|
||||
// Level Shifter SPI Interface (STM32F7 to ADAR1000)
|
||||
input wire stm32_sclk_3v3,
|
||||
input wire stm32_mosi_3v3,
|
||||
@@ -62,188 +70,210 @@ module radar_transmitter(
|
||||
input wire stm32_cs_adar2_3v3,
|
||||
input wire stm32_cs_adar3_3v3,
|
||||
input wire stm32_cs_adar4_3v3,
|
||||
|
||||
|
||||
output wire stm32_sclk_1v8,
|
||||
output wire stm32_mosi_1v8,
|
||||
input wire stm32_miso_1v8,
|
||||
output wire stm32_cs_adar1_1v8,
|
||||
output wire stm32_cs_adar2_1v8,
|
||||
output wire stm32_cs_adar3_1v8,
|
||||
output wire stm32_cs_adar4_1v8,
|
||||
|
||||
// Beam Position Tracking
|
||||
output wire [5:0] current_elevation,
|
||||
output wire [5:0] current_azimuth,
|
||||
output wire [5:0] current_chirp,
|
||||
output wire new_chirp_frame
|
||||
output wire stm32_cs_adar4_1v8,
|
||||
|
||||
|
||||
);
|
||||
|
||||
// ========== SPI LEVEL SHIFTER PASSTHROUGH ==========
|
||||
// FPGA bridges 3.3V STM32 SPI bus (Bank 15) to 1.8V ADAR1000 SPI bus (Bank 34).
|
||||
// The FPGA I/O banks handle the actual voltage translation; these assigns
|
||||
// route the signals through the fabric.
|
||||
assign stm32_sclk_1v8 = stm32_sclk_3v3;
|
||||
assign stm32_mosi_1v8 = stm32_mosi_3v3;
|
||||
assign stm32_miso_3v3 = stm32_miso_1v8;
|
||||
assign stm32_cs_adar1_1v8 = stm32_cs_adar1_3v3;
|
||||
assign stm32_cs_adar2_1v8 = stm32_cs_adar2_3v3;
|
||||
assign stm32_cs_adar3_1v8 = stm32_cs_adar3_3v3;
|
||||
assign stm32_cs_adar4_1v8 = stm32_cs_adar4_3v3;
|
||||
|
||||
// Edge Detection Signals
|
||||
wire new_chirp_pulse;
|
||||
wire new_elevation_pulse;
|
||||
wire new_azimuth_pulse;
|
||||
|
||||
// CDC: Synchronized versions of async STM32 GPIO inputs to clk_100m
|
||||
wire stm32_new_chirp_sync;
|
||||
wire stm32_new_elevation_sync;
|
||||
wire stm32_new_azimuth_sync;
|
||||
|
||||
// CDC: Synchronized versions of signals crossing clk_100m -> clk_120m_dac
|
||||
wire mixers_enable_120m; // stm32_mixers_enable sync'd to clk_120m_dac
|
||||
wire new_chirp_pulse_120m; // new_chirp_pulse (toggle CDC) in clk_120m_dac domain
|
||||
|
||||
// Chirp Control Signals
|
||||
wire [7:0] chirp_data;
|
||||
wire chirp_valid;
|
||||
wire chirp_sequence_done;
|
||||
|
||||
// Toggle CDC for new_chirp_pulse: clk_100m -> clk_120m_dac
|
||||
// Edge detector produces a 1-cycle pulse on clk_100m. A level synchronizer
|
||||
// would miss it (120/100 MHz ratio). Toggle CDC converts pulse to level toggle,
|
||||
// syncs the toggle, then detects edges on the destination side.
|
||||
reg chirp_toggle_100m;
|
||||
always @(posedge clk_100m or negedge reset_100m_n) begin
|
||||
if (!reset_100m_n)
|
||||
chirp_toggle_100m <= 1'b0;
|
||||
else if (new_chirp_pulse)
|
||||
chirp_toggle_100m <= ~chirp_toggle_100m;
|
||||
end
|
||||
|
||||
// Sync the toggle to clk_120m_dac domain
|
||||
wire chirp_toggle_120m;
|
||||
cdc_single_bit #(.STAGES(3)) cdc_chirp_toggle (
|
||||
.src_clk(clk_100m),
|
||||
.dst_clk(clk_120m_dac),
|
||||
.reset_n(reset_n),
|
||||
.src_signal(chirp_toggle_100m),
|
||||
.dst_signal(chirp_toggle_120m)
|
||||
);
|
||||
|
||||
// Detect edges on synchronized toggle to recover pulse in clk_120m domain
|
||||
reg chirp_toggle_120m_prev;
|
||||
always @(posedge clk_120m_dac or negedge reset_n) begin
|
||||
if (!reset_n)
|
||||
chirp_toggle_120m_prev <= 1'b0;
|
||||
else
|
||||
chirp_toggle_120m_prev <= chirp_toggle_120m;
|
||||
end
|
||||
assign new_chirp_pulse_120m = chirp_toggle_120m ^ chirp_toggle_120m_prev;
|
||||
|
||||
// Sync stm32_mixers_enable (async GPIO level) to clk_120m_dac domain
|
||||
cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m (
|
||||
.src_clk(clk_100m), // Treat as pseudo-source (GPIO is async)
|
||||
.dst_clk(clk_120m_dac),
|
||||
.reset_n(reset_n),
|
||||
.src_signal(stm32_mixers_enable),
|
||||
.dst_signal(mixers_enable_120m)
|
||||
// Beam Position Tracking
|
||||
output wire [5:0] current_elevation,
|
||||
output wire [5:0] current_azimuth,
|
||||
output wire [5:0] current_chirp,
|
||||
output wire new_chirp_frame
|
||||
);
|
||||
|
||||
// CDC synchronizers: async STM32 GPIO inputs -> clk_100m domain
|
||||
// These prevent metastability in the edge detectors. Without these,
|
||||
// the edge detector's first FF can go metastable, and the XOR output
|
||||
// can glitch, producing false chirp/elevation/azimuth pulses.
|
||||
cdc_single_bit #(.STAGES(2)) cdc_stm32_chirp (
|
||||
.src_clk(clk_100m), // Pseudo-source for async GPIO
|
||||
.dst_clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.src_signal(stm32_new_chirp),
|
||||
.dst_signal(stm32_new_chirp_sync)
|
||||
);
|
||||
|
||||
cdc_single_bit #(.STAGES(2)) cdc_stm32_elevation (
|
||||
.src_clk(clk_100m),
|
||||
.dst_clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.src_signal(stm32_new_elevation),
|
||||
.dst_signal(stm32_new_elevation_sync)
|
||||
);
|
||||
|
||||
cdc_single_bit #(.STAGES(2)) cdc_stm32_azimuth (
|
||||
.src_clk(clk_100m),
|
||||
.dst_clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.src_signal(stm32_new_azimuth),
|
||||
.dst_signal(stm32_new_azimuth_sync)
|
||||
);
|
||||
|
||||
// Enhanced STM32 Input Edge Detection with Debouncing
|
||||
// Inputs are now CDC-synchronized (safe from metastability)
|
||||
edge_detector_enhanced chirp_edge (
|
||||
.clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.signal_in(stm32_new_chirp_sync),
|
||||
.rising_falling_edge(new_chirp_pulse)
|
||||
);
|
||||
|
||||
edge_detector_enhanced elevation_edge (
|
||||
.clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.signal_in(stm32_new_elevation_sync),
|
||||
.rising_falling_edge(new_elevation_pulse)
|
||||
);
|
||||
|
||||
edge_detector_enhanced azimuth_edge (
|
||||
.clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.signal_in(stm32_new_azimuth_sync),
|
||||
.rising_falling_edge(new_azimuth_pulse)
|
||||
|
||||
// ========== SPI LEVEL SHIFTER PASSTHROUGH ==========
|
||||
// FPGA bridges 3.3V STM32 SPI bus (Bank 15) to 1.8V ADAR1000 SPI bus (Bank 34).
|
||||
// The FPGA I/O banks handle the actual voltage translation; these assigns
|
||||
// route the signals through the fabric.
|
||||
assign stm32_sclk_1v8 = stm32_sclk_3v3;
|
||||
assign stm32_mosi_1v8 = stm32_mosi_3v3;
|
||||
assign stm32_miso_3v3 = stm32_miso_1v8;
|
||||
assign stm32_cs_adar1_1v8 = stm32_cs_adar1_3v3;
|
||||
assign stm32_cs_adar2_1v8 = stm32_cs_adar2_3v3;
|
||||
assign stm32_cs_adar3_1v8 = stm32_cs_adar3_3v3;
|
||||
assign stm32_cs_adar4_1v8 = stm32_cs_adar4_3v3;
|
||||
|
||||
// Beam-step edge detection (STM32 GPIO -> clk_100m pulses)
|
||||
wire new_elevation_pulse;
|
||||
wire new_azimuth_pulse;
|
||||
|
||||
// CDC: Synchronized versions of async STM32 GPIO inputs to clk_100m
|
||||
wire stm32_new_elevation_sync;
|
||||
wire stm32_new_azimuth_sync;
|
||||
|
||||
// CDC: stm32_mixers_enable into clk_120m_dac domain
|
||||
wire mixers_enable_120m;
|
||||
|
||||
// PR-E: scheduler bridge outputs in clk_120m_dac domain
|
||||
wire dst_chirp_valid;
|
||||
wire [1:0] dst_wave_sel;
|
||||
wire sched_overrun_unused;
|
||||
wire frame_pulse_120m;
|
||||
|
||||
// Chirp Control Signals
|
||||
wire [7:0] chirp_data;
|
||||
wire chirp_valid;
|
||||
wire chirp_sequence_done;
|
||||
|
||||
// ============================================================================
|
||||
// PR-E: chirp_pulse + wave_sel CDC (clk_100m → clk_120m_dac)
|
||||
//
|
||||
// Each scheduler chirp_pulse on clk_100m pushes wave_sel into a Gray-coded
|
||||
// async FIFO. The dst side auto-drains so dst_chirp_valid is a 1-cycle pulse
|
||||
// on clk_120m_dac, and dst_wave_sel carries the matching waveform identity.
|
||||
// ============================================================================
|
||||
cdc_async_fifo #(
|
||||
.WIDTH(2),
|
||||
.DEPTH(4)
|
||||
) cdc_chirp_fifo (
|
||||
.src_clk (clk_100m),
|
||||
.dst_clk (clk_120m_dac),
|
||||
.src_reset_n (reset_100m_n),
|
||||
.dst_reset_n (reset_n),
|
||||
.src_data (sched_wave_sel),
|
||||
.src_valid (sched_chirp_pulse),
|
||||
.dst_data (dst_wave_sel),
|
||||
.dst_valid (dst_chirp_valid),
|
||||
.overrun (sched_overrun_unused)
|
||||
);
|
||||
|
||||
// Enhanced PLFM Chirp Generation
|
||||
plfm_chirp_controller_enhanced plfm_chirp_inst (
|
||||
.clk_120m(clk_120m_dac),
|
||||
.clk_100m(clk_100m),
|
||||
.reset_n(reset_n),
|
||||
.new_chirp(new_chirp_pulse_120m), // CDC-synchronized pulse in clk_120m domain
|
||||
.new_elevation(new_elevation_pulse),
|
||||
.new_azimuth(new_azimuth_pulse),
|
||||
.new_chirp_frame(new_chirp_frame),
|
||||
.mixers_enable(mixers_enable_120m), // CDC-synchronized level in clk_120m domain
|
||||
.chirp_data(chirp_data),
|
||||
.chirp_valid(chirp_valid),
|
||||
.chirp_done(chirp_sequence_done),
|
||||
.rf_switch_ctrl(fpga_rf_switch),
|
||||
.rx_mixer_en(rx_mixer_en),
|
||||
.tx_mixer_en(tx_mixer_en),
|
||||
.adar_tx_load_1(adar_tx_load_1),
|
||||
.adar_rx_load_1(adar_rx_load_1),
|
||||
.adar_tx_load_2(adar_tx_load_2),
|
||||
.adar_rx_load_2(adar_rx_load_2),
|
||||
.adar_tx_load_3(adar_tx_load_3),
|
||||
.adar_rx_load_3(adar_rx_load_3),
|
||||
.adar_tx_load_4(adar_tx_load_4),
|
||||
.adar_rx_load_4(adar_rx_load_4),
|
||||
.adar_tr_1(adar_tr_1),
|
||||
.adar_tr_2(adar_tr_2),
|
||||
.adar_tr_3(adar_tr_3),
|
||||
.adar_tr_4(adar_tr_4),
|
||||
.elevation_counter(current_elevation),
|
||||
.azimuth_counter(current_azimuth),
|
||||
.chirp_counter(current_chirp)
|
||||
);
|
||||
|
||||
// Enhanced DAC Interface
|
||||
dac_interface_enhanced dac_interface_inst (
|
||||
.clk_120m(clk_120m_dac),
|
||||
|
||||
// ============================================================================
|
||||
// frame_pulse toggle CDC (clk_100m → clk_120m_dac)
|
||||
// ============================================================================
|
||||
reg frame_toggle_100m;
|
||||
always @(posedge clk_100m or negedge reset_100m_n) begin
|
||||
if (!reset_100m_n)
|
||||
frame_toggle_100m <= 1'b0;
|
||||
else if (sched_frame_pulse)
|
||||
frame_toggle_100m <= ~frame_toggle_100m;
|
||||
end
|
||||
|
||||
wire frame_toggle_120m;
|
||||
cdc_single_bit #(.STAGES(3)) cdc_frame_toggle (
|
||||
.src_clk(clk_100m),
|
||||
.dst_clk(clk_120m_dac),
|
||||
.reset_n(reset_n),
|
||||
.chirp_data(chirp_data),
|
||||
.src_signal(frame_toggle_100m),
|
||||
.dst_signal(frame_toggle_120m)
|
||||
);
|
||||
|
||||
reg frame_toggle_120m_prev;
|
||||
always @(posedge clk_120m_dac or negedge reset_n) begin
|
||||
if (!reset_n)
|
||||
frame_toggle_120m_prev <= 1'b0;
|
||||
else
|
||||
frame_toggle_120m_prev <= frame_toggle_120m;
|
||||
end
|
||||
assign frame_pulse_120m = frame_toggle_120m ^ frame_toggle_120m_prev;
|
||||
|
||||
// ============================================================================
|
||||
// stm32_mixers_enable level CDC into clk_120m_dac
|
||||
// ============================================================================
|
||||
cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m (
|
||||
.src_clk(clk_100m), // Treat as pseudo-source (GPIO is async)
|
||||
.dst_clk(clk_120m_dac),
|
||||
.reset_n(reset_n),
|
||||
.src_signal(stm32_mixers_enable),
|
||||
.dst_signal(mixers_enable_120m)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// Beam-step CDC + edge detection (clk_100m, unchanged from v1)
|
||||
// ============================================================================
|
||||
cdc_single_bit #(.STAGES(2)) cdc_stm32_elevation (
|
||||
.src_clk(clk_100m),
|
||||
.dst_clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.src_signal(stm32_new_elevation),
|
||||
.dst_signal(stm32_new_elevation_sync)
|
||||
);
|
||||
|
||||
cdc_single_bit #(.STAGES(2)) cdc_stm32_azimuth (
|
||||
.src_clk(clk_100m),
|
||||
.dst_clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.src_signal(stm32_new_azimuth),
|
||||
.dst_signal(stm32_new_azimuth_sync)
|
||||
);
|
||||
|
||||
edge_detector_enhanced elevation_edge (
|
||||
.clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.signal_in(stm32_new_elevation_sync),
|
||||
.rising_falling_edge(new_elevation_pulse)
|
||||
);
|
||||
|
||||
edge_detector_enhanced azimuth_edge (
|
||||
.clk(clk_100m),
|
||||
.reset_n(reset_100m_n),
|
||||
.signal_in(stm32_new_azimuth_sync),
|
||||
.rising_falling_edge(new_azimuth_pulse)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// PLFM Chirp Generator (chirp-v2)
|
||||
// ============================================================================
|
||||
plfm_chirp_controller_v2 plfm_chirp_inst (
|
||||
.clk_120m (clk_120m_dac),
|
||||
.clk_100m (clk_100m),
|
||||
.reset_n (reset_n),
|
||||
.reset_100m_n (reset_100m_n),
|
||||
.mixers_enable (mixers_enable_120m),
|
||||
|
||||
// Scheduler bridge (clk_120m_dac, post-CDC)
|
||||
.dst_chirp_valid (dst_chirp_valid),
|
||||
.dst_wave_sel (dst_wave_sel),
|
||||
.frame_pulse_120m(frame_pulse_120m),
|
||||
|
||||
// Beam-step pulses (clk_100m)
|
||||
.new_elevation (new_elevation_pulse),
|
||||
.new_azimuth (new_azimuth_pulse),
|
||||
|
||||
// DAC outputs
|
||||
.chirp_data (chirp_data),
|
||||
.chirp_valid (chirp_valid),
|
||||
.new_chirp_frame(new_chirp_frame),
|
||||
.chirp_done (chirp_sequence_done),
|
||||
.rf_switch_ctrl (fpga_rf_switch),
|
||||
.rx_mixer_en (rx_mixer_en),
|
||||
.tx_mixer_en (tx_mixer_en),
|
||||
|
||||
// ADAR
|
||||
.adar_tx_load_1 (adar_tx_load_1),
|
||||
.adar_rx_load_1 (adar_rx_load_1),
|
||||
.adar_tx_load_2 (adar_tx_load_2),
|
||||
.adar_rx_load_2 (adar_rx_load_2),
|
||||
.adar_tx_load_3 (adar_tx_load_3),
|
||||
.adar_rx_load_3 (adar_rx_load_3),
|
||||
.adar_tx_load_4 (adar_tx_load_4),
|
||||
.adar_rx_load_4 (adar_rx_load_4),
|
||||
.adar_tr_1 (adar_tr_1),
|
||||
.adar_tr_2 (adar_tr_2),
|
||||
.adar_tr_3 (adar_tr_3),
|
||||
.adar_tr_4 (adar_tr_4),
|
||||
|
||||
// Status counters
|
||||
.chirp_counter (current_chirp),
|
||||
.elevation_counter(current_elevation),
|
||||
.azimuth_counter (current_azimuth)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// DAC Output Interface
|
||||
// ============================================================================
|
||||
dac_interface_enhanced dac_interface_inst (
|
||||
.clk_120m (clk_120m_dac),
|
||||
.reset_n (reset_n),
|
||||
.chirp_data (chirp_data),
|
||||
.chirp_valid(chirp_valid),
|
||||
.dac_data(dac_data),
|
||||
.dac_clk(dac_clk),
|
||||
.dac_sleep(dac_sleep)
|
||||
);
|
||||
endmodule
|
||||
.dac_data (dac_data),
|
||||
.dac_clk (dac_clk),
|
||||
.dac_sleep (dac_sleep)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
@@ -52,7 +52,7 @@ PROD_RTL=(
|
||||
radar_system_top.v
|
||||
radar_transmitter.v
|
||||
dac_interface_single.v
|
||||
plfm_chirp_controller.v
|
||||
plfm_chirp_controller_v2.v
|
||||
radar_receiver_final.v
|
||||
tb/ad9484_interface_400m_stub.v
|
||||
ddc_400m.v
|
||||
@@ -112,7 +112,7 @@ RECEIVER_RTL=(
|
||||
# Full system top (receiver chain + TX + USB + detection + self-test)
|
||||
SYSTEM_RTL=(
|
||||
radar_system_top.v
|
||||
radar_transmitter.v dac_interface_single.v plfm_chirp_controller.v
|
||||
radar_transmitter.v dac_interface_single.v plfm_chirp_controller_v2.v
|
||||
"${RECEIVER_RTL[@]}"
|
||||
usb_data_interface.v usb_data_interface_ft2232h.v edge_detector.v
|
||||
cfar_ca.v fpga_self_test.v
|
||||
@@ -517,11 +517,11 @@ run_test "CIC Decimator" \
|
||||
|
||||
run_test "Chirp Controller (BRAM)" \
|
||||
tb/tb_chirp_reg.vvp \
|
||||
tb/tb_chirp_controller.v plfm_chirp_controller.v
|
||||
tb/tb_chirp_controller.v plfm_chirp_controller_v2.v
|
||||
|
||||
run_test "Chirp Contract" \
|
||||
tb/tb_chirp_ctr_reg.vvp \
|
||||
tb/tb_chirp_contract.v plfm_chirp_controller.v
|
||||
tb/tb_chirp_contract.v plfm_chirp_controller_v2.v
|
||||
|
||||
run_doppler_cosim "stationary" ""
|
||||
run_doppler_cosim "moving" "-DSCENARIO_MOVING"
|
||||
|
||||
@@ -67,7 +67,7 @@ set rtl_files [list \
|
||||
"${rtl_dir}/matched_filter_multi_segment.v" \
|
||||
"${rtl_dir}/matched_filter_processing_chain.v" \
|
||||
"${rtl_dir}/nco_400m_enhanced.v" \
|
||||
"${rtl_dir}/plfm_chirp_controller.v" \
|
||||
"${rtl_dir}/plfm_chirp_controller_v2.v" \
|
||||
"${rtl_dir}/chirp_scheduler.v" \
|
||||
"${rtl_dir}/radar_receiver_final.v" \
|
||||
"${rtl_dir}/radar_system_top.v" \
|
||||
|
||||
@@ -1,76 +1,80 @@
|
||||
`timescale 1ns / 1ps
|
||||
// ============================================================================
|
||||
// tb_chirp_contract.v — Architectural Contract Regression Test
|
||||
// tb_chirp_contract.v — Architectural Contract Regression for plfm_chirp_controller_v2
|
||||
// ============================================================================
|
||||
// Purpose: Encode the invariants of the chirp_counter signal path as hard
|
||||
// assertions. If the original author (or anyone) modifies the RTL in a way
|
||||
// that violates these contracts, this testbench will FAIL immediately.
|
||||
// Encodes the chirp-v2 (PR-E) invariants of the chirp_counter signal path as
|
||||
// hard assertions. If the RTL is modified in a way that violates one of these
|
||||
// contracts, this testbench fails immediately.
|
||||
//
|
||||
// Contracts verified:
|
||||
// C1. chirp_counter is 0-indexed, range [0, CHIRP_MAX-1]
|
||||
// C2. chirp_counter resets to 0 (not 1)
|
||||
// C3. chirp_counter increments only on clk_120m (never on clk_100m alone)
|
||||
// C4. chirp_counter increments monotonically (no skips > 1)
|
||||
// C5. chirp_counter increments only at end of listen states
|
||||
// C6. new_chirp input does NOT directly drive chirp_counter
|
||||
// C7. chirp_counter wraps correctly: 0 → CHIRP_MAX-1 → 0
|
||||
// C8. Frame sync compatibility: chirp_counter hits 0 at frame start
|
||||
// C9. GUI mask compatibility: chirp_counter stays within [0, 31] (5-bit safe)
|
||||
// C10. Receiver port connectivity: chirp_counter output matches input expectation
|
||||
// C1. chirp_counter is 0-indexed, wraps via frame_pulse_120m
|
||||
// C2. chirp_counter resets to 0 on frame_pulse_120m (not at chirp_done)
|
||||
// C3. chirp_counter increments only on clk_120m edges (never clk_100m alone)
|
||||
// C4. chirp_counter increments monotonically (no skips > 1)
|
||||
// C5. chirp_counter increments exactly when the FSM leaves ST_CHIRP
|
||||
// C6. dst_chirp_valid pulses (not stm32 toggles) drive chirp_counter
|
||||
// C7. chirp_counter wraps cleanly via frame_pulse: N → 0
|
||||
// C8. chirp_counter stays in [0, 31] when frame ≤ 32 chirps (5-bit safe)
|
||||
// C9. Receiver port-connectivity: TX-side chirp_counter still surfaces on
|
||||
// radar_transmitter.current_chirp (for status_reg compatibility)
|
||||
//
|
||||
// Related bugs: A5 (multi-driven fix), NEW-1 (receiver port fix)
|
||||
// Related history: chirp-v1 had a multi-driven chirp_counter bug (A5).
|
||||
// In chirp-v2 the counter has only ONE driver (the FSM in clk_120m), so
|
||||
// the original A5 race is structurally unreachable — but C3 / C5 still
|
||||
// guard against any future regression that re-introduces a clk_100m driver.
|
||||
// ============================================================================
|
||||
`include "radar_params.vh"
|
||||
|
||||
module tb_chirp_contract;
|
||||
|
||||
// ---- Parameters (must match RTL) ----
|
||||
localparam CHIRP_MAX = 32;
|
||||
localparam T1_SAMPLES = 3600;
|
||||
localparam T1_RADAR_LISTENING = 16440;
|
||||
localparam T2_SAMPLES = 60;
|
||||
localparam T2_RADAR_LISTENING = 20940;
|
||||
localparam GUARD_SAMPLES = 21048;
|
||||
|
||||
// For fast simulation, use a reduced version
|
||||
// Set USE_FAST_SIM=1 to use CHIRP_MAX=4 (completes in ~1ms sim time)
|
||||
// Set USE_FAST_SIM=0 to use real parameters (very long sim time)
|
||||
localparam USE_FAST_SIM = 1;
|
||||
localparam SIM_CHIRP_MAX = USE_FAST_SIM ? 4 : CHIRP_MAX;
|
||||
// ---- Sample-count constants ----
|
||||
localparam integer SHORT_SAMPLES = 120;
|
||||
localparam integer MEDIUM_SAMPLES = 600;
|
||||
|
||||
// ---- Clock generation ----
|
||||
reg clk_120m, clk_100m;
|
||||
reg reset_n;
|
||||
reg new_chirp, new_elevation, new_azimuth, mixers_enable;
|
||||
reg reset_n, reset_100m_n;
|
||||
reg mixers_enable;
|
||||
reg dst_chirp_valid;
|
||||
reg [1:0] dst_wave_sel;
|
||||
reg frame_pulse_120m;
|
||||
reg new_elevation, new_azimuth;
|
||||
|
||||
// DUT outputs
|
||||
// DUT outputs (subset — only those used in the contract checks)
|
||||
wire [7:0] chirp_data;
|
||||
wire chirp_valid;
|
||||
wire new_chirp_frame;
|
||||
wire chirp_done;
|
||||
wire rf_switch_ctrl;
|
||||
wire rx_mixer_en, tx_mixer_en;
|
||||
wire tx_mixer_en, rx_mixer_en;
|
||||
wire adar_tx_load_1, adar_rx_load_1;
|
||||
wire adar_tx_load_2, adar_rx_load_2;
|
||||
wire adar_tx_load_3, adar_rx_load_3;
|
||||
wire adar_tx_load_4, adar_rx_load_4;
|
||||
wire adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4;
|
||||
wire new_chirp_frame;
|
||||
wire [5:0] chirp_counter;
|
||||
wire [5:0] elevation_counter;
|
||||
wire [5:0] azimuth_counter;
|
||||
wire [5:0] elevation_counter, azimuth_counter;
|
||||
|
||||
// ---- DUT instantiation ----
|
||||
plfm_chirp_controller_enhanced #(
|
||||
.CHIRP_MAX(SIM_CHIRP_MAX),
|
||||
.ELEVATION_MAX(31),
|
||||
.AZIMUTH_MAX(50)
|
||||
) dut (
|
||||
// 120 MHz: period = 8.333 ns
|
||||
initial clk_120m = 0;
|
||||
always #4.166 clk_120m = ~clk_120m;
|
||||
|
||||
// 100 MHz: period = 10 ns
|
||||
initial clk_100m = 0;
|
||||
always #5 clk_100m = ~clk_100m;
|
||||
|
||||
// ---- DUT ----
|
||||
plfm_chirp_controller_v2 dut (
|
||||
.clk_120m(clk_120m),
|
||||
.clk_100m(clk_100m),
|
||||
.reset_n(reset_n),
|
||||
.new_chirp(new_chirp),
|
||||
.reset_100m_n(reset_100m_n),
|
||||
.mixers_enable(mixers_enable),
|
||||
.dst_chirp_valid(dst_chirp_valid),
|
||||
.dst_wave_sel(dst_wave_sel),
|
||||
.frame_pulse_120m(frame_pulse_120m),
|
||||
.new_elevation(new_elevation),
|
||||
.new_azimuth(new_azimuth),
|
||||
.mixers_enable(mixers_enable),
|
||||
.chirp_data(chirp_data),
|
||||
.chirp_valid(chirp_valid),
|
||||
.new_chirp_frame(new_chirp_frame),
|
||||
@@ -95,456 +99,190 @@ plfm_chirp_controller_enhanced #(
|
||||
.azimuth_counter(azimuth_counter)
|
||||
);
|
||||
|
||||
// ---- Clock generation ----
|
||||
// 120 MHz: period = 8.333ns
|
||||
initial clk_120m = 0;
|
||||
always #4.167 clk_120m = ~clk_120m;
|
||||
|
||||
// 100 MHz: period = 10ns
|
||||
initial clk_100m = 0;
|
||||
always #5 clk_100m = ~clk_100m;
|
||||
|
||||
// ---- Test infrastructure ----
|
||||
integer pass_count = 0;
|
||||
integer fail_count = 0;
|
||||
integer total_tests = 0;
|
||||
integer test_num;
|
||||
integer pass_count;
|
||||
integer fail_count;
|
||||
integer total_tests;
|
||||
|
||||
// C4 monitor: chirp_counter must change by ±1 or 0 per clk_120m edge.
|
||||
// Wraps via frame_pulse_120m: the FSM samples the pulse at edge T and
|
||||
// schedules chirp_counter <= 0; the wrap (K → 0) is observable on the
|
||||
// next monitor sample (edge T+1). frame_pulse_seen carries the pulse
|
||||
// forward one cycle so the (pre, post) = (K, 0) transition is allowed.
|
||||
reg [5:0] prev_counter;
|
||||
reg frame_pulse_seen;
|
||||
reg c4_violated;
|
||||
|
||||
always @(posedge clk_120m or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
prev_counter <= 6'd0;
|
||||
frame_pulse_seen <= 1'b0;
|
||||
c4_violated <= 1'b0;
|
||||
end else begin
|
||||
if (chirp_counter != prev_counter &&
|
||||
chirp_counter != prev_counter + 6'd1 &&
|
||||
!(chirp_counter == 6'd0 && (frame_pulse_120m || frame_pulse_seen)))
|
||||
begin
|
||||
c4_violated <= 1'b1;
|
||||
end
|
||||
frame_pulse_seen <= frame_pulse_120m;
|
||||
prev_counter <= chirp_counter;
|
||||
end
|
||||
end
|
||||
|
||||
task check;
|
||||
input [255:0] name; // Reduced from 512 for Icarus compat
|
||||
input [255:0] test_name;
|
||||
input condition;
|
||||
begin
|
||||
total_tests = total_tests + 1;
|
||||
test_num = test_num + 1;
|
||||
if (condition) begin
|
||||
$display(" [PASS] Contract %0d: %0s", test_num, test_name);
|
||||
pass_count = pass_count + 1;
|
||||
$display(" [PASS] %0s", name);
|
||||
end else begin
|
||||
$display(" [FAIL] Contract %0d: %0s", test_num, test_name);
|
||||
fail_count = fail_count + 1;
|
||||
$display(" [FAIL] %0s", name);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---- Continuous monitors for contract violations ----
|
||||
|
||||
// Contract C1: Range check — chirp_counter must always be in [0, SIM_CHIRP_MAX]
|
||||
// KNOWN BEHAVIOR: chirp_counter reaches CHIRP_MAX for exactly 1 cycle during DONE state.
|
||||
// This is because the combinational next_state logic checks chirp_counter == CHIRP_MAX-1
|
||||
// at the same clock edge that the registered block increments chirp_counter.
|
||||
// The value CHIRP_MAX only appears in DONE (state 6) and IDLE (state 0, briefly).
|
||||
// This is benign: no chirp is transmitting during DONE, and the receiver doesn't use
|
||||
// chirp_counter during that state. The counter resets to 0 on the next reset.
|
||||
// We flag as a violation ONLY if chirp_counter exceeds CHIRP_MAX (should never happen).
|
||||
reg reset_done;
|
||||
initial reset_done = 0;
|
||||
|
||||
always @(posedge clk_120m) begin
|
||||
if (reset_done && chirp_counter > SIM_CHIRP_MAX) begin
|
||||
$display(" [FAIL] CONTRACT C1 VIOLATION: chirp_counter=%0d > CHIRP_MAX=%0d at time %0t",
|
||||
chirp_counter, SIM_CHIRP_MAX, $time);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// Contract C4: Monotonicity — chirp_counter must not skip values
|
||||
// It can increment by 0 (hold) or 1 (increment), or reset to 0 (via reset or new sequence)
|
||||
reg [5:0] prev_chirp_counter;
|
||||
reg prev_valid;
|
||||
initial prev_valid = 0;
|
||||
|
||||
always @(posedge clk_120m) begin
|
||||
if (reset_done && prev_valid) begin
|
||||
// Allowed transitions:
|
||||
// same value (hold)
|
||||
// +1 (increment, including CHIRP_MAX-1 → CHIRP_MAX overshoot)
|
||||
// reset to 0 (from DONE/IDLE or hardware reset)
|
||||
if (chirp_counter != prev_chirp_counter &&
|
||||
chirp_counter != prev_chirp_counter + 1 &&
|
||||
chirp_counter != 0) begin
|
||||
$display(" [FAIL] CONTRACT C4 VIOLATION: chirp_counter jumped %0d -> %0d at time %0t",
|
||||
prev_chirp_counter, chirp_counter, $time);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
prev_chirp_counter <= chirp_counter;
|
||||
if (reset_done) prev_valid <= 1;
|
||||
end
|
||||
|
||||
// ---- Helper: wait for N clk_120m rising edges ----
|
||||
task wait_120m_cycles;
|
||||
input integer n;
|
||||
integer i;
|
||||
task issue_chirp;
|
||||
input [1:0] wsel;
|
||||
begin
|
||||
@(posedge clk_120m);
|
||||
dst_wave_sel <= wsel;
|
||||
dst_chirp_valid <= 1'b1;
|
||||
@(posedge clk_120m);
|
||||
dst_chirp_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task wait_for_idle;
|
||||
input integer timeout_cycles;
|
||||
integer i;
|
||||
begin
|
||||
for (i = 0; i < timeout_cycles; i = i + 1) begin
|
||||
@(posedge clk_120m);
|
||||
if (dut.state == 1'b0) i = timeout_cycles;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task pulse_frame;
|
||||
begin
|
||||
for (i = 0; i < n; i = i + 1)
|
||||
@(posedge clk_120m);
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---- Helper: wait for N clk_100m rising edges ----
|
||||
task wait_100m_cycles;
|
||||
input integer n;
|
||||
integer i;
|
||||
begin
|
||||
for (i = 0; i < n; i = i + 1)
|
||||
@(posedge clk_100m);
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---- Helper: run one full chirp sequence (IDLE → DONE) ----
|
||||
// Returns the final chirp_counter value
|
||||
reg [5:0] final_chirp_value;
|
||||
reg sequence_completed;
|
||||
|
||||
task run_full_sequence;
|
||||
begin
|
||||
// Trigger: assert new_chirp and mixers_enable
|
||||
mixers_enable = 1;
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(5);
|
||||
|
||||
// Wait for FSM to leave IDLE
|
||||
@(posedge clk_120m);
|
||||
while (dut.current_state == 3'd0) // IDLE = 0
|
||||
@(posedge clk_120m);
|
||||
|
||||
// Now wait for DONE state (state 6)
|
||||
while (dut.current_state != 3'd6) // DONE = 6
|
||||
@(posedge clk_120m);
|
||||
|
||||
final_chirp_value = chirp_counter;
|
||||
sequence_completed = 1;
|
||||
|
||||
// Wait for return to IDLE
|
||||
frame_pulse_120m <= 1'b1;
|
||||
@(posedge clk_120m);
|
||||
while (dut.current_state != 3'd0)
|
||||
@(posedge clk_120m);
|
||||
|
||||
// Deassert
|
||||
new_chirp = 0;
|
||||
mixers_enable = 0;
|
||||
wait_120m_cycles(5);
|
||||
frame_pulse_120m <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---- Main test sequence ----
|
||||
// =========================================================================
|
||||
// MAIN
|
||||
// =========================================================================
|
||||
initial begin
|
||||
$dumpfile("tb_chirp_contract.vcd");
|
||||
$dumpvars(0, tb_chirp_contract);
|
||||
|
||||
// Initialize
|
||||
reset_n = 0;
|
||||
new_chirp = 0;
|
||||
new_elevation = 0;
|
||||
new_azimuth = 0;
|
||||
mixers_enable = 0;
|
||||
sequence_completed = 0;
|
||||
|
||||
$display("============================================================");
|
||||
$display("ARCHITECTURAL CONTRACT REGRESSION TEST — chirp_counter");
|
||||
$display("CHIRP_MAX (sim) = %0d", SIM_CHIRP_MAX);
|
||||
$display("============================================================");
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 1: Reset Contracts
|
||||
// ================================================================
|
||||
|
||||
test_num = 0;
|
||||
pass_count = 0;
|
||||
fail_count = 0;
|
||||
|
||||
reset_n = 0;
|
||||
reset_100m_n = 0;
|
||||
mixers_enable = 0;
|
||||
dst_chirp_valid = 0;
|
||||
dst_wave_sel = `RP_WAVE_SHORT;
|
||||
frame_pulse_120m = 0;
|
||||
new_elevation = 0;
|
||||
new_azimuth = 0;
|
||||
|
||||
$display("");
|
||||
$display("--- GROUP 1: Reset Contracts ---");
|
||||
|
||||
// Apply reset
|
||||
$display("============================================================");
|
||||
$display(" CHIRP CONTRACT REGRESSION (chirp-v2 PR-E)");
|
||||
$display("============================================================");
|
||||
$display("");
|
||||
|
||||
#100;
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
// C2: Reset value is 0
|
||||
check("C2: chirp_counter resets to 0 (not 1)", chirp_counter == 6'd0);
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 2: clk_100m Isolation (Contract C3)
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 2: clk_100m Isolation (Contract C3) ---");
|
||||
|
||||
// C3a: Toggling new_chirp on clk_100m with mixers OFF should not change chirp_counter
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(20);
|
||||
new_chirp = 0;
|
||||
wait_100m_cycles(20);
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(20);
|
||||
new_chirp = 0;
|
||||
wait_100m_cycles(10);
|
||||
check("C3a: new_chirp pulses (mixers off) don't change chirp_counter", chirp_counter == 6'd0);
|
||||
|
||||
// C3b: Toggling new_chirp on clk_100m with mixers ON but before FSM starts
|
||||
// chirp_counter should still be 0 until FSM actually enters a listen state
|
||||
mixers_enable = 1;
|
||||
wait_100m_cycles(5);
|
||||
// FSM should transition out of IDLE now (chirp__toggling is high and mixers on)
|
||||
// But chirp_counter should only change at end of listen, not from clk_100m
|
||||
|
||||
// Record value immediately
|
||||
begin : c3b_block
|
||||
reg [5:0] val_before;
|
||||
val_before = chirp_counter;
|
||||
// Now toggle new_chirp rapidly on clk_100m only
|
||||
new_chirp = 0;
|
||||
wait_100m_cycles(3);
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(3);
|
||||
new_chirp = 0;
|
||||
wait_100m_cycles(3);
|
||||
// If there was a clk_100m driver, chirp_counter would have changed
|
||||
// But the clk_100m toggling alone should have no effect on chirp_counter
|
||||
// (FSM may increment it on clk_120m — that's OK, we just check no EXTRA increments)
|
||||
check("C3b: clk_100m toggling alone doesn't add extra increments",
|
||||
chirp_counter >= val_before); // Must be >= (FSM may have started)
|
||||
end
|
||||
|
||||
// Reset for next test group
|
||||
reset_n = 0;
|
||||
reset_done = 0;
|
||||
prev_valid = 0;
|
||||
new_chirp = 0;
|
||||
mixers_enable = 0;
|
||||
wait_120m_cycles(5);
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 3: Full Sequence Contracts (C1, C5, C7, C8, C9)
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 3: Full Sequence Contracts ---");
|
||||
|
||||
// Run a complete chirp sequence
|
||||
run_full_sequence;
|
||||
|
||||
// C1: Final value in DONE state is CHIRP_MAX (1-cycle overshoot — see C1 comment)
|
||||
// The combinational FSM correctly sees CHIRP_MAX-1 for the state transition,
|
||||
// but the registered increment on the same edge pushes it to CHIRP_MAX.
|
||||
check("C1: Final chirp_counter = CHIRP_MAX (known DONE overshoot)",
|
||||
final_chirp_value == SIM_CHIRP_MAX);
|
||||
|
||||
// C7: After DONE → IDLE, chirp_counter should still be CHIRP_MAX
|
||||
// (it resets to 0 on the next reset, not automatically)
|
||||
check("C7a: chirp_counter holds at CHIRP_MAX after DONE",
|
||||
chirp_counter == SIM_CHIRP_MAX);
|
||||
|
||||
// C8: Verify that chirp_counter was 0 at the start of the sequence
|
||||
// (we tested this via C2 — it starts at 0 after reset)
|
||||
check("C8: Frame start aligns with chirp_counter=0 (from reset)",
|
||||
1'b1); // Verified by C2 above
|
||||
|
||||
// C9: GUI mask compatibility — all OPERATIONAL values must be <= 31 (5-bit safe)
|
||||
// The DONE-state overshoot to CHIRP_MAX is OK because no USB data is packed in DONE.
|
||||
// With real CHIRP_MAX=32, the overshoot value (32) exceeds 5 bits, but it's never sent.
|
||||
// For this test with SIM_CHIRP_MAX=4, the value is 4 which fits in 5 bits anyway.
|
||||
check("C9: Overshoot value fits in 6 bits (port width safe)",
|
||||
final_chirp_value <= 6'd63);
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 4: Contract C6 — new_chirp doesn't drive chirp_counter
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 4: new_chirp Independence (Contract C6) ---");
|
||||
|
||||
// Reset
|
||||
reset_n = 0;
|
||||
reset_done = 0;
|
||||
prev_valid = 0;
|
||||
new_chirp = 0;
|
||||
mixers_enable = 0;
|
||||
wait_120m_cycles(5);
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
// C6a: With mixers OFF, new_chirp pulses should not increment chirp_counter
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(10);
|
||||
new_chirp = 0;
|
||||
wait_100m_cycles(10);
|
||||
check("C6a: new_chirp pulse (mixers off) -> chirp_counter stays 0",
|
||||
chirp_counter == 6'd0);
|
||||
|
||||
// C6b: 100 rapid new_chirp toggles should not cause any chirp_counter change
|
||||
begin : c6b_block
|
||||
integer k;
|
||||
for (k = 0; k < 100; k = k + 1) begin
|
||||
new_chirp = ~new_chirp;
|
||||
#10; // 10ns per toggle = 100MHz-ish
|
||||
end
|
||||
new_chirp = 0;
|
||||
wait_100m_cycles(5);
|
||||
check("C6b: 100 rapid new_chirp toggles -> chirp_counter still 0",
|
||||
chirp_counter == 6'd0);
|
||||
end
|
||||
|
||||
// C6c: Even with mixers ON, new_chirp should only START the FSM,
|
||||
// not directly increment chirp_counter
|
||||
mixers_enable = 1;
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(3);
|
||||
// FSM should be transitioning, but chirp_counter should still be 0
|
||||
// (it only increments at end of first listen state)
|
||||
check("C6c: FSM started but chirp_counter still 0 (no direct drive)",
|
||||
chirp_counter == 6'd0);
|
||||
|
||||
new_chirp = 0;
|
||||
mixers_enable = 0;
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 5: Contract C5 — Increment only at listen state end
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 5: Increment Timing (Contract C5) ---");
|
||||
|
||||
// Reset
|
||||
reset_n = 0;
|
||||
reset_done = 0;
|
||||
prev_valid = 0;
|
||||
new_chirp = 0;
|
||||
mixers_enable = 0;
|
||||
wait_120m_cycles(5);
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
// Start sequence
|
||||
mixers_enable = 1;
|
||||
new_chirp = 1;
|
||||
wait_100m_cycles(5);
|
||||
|
||||
// Wait for LONG_CHIRP state (state 1)
|
||||
@(posedge clk_120m);
|
||||
while (dut.current_state == 3'd0)
|
||||
@(posedge clk_120m);
|
||||
|
||||
// C5a: During LONG_CHIRP, chirp_counter should remain 0
|
||||
check("C5a: chirp_counter=0 during first LONG_CHIRP", chirp_counter == 6'd0);
|
||||
|
||||
// Wait through LONG_CHIRP into LONG_LISTEN
|
||||
while (dut.current_state == 3'd1) // LONG_CHIRP
|
||||
@(posedge clk_120m);
|
||||
|
||||
// Now in LONG_LISTEN (state 2)
|
||||
// C5b: At start of LONG_LISTEN, chirp_counter should still be 0
|
||||
check("C5b: chirp_counter=0 at start of LONG_LISTEN", chirp_counter == 6'd0);
|
||||
|
||||
// Wait for LONG_LISTEN to finish
|
||||
while (dut.current_state == 3'd2) // LONG_LISTEN
|
||||
@(posedge clk_120m);
|
||||
|
||||
// C5c: After first LONG_LISTEN completes, chirp_counter should be 1
|
||||
check("C5c: chirp_counter=1 after first LONG_LISTEN", chirp_counter == 6'd1);
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 6: Multi-Reset Stability (C2 regression)
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 6: Multi-Reset Stability ---");
|
||||
|
||||
// Reset mid-sequence
|
||||
reset_n = 0;
|
||||
reset_done = 0;
|
||||
prev_valid = 0;
|
||||
wait_120m_cycles(3);
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
check("C2-repeat: chirp_counter=0 after mid-sequence reset", chirp_counter == 6'd0);
|
||||
|
||||
// Another reset
|
||||
reset_n = 0;
|
||||
reset_done = 0;
|
||||
prev_valid = 0;
|
||||
wait_120m_cycles(10);
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
check("C2-long: chirp_counter=0 after long reset", chirp_counter == 6'd0);
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 7: Back-to-Back Sequences (C7 wrap behavior)
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 7: Back-to-Back Sequences (Wrap Behavior) ---");
|
||||
|
||||
// Run first sequence
|
||||
run_full_sequence;
|
||||
begin : c7b_check
|
||||
reg [5:0] val_after_first;
|
||||
val_after_first = chirp_counter;
|
||||
check("C7b: First sequence ends at CHIRP_MAX (DONE overshoot)",
|
||||
val_after_first == SIM_CHIRP_MAX);
|
||||
reset_n <= 1;
|
||||
@(posedge clk_100m);
|
||||
reset_100m_n <= 1;
|
||||
@(posedge clk_120m);
|
||||
|
||||
mixers_enable = 1;
|
||||
@(posedge clk_120m);
|
||||
|
||||
// ---------- C2: counter is 0 after reset (before any chirp) ----------
|
||||
check("C2: chirp_counter == 0 after reset", chirp_counter == 6'd0);
|
||||
|
||||
// ---------- C5/C6: dst_chirp_valid drives the counter ----------
|
||||
issue_chirp(`RP_WAVE_SHORT);
|
||||
wait_for_idle(SHORT_SAMPLES + 20);
|
||||
check("C5/C6: chirp_counter == 1 after first SHORT chirp", chirp_counter == 6'd1);
|
||||
|
||||
issue_chirp(`RP_WAVE_SHORT);
|
||||
wait_for_idle(SHORT_SAMPLES + 20);
|
||||
check("C5/C6: chirp_counter == 2 after second SHORT chirp", chirp_counter == 6'd2);
|
||||
|
||||
// ---------- C3: stm32 toggles do NOT drive chirp_counter ----------
|
||||
repeat (8) begin
|
||||
new_elevation = ~new_elevation;
|
||||
new_azimuth = ~new_azimuth;
|
||||
@(posedge clk_100m);
|
||||
end
|
||||
|
||||
// Reset and run second sequence
|
||||
reset_n = 0;
|
||||
reset_done = 0;
|
||||
prev_valid = 0;
|
||||
new_chirp = 0;
|
||||
mixers_enable = 0;
|
||||
wait_120m_cycles(5);
|
||||
reset_n = 1;
|
||||
wait_120m_cycles(3);
|
||||
reset_done = 1;
|
||||
|
||||
check("C7c: chirp_counter wraps to 0 after reset between sequences",
|
||||
chirp_counter == 6'd0);
|
||||
|
||||
// Run second sequence
|
||||
run_full_sequence;
|
||||
check("C7d: Second sequence also ends at CHIRP_MAX",
|
||||
chirp_counter == SIM_CHIRP_MAX);
|
||||
|
||||
// ================================================================
|
||||
// TEST GROUP 8: Contract C10 — Receiver Port Compatibility
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("--- GROUP 8: Receiver Port Compatibility (C10) ---");
|
||||
|
||||
// Verify the output port width is 6 bits (compile-time check via the wire declaration)
|
||||
// If someone changes it to 5 bits, the connection will produce warnings/errors
|
||||
check("C10a: chirp_counter output is 6 bits wide",
|
||||
$bits(chirp_counter) == 6);
|
||||
|
||||
// Verify value range is compatible with receiver frame sync
|
||||
// Receiver checks: chirp_counter == 0 || chirp_counter == 32
|
||||
// With CHIRP_MAX=32, value 32 is never reached (range is 0-31)
|
||||
// So only chirp_counter==0 triggers frame sync — this is correct
|
||||
check("C10b: CHIRP_MAX-1 < 32, so chirp_counter==32 never occurs (expected)",
|
||||
SIM_CHIRP_MAX - 1 < 32 || SIM_CHIRP_MAX > 32);
|
||||
|
||||
// ================================================================
|
||||
new_elevation = 0;
|
||||
new_azimuth = 0;
|
||||
check("C3: stm32 toggles do not change chirp_counter", chirp_counter == 6'd2);
|
||||
|
||||
// ---------- C7: frame_pulse wraps to 0 ----------
|
||||
pulse_frame();
|
||||
@(posedge clk_120m);
|
||||
check("C7: chirp_counter wraps to 0 on frame_pulse", chirp_counter == 6'd0);
|
||||
|
||||
// ---------- C5/C6: incremental sequence after wrap ----------
|
||||
issue_chirp(`RP_WAVE_MEDIUM);
|
||||
wait_for_idle(MEDIUM_SAMPLES + 20);
|
||||
check("C5/C6: chirp_counter == 1 after MEDIUM post-wrap", chirp_counter == 6'd1);
|
||||
|
||||
issue_chirp(`RP_WAVE_SHORT);
|
||||
wait_for_idle(SHORT_SAMPLES + 20);
|
||||
check("C5/C6: chirp_counter == 2 after SHORT post-wrap", chirp_counter == 6'd2);
|
||||
|
||||
// ---------- C4: monotonic — confirmed by the running monitor ----------
|
||||
check("C4: monotonic ±1 increments only (monitor flag)", c4_violated == 1'b0);
|
||||
|
||||
// ---------- C8: 5-bit safe over a 4-chirp run ----------
|
||||
issue_chirp(`RP_WAVE_SHORT); wait_for_idle(SHORT_SAMPLES + 20);
|
||||
issue_chirp(`RP_WAVE_SHORT); wait_for_idle(SHORT_SAMPLES + 20);
|
||||
check("C8: chirp_counter ≤ 31 during a normal frame", chirp_counter <= 6'd31);
|
||||
|
||||
// ---------- C1: full sequence then frame wrap to 0 ----------
|
||||
pulse_frame();
|
||||
@(posedge clk_120m);
|
||||
check("C1/C7: chirp_counter wraps cleanly back to 0", chirp_counter == 6'd0);
|
||||
|
||||
// =====================================================================
|
||||
// SUMMARY
|
||||
// ================================================================
|
||||
// =====================================================================
|
||||
$display("");
|
||||
$display("============================================================");
|
||||
$display("ARCHITECTURAL CONTRACT TEST SUMMARY");
|
||||
$display("============================================================");
|
||||
$display(" Total : %0d", total_tests);
|
||||
$display(" Passed: %0d", pass_count);
|
||||
$display(" Failed: %0d", fail_count);
|
||||
$display("============================================================");
|
||||
|
||||
total_tests = pass_count + fail_count;
|
||||
$display(" CONTRACT RESULTS: %0d/%0d contracts upheld", pass_count, total_tests);
|
||||
if (fail_count == 0)
|
||||
$display("ALL CONTRACTS VERIFIED — chirp_counter architecture is safe.");
|
||||
$display(" STATUS: ALL CONTRACTS UPHELD");
|
||||
else
|
||||
$display("CONTRACT VIOLATIONS DETECTED — review changes to chirp_counter!");
|
||||
|
||||
$display(" STATUS: %0d CONTRACT VIOLATIONS", fail_count);
|
||||
$display("============================================================");
|
||||
$display("");
|
||||
|
||||
#100;
|
||||
$finish;
|
||||
end
|
||||
|
||||
// ---- Timeout watchdog ----
|
||||
initial begin
|
||||
#500_000_000; // 500ms sim time
|
||||
$display("[TIMEOUT] Simulation exceeded 500ms — aborting");
|
||||
$display(" Tests run so far: %0d passed, %0d failed", pass_count, fail_count);
|
||||
#500000; // 500 µs
|
||||
$display("TIMEOUT: Simulation took too long!");
|
||||
$finish;
|
||||
end
|
||||
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
`timescale 1ns / 1ps
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Testbench: plfm_chirp_controller_enhanced
|
||||
// Tests: A5 fix (multi-driven chirp_counter removed), FSM sequencing,
|
||||
// chirp waveform output, T/R switch timing, beam scanning counters
|
||||
// Testbench: plfm_chirp_controller_v2 (chirp-v2 PR-E)
|
||||
//
|
||||
// NOTE: Uses shortened timing parameters for feasible simulation.
|
||||
// The real module uses T1_SAMPLES=3600, T1_RADAR_LISTENING=16440, etc.
|
||||
// We override to T1=8, LISTEN=4, T2=4, GUARD=4 for fast verification.
|
||||
// The v2 module is a pure DAC playback driver — it no longer owns its own
|
||||
// LISTEN/GUARD/DONE FSM (that moved into chirp_scheduler on the RX side).
|
||||
// Tests here verify:
|
||||
// - Reset behavior (IDLE, idle-code 128, all flags low)
|
||||
// - IDLE hold while mixers_enable=0
|
||||
// - SHORT/MEDIUM/LONG chirp playback durations match LUT lengths
|
||||
// - chirp_data exits idle code and rf_switch / adar_tr / chirp_valid go
|
||||
// active during CHIRP, deassert after
|
||||
// - chirp_counter increments per chirp and clears on frame_pulse_120m
|
||||
// - mixer enables: tx_mixer_en active during CHIRP, rx_mixer_en otherwise
|
||||
// - elevation_counter / azimuth_counter still bump on STM32 toggles
|
||||
//
|
||||
// Sample counts (must mirror plfm_chirp_controller_v2.v localparams):
|
||||
// SHORT = 120, MEDIUM = 600, LONG = 3600
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
`include "radar_params.vh"
|
||||
|
||||
module tb_chirp_controller;
|
||||
|
||||
// =========================================================================
|
||||
// PARAMETERS — shortened for simulation
|
||||
// =========================================================================
|
||||
parameter T1_SAMPLES = 8; // was 3600
|
||||
parameter T1_RADAR_LISTENING = 4; // was 16440
|
||||
parameter T2_SAMPLES = 4; // was 60
|
||||
parameter T2_RADAR_LISTENING = 4; // was 20940
|
||||
parameter GUARD_SAMPLES = 4; // was 21048
|
||||
parameter CHIRP_MAX = 4; // was 32 (use 4: 2 long + 2 short)
|
||||
parameter ELEVATION_MAX = 2; // was 31
|
||||
parameter AZIMUTH_MAX = 2; // was 50
|
||||
// ---- Sample-count constants (match the RTL) ----
|
||||
localparam integer SHORT_SAMPLES = 120;
|
||||
localparam integer MEDIUM_SAMPLES = 600;
|
||||
localparam integer LONG_SAMPLES = 3600;
|
||||
|
||||
// =========================================================================
|
||||
// CLOCK GENERATION
|
||||
// =========================================================================
|
||||
reg clk_120m, clk_100m;
|
||||
reg reset_n;
|
||||
reg reset_n, reset_100m_n;
|
||||
|
||||
// 120 MHz: period = 8.333 ns
|
||||
initial clk_120m = 0;
|
||||
@@ -40,8 +43,12 @@ always #5 clk_100m = ~clk_100m;
|
||||
// =========================================================================
|
||||
// DUT SIGNALS
|
||||
// =========================================================================
|
||||
reg new_chirp, new_elevation, new_azimuth;
|
||||
reg mixers_enable;
|
||||
reg mixers_enable;
|
||||
reg dst_chirp_valid;
|
||||
reg [1:0] dst_wave_sel;
|
||||
reg frame_pulse_120m;
|
||||
reg new_elevation;
|
||||
reg new_azimuth;
|
||||
|
||||
wire [7:0] chirp_data;
|
||||
wire chirp_valid;
|
||||
@@ -59,25 +66,19 @@ wire [5:0] elevation_counter;
|
||||
wire [5:0] azimuth_counter;
|
||||
|
||||
// =========================================================================
|
||||
// DUT INSTANTIATION with overridden parameters
|
||||
// DUT
|
||||
// =========================================================================
|
||||
plfm_chirp_controller_enhanced #(
|
||||
.T1_SAMPLES(T1_SAMPLES),
|
||||
.T1_RADAR_LISTENING(T1_RADAR_LISTENING),
|
||||
.T2_SAMPLES(T2_SAMPLES),
|
||||
.T2_RADAR_LISTENING(T2_RADAR_LISTENING),
|
||||
.GUARD_SAMPLES(GUARD_SAMPLES),
|
||||
.CHIRP_MAX(CHIRP_MAX),
|
||||
.ELEVATION_MAX(ELEVATION_MAX),
|
||||
.AZIMUTH_MAX(AZIMUTH_MAX)
|
||||
) dut (
|
||||
plfm_chirp_controller_v2 dut (
|
||||
.clk_120m(clk_120m),
|
||||
.clk_100m(clk_100m),
|
||||
.reset_n(reset_n),
|
||||
.new_chirp(new_chirp),
|
||||
.reset_100m_n(reset_100m_n),
|
||||
.mixers_enable(mixers_enable),
|
||||
.dst_chirp_valid(dst_chirp_valid),
|
||||
.dst_wave_sel(dst_wave_sel),
|
||||
.frame_pulse_120m(frame_pulse_120m),
|
||||
.new_elevation(new_elevation),
|
||||
.new_azimuth(new_azimuth),
|
||||
.mixers_enable(mixers_enable),
|
||||
.chirp_data(chirp_data),
|
||||
.chirp_valid(chirp_valid),
|
||||
.new_chirp_frame(new_chirp_frame),
|
||||
@@ -110,23 +111,6 @@ integer pass_count;
|
||||
integer fail_count;
|
||||
integer total_tests;
|
||||
|
||||
// State name decoder for debug
|
||||
function [95:0] state_name;
|
||||
input [2:0] state;
|
||||
begin
|
||||
case (state)
|
||||
3'b000: state_name = "IDLE ";
|
||||
3'b001: state_name = "LONG_CHIRP ";
|
||||
3'b010: state_name = "LONG_LISTEN ";
|
||||
3'b011: state_name = "GUARD_TIME ";
|
||||
3'b100: state_name = "SHORT_CHIRP ";
|
||||
3'b101: state_name = "SHORT_LISTEN";
|
||||
3'b110: state_name = "DONE ";
|
||||
default: state_name = "UNKNOWN ";
|
||||
endcase
|
||||
end
|
||||
endfunction
|
||||
|
||||
task check;
|
||||
input [255:0] test_name;
|
||||
input condition;
|
||||
@@ -142,357 +126,190 @@ task check;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Wait for N cycles of clk_120m
|
||||
task wait_120m;
|
||||
input integer n;
|
||||
integer i;
|
||||
// Pulse dst_chirp_valid for 1 cycle on clk_120m with the requested wave_sel
|
||||
task issue_chirp;
|
||||
input [1:0] wsel;
|
||||
begin
|
||||
for (i = 0; i < n; i = i + 1)
|
||||
@(posedge clk_120m);
|
||||
@(posedge clk_120m);
|
||||
dst_wave_sel <= wsel;
|
||||
dst_chirp_valid <= 1'b1;
|
||||
@(posedge clk_120m);
|
||||
dst_chirp_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Wait until DUT enters a specific state (with timeout)
|
||||
task wait_for_state;
|
||||
input [2:0] target_state;
|
||||
// Wait until DUT enters ST_IDLE again (chirp finished), with timeout
|
||||
task wait_for_idle;
|
||||
input integer timeout_cycles;
|
||||
integer i;
|
||||
begin
|
||||
for (i = 0; i < timeout_cycles; i = i + 1) begin
|
||||
@(posedge clk_120m);
|
||||
if (dut.current_state == target_state) begin
|
||||
i = timeout_cycles; // exit
|
||||
if (dut.state == 1'b0) begin
|
||||
i = timeout_cycles;
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
// Pulse frame_pulse_120m for 1 cycle on clk_120m
|
||||
task pulse_frame;
|
||||
begin
|
||||
@(posedge clk_120m);
|
||||
frame_pulse_120m <= 1'b1;
|
||||
@(posedge clk_120m);
|
||||
frame_pulse_120m <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// =========================================================================
|
||||
// MAIN TEST SEQUENCE
|
||||
// MAIN
|
||||
// =========================================================================
|
||||
initial begin
|
||||
$dumpfile("tb_chirp_controller.vcd");
|
||||
$dumpvars(0, tb_chirp_controller);
|
||||
|
||||
|
||||
test_num = 0;
|
||||
pass_count = 0;
|
||||
fail_count = 0;
|
||||
|
||||
// Initialize
|
||||
reset_n = 0;
|
||||
new_chirp = 0;
|
||||
new_elevation = 0;
|
||||
new_azimuth = 0;
|
||||
mixers_enable = 0;
|
||||
|
||||
|
||||
reset_n = 0;
|
||||
reset_100m_n = 0;
|
||||
mixers_enable = 0;
|
||||
dst_chirp_valid = 0;
|
||||
dst_wave_sel = `RP_WAVE_SHORT;
|
||||
frame_pulse_120m = 0;
|
||||
new_elevation = 0;
|
||||
new_azimuth = 0;
|
||||
|
||||
$display("");
|
||||
$display("============================================================");
|
||||
$display(" CHIRP CONTROLLER TESTBENCH");
|
||||
$display(" Testing A5 fix: single-driver chirp_counter on clk_120m");
|
||||
$display(" Parameters: CHIRP_MAX=%0d, T1=%0d, T2=%0d", CHIRP_MAX, T1_SAMPLES, T2_SAMPLES);
|
||||
$display(" PLFM CHIRP CONTROLLER V2 TESTBENCH (chirp-v2 PR-E)");
|
||||
$display(" SHORT=%0d, MEDIUM=%0d, LONG=%0d samples",
|
||||
SHORT_SAMPLES, MEDIUM_SAMPLES, LONG_SAMPLES);
|
||||
$display("============================================================");
|
||||
$display("");
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 1: RESET BEHAVIOR
|
||||
// =====================================================================
|
||||
$display("--- Group 1: Reset Behavior ---");
|
||||
|
||||
|
||||
// ---------- Reset ----------
|
||||
$display("--- Group 1: Reset ---");
|
||||
#100;
|
||||
|
||||
// T1.1: After reset, should be in IDLE
|
||||
check("Reset: state is IDLE", dut.current_state == 3'b000);
|
||||
|
||||
// T1.2: chirp_counter should be 0 after reset (was the A5 bug: Driver1 reset to 1, Driver2 to 0)
|
||||
check("Reset: chirp_counter is 0", chirp_counter == 6'd0);
|
||||
|
||||
// T1.3: chirp_data should be 128 (midpoint) in IDLE
|
||||
check("Reset: chirp_data is 128 (midpoint)", chirp_data == 8'd128);
|
||||
|
||||
// T1.4: rf_switch should be off
|
||||
check("Reset: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0);
|
||||
|
||||
// T1.5: chirp_valid should be 0
|
||||
check("Reset: chirp_valid is 0", chirp_valid == 1'b0);
|
||||
|
||||
// T1.6: chirp_done should be 0
|
||||
check("Reset: chirp_done is 0", chirp_done == 1'b0);
|
||||
|
||||
// Release reset
|
||||
check("Reset: state == IDLE", dut.state == 1'b0);
|
||||
check("Reset: chirp_data == 128", chirp_data == 8'd128);
|
||||
check("Reset: chirp_valid low", chirp_valid == 1'b0);
|
||||
check("Reset: rf_switch_ctrl low", rf_switch_ctrl == 1'b0);
|
||||
check("Reset: chirp_done low", chirp_done == 1'b0);
|
||||
check("Reset: chirp_counter == 0", chirp_counter == 6'd0);
|
||||
check("Reset: elevation_counter==1", elevation_counter == 6'd1);
|
||||
check("Reset: azimuth_counter==1", azimuth_counter == 6'd1);
|
||||
|
||||
@(posedge clk_120m);
|
||||
reset_n = 1;
|
||||
reset_n <= 1;
|
||||
@(posedge clk_100m);
|
||||
reset_100m_n <= 1;
|
||||
@(posedge clk_120m);
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 2: IDLE STATE — no transition without mixers_enable
|
||||
// =====================================================================
|
||||
$display("--- Group 2: IDLE Hold ---");
|
||||
|
||||
// T2.1: With new_chirp but no mixers_enable, stay in IDLE
|
||||
new_chirp = 1;
|
||||
wait_120m(5);
|
||||
check("IDLE hold: no transition without mixers_enable", dut.current_state == 3'b000);
|
||||
new_chirp = 0;
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 3: FULL FSM SEQUENCE
|
||||
// =====================================================================
|
||||
$display("--- Group 3: Full FSM Sequence ---");
|
||||
|
||||
// Enable mixers and trigger chirp
|
||||
|
||||
// ---------- IDLE hold without mixers_enable ----------
|
||||
$display("--- Group 2: IDLE Hold (mixers_enable=0) ---");
|
||||
issue_chirp(`RP_WAVE_SHORT);
|
||||
repeat (4) @(posedge clk_120m);
|
||||
check("Without mixers_enable, no transition into CHIRP", dut.state == 1'b0);
|
||||
check("Without mixers_enable, chirp_data stays 128", chirp_data == 8'd128);
|
||||
check("Without mixers_enable, chirp_valid stays 0", chirp_valid == 1'b0);
|
||||
|
||||
// ---------- SHORT chirp playback ----------
|
||||
$display("--- Group 3: SHORT chirp playback (120 samples) ---");
|
||||
mixers_enable = 1;
|
||||
@(posedge clk_120m);
|
||||
new_chirp = 1; // chirp__toggling is just new_chirp pass-through
|
||||
|
||||
issue_chirp(`RP_WAVE_SHORT);
|
||||
|
||||
// 1 dst_clk for IDLE→CHIRP transition, 1 more for CHIRP-branch output
|
||||
// registers (rf_switch / adar_tr / chirp_valid) to assert.
|
||||
@(posedge clk_120m);
|
||||
|
||||
// T3.1: Should transition to LONG_CHIRP
|
||||
wait_for_state(3'b001, 5); // LONG_CHIRP
|
||||
check("FSM: enters LONG_CHIRP", dut.current_state == 3'b001);
|
||||
|
||||
// T3.2: RF switch should be ON during LONG_CHIRP
|
||||
@(posedge clk_120m); // one cycle for output to settle
|
||||
check("LONG_CHIRP: rf_switch_ctrl is 1", rf_switch_ctrl == 1'b1);
|
||||
|
||||
// T3.3: ADAR T/R switches should be 1 (transmit mode)
|
||||
check("LONG_CHIRP: adar_tr_1 is 1", adar_tr_1 == 1'b1);
|
||||
|
||||
// T3.4: chirp_valid should be 1
|
||||
check("LONG_CHIRP: chirp_valid is 1", chirp_valid == 1'b1);
|
||||
|
||||
// T3.5: chirp_data should NOT be 128 (should be reading from LUT)
|
||||
// Note: with shortened params, LUT index wraps, but data shouldn't be stuck at 128
|
||||
// Actually, with T1_SAMPLES=8, it reads long_chirp_lut[0..7] which has real data
|
||||
check("LONG_CHIRP: chirp_data comes from LUT (not midpoint)", chirp_data != 8'd128);
|
||||
|
||||
// Wait for LONG_CHIRP to finish (T1_SAMPLES = 8 cycles)
|
||||
wait_for_state(3'b010, T1_SAMPLES + 5); // LONG_LISTEN
|
||||
|
||||
// T3.6: Should reach LONG_LISTEN
|
||||
check("FSM: enters LONG_LISTEN", dut.current_state == 3'b010);
|
||||
|
||||
// T3.7: RF switch OFF during listen
|
||||
@(posedge clk_120m); #1;
|
||||
check("SHORT: enters CHIRP", dut.state == 1'b1);
|
||||
check("SHORT: rf_switch_ctrl asserted", rf_switch_ctrl == 1'b1);
|
||||
check("SHORT: adar_tr_1 asserted", adar_tr_1 == 1'b1);
|
||||
check("SHORT: chirp_valid asserted", chirp_valid == 1'b1);
|
||||
check("SHORT: tx_mixer_en asserted", tx_mixer_en == 1'b1);
|
||||
check("SHORT: rx_mixer_en deasserted", rx_mixer_en == 1'b0);
|
||||
|
||||
// Drain the chirp window and confirm we land back in IDLE within bound.
|
||||
wait_for_idle(SHORT_SAMPLES + 20);
|
||||
check("SHORT: returns to IDLE within 120+20 cycles", dut.state == 1'b0);
|
||||
check("SHORT: rf_switch_ctrl deasserted in IDLE", rf_switch_ctrl == 1'b0);
|
||||
check("SHORT: chirp_data idle code 128 in IDLE", chirp_data == 8'd128);
|
||||
check("SHORT: chirp_counter incremented to 1", chirp_counter == 6'd1);
|
||||
|
||||
// ---------- MEDIUM chirp playback ----------
|
||||
$display("--- Group 4: MEDIUM chirp playback (600 samples) ---");
|
||||
issue_chirp(`RP_WAVE_MEDIUM);
|
||||
@(posedge clk_120m);
|
||||
check("LONG_LISTEN: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0);
|
||||
|
||||
// T3.8: chirp_data should be 128 during listen
|
||||
check("LONG_LISTEN: chirp_data is 128", chirp_data == 8'd128);
|
||||
|
||||
// T3.9: chirp_counter should have incremented to 1 after first LONG_LISTEN
|
||||
// Wait for listen to finish
|
||||
wait_for_state(3'b001, T1_RADAR_LISTENING + 5); // back to LONG_CHIRP
|
||||
check("chirp_counter: incremented to 1 after first listen", chirp_counter == 6'd1);
|
||||
|
||||
// Now wait through second LONG_CHIRP + LONG_LISTEN cycle
|
||||
// After CHIRP_MAX/2 = 2 long chirps, should go to GUARD_TIME
|
||||
wait_for_state(3'b010, T1_SAMPLES + 5); // LONG_LISTEN again
|
||||
wait_for_state(3'b011, T1_RADAR_LISTENING + 5); // GUARD_TIME
|
||||
|
||||
// T3.10: After CHIRP_MAX/2 long chirps, enters GUARD_TIME
|
||||
check("FSM: enters GUARD_TIME after CHIRP_MAX/2 long chirps", dut.current_state == 3'b011);
|
||||
|
||||
// Wait through guard time
|
||||
wait_for_state(3'b100, GUARD_SAMPLES + 5); // SHORT_CHIRP
|
||||
|
||||
// T3.11: Enters SHORT_CHIRP
|
||||
check("FSM: enters SHORT_CHIRP", dut.current_state == 3'b100);
|
||||
|
||||
// T3.12: RF switch ON during SHORT_CHIRP
|
||||
check("MEDIUM: enters CHIRP", dut.state == 1'b1);
|
||||
check("MEDIUM: active_max_samples==600", dut.active_max_samples == 12'd600);
|
||||
wait_for_idle(MEDIUM_SAMPLES + 20);
|
||||
check("MEDIUM: returns to IDLE", dut.state == 1'b0);
|
||||
check("MEDIUM: chirp_counter == 2", chirp_counter == 6'd2);
|
||||
|
||||
// ---------- LONG chirp playback ----------
|
||||
$display("--- Group 5: LONG chirp playback (3600 samples) ---");
|
||||
issue_chirp(`RP_WAVE_LONG);
|
||||
@(posedge clk_120m);
|
||||
check("SHORT_CHIRP: rf_switch_ctrl is 1", rf_switch_ctrl == 1'b1);
|
||||
|
||||
// Wait through SHORT_CHIRP -> SHORT_LISTEN -> SHORT_CHIRP -> SHORT_LISTEN -> DONE
|
||||
// That's 2 more chirps (chirp_counter goes from 2 to 3, then 3 to CHIRP_MAX-1=3)
|
||||
wait_for_state(3'b101, T2_SAMPLES + 5); // SHORT_LISTEN
|
||||
wait_for_state(3'b100, T2_RADAR_LISTENING + 5); // SHORT_CHIRP again
|
||||
wait_for_state(3'b101, T2_SAMPLES + 5); // SHORT_LISTEN again
|
||||
wait_for_state(3'b110, T2_RADAR_LISTENING + 5); // DONE
|
||||
|
||||
// T3.13: FSM reaches DONE state
|
||||
check("FSM: reaches DONE state", dut.current_state == 3'b110);
|
||||
|
||||
// T3.14: chirp_done asserted — check on next clock edge
|
||||
// Also deassert new_chirp NOW (during DONE state) so FSM stays in IDLE
|
||||
// after DONE transitions. If we wait, FSM goes DONE→IDLE→LONG_CHIRP instantly.
|
||||
new_chirp = 0;
|
||||
check("LONG: enters CHIRP", dut.state == 1'b1);
|
||||
check("LONG: active_max_samples==3600", dut.active_max_samples == 12'd3600);
|
||||
wait_for_idle(LONG_SAMPLES + 20);
|
||||
check("LONG: returns to IDLE", dut.state == 1'b0);
|
||||
check("LONG: chirp_counter == 3", chirp_counter == 6'd3);
|
||||
|
||||
// ---------- frame_pulse clears chirp_counter ----------
|
||||
$display("--- Group 6: frame_pulse clears chirp_counter ---");
|
||||
pulse_frame();
|
||||
@(posedge clk_120m);
|
||||
check("DONE: chirp_done is 1", chirp_done == 1'b1);
|
||||
|
||||
// T3.15: Returns to IDLE
|
||||
// Note: chirp_done check consumed one edge (DONE→IDLE already happened)
|
||||
// With new_chirp=0, FSM should stay in IDLE
|
||||
@(posedge clk_120m);
|
||||
check("FSM: returns to IDLE after DONE", dut.current_state == 3'b000);
|
||||
check("frame_pulse: chirp_counter back to 0", chirp_counter == 6'd0);
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 3b: MULTI-FRAME REGRESSION (C-3)
|
||||
//
|
||||
// Bug: plfm_chirp_controller_enhanced never reset chirp_counter when the
|
||||
// frame completed. After frame 1 the counter sat at CHIRP_MAX, so the
|
||||
// LONG_LISTEN -> GUARD transition guard (== CHIRP_MAX/2-1) never matched
|
||||
// on subsequent frames and frame 2+ ran extra chirps until the 6-bit
|
||||
// counter wrapped.
|
||||
//
|
||||
// These checks prove the counter is cleared at DONE and frame 2 matches
|
||||
// frame 1 exactly.
|
||||
// =====================================================================
|
||||
$display("--- Group 3b: Multi-Frame Regression (C-3) ---");
|
||||
// ---------- LUT data (chirp_data leaves idle during CHIRP) ----------
|
||||
$display("--- Group 7: LUT-driven chirp_data ---");
|
||||
issue_chirp(`RP_WAVE_SHORT);
|
||||
repeat (4) @(posedge clk_120m);
|
||||
check("SHORT mid-chirp: chirp_data != 128 (LUT-driven)", chirp_data != 8'd128);
|
||||
wait_for_idle(SHORT_SAMPLES + 20);
|
||||
|
||||
// T3b.1: Immediately after frame 1 DONE -> IDLE, counter is back to 0.
|
||||
check("C-3: chirp_counter reset to 0 after 1st DONE", chirp_counter == 6'd0);
|
||||
|
||||
// Kick off frame 2 from the same IDLE state (no reset between frames).
|
||||
@(posedge clk_120m);
|
||||
new_chirp = 1;
|
||||
@(posedge clk_120m);
|
||||
|
||||
// T3b.2: Frame 2 enters LONG_CHIRP.
|
||||
wait_for_state(3'b001, 10);
|
||||
check("Frame 2: enters LONG_CHIRP", dut.current_state == 3'b001);
|
||||
|
||||
// T3b.3: Frame 2 reaches GUARD_TIME after exactly CHIRP_MAX/2 long chirps.
|
||||
// (If the counter were not reset, the FSM would stall in
|
||||
// LONG_CHIRP/LONG_LISTEN until the 6-bit counter wrapped.)
|
||||
wait_for_state(3'b011,
|
||||
(T1_SAMPLES + T1_RADAR_LISTENING) * (CHIRP_MAX/2) + 20);
|
||||
check("Frame 2: reaches GUARD_TIME after CHIRP_MAX/2 long chirps",
|
||||
dut.current_state == 3'b011);
|
||||
check("Frame 2: chirp_counter == CHIRP_MAX/2 at GUARD_TIME",
|
||||
chirp_counter == CHIRP_MAX/2);
|
||||
|
||||
// T3b.4: Frame 2 reaches DONE.
|
||||
wait_for_state(3'b110,
|
||||
GUARD_SAMPLES +
|
||||
(T2_SAMPLES + T2_RADAR_LISTENING) * (CHIRP_MAX/2) + 20);
|
||||
check("Frame 2: reaches DONE", dut.current_state == 3'b110);
|
||||
|
||||
// Deassert new_chirp so FSM stays in IDLE after DONE.
|
||||
new_chirp = 0;
|
||||
@(posedge clk_120m);
|
||||
|
||||
// T3b.5: Counter cleared again after frame 2 completes.
|
||||
check("C-3: chirp_counter reset to 0 after 2nd DONE", chirp_counter == 6'd0);
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 4: SINGLE-DRIVER VERIFICATION (A5 FIX CORE TEST)
|
||||
// =====================================================================
|
||||
$display("--- Group 4: A5 Fix - Single Driver Verification ---");
|
||||
|
||||
// Reset and re-run with both clocks to verify no race condition
|
||||
reset_n = 0;
|
||||
// ---------- Mixer disable resets state ----------
|
||||
$display("--- Group 8: Mixer disable ---");
|
||||
issue_chirp(`RP_WAVE_MEDIUM);
|
||||
repeat (10) @(posedge clk_120m);
|
||||
mixers_enable = 0;
|
||||
new_chirp = 0;
|
||||
#100;
|
||||
reset_n = 1;
|
||||
@(posedge clk_120m);
|
||||
|
||||
// T4.1: After re-reset, chirp_counter is 0
|
||||
check("Re-reset: chirp_counter is 0", chirp_counter == 6'd0);
|
||||
|
||||
// T4.2: Toggling new_chirp on clk_100m should NOT change chirp_counter
|
||||
// (The old bug: clk_100m driver would increment it)
|
||||
@(posedge clk_100m);
|
||||
new_chirp = 1;
|
||||
@(posedge clk_100m);
|
||||
@(posedge clk_100m);
|
||||
@(posedge clk_100m);
|
||||
@(posedge clk_100m);
|
||||
check("A5 fix: new_chirp pulses alone don't change chirp_counter", chirp_counter == 6'd0);
|
||||
new_chirp = 0;
|
||||
|
||||
// T4.3: Only the FSM (clk_120m) should drive chirp_counter
|
||||
// Start a chirp sequence and verify counter increments only at listen end
|
||||
mixers_enable = 1;
|
||||
@(posedge clk_120m);
|
||||
new_chirp = 1;
|
||||
@(posedge clk_120m);
|
||||
|
||||
// Wait for first LONG_CHIRP
|
||||
wait_for_state(3'b001, 5);
|
||||
check("A5 fix: chirp_counter still 0 at start of LONG_CHIRP", chirp_counter == 6'd0);
|
||||
|
||||
// Wait for first LONG_LISTEN completion
|
||||
wait_for_state(3'b010, T1_SAMPLES + 5);
|
||||
// During listen, counter hasn't incremented yet
|
||||
check("A5 fix: chirp_counter still 0 during LONG_LISTEN", chirp_counter == 6'd0);
|
||||
|
||||
// Wait for listen to end and counter to increment
|
||||
wait_for_state(3'b001, T1_RADAR_LISTENING + 5); // back to LONG_CHIRP
|
||||
check("A5 fix: chirp_counter is 1 after first listen completes", chirp_counter == 6'd1);
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 5: MIXER DISABLE
|
||||
// =====================================================================
|
||||
$display("--- Group 5: Mixer Disable ---");
|
||||
|
||||
// T5.1: Disabling mixers should reset outputs
|
||||
mixers_enable = 0;
|
||||
wait_120m(3);
|
||||
check("Mixer disable: chirp_data returns to 128", chirp_data == 8'd128);
|
||||
check("Mixer disable: chirp_valid is 0", chirp_valid == 1'b0);
|
||||
check("Mixer disable: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0);
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 6: ELEVATION/AZIMUTH COUNTERS (clk_100m domain, separate)
|
||||
// =====================================================================
|
||||
$display("--- Group 6: Beam Steering Counters ---");
|
||||
|
||||
// Reset
|
||||
reset_n = 0;
|
||||
mixers_enable = 0;
|
||||
new_chirp = 0;
|
||||
new_elevation = 0;
|
||||
new_azimuth = 0;
|
||||
#100;
|
||||
reset_n = 1;
|
||||
@(posedge clk_100m);
|
||||
|
||||
// T6.1: Elevation counter resets to 1
|
||||
check("Reset: elevation_counter is 1", elevation_counter == 6'd1);
|
||||
|
||||
// T6.2: Azimuth counter resets to 1
|
||||
check("Reset: azimuth_counter is 1", azimuth_counter == 6'd1);
|
||||
|
||||
// T6.3: Elevation counter increments on new_elevation
|
||||
// Note: elevation__toggling = new_elevation (level-sensitive pass-through)
|
||||
// With ELEVATION_MAX=2, holding high oscillates 1->2->1->...
|
||||
repeat (3) @(posedge clk_120m);
|
||||
check("Mixer disable: chirp_data idle 128", chirp_data == 8'd128);
|
||||
check("Mixer disable: chirp_valid 0", chirp_valid == 1'b0);
|
||||
check("Mixer disable: rf_switch_ctrl 0", rf_switch_ctrl == 1'b0);
|
||||
check("Mixer disable: tx_mixer_en 0", tx_mixer_en == 1'b0);
|
||||
check("Mixer disable: rx_mixer_en 0", rx_mixer_en == 1'b0);
|
||||
check("Mixer disable: state forced IDLE", dut.state == 1'b0);
|
||||
|
||||
// ---------- Beam-step counters ----------
|
||||
$display("--- Group 9: Beam steering counters ---");
|
||||
new_elevation = 1;
|
||||
@(posedge clk_100m);
|
||||
@(posedge clk_100m);
|
||||
check("Elevation: increments on new_elevation", elevation_counter == 6'd2 || elevation_counter == 6'd1);
|
||||
|
||||
// T6.4: Elevation counter wraps at ELEVATION_MAX
|
||||
// Counter toggles between 1 and 2 each cycle when held high
|
||||
@(posedge clk_100m);
|
||||
check("Elevation: wraps at ELEVATION_MAX",
|
||||
(elevation_counter == 6'd1) || (elevation_counter == 6'd2));
|
||||
check("Elevation: increments on toggle",
|
||||
elevation_counter == 6'd2 || elevation_counter == 6'd3);
|
||||
new_elevation = 0;
|
||||
@(posedge clk_100m);
|
||||
|
||||
// T6.5: Azimuth counter increments on new_azimuth
|
||||
|
||||
new_azimuth = 1;
|
||||
@(posedge clk_100m);
|
||||
@(posedge clk_100m);
|
||||
check("Azimuth: increments on new_azimuth", azimuth_counter == 6'd2 || azimuth_counter == 6'd1);
|
||||
check("Azimuth: increments on toggle",
|
||||
azimuth_counter == 6'd2 || azimuth_counter == 6'd3);
|
||||
new_azimuth = 0;
|
||||
|
||||
// =====================================================================
|
||||
// TEST GROUP 7: MIXER ENABLE SIGNALS
|
||||
// =====================================================================
|
||||
$display("--- Group 7: Mixer Control Outputs ---");
|
||||
|
||||
// T7.1: In IDLE state, both mixers are off even with mixers_enable=1
|
||||
// (Fix #4: mixers are state-dependent, not tied to mixers_enable directly)
|
||||
mixers_enable = 1;
|
||||
#1;
|
||||
check("rx_mixer_en off in IDLE (state-dependent)", rx_mixer_en == 1'b0);
|
||||
|
||||
// T7.2: tx_mixer_en also off in IDLE
|
||||
check("tx_mixer_en off in IDLE (state-dependent)", tx_mixer_en == 1'b0);
|
||||
|
||||
// T7.3: ADAR load pins tied low
|
||||
check("ADAR load pins: adar_tx_load_1 is 0", adar_tx_load_1 == 1'b0);
|
||||
check("ADAR load pins: adar_rx_load_1 is 0", adar_rx_load_1 == 1'b0);
|
||||
|
||||
|
||||
// ---------- ADAR load pins tied low ----------
|
||||
$display("--- Group 10: ADAR load pins ---");
|
||||
check("adar_tx_load_1 tied low", adar_tx_load_1 == 1'b0);
|
||||
check("adar_rx_load_1 tied low", adar_rx_load_1 == 1'b0);
|
||||
check("adar_tx_load_4 tied low", adar_tx_load_4 == 1'b0);
|
||||
check("adar_rx_load_4 tied low", adar_rx_load_4 == 1'b0);
|
||||
|
||||
// =====================================================================
|
||||
// SUMMARY
|
||||
// =====================================================================
|
||||
@@ -506,14 +323,14 @@ initial begin
|
||||
$display(" STATUS: %0d TESTS FAILED", fail_count);
|
||||
$display("============================================================");
|
||||
$display("");
|
||||
|
||||
|
||||
#100;
|
||||
$finish;
|
||||
end
|
||||
|
||||
// Timeout watchdog
|
||||
initial begin
|
||||
#500000; // 500 us max
|
||||
#500000; // 500 µs — covers LONG playback (~30 µs) + headroom
|
||||
$display("TIMEOUT: Simulation took too long!");
|
||||
$finish;
|
||||
end
|
||||
|
||||
@@ -181,7 +181,10 @@ radar_receiver_final dut (
|
||||
// AUDIT-C3: ADC format select — offset-binary baseline
|
||||
.host_adc_format(2'b00),
|
||||
// CFAR: frame-complete output (not used in this TB)
|
||||
.doppler_frame_done_out()
|
||||
.doppler_frame_done_out(),
|
||||
|
||||
// PR-E: pin mixers_enable HIGH so the scheduler runs in this TB
|
||||
.mixers_enable_100m(1'b1)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user