{% from "_helpers.j2" import get_vendor_id, get_device_id, get_subsystem_vendor_id, get_subsystem_device_id %}
{{ header_comment }}

# Set the reference directory for source file relative paths
set origin_dir "."

# Use origin directory path location variable, if specified in the tcl shell
if { [info exists ::origin_dir_loc] } {
  set origin_dir $::origin_dir_loc
}

puts "Generating project"
puts "Origin directory: $origin_dir"
set_msg_config -id {Common 17-55} -suppress
set_msg_config -id {Vivado 12-1387} -suppress
# End of error handling configuration

# Project configuration
set project_name "{{ project_name }}"
set project_dir "{{ project_dir }}"
set fpga_part "{{ fpga_part }}"
set board_name "{{ board_name }}"

# PCILeech directory structure
set src_dir "{{ pcileech.src_dir }}"
set ip_dir "{{ pcileech.ip_dir }}"

# Create project directory if it doesn't exist
file mkdir $project_dir

# Close any existing project to avoid conflicts
catch {close_project -quiet}

# Create Vivado project
create_project $project_name $project_dir -part $fpga_part -force

# Set project properties - comprehensive configuration for robust builds
set obj [current_project]

# Core language and library settings
set_property -name "target_language" -value "Verilog" -objects $obj
set_property -name "simulator_language" -value "Mixed" -objects $obj
set_property -name "default_lib" -value "xil_defaultlib" -objects $obj

# XPM Libraries - CRITICAL: Required for all FPGA families (FIFO, CDC, Memory primitives)
set_property -name "xpm_libraries" -value "XPM_CDC XPM_FIFO XPM_MEMORY" -objects $obj

# IP cache and output management - prevents cache corruption and build errors
set_property -name "ip_cache_permissions" -value "read write" -objects $obj
set_property -name "ip_output_repo" -value "$project_dir/${project_name}.cache/ip" -objects $obj

# Simulation configuration - enables proper simulation file management
set_property -name "sim.central_dir" -value "$project_dir/${project_name}.ip_user_files" -objects $obj
set_property -name "sim.ip.auto_export_scripts" -value "1" -objects $obj

# Memory map generation - useful for debugging and verification
set_property -name "mem.enable_memory_map_generation" -value "1" -objects $obj

{% if board_part_id %}
# Board-specific configuration (if board part specified)
set_property -name "board_part" -value "{{ board_part_id }}" -objects $obj
set_property -name "platform.board_id" -value "{{ board_name }}" -objects $obj
{% endif %}

# Create source directory structure
file mkdir [file join $project_dir $src_dir]
file mkdir [file join $project_dir $ip_dir]

puts "Created PCILeech directory structure:"
puts "  Source directory: $src_dir"
puts "  IP directory: $ip_dir"

# Create filesets with existence checks (prevents conflicts)
if {[string equal [get_filesets -quiet sources_1] ""]} {
    create_fileset -srcset sources_1
    puts "Created sources_1 fileset"
}

if {[string equal [get_filesets -quiet constrs_1] ""]} {
    create_fileset -constrset constrs_1
    puts "Created constrs_1 fileset"
}

if {[string equal [get_filesets -quiet sim_1] ""]} {
    create_fileset -simset sim_1
    puts "Created sim_1 fileset"
}

# Add SystemVerilog source files
{% if pcileech.source_files %}
puts "Adding SystemVerilog source files..."
{% for source_file in pcileech.source_files %}
if {[file exists "{{ source_file }}"]} {
    add_files -norecurse "{{ source_file }}"
    puts "  Added: {{ source_file }}"
} else {
    puts "  Warning: Source file not found: {{ source_file }}"
}
{% endfor %}
# Ensure all *.sv files are treated as SystemVerilog
set sv_in_proj [get_files *.sv]
if {[llength $sv_in_proj] > 0} {
    puts "Setting file type=SystemVerilog for [llength $sv_in_proj] .sv files"
    set_property file_type SystemVerilog $sv_in_proj
}
{% else %}
# Add all SystemVerilog files from src directory
if {[file exists $src_dir]} {
    set sv_files [glob -nocomplain [file join $src_dir "*.sv"]]
    set v_files [glob -nocomplain [file join $src_dir "*.v"]]
    
    foreach sv_file $sv_files {
        add_files -norecurse $sv_file
        puts "  Added SystemVerilog: [file tail $sv_file]"
    }
    
    foreach v_file $v_files {
        add_files -norecurse $v_file
        puts "  Added Verilog: [file tail $v_file]"
    }

    # Ensure all *.sv files are treated as SystemVerilog
    set sv_in_proj [get_files -of_objects [get_filesets sources_1] *.sv]
    if {[llength $sv_in_proj] > 0} {
        puts "Setting file type=SystemVerilog for [llength $sv_in_proj] .sv files"
        set_property file_type SystemVerilog $sv_in_proj
        foreach sv_file $sv_in_proj {
            puts "  -> File type now: [get_property file_type [get_files $sv_file]] ($sv_file)"
        }
    }
    # Refresh compile order after changing file types
    update_compile_order -fileset sources_1
}
{% endif %}

{% from "_helpers.j2" import safe_attr %}
# Configure PCIe IP Core based on FPGA family
puts "Configuring PCIe IP Core for {{ pcie_ip_type }}..."

{# default supports flags when not provided #}
{%- set _supports_msi = False -%}
{%- if supports_msi is defined %}
    {%- set _supports_msi = supports_msi -%}
{%- elif pcileech is defined and (pcileech.get('supports_msi') is defined) %}
    {%- set _supports_msi = pcileech.get('supports_msi') -%}
{%- elif device is defined and (getattr(device, 'supports_msi', None) is not none) %}
    {%- set _supports_msi = getattr(device, 'supports_msi', False) -%}
{%- endif %}

{%- set _supports_msix = False -%}
{%- if supports_msix is defined %}
    {%- set _supports_msix = supports_msix -%}
{%- elif pcileech is defined and (pcileech.get('supports_msix') is defined) %}
    {%- set _supports_msix = pcileech.get('supports_msix') -%}
{%- elif device is defined and (getattr(device, 'supports_msix', None) is not none) %}
    {%- set _supports_msix = getattr(device, 'supports_msix', False) -%}
{%- endif %}

{%- if pcie_ip_type == "axi_pcie" %}
# Custom PCIe implementation for Artix-7 35T
# This part uses custom SystemVerilog modules instead of Xilinx IP cores

# Set device-specific parameters for custom PCIe implementation
{%- if not (device.device_id if device is defined else None) or not (device.vendor_id if device is defined else None) %}
{%- error "Device ID and Vendor ID are required - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not (device.revision_id if device is defined else None) %}
{%- error "Revision ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not (device.subsys_device_id if device is defined else None) %}
{%- error "Subsystem Device ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not (device.subsys_vendor_id if device is defined else None) %}
{%- error "Subsystem Vendor ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
set DEVICE_ID {{ get_device_id(device_config, device, config_space, device_id_int) }}
set VENDOR_ID {{ get_vendor_id(device_config, device, config_space, vendor_id_int) }}
set REVISION_ID {{ device.revision_id }}
set SUBSYSTEM_VENDOR_ID {{ get_subsystem_vendor_id(device_config, device, vendor_id_int) }}
set SUBSYSTEM_ID {{ get_subsystem_device_id(device_config, device, device_id_int) }}

puts "Using custom PCIe implementation for Artix-7 35T"
puts "Device ID: $DEVICE_ID"
puts "Vendor ID: $VENDOR_ID"
puts "Revision ID: $REVISION_ID"

# No IP cores required - PCIe functionality implemented in custom SystemVerilog modules

{% elif pcie_ip_type == "pcie_ultrascale" %}
# UltraScale PCIe IP
create_ip -name pcie4_uscale_plus -vendor xilinx.com -library ip -module_name pcie_ultrascale_bridge
{%- if not device.device_id or not device.vendor_id %}
{%- error "Device ID and Vendor ID are required - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.revision_id %}
{%- error "Revision ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.subsys_device_id %}
{%- error "Subsystem Device ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.subsys_vendor_id %}
{%- error "Subsystem Vendor ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.class_code %}
{%- error "Class Code must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
set_property -dict [list \
    CONFIG.PL_LINK_CAP_MAX_LINK_SPEED {{ target_link_speed }} \
    CONFIG.PL_LINK_CAP_MAX_LINK_WIDTH {{ target_link_width_enum }} \
    CONFIG.AXISTEN_IF_EXT_512_RQ_STRADDLE {false} \
    CONFIG.PF0_DEVICE_ID {{ get_device_id(device_config, device, config_space, device_id_int) }} \
    CONFIG.PF0_VENDOR_ID {{ get_vendor_id(device_config, device, config_space, vendor_id_int) }} \
    CONFIG.PF0_CLASS_CODE {{ device.class_code }} \
    CONFIG.PF0_REVISION_ID {{ device.revision_id }} \
    CONFIG.PF0_SUBSYSTEM_ID {{ get_subsystem_device_id(device_config, device, device_id_int) }} \
    CONFIG.PF0_SUBSYSTEM_VENDOR_ID {{ get_subsystem_vendor_id(device_config, device, vendor_id_int) }} \
    CONFIG.PF0_BAR0_ENABLED {true} \
    CONFIG.PF0_BAR0_64BIT {true} \
    CONFIG.PF0_BAR0_SIZE {1} \
    CONFIG.PF0_MSI_ENABLED {% if _supports_msi %}{true}{% else %}{false}{% endif %} \
    CONFIG.PF0_MSIX_ENABLED {% if _supports_msix %}{true}{% else %}{false}{% endif %} \
] [get_ips pcie_ultrascale_bridge]

{% else %}
# TEMPLATE FILE: /src/templates/tcl/pcileech_generate_project.j2
# 7-Series PCIe IP (default)
create_ip -name pcie_7x -vendor xilinx.com -library ip -module_name pcie_7x_bridge
{%- if not device.device_id or not device.vendor_id %}
{%- error "Device ID and Vendor ID are required - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.revision_id %}
{%- error "Revision ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.subsys_device_id %}
{%- error "Subsystem Device ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{%- if not device.subsys_vendor_id %}
{%- error "Subsystem Vendor ID must be derived from donor device - no fallback values allowed to prevent generic firmware" %}
{%- endif %}
{# Compute a valid 7-series lane width enum: Artix-7(X1/X2/X4), Kintex-7(+X8) #}
{% set _allow8 = (fpga_family == 'kintex7') %}
{% set _valid = [1,2,4,8] if _allow8 else [1,2,4] %}
{% set _lanes = max_lanes if max_lanes in _valid else (_valid|select('le', max_lanes)|list|max if (max_lanes is number) else 1) %}
{% set _lane_enum = 'X' ~ _lanes %}
set_property -dict [list \
    CONFIG.Link_Speed {{ target_link_speed }} \
    CONFIG.Maximum_Link_Width {{ target_link_width_enum }} \
    CONFIG.Device_ID {{ get_device_id(device_config, device, config_space, device_id_int) }} \
    CONFIG.Vendor_ID {{ get_vendor_id(device_config, device, config_space, vendor_id_int) }} \
    CONFIG.Revision_ID {{ device.revision_id }} \
    CONFIG.Class_Code_Base {02} \
    CONFIG.Class_Code_Interface {00} \
    CONFIG.Class_Code_Sub {00} \
    CONFIG.Subsystem_ID {{ get_subsystem_device_id(device_config, device, device_id_int) }} \
    CONFIG.Subsystem_Vendor_ID {{ get_subsystem_vendor_id(device_config, device, vendor_id_int) }} \
    CONFIG.Bar0_Enabled {true} \
    CONFIG.Bar0_Type {Memory} \
    CONFIG.Bar0_64bit {true} \
    CONFIG.Bar0_Size {1}{% if supports_msi %} \
    CONFIG.MSI_Enabled {true}{% endif %}{% if supports_msix %} \
    CONFIG.MSIx_Enabled {true}{% endif %} \
] [get_ips pcie_7x_bridge]
{% endif %}

# Add IP files to project
{% if pcileech.ip_files %}
puts "Adding IP core files..."
{% for ip_file in pcileech.ip_files %}
if {[file exists "{{ ip_file }}"]} {
    add_files -norecurse "{{ ip_file }}"
    puts "  Added IP: {{ ip_file }}"
} else {
    puts "  Warning: IP file not found: {{ ip_file }}"
}
{% endfor %}
{% endif %}

# Add coefficient files if present
{% if pcileech.coefficient_files %}
puts "Adding coefficient files..."
{% for coeff_file in pcileech.coefficient_files %}
if {[file exists "{{ coeff_file }}"]} {
    add_files -norecurse "{{ coeff_file }}"
    puts "  Added coefficient file: {{ coeff_file }}"
} else {
    puts "  Warning: Coefficient file not found: {{ coeff_file }}"
}
{% endfor %}
{% endif %}

# Add constraint files
puts "Adding constraint files..."
{% if constraint_files %}
{% for constraint_file in constraint_files %}
if {[file exists "{{ constraint_file }}"]} {
    add_files -fileset constrs_1 -norecurse "{{ constraint_file }}"
    puts "  Added constraint: {{ constraint_file }}"
} else {
    puts "  Warning: Constraint file not found: {{ constraint_file }}"
}
{% endfor %}
{% else %}
# Add all XDC files from current directory
set xdc_files [glob -nocomplain "*.xdc"]
foreach xdc_file $xdc_files {
    add_files -fileset constrs_1 -norecurse $xdc_file
    puts "  Added constraint: [file tail $xdc_file]"
}
{% endif %}

# Set top-level module
{% if pcileech.source_files and pcileech.source_files|length > 0 %}
# Try to determine top module from first source file
set top_module [file rootname [file tail "{{ pcileech.source_files[0] }}"]]
{% else %}
# Use PCILeech top-level wrapper as default
set top_module "pcileech_top"
{% endif %}

# Set source management mode to manual before setting top
set_property source_mgmt_mode None [current_project]
if {$top_module != ""} {
    set_property top $top_module [current_fileset]
    puts "Set top module: $top_module"
} else {
    puts "Top module will be auto-detected by Vivado"
}

# Update compile order
update_compile_order -fileset sources_1

# Generate IP cores
puts "Generating IP cores..."
generate_target all [get_files *.xci]
catch {config_ip_cache -export [get_ips -all]}

# Configure IP file properties for proper synthesis and checkpoint management
puts "Configuring IP file properties..."
set ip_files [get_files -filter {FILE_TYPE == "IP"}]
if {[llength $ip_files] > 0} {
    foreach ip_file $ip_files {
        # Prevent reference file generation (keeps project cleaner)
        set_property -name "generate_files_for_reference" -value "0" -objects $ip_file
        
        # Register with IP catalog manager for proper tracking
        set_property -name "registered_with_manager" -value "1" -objects $ip_file
        
        # Set synthesis checkpoint mode (only if IP is not locked)
        if { ![get_property "is_locked" $ip_file] } {
            set_property -name "synth_checkpoint_mode" -value "Singular" -objects $ip_file
        }
    }
    puts "  Configured [llength $ip_files] IP file(s)"
} else {
    puts "  No IP files found to configure"
}

# Skip validation (validate_design is not available in all Vivado versions)
puts "Skipping design validation..."
# validate_design -quiet

puts "PCILeech project generation completed successfully!"
puts "Project: $project_name"
puts "Location: $project_dir"
puts "FPGA Part: $fpga_part"
puts "PCIe IP Type: {{ pcie_ip_type }}"
{% if pcie_ip_type != 'pcie_ultrascale' %}
puts "Max Lanes (effective 7x): {{ _lanes }}"
{% else %}
puts "Max Lanes: {{ max_lanes }}"
{% endif %}
{% if supports_msi %}
puts "MSI Support: Enabled"
{% endif %}
{% if supports_msix %}
puts "MSI-X Support: Enabled"
{% endif %}

puts "Project setup completed. Ready for build phase."
