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 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 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 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:
|
# DRC Fix History:
|
||||||
# - PLIO-9: Moved clk_120m_dac from C13 (N-type) to D13 (P-type MRCC).
|
# - 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
|
# IBUFDS input buffers are VCCO-independent. BIVC-1 also waived via
|
||||||
# set_property SEVERITY in the build script as an additional safety net.
|
# set_property SEVERITY in the build script as an additional safety net.
|
||||||
# in the build script. adc_pwdn (LVCMOS25) coexists in the same bank.
|
# in the build script. adc_pwdn (LVCMOS25) coexists in the same bank.
|
||||||
# - UCIO/NSTD: 118 unconstrained ports (FT601 unwired, status/debug outputs
|
# - UCIO/NSTD: Unconstrained ports (FT601 ports inactive with USB_MODE=1,
|
||||||
# have no physical pins). Handled with SEVERITY demotion + default IOSTANDARD.
|
# 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.
|
# 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]
|
# → 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),
|
# NSTD-1 / UCIO-1: Unconstrained ports — FT601 USB ports (inactive with
|
||||||
# dac_clk (DAC clock comes from AD9523, not FPGA), and all status/debug
|
# USB_MODE=1 generate block), dac_clk (DAC clock comes from AD9523, not FPGA),
|
||||||
# outputs (no physical pins available). These ports are present in the
|
# and all status/debug outputs (no physical pins available). These ports are
|
||||||
# shared RTL but have no connections on the 50T board.
|
# 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}]
|
# → 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
|
set_input_jitter [get_clocks adc_dco_p] 0.05
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# FT601 Clock — COMMENTED OUT: FT601 (U6) is placed in schematic but has
|
# FT2232H 60 MHz CLKOUT (Bank 35, MRCC pin C4)
|
||||||
# zero net connections. No physical clock pin exists on this board.
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# create_clock -name ft601_clk_in -period 10.0 [get_ports {ft601_clk_in}]
|
# The FT2232H provides a 60 MHz clock in 245 Synchronous FIFO mode.
|
||||||
# set_input_jitter [get_clocks ft601_clk_in] 0.1
|
# 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)
|
# 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
|
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
|
# FT2232H (U6) Channel A in 245 Synchronous FIFO mode.
|
||||||
# ZERO net connections — no signals are routed between it and the FPGA.
|
# All signals are direct connections to FPGA Bank 35 (LVCMOS33).
|
||||||
# Bank 35 (which would logically host FT601 signals) has no signal pins
|
# Pin mapping extracted from Eagle schematic (RADAR_Main_Board.sch).
|
||||||
# connected, only VCCO_35 power.
|
|
||||||
#
|
#
|
||||||
# ALL FT601 constraints are commented out. The RTL module usb_data_interface.v
|
# The FT2232H replaces the previously-unwired FT601 on the 50T production
|
||||||
# instantiates the FT601 interface, but it cannot function without physical
|
# board. The 200T dev board retains FT601 USB 3.0 (32-bit).
|
||||||
# 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.
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
# 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
|
# 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_100m] -to [get_clocks clk_120m_dac]
|
||||||
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_100m]
|
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
|
# PHYSICAL CONSTRAINTS
|
||||||
|
|||||||
@@ -103,13 +103,17 @@ reg [7:0] signal_power_i, signal_power_q;
|
|||||||
|
|
||||||
// Internal mixing signals
|
// Internal mixing signals
|
||||||
// DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 handles all internal pipelining
|
// 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;
|
wire signed [MIXER_WIDTH-1:0] adc_signed_w;
|
||||||
reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q;
|
reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q;
|
||||||
reg mixed_valid;
|
reg mixed_valid;
|
||||||
reg mixer_overflow_i, mixer_overflow_q;
|
reg mixer_overflow_i, mixer_overflow_q;
|
||||||
// Pipeline valid tracking: 3-stage shift register to match DSP48E1 AREG+MREG+PREG latency
|
// Pipeline valid tracking: 4-stage shift register (3 for DSP48E1 + 1 for post-DSP retiming)
|
||||||
reg [2:0] dsp_valid_pipe;
|
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
|
// Output stage registers
|
||||||
reg signed [17:0] baseband_i_reg, baseband_q_reg;
|
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}}} -
|
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;
|
{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
|
always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||||
if (!reset_n_400m) begin
|
if (!reset_n_400m) begin
|
||||||
dsp_valid_pipe <= 3'b000;
|
dsp_valid_pipe <= 4'b0000;
|
||||||
end else begin
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -271,6 +275,17 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
|
|||||||
end
|
end
|
||||||
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
|
`else
|
||||||
// ---- Direct DSP48E1 instantiation for Vivado synthesis ----
|
// ---- Direct DSP48E1 instantiation for Vivado synthesis ----
|
||||||
// This guarantees AREG/BREG/MREG are used, achieving timing closure at 400 MHz
|
// 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_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];
|
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
|
`endif
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -464,7 +492,7 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
|
|||||||
mixer_overflow_q <= 0;
|
mixer_overflow_q <= 0;
|
||||||
saturation_count <= 0;
|
saturation_count <= 0;
|
||||||
overflow_detected <= 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)
|
// Force saturation for testing (applied after DSP output, not on input path)
|
||||||
if (force_saturation_sync) begin
|
if (force_saturation_sync) begin
|
||||||
mixed_i <= 34'h1FFFFFFFF;
|
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_i <= 1'b1;
|
||||||
mixer_overflow_q <= 1'b1;
|
mixer_overflow_q <= 1'b1;
|
||||||
end else begin
|
end else begin
|
||||||
// Normal path: take DSP48E1 multiply result
|
// Normal path: take retimed DSP48E1 multiply result
|
||||||
mixed_i <= mult_i_reg;
|
mixed_i <= mult_i_retimed;
|
||||||
mixed_q <= mult_q_reg;
|
mixed_q <= mult_q_retimed;
|
||||||
|
|
||||||
// Overflow detection on current cycle's multiply result
|
// Overflow detection on retimed multiply result
|
||||||
mixer_overflow_i <= (mult_i_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
mixer_overflow_i <= (mult_i_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
||||||
(mult_i_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
(mult_i_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
||||||
mixer_overflow_q <= (mult_q_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
mixer_overflow_q <= (mult_q_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
|
||||||
(mult_q_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
(mult_q_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
|
||||||
end
|
end
|
||||||
|
|
||||||
mixed_valid <= 1;
|
mixed_valid <= 1;
|
||||||
|
|||||||
@@ -7,12 +7,16 @@
|
|||||||
* Integrates:
|
* Integrates:
|
||||||
* - Radar Transmitter (PLFM chirp generation)
|
* - Radar Transmitter (PLFM chirp generation)
|
||||||
* - Radar Receiver (ADC interface, DDC, matched filtering, Doppler processing)
|
* - 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:
|
* Clock domains:
|
||||||
* - clk_100m: System clock (100MHz)
|
* - clk_100m: System clock (100MHz)
|
||||||
* - clk_120m_dac: DAC clock (120MHz)
|
* - 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 (
|
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_srb, // Selected read buffer
|
||||||
input wire [1:0] ft601_swb, // Selected write 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,
|
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 ==========
|
// ========== STATUS OUTPUTS ==========
|
||||||
|
|
||||||
// Beam position tracking
|
// Beam position tracking
|
||||||
@@ -122,6 +136,7 @@ module radar_system_top (
|
|||||||
parameter USE_LONG_CHIRP = 1'b1; // Default to long chirp
|
parameter USE_LONG_CHIRP = 1'b1; // Default to long chirp
|
||||||
parameter DOPPLER_ENABLE = 1'b1; // Enable Doppler processing
|
parameter DOPPLER_ENABLE = 1'b1; // Enable Doppler processing
|
||||||
parameter USB_ENABLE = 1'b1; // Enable USB data transfer
|
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
|
// INTERNAL SIGNALS
|
||||||
@@ -667,67 +682,143 @@ assign usb_detect_flag = rx_detect_flag;
|
|||||||
assign usb_detect_valid = rx_detect_valid;
|
assign usb_detect_valid = rx_detect_valid;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// USB DATA INTERFACE INSTANTIATION
|
// USB DATA INTERFACE INSTANTIATION (parametric: FT601 or FT2232H)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
usb_data_interface usb_inst (
|
generate
|
||||||
.clk(clk_100m_buf),
|
if (USB_MODE == 0) begin : gen_ft601
|
||||||
.reset_n(sys_reset_n),
|
// ---- FT601 USB 3.0 (32-bit, 200T premium board) ----
|
||||||
.ft601_reset_n(sys_reset_ft601_n), // FT601-domain synchronized reset
|
usb_data_interface usb_inst (
|
||||||
|
.clk(clk_100m_buf),
|
||||||
// Radar data inputs
|
.reset_n(sys_reset_n),
|
||||||
.range_profile(usb_range_profile),
|
.ft601_reset_n(sys_reset_ft601_n),
|
||||||
.range_valid(usb_range_valid),
|
|
||||||
.doppler_real(usb_doppler_real),
|
// Radar data inputs
|
||||||
.doppler_imag(usb_doppler_imag),
|
.range_profile(usb_range_profile),
|
||||||
.doppler_valid(usb_doppler_valid),
|
.range_valid(usb_range_valid),
|
||||||
.cfar_detection(usb_detect_flag),
|
.doppler_real(usb_doppler_real),
|
||||||
.cfar_valid(usb_detect_valid),
|
.doppler_imag(usb_doppler_imag),
|
||||||
|
.doppler_valid(usb_doppler_valid),
|
||||||
// FT601 Interface
|
.cfar_detection(usb_detect_flag),
|
||||||
.ft601_data(ft601_data),
|
.cfar_valid(usb_detect_valid),
|
||||||
.ft601_be(ft601_be),
|
|
||||||
.ft601_txe_n(ft601_txe_n),
|
// FT601 Interface
|
||||||
.ft601_rxf_n(ft601_rxf_n),
|
.ft601_data(ft601_data),
|
||||||
.ft601_txe(ft601_txe),
|
.ft601_be(ft601_be),
|
||||||
.ft601_rxf(ft601_rxf),
|
.ft601_txe_n(ft601_txe_n),
|
||||||
.ft601_wr_n(ft601_wr_n),
|
.ft601_rxf_n(ft601_rxf_n),
|
||||||
.ft601_rd_n(ft601_rd_n),
|
.ft601_txe(ft601_txe),
|
||||||
.ft601_oe_n(ft601_oe_n),
|
.ft601_rxf(ft601_rxf),
|
||||||
.ft601_siwu_n(ft601_siwu_n),
|
.ft601_wr_n(ft601_wr_n),
|
||||||
.ft601_srb(ft601_srb),
|
.ft601_rd_n(ft601_rd_n),
|
||||||
.ft601_swb(ft601_swb),
|
.ft601_oe_n(ft601_oe_n),
|
||||||
.ft601_clk_out(ft601_clk_out),
|
.ft601_siwu_n(ft601_siwu_n),
|
||||||
.ft601_clk_in(ft601_clk_buf),
|
.ft601_srb(ft601_srb),
|
||||||
|
.ft601_swb(ft601_swb),
|
||||||
// Host command outputs (Gap 4: USB Read Path)
|
.ft601_clk_out(ft601_clk_out),
|
||||||
.cmd_data(usb_cmd_data),
|
.ft601_clk_in(ft601_clk_buf),
|
||||||
.cmd_valid(usb_cmd_valid),
|
|
||||||
.cmd_opcode(usb_cmd_opcode),
|
// Host command outputs
|
||||||
.cmd_addr(usb_cmd_addr),
|
.cmd_data(usb_cmd_data),
|
||||||
.cmd_value(usb_cmd_value),
|
.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
|
||||||
.stream_control(host_stream_control),
|
.stream_control(host_stream_control),
|
||||||
|
|
||||||
// Gap 2: Status readback inputs
|
// Status readback inputs
|
||||||
.status_request(host_status_request),
|
.status_request(host_status_request),
|
||||||
.status_cfar_threshold(host_detect_threshold),
|
.status_cfar_threshold(host_detect_threshold),
|
||||||
.status_stream_ctrl(host_stream_control),
|
.status_stream_ctrl(host_stream_control),
|
||||||
.status_radar_mode(host_radar_mode),
|
.status_radar_mode(host_radar_mode),
|
||||||
.status_long_chirp(host_long_chirp_cycles),
|
.status_long_chirp(host_long_chirp_cycles),
|
||||||
.status_long_listen(host_long_listen_cycles),
|
.status_long_listen(host_long_listen_cycles),
|
||||||
.status_guard(host_guard_cycles),
|
.status_guard(host_guard_cycles),
|
||||||
.status_short_chirp(host_short_chirp_cycles),
|
.status_short_chirp(host_short_chirp_cycles),
|
||||||
.status_short_listen(host_short_listen_cycles),
|
.status_short_listen(host_short_listen_cycles),
|
||||||
.status_chirps_per_elev(host_chirps_per_elev),
|
.status_chirps_per_elev(host_chirps_per_elev),
|
||||||
.status_range_mode(host_range_mode),
|
.status_range_mode(host_range_mode),
|
||||||
|
|
||||||
// Self-test status readback
|
// Self-test status readback
|
||||||
.status_self_test_flags(self_test_flags_latched),
|
.status_self_test_flags(self_test_flags_latched),
|
||||||
.status_self_test_detail(self_test_detail_latched),
|
.status_self_test_detail(self_test_detail_latched),
|
||||||
.status_self_test_busy(self_test_busy)
|
.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)
|
// USB COMMAND CDC: ft601_clk → clk_100m (Gap 4: USB Read Path)
|
||||||
|
|||||||
@@ -6,12 +6,17 @@
|
|||||||
* 50T Production Wrapper for radar_system_top
|
* 50T Production Wrapper for radar_system_top
|
||||||
*
|
*
|
||||||
* The XC7A50T-FTG256 has only 69 usable IO pins, but 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).
|
* status signals that have no physical connections on the 50T board).
|
||||||
*
|
*
|
||||||
* This wrapper exposes only the 64 physically-connected ports and ties
|
* This wrapper exposes the physically-connected ports and ties off unused
|
||||||
* off unused inputs. Unused outputs remain internally connected so the
|
* inputs. Unused outputs remain internally connected so the full radar
|
||||||
* full radar pipeline is preserved in the netlist.
|
* 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 (
|
module radar_system_top_50t (
|
||||||
@@ -61,11 +66,20 @@ module radar_system_top_50t (
|
|||||||
input wire stm32_new_chirp,
|
input wire stm32_new_chirp,
|
||||||
input wire stm32_new_elevation,
|
input wire stm32_new_elevation,
|
||||||
input wire stm32_new_azimuth,
|
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 =====
|
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
|
||||||
wire ft601_clk_in_tied = 1'b0;
|
|
||||||
wire ft601_txe_tied = 1'b0;
|
wire ft601_txe_tied = 1'b0;
|
||||||
wire ft601_rxf_tied = 1'b0;
|
wire ft601_rxf_tied = 1'b0;
|
||||||
wire [1:0] ft601_srb_tied = 2'b00;
|
wire [1:0] ft601_srb_tied = 2'b00;
|
||||||
@@ -96,11 +110,13 @@ module radar_system_top_50t (
|
|||||||
wire [3:0] system_status_nc;
|
wire [3:0] system_status_nc;
|
||||||
|
|
||||||
(* DONT_TOUCH = "TRUE" *)
|
(* 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 -----
|
// ----- Clocks & Reset -----
|
||||||
.clk_100m (clk_100m),
|
.clk_100m (clk_100m),
|
||||||
.clk_120m_dac (clk_120m_dac),
|
.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),
|
.reset_n (reset_n),
|
||||||
|
|
||||||
// ----- DAC -----
|
// ----- DAC -----
|
||||||
@@ -158,7 +174,16 @@ module radar_system_top_50t (
|
|||||||
.stm32_new_azimuth (stm32_new_azimuth),
|
.stm32_new_azimuth (stm32_new_azimuth),
|
||||||
.stm32_mixers_enable (stm32_mixers_enable),
|
.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_data (ft601_data_internal),
|
||||||
.ft601_be (ft601_be_nc),
|
.ft601_be (ft601_be_nc),
|
||||||
.ft601_txe_n (ft601_txe_n_nc),
|
.ft601_txe_n (ft601_txe_n_nc),
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ set rtl_files [list \
|
|||||||
"${rtl_dir}/cfar_ca.v" \
|
"${rtl_dir}/cfar_ca.v" \
|
||||||
"${rtl_dir}/fpga_self_test.v" \
|
"${rtl_dir}/fpga_self_test.v" \
|
||||||
"${rtl_dir}/usb_data_interface.v" \
|
"${rtl_dir}/usb_data_interface.v" \
|
||||||
|
"${rtl_dir}/usb_data_interface_ft2232h.v" \
|
||||||
"${rtl_dir}/xfft_16.v" \
|
"${rtl_dir}/xfft_16.v" \
|
||||||
"${rtl_dir}/fft_engine.v" \
|
"${rtl_dir}/fft_engine.v" \
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -68,12 +68,18 @@ add_files -fileset constrs_1 -norecurse [file join $project_root "constraints" "
|
|||||||
# conflict.
|
# conflict.
|
||||||
set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
|
set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
|
||||||
|
|
||||||
# NSTD-1 / UCIO-1: 118 unconstrained port bits — FT601 USB 3.0 (chip unwired
|
# NSTD-1 / UCIO-1: Unconstrained port bits — FT601 USB ports (inactive with
|
||||||
# on 50T board), dac_clk (DAC clock from AD9523, not FPGA), and all
|
# USB_MODE=1 generate block), dac_clk (DAC clock from AD9523, not FPGA),
|
||||||
# status/debug outputs (no physical pins on FTG256 package).
|
# 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 NSTD-1]
|
||||||
set_property SEVERITY {Warning} [get_drc_checks UCIO-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 =====
|
# ===== SYNTHESIS =====
|
||||||
set synth_start [clock seconds]
|
set synth_start [clock seconds]
|
||||||
launch_runs synth_1 -jobs 8
|
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 BIVC-1]
|
||||||
set_property SEVERITY {Warning} [get_drc_checks NSTD-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 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 ----
|
# ---- Run implementation steps ----
|
||||||
opt_design -directive Explore
|
opt_design -directive Explore
|
||||||
|
|||||||
@@ -619,7 +619,7 @@ initial begin
|
|||||||
// Optional: dump specific signals for debugging
|
// Optional: dump specific signals for debugging
|
||||||
$dumpvars(1, dut.tx_inst);
|
$dumpvars(1, dut.tx_inst);
|
||||||
$dumpvars(1, dut.rx_inst);
|
$dumpvars(1, dut.rx_inst);
|
||||||
$dumpvars(1, dut.usb_inst);
|
$dumpvars(1, dut.gen_ft601.usb_inst);
|
||||||
end
|
end
|
||||||
|
|
||||||
endmodule
|
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
|
||||||
@@ -2,17 +2,26 @@
|
|||||||
"""
|
"""
|
||||||
AERIS-10 Radar Protocol Layer
|
AERIS-10 Radar Protocol Layer
|
||||||
===============================
|
===============================
|
||||||
Pure-logic module for FT601 packet parsing and command building.
|
Pure-logic module for USB packet parsing and command building.
|
||||||
No GUI dependencies — safe to import from tests and headless scripts.
|
No GUI dependencies — safe to import from tests and headless scripts.
|
||||||
|
|
||||||
Matches usb_data_interface.v packet format exactly.
|
Supports two USB interfaces:
|
||||||
|
- FT601 USB 3.0 (32-bit, 200T dev board) via ftd3xx
|
||||||
|
- FT2232H USB 2.0 (8-bit, 50T production board) via pyftdi
|
||||||
|
|
||||||
USB Packet Protocol:
|
USB Packet Protocol (FT601, 35-byte):
|
||||||
TX (FPGA→Host):
|
TX (FPGA→Host):
|
||||||
Data packet: [0xAA] [range 4×32b] [doppler 4×32b] [det 1B] [0x55]
|
Data packet: [0xAA] [range 4×32b] [doppler 4×32b] [det 1B] [0x55]
|
||||||
Status packet: [0xBB] [status 6×32b] [0x55]
|
Status packet: [0xBB] [status 6×32b] [0x55]
|
||||||
RX (Host→FPGA):
|
RX (Host→FPGA):
|
||||||
Command word: {opcode[31:24], addr[23:16], value[15:0]}
|
Command word: {opcode[31:24], addr[23:16], value[15:0]}
|
||||||
|
|
||||||
|
USB Packet Protocol (FT2232H, 11-byte compact):
|
||||||
|
TX (FPGA→Host):
|
||||||
|
Data packet: [0xAA] [range_q 2B] [range_i 2B] [dop_re 2B] [dop_im 2B] [det 1B] [0x55]
|
||||||
|
Status packet: [0xBB] [status 6×32b] [0x55] (same 26-byte format)
|
||||||
|
RX (Host→FPGA):
|
||||||
|
Command: 4 bytes received sequentially {opcode, addr, value_hi, value_lo}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -38,6 +47,11 @@ HEADER_BYTE = 0xAA
|
|||||||
FOOTER_BYTE = 0x55
|
FOOTER_BYTE = 0x55
|
||||||
STATUS_HEADER_BYTE = 0xBB
|
STATUS_HEADER_BYTE = 0xBB
|
||||||
|
|
||||||
|
# Packet sizes
|
||||||
|
DATA_PACKET_SIZE_FT601 = 35 # FT601: 1 + 16 + 16 + 1 + 1
|
||||||
|
DATA_PACKET_SIZE_FT2232H = 11 # FT2232H: 1 + 4 + 2 + 2 + 1 + 1
|
||||||
|
STATUS_PACKET_SIZE = 26 # Same for both: 1 + 24 + 1
|
||||||
|
|
||||||
NUM_RANGE_BINS = 64
|
NUM_RANGE_BINS = 64
|
||||||
NUM_DOPPLER_BINS = 32
|
NUM_DOPPLER_BINS = 32
|
||||||
NUM_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS # 2048
|
NUM_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS # 2048
|
||||||
@@ -198,6 +212,43 @@ class RadarProtocol:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_data_packet_compact(raw: bytes) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Parse a compact 11-byte data packet from the FT2232H byte stream.
|
||||||
|
Returns dict with keys: 'range_i', 'range_q', 'doppler_i', 'doppler_q',
|
||||||
|
'detection', or None if invalid.
|
||||||
|
|
||||||
|
Compact packet format (FT2232H, 11 bytes):
|
||||||
|
Byte 0: 0xAA (header)
|
||||||
|
Bytes 1-2: range_q[15:0] MSB first
|
||||||
|
Bytes 3-4: 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)
|
||||||
|
"""
|
||||||
|
if len(raw) < DATA_PACKET_SIZE_FT2232H:
|
||||||
|
return None
|
||||||
|
if raw[0] != HEADER_BYTE:
|
||||||
|
return None
|
||||||
|
if raw[10] != FOOTER_BYTE:
|
||||||
|
return None
|
||||||
|
|
||||||
|
range_q = _to_signed16(struct.unpack_from(">H", raw, 1)[0])
|
||||||
|
range_i = _to_signed16(struct.unpack_from(">H", raw, 3)[0])
|
||||||
|
doppler_i = _to_signed16(struct.unpack_from(">H", raw, 5)[0])
|
||||||
|
doppler_q = _to_signed16(struct.unpack_from(">H", raw, 7)[0])
|
||||||
|
detection = raw[9] & 0x01
|
||||||
|
|
||||||
|
return {
|
||||||
|
"range_i": range_i,
|
||||||
|
"range_q": range_q,
|
||||||
|
"doppler_i": doppler_i,
|
||||||
|
"doppler_q": doppler_q,
|
||||||
|
"detection": detection,
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_status_packet(raw: bytes) -> Optional[StatusResponse]:
|
def parse_status_packet(raw: bytes) -> Optional[StatusResponse]:
|
||||||
"""
|
"""
|
||||||
@@ -241,25 +292,31 @@ class RadarProtocol:
|
|||||||
return sr
|
return sr
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_packet_boundaries(buf: bytes) -> List[Tuple[int, int, str]]:
|
def find_packet_boundaries(buf: bytes,
|
||||||
|
compact: bool = False) -> List[Tuple[int, int, str]]:
|
||||||
"""
|
"""
|
||||||
Scan buffer for packet start markers (0xAA data, 0xBB status).
|
Scan buffer for packet start markers (0xAA data, 0xBB status).
|
||||||
Returns list of (start_idx, expected_end_idx, packet_type).
|
Returns list of (start_idx, expected_end_idx, packet_type).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
buf: Raw byte buffer from USB read.
|
||||||
|
compact: If True, use 11-byte compact packets (FT2232H).
|
||||||
|
If False, use 35-byte packets (FT601, default).
|
||||||
"""
|
"""
|
||||||
|
data_size = DATA_PACKET_SIZE_FT2232H if compact else DATA_PACKET_SIZE_FT601
|
||||||
packets = []
|
packets = []
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(buf):
|
while i < len(buf):
|
||||||
if buf[i] == HEADER_BYTE:
|
if buf[i] == HEADER_BYTE:
|
||||||
# Data packet: 35 bytes (all streams)
|
end = i + data_size
|
||||||
end = i + 35
|
|
||||||
if end <= len(buf):
|
if end <= len(buf):
|
||||||
packets.append((i, end, "data"))
|
packets.append((i, end, "data"))
|
||||||
i = end
|
i = end
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
elif buf[i] == STATUS_HEADER_BYTE:
|
elif buf[i] == STATUS_HEADER_BYTE:
|
||||||
# Status packet: 26 bytes (6 words + header + footer)
|
# Status packet: 26 bytes (same for both interfaces)
|
||||||
end = i + 26
|
end = i + STATUS_PACKET_SIZE
|
||||||
if end <= len(buf):
|
if end <= len(buf):
|
||||||
packets.append((i, end, "status"))
|
packets.append((i, end, "status"))
|
||||||
i = end
|
i = end
|
||||||
@@ -415,6 +472,150 @@ class FT601Connection:
|
|||||||
return bytes(buf)
|
return bytes(buf)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# FT2232H USB 2.0 Connection (pyftdi, 245 Synchronous FIFO)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Optional pyftdi import
|
||||||
|
try:
|
||||||
|
from pyftdi.ftdi import Ftdi as PyFtdi
|
||||||
|
PYFTDI_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
PYFTDI_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
class FT2232HConnection:
|
||||||
|
"""
|
||||||
|
FT2232H USB 2.0 Hi-Speed FIFO bridge communication.
|
||||||
|
Uses pyftdi in 245 Synchronous FIFO mode (Channel A).
|
||||||
|
VID:PID = 0x0403:0x6010 (FTDI default for FT2232H).
|
||||||
|
"""
|
||||||
|
|
||||||
|
VID = 0x0403
|
||||||
|
PID = 0x6010
|
||||||
|
|
||||||
|
def __init__(self, mock: bool = True):
|
||||||
|
self._mock = mock
|
||||||
|
self._ftdi = None
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self.is_open = False
|
||||||
|
# Mock state
|
||||||
|
self._mock_frame_num = 0
|
||||||
|
self._mock_rng = np.random.RandomState(42)
|
||||||
|
|
||||||
|
def open(self, device_index: int = 0) -> bool:
|
||||||
|
if self._mock:
|
||||||
|
self.is_open = True
|
||||||
|
log.info("FT2232H mock device opened (no hardware)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not PYFTDI_AVAILABLE:
|
||||||
|
log.error("pyftdi not installed — cannot open real FT2232H device")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._ftdi = PyFtdi()
|
||||||
|
url = f"ftdi://0x{self.VID:04x}:0x{self.PID:04x}/{device_index + 1}"
|
||||||
|
self._ftdi.open_from_url(url)
|
||||||
|
# Configure for 245 Synchronous FIFO mode
|
||||||
|
self._ftdi.set_bitmode(0xFF, PyFtdi.BitMode.SYNCFF)
|
||||||
|
# Set USB transfer size for throughput
|
||||||
|
self._ftdi.read_data_set_chunksize(65536)
|
||||||
|
self._ftdi.write_data_set_chunksize(65536)
|
||||||
|
# Purge buffers
|
||||||
|
self._ftdi.purge_buffers()
|
||||||
|
self.is_open = True
|
||||||
|
log.info(f"FT2232H device opened: {url}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"FT2232H open failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._ftdi is not None:
|
||||||
|
try:
|
||||||
|
self._ftdi.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._ftdi = None
|
||||||
|
self.is_open = False
|
||||||
|
|
||||||
|
def read(self, size: int = 4096) -> Optional[bytes]:
|
||||||
|
"""Read raw bytes from FT2232H. Returns None on error/timeout."""
|
||||||
|
if not self.is_open:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self._mock:
|
||||||
|
return self._mock_read(size)
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
try:
|
||||||
|
data = self._ftdi.read_data(size)
|
||||||
|
return bytes(data) if data else None
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"FT2232H read error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def write(self, data: bytes) -> bool:
|
||||||
|
"""Write raw bytes to FT2232H (4-byte commands)."""
|
||||||
|
if not self.is_open:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._mock:
|
||||||
|
log.info(f"FT2232H mock write: {data.hex()}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
try:
|
||||||
|
written = self._ftdi.write_data(data)
|
||||||
|
return written == len(data)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"FT2232H write error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _mock_read(self, size: int) -> bytes:
|
||||||
|
"""
|
||||||
|
Generate synthetic compact radar data packets (11-byte) for testing.
|
||||||
|
Same target simulation as FT601 mock but using compact format.
|
||||||
|
"""
|
||||||
|
time.sleep(0.05) # Simulate USB latency
|
||||||
|
self._mock_frame_num += 1
|
||||||
|
|
||||||
|
buf = bytearray()
|
||||||
|
num_packets = min(32, size // DATA_PACKET_SIZE_FT2232H)
|
||||||
|
for _ in range(num_packets):
|
||||||
|
rbin = self._mock_rng.randint(0, NUM_RANGE_BINS)
|
||||||
|
dbin = self._mock_rng.randint(0, NUM_DOPPLER_BINS)
|
||||||
|
|
||||||
|
range_i = int(self._mock_rng.normal(0, 100))
|
||||||
|
range_q = int(self._mock_rng.normal(0, 100))
|
||||||
|
if abs(rbin - 20) < 3:
|
||||||
|
range_i += 5000
|
||||||
|
range_q += 3000
|
||||||
|
|
||||||
|
dop_i = int(self._mock_rng.normal(0, 50))
|
||||||
|
dop_q = int(self._mock_rng.normal(0, 50))
|
||||||
|
if abs(rbin - 20) < 3 and abs(dbin - 8) < 2:
|
||||||
|
dop_i += 8000
|
||||||
|
dop_q += 4000
|
||||||
|
|
||||||
|
detection = 1 if (abs(rbin - 20) < 2 and abs(dbin - 8) < 2) else 0
|
||||||
|
|
||||||
|
# Build compact 11-byte packet
|
||||||
|
pkt = bytearray()
|
||||||
|
pkt.append(HEADER_BYTE)
|
||||||
|
pkt += struct.pack(">h", np.clip(range_q, -32768, 32767))
|
||||||
|
pkt += struct.pack(">h", np.clip(range_i, -32768, 32767))
|
||||||
|
pkt += struct.pack(">h", np.clip(dop_i, -32768, 32767))
|
||||||
|
pkt += struct.pack(">h", np.clip(dop_q, -32768, 32767))
|
||||||
|
pkt.append(detection & 0x01)
|
||||||
|
pkt.append(FOOTER_BYTE)
|
||||||
|
|
||||||
|
buf += pkt
|
||||||
|
|
||||||
|
return bytes(buf)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Replay Connection — feed real .npy data through the dashboard
|
# Replay Connection — feed real .npy data through the dashboard
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -579,10 +780,11 @@ class ReplayConnection:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, npy_dir: str, use_mti: bool = True,
|
def __init__(self, npy_dir: str, use_mti: bool = True,
|
||||||
replay_fps: float = 5.0):
|
replay_fps: float = 5.0, compact: bool = False):
|
||||||
self._npy_dir = npy_dir
|
self._npy_dir = npy_dir
|
||||||
self._use_mti = use_mti
|
self._use_mti = use_mti
|
||||||
self._replay_fps = max(replay_fps, 0.1)
|
self._replay_fps = max(replay_fps, 0.1)
|
||||||
|
self._compact = compact # True = FT2232H 11-byte packets
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self.is_open = False
|
self.is_open = False
|
||||||
self._packets: bytes = b""
|
self._packets: bytes = b""
|
||||||
@@ -756,8 +958,9 @@ class ReplayConnection:
|
|||||||
det = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=bool)
|
det = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=bool)
|
||||||
|
|
||||||
det_count = int(det.sum())
|
det_count = int(det.sum())
|
||||||
log.info(f"Replay: rebuilt {NUM_CELLS} packets "
|
pkt_fmt = "compact" if self._compact else "FT601"
|
||||||
f"(MTI={'ON' if self._mti_enable else 'OFF'}, "
|
log.info(f"Replay: rebuilt {NUM_CELLS} packets ({pkt_fmt}, "
|
||||||
|
f"MTI={'ON' if self._mti_enable else 'OFF'}, "
|
||||||
f"DC_notch={self._dc_notch_width}, "
|
f"DC_notch={self._dc_notch_width}, "
|
||||||
f"CFAR={'ON' if self._cfar_enable else 'OFF'} "
|
f"CFAR={'ON' if self._cfar_enable else 'OFF'} "
|
||||||
f"G={self._cfar_guard} T={self._cfar_train} "
|
f"G={self._cfar_guard} T={self._cfar_train} "
|
||||||
@@ -767,8 +970,38 @@ class ReplayConnection:
|
|||||||
range_i = self._range_i_vec
|
range_i = self._range_i_vec
|
||||||
range_q = self._range_q_vec
|
range_q = self._range_q_vec
|
||||||
|
|
||||||
# Pre-allocate buffer (35 bytes per packet * 2048 cells)
|
if self._compact:
|
||||||
buf = bytearray(NUM_CELLS * 35)
|
return self._build_packets_compact(range_i, range_q, dop_i, dop_q, det)
|
||||||
|
else:
|
||||||
|
return self._build_packets_ft601(range_i, range_q, dop_i, dop_q, det)
|
||||||
|
|
||||||
|
def _build_packets_compact(self, range_i, range_q, dop_i, dop_q, det) -> bytes:
|
||||||
|
"""Build compact 11-byte packets for FT2232H interface."""
|
||||||
|
buf = bytearray(NUM_CELLS * DATA_PACKET_SIZE_FT2232H)
|
||||||
|
pos = 0
|
||||||
|
for rbin in range(NUM_RANGE_BINS):
|
||||||
|
ri = int(np.clip(range_i[rbin], -32768, 32767))
|
||||||
|
rq = int(np.clip(range_q[rbin], -32768, 32767))
|
||||||
|
rq_bytes = struct.pack(">h", rq)
|
||||||
|
ri_bytes = struct.pack(">h", ri)
|
||||||
|
for dbin in range(NUM_DOPPLER_BINS):
|
||||||
|
di = int(np.clip(dop_i[rbin, dbin], -32768, 32767))
|
||||||
|
dq = int(np.clip(dop_q[rbin, dbin], -32768, 32767))
|
||||||
|
d = 1 if det[rbin, dbin] else 0
|
||||||
|
|
||||||
|
buf[pos] = HEADER_BYTE; pos += 1
|
||||||
|
buf[pos:pos+2] = rq_bytes; pos += 2
|
||||||
|
buf[pos:pos+2] = ri_bytes; pos += 2
|
||||||
|
buf[pos:pos+2] = struct.pack(">h", di); pos += 2
|
||||||
|
buf[pos:pos+2] = struct.pack(">h", dq); pos += 2
|
||||||
|
buf[pos] = d; pos += 1
|
||||||
|
buf[pos] = FOOTER_BYTE; pos += 1
|
||||||
|
|
||||||
|
return bytes(buf)
|
||||||
|
|
||||||
|
def _build_packets_ft601(self, range_i, range_q, dop_i, dop_q, det) -> bytes:
|
||||||
|
"""Build 35-byte packets for FT601 interface."""
|
||||||
|
buf = bytearray(NUM_CELLS * DATA_PACKET_SIZE_FT601)
|
||||||
pos = 0
|
pos = 0
|
||||||
for rbin in range(NUM_RANGE_BINS):
|
for rbin in range(NUM_RANGE_BINS):
|
||||||
ri = int(np.clip(range_i[rbin], -32768, 32767)) & 0xFFFF
|
ri = int(np.clip(range_i[rbin], -32768, 32767)) & 0xFFFF
|
||||||
@@ -879,18 +1112,20 @@ class DataRecorder:
|
|||||||
|
|
||||||
class RadarAcquisition(threading.Thread):
|
class RadarAcquisition(threading.Thread):
|
||||||
"""
|
"""
|
||||||
Background thread: reads from FT601, parses packets, assembles frames,
|
Background thread: reads from USB (FT601 or FT2232H), parses packets,
|
||||||
and pushes complete frames to the display queue.
|
assembles frames, and pushes complete frames to the display queue.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, connection: FT601Connection, frame_queue: queue.Queue,
|
def __init__(self, connection, frame_queue: queue.Queue,
|
||||||
recorder: Optional[DataRecorder] = None,
|
recorder: Optional[DataRecorder] = None,
|
||||||
status_callback=None):
|
status_callback=None,
|
||||||
|
compact: bool = False):
|
||||||
super().__init__(daemon=True)
|
super().__init__(daemon=True)
|
||||||
self.conn = connection
|
self.conn = connection
|
||||||
self.frame_queue = frame_queue
|
self.frame_queue = frame_queue
|
||||||
self.recorder = recorder
|
self.recorder = recorder
|
||||||
self._status_callback = status_callback
|
self._status_callback = status_callback
|
||||||
|
self._compact = compact # True for FT2232H 11-byte packets
|
||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
self._frame = RadarFrame()
|
self._frame = RadarFrame()
|
||||||
self._sample_idx = 0
|
self._sample_idx = 0
|
||||||
@@ -900,17 +1135,23 @@ class RadarAcquisition(threading.Thread):
|
|||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
log.info("Acquisition thread started")
|
log.info(f"Acquisition thread started (compact={self._compact})")
|
||||||
while not self._stop_event.is_set():
|
while not self._stop_event.is_set():
|
||||||
raw = self.conn.read(4096)
|
raw = self.conn.read(4096)
|
||||||
if raw is None or len(raw) == 0:
|
if raw is None or len(raw) == 0:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
packets = RadarProtocol.find_packet_boundaries(raw)
|
packets = RadarProtocol.find_packet_boundaries(
|
||||||
|
raw, compact=self._compact)
|
||||||
for start, end, ptype in packets:
|
for start, end, ptype in packets:
|
||||||
if ptype == "data":
|
if ptype == "data":
|
||||||
parsed = RadarProtocol.parse_data_packet(raw[start:end])
|
if self._compact:
|
||||||
|
parsed = RadarProtocol.parse_data_packet_compact(
|
||||||
|
raw[start:end])
|
||||||
|
else:
|
||||||
|
parsed = RadarProtocol.parse_data_packet(
|
||||||
|
raw[start:end])
|
||||||
if parsed is not None:
|
if parsed is not None:
|
||||||
self._ingest_sample(parsed)
|
self._ingest_sample(parsed)
|
||||||
elif ptype == "status":
|
elif ptype == "status":
|
||||||
|
|||||||
Reference in New Issue
Block a user