feat: initial Zynq XC7Z100 Flasher GUI project
Add cross-platform GUI application for programming Zynq devices with step-by-step workflow: - CustomTkinter GUI with dark/light theme support - 4-step workflow: Check Env → Flash → Bootloader → Main Program - YAML configuration with relative path resolution - Serial monitoring and boot log parsing - TFTP upload with CRC32 verification - SSH/serial reboot management - Vivado/Impact tool auto-detection Project structure: - app.py: Entry point - src/: Core modules (config, flash, bitstream, tftp, serial, gui) - config/: Default configuration template - .flasher_env/: Python virtual environment
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
"""Vitis/Vivado tool detection for Zynq Flasher GUI.
|
||||
|
||||
Checks for the presence and accessibility of required Xilinx tools
|
||||
(bootgen, impact, etc.) on the system.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from config_manager import Config
|
||||
|
||||
|
||||
@dataclass
|
||||
class ToolStatus:
|
||||
"""Status of a single Vitis/Vivado tool."""
|
||||
|
||||
name: str
|
||||
found: bool
|
||||
path: str = ""
|
||||
version: str = ""
|
||||
error: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class VitisCheckResult:
|
||||
"""Result of a complete Vitis/Vivado environment check."""
|
||||
|
||||
system: str
|
||||
vitis_path: str
|
||||
tools: dict[str, ToolStatus]
|
||||
is_ready: bool
|
||||
errors: list[str]
|
||||
|
||||
|
||||
def check_vitis(config: Config) -> VitisCheckResult:
|
||||
"""Check for Vitis/Vivado tools availability.
|
||||
|
||||
Scans the configured vitis_path and system PATH for required tools:
|
||||
bootgen (boot image generator), impact (JTAG controller).
|
||||
|
||||
Args:
|
||||
config: Application configuration with vitis_path setting.
|
||||
|
||||
Returns:
|
||||
VitisCheckResult with details on each tool's status.
|
||||
"""
|
||||
system = platform.system().lower()
|
||||
vitis_path = config.vitis_path or ""
|
||||
tools: dict[str, ToolStatus] = {}
|
||||
errors: list[str] = []
|
||||
|
||||
required_tools = ["bootgen", "impact"]
|
||||
|
||||
for tool_name in required_tools:
|
||||
status = _check_tool(tool_name, vitis_path, system)
|
||||
tools[tool_name] = status
|
||||
if not status.found:
|
||||
errors.append(f"{tool_name}: {status.error or 'not found'}")
|
||||
|
||||
return VitisCheckResult(
|
||||
system=system,
|
||||
vitis_path=vitis_path,
|
||||
tools=tools,
|
||||
is_ready=len(errors) == 0,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
def _check_tool(
|
||||
tool_name: str, vitis_path: str, system: str
|
||||
) -> ToolStatus:
|
||||
"""Check if a single tool is available.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool to check.
|
||||
vitis_path: Vitis installation path to search.
|
||||
system: Platform identifier (linux, windows, darwin).
|
||||
|
||||
Returns:
|
||||
ToolStatus with detection results.
|
||||
"""
|
||||
# Search in vitis_path/bin first, then system PATH
|
||||
search_dirs: list[str] = []
|
||||
if vitis_path:
|
||||
bin_dir = Path(vitis_path) / "bin"
|
||||
if bin_dir.exists():
|
||||
search_dirs.append(str(bin_dir))
|
||||
|
||||
# Add system PATH components
|
||||
path_env = os.environ.get("PATH", "")
|
||||
search_dirs.extend(path_env.split(os.pathsep))
|
||||
|
||||
exec_name = f"{tool_name}.exe" if system == "windows" else tool_name
|
||||
|
||||
for search_dir in search_dirs:
|
||||
candidate = Path(search_dir) / exec_name
|
||||
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,
|
||||
)
|
||||
|
||||
return ToolStatus(
|
||||
name=tool_name,
|
||||
found=False,
|
||||
error=f"Could not locate {exec_name} in PATH or {vitis_path}/bin",
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
result: VitisCheckResult from check_vitis().
|
||||
|
||||
Returns:
|
||||
Dictionary suitable for JSON serialization or GUI display.
|
||||
"""
|
||||
return {
|
||||
"system": result.system,
|
||||
"vitis_path": result.vitis_path,
|
||||
"is_ready": result.is_ready,
|
||||
"tools": {
|
||||
name: {
|
||||
"found": status.found,
|
||||
"path": status.path,
|
||||
"version": status.version,
|
||||
"error": status.error,
|
||||
}
|
||||
for name, status in result.tools.items()
|
||||
},
|
||||
"errors": result.errors,
|
||||
}
|
||||
Reference in New Issue
Block a user