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
+43 -15
View File
@@ -103,13 +103,17 @@ reg [7:0] signal_power_i, signal_power_q;
// Internal mixing signals
// DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 handles all internal pipelining
// Latency: 3 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG)
// Latency: 4 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG, 1 for post-DSP retiming)
wire signed [MIXER_WIDTH-1:0] adc_signed_w;
reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q;
reg mixed_valid;
reg mixer_overflow_i, mixer_overflow_q;
// Pipeline valid tracking: 3-stage shift register to match DSP48E1 AREG+MREG+PREG latency
reg [2:0] dsp_valid_pipe;
// Pipeline valid tracking: 4-stage shift register (3 for DSP48E1 + 1 for post-DSP retiming)
reg [3:0] dsp_valid_pipe;
// Post-DSP retiming registers breaks DSP48E1 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
reg signed [17:0] baseband_i_reg, baseband_q_reg;
@@ -219,12 +223,12 @@ nco_400m_enhanced nco_core (
assign adc_signed_w = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} -
{1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2;
// Valid pipeline: 3-stage shift register matching DSP48E1 AREG+MREG+PREG latency
// Valid pipeline: 4-stage shift register (3 for DSP48E1 AREG+MREG+PREG + 1 for retiming)
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
dsp_valid_pipe <= 3'b000;
dsp_valid_pipe <= 4'b0000;
end else begin
dsp_valid_pipe <= {dsp_valid_pipe[1:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)};
dsp_valid_pipe <= {dsp_valid_pipe[2:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)};
end
end
@@ -271,6 +275,17 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
end
end
// Stage 4: Post-DSP retiming register (matches synthesis path)
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mult_i_retimed <= 0;
mult_q_retimed <= 0;
end else begin
mult_i_retimed <= mult_i_reg;
mult_q_retimed <= mult_q_reg;
end
end
`else
// ---- Direct DSP48E1 instantiation for Vivado synthesis ----
// This guarantees AREG/BREG/MREG are used, achieving timing closure at 400 MHz
@@ -448,6 +463,19 @@ DSP48E1 #(
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_reg = dsp_p_i[MIXER_WIDTH+NCO_WIDTH-1:0];
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_q_reg = dsp_p_q[MIXER_WIDTH+NCO_WIDTH-1:0];
// Stage 4: Post-DSP retiming register breaks DSP48E1 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
// ============================================================================
@@ -464,7 +492,7 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
mixer_overflow_q <= 0;
saturation_count <= 0;
overflow_detected <= 0;
end else if (dsp_valid_pipe[2]) begin
end else if (dsp_valid_pipe[3]) begin
// Force saturation for testing (applied after DSP output, not on input path)
if (force_saturation_sync) begin
mixed_i <= 34'h1FFFFFFFF;
@@ -472,15 +500,15 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
mixer_overflow_i <= 1'b1;
mixer_overflow_q <= 1'b1;
end else begin
// Normal path: take DSP48E1 multiply result
mixed_i <= mult_i_reg;
mixed_q <= mult_q_reg;
// Normal path: take retimed DSP48E1 multiply result
mixed_i <= mult_i_retimed;
mixed_q <= mult_q_retimed;
// Overflow detection on current cycle's multiply result
mixer_overflow_i <= (mult_i_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_i_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
mixer_overflow_q <= (mult_q_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_q_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
// Overflow detection on retimed multiply result
mixer_overflow_i <= (mult_i_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_i_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
mixer_overflow_q <= (mult_q_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_q_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
end
mixed_valid <= 1;