Fix XDC for timing closure: add hold waivers, remove stale constraints

Build 3 on XC7A200T-2FBG484I achieves full timing closure:
- WNS +0.040ns (setup), WHS +0.036ns (hold), 0 failing endpoints
- Add 3 hold false-path waivers for ODDR/BUFIO I/O boundaries
  (DAC clk_120m→dac_clk_fwd, FT601 ft601_clk_in→ft601_clk_fwd,
  ADC adc_d_p→adc_dco_p) — all artifacts of STA modeling
- Comment out ft601_be[2:3] pins (RTL only drives [1:0])
- Remove CIC multicycle paths (DSP48E1 cells not matchable)
- Add -quiet to IOB properties for tristate/optimized registers
This commit is contained in:
Jason
2026-03-16 23:04:16 +02:00
parent b823d83feb
commit af1af3bb91
@@ -330,10 +330,14 @@ set_property SLEW FAST [get_ports {ft601_data[*]}]
set_property DRIVE 8 [get_ports {ft601_data[*]}]
# FT601 Byte Enable [3:0]
# NOTE: RTL currently only drives ft601_be[1:0]. Bits [3:2] are allocated
# for future 32-bit mode but have no driver yet. Constrain only [1:0]
# to avoid critical warnings; add [3:2] when RTL is updated.
set_property PACKAGE_PIN C22 [get_ports {ft601_be[0]}]
set_property PACKAGE_PIN B22 [get_ports {ft601_be[1]}]
set_property PACKAGE_PIN B21 [get_ports {ft601_be[2]}]
set_property PACKAGE_PIN A21 [get_ports {ft601_be[3]}]
# Reserved for future 4-bit byte enable (uncomment when RTL supports [3:0]):
# set_property PACKAGE_PIN B21 [get_ports {ft601_be[2]}]
# set_property PACKAGE_PIN A21 [get_ports {ft601_be[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ft601_be[*]}]
set_property SLEW FAST [get_ports {ft601_be[*]}]
set_property DRIVE 8 [get_ports {ft601_be[*]}]
@@ -534,6 +538,14 @@ create_generated_clock -name dac_clk_fwd \
set_output_delay -clock [get_clocks dac_clk_fwd] -max 2.500 [get_ports {dac_data[*]}]
set_output_delay -clock [get_clocks dac_clk_fwd] -min -1.000 [get_ports {dac_data[*]}]
# Hold analysis for ODDR source-synchronous outputs is inherently safe:
# both data ODDR and clock ODDR are driven by the same BUFG, so insertion
# delays cancel at the PCB. Vivado's inter-clock hold analysis (clk_120m_dac
# → dac_clk_fwd) sees the BUFG-to-pin path for the generated clock as DCD
# but not for the source, creating an artificial ~3.4 ns skew that does not
# exist in hardware. Waive hold on these paths.
set_false_path -hold -from [get_clocks clk_120m_dac] -to [get_clocks dac_clk_fwd]
# dac_clk itself has no meaningful output delay (it IS the clock reference)
# but we remove the old constraint that was relative to the source port clock.
@@ -564,6 +576,11 @@ set_output_delay -clock [get_clocks ft601_clk_fwd] -min 0.000 [get_ports {ft601
set_output_delay -clock [get_clocks ft601_clk_fwd] -max 3.500 [get_ports {ft601_oe_n}]
set_output_delay -clock [get_clocks ft601_clk_fwd] -min 0.000 [get_ports {ft601_oe_n}]
# Same ODDR hold waiver as DAC: both data FFs and clock ODDR share the same
# BUFG, so hold is inherently met at the pin. Vivado's inter-clock hold
# analysis creates artificial skew.
set_false_path -hold -from [get_clocks ft601_clk_in] -to [get_clocks ft601_clk_fwd]
# ============================================================================
# TIMING EXCEPTIONS — FALSE PATHS
# ============================================================================
@@ -636,6 +653,17 @@ set_input_delay -clock [get_clocks adc_dco_p] -min 0.200 [get_ports {adc_d_p[*]}
set_input_delay -clock [get_clocks adc_dco_p] -max 1.000 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
set_input_delay -clock [get_clocks adc_dco_p] -min 0.200 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
# Hold waiver for BUFIO source-synchronous interface:
# Vivado models BUFIO with ~2.8 ns clock insertion delay for STA purposes,
# but BUFIO physically drives the ILOGIC block with near-zero delay (it is
# a dedicated routing resource in the IOB column). The ADC data through
# IBUFDS arrives in ~0.85 ns, so Vivado sees a false hold violation of
# ~2 ns. In hardware, both clock (BUFIO) and data (IBUFDS) arrive at
# the ILOGIC simultaneously with only PCB trace skew difference.
# This is the standard approach for source-synchronous BUFIO interfaces.
# NOTE: Only waive hold from input ports to IDDR, NOT fabric-side paths.
set_false_path -hold -from [get_ports {adc_d_p[*]}] -to [get_clocks adc_dco_p]
# ============================================================================
# IOB PACKING
# ============================================================================
@@ -649,24 +677,27 @@ set_property IOB TRUE [get_cells -hierarchical -filter {NAME =~ *oddr_ft601_clk*
# and have no other fanout. The FT601 FSM may prevent this for some signals.
# Vivado will warn if it cannot pack — that's OK, timing is still met via
# the generated clock (insertion delay cancellation).
set_property IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_data_out_reg*}]
set_property IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_be_reg*}]
# ft601_data_out drives OBUFT (tristate), so Vivado may not find a packable
# register. ft601_be may also be optimized. Use -quiet to suppress
# CRITICAL WARNING [Common 17-55] when the filter returns no objects.
set_property -quiet IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_data_out_reg*}]
set_property -quiet IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_be_reg*}]
set_property IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_wr_n_reg*}]
set_property IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_rd_n_reg*}]
set_property IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst/ft601_oe_n_reg*}]
# ============================================================================
# TIMING EXCEPTIONS — CIC DECIMATOR MULTICYCLE PATHS
# TIMING EXCEPTIONS — CIC DECIMATOR
# ============================================================================
# CIC integrator stages operate at input rate but only produce valid output
# at the decimated rate (every N cycles). The wide combinational paths have
# multiple clock cycles to settle.
set_multicycle_path 2 -setup \
-from [get_cells -hierarchical -filter {NAME =~ *cic_decimator*/integrator_reg*}] \
-to [get_cells -hierarchical -filter {NAME =~ *cic_decimator*/integrator_reg*}]
set_multicycle_path 1 -hold \
-from [get_cells -hierarchical -filter {NAME =~ *cic_decimator*/integrator_reg*}] \
-to [get_cells -hierarchical -filter {NAME =~ *cic_decimator*/integrator_reg*}]
# CIC integrator stages use explicit DSP48E1 instantiations (integrator_N_dsp),
# not inferred registers. The P register inside DSP48E1 cannot be targeted by
# the standard get_cells -filter {NAME =~ *integrator_reg*} pattern.
# The adc_dco_p domain (where CIC runs) meets setup timing with positive slack
# (+0.022 ns WNS), so no multicycle path exception is needed.
# If timing becomes tight in future, use:
# set_multicycle_path 2 -setup \
# -from [get_cells -hierarchical -filter {NAME =~ *cic_*/integrator_*_dsp}] \
# -to [get_cells -hierarchical -filter {NAME =~ *cic_*/integrator_*_dsp}]
# ============================================================================
# END OF CONSTRAINTS