feat(usb): add FT2232H USB 2.0 interface for 50T production board
Replace FT601 (USB 3.0, 32-bit) with FT2232H (USB 2.0, 8-bit) on the
50T production board per updated Eagle schematic (commit 0db0e7b).
USB 3.0 via FT601 remains available on the 200T premium board.
RTL changes:
- Add usb_data_interface_ft2232h.v: 245 Sync FIFO interface with toggle
CDC (3-stage) for reliable 100MHz->60MHz clock domain crossing,
mux-based byte serialization for 11-byte data packets, 26-byte status
packets, and 4-byte sequential command read FSM
- Add USB_MODE parameter to radar_system_top.v with generate block:
USB_MODE=0 selects FT601 (200T), USB_MODE=1 selects FT2232H (50T)
- Wire FT2232H ports in radar_system_top_50t.v with USB_MODE=1 override,
connect ft_clkout to shared clock input port
- Add post-DSP retiming register in ddc_400m.v to fix marginal 400MHz
timing path (WNS improved from +0.070ns to +0.088ns)
Constraints:
- Add FT2232H pin assignments for all 15 signals on Bank 35 (LVCMOS33)
- Add 60MHz ft_clkout clock constraint (16.667ns) on MRCC N-type pin C4
- Add CLOCK_DEDICATED_ROUTE FALSE for N-type MRCC workaround
- Add CDC false paths between ft_clkout and clk_100m/clk_120m_dac
Build scripts:
- Add PLIO-9 DRC demotion and CLOCK_DEDICATED_ROUTE property in build_50t.tcl
- Add usb_data_interface_ft2232h.v to build_200t.tcl explicit file list
Python host:
- Add FT2232HConnection class using pyftdi SyncFIFO (VID 0x0403:0x6010)
- Add compact 11-byte packet parser for FT2232H data packets
- Update RadarAcquisition to support both FT601 and FT2232H connections
Test results:
- iverilog regression: 23/23 PASS
- Vivado Build 15 (XC7A50T): WNS=+0.088ns, WHS=+0.059ns, 0 violations
- DSP48E1: 112/120 (93.3%), LUTs: 10,060/32,600 (30.9%)
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
# Bank 14: VCCO = 2.5V (ADC LVDS_25 data — placer-enforced; adc_pwdn as LVCMOS25)
|
||||
# Bank 15: VCCO = 3.3V (DAC, clocks, STM32 SPI 3.3V side, DIG bus, mixer)
|
||||
# Bank 34: VCCO = 1.8V (ADAR1000 beamformer control, SPI 1.8V side)
|
||||
# Bank 35: VCCO = 3.3V (unused — no signal connections)
|
||||
# Bank 35: VCCO = 3.3V (FT2232H USB 2.0 FIFO — 15 signals)
|
||||
#
|
||||
# DRC Fix History:
|
||||
# - PLIO-9: Moved clk_120m_dac from C13 (N-type) to D13 (P-type MRCC).
|
||||
@@ -25,8 +25,12 @@
|
||||
# IBUFDS input buffers are VCCO-independent. BIVC-1 also waived via
|
||||
# set_property SEVERITY in the build script as an additional safety net.
|
||||
# in the build script. adc_pwdn (LVCMOS25) coexists in the same bank.
|
||||
# - UCIO/NSTD: 118 unconstrained ports (FT601 unwired, status/debug outputs
|
||||
# have no physical pins). Handled with SEVERITY demotion + default IOSTANDARD.
|
||||
# - UCIO/NSTD: Unconstrained ports (FT601 ports inactive with USB_MODE=1,
|
||||
# status/debug outputs have no physical pins). Handled with SEVERITY
|
||||
# demotion + default IOSTANDARD.
|
||||
# - PLIO-9: FT2232H CLKOUT routed to C4 (IO_L12N_T1_MRCC_35, N-type).
|
||||
# Clock inputs normally use P-type MRCC pins, but IBUFG works correctly
|
||||
# on N-type. Demote PLIO-9 to warning in build script.
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
@@ -46,10 +50,10 @@ set_property CONFIG_VOLTAGE 3.3 [current_design]
|
||||
# and one LVCMOS33 output, this is safe to demote to a warning.
|
||||
# → Applied in build_50t_test.tcl: set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
|
||||
#
|
||||
# NSTD-1 / UCIO-1: Unconstrained ports — FT601 USB (unwired on this board),
|
||||
# dac_clk (DAC clock comes from AD9523, not FPGA), and all status/debug
|
||||
# outputs (no physical pins available). These ports are present in the
|
||||
# shared RTL but have no connections on the 50T board.
|
||||
# NSTD-1 / UCIO-1: Unconstrained ports — FT601 USB ports (inactive with
|
||||
# USB_MODE=1 generate block), dac_clk (DAC clock comes from AD9523, not FPGA),
|
||||
# and all status/debug outputs (no physical pins available). These ports are
|
||||
# present in the shared RTL but have no connections on the 50T board.
|
||||
# → Applied in build_50t_test.tcl: set_property SEVERITY {Warning} [get_drc_checks {NSTD-1 UCIO-1}]
|
||||
|
||||
# ============================================================================
|
||||
@@ -90,11 +94,20 @@ create_clock -name adc_dco_p -period 2.5 [get_ports {adc_dco_p}]
|
||||
set_input_jitter [get_clocks adc_dco_p] 0.05
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# FT601 Clock — COMMENTED OUT: FT601 (U6) is placed in schematic but has
|
||||
# zero net connections. No physical clock pin exists on this board.
|
||||
# FT2232H 60 MHz CLKOUT (Bank 35, MRCC pin C4)
|
||||
# --------------------------------------------------------------------------
|
||||
# create_clock -name ft601_clk_in -period 10.0 [get_ports {ft601_clk_in}]
|
||||
# set_input_jitter [get_clocks ft601_clk_in] 0.1
|
||||
# The FT2232H provides a 60 MHz clock in 245 Synchronous FIFO mode.
|
||||
# Pin C4 is IO_L12N_T1_MRCC_35 (N-type of MRCC pair). Vivado requires
|
||||
# CLOCK_DEDICATED_ROUTE FALSE for clock inputs on N-type MRCC pins
|
||||
# (Place 30-876). The schematic routes CLKOUT to C4; this cannot be
|
||||
# changed without a board respin. The clock still uses an IBUFG and
|
||||
# reaches the clock network — the constraint only disables the DRC check.
|
||||
set_property PACKAGE_PIN C4 [get_ports {ft_clkout}]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_clkout}]
|
||||
create_clock -name ft_clkout -period 16.667 [get_ports {ft_clkout}]
|
||||
set_input_jitter [get_clocks ft_clkout] 0.2
|
||||
# N-type MRCC pin requires dedicated route override (Place 30-876)
|
||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {ft_clkout_IBUF}]
|
||||
|
||||
# ============================================================================
|
||||
# RESET (Active-Low)
|
||||
@@ -262,26 +275,55 @@ set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 -clock_fall [get_ports {a
|
||||
set_input_delay -clock [get_clocks adc_dco_p] -min 0.2 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
|
||||
|
||||
# ============================================================================
|
||||
# FT601 USB 3.0 INTERFACE — ACTIVE: NO PHYSICAL CONNECTIONS
|
||||
# FT2232H USB 2.0 INTERFACE (Bank 35, VCCO=3.3V)
|
||||
# ============================================================================
|
||||
# The FT601 chip (U6, FT601Q-B-T) is placed in the Eagle schematic but has
|
||||
# ZERO net connections — no signals are routed between it and the FPGA.
|
||||
# Bank 35 (which would logically host FT601 signals) has no signal pins
|
||||
# connected, only VCCO_35 power.
|
||||
# FT2232H (U6) Channel A in 245 Synchronous FIFO mode.
|
||||
# All signals are direct connections to FPGA Bank 35 (LVCMOS33).
|
||||
# Pin mapping extracted from Eagle schematic (RADAR_Main_Board.sch).
|
||||
#
|
||||
# ALL FT601 constraints are commented out. The RTL module usb_data_interface.v
|
||||
# instantiates the FT601 interface, but it cannot function without physical
|
||||
# pin assignments. To use USB, the schematic must be updated to wire the
|
||||
# FT601 to FPGA Bank 35 pins, and then these constraints can be populated.
|
||||
#
|
||||
# Ports affected (from radar_system_top.v):
|
||||
# ft601_data[31:0], ft601_be[1:0], ft601_txe_n, ft601_rxf_n, ft601_txe,
|
||||
# ft601_rxf, ft601_wr_n, ft601_rd_n, ft601_oe_n, ft601_siwu_n,
|
||||
# ft601_srb[1:0], ft601_swb[1:0], ft601_clk_out, ft601_clk_in
|
||||
#
|
||||
# TODO: Wire FT601 in schematic, then assign pins here.
|
||||
# The FT2232H replaces the previously-unwired FT601 on the 50T production
|
||||
# board. The 200T dev board retains FT601 USB 3.0 (32-bit).
|
||||
# ============================================================================
|
||||
|
||||
# 8-bit bidirectional data bus (ADBUS0–ADBUS7)
|
||||
set_property PACKAGE_PIN K1 [get_ports {ft_data[0]}] ;# ADBUS0 → IO_L22P_T3_35
|
||||
set_property PACKAGE_PIN J3 [get_ports {ft_data[1]}] ;# ADBUS1 → IO_L21P_T3_DQS_35
|
||||
set_property PACKAGE_PIN H3 [get_ports {ft_data[2]}] ;# ADBUS2 → IO_L21N_T3_DQS_35
|
||||
set_property PACKAGE_PIN G4 [get_ports {ft_data[3]}] ;# ADBUS3 → IO_L16N_T2_35
|
||||
set_property PACKAGE_PIN F2 [get_ports {ft_data[4]}] ;# ADBUS4 → IO_L15P_T2_DQS_35
|
||||
set_property PACKAGE_PIN D1 [get_ports {ft_data[5]}] ;# ADBUS5 → IO_L10N_T1_AD15N_35
|
||||
set_property PACKAGE_PIN C3 [get_ports {ft_data[6]}] ;# ADBUS6 → IO_L7P_T1_AD6P_35
|
||||
set_property PACKAGE_PIN C1 [get_ports {ft_data[7]}] ;# ADBUS7 → IO_L9P_T1_DQS_AD7P_35
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_data[*]}]
|
||||
|
||||
# Control signals (active low where noted)
|
||||
set_property PACKAGE_PIN A2 [get_ports {ft_rxf_n}] ;# ACBUS0 → IO_L8N_T1_AD14N_35
|
||||
set_property PACKAGE_PIN B2 [get_ports {ft_txe_n}] ;# ACBUS1 → IO_L8P_T1_AD14P_35
|
||||
set_property PACKAGE_PIN A3 [get_ports {ft_rd_n}] ;# ACBUS2 → IO_L4N_T0_35
|
||||
set_property PACKAGE_PIN A4 [get_ports {ft_wr_n}] ;# ACBUS3 → IO_L3N_T0_DQS_AD5N_35
|
||||
set_property PACKAGE_PIN A5 [get_ports {ft_siwu}] ;# ACBUS4 → IO_L3P_T0_DQS_AD5P_35
|
||||
set_property PACKAGE_PIN B7 [get_ports {ft_oe_n}] ;# ACBUS6 → IO_L1P_T0_AD4P_35
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_rxf_n}]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_txe_n}]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_rd_n}]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_wr_n}]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_siwu}]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_oe_n}]
|
||||
|
||||
# Output timing: SLEW FAST + DRIVE 8 for FT2232H signals
|
||||
set_property SLEW FAST [get_ports {ft_rd_n}]
|
||||
set_property SLEW FAST [get_ports {ft_wr_n}]
|
||||
set_property SLEW FAST [get_ports {ft_oe_n}]
|
||||
set_property SLEW FAST [get_ports {ft_siwu}]
|
||||
set_property SLEW FAST [get_ports {ft_data[*]}]
|
||||
set_property DRIVE 8 [get_ports {ft_rd_n}]
|
||||
set_property DRIVE 8 [get_ports {ft_wr_n}]
|
||||
set_property DRIVE 8 [get_ports {ft_oe_n}]
|
||||
set_property DRIVE 8 [get_ports {ft_siwu}]
|
||||
set_property DRIVE 8 [get_ports {ft_data[*]}]
|
||||
|
||||
# ft_clkout constrained above in CLOCK CONSTRAINTS section (C4, 60 MHz)
|
||||
|
||||
# ============================================================================
|
||||
# STATUS / DEBUG OUTPUTS — NO PHYSICAL CONNECTIONS
|
||||
# ============================================================================
|
||||
@@ -333,7 +375,13 @@ set_false_path -from [get_clocks adc_dco_p] -to [get_clocks clk_100m]
|
||||
set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_120m_dac]
|
||||
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_100m]
|
||||
|
||||
# FT601 CDC paths removed — no ft601_clk_in clock defined (chip unwired)
|
||||
# FT2232H CDC: clk_100m ↔ ft_clkout (60 MHz), toggle CDC in RTL
|
||||
set_false_path -from [get_clocks clk_100m] -to [get_clocks ft_clkout]
|
||||
set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_100m]
|
||||
|
||||
# FT2232H CDC: clk_120m_dac ↔ ft_clkout (no direct crossing, but belt-and-suspenders)
|
||||
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks ft_clkout]
|
||||
set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_120m_dac]
|
||||
|
||||
# ============================================================================
|
||||
# PHYSICAL CONSTRAINTS
|
||||
|
||||
@@ -103,13 +103,17 @@ reg [7:0] signal_power_i, signal_power_q;
|
||||
|
||||
// Internal mixing signals
|
||||
// DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 handles all internal pipelining
|
||||
// Latency: 3 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG)
|
||||
// Latency: 4 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG, 1 for post-DSP retiming)
|
||||
wire signed [MIXER_WIDTH-1:0] adc_signed_w;
|
||||
reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q;
|
||||
reg mixed_valid;
|
||||
reg mixer_overflow_i, mixer_overflow_q;
|
||||
// Pipeline valid tracking: 3-stage shift register to match DSP48E1 AREG+MREG+PREG latency
|
||||
reg [2:0] dsp_valid_pipe;
|
||||
// Pipeline valid tracking: 4-stage shift register (3 for DSP48E1 + 1 for post-DSP retiming)
|
||||
reg [3:0] dsp_valid_pipe;
|
||||
// Post-DSP retiming registers — breaks DSP48E1 CLK→P to fabric timing path
|
||||
// This extra pipeline stage absorbs the 1.866ns DSP output prop delay + routing,
|
||||
// ensuring WNS > 0 at 400 MHz regardless of placement seed
|
||||
(* DONT_TOUCH = "TRUE" *) reg signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_retimed, mult_q_retimed;
|
||||
|
||||
// Output stage registers
|
||||
reg signed [17:0] baseband_i_reg, baseband_q_reg;
|
||||
@@ -219,12 +223,12 @@ nco_400m_enhanced nco_core (
|
||||
assign adc_signed_w = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} -
|
||||
{1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2;
|
||||
|
||||
// Valid pipeline: 3-stage shift register matching DSP48E1 AREG+MREG+PREG latency
|
||||
// Valid pipeline: 4-stage shift register (3 for DSP48E1 AREG+MREG+PREG + 1 for retiming)
|
||||
always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
if (!reset_n_400m) begin
|
||||
dsp_valid_pipe <= 3'b000;
|
||||
dsp_valid_pipe <= 4'b0000;
|
||||
end else begin
|
||||
dsp_valid_pipe <= {dsp_valid_pipe[1:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)};
|
||||
dsp_valid_pipe <= {dsp_valid_pipe[2:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)};
|
||||
end
|
||||
end
|
||||
|
||||
@@ -271,6 +275,17 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
end
|
||||
end
|
||||
|
||||
// Stage 4: Post-DSP retiming register (matches synthesis path)
|
||||
always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
if (!reset_n_400m) begin
|
||||
mult_i_retimed <= 0;
|
||||
mult_q_retimed <= 0;
|
||||
end else begin
|
||||
mult_i_retimed <= mult_i_reg;
|
||||
mult_q_retimed <= mult_q_reg;
|
||||
end
|
||||
end
|
||||
|
||||
`else
|
||||
// ---- Direct DSP48E1 instantiation for Vivado synthesis ----
|
||||
// This guarantees AREG/BREG/MREG are used, achieving timing closure at 400 MHz
|
||||
@@ -448,6 +463,19 @@ DSP48E1 #(
|
||||
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_reg = dsp_p_i[MIXER_WIDTH+NCO_WIDTH-1:0];
|
||||
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_q_reg = dsp_p_q[MIXER_WIDTH+NCO_WIDTH-1:0];
|
||||
|
||||
// Stage 4: Post-DSP retiming register — breaks DSP48E1 CLK→P to fabric path
|
||||
// Without this, the DSP output prop delay (1.866ns) + routing (0.515ns) exceeds
|
||||
// the 2.500ns clock period at slow process corner
|
||||
always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
if (!reset_n_400m) begin
|
||||
mult_i_retimed <= 0;
|
||||
mult_q_retimed <= 0;
|
||||
end else begin
|
||||
mult_i_retimed <= mult_i_reg;
|
||||
mult_q_retimed <= mult_q_reg;
|
||||
end
|
||||
end
|
||||
|
||||
`endif
|
||||
|
||||
// ============================================================================
|
||||
@@ -464,7 +492,7 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
mixer_overflow_q <= 0;
|
||||
saturation_count <= 0;
|
||||
overflow_detected <= 0;
|
||||
end else if (dsp_valid_pipe[2]) begin
|
||||
end else if (dsp_valid_pipe[3]) begin
|
||||
// Force saturation for testing (applied after DSP output, not on input path)
|
||||
if (force_saturation_sync) begin
|
||||
mixed_i <= 34'h1FFFFFFFF;
|
||||
@@ -472,15 +500,15 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
mixer_overflow_i <= 1'b1;
|
||||
mixer_overflow_q <= 1'b1;
|
||||
end else begin
|
||||
// Normal path: take DSP48E1 multiply result
|
||||
mixed_i <= mult_i_reg;
|
||||
mixed_q <= mult_q_reg;
|
||||
// Normal path: take retimed DSP48E1 multiply result
|
||||
mixed_i <= mult_i_retimed;
|
||||
mixed_q <= mult_q_retimed;
|
||||
|
||||
// Overflow detection on current cycle's multiply result
|
||||
mixer_overflow_i <= (mult_i_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
||||
(mult_i_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
||||
mixer_overflow_q <= (mult_q_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
||||
(mult_q_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
||||
// Overflow detection on retimed multiply result
|
||||
mixer_overflow_i <= (mult_i_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
||||
(mult_i_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
||||
mixer_overflow_q <= (mult_q_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
||||
(mult_q_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
||||
end
|
||||
|
||||
mixed_valid <= 1;
|
||||
|
||||
@@ -7,12 +7,16 @@
|
||||
* Integrates:
|
||||
* - Radar Transmitter (PLFM chirp generation)
|
||||
* - Radar Receiver (ADC interface, DDC, matched filtering, Doppler processing)
|
||||
* - USB Data Interface (FT601 for high-speed data transfer)
|
||||
* - USB Data Interface (FT601 USB 3.0 or FT2232H USB 2.0, selected by USB_MODE)
|
||||
*
|
||||
* Clock domains:
|
||||
* - clk_100m: System clock (100MHz)
|
||||
* - clk_120m_dac: DAC clock (120MHz)
|
||||
* - ft601_clk: FT601 interface clock (100MHz from FT601)
|
||||
* - ft601_clk: USB interface clock (100MHz FT601 or 60MHz FT2232H)
|
||||
*
|
||||
* USB_MODE parameter:
|
||||
* 0 = FT601 (32-bit, USB 3.0) — 200T premium board
|
||||
* 1 = FT2232H (8-bit, USB 2.0) — 50T production board
|
||||
*/
|
||||
|
||||
module radar_system_top (
|
||||
@@ -93,9 +97,19 @@ module radar_system_top (
|
||||
input wire [1:0] ft601_srb, // Selected read buffer
|
||||
input wire [1:0] ft601_swb, // Selected write buffer
|
||||
|
||||
// Clock output (optional)
|
||||
// Clock output (optional, FT601 only — not used for FT2232H)
|
||||
output wire ft601_clk_out,
|
||||
|
||||
// ========== FT2232H USB 2.0 INTERFACE (USB_MODE=1) ==========
|
||||
// 8-bit bidirectional data bus (245 Synchronous FIFO mode, Channel A)
|
||||
inout wire [7:0] ft_data, // 8-bit bidirectional data bus
|
||||
input wire ft_rxf_n, // RX FIFO not empty (active low)
|
||||
input wire ft_txe_n, // TX FIFO not full (active low)
|
||||
output wire ft_rd_n, // Read strobe (active low)
|
||||
output wire ft_wr_n, // Write strobe (active low)
|
||||
output wire ft_oe_n, // Output enable / bus direction
|
||||
output wire ft_siwu, // Send Immediate / WakeUp
|
||||
|
||||
// ========== STATUS OUTPUTS ==========
|
||||
|
||||
// Beam position tracking
|
||||
@@ -122,6 +136,7 @@ module radar_system_top (
|
||||
parameter USE_LONG_CHIRP = 1'b1; // Default to long chirp
|
||||
parameter DOPPLER_ENABLE = 1'b1; // Enable Doppler processing
|
||||
parameter USB_ENABLE = 1'b1; // Enable USB data transfer
|
||||
parameter USB_MODE = 0; // 0=FT601 (32-bit, 200T), 1=FT2232H (8-bit, 50T)
|
||||
|
||||
// ============================================================================
|
||||
// INTERNAL SIGNALS
|
||||
@@ -667,67 +682,143 @@ assign usb_detect_flag = rx_detect_flag;
|
||||
assign usb_detect_valid = rx_detect_valid;
|
||||
|
||||
// ============================================================================
|
||||
// USB DATA INTERFACE INSTANTIATION
|
||||
// USB DATA INTERFACE INSTANTIATION (parametric: FT601 or FT2232H)
|
||||
// ============================================================================
|
||||
|
||||
usb_data_interface usb_inst (
|
||||
.clk(clk_100m_buf),
|
||||
.reset_n(sys_reset_n),
|
||||
.ft601_reset_n(sys_reset_ft601_n), // FT601-domain synchronized reset
|
||||
|
||||
// Radar data inputs
|
||||
.range_profile(usb_range_profile),
|
||||
.range_valid(usb_range_valid),
|
||||
.doppler_real(usb_doppler_real),
|
||||
.doppler_imag(usb_doppler_imag),
|
||||
.doppler_valid(usb_doppler_valid),
|
||||
.cfar_detection(usb_detect_flag),
|
||||
.cfar_valid(usb_detect_valid),
|
||||
|
||||
// FT601 Interface
|
||||
.ft601_data(ft601_data),
|
||||
.ft601_be(ft601_be),
|
||||
.ft601_txe_n(ft601_txe_n),
|
||||
.ft601_rxf_n(ft601_rxf_n),
|
||||
.ft601_txe(ft601_txe),
|
||||
.ft601_rxf(ft601_rxf),
|
||||
.ft601_wr_n(ft601_wr_n),
|
||||
.ft601_rd_n(ft601_rd_n),
|
||||
.ft601_oe_n(ft601_oe_n),
|
||||
.ft601_siwu_n(ft601_siwu_n),
|
||||
.ft601_srb(ft601_srb),
|
||||
.ft601_swb(ft601_swb),
|
||||
.ft601_clk_out(ft601_clk_out),
|
||||
.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),
|
||||
generate
|
||||
if (USB_MODE == 0) begin : gen_ft601
|
||||
// ---- FT601 USB 3.0 (32-bit, 200T premium board) ----
|
||||
usb_data_interface usb_inst (
|
||||
.clk(clk_100m_buf),
|
||||
.reset_n(sys_reset_n),
|
||||
.ft601_reset_n(sys_reset_ft601_n),
|
||||
|
||||
// Radar data inputs
|
||||
.range_profile(usb_range_profile),
|
||||
.range_valid(usb_range_valid),
|
||||
.doppler_real(usb_doppler_real),
|
||||
.doppler_imag(usb_doppler_imag),
|
||||
.doppler_valid(usb_doppler_valid),
|
||||
.cfar_detection(usb_detect_flag),
|
||||
.cfar_valid(usb_detect_valid),
|
||||
|
||||
// FT601 Interface
|
||||
.ft601_data(ft601_data),
|
||||
.ft601_be(ft601_be),
|
||||
.ft601_txe_n(ft601_txe_n),
|
||||
.ft601_rxf_n(ft601_rxf_n),
|
||||
.ft601_txe(ft601_txe),
|
||||
.ft601_rxf(ft601_rxf),
|
||||
.ft601_wr_n(ft601_wr_n),
|
||||
.ft601_rd_n(ft601_rd_n),
|
||||
.ft601_oe_n(ft601_oe_n),
|
||||
.ft601_siwu_n(ft601_siwu_n),
|
||||
.ft601_srb(ft601_srb),
|
||||
.ft601_swb(ft601_swb),
|
||||
.ft601_clk_out(ft601_clk_out),
|
||||
.ft601_clk_in(ft601_clk_buf),
|
||||
|
||||
// Host command outputs
|
||||
.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),
|
||||
|
||||
// Gap 2: Stream control (clk_100m domain, CDC'd inside usb_data_interface)
|
||||
.stream_control(host_stream_control),
|
||||
// Stream control
|
||||
.stream_control(host_stream_control),
|
||||
|
||||
// Gap 2: Status readback inputs
|
||||
.status_request(host_status_request),
|
||||
.status_cfar_threshold(host_detect_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),
|
||||
.status_range_mode(host_range_mode),
|
||||
// Status readback inputs
|
||||
.status_request(host_status_request),
|
||||
.status_cfar_threshold(host_detect_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),
|
||||
.status_range_mode(host_range_mode),
|
||||
|
||||
// Self-test status readback
|
||||
.status_self_test_flags(self_test_flags_latched),
|
||||
.status_self_test_detail(self_test_detail_latched),
|
||||
.status_self_test_busy(self_test_busy)
|
||||
);
|
||||
// Self-test status readback
|
||||
.status_self_test_flags(self_test_flags_latched),
|
||||
.status_self_test_detail(self_test_detail_latched),
|
||||
.status_self_test_busy(self_test_busy)
|
||||
);
|
||||
|
||||
// FT2232H ports unused in FT601 mode — tie off
|
||||
assign ft_rd_n = 1'b1;
|
||||
assign ft_wr_n = 1'b1;
|
||||
assign ft_oe_n = 1'b1;
|
||||
assign ft_siwu = 1'b0;
|
||||
|
||||
end else begin : gen_ft2232h
|
||||
// ---- FT2232H USB 2.0 (8-bit, 50T production board) ----
|
||||
usb_data_interface_ft2232h usb_inst (
|
||||
.clk(clk_100m_buf),
|
||||
.reset_n(sys_reset_n),
|
||||
.ft_reset_n(sys_reset_ft601_n), // Reuse same synchronized reset
|
||||
|
||||
// Radar data inputs
|
||||
.range_profile(usb_range_profile),
|
||||
.range_valid(usb_range_valid),
|
||||
.doppler_real(usb_doppler_real),
|
||||
.doppler_imag(usb_doppler_imag),
|
||||
.doppler_valid(usb_doppler_valid),
|
||||
.cfar_detection(usb_detect_flag),
|
||||
.cfar_valid(usb_detect_valid),
|
||||
|
||||
// FT2232H Interface
|
||||
.ft_data(ft_data),
|
||||
.ft_rxf_n(ft_rxf_n),
|
||||
.ft_txe_n(ft_txe_n),
|
||||
.ft_rd_n(ft_rd_n),
|
||||
.ft_wr_n(ft_wr_n),
|
||||
.ft_oe_n(ft_oe_n),
|
||||
.ft_siwu(ft_siwu),
|
||||
.ft_clk(ft601_clk_buf), // Reuse BUFG'd USB clock
|
||||
|
||||
// Host command outputs
|
||||
.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),
|
||||
|
||||
// Stream control
|
||||
.stream_control(host_stream_control),
|
||||
|
||||
// Status readback inputs
|
||||
.status_request(host_status_request),
|
||||
.status_cfar_threshold(host_detect_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),
|
||||
.status_range_mode(host_range_mode),
|
||||
|
||||
// Self-test status readback
|
||||
.status_self_test_flags(self_test_flags_latched),
|
||||
.status_self_test_detail(self_test_detail_latched),
|
||||
.status_self_test_busy(self_test_busy)
|
||||
);
|
||||
|
||||
// FT601 ports unused in FT2232H mode — tie off
|
||||
assign ft601_be = 4'b0000;
|
||||
assign ft601_txe_n = 1'b1;
|
||||
assign ft601_rxf_n = 1'b1;
|
||||
assign ft601_wr_n = 1'b1;
|
||||
assign ft601_rd_n = 1'b1;
|
||||
assign ft601_oe_n = 1'b1;
|
||||
assign ft601_siwu_n = 1'b1;
|
||||
assign ft601_clk_out = 1'b0;
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ============================================================================
|
||||
// USB COMMAND CDC: ft601_clk → clk_100m (Gap 4: USB Read Path)
|
||||
|
||||
@@ -6,12 +6,17 @@
|
||||
* 50T Production Wrapper for radar_system_top
|
||||
*
|
||||
* The XC7A50T-FTG256 has only 69 usable IO pins, but radar_system_top
|
||||
* declares 182 port bits (including FT601 USB 3.0, debug outputs, and
|
||||
* declares many port bits (including FT601 USB 3.0, debug outputs, and
|
||||
* status signals that have no physical connections on the 50T board).
|
||||
*
|
||||
* This wrapper exposes only the 64 physically-connected ports and ties
|
||||
* off unused inputs. Unused outputs remain internally connected so the
|
||||
* full radar pipeline is preserved in the netlist.
|
||||
* This wrapper exposes the physically-connected ports and ties off unused
|
||||
* inputs. Unused outputs remain internally connected so the full radar
|
||||
* pipeline is preserved in the netlist.
|
||||
*
|
||||
* USB: FT2232H (USB 2.0, 8-bit, 245 Synchronous FIFO mode)
|
||||
* - USB_MODE=1 selects the FT2232H interface in radar_system_top
|
||||
* - FT2232H CLKOUT (60 MHz) connected to ft601_clk_in (shared clock port)
|
||||
* - 15 signals on Bank 35 (VCCO=3.3V, LVCMOS33)
|
||||
*/
|
||||
|
||||
module radar_system_top_50t (
|
||||
@@ -61,11 +66,20 @@ module radar_system_top_50t (
|
||||
input wire stm32_new_chirp,
|
||||
input wire stm32_new_elevation,
|
||||
input wire stm32_new_azimuth,
|
||||
input wire stm32_mixers_enable
|
||||
input wire stm32_mixers_enable,
|
||||
|
||||
// ===== FT2232H USB 2.0 Interface (Bank 35: 3.3V) =====
|
||||
input wire ft_clkout, // 60 MHz from FT2232H CLKOUT (MRCC pin C4)
|
||||
inout wire [7:0] ft_data, // 8-bit bidirectional data bus
|
||||
input wire ft_rxf_n, // RX FIFO not empty (active low)
|
||||
input wire ft_txe_n, // TX FIFO not full (active low)
|
||||
output wire ft_rd_n, // Read strobe (active low)
|
||||
output wire ft_wr_n, // Write strobe (active low)
|
||||
output wire ft_oe_n, // Output enable / bus direction
|
||||
output wire ft_siwu // Send Immediate / WakeUp
|
||||
);
|
||||
|
||||
// ===== Tie-off wires for unconstrained inputs =====
|
||||
wire ft601_clk_in_tied = 1'b0;
|
||||
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
|
||||
wire ft601_txe_tied = 1'b0;
|
||||
wire ft601_rxf_tied = 1'b0;
|
||||
wire [1:0] ft601_srb_tied = 2'b00;
|
||||
@@ -96,11 +110,13 @@ module radar_system_top_50t (
|
||||
wire [3:0] system_status_nc;
|
||||
|
||||
(* DONT_TOUCH = "TRUE" *)
|
||||
radar_system_top u_core (
|
||||
radar_system_top #(
|
||||
.USB_MODE(1) // FT2232H (8-bit USB 2.0) for 50T production
|
||||
) u_core (
|
||||
// ----- Clocks & Reset -----
|
||||
.clk_100m (clk_100m),
|
||||
.clk_120m_dac (clk_120m_dac),
|
||||
.ft601_clk_in (ft601_clk_in_tied),
|
||||
.ft601_clk_in (ft_clkout), // FT2232H 60 MHz CLKOUT → shared USB clock port
|
||||
.reset_n (reset_n),
|
||||
|
||||
// ----- DAC -----
|
||||
@@ -158,7 +174,16 @@ module radar_system_top_50t (
|
||||
.stm32_new_azimuth (stm32_new_azimuth),
|
||||
.stm32_mixers_enable (stm32_mixers_enable),
|
||||
|
||||
// ----- FT601 (unwired on 50T) -----
|
||||
// ----- FT2232H USB 2.0 (active on 50T, USB_MODE=1) -----
|
||||
.ft_data (ft_data),
|
||||
.ft_rxf_n (ft_rxf_n),
|
||||
.ft_txe_n (ft_txe_n),
|
||||
.ft_rd_n (ft_rd_n),
|
||||
.ft_wr_n (ft_wr_n),
|
||||
.ft_oe_n (ft_oe_n),
|
||||
.ft_siwu (ft_siwu),
|
||||
|
||||
// ----- FT601 (inactive with USB_MODE=1 — generate block ties off) -----
|
||||
.ft601_data (ft601_data_internal),
|
||||
.ft601_be (ft601_be_nc),
|
||||
.ft601_txe_n (ft601_txe_n_nc),
|
||||
|
||||
@@ -79,6 +79,7 @@ set rtl_files [list \
|
||||
"${rtl_dir}/cfar_ca.v" \
|
||||
"${rtl_dir}/fpga_self_test.v" \
|
||||
"${rtl_dir}/usb_data_interface.v" \
|
||||
"${rtl_dir}/usb_data_interface_ft2232h.v" \
|
||||
"${rtl_dir}/xfft_16.v" \
|
||||
"${rtl_dir}/fft_engine.v" \
|
||||
]
|
||||
|
||||
@@ -68,12 +68,18 @@ add_files -fileset constrs_1 -norecurse [file join $project_root "constraints" "
|
||||
# conflict.
|
||||
set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
|
||||
|
||||
# NSTD-1 / UCIO-1: 118 unconstrained port bits — FT601 USB 3.0 (chip unwired
|
||||
# on 50T board), dac_clk (DAC clock from AD9523, not FPGA), and all
|
||||
# status/debug outputs (no physical pins on FTG256 package).
|
||||
# NSTD-1 / UCIO-1: Unconstrained port bits — FT601 USB ports (inactive with
|
||||
# USB_MODE=1 generate block), dac_clk (DAC clock from AD9523, not FPGA),
|
||||
# and all status/debug outputs (no physical pins on FTG256 package).
|
||||
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
|
||||
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
|
||||
|
||||
# PLIO-9: FT2232H CLKOUT is routed to C4 (IO_L12N_T1_MRCC_35), the N-type
|
||||
# pin of a Multi-Region Clock-Capable pair. Clock inputs should ideally use
|
||||
# the P-type pin, but IBUFG works correctly on either. The schematic routes
|
||||
# to C4 and cannot be changed. Safe to demote.
|
||||
set_property SEVERITY {Warning} [get_drc_checks PLIO-9]
|
||||
|
||||
# ===== SYNTHESIS =====
|
||||
set synth_start [clock seconds]
|
||||
launch_runs synth_1 -jobs 8
|
||||
@@ -103,6 +109,14 @@ set impl_start [clock seconds]
|
||||
set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
|
||||
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
|
||||
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
|
||||
set_property SEVERITY {Warning} [get_drc_checks PLIO-9]
|
||||
|
||||
# FT2232H CLKOUT on C4 (N-type MRCC) — override dedicated clock route check.
|
||||
# The schematic routes the FT2232H 60 MHz clock to the N-pin of a differential
|
||||
# MRCC pair. Vivado Place 30-876 requires this property to allow placement.
|
||||
# The clock still reaches the clock network via IBUFG — this only suppresses
|
||||
# the DRC that demands the P-type pin.
|
||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {ft_clkout_IBUF}]
|
||||
|
||||
# ---- Run implementation steps ----
|
||||
opt_design -directive Explore
|
||||
|
||||
@@ -619,7 +619,7 @@ initial begin
|
||||
// Optional: dump specific signals for debugging
|
||||
$dumpvars(1, dut.tx_inst);
|
||||
$dumpvars(1, dut.rx_inst);
|
||||
$dumpvars(1, dut.usb_inst);
|
||||
$dumpvars(1, dut.gen_ft601.usb_inst);
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/**
|
||||
* usb_data_interface_ft2232h.v
|
||||
*
|
||||
* FT2232H USB 2.0 Hi-Speed FIFO Interface (245 Synchronous FIFO Mode)
|
||||
* Channel A only — 8-bit data bus, 60 MHz CLKOUT from FT2232H.
|
||||
*
|
||||
* This module is the 50T production board equivalent of usb_data_interface.v
|
||||
* (FT601, 32-bit, USB 3.0). Both share the same internal interface signals
|
||||
* so they can be swapped via a generate block in radar_system_top.v.
|
||||
*
|
||||
* Data packet (FPGA→Host): 11 bytes
|
||||
* Byte 0: 0xAA (header)
|
||||
* Bytes 1-4: range_profile[31:0] = {range_q[15:0], range_i[15:0]} MSB first
|
||||
* Bytes 5-6: doppler_real[15:0] MSB first
|
||||
* Bytes 7-8: doppler_imag[15:0] MSB first
|
||||
* Byte 9: {7'b0, cfar_detection}
|
||||
* Byte 10: 0x55 (footer)
|
||||
*
|
||||
* Status packet (FPGA→Host): 26 bytes
|
||||
* Byte 0: 0xBB (status header)
|
||||
* Bytes 1-24: 6 × 32-bit status words, MSB first
|
||||
* Byte 25: 0x55 (footer)
|
||||
*
|
||||
* Command (Host→FPGA): 4 bytes received sequentially
|
||||
* Byte 0: opcode[7:0]
|
||||
* Byte 1: addr[7:0]
|
||||
* Byte 2: value[15:8]
|
||||
* Byte 3: value[7:0]
|
||||
*
|
||||
* CDC: Toggle CDC (not level sync) for all valid pulse crossings from
|
||||
* 100 MHz → 60 MHz. Toggle CDC is guaranteed to work regardless of
|
||||
* clock frequency ratio.
|
||||
*
|
||||
* Clock domains:
|
||||
* clk = 100 MHz system clock (radar data domain)
|
||||
* ft_clk = 60 MHz from FT2232H CLKOUT (USB FIFO domain)
|
||||
*/
|
||||
|
||||
module usb_data_interface_ft2232h (
|
||||
input wire clk, // Main clock (100 MHz)
|
||||
input wire reset_n, // System reset (clk domain)
|
||||
input wire ft_reset_n, // FT2232H-domain synchronized reset
|
||||
|
||||
// Radar data inputs (clk domain)
|
||||
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,
|
||||
|
||||
// FT2232H Physical Interface (245 Synchronous FIFO mode)
|
||||
inout wire [7:0] ft_data, // 8-bit bidirectional data bus
|
||||
input wire ft_rxf_n, // Receive FIFO not empty (active low)
|
||||
input wire ft_txe_n, // Transmit FIFO not full (active low)
|
||||
output reg ft_rd_n, // Read strobe (active low)
|
||||
output reg ft_wr_n, // Write strobe (active low)
|
||||
output reg ft_oe_n, // Output enable (active low) — bus direction
|
||||
output reg ft_siwu, // Send Immediate / WakeUp
|
||||
|
||||
// Clock from FT2232H (directly used — no ODDR forwarding needed)
|
||||
input wire ft_clk, // 60 MHz from FT2232H CLKOUT
|
||||
|
||||
// Host command outputs (ft_clk domain — CDC'd by consumer)
|
||||
output reg [31:0] cmd_data,
|
||||
output reg cmd_valid,
|
||||
output reg [7:0] cmd_opcode,
|
||||
output reg [7:0] cmd_addr,
|
||||
output reg [15:0] cmd_value,
|
||||
|
||||
// Stream control input (clk domain, CDC'd internally)
|
||||
input wire [2:0] stream_control,
|
||||
|
||||
// Status readback inputs (clk domain, CDC'd internally)
|
||||
input wire status_request,
|
||||
input wire [15:0] status_cfar_threshold,
|
||||
input wire [2:0] status_stream_ctrl,
|
||||
input wire [1:0] status_radar_mode,
|
||||
input wire [15:0] status_long_chirp,
|
||||
input wire [15:0] status_long_listen,
|
||||
input wire [15:0] status_guard,
|
||||
input wire [15:0] status_short_chirp,
|
||||
input wire [15:0] status_short_listen,
|
||||
input wire [5:0] status_chirps_per_elev,
|
||||
input wire [1:0] status_range_mode,
|
||||
|
||||
// Self-test status readback
|
||||
input wire [4:0] status_self_test_flags,
|
||||
input wire [7:0] status_self_test_detail,
|
||||
input wire status_self_test_busy
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// PACKET FORMAT CONSTANTS
|
||||
// ============================================================================
|
||||
localparam HEADER = 8'hAA;
|
||||
localparam FOOTER = 8'h55;
|
||||
localparam STATUS_HEADER = 8'hBB;
|
||||
|
||||
// Data packet: 11 bytes total
|
||||
localparam DATA_PKT_LEN = 5'd11;
|
||||
// Status packet: 26 bytes total (1 header + 24 data + 1 footer)
|
||||
localparam STATUS_PKT_LEN = 5'd26;
|
||||
|
||||
// ============================================================================
|
||||
// WRITE FSM STATES (FPGA → Host)
|
||||
// ============================================================================
|
||||
localparam [2:0] WR_IDLE = 3'd0,
|
||||
WR_DATA_SEND = 3'd1,
|
||||
WR_STATUS_SEND = 3'd2,
|
||||
WR_DONE = 3'd3;
|
||||
|
||||
reg [2:0] wr_state;
|
||||
reg [4:0] wr_byte_idx; // Byte counter within packet (0..10 data, 0..25 status)
|
||||
|
||||
// ============================================================================
|
||||
// READ FSM STATES (Host → FPGA)
|
||||
// ============================================================================
|
||||
localparam [2:0] RD_IDLE = 3'd0,
|
||||
RD_OE_ASSERT = 3'd1,
|
||||
RD_READING = 3'd2,
|
||||
RD_DEASSERT = 3'd3,
|
||||
RD_PROCESS = 3'd4;
|
||||
|
||||
reg [2:0] rd_state;
|
||||
reg [1:0] rd_byte_cnt; // 0..3 for 4-byte command word
|
||||
reg [31:0] rd_shift_reg; // Shift register to assemble 4-byte command
|
||||
|
||||
// ============================================================================
|
||||
// DATA BUS DIRECTION CONTROL
|
||||
// ============================================================================
|
||||
reg [7:0] ft_data_out;
|
||||
reg ft_data_oe; // 1 = FPGA drives bus, 0 = FT2232H drives bus
|
||||
|
||||
assign ft_data = ft_data_oe ? ft_data_out : 8'hZZ;
|
||||
|
||||
// ============================================================================
|
||||
// TOGGLE CDC: clk (100 MHz) → ft_clk (60 MHz)
|
||||
// ============================================================================
|
||||
// Toggle CDC is used instead of level synchronizers because a 10 ns pulse
|
||||
// on clk_100m could be missed by the 16.67 ns ft_clk period. Toggle CDC
|
||||
// converts pulses to level transitions, which are always captured.
|
||||
|
||||
// --- Toggle registers (clk domain) ---
|
||||
reg range_valid_toggle;
|
||||
reg doppler_valid_toggle;
|
||||
reg cfar_valid_toggle;
|
||||
reg status_req_toggle;
|
||||
|
||||
// --- Holding registers (clk domain) ---
|
||||
// Data captured on valid pulse, held stable for ft_clk domain to read
|
||||
reg [31:0] range_profile_hold;
|
||||
reg [15:0] doppler_real_hold;
|
||||
reg [15:0] doppler_imag_hold;
|
||||
reg cfar_detection_hold;
|
||||
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
range_valid_toggle <= 1'b0;
|
||||
doppler_valid_toggle <= 1'b0;
|
||||
cfar_valid_toggle <= 1'b0;
|
||||
status_req_toggle <= 1'b0;
|
||||
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) begin
|
||||
range_valid_toggle <= ~range_valid_toggle;
|
||||
range_profile_hold <= range_profile;
|
||||
end
|
||||
if (doppler_valid) begin
|
||||
doppler_valid_toggle <= ~doppler_valid_toggle;
|
||||
doppler_real_hold <= doppler_real;
|
||||
doppler_imag_hold <= doppler_imag;
|
||||
end
|
||||
if (cfar_valid) begin
|
||||
cfar_valid_toggle <= ~cfar_valid_toggle;
|
||||
cfar_detection_hold <= cfar_detection;
|
||||
end
|
||||
if (status_request)
|
||||
status_req_toggle <= ~status_req_toggle;
|
||||
end
|
||||
end
|
||||
|
||||
// --- 3-stage synchronizers (ft_clk domain) ---
|
||||
// 3 stages for better MTBF at 60 MHz
|
||||
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] range_toggle_sync;
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] doppler_toggle_sync;
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] cfar_toggle_sync;
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] status_toggle_sync;
|
||||
|
||||
reg range_toggle_prev;
|
||||
reg doppler_toggle_prev;
|
||||
reg cfar_toggle_prev;
|
||||
reg status_toggle_prev;
|
||||
|
||||
// Edge-detected pulses in ft_clk domain
|
||||
wire range_valid_ft = range_toggle_sync[2] ^ range_toggle_prev;
|
||||
wire doppler_valid_ft = doppler_toggle_sync[2] ^ doppler_toggle_prev;
|
||||
wire cfar_valid_ft = cfar_toggle_sync[2] ^ cfar_toggle_prev;
|
||||
wire status_req_ft = status_toggle_sync[2] ^ status_toggle_prev;
|
||||
|
||||
// --- Stream control CDC (per-bit 2-stage, changes infrequently) ---
|
||||
(* 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];
|
||||
|
||||
// --- Captured data in ft_clk domain ---
|
||||
reg [31:0] range_profile_cap;
|
||||
reg [15:0] doppler_real_cap;
|
||||
reg [15:0] doppler_imag_cap;
|
||||
reg cfar_detection_cap;
|
||||
|
||||
// Data-pending flags (ft_clk domain)
|
||||
reg doppler_data_pending;
|
||||
reg cfar_data_pending;
|
||||
|
||||
// Status snapshot (ft_clk domain)
|
||||
reg [31:0] status_words [0:5];
|
||||
|
||||
always @(posedge ft_clk or negedge ft_reset_n) begin
|
||||
if (!ft_reset_n) begin
|
||||
range_toggle_sync <= 3'b000;
|
||||
doppler_toggle_sync <= 3'b000;
|
||||
cfar_toggle_sync <= 3'b000;
|
||||
status_toggle_sync <= 3'b000;
|
||||
range_toggle_prev <= 1'b0;
|
||||
doppler_toggle_prev <= 1'b0;
|
||||
cfar_toggle_prev <= 1'b0;
|
||||
status_toggle_prev <= 1'b0;
|
||||
range_profile_cap <= 32'd0;
|
||||
doppler_real_cap <= 16'd0;
|
||||
doppler_imag_cap <= 16'd0;
|
||||
cfar_detection_cap <= 1'b0;
|
||||
// Default to range-only on reset (prevents write FSM deadlock)
|
||||
stream_ctrl_sync_0 <= 3'b001;
|
||||
stream_ctrl_sync_1 <= 3'b001;
|
||||
end else begin
|
||||
// 3-stage toggle synchronizers
|
||||
range_toggle_sync <= {range_toggle_sync[1:0], range_valid_toggle};
|
||||
doppler_toggle_sync <= {doppler_toggle_sync[1:0], doppler_valid_toggle};
|
||||
cfar_toggle_sync <= {cfar_toggle_sync[1:0], cfar_valid_toggle};
|
||||
status_toggle_sync <= {status_toggle_sync[1:0], status_req_toggle};
|
||||
|
||||
// Previous toggle value for edge detection
|
||||
range_toggle_prev <= range_toggle_sync[2];
|
||||
doppler_toggle_prev <= doppler_toggle_sync[2];
|
||||
cfar_toggle_prev <= cfar_toggle_sync[2];
|
||||
status_toggle_prev <= status_toggle_sync[2];
|
||||
|
||||
// Stream control CDC (2-stage)
|
||||
stream_ctrl_sync_0 <= stream_control;
|
||||
stream_ctrl_sync_1 <= stream_ctrl_sync_0;
|
||||
|
||||
// Capture data on toggle edge
|
||||
if (range_valid_ft)
|
||||
range_profile_cap <= range_profile_hold;
|
||||
if (doppler_valid_ft) begin
|
||||
doppler_real_cap <= doppler_real_hold;
|
||||
doppler_imag_cap <= doppler_imag_hold;
|
||||
end
|
||||
if (cfar_valid_ft)
|
||||
cfar_detection_cap <= cfar_detection_hold;
|
||||
|
||||
// Status snapshot on request
|
||||
if (status_req_ft) begin
|
||||
status_words[0] <= {8'hFF, 3'b000, status_radar_mode,
|
||||
5'b00000, status_stream_ctrl,
|
||||
status_cfar_threshold};
|
||||
status_words[1] <= {status_long_chirp, status_long_listen};
|
||||
status_words[2] <= {status_guard, status_short_chirp};
|
||||
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||
status_words[4] <= {30'd0, status_range_mode};
|
||||
status_words[5] <= {7'd0, status_self_test_busy,
|
||||
8'd0, status_self_test_detail,
|
||||
3'd0, status_self_test_flags};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// WRITE DATA MUX — byte selection for data packet (11 bytes)
|
||||
// ============================================================================
|
||||
// Mux-based byte selection is simpler than a shift register and gives
|
||||
// explicit byte ordering for synthesis.
|
||||
|
||||
reg [7:0] data_pkt_byte;
|
||||
|
||||
always @(*) begin
|
||||
case (wr_byte_idx)
|
||||
5'd0: data_pkt_byte = HEADER;
|
||||
5'd1: data_pkt_byte = range_profile_cap[31:24]; // range MSB
|
||||
5'd2: data_pkt_byte = range_profile_cap[23:16];
|
||||
5'd3: data_pkt_byte = range_profile_cap[15:8];
|
||||
5'd4: data_pkt_byte = range_profile_cap[7:0]; // range LSB
|
||||
5'd5: data_pkt_byte = doppler_real_cap[15:8]; // doppler_real MSB
|
||||
5'd6: data_pkt_byte = doppler_real_cap[7:0]; // doppler_real LSB
|
||||
5'd7: data_pkt_byte = doppler_imag_cap[15:8]; // doppler_imag MSB
|
||||
5'd8: data_pkt_byte = doppler_imag_cap[7:0]; // doppler_imag LSB
|
||||
5'd9: data_pkt_byte = {7'b0, cfar_detection_cap}; // detection
|
||||
5'd10: data_pkt_byte = FOOTER;
|
||||
default: data_pkt_byte = 8'h00;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// WRITE DATA MUX — byte selection for status packet (26 bytes)
|
||||
// ============================================================================
|
||||
|
||||
reg [7:0] status_pkt_byte;
|
||||
|
||||
always @(*) begin
|
||||
case (wr_byte_idx)
|
||||
5'd0: status_pkt_byte = STATUS_HEADER;
|
||||
// Word 0 (bytes 1-4)
|
||||
5'd1: status_pkt_byte = status_words[0][31:24];
|
||||
5'd2: status_pkt_byte = status_words[0][23:16];
|
||||
5'd3: status_pkt_byte = status_words[0][15:8];
|
||||
5'd4: status_pkt_byte = status_words[0][7:0];
|
||||
// Word 1 (bytes 5-8)
|
||||
5'd5: status_pkt_byte = status_words[1][31:24];
|
||||
5'd6: status_pkt_byte = status_words[1][23:16];
|
||||
5'd7: status_pkt_byte = status_words[1][15:8];
|
||||
5'd8: status_pkt_byte = status_words[1][7:0];
|
||||
// Word 2 (bytes 9-12)
|
||||
5'd9: status_pkt_byte = status_words[2][31:24];
|
||||
5'd10: status_pkt_byte = status_words[2][23:16];
|
||||
5'd11: status_pkt_byte = status_words[2][15:8];
|
||||
5'd12: status_pkt_byte = status_words[2][7:0];
|
||||
// Word 3 (bytes 13-16)
|
||||
5'd13: status_pkt_byte = status_words[3][31:24];
|
||||
5'd14: status_pkt_byte = status_words[3][23:16];
|
||||
5'd15: status_pkt_byte = status_words[3][15:8];
|
||||
5'd16: status_pkt_byte = status_words[3][7:0];
|
||||
// Word 4 (bytes 17-20)
|
||||
5'd17: status_pkt_byte = status_words[4][31:24];
|
||||
5'd18: status_pkt_byte = status_words[4][23:16];
|
||||
5'd19: status_pkt_byte = status_words[4][15:8];
|
||||
5'd20: status_pkt_byte = status_words[4][7:0];
|
||||
// Word 5 (bytes 21-24)
|
||||
5'd21: status_pkt_byte = status_words[5][31:24];
|
||||
5'd22: status_pkt_byte = status_words[5][23:16];
|
||||
5'd23: status_pkt_byte = status_words[5][15:8];
|
||||
5'd24: status_pkt_byte = status_words[5][7:0];
|
||||
// Footer (byte 25)
|
||||
5'd25: status_pkt_byte = FOOTER;
|
||||
default: status_pkt_byte = 8'h00;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// MAIN FSM (ft_clk domain)
|
||||
// ============================================================================
|
||||
// Write FSM and Read FSM share the bus. Write FSM operates when Read FSM
|
||||
// is idle. Read FSM takes priority when host has data available.
|
||||
|
||||
always @(posedge ft_clk or negedge ft_reset_n) begin
|
||||
if (!ft_reset_n) begin
|
||||
wr_state <= WR_IDLE;
|
||||
wr_byte_idx <= 5'd0;
|
||||
rd_state <= RD_IDLE;
|
||||
rd_byte_cnt <= 2'd0;
|
||||
rd_shift_reg <= 32'd0;
|
||||
ft_data_out <= 8'd0;
|
||||
ft_data_oe <= 1'b0;
|
||||
ft_rd_n <= 1'b1;
|
||||
ft_wr_n <= 1'b1;
|
||||
ft_oe_n <= 1'b1;
|
||||
ft_siwu <= 1'b0;
|
||||
cmd_data <= 32'd0;
|
||||
cmd_valid <= 1'b0;
|
||||
cmd_opcode <= 8'd0;
|
||||
cmd_addr <= 8'd0;
|
||||
cmd_value <= 16'd0;
|
||||
doppler_data_pending <= 1'b0;
|
||||
cfar_data_pending <= 1'b0;
|
||||
end else begin
|
||||
// Default: clear one-shot signals
|
||||
cmd_valid <= 1'b0;
|
||||
|
||||
// Data-pending flag management
|
||||
if (doppler_valid_ft)
|
||||
doppler_data_pending <= 1'b1;
|
||||
if (cfar_valid_ft)
|
||||
cfar_data_pending <= 1'b1;
|
||||
|
||||
// ================================================================
|
||||
// READ FSM — Host → FPGA command path (4-byte sequential read)
|
||||
// ================================================================
|
||||
case (rd_state)
|
||||
RD_IDLE: begin
|
||||
// Only start reading if write FSM is idle and host has data
|
||||
if (wr_state == WR_IDLE && !ft_rxf_n) begin
|
||||
ft_oe_n <= 1'b0; // Assert OE: FT2232H drives bus
|
||||
ft_data_oe <= 1'b0; // FPGA releases bus
|
||||
rd_state <= RD_OE_ASSERT;
|
||||
end
|
||||
end
|
||||
|
||||
RD_OE_ASSERT: begin
|
||||
// 1-cycle turnaround: OE asserted, bus settling
|
||||
if (!ft_rxf_n) begin
|
||||
ft_rd_n <= 1'b0; // Assert RD: start reading
|
||||
rd_state <= RD_READING;
|
||||
end else begin
|
||||
// Host withdrew data — abort
|
||||
ft_oe_n <= 1'b1;
|
||||
rd_state <= RD_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
RD_READING: begin
|
||||
// Sample byte and shift into command register
|
||||
// Byte order: opcode, addr, value_hi, value_lo
|
||||
rd_shift_reg <= {rd_shift_reg[23:0], ft_data};
|
||||
if (rd_byte_cnt == 2'd3) begin
|
||||
// All 4 bytes received
|
||||
ft_rd_n <= 1'b1;
|
||||
rd_byte_cnt <= 2'd0;
|
||||
rd_state <= RD_DEASSERT;
|
||||
end else begin
|
||||
rd_byte_cnt <= rd_byte_cnt + 2'd1;
|
||||
// Keep reading if more data available
|
||||
if (ft_rxf_n) begin
|
||||
// Host ran out of data mid-command — abort
|
||||
ft_rd_n <= 1'b1;
|
||||
rd_byte_cnt <= 2'd0;
|
||||
rd_state <= RD_DEASSERT;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RD_DEASSERT: begin
|
||||
// Deassert OE (1 cycle after RD deasserted)
|
||||
ft_oe_n <= 1'b1;
|
||||
// Only process if we received a full 4-byte command
|
||||
if (rd_byte_cnt == 2'd0) begin
|
||||
rd_state <= RD_PROCESS;
|
||||
end else begin
|
||||
// Incomplete command — discard
|
||||
rd_state <= RD_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
RD_PROCESS: begin
|
||||
// Decode the assembled command word
|
||||
cmd_data <= rd_shift_reg;
|
||||
cmd_opcode <= rd_shift_reg[31:24];
|
||||
cmd_addr <= rd_shift_reg[23:16];
|
||||
cmd_value <= rd_shift_reg[15:0];
|
||||
cmd_valid <= 1'b1;
|
||||
rd_state <= RD_IDLE;
|
||||
end
|
||||
|
||||
default: rd_state <= RD_IDLE;
|
||||
endcase
|
||||
|
||||
// ================================================================
|
||||
// WRITE FSM — FPGA → Host data streaming (byte-sequential)
|
||||
// ================================================================
|
||||
if (rd_state == RD_IDLE) begin
|
||||
case (wr_state)
|
||||
WR_IDLE: begin
|
||||
ft_wr_n <= 1'b1;
|
||||
ft_data_oe <= 1'b0; // Release data bus
|
||||
|
||||
// Status readback takes priority
|
||||
if (status_req_ft && ft_rxf_n) begin
|
||||
wr_state <= WR_STATUS_SEND;
|
||||
wr_byte_idx <= 5'd0;
|
||||
end
|
||||
// Trigger on range_valid edge (primary data trigger)
|
||||
else if (range_valid_ft && stream_range_en) begin
|
||||
if (ft_rxf_n) begin // No host read pending
|
||||
wr_state <= WR_DATA_SEND;
|
||||
wr_byte_idx <= 5'd0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WR_DATA_SEND: begin
|
||||
if (!ft_txe_n) begin
|
||||
// TXE# low = TX FIFO has room
|
||||
ft_data_oe <= 1'b1;
|
||||
ft_data_out <= data_pkt_byte;
|
||||
ft_wr_n <= 1'b0; // Assert write strobe
|
||||
|
||||
if (wr_byte_idx == DATA_PKT_LEN - 5'd1) begin
|
||||
// Last byte of data packet
|
||||
wr_state <= WR_DONE;
|
||||
wr_byte_idx <= 5'd0;
|
||||
end else begin
|
||||
wr_byte_idx <= wr_byte_idx + 5'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WR_STATUS_SEND: begin
|
||||
if (!ft_txe_n) begin
|
||||
ft_data_oe <= 1'b1;
|
||||
ft_data_out <= status_pkt_byte;
|
||||
ft_wr_n <= 1'b0;
|
||||
|
||||
if (wr_byte_idx == STATUS_PKT_LEN - 5'd1) begin
|
||||
wr_state <= WR_DONE;
|
||||
wr_byte_idx <= 5'd0;
|
||||
end else begin
|
||||
wr_byte_idx <= wr_byte_idx + 5'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WR_DONE: begin
|
||||
ft_wr_n <= 1'b1;
|
||||
ft_data_oe <= 1'b0; // Release data bus
|
||||
// Clear pending flags — data consumed
|
||||
doppler_data_pending <= 1'b0;
|
||||
cfar_data_pending <= 1'b0;
|
||||
wr_state <= WR_IDLE;
|
||||
end
|
||||
|
||||
default: wr_state <= WR_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user