♻️ refactor(toolchain): replace impact/bootgen with xsct/program_flash + add fsbl
BREAKING: Replaces obsolete impact (ISE-era) and misused bootgen with the correct Xilinx 2018.3–2023.2 toolchain. Toolchain changes: - vitis_checker: impact → xsct + program_flash + bootgen detection - flash_programmer: bootgen/impact → program_flash (erase + program + verify) - bitstream_programmer: impact → xsct fpga/dow (primary), vivado (fallback) - Removed duplicated _get_impact_path() — now shared from vitis_checker Config additions: - fsbl_elf_path: FSBL ELF required by program_flash for QSPI init - flash_type: QSPI mode (default: qspi-x4-single) GUI additions: - FSBL ELF file selector in Configuration panel - Step 2 validates FSBL ELF is selected - Step 1 now shows xsct/program_flash/bootgen in tool check
This commit is contained in:
+142
-77
@@ -1,7 +1,11 @@
|
||||
"""Vitis/Vivado tool detection for Zynq Flasher GUI.
|
||||
|
||||
Checks for the presence and accessibility of required Xilinx tools
|
||||
(bootgen, impact, etc.) on the system.
|
||||
Checks for required Xilinx tools:
|
||||
- xsct (Xilinx Software Command Line Tool) — JTAG operations
|
||||
- program_flash — QSPI flash programming
|
||||
- bootgen — BOOT.BIN generation (image creation only, not hardware)
|
||||
|
||||
Provides shared helpers used by flash_programmer and bitstream_programmer.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -10,7 +14,7 @@ import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -26,7 +30,7 @@ class ToolStatus:
|
||||
path: str = ""
|
||||
version: str = ""
|
||||
error: str = ""
|
||||
alias: str = "" # Which alias was found (e.g. 'xsct' for 'impact')
|
||||
alias: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -40,30 +44,138 @@ class VitisCheckResult:
|
||||
errors: list[str]
|
||||
|
||||
|
||||
def check_vitis(config: Config) -> VitisCheckResult:
|
||||
"""Check for Vitis/Vivado tools availability.
|
||||
# ── Tool aliases: canonical name → [possible executable names] ──────
|
||||
TOOL_ALIASES: dict[str, list[str]] = {
|
||||
"xsct": ["xsct"],
|
||||
"program_flash": ["program_flash"],
|
||||
"bootgen": ["bootgen"],
|
||||
}
|
||||
|
||||
Scans the configured vitis_path and system PATH for required tools:
|
||||
bootgen (boot image generator), impact (JTAG controller).
|
||||
|
||||
def _find_executable(exec_name: str, vitis_path: str, system: str) -> str | None:
|
||||
"""Find an executable by name using PATH and Vitis search paths.
|
||||
|
||||
Args:
|
||||
config: Application configuration with vitis_path setting.
|
||||
exec_name: Executable name (without extension).
|
||||
vitis_path: Vitis/Vivado installation root directory.
|
||||
system: Platform identifier (linux, windows, darwin).
|
||||
|
||||
Returns:
|
||||
VitisCheckResult with details on each tool's status.
|
||||
Absolute path to the executable, or None.
|
||||
"""
|
||||
exec_suffix = ".exe" if system == "windows" else ""
|
||||
|
||||
# 1. shutil.which() — most reliable cross-platform PATH resolution
|
||||
found = shutil.which(f"{exec_name}{exec_suffix}")
|
||||
if found:
|
||||
return found
|
||||
|
||||
# 2. Scan vitis_path/bin
|
||||
if vitis_path:
|
||||
candidate = Path(vitis_path) / "bin" / f"{exec_name}{exec_suffix}"
|
||||
if candidate.is_file():
|
||||
return str(candidate)
|
||||
|
||||
# 3. Common Xilinx install paths
|
||||
common_paths = [
|
||||
f"/data/xilinx/Vitis/2023.2/bin/{exec_name}",
|
||||
f"/data/xilinx/Vivado/2023.2/bin/{exec_name}",
|
||||
f"/opt/xilinx/bin/{exec_name}",
|
||||
os.path.expanduser(f"~/Xilinx/Vitis/2023.2/bin/{exec_name}"),
|
||||
os.path.expanduser(f"~/Xilinx/Vivado/2023.2/bin/{exec_name}"),
|
||||
]
|
||||
for p in common_paths:
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_xsct_path(vitis_path: str = "") -> str | None:
|
||||
"""Find the xsct (Xilinx Software Command Line Tool) executable.
|
||||
|
||||
xsct is the primary JTAG scripting tool for Vivado/Vitis (2015.x+).
|
||||
|
||||
Args:
|
||||
vitis_path: Optional Vitis installation directory.
|
||||
|
||||
Returns:
|
||||
Absolute path to xsct, or None.
|
||||
"""
|
||||
return _find_executable("xsct", vitis_path, platform.system().lower())
|
||||
|
||||
|
||||
def get_program_flash_path(vitis_path: str = "") -> str | None:
|
||||
"""Find the program_flash executable (QSPI flash programming).
|
||||
|
||||
program_flash is part of Vitis/SDK (2014.x+).
|
||||
|
||||
Args:
|
||||
vitis_path: Optional Vitis installation directory.
|
||||
|
||||
Returns:
|
||||
Absolute path to program_flash, or None.
|
||||
"""
|
||||
return _find_executable("program_flash", vitis_path, platform.system().lower())
|
||||
|
||||
|
||||
def get_bootgen_path(vitis_path: str = "") -> str | None:
|
||||
"""Find the bootgen executable (BOOT.BIN generation).
|
||||
|
||||
bootgen creates boot images from BIF descriptions. It does NOT
|
||||
program flash — use program_flash for that.
|
||||
|
||||
Args:
|
||||
vitis_path: Optional Vitis installation directory.
|
||||
|
||||
Returns:
|
||||
Absolute path to bootgen, or None.
|
||||
"""
|
||||
return _find_executable("bootgen", vitis_path, platform.system().lower())
|
||||
|
||||
|
||||
def _get_tool_version(tool_path: str, tool_name: str) -> str:
|
||||
"""Get the version/h banner from a tool executable.
|
||||
|
||||
Args:
|
||||
tool_path: Absolute path to the tool.
|
||||
tool_name: Canonical tool name.
|
||||
|
||||
Returns:
|
||||
First line of help output (truncated to 120 chars), or empty string.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[tool_path, "-help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
if output:
|
||||
return output.split("\n")[0][:120]
|
||||
except (subprocess.TimeoutExpired, OSError):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def check_vitis(config: Config) -> VitisCheckResult:
|
||||
"""Check availability of all required Xilinx tools.
|
||||
|
||||
Scans PATH and Vitis paths for: xsct, program_flash, bootgen.
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
|
||||
Returns:
|
||||
VitisCheckResult with per-tool status.
|
||||
"""
|
||||
system = platform.system().lower()
|
||||
vitis_path = config.vitis_path or ""
|
||||
tools: dict[str, ToolStatus] = {}
|
||||
errors: list[str] = []
|
||||
|
||||
# Xilinx tool aliases: map tool names to their possible executables
|
||||
tool_aliases: dict[str, list[str]] = {
|
||||
"bootgen": ["bootgen"],
|
||||
"impact": ["impact", "xsct"], # xsct can replace impact in newer versions
|
||||
}
|
||||
|
||||
for tool_name, aliases in tool_aliases.items():
|
||||
for tool_name, aliases in TOOL_ALIASES.items():
|
||||
status = _check_tool(tool_name, aliases, vitis_path, system)
|
||||
tools[tool_name] = status
|
||||
if not status.found:
|
||||
@@ -79,53 +191,34 @@ def check_vitis(config: Config) -> VitisCheckResult:
|
||||
|
||||
|
||||
def _check_tool(
|
||||
tool_name: str, aliases: list[str], vitis_path: str, system: str
|
||||
tool_name: str,
|
||||
aliases: list[str],
|
||||
vitis_path: str,
|
||||
system: str,
|
||||
) -> ToolStatus:
|
||||
"""Check if a single tool is available.
|
||||
|
||||
Uses shutil.which() for reliable PATH resolution, then falls back
|
||||
to scanning vitis_path/bin for each alias.
|
||||
|
||||
Args:
|
||||
tool_name: Canonical name of the tool (e.g. 'impact').
|
||||
aliases: List of possible executable names (e.g. ['impact', 'xsct']).
|
||||
vitis_path: Vitis installation path to search.
|
||||
system: Platform identifier (linux, windows, darwin).
|
||||
tool_name: Canonical tool name.
|
||||
aliases: Executable names to try.
|
||||
vitis_path: Vitis installation path.
|
||||
system: Platform identifier.
|
||||
|
||||
Returns:
|
||||
ToolStatus with detection results.
|
||||
"""
|
||||
exec_suffix = ".exe" if system == "windows" else ""
|
||||
|
||||
# 1. Try shutil.which() for each alias — most reliable PATH resolution
|
||||
for alias in aliases:
|
||||
found_path = shutil.which(f"{alias}{exec_suffix}")
|
||||
if found_path:
|
||||
version = _get_tool_version(found_path, tool_name, system)
|
||||
found = _find_executable(alias, vitis_path, system)
|
||||
if found:
|
||||
version = _get_tool_version(found, tool_name)
|
||||
return ToolStatus(
|
||||
name=tool_name,
|
||||
found=True,
|
||||
path=found_path,
|
||||
path=found,
|
||||
version=version,
|
||||
alias=alias,
|
||||
)
|
||||
|
||||
# 2. Try vitis_path/bin for each alias
|
||||
if vitis_path:
|
||||
bin_dir = Path(vitis_path) / "bin"
|
||||
if bin_dir.exists():
|
||||
for alias in aliases:
|
||||
candidate = bin_dir / f"{alias}{exec_suffix}"
|
||||
if candidate.exists() and candidate.is_file():
|
||||
version = _get_tool_version(str(candidate), tool_name, system)
|
||||
return ToolStatus(
|
||||
name=tool_name,
|
||||
found=True,
|
||||
path=str(candidate),
|
||||
version=version,
|
||||
alias=alias,
|
||||
)
|
||||
|
||||
return ToolStatus(
|
||||
name=tool_name,
|
||||
found=False,
|
||||
@@ -137,34 +230,6 @@ def _check_tool(
|
||||
)
|
||||
|
||||
|
||||
def _get_tool_version(tool_path: str, tool_name: str, system: str) -> str:
|
||||
"""Get version string from a tool executable.
|
||||
|
||||
Args:
|
||||
tool_path: Absolute path to the tool executable.
|
||||
tool_name: Name of the tool.
|
||||
system: Platform identifier.
|
||||
|
||||
Returns:
|
||||
Version string or empty string if unavailable.
|
||||
"""
|
||||
try:
|
||||
flag = "-h" if tool_name == "impact" else "-h"
|
||||
result = subprocess.run(
|
||||
[tool_path, flag],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
if output:
|
||||
first_line = output.split("\n")[0]
|
||||
return first_line[:120]
|
||||
except (subprocess.TimeoutExpired, OSError, FileNotFoundError):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def get_tool_status_dict(result: VitisCheckResult) -> dict[str, Any]:
|
||||
"""Convert a VitisCheckResult to a serializable dictionary.
|
||||
|
||||
@@ -172,7 +237,7 @@ def get_tool_status_dict(result: VitisCheckResult) -> dict[str, Any]:
|
||||
result: VitisCheckResult from check_vitis().
|
||||
|
||||
Returns:
|
||||
Dictionary suitable for JSON serialization or GUI display.
|
||||
Dictionary suitable for JSON serialization / GUI display.
|
||||
"""
|
||||
return {
|
||||
"system": result.system,
|
||||
|
||||
Reference in New Issue
Block a user