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:
Jason
2026-04-07 19:22:16 +03:00
parent 3e737fb90e
commit 408f4d126f
9 changed files with 1119 additions and 136 deletions
@@ -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 (unusedno signal connections) # Bank 35: VCCO = 3.3V (FT2232H USB 2.0 FIFO15 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 (ADBUS0ADBUS7)
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
+43 -15
View File
@@ -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 CLKP 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 CLKP 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;
+99 -8
View File
@@ -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,13 +682,16 @@ 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)
// ============================================================================ // ============================================================================
generate
if (USB_MODE == 0) begin : gen_ft601
// ---- FT601 USB 3.0 (32-bit, 200T premium board) ----
usb_data_interface usb_inst ( usb_data_interface usb_inst (
.clk(clk_100m_buf), .clk(clk_100m_buf),
.reset_n(sys_reset_n), .reset_n(sys_reset_n),
.ft601_reset_n(sys_reset_ft601_n), // FT601-domain synchronized reset .ft601_reset_n(sys_reset_ft601_n),
// Radar data inputs // Radar data inputs
.range_profile(usb_range_profile), .range_profile(usb_range_profile),
@@ -700,17 +718,17 @@ usb_data_interface usb_inst (
.ft601_clk_out(ft601_clk_out), .ft601_clk_out(ft601_clk_out),
.ft601_clk_in(ft601_clk_buf), .ft601_clk_in(ft601_clk_buf),
// Host command outputs (Gap 4: USB Read Path) // Host command outputs
.cmd_data(usb_cmd_data), .cmd_data(usb_cmd_data),
.cmd_valid(usb_cmd_valid), .cmd_valid(usb_cmd_valid),
.cmd_opcode(usb_cmd_opcode), .cmd_opcode(usb_cmd_opcode),
.cmd_addr(usb_cmd_addr), .cmd_addr(usb_cmd_addr),
.cmd_value(usb_cmd_value), .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),
@@ -729,6 +747,79 @@ usb_data_interface usb_inst (
.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)
// ============================================================================ // ============================================================================
+35 -10
View File
@@ -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" \
] ]
+17 -3
View File
@@ -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
+1 -1
View File
@@ -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 (FPGAHost): 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 (FPGAHost): 26 bytes
* Byte 0: 0xBB (status header)
* Bytes 1-24: 6 × 32-bit status words, MSB first
* Byte 25: 0x55 (footer)
*
* Command (HostFPGA): 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
+261 -20
View File
@@ -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 (FPGAHost): TX (FPGAHost):
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 (HostFPGA): RX (HostFPGA):
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 (FPGAHost):
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 (HostFPGA):
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":