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:
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -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
|
||||||
// ════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -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,101 +211,179 @@ 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
|
||||||
case (current_state)
|
// Default: clear one-shot signals
|
||||||
IDLE: begin
|
cmd_valid <= 1'b0;
|
||||||
ft601_wr_n <= 1;
|
|
||||||
ft601_data_oe <= 0; // Release data bus
|
// ================================================================
|
||||||
if (range_valid_ft || doppler_valid_ft || cfar_valid_ft) begin
|
// READ FSM — host-to-FPGA command path (Gap 4)
|
||||||
current_state <= SEND_HEADER;
|
//
|
||||||
byte_counter <= 0;
|
// 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
|
||||||
end
|
end
|
||||||
|
|
||||||
SEND_HEADER: begin
|
RD_OE_ASSERT: begin
|
||||||
if (!ft601_txe) begin // FT601 TX FIFO not empty
|
// 1-cycle turnaround: OE_N asserted, bus settling.
|
||||||
ft601_data_oe <= 1;
|
// FT601 spec requires 1 clock of OE_N before RD_N assertion.
|
||||||
ft601_data_out <= {24'b0, HEADER};
|
if (!ft601_rxf) begin
|
||||||
ft601_be <= 4'b0001; // Only lower byte valid
|
ft601_rd_n <= 1'b0; // Assert RD: start reading
|
||||||
ft601_wr_n <= 0; // Assert write strobe
|
read_state <= RD_READING;
|
||||||
current_state <= SEND_RANGE_DATA;
|
end else begin
|
||||||
|
// Host withdrew data — abort
|
||||||
|
ft601_oe_n <= 1'b1;
|
||||||
|
read_state <= RD_IDLE;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
SEND_RANGE_DATA: begin
|
RD_READING: begin
|
||||||
if (!ft601_txe) begin
|
// Data is valid on ft601_data. Sample it.
|
||||||
ft601_data_oe <= 1;
|
// For now we read a single 32-bit command word per transaction.
|
||||||
ft601_be <= 4'b1111; // All bytes valid for 32-bit word
|
rx_data_captured <= ft601_data;
|
||||||
|
ft601_rd_n <= 1'b1; // Deassert RD
|
||||||
case (byte_counter)
|
read_state <= RD_DEASSERT;
|
||||||
0: ft601_data_out <= range_profile_cap;
|
|
||||||
1: ft601_data_out <= {range_profile_cap[23:0], 8'h00};
|
|
||||||
2: ft601_data_out <= {range_profile_cap[15:0], 16'h0000};
|
|
||||||
3: ft601_data_out <= {range_profile_cap[7:0], 24'h000000};
|
|
||||||
endcase
|
|
||||||
|
|
||||||
ft601_wr_n <= 0;
|
|
||||||
|
|
||||||
if (byte_counter == 3) begin
|
|
||||||
byte_counter <= 0;
|
|
||||||
current_state <= SEND_DOPPLER_DATA;
|
|
||||||
end else begin
|
|
||||||
byte_counter <= byte_counter + 1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
SEND_DOPPLER_DATA: begin
|
RD_DEASSERT: begin
|
||||||
if (!ft601_txe && doppler_valid_ft) begin
|
// Deassert OE_N (1 cycle after RD_N deasserted)
|
||||||
ft601_data_oe <= 1;
|
ft601_oe_n <= 1'b1;
|
||||||
ft601_be <= 4'b1111;
|
read_state <= RD_PROCESS;
|
||||||
|
|
||||||
case (byte_counter)
|
|
||||||
0: ft601_data_out <= {doppler_real_cap, doppler_imag_cap};
|
|
||||||
1: ft601_data_out <= {doppler_imag_cap, doppler_real_cap[15:8], 8'h00};
|
|
||||||
2: ft601_data_out <= {doppler_real_cap[7:0], doppler_imag_cap[15:8], 16'h0000};
|
|
||||||
3: ft601_data_out <= {doppler_imag_cap[7:0], 24'h000000};
|
|
||||||
endcase
|
|
||||||
|
|
||||||
ft601_wr_n <= 0;
|
|
||||||
|
|
||||||
if (byte_counter == 3) begin
|
|
||||||
byte_counter <= 0;
|
|
||||||
current_state <= SEND_DETECTION_DATA;
|
|
||||||
end else begin
|
|
||||||
byte_counter <= byte_counter + 1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
SEND_DETECTION_DATA: begin
|
RD_PROCESS: begin
|
||||||
if (!ft601_txe && cfar_valid_ft) begin
|
// Decode the received command word and pulse cmd_valid.
|
||||||
ft601_data_oe <= 1;
|
// Format: {opcode[31:24], addr[23:16], value[15:0]}
|
||||||
ft601_be <= 4'b0001;
|
cmd_data <= rx_data_captured;
|
||||||
ft601_data_out <= {24'b0, 7'b0, cfar_detection_cap};
|
cmd_opcode <= rx_data_captured[31:24];
|
||||||
ft601_wr_n <= 0;
|
cmd_addr <= rx_data_captured[23:16];
|
||||||
current_state <= SEND_FOOTER;
|
cmd_value <= rx_data_captured[15:0];
|
||||||
end
|
cmd_valid <= 1'b1;
|
||||||
|
read_state <= RD_IDLE;
|
||||||
end
|
end
|
||||||
|
|
||||||
SEND_FOOTER: begin
|
default: read_state <= RD_IDLE;
|
||||||
if (!ft601_txe) begin
|
|
||||||
ft601_data_oe <= 1;
|
|
||||||
ft601_be <= 4'b0001;
|
|
||||||
ft601_data_out <= {24'b0, FOOTER};
|
|
||||||
ft601_wr_n <= 0;
|
|
||||||
current_state <= WAIT_ACK;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
WAIT_ACK: begin
|
|
||||||
ft601_wr_n <= 1;
|
|
||||||
ft601_data_oe <= 0; // Release data bus
|
|
||||||
current_state <= IDLE;
|
|
||||||
end
|
|
||||||
endcase
|
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)
|
||||||
|
IDLE: begin
|
||||||
|
ft601_wr_n <= 1;
|
||||||
|
ft601_data_oe <= 0; // Release data bus
|
||||||
|
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;
|
||||||
|
byte_counter <= 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SEND_HEADER: begin
|
||||||
|
if (!ft601_txe) begin // FT601 TX FIFO not empty
|
||||||
|
ft601_data_oe <= 1;
|
||||||
|
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;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SEND_RANGE_DATA: begin
|
||||||
|
if (!ft601_txe) begin
|
||||||
|
ft601_data_oe <= 1;
|
||||||
|
ft601_be <= 4'b1111; // All bytes valid for 32-bit word
|
||||||
|
|
||||||
|
case (byte_counter)
|
||||||
|
0: ft601_data_out <= range_profile_cap;
|
||||||
|
1: ft601_data_out <= {range_profile_cap[23:0], 8'h00};
|
||||||
|
2: ft601_data_out <= {range_profile_cap[15:0], 16'h0000};
|
||||||
|
3: ft601_data_out <= {range_profile_cap[7:0], 24'h000000};
|
||||||
|
endcase
|
||||||
|
|
||||||
|
ft601_wr_n <= 0;
|
||||||
|
|
||||||
|
if (byte_counter == 3) begin
|
||||||
|
byte_counter <= 0;
|
||||||
|
current_state <= SEND_DOPPLER_DATA;
|
||||||
|
end else begin
|
||||||
|
byte_counter <= byte_counter + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SEND_DOPPLER_DATA: begin
|
||||||
|
if (!ft601_txe && doppler_valid_ft) begin
|
||||||
|
ft601_data_oe <= 1;
|
||||||
|
ft601_be <= 4'b1111;
|
||||||
|
|
||||||
|
case (byte_counter)
|
||||||
|
0: ft601_data_out <= {doppler_real_cap, doppler_imag_cap};
|
||||||
|
1: ft601_data_out <= {doppler_imag_cap, doppler_real_cap[15:8], 8'h00};
|
||||||
|
2: ft601_data_out <= {doppler_real_cap[7:0], doppler_imag_cap[15:8], 16'h0000};
|
||||||
|
3: ft601_data_out <= {doppler_imag_cap[7:0], 24'h000000};
|
||||||
|
endcase
|
||||||
|
|
||||||
|
ft601_wr_n <= 0;
|
||||||
|
|
||||||
|
if (byte_counter == 3) begin
|
||||||
|
byte_counter <= 0;
|
||||||
|
current_state <= SEND_DETECTION_DATA;
|
||||||
|
end else begin
|
||||||
|
byte_counter <= byte_counter + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SEND_DETECTION_DATA: begin
|
||||||
|
if (!ft601_txe && cfar_valid_ft) begin
|
||||||
|
ft601_data_oe <= 1;
|
||||||
|
ft601_be <= 4'b0001;
|
||||||
|
ft601_data_out <= {24'b0, 7'b0, cfar_detection_cap};
|
||||||
|
ft601_wr_n <= 0;
|
||||||
|
current_state <= SEND_FOOTER;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SEND_FOOTER: begin
|
||||||
|
if (!ft601_txe) begin
|
||||||
|
ft601_data_oe <= 1;
|
||||||
|
ft601_be <= 4'b0001;
|
||||||
|
ft601_data_out <= {24'b0, FOOTER};
|
||||||
|
ft601_wr_n <= 0;
|
||||||
|
current_state <= WAIT_ACK;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
WAIT_ACK: begin
|
||||||
|
ft601_wr_n <= 1;
|
||||||
|
ft601_data_oe <= 0; // Release data bus
|
||||||
|
current_state <= IDLE;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user