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:
Jason
2026-03-19 23:54:48 +02:00
parent d2f20f5c15
commit 7cdfa486e5
8 changed files with 604 additions and 26 deletions
@@ -66,6 +66,14 @@ module fv_radar_mode_controller (
(* anyseq *) wire stm32_new_azimuth; (* anyseq *) wire stm32_new_azimuth;
(* anyseq *) wire trigger; (* 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 // DUT outputs
// ================================================================ // ================================================================
@@ -101,6 +109,13 @@ module fv_radar_mode_controller (
.stm32_new_elevation(stm32_new_elevation), .stm32_new_elevation(stm32_new_elevation),
.stm32_new_azimuth (stm32_new_azimuth), .stm32_new_azimuth (stm32_new_azimuth),
.trigger (trigger), .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), .use_long_chirp (use_long_chirp),
.mc_new_chirp (mc_new_chirp), .mc_new_chirp (mc_new_chirp),
.mc_new_elevation (mc_new_elevation), .mc_new_elevation (mc_new_elevation),
+22 -10
View File
@@ -61,6 +61,17 @@ module radar_mode_controller #(
// Single-chirp trigger (active in mode 10) // Single-chirp trigger (active in mode 10)
input wire trigger, 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 // Outputs to receiver processing chain
output reg use_long_chirp, output reg use_long_chirp,
output reg mc_new_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 mc_new_chirp <= ~mc_new_chirp; // Toggle output
use_long_chirp <= 1'b1; // Default to long chirp use_long_chirp <= 1'b1; // Default to long chirp
// Track chirp count // Track chirp count (Gap 2: use runtime cfg_chirps_per_elev)
if (chirp_count < CHIRPS_PER_ELEVATION - 1) if (chirp_count < cfg_chirps_per_elev - 1)
chirp_count <= chirp_count + 1; chirp_count <= chirp_count + 1;
else else
chirp_count <= 6'd0; chirp_count <= 6'd0;
@@ -226,7 +237,7 @@ always @(posedge clk or negedge reset_n) begin
S_LONG_CHIRP: begin S_LONG_CHIRP: begin
use_long_chirp <= 1'b1; use_long_chirp <= 1'b1;
if (timer < LONG_CHIRP_CYCLES - 1) if (timer < cfg_long_chirp_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
timer <= 18'd0; timer <= 18'd0;
@@ -235,7 +246,7 @@ always @(posedge clk or negedge reset_n) begin
end end
S_LONG_LISTEN: begin S_LONG_LISTEN: begin
if (timer < LONG_LISTEN_CYCLES - 1) if (timer < cfg_long_listen_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
timer <= 18'd0; timer <= 18'd0;
@@ -244,7 +255,7 @@ always @(posedge clk or negedge reset_n) begin
end end
S_GUARD: begin S_GUARD: begin
if (timer < GUARD_CYCLES - 1) if (timer < cfg_guard_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
timer <= 18'd0; timer <= 18'd0;
@@ -255,7 +266,7 @@ always @(posedge clk or negedge reset_n) begin
S_SHORT_CHIRP: begin S_SHORT_CHIRP: begin
use_long_chirp <= 1'b0; use_long_chirp <= 1'b0;
if (timer < SHORT_CHIRP_CYCLES - 1) if (timer < cfg_short_chirp_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
timer <= 18'd0; timer <= 18'd0;
@@ -264,7 +275,7 @@ always @(posedge clk or negedge reset_n) begin
end end
S_SHORT_LISTEN: begin S_SHORT_LISTEN: begin
if (timer < SHORT_LISTEN_CYCLES - 1) if (timer < cfg_short_listen_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
timer <= 18'd0; timer <= 18'd0;
@@ -274,7 +285,8 @@ always @(posedge clk or negedge reset_n) begin
S_ADVANCE: begin S_ADVANCE: begin
// Advance chirp/elevation/azimuth counters // 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 // Next chirp in current elevation
chirp_count <= chirp_count + 1; chirp_count <= chirp_count + 1;
mc_new_chirp <= ~mc_new_chirp; mc_new_chirp <= ~mc_new_chirp;
@@ -339,7 +351,7 @@ always @(posedge clk or negedge reset_n) begin
end end
S_LONG_CHIRP: begin S_LONG_CHIRP: begin
if (timer < LONG_CHIRP_CYCLES - 1) if (timer < cfg_long_chirp_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
timer <= 18'd0; timer <= 18'd0;
@@ -348,7 +360,7 @@ always @(posedge clk or negedge reset_n) begin
end end
S_LONG_LISTEN: begin S_LONG_LISTEN: begin
if (timer < LONG_LISTEN_CYCLES - 1) if (timer < cfg_long_listen_cycles - 1)
timer <= timer + 1; timer <= timer + 1;
else begin else begin
// Single chirp done, return to idle // Single chirp done, return to idle
+17 -2
View File
@@ -24,10 +24,18 @@ module radar_receiver_final (
output wire signed [15:0] range_profile_q_out, output wire signed [15:0] range_profile_q_out,
output wire range_profile_valid_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 // 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 [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 ========== // ========== INTERNAL SIGNALS ==========
@@ -91,6 +99,13 @@ radar_mode_controller rmc (
.stm32_new_elevation(1'b0), // Unused in auto mode .stm32_new_elevation(1'b0), // Unused in auto mode
.stm32_new_azimuth(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 .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), .use_long_chirp(use_long_chirp),
.mc_new_chirp(mc_new_chirp), .mc_new_chirp(mc_new_chirp),
.mc_new_elevation(mc_new_elevation), .mc_new_elevation(mc_new_elevation),
+56 -5
View File
@@ -191,6 +191,18 @@ reg host_trigger_pulse;
reg [15:0] host_cfar_threshold; reg [15:0] host_cfar_threshold;
reg [2:0] host_stream_control; 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 // CLOCK BUFFERING
// ============================================================================ // ============================================================================
@@ -426,7 +438,14 @@ radar_receiver_final rx_inst (
// Host command inputs (Gap 4: USB Read Path, CDC-synchronized) // Host command inputs (Gap 4: USB Read Path, CDC-synchronized)
.host_mode(host_radar_mode), .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) + cfar_mag <= (rx_doppler_real[15] ? -rx_doppler_real : rx_doppler_real) +
(rx_doppler_imag[15] ? -rx_doppler_imag : rx_doppler_imag); (rx_doppler_imag[15] ? -rx_doppler_imag : rx_doppler_imag);
// Threshold detection // Threshold detection (Gap 2: uses host-configurable threshold)
if (cfar_mag > 17'd10000) begin if (cfar_mag > {1'b0, host_cfar_threshold}) begin
rx_cfar_detection <= 1'b1; rx_cfar_detection <= 1'b1;
rx_cfar_valid <= 1'b1; rx_cfar_valid <= 1'b1;
cfar_counter <= cfar_counter + 1; cfar_counter <= cfar_counter + 1;
@@ -522,7 +541,22 @@ usb_data_interface usb_inst (
.cmd_valid(usb_cmd_valid), .cmd_valid(usb_cmd_valid),
.cmd_opcode(usb_cmd_opcode), .cmd_opcode(usb_cmd_opcode),
.cmd_addr(usb_cmd_addr), .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_trigger_pulse <= 1'b0;
host_cfar_threshold <= 16'd10000; // Default threshold host_cfar_threshold <= 16'd10000; // Default threshold
host_stream_control <= 3'b111; // Default: all streams enabled 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 end else begin
host_trigger_pulse <= 1'b0; // Self-clearing pulse host_trigger_pulse <= 1'b0; // Self-clearing pulse
host_status_request <= 1'b0; // Self-clearing pulse
if (cmd_valid_100m) begin if (cmd_valid_100m) begin
case (usb_cmd_opcode) case (usb_cmd_opcode)
8'h01: host_radar_mode <= usb_cmd_value[1:0]; 8'h01: host_radar_mode <= usb_cmd_value[1:0];
8'h02: host_trigger_pulse <= 1'b1; 8'h02: host_trigger_pulse <= 1'b1;
8'h03: host_cfar_threshold <= usb_cmd_value; 8'h03: host_cfar_threshold <= usb_cmd_value;
8'h04: host_stream_control <= usb_cmd_value[2:0]; 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 endcase
end end
end end
@@ -26,6 +26,14 @@ module tb_radar_mode_controller;
reg stm32_new_azimuth; reg stm32_new_azimuth;
reg trigger; 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 use_long_chirp;
wire mc_new_chirp; wire mc_new_chirp;
wire mc_new_elevation; wire mc_new_elevation;
@@ -78,6 +86,14 @@ module tb_radar_mode_controller;
.stm32_new_elevation(stm32_new_elevation), .stm32_new_elevation(stm32_new_elevation),
.stm32_new_azimuth (stm32_new_azimuth), .stm32_new_azimuth (stm32_new_azimuth),
.trigger (trigger), .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), .use_long_chirp (use_long_chirp),
.mc_new_chirp (mc_new_chirp), .mc_new_chirp (mc_new_chirp),
.mc_new_elevation (mc_new_elevation), .mc_new_elevation (mc_new_elevation),
@@ -114,6 +130,13 @@ module tb_radar_mode_controller;
stm32_new_elevation = 0; stm32_new_elevation = 0;
stm32_new_azimuth = 0; stm32_new_azimuth = 0;
trigger = 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); repeat (4) @(posedge clk);
reset_n = 1; reset_n = 1;
@(posedge clk); #1; @(posedge clk); #1;
@@ -629,6 +652,80 @@ module tb_radar_mode_controller;
$display(" chirp_count after 4th toggle: %0d (expect 0)", chirp_count); $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"); 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 // Summary
// //
@@ -132,7 +132,24 @@ radar_receiver_final dut (
.doppler_output(doppler_output), .doppler_output(doppler_output),
.doppler_valid(doppler_valid), .doppler_valid(doppler_valid),
.doppler_bin(doppler_bin), .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)
); );
// ============================================================================ // ============================================================================
+228 -2
View File
@@ -13,7 +13,8 @@ module tb_usb_data_interface;
S_SEND_DOPPLER = 3'd3, S_SEND_DOPPLER = 3'd3,
S_SEND_DETECT = 3'd4, S_SEND_DETECT = 3'd4,
S_SEND_FOOTER = 3'd5, 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 // Signals
reg clk; reg clk;
@@ -59,6 +60,19 @@ module tb_usb_data_interface;
wire [7:0] cmd_addr; wire [7:0] cmd_addr;
wire [15:0] cmd_value; 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) // Clock generators (asynchronous)
always #(CLK_PERIOD / 2) clk = ~clk; always #(CLK_PERIOD / 2) clk = ~clk;
always #(FT_CLK_PERIOD / 2) ft601_clk_in = ~ft601_clk_in; 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_valid (cmd_valid),
.cmd_opcode (cmd_opcode), .cmd_opcode (cmd_opcode),
.cmd_addr (cmd_addr), .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 // Test bookkeeping
@@ -139,6 +166,18 @@ module tb_usb_data_interface;
ft601_swb = 2'b00; ft601_swb = 2'b00;
host_data_drive = 32'h0; host_data_drive = 32'h0;
host_data_drive_en = 0; 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); repeat (6) @(posedge ft601_clk_in);
reset_n = 1; reset_n = 1;
repeat (2) @(posedge ft601_clk_in); repeat (2) @(posedge ft601_clk_in);
@@ -721,6 +760,193 @@ module tb_usb_data_interface;
check(cmd_value === 16'h0002, check(cmd_value === 16'h0002,
"Read after write: cmd_value=0x0002"); "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 // Summary
// //
+148 -3
View File
@@ -45,12 +45,33 @@ module usb_data_interface (
// 0x02 = Single chirp trigger (value ignored, pulse cmd_valid) // 0x02 = Single chirp trigger (value ignored, pulse cmd_valid)
// 0x03 = Set CFAR threshold (value[15:0] -> threshold) // 0x03 = Set CFAR threshold (value[15:0] -> threshold)
// 0x04 = Set stream control (value[2:0] -> enable range/doppler/cfar) // 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) // 0xFF = Status request (triggers status response packet)
output reg [31:0] cmd_data, // Last received command word output reg [31:0] cmd_data, // Last received command word
output reg cmd_valid, // Pulse: new command received (ft601_clk domain) 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_opcode, // Decoded opcode for convenience
output reg [7:0] cmd_addr, // Decoded register address 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) // USB packet structure (same as before)
@@ -70,7 +91,8 @@ localparam [2:0] IDLE = 3'd0,
SEND_DOPPLER_DATA = 3'd3, SEND_DOPPLER_DATA = 3'd3,
SEND_DETECTION_DATA = 3'd4, SEND_DETECTION_DATA = 3'd4,
SEND_FOOTER = 3'd5, 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 [2:0] current_state;
reg [7:0] byte_counter; reg [7:0] byte_counter;
@@ -123,6 +145,10 @@ reg [15:0] doppler_real_hold;
reg [15:0] doppler_imag_hold; reg [15:0] doppler_imag_hold;
reg cfar_detection_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) // Source-domain holding registers (clk domain)
always @(posedge clk or negedge reset_n) begin always @(posedge clk or negedge reset_n) begin
if (!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_real_hold <= 16'd0;
doppler_imag_hold <= 16'd0; doppler_imag_hold <= 16'd0;
cfar_detection_hold <= 1'b0; cfar_detection_hold <= 1'b0;
status_req_toggle_100m <= 1'b0;
end else begin end else begin
if (range_valid) if (range_valid)
range_profile_hold <= range_profile; range_profile_hold <= range_profile;
@@ -139,6 +166,9 @@ always @(posedge clk or negedge reset_n) begin
end end
if (cfar_valid) if (cfar_valid)
cfar_detection_hold <= cfar_detection; 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
end end
@@ -148,6 +178,29 @@ reg [15:0] doppler_real_cap;
reg [15:0] doppler_imag_cap; reg [15:0] doppler_imag_cap;
reg cfar_detection_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 range_valid_ft;
wire doppler_valid_ft; wire doppler_valid_ft;
wire cfar_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_real_cap <= 16'd0;
doppler_imag_cap <= 16'd0; doppler_imag_cap <= 16'd0;
cfar_detection_cap <= 1'b0; 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 end else begin
// Synchronize valid strobes (2-stage sync chain) // Synchronize valid strobes (2-stage sync chain)
range_valid_sync <= {range_valid_sync[0], range_valid}; range_valid_sync <= {range_valid_sync[0], range_valid};
doppler_valid_sync <= {doppler_valid_sync[0], doppler_valid}; doppler_valid_sync <= {doppler_valid_sync[0], doppler_valid};
cfar_valid_sync <= {cfar_valid_sync[0], cfar_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 // Delayed version of sync[1] for edge detection
range_valid_sync_d <= range_valid_sync[1]; range_valid_sync_d <= range_valid_sync[1];
doppler_valid_sync_d <= doppler_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 IDLE: begin
ft601_wr_n <= 1; ft601_wr_n <= 1;
ft601_data_oe <= 0; // Release data bus 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 // Don't start write if a read is about to begin
if (ft601_rxf) begin // rxf=1 means no host data pending if (ft601_rxf) begin // rxf=1 means no host data pending
current_state <= SEND_HEADER; 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_data_out <= {24'b0, HEADER};
ft601_be <= 4'b0001; // Only lower byte valid ft601_be <= 4'b0001; // Only lower byte valid
ft601_wr_n <= 0; // Assert write strobe ft601_wr_n <= 0; // Assert write strobe
// Gap 2: skip to first enabled stream
if (stream_range_en)
current_state <= SEND_RANGE_DATA; 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
end end
@@ -327,7 +428,13 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
if (byte_counter == 3) begin if (byte_counter == 3) begin
byte_counter <= 0; byte_counter <= 0;
// Gap 2: skip disabled streams
if (stream_doppler_en)
current_state <= SEND_DOPPLER_DATA; current_state <= SEND_DOPPLER_DATA;
else if (stream_cfar_en)
current_state <= SEND_DETECTION_DATA;
else
current_state <= SEND_FOOTER;
end else begin end else begin
byte_counter <= byte_counter + 1; byte_counter <= byte_counter + 1;
end end
@@ -350,7 +457,11 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
if (byte_counter == 3) begin if (byte_counter == 3) begin
byte_counter <= 0; byte_counter <= 0;
// Gap 2: skip disabled cfar stream
if (stream_cfar_en)
current_state <= SEND_DETECTION_DATA; current_state <= SEND_DETECTION_DATA;
else
current_state <= SEND_FOOTER;
end else begin end else begin
byte_counter <= byte_counter + 1; byte_counter <= byte_counter + 1;
end end
@@ -377,6 +488,40 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
end end
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 WAIT_ACK: begin
ft601_wr_n <= 1; ft601_wr_n <= 1;
ft601_data_oe <= 0; // Release data bus ft601_data_oe <= 0; // Release data bus