Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/finn/custom_op/fpgadataflow/rtl/finn_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from finn.custom_op.fpgadataflow.hwcustomop import HWCustomOp
from finn.custom_op.fpgadataflow.rtlbackend import RTLBackend
from finn.transformation.fpgadataflow.annotate_cycles import AnnotateCycles
from finn.util.basic import make_build_dir
from finn.util.basic import make_build_dir, resolve_xilinx_tool
from finn.util.create import adjacency_list
from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy
from finn.util.mlo_sim import mlo_prehook_func_factory
Expand Down Expand Up @@ -1086,10 +1086,11 @@ def ipgen_singlenode_code(self, fpgapart=None):
# create a shell script and call Vivado
make_project_sh = vivado_stitch_proj_dir + "/make_loop_ip.sh"
working_dir = os.environ["PWD"]
vivado_cmd = resolve_xilinx_tool("vivado")
with open(make_project_sh, "w") as f:
f.write("#!/bin/bash \n")
f.write("cd {}\n".format(vivado_stitch_proj_dir))
f.write("vivado -mode batch -source make_loop_ip.tcl\n")
f.write("{} -mode batch -source make_loop_ip.tcl\n".format(vivado_cmd))
f.write("cd {}\n".format(working_dir))
bash_command = ["bash", make_project_sh]
process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE)
Expand Down
12 changes: 8 additions & 4 deletions src/finn/transformation/fpgadataflow/alveo_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from finn.transformation.fpgadataflow.insert_iodma import InsertIODMA
from finn.transformation.fpgadataflow.prepare_ip import PrepareIP
from finn.transformation.fpgadataflow.specialize_layers import SpecializeLayers
from finn.util.basic import make_build_dir
from finn.util.basic import make_build_dir, resolve_xilinx_tool

from . import templates

Expand Down Expand Up @@ -182,10 +182,11 @@ def apply(self, model):
# create a shell script and call Vivado
package_xo_sh = vivado_proj_dir + "/gen_xo.sh"
working_dir = os.environ["PWD"]
vivado_cmd = resolve_xilinx_tool("vivado")
with open(package_xo_sh, "w") as f:
f.write("#!/bin/bash \n")
f.write("cd {}\n".format(vivado_proj_dir))
f.write("vivado -mode batch -source gen_xo.tcl\n")
f.write("{} -mode batch -source gen_xo.tcl\n".format(vivado_cmd))
f.write("cd {}\n".format(working_dir))
bash_command = ["bash", package_xo_sh]
process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE)
Expand Down Expand Up @@ -418,14 +419,16 @@ def apply(self, model):
# create a shell script and call Vitis
script = link_dir + "/run_vitis_link.sh"
working_dir = os.environ["PWD"]
vxx_cmd = resolve_xilinx_tool("v++")
with open(script, "w") as f:
f.write("#!/bin/bash \n")
f.write("cd {}\n".format(link_dir))
f.write(
"v++ -t hw --platform %s --link %s"
"%s -t hw --platform %s --link %s"
" --kernel_frequency %d --config config.txt --optimize %s"
" --save-temps -R2 %s\n"
% (
vxx_cmd,
self.vitis_platform,
" ".join(object_files),
self.f_mhz,
Expand All @@ -447,10 +450,11 @@ def apply(self, model):
# run Vivado to gen xml report
gen_rep_xml_sh = link_dir + "/gen_report_xml.sh"
working_dir = os.environ["PWD"]
vivado_cmd = resolve_xilinx_tool("vivado")
with open(gen_rep_xml_sh, "w") as f:
f.write("#!/bin/bash \n")
f.write("cd {}\n".format(link_dir))
f.write("vivado -mode batch -source %s\n" % (link_dir + "/gen_report_xml.tcl"))
f.write("%s -mode batch -source %s\n" % (vivado_cmd, link_dir + "/gen_report_xml.tcl"))
f.write("cd {}\n".format(working_dir))
bash_command = ["bash", gen_rep_xml_sh]
process_genxml = subprocess.Popen(bash_command, stdout=subprocess.PIPE)
Expand Down
5 changes: 3 additions & 2 deletions src/finn/transformation/fpgadataflow/create_stitched_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from finn.transformation.fpgadataflow.replace_verilog_relpaths import (
ReplaceVerilogRelPaths,
)
from finn.util.basic import make_build_dir
from finn.util.basic import make_build_dir, resolve_xilinx_tool
from finn.util.fpgadataflow import is_hls_node, is_rtl_node


Expand Down Expand Up @@ -745,10 +745,11 @@ def apply(self, model):
# create a shell script and call Vivado
make_project_sh = vivado_stitch_proj_dir + "/make_project.sh"
working_dir = os.environ["PWD"]
vivado_cmd = resolve_xilinx_tool("vivado")
with open(make_project_sh, "w") as f:
f.write("#!/bin/bash \n")
f.write("cd {}\n".format(vivado_stitch_proj_dir))
f.write("vivado -mode batch -source make_project.tcl\n")
f.write("{} -mode batch -source make_project.tcl\n".format(vivado_cmd))
f.write("cd {}\n".format(working_dir))
bash_command = ["bash", make_project_sh]
process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE)
Expand Down
10 changes: 8 additions & 2 deletions src/finn/transformation/fpgadataflow/make_zynq_proj.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@
from finn.transformation.fpgadataflow.insert_iodma import InsertIODMA
from finn.transformation.fpgadataflow.prepare_ip import PrepareIP
from finn.transformation.fpgadataflow.specialize_layers import SpecializeLayers
from finn.util.basic import make_build_dir, pynq_native_port_width, pynq_part_map
from finn.util.basic import (
make_build_dir,
pynq_native_port_width,
pynq_part_map,
resolve_xilinx_tool,
)

from . import templates

Expand Down Expand Up @@ -249,10 +254,11 @@ def apply(self, model):
# create a TCL recipe for the project
synth_project_sh = vivado_pynq_proj_dir + "/synth_project.sh"
working_dir = os.environ["PWD"]
vivado_cmd = resolve_xilinx_tool("vivado")
with open(synth_project_sh, "w") as f:
f.write("#!/bin/bash \n")
f.write("cd {}\n".format(vivado_pynq_proj_dir))
f.write("vivado -mode batch -source %s\n" % ipcfg)
f.write("%s -mode batch -source %s\n" % (vivado_cmd, ipcfg))
f.write("cd {}\n".format(working_dir))

# call the synthesis script
Expand Down
31 changes: 31 additions & 0 deletions src/finn/util/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,37 @@ def is_exe(fpath):
return None


_XILINX_TOOL_DIR_ENV = "FINN_TOOL_DIR_OVERRIDE"


def resolve_xilinx_tool(tool_name):
"""Return the Xilinx-tool command to embed in generated bash scripts. Update
the following list if new tools use this resolver.

Default names:
- vivado
- vitis_hls
- vitis-run
- v++

With FINN_TOOL_DIR_OVERRIDE set, the command resolves to
<override>/<tool_name>, otherwise the bare tool_name is used.
The single directory override is all a tool-wrapping site (e.g. an LSF
bsub dispatcher) needs: point it at a shim dir whose filenames match the
bare tool names. Raises FileNotFoundError when the resolved command is
not found, so all the default names must have a corresponding shim filename.
"""
dir_override = os.environ.get(_XILINX_TOOL_DIR_ENV)
tool = os.path.join(dir_override, tool_name) if dir_override else tool_name
if which(tool) is None:
if dir_override:
raise FileNotFoundError(
"%s not found (%s=%r)" % (tool, _XILINX_TOOL_DIR_ENV, dir_override)
)
raise FileNotFoundError("%s not found in PATH" % tool)
return tool


mem_primitives_versal = {
"URAM_72x4096": (72, 4096),
"URAM_36x8192": (36, 8192),
Expand Down
10 changes: 5 additions & 5 deletions src/finn/util/hls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import re
import subprocess

from finn.util.basic import which
from finn.util.basic import resolve_xilinx_tool


class CallHLS:
Expand Down Expand Up @@ -60,11 +60,11 @@ def build(self, code_gen_dir):
match = re.search(r"\b(20\d{2})\.(1|2)\b", vivado_path)
year, minor = int(match.group(1)), int(match.group(2))
if (year, minor) > (2024, 2):
assert which("vitis-run") is not None, "vitis-run not found in PATH"
vitis_cmd = "vitis-run --mode hls --tcl %s\n" % (self.tcl_script)
tool = resolve_xilinx_tool("vitis-run")
vitis_cmd = "%s --mode hls --tcl %s\n" % (tool, self.tcl_script)
else:
assert which("vitis_hls") is not None, "vitis_hls not found in PATH"
vitis_cmd = "vitis_hls %s\n" % (self.tcl_script)
tool = resolve_xilinx_tool("vitis_hls")
vitis_cmd = "%s %s\n" % (tool, self.tcl_script)
self.code_gen_dir = code_gen_dir
self.ipgen_script = str(self.code_gen_dir) + "/ipgen.sh"
working_dir = os.environ["PWD"]
Expand Down
59 changes: 59 additions & 0 deletions tests/util/test_resolve_xilinx_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
############################################################################
# Copyright (C) 2026, Advanced Micro Devices, Inc.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
############################################################################

import pytest

import os
import re
from pathlib import Path

import finn.util.basic as basic
from finn.util.basic import make_build_dir, robust_rmtree

# representative tool used for the resolution-order scenarios below.
SAMPLE_TOOL = "vivado"


def _make_executable(path):
"""Create a runnable stand-in for a Xilinx tool at ``path``."""
path = Path(path)
path.write_text("#!/bin/bash\n")
path.chmod(0o755)


@pytest.fixture
def tool_dir():
build_dir = make_build_dir(prefix="test_resolve_xilinx_tool_")
try:
yield build_dir
finally:
robust_rmtree(build_dir)


@pytest.mark.util
def test_resolve_no_override_returns_bare_name(tool_dir, monkeypatch):
monkeypatch.delenv(basic._XILINX_TOOL_DIR_ENV, raising=False)
_make_executable(os.path.join(tool_dir, SAMPLE_TOOL))
monkeypatch.setenv("PATH", tool_dir + os.pathsep + os.environ.get("PATH", ""))
assert basic.resolve_xilinx_tool(SAMPLE_TOOL) == SAMPLE_TOOL


@pytest.mark.util
def test_resolve_dir_override_joined_with_tool_name(tool_dir, monkeypatch):
tool_path = os.path.join(tool_dir, SAMPLE_TOOL)
_make_executable(tool_path)
monkeypatch.setenv(basic._XILINX_TOOL_DIR_ENV, tool_dir)
assert basic.resolve_xilinx_tool(SAMPLE_TOOL) == tool_path


@pytest.mark.util
def test_resolve_override_missing_raises_with_override_in_message(tool_dir, monkeypatch):
# override points at a directory with no matching shim, so resolution fails.
monkeypatch.setenv(basic._XILINX_TOOL_DIR_ENV, tool_dir)
with pytest.raises(FileNotFoundError, match=re.escape(basic._XILINX_TOOL_DIR_ENV)):
basic.resolve_xilinx_tool(SAMPLE_TOOL)
Loading