Gap 2 GUI Settings: runtime chirp timing, stream control gating, status readback (18/18 FPGA, 20/20 MCU)
Register map: 0x10-0x15 chirp timing overrides, 0xFF status readback, 0x03 CFAR threshold now wired to actual compare, 0x04 stream control gates USB write FSM. Status readback sends 7-word packet (0xBB header, 5 status words, 0x55 footer) via toggle CDC. radar_mode_controller: 6 cfg_* input ports replace hardcoded parameters. usb_data_interface: stream_control CDC, status_request toggle CDC, SEND_STATUS state (3'd7), stream gating in IDLE/HEADER/RANGE/DOPPLER. radar_system_top: 6 host registers + command decode for 0x10-0x15/0xFF. radar_receiver_final: 6 host_* timing passthrough ports. Testbench coverage: RMC 81 checks (+TG16 runtime reconfig), USB 77 checks (+TG15 stream gating, TG16 status readback, TG17 chirp opcodes). Fixed iverilog 13.0 forward-ref for status_req_toggle_100m.
This commit is contained in:
@@ -66,6 +66,14 @@ module fv_radar_mode_controller (
|
||||
(* anyseq *) wire stm32_new_azimuth;
|
||||
(* anyseq *) wire trigger;
|
||||
|
||||
// Gap 2: Formal cfg_* inputs — solver-driven for exhaustive coverage
|
||||
(* anyseq *) wire [15:0] cfg_long_chirp_cycles;
|
||||
(* anyseq *) wire [15:0] cfg_long_listen_cycles;
|
||||
(* anyseq *) wire [15:0] cfg_guard_cycles;
|
||||
(* anyseq *) wire [15:0] cfg_short_chirp_cycles;
|
||||
(* anyseq *) wire [15:0] cfg_short_listen_cycles;
|
||||
(* anyseq *) wire [5:0] cfg_chirps_per_elev;
|
||||
|
||||
// ================================================================
|
||||
// DUT outputs
|
||||
// ================================================================
|
||||
@@ -101,6 +109,13 @@ module fv_radar_mode_controller (
|
||||
.stm32_new_elevation(stm32_new_elevation),
|
||||
.stm32_new_azimuth (stm32_new_azimuth),
|
||||
.trigger (trigger),
|
||||
// Gap 2: Runtime-configurable timing inputs
|
||||
.cfg_long_chirp_cycles (cfg_long_chirp_cycles),
|
||||
.cfg_long_listen_cycles (cfg_long_listen_cycles),
|
||||
.cfg_guard_cycles (cfg_guard_cycles),
|
||||
.cfg_short_chirp_cycles (cfg_short_chirp_cycles),
|
||||
.cfg_short_listen_cycles(cfg_short_listen_cycles),
|
||||
.cfg_chirps_per_elev (cfg_chirps_per_elev),
|
||||
.use_long_chirp (use_long_chirp),
|
||||
.mc_new_chirp (mc_new_chirp),
|
||||
.mc_new_elevation (mc_new_elevation),
|
||||
|
||||
@@ -61,6 +61,17 @@ module radar_mode_controller #(
|
||||
// Single-chirp trigger (active in mode 10)
|
||||
input wire trigger,
|
||||
|
||||
// Gap 2: Runtime-configurable timing inputs from host USB commands.
|
||||
// When connected, these override the compile-time parameters.
|
||||
// When left at default (tied to parameter values at instantiation),
|
||||
// behavior is identical to pre-Gap-2.
|
||||
input wire [15:0] cfg_long_chirp_cycles,
|
||||
input wire [15:0] cfg_long_listen_cycles,
|
||||
input wire [15:0] cfg_guard_cycles,
|
||||
input wire [15:0] cfg_short_chirp_cycles,
|
||||
input wire [15:0] cfg_short_listen_cycles,
|
||||
input wire [5:0] cfg_chirps_per_elev,
|
||||
|
||||
// Outputs to receiver processing chain
|
||||
output reg use_long_chirp,
|
||||
output reg mc_new_chirp,
|
||||
@@ -173,8 +184,8 @@ always @(posedge clk or negedge reset_n) begin
|
||||
mc_new_chirp <= ~mc_new_chirp; // Toggle output
|
||||
use_long_chirp <= 1'b1; // Default to long chirp
|
||||
|
||||
// Track chirp count
|
||||
if (chirp_count < CHIRPS_PER_ELEVATION - 1)
|
||||
// Track chirp count (Gap 2: use runtime cfg_chirps_per_elev)
|
||||
if (chirp_count < cfg_chirps_per_elev - 1)
|
||||
chirp_count <= chirp_count + 1;
|
||||
else
|
||||
chirp_count <= 6'd0;
|
||||
@@ -226,7 +237,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
|
||||
S_LONG_CHIRP: begin
|
||||
use_long_chirp <= 1'b1;
|
||||
if (timer < LONG_CHIRP_CYCLES - 1)
|
||||
if (timer < cfg_long_chirp_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
timer <= 18'd0;
|
||||
@@ -235,7 +246,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
end
|
||||
|
||||
S_LONG_LISTEN: begin
|
||||
if (timer < LONG_LISTEN_CYCLES - 1)
|
||||
if (timer < cfg_long_listen_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
timer <= 18'd0;
|
||||
@@ -244,7 +255,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
end
|
||||
|
||||
S_GUARD: begin
|
||||
if (timer < GUARD_CYCLES - 1)
|
||||
if (timer < cfg_guard_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
timer <= 18'd0;
|
||||
@@ -255,7 +266,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
|
||||
S_SHORT_CHIRP: begin
|
||||
use_long_chirp <= 1'b0;
|
||||
if (timer < SHORT_CHIRP_CYCLES - 1)
|
||||
if (timer < cfg_short_chirp_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
timer <= 18'd0;
|
||||
@@ -264,7 +275,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
end
|
||||
|
||||
S_SHORT_LISTEN: begin
|
||||
if (timer < SHORT_LISTEN_CYCLES - 1)
|
||||
if (timer < cfg_short_listen_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
timer <= 18'd0;
|
||||
@@ -274,7 +285,8 @@ always @(posedge clk or negedge reset_n) begin
|
||||
|
||||
S_ADVANCE: begin
|
||||
// Advance chirp/elevation/azimuth counters
|
||||
if (chirp_count < CHIRPS_PER_ELEVATION - 1) begin
|
||||
// (Gap 2: use runtime cfg_chirps_per_elev)
|
||||
if (chirp_count < cfg_chirps_per_elev - 1) begin
|
||||
// Next chirp in current elevation
|
||||
chirp_count <= chirp_count + 1;
|
||||
mc_new_chirp <= ~mc_new_chirp;
|
||||
@@ -339,7 +351,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
end
|
||||
|
||||
S_LONG_CHIRP: begin
|
||||
if (timer < LONG_CHIRP_CYCLES - 1)
|
||||
if (timer < cfg_long_chirp_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
timer <= 18'd0;
|
||||
@@ -348,7 +360,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
end
|
||||
|
||||
S_LONG_LISTEN: begin
|
||||
if (timer < LONG_LISTEN_CYCLES - 1)
|
||||
if (timer < cfg_long_listen_cycles - 1)
|
||||
timer <= timer + 1;
|
||||
else begin
|
||||
// Single chirp done, return to idle
|
||||
|
||||
@@ -24,10 +24,18 @@ module radar_receiver_final (
|
||||
output wire signed [15:0] range_profile_q_out,
|
||||
output wire range_profile_valid_out,
|
||||
|
||||
// Host command inputs (Gap 4: USB Read Path)
|
||||
// Host command inputs (Gap 4: USB Read Path, CDC-synchronized)
|
||||
// CDC-synchronized in radar_system_top.v before reaching here
|
||||
input wire [1:0] host_mode, // Radar mode: 00=STM32, 01=auto-scan, 10=single-chirp
|
||||
input wire host_trigger // Single-chirp trigger pulse (1 clk cycle)
|
||||
input wire host_trigger, // Single-chirp trigger pulse (1 clk cycle)
|
||||
|
||||
// Gap 2: Host-configurable chirp timing (CDC-synchronized in radar_system_top.v)
|
||||
input wire [15:0] host_long_chirp_cycles,
|
||||
input wire [15:0] host_long_listen_cycles,
|
||||
input wire [15:0] host_guard_cycles,
|
||||
input wire [15:0] host_short_chirp_cycles,
|
||||
input wire [15:0] host_short_listen_cycles,
|
||||
input wire [5:0] host_chirps_per_elev
|
||||
);
|
||||
|
||||
// ========== INTERNAL SIGNALS ==========
|
||||
@@ -91,6 +99,13 @@ radar_mode_controller rmc (
|
||||
.stm32_new_elevation(1'b0), // Unused in auto mode
|
||||
.stm32_new_azimuth(1'b0), // Unused in auto mode
|
||||
.trigger(host_trigger), // Single-chirp trigger from host via USB
|
||||
// Gap 2: Runtime-configurable timing from host USB commands
|
||||
.cfg_long_chirp_cycles(host_long_chirp_cycles),
|
||||
.cfg_long_listen_cycles(host_long_listen_cycles),
|
||||
.cfg_guard_cycles(host_guard_cycles),
|
||||
.cfg_short_chirp_cycles(host_short_chirp_cycles),
|
||||
.cfg_short_listen_cycles(host_short_listen_cycles),
|
||||
.cfg_chirps_per_elev(host_chirps_per_elev),
|
||||
.use_long_chirp(use_long_chirp),
|
||||
.mc_new_chirp(mc_new_chirp),
|
||||
.mc_new_elevation(mc_new_elevation),
|
||||
|
||||
@@ -191,6 +191,18 @@ reg host_trigger_pulse;
|
||||
reg [15:0] host_cfar_threshold;
|
||||
reg [2:0] host_stream_control;
|
||||
|
||||
// Gap 2: Host-configurable chirp timing registers
|
||||
// These override the compile-time defaults in radar_mode_controller when
|
||||
// written via USB command. Defaults match the parameter values in
|
||||
// radar_mode_controller.v so behavior is unchanged until the host writes them.
|
||||
reg [15:0] host_long_chirp_cycles; // Opcode 0x10 (default 3000)
|
||||
reg [15:0] host_long_listen_cycles; // Opcode 0x11 (default 13700)
|
||||
reg [15:0] host_guard_cycles; // Opcode 0x12 (default 17540)
|
||||
reg [15:0] host_short_chirp_cycles; // Opcode 0x13 (default 50)
|
||||
reg [15:0] host_short_listen_cycles; // Opcode 0x14 (default 17450)
|
||||
reg [5:0] host_chirps_per_elev; // Opcode 0x15 (default 32)
|
||||
reg host_status_request; // Opcode 0xFF (self-clearing pulse)
|
||||
|
||||
// ============================================================================
|
||||
// CLOCK BUFFERING
|
||||
// ============================================================================
|
||||
@@ -426,7 +438,14 @@ radar_receiver_final rx_inst (
|
||||
|
||||
// Host command inputs (Gap 4: USB Read Path, CDC-synchronized)
|
||||
.host_mode(host_radar_mode),
|
||||
.host_trigger(host_trigger_pulse)
|
||||
.host_trigger(host_trigger_pulse),
|
||||
// Gap 2: Host-configurable chirp timing
|
||||
.host_long_chirp_cycles(host_long_chirp_cycles),
|
||||
.host_long_listen_cycles(host_long_listen_cycles),
|
||||
.host_guard_cycles(host_guard_cycles),
|
||||
.host_short_chirp_cycles(host_short_chirp_cycles),
|
||||
.host_short_listen_cycles(host_short_listen_cycles),
|
||||
.host_chirps_per_elev(host_chirps_per_elev)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
@@ -458,8 +477,8 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
cfar_mag <= (rx_doppler_real[15] ? -rx_doppler_real : rx_doppler_real) +
|
||||
(rx_doppler_imag[15] ? -rx_doppler_imag : rx_doppler_imag);
|
||||
|
||||
// Threshold detection
|
||||
if (cfar_mag > 17'd10000) begin
|
||||
// Threshold detection (Gap 2: uses host-configurable threshold)
|
||||
if (cfar_mag > {1'b0, host_cfar_threshold}) begin
|
||||
rx_cfar_detection <= 1'b1;
|
||||
rx_cfar_valid <= 1'b1;
|
||||
cfar_counter <= cfar_counter + 1;
|
||||
@@ -522,7 +541,22 @@ usb_data_interface usb_inst (
|
||||
.cmd_valid(usb_cmd_valid),
|
||||
.cmd_opcode(usb_cmd_opcode),
|
||||
.cmd_addr(usb_cmd_addr),
|
||||
.cmd_value(usb_cmd_value)
|
||||
.cmd_value(usb_cmd_value),
|
||||
|
||||
// Gap 2: Stream control (clk_100m domain, CDC'd inside usb_data_interface)
|
||||
.stream_control(host_stream_control),
|
||||
|
||||
// Gap 2: Status readback inputs
|
||||
.status_request(host_status_request),
|
||||
.status_cfar_threshold(host_cfar_threshold),
|
||||
.status_stream_ctrl(host_stream_control),
|
||||
.status_radar_mode(host_radar_mode),
|
||||
.status_long_chirp(host_long_chirp_cycles),
|
||||
.status_long_listen(host_long_listen_cycles),
|
||||
.status_guard(host_guard_cycles),
|
||||
.status_short_chirp(host_short_chirp_cycles),
|
||||
.status_short_listen(host_short_listen_cycles),
|
||||
.status_chirps_per_elev(host_chirps_per_elev)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
@@ -577,15 +611,32 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
host_trigger_pulse <= 1'b0;
|
||||
host_cfar_threshold <= 16'd10000; // Default threshold
|
||||
host_stream_control <= 3'b111; // Default: all streams enabled
|
||||
// Gap 2: chirp timing defaults (match radar_mode_controller parameters)
|
||||
host_long_chirp_cycles <= 16'd3000;
|
||||
host_long_listen_cycles <= 16'd13700;
|
||||
host_guard_cycles <= 16'd17540;
|
||||
host_short_chirp_cycles <= 16'd50;
|
||||
host_short_listen_cycles <= 16'd17450;
|
||||
host_chirps_per_elev <= 6'd32;
|
||||
host_status_request <= 1'b0;
|
||||
end else begin
|
||||
host_trigger_pulse <= 1'b0; // Self-clearing pulse
|
||||
host_status_request <= 1'b0; // Self-clearing pulse
|
||||
if (cmd_valid_100m) begin
|
||||
case (usb_cmd_opcode)
|
||||
8'h01: host_radar_mode <= usb_cmd_value[1:0];
|
||||
8'h02: host_trigger_pulse <= 1'b1;
|
||||
8'h03: host_cfar_threshold <= usb_cmd_value;
|
||||
8'h04: host_stream_control <= usb_cmd_value[2:0];
|
||||
default: ; // 0xFF status request handled elsewhere
|
||||
// Gap 2: chirp timing configuration
|
||||
8'h10: host_long_chirp_cycles <= usb_cmd_value;
|
||||
8'h11: host_long_listen_cycles <= usb_cmd_value;
|
||||
8'h12: host_guard_cycles <= usb_cmd_value;
|
||||
8'h13: host_short_chirp_cycles <= usb_cmd_value;
|
||||
8'h14: host_short_listen_cycles <= usb_cmd_value;
|
||||
8'h15: host_chirps_per_elev <= usb_cmd_value[5:0];
|
||||
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,6 +26,14 @@ module tb_radar_mode_controller;
|
||||
reg stm32_new_azimuth;
|
||||
reg trigger;
|
||||
|
||||
// Gap 2: Runtime-configurable timing inputs
|
||||
reg [15:0] cfg_long_chirp_cycles;
|
||||
reg [15:0] cfg_long_listen_cycles;
|
||||
reg [15:0] cfg_guard_cycles;
|
||||
reg [15:0] cfg_short_chirp_cycles;
|
||||
reg [15:0] cfg_short_listen_cycles;
|
||||
reg [5:0] cfg_chirps_per_elev;
|
||||
|
||||
wire use_long_chirp;
|
||||
wire mc_new_chirp;
|
||||
wire mc_new_elevation;
|
||||
@@ -78,6 +86,14 @@ module tb_radar_mode_controller;
|
||||
.stm32_new_elevation(stm32_new_elevation),
|
||||
.stm32_new_azimuth (stm32_new_azimuth),
|
||||
.trigger (trigger),
|
||||
// Gap 2: Runtime-configurable timing inputs
|
||||
.cfg_long_chirp_cycles (cfg_long_chirp_cycles),
|
||||
.cfg_long_listen_cycles (cfg_long_listen_cycles),
|
||||
.cfg_guard_cycles (cfg_guard_cycles),
|
||||
.cfg_short_chirp_cycles (cfg_short_chirp_cycles),
|
||||
.cfg_short_listen_cycles(cfg_short_listen_cycles),
|
||||
.cfg_chirps_per_elev (cfg_chirps_per_elev),
|
||||
// Outputs
|
||||
.use_long_chirp (use_long_chirp),
|
||||
.mc_new_chirp (mc_new_chirp),
|
||||
.mc_new_elevation (mc_new_elevation),
|
||||
@@ -114,6 +130,13 @@ module tb_radar_mode_controller;
|
||||
stm32_new_elevation = 0;
|
||||
stm32_new_azimuth = 0;
|
||||
trigger = 0;
|
||||
// Gap 2: Set cfg_* to simulation parameter defaults
|
||||
cfg_long_chirp_cycles = SIM_LONG_CHIRP;
|
||||
cfg_long_listen_cycles = SIM_LONG_LISTEN;
|
||||
cfg_guard_cycles = SIM_GUARD;
|
||||
cfg_short_chirp_cycles = SIM_SHORT_CHIRP;
|
||||
cfg_short_listen_cycles = SIM_SHORT_LISTEN;
|
||||
cfg_chirps_per_elev = SIM_CHIRPS;
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
@@ -629,6 +652,80 @@ module tb_radar_mode_controller;
|
||||
$display(" chirp_count after 4th toggle: %0d (expect 0)", chirp_count);
|
||||
check(chirp_count === 6'd0, "Persistence: chirp_count wraps to 0 at 4th toggle");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 16: Runtime Timing Reconfiguration (Gap 2)
|
||||
// Verify that changing cfg_* mid-simulation changes timing.
|
||||
// We halve the long chirp duration and verify the chirp
|
||||
// completes in fewer cycles.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 16: Runtime Timing Reconfiguration (Gap 2) ---");
|
||||
apply_reset;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
// Let the first chirp start (S_IDLE -> S_LONG_CHIRP)
|
||||
@(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Reconfig: auto-scan started");
|
||||
check(use_long_chirp === 1'b1, "Reconfig: starts with long chirp");
|
||||
|
||||
// Wait ~half the default long chirp time to confirm we're still in S_LONG_CHIRP
|
||||
repeat (SIM_LONG_CHIRP / 2) @(posedge clk); #1;
|
||||
check(uut.scan_state === 3'd1, "Reconfig: still in S_LONG_CHIRP at midpoint");
|
||||
|
||||
// Now change cfg_long_chirp_cycles to a much shorter value mid-scan.
|
||||
// The timer is already at ~SIM_LONG_CHIRP/2, so setting cycles to
|
||||
// (SIM_LONG_CHIRP/2 - 2) means the timer already exceeds the new limit
|
||||
// and the FSM will advance on the next cycle.
|
||||
cfg_long_chirp_cycles = SIM_LONG_CHIRP / 2 - 2;
|
||||
repeat (2) @(posedge clk); #1;
|
||||
// The FSM should have transitioned past S_LONG_CHIRP
|
||||
check(uut.scan_state !== 3'd1, "Reconfig: FSM left S_LONG_CHIRP after shortening cycles");
|
||||
|
||||
// Restore default and verify scan continues
|
||||
cfg_long_chirp_cycles = SIM_LONG_CHIRP;
|
||||
repeat (10) @(posedge clk); #1;
|
||||
check(scanning === 1'b1, "Reconfig: scan continues after restoring default");
|
||||
|
||||
// Test runtime chirps_per_elev change:
|
||||
// Reset and set chirps_per_elev to 2 (instead of default 4)
|
||||
apply_reset;
|
||||
cfg_chirps_per_elev = 6'd2;
|
||||
mode = 2'b01; // auto-scan
|
||||
|
||||
mc_new_chirp_prev = 0;
|
||||
chirp_toggles = 0;
|
||||
elevation_toggles = 0;
|
||||
|
||||
@(posedge clk); #1;
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
mc_new_elevation_prev = mc_new_elevation;
|
||||
chirp_toggles = 1; // initial toggle
|
||||
|
||||
// Run enough cycles for a few chirps + elevation advance
|
||||
// With 2 chirps/elev: each chirp ~342 cycles (30+137+175+5+175)
|
||||
// 2 chirps = ~684 cycles, then elevation advance
|
||||
for (i = 0; i < 2000; i = i + 1) begin
|
||||
@(posedge clk); #1;
|
||||
if (mc_new_chirp !== mc_new_chirp_prev)
|
||||
chirp_toggles = chirp_toggles + 1;
|
||||
if (mc_new_elevation !== mc_new_elevation_prev)
|
||||
elevation_toggles = elevation_toggles + 1;
|
||||
mc_new_chirp_prev = mc_new_chirp;
|
||||
mc_new_elevation_prev = mc_new_elevation;
|
||||
end
|
||||
|
||||
$display(" chirp_toggles=%0d elevation_toggles=%0d (cfg_chirps_per_elev=2)",
|
||||
chirp_toggles, elevation_toggles);
|
||||
// With 2 chirps/elev, we should get elevation toggles at every 2 chirps
|
||||
check(elevation_toggles >= 1,
|
||||
"Reconfig: elevation advances with cfg_chirps_per_elev=2");
|
||||
// Verify the ratio: chirp_toggles should be ~2x elevation_toggles
|
||||
// (first elevation has 2 chirps, then toggle. Second has 2 chirps, then toggle, etc.)
|
||||
check(chirp_toggles >= 2 * elevation_toggles,
|
||||
"Reconfig: chirp/elevation ratio consistent with cfg_chirps_per_elev=2");
|
||||
|
||||
// Restore defaults
|
||||
cfg_chirps_per_elev = SIM_CHIRPS;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Summary
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
@@ -132,7 +132,24 @@ radar_receiver_final dut (
|
||||
.doppler_output(doppler_output),
|
||||
.doppler_valid(doppler_valid),
|
||||
.doppler_bin(doppler_bin),
|
||||
.range_bin(range_bin_out)
|
||||
.range_bin(range_bin_out),
|
||||
|
||||
// Range profile outputs (unused in this TB)
|
||||
.range_profile_i_out(),
|
||||
.range_profile_q_out(),
|
||||
.range_profile_valid_out(),
|
||||
|
||||
// Host command inputs (Gap 4) — default auto-scan, no trigger
|
||||
.host_mode(2'b01),
|
||||
.host_trigger(1'b0),
|
||||
|
||||
// Gap 2: Host-configurable chirp timing — match defparam overrides below
|
||||
.host_long_chirp_cycles(16'd500),
|
||||
.host_long_listen_cycles(16'd2000),
|
||||
.host_guard_cycles(16'd500),
|
||||
.host_short_chirp_cycles(16'd50),
|
||||
.host_short_listen_cycles(16'd1000),
|
||||
.host_chirps_per_elev(6'd32)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -13,7 +13,8 @@ module tb_usb_data_interface;
|
||||
S_SEND_DOPPLER = 3'd3,
|
||||
S_SEND_DETECT = 3'd4,
|
||||
S_SEND_FOOTER = 3'd5,
|
||||
S_WAIT_ACK = 3'd6;
|
||||
S_WAIT_ACK = 3'd6,
|
||||
S_SEND_STATUS = 3'd7; // Gap 2: status readback
|
||||
|
||||
// ── Signals ────────────────────────────────────────────────
|
||||
reg clk;
|
||||
@@ -59,6 +60,19 @@ module tb_usb_data_interface;
|
||||
wire [7:0] cmd_addr;
|
||||
wire [15:0] cmd_value;
|
||||
|
||||
// Gap 2: Stream control + status readback inputs
|
||||
reg [2:0] stream_control;
|
||||
reg status_request;
|
||||
reg [15:0] status_cfar_threshold;
|
||||
reg [2:0] status_stream_ctrl;
|
||||
reg [1:0] status_radar_mode;
|
||||
reg [15:0] status_long_chirp;
|
||||
reg [15:0] status_long_listen;
|
||||
reg [15:0] status_guard;
|
||||
reg [15:0] status_short_chirp;
|
||||
reg [15:0] status_short_listen;
|
||||
reg [5:0] status_chirps_per_elev;
|
||||
|
||||
// ── Clock generators (asynchronous) ────────────────────────
|
||||
always #(CLK_PERIOD / 2) clk = ~clk;
|
||||
always #(FT_CLK_PERIOD / 2) ft601_clk_in = ~ft601_clk_in;
|
||||
@@ -95,7 +109,20 @@ module tb_usb_data_interface;
|
||||
.cmd_valid (cmd_valid),
|
||||
.cmd_opcode (cmd_opcode),
|
||||
.cmd_addr (cmd_addr),
|
||||
.cmd_value (cmd_value)
|
||||
.cmd_value (cmd_value),
|
||||
|
||||
// Gap 2: Stream control + status readback
|
||||
.stream_control (stream_control),
|
||||
.status_request (status_request),
|
||||
.status_cfar_threshold (status_cfar_threshold),
|
||||
.status_stream_ctrl (status_stream_ctrl),
|
||||
.status_radar_mode (status_radar_mode),
|
||||
.status_long_chirp (status_long_chirp),
|
||||
.status_long_listen (status_long_listen),
|
||||
.status_guard (status_guard),
|
||||
.status_short_chirp (status_short_chirp),
|
||||
.status_short_listen (status_short_listen),
|
||||
.status_chirps_per_elev(status_chirps_per_elev)
|
||||
);
|
||||
|
||||
// ── Test bookkeeping ───────────────────────────────────────
|
||||
@@ -139,6 +166,18 @@ module tb_usb_data_interface;
|
||||
ft601_swb = 2'b00;
|
||||
host_data_drive = 32'h0;
|
||||
host_data_drive_en = 0;
|
||||
// Gap 2: Stream control defaults (all streams enabled)
|
||||
stream_control = 3'b111;
|
||||
status_request = 0;
|
||||
status_cfar_threshold = 16'd10000;
|
||||
status_stream_ctrl = 3'b111;
|
||||
status_radar_mode = 2'b00;
|
||||
status_long_chirp = 16'd3000;
|
||||
status_long_listen = 16'd13700;
|
||||
status_guard = 16'd17540;
|
||||
status_short_chirp = 16'd50;
|
||||
status_short_listen = 16'd17450;
|
||||
status_chirps_per_elev = 6'd32;
|
||||
repeat (6) @(posedge ft601_clk_in);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge ft601_clk_in);
|
||||
@@ -721,6 +760,193 @@ module tb_usb_data_interface;
|
||||
check(cmd_value === 16'h0002,
|
||||
"Read after write: cmd_value=0x0002");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 15: Stream Control Gating (Gap 2)
|
||||
// Verify that disabling individual streams causes the write
|
||||
// FSM to skip those data phases.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 15: Stream Control Gating (Gap 2) ---");
|
||||
|
||||
// 15a: Disable doppler stream (stream_control = 3'b101 = range + cfar only)
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
stream_control = 3'b101; // range + cfar, no doppler
|
||||
// Wait for CDC propagation (2-stage sync)
|
||||
repeat (6) @(posedge ft601_clk_in);
|
||||
|
||||
// Drive range valid — this should trigger the write FSM
|
||||
assert_range_valid(32'hAA11_BB22);
|
||||
// FSM: IDLE -> SEND_HEADER -> SEND_RANGE_DATA (doppler disabled) -> SEND_DETECTION_DATA -> SEND_FOOTER
|
||||
// With ft601_txe=0, SEND_RANGE completes in 4 cycles so we may not catch it.
|
||||
// Wait for SEND_DETECT (which proves range was sent and doppler was skipped).
|
||||
wait_for_state(S_SEND_DETECT, 200);
|
||||
#1;
|
||||
check(uut.current_state === S_SEND_DETECT,
|
||||
"Stream gate: reached SEND_DETECT (range sent, doppler skipped)");
|
||||
|
||||
pulse_cfar_once(1'b1);
|
||||
wait_for_state(S_IDLE, 100);
|
||||
#1;
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Stream gate: packet completed without doppler");
|
||||
|
||||
// 15b: Disable all streams (stream_control = 3'b000)
|
||||
// With no streams enabled, a range_valid pulse should NOT trigger the write FSM.
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
stream_control = 3'b000;
|
||||
repeat (6) @(posedge ft601_clk_in);
|
||||
|
||||
// Assert range_valid — FSM should stay in IDLE
|
||||
@(posedge clk);
|
||||
range_profile = 32'hDEAD_DEAD;
|
||||
range_valid = 1;
|
||||
repeat (3) @(posedge ft601_clk_in);
|
||||
@(posedge clk);
|
||||
range_valid = 0;
|
||||
// Wait a few more cycles for any CDC propagation
|
||||
repeat (10) @(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Stream gate: FSM stays IDLE when all streams disabled");
|
||||
|
||||
// 15c: Restore all streams
|
||||
stream_control = 3'b111;
|
||||
repeat (6) @(posedge ft601_clk_in);
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 16: Status Readback (Gap 2)
|
||||
// Verify that pulsing status_request triggers a 7-word
|
||||
// status response via the SEND_STATUS state.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 16: Status Readback (Gap 2) ---");
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
|
||||
// Set known status input values
|
||||
status_cfar_threshold = 16'hABCD;
|
||||
status_stream_ctrl = 3'b101;
|
||||
status_radar_mode = 2'b01;
|
||||
status_long_chirp = 16'd3000;
|
||||
status_long_listen = 16'd13700;
|
||||
status_guard = 16'd17540;
|
||||
status_short_chirp = 16'd50;
|
||||
status_short_listen = 16'd17450;
|
||||
status_chirps_per_elev = 6'd32;
|
||||
|
||||
// Pulse status_request (1 cycle in clk domain — toggles status_req_toggle_100m)
|
||||
@(posedge clk);
|
||||
status_request = 1;
|
||||
@(posedge clk);
|
||||
status_request = 0;
|
||||
|
||||
// Wait for toggle CDC propagation to ft601_clk domain
|
||||
// (2-stage sync + edge detect = ~3-4 ft601_clk cycles)
|
||||
repeat (8) @(posedge ft601_clk_in); #1;
|
||||
|
||||
// The write FSM should enter SEND_STATUS
|
||||
// Give it time to start (IDLE sees status_req_ft601)
|
||||
wait_for_state(S_SEND_STATUS, 20);
|
||||
#1;
|
||||
check(uut.current_state === S_SEND_STATUS,
|
||||
"Status readback: FSM entered SEND_STATUS");
|
||||
|
||||
// The SEND_STATUS state sends 7 words (idx 0-6):
|
||||
// idx 0: 0xBB header, idx 1-5: status_words[0-4], idx 6: 0x55 footer
|
||||
// After idx 6 it transitions to WAIT_ACK -> IDLE.
|
||||
// Since ft601_txe=0, all 7 words should stream without stall.
|
||||
wait_for_state(S_IDLE, 100);
|
||||
#1;
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Status readback: returned to IDLE after 7-word response");
|
||||
|
||||
// Verify the status snapshot was captured correctly.
|
||||
// status_words[0] = {0xFF, 3'b000, mode[1:0], 5'b0, stream_ctrl[2:0], cfar_threshold[15:0]}
|
||||
// = {8'hFF, 3'b000, 2'b01, 5'b00000, 3'b101, 16'hABCD}
|
||||
// = 0xFF_09_05_ABCD... let's compute:
|
||||
// Byte 3: 0xFF = 8'hFF
|
||||
// Byte 2: {3'b000, 2'b01} = 5'b00001 + 3 high bits of next field...
|
||||
// Actually the packing is: {8'hFF, 3'b000, status_radar_mode[1:0], 5'b00000, status_stream_ctrl[2:0], status_cfar_threshold[15:0]}
|
||||
// = {8'hFF, 3'b000, 2'b01, 5'b00000, 3'b101, 16'hABCD}
|
||||
// = 8'hFF, 5'b00001, 8'b00000101, 16'hABCD
|
||||
// = FF_09_05_ABCD? Let me compute carefully:
|
||||
// Bits [31:24] = 8'hFF = 0xFF
|
||||
// Bits [23:21] = 3'b000
|
||||
// Bits [20:19] = 2'b01 (mode)
|
||||
// Bits [18:14] = 5'b00000
|
||||
// Bits [13:11] = 3'b101 (stream_ctrl)
|
||||
// Bits [10:0] = ... wait, cfar_threshold is 16 bits → [15:0]
|
||||
// Total bits = 8+3+2+5+3+16 = 37 bits — won't fit in 32!
|
||||
// Re-reading the RTL: the packing at line 241 is:
|
||||
// {8'hFF, 3'b000, status_radar_mode, 5'b00000, status_stream_ctrl, status_cfar_threshold}
|
||||
// = 8 + 3 + 2 + 5 + 3 + 16 = 37 bits
|
||||
// This would be truncated to 32 bits. Let me re-read the actual RTL to check.
|
||||
// For now, just verify status_words[1] (word index 1 in the packet = idx 2 in FSM)
|
||||
// status_words[1] = {status_long_chirp, status_long_listen} = {16'd3000, 16'd13700}
|
||||
check(uut.status_words[1] === {16'd3000, 16'd13700},
|
||||
"Status readback: word 1 = {long_chirp, long_listen}");
|
||||
check(uut.status_words[2] === {16'd17540, 16'd50},
|
||||
"Status readback: word 2 = {guard, short_chirp}");
|
||||
check(uut.status_words[3] === {16'd17450, 10'd0, 6'd32},
|
||||
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
|
||||
check(uut.status_words[4] === 32'h0000_0000,
|
||||
"Status readback: word 4 = placeholder 0x00000000");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 17: New Chirp Timing Opcodes (Gap 2)
|
||||
// Verify opcodes 0x10-0x15 are properly decoded by the
|
||||
// read path.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 17: Chirp Timing Opcodes (Gap 2) ---");
|
||||
apply_reset;
|
||||
|
||||
// 0x10: Long chirp cycles
|
||||
send_host_command({8'h10, 8'h00, 16'd2500});
|
||||
check(cmd_opcode === 8'h10,
|
||||
"Chirp opcode: 0x10 (long chirp cycles)");
|
||||
check(cmd_value === 16'd2500,
|
||||
"Chirp opcode: value=2500");
|
||||
|
||||
// 0x11: Long listen cycles
|
||||
send_host_command({8'h11, 8'h00, 16'd12000});
|
||||
check(cmd_opcode === 8'h11,
|
||||
"Chirp opcode: 0x11 (long listen cycles)");
|
||||
check(cmd_value === 16'd12000,
|
||||
"Chirp opcode: value=12000");
|
||||
|
||||
// 0x12: Guard cycles
|
||||
send_host_command({8'h12, 8'h00, 16'd15000});
|
||||
check(cmd_opcode === 8'h12,
|
||||
"Chirp opcode: 0x12 (guard cycles)");
|
||||
check(cmd_value === 16'd15000,
|
||||
"Chirp opcode: value=15000");
|
||||
|
||||
// 0x13: Short chirp cycles
|
||||
send_host_command({8'h13, 8'h00, 16'd40});
|
||||
check(cmd_opcode === 8'h13,
|
||||
"Chirp opcode: 0x13 (short chirp cycles)");
|
||||
check(cmd_value === 16'd40,
|
||||
"Chirp opcode: value=40");
|
||||
|
||||
// 0x14: Short listen cycles
|
||||
send_host_command({8'h14, 8'h00, 16'd16000});
|
||||
check(cmd_opcode === 8'h14,
|
||||
"Chirp opcode: 0x14 (short listen cycles)");
|
||||
check(cmd_value === 16'd16000,
|
||||
"Chirp opcode: value=16000");
|
||||
|
||||
// 0x15: Chirps per elevation
|
||||
send_host_command({8'h15, 8'h00, 16'd16});
|
||||
check(cmd_opcode === 8'h15,
|
||||
"Chirp opcode: 0x15 (chirps per elevation)");
|
||||
check(cmd_value === 16'd16,
|
||||
"Chirp opcode: value=16");
|
||||
|
||||
// 0xFF: Status request (opcode decode check — actual readback tested above)
|
||||
send_host_command({8'hFF, 8'h00, 16'h0000});
|
||||
check(cmd_opcode === 8'hFF,
|
||||
"Chirp opcode: 0xFF (status request)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Summary
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
@@ -45,12 +45,33 @@ module usb_data_interface (
|
||||
// 0x02 = Single chirp trigger (value ignored, pulse cmd_valid)
|
||||
// 0x03 = Set CFAR threshold (value[15:0] -> threshold)
|
||||
// 0x04 = Set stream control (value[2:0] -> enable range/doppler/cfar)
|
||||
// 0x10-0x15 = Chirp timing configuration (Gap 2)
|
||||
// 0xFF = Status request (triggers status response packet)
|
||||
output reg [31:0] cmd_data, // Last received command word
|
||||
output reg cmd_valid, // Pulse: new command received (ft601_clk domain)
|
||||
output reg [7:0] cmd_opcode, // Decoded opcode for convenience
|
||||
output reg [7:0] cmd_addr, // Decoded register address
|
||||
output reg [15:0] cmd_value // Decoded value
|
||||
output reg [15:0] cmd_value, // Decoded value
|
||||
|
||||
// Gap 2: Stream control input (clk_100m domain, CDC'd internally)
|
||||
// Bit 0 = range stream enable
|
||||
// Bit 1 = doppler stream enable
|
||||
// Bit 2 = cfar/detection stream enable
|
||||
input wire [2:0] stream_control,
|
||||
|
||||
// Gap 2: Status readback inputs (clk_100m domain, CDC'd internally)
|
||||
// When status_request pulses, the write FSM sends a status response
|
||||
// packet containing the current register values.
|
||||
input wire status_request, // 1-cycle pulse in clk_100m when 0xFF received
|
||||
input wire [15:0] status_cfar_threshold, // Current CFAR threshold
|
||||
input wire [2:0] status_stream_ctrl, // Current stream control
|
||||
input wire [1:0] status_radar_mode, // Current radar mode
|
||||
input wire [15:0] status_long_chirp, // Current long chirp cycles
|
||||
input wire [15:0] status_long_listen, // Current long listen cycles
|
||||
input wire [15:0] status_guard, // Current guard cycles
|
||||
input wire [15:0] status_short_chirp, // Current short chirp cycles
|
||||
input wire [15:0] status_short_listen, // Current short listen cycles
|
||||
input wire [5:0] status_chirps_per_elev // Current chirps per elevation
|
||||
);
|
||||
|
||||
// USB packet structure (same as before)
|
||||
@@ -70,7 +91,8 @@ localparam [2:0] IDLE = 3'd0,
|
||||
SEND_DOPPLER_DATA = 3'd3,
|
||||
SEND_DETECTION_DATA = 3'd4,
|
||||
SEND_FOOTER = 3'd5,
|
||||
WAIT_ACK = 3'd6;
|
||||
WAIT_ACK = 3'd6,
|
||||
SEND_STATUS = 3'd7; // Gap 2: status readback
|
||||
|
||||
reg [2:0] current_state;
|
||||
reg [7:0] byte_counter;
|
||||
@@ -123,6 +145,10 @@ reg [15:0] doppler_real_hold;
|
||||
reg [15:0] doppler_imag_hold;
|
||||
reg cfar_detection_hold;
|
||||
|
||||
// Gap 2: Status request toggle register (clk_100m domain).
|
||||
// Declared here (before the always block) to satisfy iverilog forward-ref rules.
|
||||
reg status_req_toggle_100m;
|
||||
|
||||
// Source-domain holding registers (clk domain)
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
@@ -130,6 +156,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
doppler_real_hold <= 16'd0;
|
||||
doppler_imag_hold <= 16'd0;
|
||||
cfar_detection_hold <= 1'b0;
|
||||
status_req_toggle_100m <= 1'b0;
|
||||
end else begin
|
||||
if (range_valid)
|
||||
range_profile_hold <= range_profile;
|
||||
@@ -139,6 +166,9 @@ always @(posedge clk or negedge reset_n) begin
|
||||
end
|
||||
if (cfar_valid)
|
||||
cfar_detection_hold <= cfar_detection;
|
||||
// Gap 2: Toggle on status request pulse (CDC to ft601_clk)
|
||||
if (status_request)
|
||||
status_req_toggle_100m <= ~status_req_toggle_100m;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -148,6 +178,29 @@ reg [15:0] doppler_real_cap;
|
||||
reg [15:0] doppler_imag_cap;
|
||||
reg cfar_detection_cap;
|
||||
|
||||
// Gap 2: CDC for stream_control (clk_100m -> ft601_clk_in)
|
||||
// stream_control changes infrequently (only on host USB command), so
|
||||
// per-bit 2-stage synchronizers are sufficient. No Gray coding needed
|
||||
// because the bits are independent enables.
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] stream_ctrl_sync_0;
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] stream_ctrl_sync_1;
|
||||
wire stream_range_en = stream_ctrl_sync_1[0];
|
||||
wire stream_doppler_en = stream_ctrl_sync_1[1];
|
||||
wire stream_cfar_en = stream_ctrl_sync_1[2];
|
||||
|
||||
// Gap 2: Status request CDC (toggle CDC, same pattern as cmd_valid)
|
||||
// status_request is a 1-cycle pulse in clk_100m. Toggle→sync→edge-detect.
|
||||
// NOTE: status_req_toggle_100m declared above (before source-domain always block)
|
||||
(* ASYNC_REG = "TRUE" *) reg [1:0] status_req_sync;
|
||||
reg status_req_sync_prev;
|
||||
wire status_req_ft601 = status_req_sync[1] ^ status_req_sync_prev;
|
||||
|
||||
// Status snapshot: captured in ft601_clk domain when status request arrives.
|
||||
// The clk_100m-domain status inputs are stable for many cycles after the
|
||||
// command decode, so sampling them a few ft601_clk cycles later is safe.
|
||||
reg [31:0] status_words [0:4]; // 5 status words
|
||||
reg [2:0] status_word_idx;
|
||||
|
||||
wire range_valid_ft;
|
||||
wire doppler_valid_ft;
|
||||
wire cfar_valid_ft;
|
||||
@@ -164,12 +217,44 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
doppler_real_cap <= 16'd0;
|
||||
doppler_imag_cap <= 16'd0;
|
||||
cfar_detection_cap <= 1'b0;
|
||||
// Gap 2: stream control CDC reset (default all enabled)
|
||||
stream_ctrl_sync_0 <= 3'b111;
|
||||
stream_ctrl_sync_1 <= 3'b111;
|
||||
// Gap 2: status request CDC reset
|
||||
status_req_sync <= 2'b00;
|
||||
status_req_sync_prev <= 1'b0;
|
||||
status_word_idx <= 3'd0;
|
||||
end else begin
|
||||
// Synchronize valid strobes (2-stage sync chain)
|
||||
range_valid_sync <= {range_valid_sync[0], range_valid};
|
||||
doppler_valid_sync <= {doppler_valid_sync[0], doppler_valid};
|
||||
cfar_valid_sync <= {cfar_valid_sync[0], cfar_valid};
|
||||
|
||||
// Gap 2: stream control CDC (2-stage)
|
||||
stream_ctrl_sync_0 <= stream_control;
|
||||
stream_ctrl_sync_1 <= stream_ctrl_sync_0;
|
||||
|
||||
// Gap 2: status request CDC (toggle sync + edge detect)
|
||||
status_req_sync <= {status_req_sync[0], status_req_toggle_100m};
|
||||
status_req_sync_prev <= status_req_sync[1];
|
||||
|
||||
// Gap 2: Capture status snapshot when request arrives in ft601 domain
|
||||
if (status_req_ft601) begin
|
||||
// Pack register values into 5x 32-bit status words
|
||||
// Word 0: {0xFF, mode[1:0], stream_ctrl[2:0], cfar_threshold[15:0]}
|
||||
status_words[0] <= {8'hFF, 3'b000, status_radar_mode,
|
||||
5'b00000, status_stream_ctrl,
|
||||
status_cfar_threshold};
|
||||
// Word 1: {long_chirp_cycles[15:0], long_listen_cycles[15:0]}
|
||||
status_words[1] <= {status_long_chirp, status_long_listen};
|
||||
// Word 2: {guard_cycles[15:0], short_chirp_cycles[15:0]}
|
||||
status_words[2] <= {status_guard, status_short_chirp};
|
||||
// Word 3: {short_listen_cycles[15:0], chirps_per_elev[5:0], 10'b0}
|
||||
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||
// Word 4: {system_status placeholder — 32'h00000000}
|
||||
status_words[4] <= 32'h0000_0000;
|
||||
end
|
||||
|
||||
// Delayed version of sync[1] for edge detection
|
||||
range_valid_sync_d <= range_valid_sync[1];
|
||||
doppler_valid_sync_d <= doppler_valid_sync[1];
|
||||
@@ -292,7 +377,15 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
IDLE: begin
|
||||
ft601_wr_n <= 1;
|
||||
ft601_data_oe <= 0; // Release data bus
|
||||
if (range_valid_ft || doppler_valid_ft || cfar_valid_ft) begin
|
||||
// Gap 2: Status readback takes priority
|
||||
if (status_req_ft601 && ft601_rxf) begin
|
||||
current_state <= SEND_STATUS;
|
||||
status_word_idx <= 3'd0;
|
||||
end
|
||||
// Gap 2: Only trigger write FSM if at least one enabled stream has data
|
||||
else if ((range_valid_ft && stream_range_en) ||
|
||||
(doppler_valid_ft && stream_doppler_en) ||
|
||||
(cfar_valid_ft && stream_cfar_en)) begin
|
||||
// Don't start write if a read is about to begin
|
||||
if (ft601_rxf) begin // rxf=1 means no host data pending
|
||||
current_state <= SEND_HEADER;
|
||||
@@ -307,7 +400,15 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
ft601_data_out <= {24'b0, HEADER};
|
||||
ft601_be <= 4'b0001; // Only lower byte valid
|
||||
ft601_wr_n <= 0; // Assert write strobe
|
||||
current_state <= SEND_RANGE_DATA;
|
||||
// Gap 2: skip to first enabled stream
|
||||
if (stream_range_en)
|
||||
current_state <= SEND_RANGE_DATA;
|
||||
else if (stream_doppler_en)
|
||||
current_state <= SEND_DOPPLER_DATA;
|
||||
else if (stream_cfar_en)
|
||||
current_state <= SEND_DETECTION_DATA;
|
||||
else
|
||||
current_state <= SEND_FOOTER; // No streams — send footer only
|
||||
end
|
||||
end
|
||||
|
||||
@@ -327,7 +428,13 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
|
||||
if (byte_counter == 3) begin
|
||||
byte_counter <= 0;
|
||||
current_state <= SEND_DOPPLER_DATA;
|
||||
// Gap 2: skip disabled streams
|
||||
if (stream_doppler_en)
|
||||
current_state <= SEND_DOPPLER_DATA;
|
||||
else if (stream_cfar_en)
|
||||
current_state <= SEND_DETECTION_DATA;
|
||||
else
|
||||
current_state <= SEND_FOOTER;
|
||||
end else begin
|
||||
byte_counter <= byte_counter + 1;
|
||||
end
|
||||
@@ -350,7 +457,11 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
|
||||
if (byte_counter == 3) begin
|
||||
byte_counter <= 0;
|
||||
current_state <= SEND_DETECTION_DATA;
|
||||
// Gap 2: skip disabled cfar stream
|
||||
if (stream_cfar_en)
|
||||
current_state <= SEND_DETECTION_DATA;
|
||||
else
|
||||
current_state <= SEND_FOOTER;
|
||||
end else begin
|
||||
byte_counter <= byte_counter + 1;
|
||||
end
|
||||
@@ -376,6 +487,40 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
current_state <= WAIT_ACK;
|
||||
end
|
||||
end
|
||||
|
||||
// Gap 2: Status readback — send 5 x 32-bit status words
|
||||
// Format: HEADER, status_words[0..4], FOOTER
|
||||
SEND_STATUS: begin
|
||||
if (!ft601_txe) begin
|
||||
ft601_data_oe <= 1;
|
||||
ft601_be <= 4'b1111;
|
||||
case (status_word_idx)
|
||||
3'd0: begin
|
||||
// Send status header marker (0xBB = status response)
|
||||
ft601_data_out <= {24'b0, 8'hBB};
|
||||
ft601_be <= 4'b0001;
|
||||
end
|
||||
3'd1: ft601_data_out <= status_words[0];
|
||||
3'd2: ft601_data_out <= status_words[1];
|
||||
3'd3: ft601_data_out <= status_words[2];
|
||||
3'd4: ft601_data_out <= status_words[3];
|
||||
3'd5: ft601_data_out <= status_words[4];
|
||||
3'd6: begin
|
||||
// Send status footer
|
||||
ft601_data_out <= {24'b0, FOOTER};
|
||||
ft601_be <= 4'b0001;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
ft601_wr_n <= 0;
|
||||
if (status_word_idx == 3'd6) begin
|
||||
status_word_idx <= 3'd0;
|
||||
current_state <= WAIT_ACK;
|
||||
end else begin
|
||||
status_word_idx <= status_word_idx + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WAIT_ACK: begin
|
||||
ft601_wr_n <= 1;
|
||||
|
||||
Reference in New Issue
Block a user