5fd632bc47
CDC fixes across 6 RTL files based on post-implementation report_cdc analysis:
- P0: sync stm32_mixers_enable and new_chirp_pulse to clk_120m via toggle CDC
in radar_transmitter, add ft601 reset synchronizer and USB holding
registers with proper edge detection in usb_data_interface
- P1: add ASYNC_REG to edge_detector, convert new_chirp_frame to toggle CDC,
fix USB valid edge detect to use fully-synced signal
- P2: register Gray encoding in cdc_adc_to_processing source domain, sync
ft601_txe and stm32_mixers_enable for status_reg in radar_system_top
- Safety: add in_bin_count overflow guard in range_bin_decimator to prevent
downstream BRAM corruption
All 13 regression test suites pass (159 individual tests).
305 lines
12 KiB
Verilog
305 lines
12 KiB
Verilog
module usb_data_interface (
|
|
input wire clk, // Main clock (100MHz recommended)
|
|
input wire reset_n,
|
|
input wire ft601_reset_n, // FT601-domain synchronized reset
|
|
|
|
// Radar data inputs
|
|
input wire [31:0] range_profile,
|
|
input wire range_valid,
|
|
input wire [15:0] doppler_real,
|
|
input wire [15:0] doppler_imag,
|
|
input wire doppler_valid,
|
|
input wire cfar_detection,
|
|
input wire cfar_valid,
|
|
|
|
// FT601 Interface (Slave FIFO mode)
|
|
// Data bus
|
|
inout wire [31:0] ft601_data, // 32-bit bidirectional data bus
|
|
output reg [3:0] ft601_be, // Byte enable (4 lanes for 32-bit mode)
|
|
|
|
// Control signals
|
|
output reg ft601_txe_n, // Transmit enable (active low)
|
|
output reg ft601_rxf_n, // Receive enable (active low)
|
|
input wire ft601_txe, // Transmit FIFO empty
|
|
input wire ft601_rxf, // Receive FIFO full
|
|
output reg ft601_wr_n, // Write strobe (active low)
|
|
output reg ft601_rd_n, // Read strobe (active low)
|
|
output reg ft601_oe_n, // Output enable (active low)
|
|
output reg ft601_siwu_n, // Send immediate / Wakeup
|
|
|
|
// FIFO flags
|
|
input wire [1:0] ft601_srb, // Selected read buffer
|
|
input wire [1:0] ft601_swb, // Selected write buffer
|
|
|
|
// Clock
|
|
output wire ft601_clk_out, // Output clock to FT601 (forwarded via ODDR)
|
|
input wire ft601_clk_in // Clock from FT601 (60/100MHz)
|
|
);
|
|
|
|
// USB packet structure (same as before)
|
|
localparam HEADER = 8'hAA;
|
|
localparam FOOTER = 8'h55;
|
|
|
|
// FT601 configuration
|
|
localparam FT601_DATA_WIDTH = 32;
|
|
localparam FT601_BURST_SIZE = 512; // Max burst size in bytes
|
|
|
|
// State definitions (Verilog-2001 compatible)
|
|
localparam [2:0] IDLE = 3'd0,
|
|
SEND_HEADER = 3'd1,
|
|
SEND_RANGE_DATA = 3'd2,
|
|
SEND_DOPPLER_DATA = 3'd3,
|
|
SEND_DETECTION_DATA = 3'd4,
|
|
SEND_FOOTER = 3'd5,
|
|
WAIT_ACK = 3'd6;
|
|
|
|
reg [2:0] current_state;
|
|
reg [7:0] byte_counter;
|
|
reg [31:0] data_buffer;
|
|
reg [31:0] ft601_data_out;
|
|
reg ft601_data_oe; // Output enable for bidirectional data bus
|
|
|
|
// ========== 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.
|
|
// Even though both are 100 MHz, they are asynchronous clocks and need synchronization.
|
|
|
|
// 2-stage synchronizers for valid signals
|
|
(* ASYNC_REG = "TRUE" *) reg [1:0] range_valid_sync;
|
|
(* ASYNC_REG = "TRUE" *) reg [1:0] doppler_valid_sync;
|
|
(* ASYNC_REG = "TRUE" *) reg [1:0] cfar_valid_sync;
|
|
|
|
// Delayed versions of sync[1] for proper edge detection
|
|
reg range_valid_sync_d;
|
|
reg doppler_valid_sync_d;
|
|
reg cfar_valid_sync_d;
|
|
|
|
// Holding registers: data captured in SOURCE domain (clk_100m) when valid
|
|
// asserts, then read by ft601 domain after synchronized valid edge.
|
|
// This is safe because the data is stable for the entire time the valid
|
|
// pulse is being synchronized (2+ ft601_clk cycles).
|
|
reg [31:0] range_profile_hold;
|
|
reg [15:0] doppler_real_hold;
|
|
reg [15:0] doppler_imag_hold;
|
|
reg cfar_detection_hold;
|
|
|
|
// Source-domain holding registers (clk domain)
|
|
always @(posedge clk or negedge reset_n) begin
|
|
if (!reset_n) begin
|
|
range_profile_hold <= 32'd0;
|
|
doppler_real_hold <= 16'd0;
|
|
doppler_imag_hold <= 16'd0;
|
|
cfar_detection_hold <= 1'b0;
|
|
end else begin
|
|
if (range_valid)
|
|
range_profile_hold <= range_profile;
|
|
if (doppler_valid) begin
|
|
doppler_real_hold <= doppler_real;
|
|
doppler_imag_hold <= doppler_imag;
|
|
end
|
|
if (cfar_valid)
|
|
cfar_detection_hold <= cfar_detection;
|
|
end
|
|
end
|
|
|
|
// FT601-domain captured data (sampled from holding regs on sync'd edge)
|
|
reg [31:0] range_profile_cap;
|
|
reg [15:0] doppler_real_cap;
|
|
reg [15:0] doppler_imag_cap;
|
|
reg cfar_detection_cap;
|
|
|
|
wire range_valid_ft;
|
|
wire doppler_valid_ft;
|
|
wire cfar_valid_ft;
|
|
|
|
always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
|
if (!ft601_reset_n) begin
|
|
range_valid_sync <= 2'b00;
|
|
doppler_valid_sync <= 2'b00;
|
|
cfar_valid_sync <= 2'b00;
|
|
range_valid_sync_d <= 1'b0;
|
|
doppler_valid_sync_d <= 1'b0;
|
|
cfar_valid_sync_d <= 1'b0;
|
|
range_profile_cap <= 32'd0;
|
|
doppler_real_cap <= 16'd0;
|
|
doppler_imag_cap <= 16'd0;
|
|
cfar_detection_cap <= 1'b0;
|
|
end else begin
|
|
// Synchronize valid strobes (2-stage sync chain)
|
|
range_valid_sync <= {range_valid_sync[0], range_valid};
|
|
doppler_valid_sync <= {doppler_valid_sync[0], doppler_valid};
|
|
cfar_valid_sync <= {cfar_valid_sync[0], cfar_valid};
|
|
|
|
// Delayed version of sync[1] for edge detection
|
|
range_valid_sync_d <= range_valid_sync[1];
|
|
doppler_valid_sync_d <= doppler_valid_sync[1];
|
|
cfar_valid_sync_d <= cfar_valid_sync[1];
|
|
|
|
// Capture data on rising edge of FULLY SYNCHRONIZED valid (sync[1])
|
|
// Data in holding regs is stable by the time sync[1] rises (2+ cycles)
|
|
if (range_valid_sync[1] && !range_valid_sync_d)
|
|
range_profile_cap <= range_profile_hold;
|
|
if (doppler_valid_sync[1] && !doppler_valid_sync_d) begin
|
|
doppler_real_cap <= doppler_real_hold;
|
|
doppler_imag_cap <= doppler_imag_hold;
|
|
end
|
|
if (cfar_valid_sync[1] && !cfar_valid_sync_d)
|
|
cfar_detection_cap <= cfar_detection_hold;
|
|
end
|
|
end
|
|
|
|
// Rising-edge detect on FULLY SYNCHRONIZED valid (sync[1], not sync[0])
|
|
// This provides full 2-stage metastability protection before use.
|
|
assign range_valid_ft = range_valid_sync[1] && !range_valid_sync_d;
|
|
assign doppler_valid_ft = doppler_valid_sync[1] && !doppler_valid_sync_d;
|
|
assign cfar_valid_ft = cfar_valid_sync[1] && !cfar_valid_sync_d;
|
|
|
|
// FT601 data bus direction control
|
|
assign ft601_data = ft601_data_oe ? ft601_data_out : 32'hzzzz_zzzz;
|
|
|
|
always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
|
if (!ft601_reset_n) begin
|
|
current_state <= IDLE;
|
|
byte_counter <= 0;
|
|
ft601_data_out <= 0;
|
|
ft601_data_oe <= 0;
|
|
ft601_be <= 4'b1111; // All bytes enabled for 32-bit mode
|
|
ft601_txe_n <= 1;
|
|
ft601_rxf_n <= 1;
|
|
ft601_wr_n <= 1;
|
|
ft601_rd_n <= 1;
|
|
ft601_oe_n <= 1;
|
|
ft601_siwu_n <= 1;
|
|
// 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.
|
|
end else 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
|
|
current_state <= SEND_HEADER;
|
|
byte_counter <= 0;
|
|
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
|
|
|
|
// ============================================================================
|
|
// FT601 clock output forwarding
|
|
// ============================================================================
|
|
// Forward ft601_clk_in back out via ODDR so that the forwarded clock at the
|
|
// pin has the same insertion delay as the data outputs (both go through the
|
|
// same BUFG). This makes the output delay analysis relative to the generated
|
|
// clock at the pin, where insertion delays cancel.
|
|
|
|
`ifndef SIMULATION
|
|
ODDR #(
|
|
.DDR_CLK_EDGE("OPPOSITE_EDGE"),
|
|
.INIT(1'b0),
|
|
.SRTYPE("SYNC")
|
|
) oddr_ft601_clk (
|
|
.Q(ft601_clk_out),
|
|
.C(ft601_clk_in),
|
|
.CE(1'b1),
|
|
.D1(1'b1),
|
|
.D2(1'b0),
|
|
.R(1'b0),
|
|
.S(1'b0)
|
|
);
|
|
`else
|
|
// Simulation: behavioral clock forwarding
|
|
reg ft601_clk_out_sim;
|
|
always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
|
if (!ft601_reset_n)
|
|
ft601_clk_out_sim <= 1'b0;
|
|
else
|
|
ft601_clk_out_sim <= 1'b1;
|
|
end
|
|
// In simulation, just pass the clock through
|
|
assign ft601_clk_out = ft601_clk_in;
|
|
`endif
|
|
|
|
endmodule |