{{ header_comment }}
#
puts "Setting up PCILeech build environment..."

{%- from "_helpers.j2" import safe_attr, safe_board_name, safe_board_fpga_part, get_vendor_id, get_device_id %}
{% set _vendor_id = get_vendor_id(device_config, device, config_space, vendor_id_int) %}
{% set _device_id = get_device_id(device_config, device, config_space, device_id_int) %}
{% if not _vendor_id or not _device_id %}
# WARNING: Missing vendor_id or device_id - Device identification incomplete
{% else %}
# Generated for: {{ _vendor_id }}:{{ _device_id }}
{% endif %}
# compute safe board values using helpers to tolerate string/None
{%- set _board_name = safe_board_name(board, '') -%}
{%- set _board_fpga = safe_board_fpga_part(board, '') -%}
{%- set _supports_msix = (supports_msix if supports_msix is defined else False) -%}
{% if not _board_name %}
# WARNING: Missing board name
{% else %}
# Board: {{ _board_name }}
{% endif %}
{% if not _board_fpga %}
# WARNING: Missing FPGA part information
{% else %}
# FPGA Part: {{ _board_fpga }}
{% endif %}
# Generation timestamp: [exec date]
#

#===============================================================================
# INITIALIZATION
#===============================================================================
puts "Applying PCILeech constraints..."

{# No default values - all pins must be specified in board configuration #}
{% set pcie_tx_p_pins = safe_attr(board, 'pcie_tx_p_pins', []) %}
{% set pcie_tx_n_pins = safe_attr(board, 'pcie_tx_n_pins', []) %}
{% set pcie_rx_p_pins = safe_attr(board, 'pcie_rx_p_pins', []) %}
{% set pcie_rx_n_pins = safe_attr(board, 'pcie_rx_n_pins', []) %}

# Allow board to override PCIe ref clock IOSTANDARDs with sensible defaults
{% set _clk_iostd_generic = safe_attr(board, 'pcie_clk_iostandard', '') %}
{% set _clk_iostd_7x = safe_attr(board, 'pcie_clk_iostandard_7x', _clk_iostd_generic if _clk_iostd_generic else 'DIFF_HSTL_I') %}
{% set _clk_iostd_us = safe_attr(board, 'pcie_clk_iostandard_ultrascale', _clk_iostd_generic if _clk_iostd_generic else 'DIFF_SSTL12') %}

#===============================================================================
# BOARD-SPECIFIC CONSTRAINTS
#===============================================================================
{# Include board-specific constraint file if available using safe board name #}
{% set _board_name_safe = _board_name %}
{% if _board_name_safe %}
set board_name "{{ _board_name_safe }}"
set constraint_file "${board_name}.xdc"

if {[file exists $constraint_file]} {
    add_files -fileset constrs_1 -norecurse $constraint_file
    puts "Added board constraint file: $constraint_file"
} else {
    puts "ERROR: Board constraint file not found: $constraint_file"
    puts "Required board-specific configuration for '${board_name}' is missing."
    puts "Please ensure the constraint file exists in the project directory."
    return
}
{% endif %}

#===============================================================================
# CLOCK CONSTRAINTS
#===============================================================================
{% set _pcie_clock_period = safe_attr(board, 'pcie_clock_period', '') %}
{% if not _pcie_clock_period %}
puts "ERROR: Missing required parameter: board.pcie_clock_period"
puts "PCIe clock period must be explicitly specified in board configuration"
return
{% else %}
# PCIe reference clock
create_clock -period {{ _pcie_clock_period }} -name pcie_clk [get_ports pcie_clk_p]
{% endif %}

# PCIe clock IO standards and pin assignments
{% set _pcie_ip_type = safe_attr(board, 'pcie_ip_type', '') %}
{% set _is_7x = (_pcie_ip_type == 'pcie_7x' or _pcie_ip_type == 'pcie7x') %}
{% set _is_us = (_pcie_ip_type == 'pcie_ultrascale' or _pcie_ip_type == 'ultrascale') %}
{% if not _pcie_ip_type %}
puts "ERROR: Missing required parameter: board.pcie_ip_type"
puts "PCIe IP type must be explicitly specified in board configuration"
return
{% elif _is_7x %}
# PCIe 7-series specific clock constraints
{% if not pcie_clk_p_pin %}
puts "ERROR: Missing required parameter: pcie_clk_p_pin for 7-series device"
return
{% else %}
set_property PACKAGE_PIN {{ pcie_clk_p_pin }} [get_ports pcie_clk_p]
{% endif %}
{% if not pcie_clk_n_pin %}
puts "ERROR: Missing required parameter: pcie_clk_n_pin for 7-series device"
return
{% else %}
set_property PACKAGE_PIN {{ pcie_clk_n_pin }} [get_ports pcie_clk_n]
{% endif %}
set_property IOSTANDARD {{ _clk_iostd_7x }} [get_ports pcie_clk_p]
set_property IOSTANDARD {{ _clk_iostd_7x }} [get_ports pcie_clk_n]
{% elif _is_us %}
# UltraScale+ specific clock constraints
{% if not pcie_clk_p_pin %}
puts "ERROR: Missing required parameter: pcie_clk_p_pin for UltraScale device"
return
{% else %}
set_property PACKAGE_PIN {{ pcie_clk_p_pin }} [get_ports pcie_clk_p]
{% endif %}
{% if not pcie_clk_n_pin %}
puts "ERROR: Missing required parameter: pcie_clk_n_pin for UltraScale device"
return
{% else %}
set_property PACKAGE_PIN {{ pcie_clk_n_pin }} [get_ports pcie_clk_n]
{% endif %}
set_property IOSTANDARD {{ _clk_iostd_us }} [get_ports pcie_clk_p]
set_property IOSTANDARD {{ _clk_iostd_us }} [get_ports pcie_clk_n]
{% else %}
# Error for unsupported FPGA family
puts "ERROR: Unsupported PCIe IP type: {{ _pcie_ip_type }}"
puts "Supported types are: pcie_7x|pcie7x, pcie_ultrascale|ultrascale"
return
{% endif %}

# Clock domain crossing
set_clock_groups -asynchronous -group [get_clocks pcie_clk] -group [get_clocks -include_generated_clocks clk_out1_clk_wiz_0]

#===============================================================================
# PCIe RESET CONSTRAINTS
#===============================================================================
{% if not pcie_rst_pin %}
puts "ERROR: Missing required parameter: pcie_rst_pin"
return
{% else %}
set_property PACKAGE_PIN {{ pcie_rst_pin }} [get_ports pcie_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports pcie_rst_n]
set_property PULLUP true [get_ports pcie_rst_n]
{% endif %}

#===============================================================================
# PCIe LANE CONSTRAINTS
#===============================================================================
{% if not max_lanes %}
puts "ERROR: Missing required parameter: max_lanes"
return
{% else %}
{% set num_lanes = max_lanes %}
{% endif %}

{% if num_lanes > 4 %}
puts "ERROR: Only up to 4 lanes supported in this template"
return
{% endif %}

{% if pcie_tx_p_pins|length < num_lanes or pcie_tx_n_pins|length < num_lanes or
      pcie_rx_p_pins|length < num_lanes or pcie_rx_n_pins|length < num_lanes %}
puts "ERROR: Insufficient pin definitions for {{ num_lanes }} PCIe lanes"
puts "Required pins must be defined in board configuration for all lanes"
return
{% else %}
{% for lane in range(num_lanes) %}
# Lane {{ lane }} pin assignments
set_property PACKAGE_PIN {{ pcie_tx_p_pins[lane] }} [get_ports {pcie_tx_p[{{ lane }}]}]
set_property PACKAGE_PIN {{ pcie_tx_n_pins[lane] }} [get_ports {pcie_tx_n[{{ lane }}]}]
set_property PACKAGE_PIN {{ pcie_rx_p_pins[lane] }} [get_ports {pcie_rx_p[{{ lane }}]}]
set_property PACKAGE_PIN {{ pcie_rx_n_pins[lane] }} [get_ports {pcie_rx_n[{{ lane }}]}]
{% endfor %}
{% endif %}

#===============================================================================
# CLOCK DOMAIN CROSSING CONSTRAINTS
#===============================================================================
# PCILeech BAR controller CDC constraints
set_false_path -from [get_clocks pcie_clk] -to [get_pins -hierarchical *bar_controller*/reg*/D]
set_false_path -from [get_pins -hierarchical *bar_controller*/reg*/C] -to [get_clocks pcie_clk]

# Configuration space shadow CDC constraints
set_false_path -from [get_clocks pcie_clk] -to [get_pins -hierarchical *cfg_shadow*/reg*/D]
set_false_path -from [get_pins -hierarchical *cfg_shadow*/reg*/C] -to [get_clocks pcie_clk]

# PCILeech FIFO timing constraints
{% set _fifo_max = safe_attr(board, 'fifo_max_delay', '') %}
{% set _fifo_min = safe_attr(board, 'fifo_min_delay', '') %}
{% if not _fifo_max or not _fifo_min %}
puts "ERROR: Missing required parameters: board.fifo_max_delay and/or board.fifo_min_delay"
puts "FIFO timing constraints must be specified in board configuration"
return
{% else %}
set_max_delay -from [get_pins -hierarchical *pcileech_fifo*/reg*/C] -to [get_pins -hierarchical *pcileech_fifo*/reg*/D] {{ _fifo_max }}
set_min_delay -from [get_pins -hierarchical *pcileech_fifo*/reg*/C] -to [get_pins -hierarchical *pcileech_fifo*/reg*/D] {{ _fifo_min }}
{% endif %}

#===============================================================================
# FEATURE-SPECIFIC CONSTRAINTS
#===============================================================================
# MSI-X table constraints (if supported)
{%- if _supports_msix %}
set_false_path -from [get_clocks pcie_clk] -to [get_pins -hierarchical *msix_table*/reg*/D]
set_false_path -from [get_pins -hierarchical *msix_table*/reg*/C] -to [get_clocks pcie_clk]
{%- endif %}

# Option ROM constraints (if present)
{%- set _has_option_rom = (device.has_option_rom if (device is defined and device is mapping or device is defined and device.has_option_rom is defined) else (device.get('has_option_rom', False) if device is mapping else False)) -%}
{%- if _has_option_rom %}
set_false_path -from [get_clocks pcie_clk] -to [get_pins -hierarchical *option_rom*/reg*/D]
set_false_path -from [get_pins -hierarchical *option_rom*/reg*/C] -to [get_clocks pcie_clk]
{% endif %}

#===============================================================================
# DEVICE-SPECIFIC CONSTRAINTS
#===============================================================================
# Device {{ _vendor_id }}:{{ _device_id }} specific constraints
{% if _vendor_id and _device_id %}
# Device {{ _vendor_id }}:{{ _device_id }} specific constraints
{% if device.class_code %}
{% if device.class_code == "0x020000" %}
# Network controller specific constraints
{% if not device.network_max_delay %}
puts "ERROR: Missing required parameter: device.network_max_delay for network controller"
return
{% else %}
set_max_delay -from [get_pins -hierarchical *network*/reg*/C] -to [get_pins -hierarchical *network*/reg*/D] {{ device.network_max_delay }}
{% endif %}
{% elif device.class_code == "0x040300" %}
# Audio controller specific constraints
{% if not device.audio_max_delay %}
puts "ERROR: Missing required parameter: device.audio_max_delay for audio controller"
return
{% else %}
set_max_delay -from [get_pins -hierarchical *audio*/reg*/C] -to [get_pins -hierarchical *audio*/reg*/D] {{ device.audio_max_delay }}
{% endif %}
{% elif device.class_code == "0x0c0330" %}
# USB controller specific constraints
{% if not device.usb_max_delay %}
puts "ERROR: Missing required parameter: device.usb_max_delay for USB controller"
return
{% else %}
set_max_delay -from [get_pins -hierarchical *usb*/reg*/C] -to [get_pins -hierarchical *usb*/reg*/D] {{ device.usb_max_delay }}
{% endif %}
{% elif device.class_code == "0x018000" %}
# Display controller specific constraints
{% if not device.display_max_delay %}
puts "ERROR: Missing required parameter: device.display_max_delay for display controller"
return
{% else %}
set_max_delay -from [get_pins -hierarchical *display*/reg*/C] -to [get_pins -hierarchical *display*/reg*/D] {{ device.display_max_delay }}
{% endif %}
{% else %}
# Generic device-specific constraints for class code: {{ device.class_code }}
puts "ERROR: No timing constraints defined for device class {{ device.class_code }}"
puts "Device-specific timing parameters must be added to configuration"
return
{% endif %}
{% else %}
puts "INFO: No device class code specified. Using default timing constraints."
{% endif %}
{% endif %}

#===============================================================================
# IMPLEMENTATION AND BITSTREAM CONSTRAINTS
#===============================================================================
# Implementation constraints
set_property CFGBVS VCCO [current_design]
{% set _config_voltage = safe_attr(board, 'config_voltage', '') %}
{% if not _config_voltage %}
puts "ERROR: Missing required parameter: board.config_voltage"
return
{% else %}
set_property CONFIG_VOLTAGE {{ _config_voltage }} [current_design]
{% endif %}

# Bitstream generation constraints
{% set _bit_unusedpin = safe_attr(board, 'bitstream_unusedpin', '') %}
{% set _bit_spi = safe_attr(board, 'bitstream_spi_buswidth', '') %}
{% set _bit_rate = safe_attr(board, 'bitstream_configrate', '') %}
{% if not _bit_unusedpin or not _bit_spi or not _bit_rate %}
puts "ERROR: Missing required bitstream parameters"
puts "Required: board.bitstream_unusedpin, board.bitstream_spi_buswidth, board.bitstream_configrate"
return
{% else %}
set_property BITSTREAM.CONFIG.UNUSEDPIN {{ _bit_unusedpin }} [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH {{ _bit_spi }} [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE {{ _bit_rate }} [current_design]
{% endif %}

# PCILeech-specific bitstream settings
{% if not _vendor_id or not _device_id %}
puts "ERROR: Missing required parameter: device.vendor_id or device.device_id"
puts "Both vendor ID and device ID must be specified for proper device identification"
return
{% else %}
set_property BITSTREAM.CONFIG.USERID "0x{{ _vendor_id }}{{ _device_id }}" [current_design]
{% endif %}

puts "PCILeech constraints applied successfully"