Gap 4 USB Read Path: wire host-to-FPGA command path with toggle CDC, add read path tests

- usb_data_interface.v: Add FT601 read FSM (RD_IDLE/OE_ASSERT/READING/
  DEASSERT/PROCESS) + cmd_data/valid/opcode/addr/value output ports
- radar_system_top.v: Connect cmd_* ports from usb_inst, add toggle CDC
  for cmd_valid (ft601_clk -> clk_100m), add command decode registers
  (mode/trigger/cfar_threshold/stream_control), wire host_mode and
  host_trigger to rx_inst
- radar_receiver_final.v: Add host_mode[1:0] and host_trigger input
  ports, replace hardcoded mode/trigger in radar_mode_controller instance
- tb_usb_data_interface.v: Connect cmd_* ports, add host data bus driver,
  add Test Groups 12 (single command), 13 (multiple commands), 14
  (read/write interleave). USB TB now has 55 checks.

Regression: 18/18 PASS
This commit is contained in:
Jason
2026-03-19 23:16:26 +02:00
parent c6103b37de
commit e5d1b3cfc3
4 changed files with 477 additions and 92 deletions
+8 -3
View File
@@ -22,7 +22,12 @@ module radar_receiver_final (
// Matched filter range profile output (for USB) // Matched filter range profile output (for USB)
output wire signed [15:0] range_profile_i_out, output wire signed [15:0] range_profile_i_out,
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)
// 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)
); );
// ========== INTERNAL SIGNALS ========== // ========== INTERNAL SIGNALS ==========
@@ -81,11 +86,11 @@ wire [5:0] rmc_azimuth_count;
radar_mode_controller rmc ( radar_mode_controller rmc (
.clk(clk), .clk(clk),
.reset_n(reset_n), .reset_n(reset_n),
.mode(2'b01), // Auto-scan mode .mode(host_mode), // Controlled by host via USB (default: 2'b01 auto-scan)
.stm32_new_chirp(1'b0), // Unused in auto mode .stm32_new_chirp(1'b0), // Unused in auto mode
.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(1'b0), // Unused in auto mode .trigger(host_trigger), // Single-chirp trigger from host via USB
.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),
+94 -2
View File
@@ -176,6 +176,21 @@ wire usb_cfar_valid;
// System status // System status
reg [3:0] status_reg; reg [3:0] status_reg;
// USB host command outputs (Gap 4: USB Read Path)
// These are in the ft601_clk domain; CDC'd to clk_100m below
wire [31:0] usb_cmd_data;
wire usb_cmd_valid; // 1-cycle pulse in ft601_clk domain
wire [7:0] usb_cmd_opcode;
wire [7:0] usb_cmd_addr;
wire [15:0] usb_cmd_value;
// USB command decode registers (clk_100m domain, driven by CDC block below)
// Declared here (before rx_inst) so Icarus Verilog can resolve forward refs.
reg [1:0] host_radar_mode;
reg host_trigger_pulse;
reg [15:0] host_cfar_threshold;
reg [2:0] host_stream_control;
// ============================================================================ // ============================================================================
// CLOCK BUFFERING // CLOCK BUFFERING
// ============================================================================ // ============================================================================
@@ -407,7 +422,11 @@ radar_receiver_final rx_inst (
// Matched filter range profile (for USB) // Matched filter range profile (for USB)
.range_profile_i_out(rx_range_profile[15:0]), .range_profile_i_out(rx_range_profile[15:0]),
.range_profile_q_out(rx_range_profile[31:16]), .range_profile_q_out(rx_range_profile[31:16]),
.range_profile_valid_out(rx_range_valid) .range_profile_valid_out(rx_range_valid),
// Host command inputs (Gap 4: USB Read Path, CDC-synchronized)
.host_mode(host_radar_mode),
.host_trigger(host_trigger_pulse)
); );
// ============================================================================ // ============================================================================
@@ -496,9 +515,82 @@ usb_data_interface usb_inst (
.ft601_srb(ft601_srb), .ft601_srb(ft601_srb),
.ft601_swb(ft601_swb), .ft601_swb(ft601_swb),
.ft601_clk_out(ft601_clk_out), .ft601_clk_out(ft601_clk_out),
.ft601_clk_in(ft601_clk_buf) .ft601_clk_in(ft601_clk_buf),
// Host command outputs (Gap 4: USB Read Path)
.cmd_data(usb_cmd_data),
.cmd_valid(usb_cmd_valid),
.cmd_opcode(usb_cmd_opcode),
.cmd_addr(usb_cmd_addr),
.cmd_value(usb_cmd_value)
); );
// ============================================================================
// USB COMMAND CDC: ft601_clk → clk_100m (Gap 4: USB Read Path)
// ============================================================================
// cmd_valid is a 1-cycle pulse in ft601_clk. Use toggle CDC (same pattern
// as chirp_frame_toggle_120m above) to safely transfer it to clk_100m.
// cmd_data/opcode/addr/value are held stable after cmd_valid pulses, so
// we simply sample them in clk_100m when the CDC'd pulse arrives.
// Step 1: Toggle on cmd_valid pulse (ft601_clk domain)
reg cmd_valid_toggle_ft601;
always @(posedge ft601_clk_buf or negedge sys_reset_ft601_n) begin
if (!sys_reset_ft601_n)
cmd_valid_toggle_ft601 <= 1'b0;
else if (usb_cmd_valid)
cmd_valid_toggle_ft601 <= ~cmd_valid_toggle_ft601;
end
// Step 2: Synchronize toggle to clk_100m domain (3-stage)
wire cmd_valid_toggle_100m;
cdc_single_bit #(
.STAGES(3)
) cdc_cmd_valid (
.src_clk(ft601_clk_buf),
.dst_clk(clk_100m_buf),
.reset_n(sys_reset_n),
.src_signal(cmd_valid_toggle_ft601),
.dst_signal(cmd_valid_toggle_100m)
);
// Step 3: Edge-detect toggle to recover pulse in clk_100m domain
reg cmd_valid_toggle_100m_prev;
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
if (!sys_reset_n)
cmd_valid_toggle_100m_prev <= 1'b0;
else
cmd_valid_toggle_100m_prev <= cmd_valid_toggle_100m;
end
wire cmd_valid_100m = cmd_valid_toggle_100m ^ cmd_valid_toggle_100m_prev;
// Step 4: Command decode registers in clk_100m domain
// Sample cmd_data fields when CDC'd valid pulse arrives. Data is stable
// because the read FSM holds cmd_opcode/addr/value until the next command.
// NOTE: reg declarations for host_radar_mode, host_trigger_pulse,
// host_cfar_threshold, host_stream_control are in INTERNAL SIGNALS section
// above (before rx_inst) to avoid Icarus Verilog forward-reference errors.
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
if (!sys_reset_n) begin
host_radar_mode <= 2'b01; // Default: auto-scan
host_trigger_pulse <= 1'b0;
host_cfar_threshold <= 16'd10000; // Default threshold
host_stream_control <= 3'b111; // Default: all streams enabled
end else begin
host_trigger_pulse <= 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
endcase
end
end
end
// ============================================================================ // ============================================================================
// OUTPUT ASSIGNMENTS // OUTPUT ASSIGNMENTS
// ============================================================================ // ============================================================================
+169 -1
View File
@@ -47,6 +47,18 @@ module tb_usb_data_interface;
// Pulldown: when nobody drives, data reads as 0 (not X) // Pulldown: when nobody drives, data reads as 0 (not X)
pulldown pd[31:0] (ft601_data); pulldown pd[31:0] (ft601_data);
// Host-to-FPGA data bus driver (for read path testing)
reg [31:0] host_data_drive;
reg host_data_drive_en;
assign ft601_data = host_data_drive_en ? host_data_drive : 32'hzzzz_zzzz;
// DUT command outputs (Gap 4: USB Read Path)
wire [31:0] cmd_data;
wire cmd_valid;
wire [7:0] cmd_opcode;
wire [7:0] cmd_addr;
wire [15:0] cmd_value;
// 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;
@@ -76,7 +88,14 @@ module tb_usb_data_interface;
.ft601_srb (ft601_srb), .ft601_srb (ft601_srb),
.ft601_swb (ft601_swb), .ft601_swb (ft601_swb),
.ft601_clk_out (ft601_clk_out), .ft601_clk_out (ft601_clk_out),
.ft601_clk_in (ft601_clk_in) .ft601_clk_in (ft601_clk_in),
// Host command outputs (Gap 4: USB Read Path)
.cmd_data (cmd_data),
.cmd_valid (cmd_valid),
.cmd_opcode (cmd_opcode),
.cmd_addr (cmd_addr),
.cmd_value (cmd_value)
); );
// Test bookkeeping // Test bookkeeping
@@ -118,6 +137,8 @@ module tb_usb_data_interface;
ft601_rxf = 1; ft601_rxf = 1;
ft601_srb = 2'b00; ft601_srb = 2'b00;
ft601_swb = 2'b00; ft601_swb = 2'b00;
host_data_drive = 32'h0;
host_data_drive_en = 0;
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);
@@ -182,6 +203,52 @@ module tb_usb_data_interface;
end end
endtask endtask
// Helper: wait for read FSM to reach a specific state
task wait_for_read_state;
input [2:0] target;
input integer max_cyc;
integer cnt;
begin
cnt = 0;
while (uut.read_state !== target && cnt < max_cyc) begin
@(posedge ft601_clk_in);
cnt = cnt + 1;
end
end
endtask
// Helper: send a single host command word via the read path
// Simulates the FT601 host presenting a 32-bit command word.
// Protocol: Assert RXF=0 (data available), wait for OE_N=0,
// drive data bus, wait for RD_N=0, then release.
task send_host_command;
input [31:0] cmd_word;
begin
// Signal host has data
ft601_rxf = 0;
// Wait for FPGA to assert OE_N (bus turnaround)
wait_for_read_state(3'd1, 20); // RD_OE_ASSERT = 3'd1
@(posedge ft601_clk_in); #1;
// Drive data bus (FT601 drives in real hardware)
host_data_drive = cmd_word;
host_data_drive_en = 1;
// Wait for FPGA to assert RD_N=0 (RD_READING state)
wait_for_read_state(3'd2, 20); // RD_READING = 3'd2
@(posedge ft601_clk_in); #1;
// Data has been sampled. FPGA deasserts RD then OE.
// Wait for RD_PROCESS or back to RD_IDLE
wait_for_read_state(3'd4, 20); // RD_PROCESS = 3'd4
@(posedge ft601_clk_in); #1;
// Release bus and deassert RXF
host_data_drive_en = 0;
host_data_drive = 32'h0;
ft601_rxf = 1;
// Wait for read FSM to return to idle
wait_for_read_state(3'd0, 20); // RD_IDLE = 3'd0
@(posedge ft601_clk_in); #1;
end
endtask
// Drive a complete packet through the FSM by sequentially providing // Drive a complete packet through the FSM by sequentially providing
// range, doppler (4x), and cfar valid pulses. // range, doppler (4x), and cfar valid pulses.
task drive_full_packet; task drive_full_packet;
@@ -212,6 +279,8 @@ module tb_usb_data_interface;
pass_count = 0; pass_count = 0;
fail_count = 0; fail_count = 0;
test_num = 0; test_num = 0;
host_data_drive = 32'h0;
host_data_drive_en = 0;
csv_file = $fopen("tb_usb_data_interface.csv", "w"); csv_file = $fopen("tb_usb_data_interface.csv", "w");
$fwrite(csv_file, "test_num,pass_fail,label\n"); $fwrite(csv_file, "test_num,pass_fail,label\n");
@@ -553,6 +622,105 @@ module tb_usb_data_interface;
check(uut.range_profile_cap === 32'hCCCC_DDDD, check(uut.range_profile_cap === 32'hCCCC_DDDD,
"Packet 2 range data captured correctly"); "Packet 2 range data captured correctly");
//
// TEST GROUP 12: Read Path - Single Command (Gap 4)
//
$display("\n--- Test Group 12: Read Path - Single Command ---");
apply_reset;
// Write FSM is IDLE, so read FSM can activate
// Send "Set radar mode" command: opcode=0x01, addr=0x00, value=0x0002
send_host_command({8'h01, 8'h00, 16'h0002});
check(cmd_opcode === 8'h01,
"Read path: cmd_opcode=0x01 (set mode)");
check(cmd_addr === 8'h00,
"Read path: cmd_addr=0x00");
check(cmd_value === 16'h0002,
"Read path: cmd_value=0x0002 (single-chirp mode)");
check(cmd_data === {8'h01, 8'h00, 16'h0002},
"Read path: cmd_data matches full command word");
// Verify read FSM returned to idle
check(uut.read_state === 3'd0,
"Read FSM returned to RD_IDLE after command");
//
// TEST GROUP 13: Read Path - Multiple Commands (Gap 4)
//
$display("\n--- Test Group 13: Read Path - Multiple Commands ---");
apply_reset;
// Command 1: Set radar mode to auto-scan (0x01)
send_host_command({8'h01, 8'h00, 16'h0001});
check(cmd_opcode === 8'h01,
"Multi-cmd 1: opcode=0x01 (set mode)");
check(cmd_value === 16'h0001,
"Multi-cmd 1: value=0x0001 (auto-scan)");
// Command 2: Single chirp trigger (0x02)
send_host_command({8'h02, 8'h00, 16'h0000});
check(cmd_opcode === 8'h02,
"Multi-cmd 2: opcode=0x02 (trigger)");
// Command 3: Set CFAR threshold (0x03)
send_host_command({8'h03, 8'h00, 16'h1234});
check(cmd_opcode === 8'h03,
"Multi-cmd 3: opcode=0x03 (CFAR threshold)");
check(cmd_value === 16'h1234,
"Multi-cmd 3: value=0x1234");
// Command 4: Set stream control (0x04)
send_host_command({8'h04, 8'h00, 16'h0005});
check(cmd_opcode === 8'h04,
"Multi-cmd 4: opcode=0x04 (stream control)");
check(cmd_value === 16'h0005,
"Multi-cmd 4: value=0x0005 (range+cfar)");
//
// TEST GROUP 14: Read/Write Interleave (Gap 4)
// Verifies no bus contention: read FSM only operates when
// write FSM is IDLE.
//
$display("\n--- Test Group 14: Read/Write Interleave ---");
apply_reset;
ft601_txe = 0;
// Start a write packet
assert_range_valid(32'hFACE_FEED);
wait_for_state(S_SEND_HEADER, 50);
@(posedge ft601_clk_in); #1;
// While write FSM is active, assert RXF=0 (host has data)
// Read FSM should NOT activate (read_state stays RD_IDLE)
ft601_rxf = 0;
repeat (5) @(posedge ft601_clk_in); #1;
check(uut.read_state === 3'd0,
"Read FSM stays in RD_IDLE while write FSM active");
// Deassert RXF, complete the write packet
ft601_rxf = 1;
wait_for_state(S_SEND_DOPPLER, 100);
pulse_doppler_once(16'hAAAA, 16'hBBBB);
pulse_doppler_once(16'hAAAA, 16'hBBBB);
pulse_doppler_once(16'hAAAA, 16'hBBBB);
pulse_doppler_once(16'hAAAA, 16'hBBBB);
wait_for_state(S_SEND_DETECT, 100);
pulse_cfar_once(1'b1);
wait_for_state(S_IDLE, 100);
@(posedge ft601_clk_in); #1;
check(uut.current_state === S_IDLE,
"Write packet completed, FSM in IDLE");
// Now send a read command should work fine after write completes
send_host_command({8'h01, 8'h00, 16'h0002});
check(cmd_opcode === 8'h01,
"Read after write: cmd_opcode=0x01");
check(cmd_value === 16'h0002,
"Read after write: cmd_value=0x0002");
// //
// Summary // Summary
// //
+122 -2
View File
@@ -33,7 +33,24 @@ module usb_data_interface (
// Clock // Clock
output wire ft601_clk_out, // Output clock to FT601 (forwarded via ODDR) output wire ft601_clk_out, // Output clock to FT601 (forwarded via ODDR)
input wire ft601_clk_in // Clock from FT601 (60/100MHz) input wire ft601_clk_in, // Clock from FT601 (60/100MHz)
// ========== HOST COMMAND OUTPUTS (Gap 4: USB Read Path) ==========
// These outputs are registered in the ft601_clk domain and must be
// CDC-synchronized by the consumer (radar_system_top.v) before use
// in the clk_100m domain.
//
// Command word format: {opcode[7:0], addr[7:0], value[15:0]}
// 0x01 = Set radar mode (value[1:0] -> mode_controller mode)
// 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)
// 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
); );
// USB packet structure (same as before) // USB packet structure (same as before)
@@ -44,7 +61,9 @@ localparam FOOTER = 8'h55;
localparam FT601_DATA_WIDTH = 32; localparam FT601_DATA_WIDTH = 32;
localparam FT601_BURST_SIZE = 512; // Max burst size in bytes localparam FT601_BURST_SIZE = 512; // Max burst size in bytes
// State definitions (Verilog-2001 compatible) // ============================================================================
// WRITE FSM State definitions (Verilog-2001 compatible)
// ============================================================================
localparam [2:0] IDLE = 3'd0, localparam [2:0] IDLE = 3'd0,
SEND_HEADER = 3'd1, SEND_HEADER = 3'd1,
SEND_RANGE_DATA = 3'd2, SEND_RANGE_DATA = 3'd2,
@@ -59,6 +78,28 @@ reg [31:0] data_buffer;
reg [31:0] ft601_data_out; reg [31:0] ft601_data_out;
reg ft601_data_oe; // Output enable for bidirectional data bus reg ft601_data_oe; // Output enable for bidirectional data bus
// ============================================================================
// READ FSM State definitions (Gap 4: USB Read Path)
// ============================================================================
// FT601 245 synchronous FIFO read protocol:
// 1. Host has data available: RXF_N = 0 (active low)
// 2. FPGA asserts OE_N = 0 (bus turnaround: FT601 starts driving data bus)
// 3. Wait 1 cycle for bus turnaround settling
// 4. FPGA asserts RD_N = 0 (start reading: data valid on each posedge)
// 5. Sample ft601_data[31:0] while RD_N = 0 and RXF_N = 0
// 6. Deassert RD_N = 1, then OE_N = 1
//
// The read FSM only activates when the write FSM is IDLE and RXF indicates
// data is available. This prevents bus contention between TX and RX.
localparam [2:0] RD_IDLE = 3'd0, // Waiting for RXF
RD_OE_ASSERT = 3'd1, // Assert OE_N=0, wait turnaround
RD_READING = 3'd2, // Assert RD_N=0, sample data
RD_DEASSERT = 3'd3, // Deassert RD_N, then OE_N
RD_PROCESS = 3'd4; // Process received command word
reg [2:0] read_state;
reg [31:0] rx_data_captured; // Data word read from host
// ========== CDC INPUT SYNCHRONIZERS (clk domain -> ft601_clk_in domain) ========== // ========== CDC INPUT SYNCHRONIZERS (clk domain -> ft601_clk_in domain) ==========
// The valid signals arrive from clk_100m but the state machine runs on ft601_clk_in. // The valid signals arrive from clk_100m but the state machine runs on ft601_clk_in.
// Even though both are 100 MHz, they are asynchronous clocks and need synchronization. // Even though both are 100 MHz, they are asynchronous clocks and need synchronization.
@@ -159,6 +200,7 @@ assign ft601_data = ft601_data_oe ? ft601_data_out : 32'hzzzz_zzzz;
always @(posedge ft601_clk_in or negedge ft601_reset_n) begin always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
if (!ft601_reset_n) begin if (!ft601_reset_n) begin
current_state <= IDLE; current_state <= IDLE;
read_state <= RD_IDLE;
byte_counter <= 0; byte_counter <= 0;
ft601_data_out <= 0; ft601_data_out <= 0;
ft601_data_oe <= 0; ft601_data_oe <= 0;
@@ -169,18 +211,95 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
ft601_rd_n <= 1; ft601_rd_n <= 1;
ft601_oe_n <= 1; ft601_oe_n <= 1;
ft601_siwu_n <= 1; ft601_siwu_n <= 1;
rx_data_captured <= 32'd0;
cmd_data <= 32'd0;
cmd_valid <= 1'b0;
cmd_opcode <= 8'd0;
cmd_addr <= 8'd0;
cmd_value <= 16'd0;
// NOTE: ft601_clk_out is driven by the clk-domain always block below. // NOTE: ft601_clk_out is driven by the clk-domain always block below.
// Do NOT assign it here (ft601_clk_in domain) — causes multi-driven net. // Do NOT assign it here (ft601_clk_in domain) — causes multi-driven net.
end else begin end else begin
// Default: clear one-shot signals
cmd_valid <= 1'b0;
// ================================================================
// READ FSM host-to-FPGA command path (Gap 4)
//
// The read FSM takes priority over write when both could activate.
// It only starts when the write FSM is IDLE and ft601_rxf
// indicates data from host is available.
// ================================================================
case (read_state)
RD_IDLE: begin
// Only start reading if write FSM is idle and host has data.
// ft601_rxf active-low: 0 means data available from host.
if (current_state == IDLE && !ft601_rxf) begin
ft601_oe_n <= 1'b0; // Assert OE: tell FT601 to drive bus
ft601_data_oe <= 1'b0; // FPGA releases bus (FT601 drives)
read_state <= RD_OE_ASSERT;
end
end
RD_OE_ASSERT: begin
// 1-cycle turnaround: OE_N asserted, bus settling.
// FT601 spec requires 1 clock of OE_N before RD_N assertion.
if (!ft601_rxf) begin
ft601_rd_n <= 1'b0; // Assert RD: start reading
read_state <= RD_READING;
end else begin
// Host withdrew data — abort
ft601_oe_n <= 1'b1;
read_state <= RD_IDLE;
end
end
RD_READING: begin
// Data is valid on ft601_data. Sample it.
// For now we read a single 32-bit command word per transaction.
rx_data_captured <= ft601_data;
ft601_rd_n <= 1'b1; // Deassert RD
read_state <= RD_DEASSERT;
end
RD_DEASSERT: begin
// Deassert OE_N (1 cycle after RD_N deasserted)
ft601_oe_n <= 1'b1;
read_state <= RD_PROCESS;
end
RD_PROCESS: begin
// Decode the received command word and pulse cmd_valid.
// Format: {opcode[31:24], addr[23:16], value[15:0]}
cmd_data <= rx_data_captured;
cmd_opcode <= rx_data_captured[31:24];
cmd_addr <= rx_data_captured[23:16];
cmd_value <= rx_data_captured[15:0];
cmd_valid <= 1'b1;
read_state <= RD_IDLE;
end
default: read_state <= RD_IDLE;
endcase
// ================================================================
// WRITE FSM — FPGA-to-host data streaming (existing)
//
// Only operates when read FSM is idle (no bus contention).
// ================================================================
if (read_state == RD_IDLE) begin
case (current_state) case (current_state)
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 if (range_valid_ft || doppler_valid_ft || cfar_valid_ft) 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; current_state <= SEND_HEADER;
byte_counter <= 0; byte_counter <= 0;
end end
end end
end
SEND_HEADER: begin SEND_HEADER: begin
if (!ft601_txe) begin // FT601 TX FIFO not empty if (!ft601_txe) begin // FT601 TX FIFO not empty
@@ -265,6 +384,7 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
end end
endcase endcase
end end
end
end end
// ============================================================================ // ============================================================================