Compare commits

..
3 Commits
Author SHA1 Message Date
yuysh 17c8f2d68b refactor: replace all config.vitis_path with config.xilinx_path across codebase
Affected files:
- flash_programmer.py (2 call sites) — get_program_flash_path
- bitstream_programmer.py (3 get_xsct_path + 1 get_vivado_path call sites)
  Also: removed duplicate _get_vivado_path() (now uses shared one from vitis_checker)
  Also: removed unused shutil import
- reboot_manager.py (1 call site) — get_xsct_path

All now use getattr(config, 'xilinx_path', '') consistently.
The only remaining 'config.vitis_path' reference is in vitis_checker.py's
check_vitis() itself (safe legacy fallback for old configs).
2026-06-11 15:33:54 +08:00
yuysh 61f0e6cd2b 🐛 fix: Step 1 JTAG check uses shared vivado path finder + build_tool_command
Root cause (2 bugs):
1. check_zynq_jtag() used config.vitis_path which no longer exists
   (config now uses xilinx_path) → always got empty path → 'Vivado not found'
2. _get_vivado_path() only checked hardcoded Linux paths, no version
   scanning, no .bat support on Windows

Fix:
- Added get_vivado_path() in vitis_checker.py (consistent with
  get_xsct_path, get_program_flash_path, etc.)
- Uses _find_executable() shared logic: scans Vivado/*/bin +
  Vitis/*/bin, picks newest version, supports .bat on Windows
- zynq_checker.py now uses get_vivado_path() + build_tool_command()
  for proper environment setup (settings64 sourcing on Linux)
- Removed obsolete _get_vivado_path() + unused shutil import
2026-06-11 15:30:43 +08:00
yuysh 6d686964e2 🐛 fix: Step 4 boot wait uses UART window monitor data, not just main monitor
Root cause: When UART window is open, self._uart_monitor is None
(the window creates its own UartMonitor). Step 4 boot wait loop
only checked self._uart_monitor.latest_info → always saw empty data
→ fell through to IP scanning even though UART had detected the IP.

Fix:
- UartMonitorWindow.get_latest_info() exposes parsed BootInfo
- Step 4 checks both: self._uart_monitor first, then UART window
- Null guard on info before accessing .ip_address
2026-06-11 15:21:36 +08:00
7 changed files with 48 additions and 85 deletions
+6 -39
View File
@@ -11,14 +11,13 @@ Compatible with Xilinx 2018.3 — 2023.2.
from __future__ import annotations
import os
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Callable
from config_manager import Config
from vitis_checker import get_xsct_path
from vitis_checker import get_xsct_path, get_vivado_path
# ── Types ───────────────────────────────────────────────────────────
@@ -38,35 +37,6 @@ ProgressCallback = Callable[[str, str], None] | None
# ── Helpers ─────────────────────────────────────────────────────────
def _get_vivado_path(vitis_path: str) -> str | None:
"""Find the Vivado executable.
Uses shutil.which() for PATH resolution, then common install paths.
Args:
vitis_path: Optional Vitis installation directory.
Returns:
Path to vivado executable, or None.
"""
found = shutil.which("vivado")
if found:
return found
if vitis_path:
candidate = Path(vitis_path) / "bin" / "vivado"
if candidate.is_file():
return str(candidate)
common = [
"/data/xilinx/Vivado/2023.2/bin/vivado",
"/opt/xilinx/bin/vivado",
os.path.expanduser("~/Xilinx/Vivado/2023.2/bin/vivado"),
]
for p in common:
if os.path.isfile(p):
return p
return None
def _find_ps7_init_tcl(config: Config) -> str:
"""Find the ps7_init.tcl file for PS initialization.
@@ -167,16 +137,15 @@ def program_bitstream(
# Try xsct first (preferred)
xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
xilinx_root=getattr(config, "xilinx_path", ""),
)
if xsct:
timeout = config.step_timeouts.get("bitstream", 60)
return _program_with_xsct(xsct, bit_path, callback, ps7_tcl, timeout)
# Fallback to Vivado
vivado = _get_vivado_path(
config.vitis_path if hasattr(config, 'vitis_path') else ""
vivado = get_vivado_path(
xilinx_root=getattr(config, "xilinx_path", ""),
)
if vivado:
return _program_with_vivado(vivado, bit_path, callback)
@@ -364,8 +333,7 @@ def run_elf(
callback("start", f"Running ELF: {elf_path.name}...")
xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
xilinx_root=getattr(config, "xilinx_path", ""),
)
if not xsct:
return BitstreamResult(
@@ -579,8 +547,7 @@ def full_bitstream_program(
message=f"FSBL not found: {fsbl_path or '(not configured)'}")]
xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
xilinx_root=getattr(config, "xilinx_path", ""),
)
if not xsct:
return [BitstreamResult(step="bitstream", success=False,
+2 -4
View File
@@ -113,8 +113,7 @@ def wipe_flash(
FlashResult with status.
"""
flash_tool = get_program_flash_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
xilinx_root=getattr(config, "xilinx_path", ""),
)
if not flash_tool:
return FlashResult(
@@ -229,8 +228,7 @@ def program_flash_bin(
)
flash_tool = get_program_flash_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
xilinx_root=getattr(config, "xilinx_path", ""),
)
if not flash_tool:
return FlashResult(
+13 -5
View File
@@ -1144,15 +1144,23 @@ class MainWindow(ctk.CTk):
# Wait for board to boot — UART if available, else config delay
boot_delay = self._config.boot_wait_delay
uart_alive = (self._uart_monitor is not None
and self._uart_monitor.is_running())
# Check both main monitor and UART window monitor
uart_alive = False
_uart_src = None # source of latest_info
if self._uart_monitor and self._uart_monitor.is_running():
uart_alive = True
_uart_src = self._uart_monitor
elif (self._uart_window and self._uart_window.winfo_exists()
and self._uart_window.is_monitoring):
uart_alive = True
_uart_src = self._uart_window
discovered_ip: str = ""
if uart_alive:
if uart_alive and _uart_src:
self._log_message(f" ⏳ Waiting for UART boot signal (max {boot_delay}s)...")
elapsed = 0.0
while elapsed < boot_delay:
info = self._uart_monitor.latest_info
if info.ip_address or info.version:
info = _uart_src.latest_info if hasattr(_uart_src, 'latest_info') else _uart_src.get_latest_info()
if info and (info.ip_address or info.version):
self._log_message(
f" ✓ Boot detected after {elapsed:.1f}s"
f" (IP={info.ip_address}, Ver={info.version})"
+6
View File
@@ -1190,6 +1190,12 @@ class UartMonitorWindow(ctk.CTkToplevel):
"""True if the UART monitor thread is currently running."""
return self._monitor is not None and self._monitor.is_running()
def get_latest_info(self):
"""Return latest parsed BootInfo from the UART monitor, or None."""
if self._monitor:
return self._monitor.latest_info
return None
def _on_close(self) -> None:
"""Cleanup UartMonitor and destroy window."""
if self._monitor:
+1 -2
View File
@@ -186,8 +186,7 @@ def reboot_via_jtag(
from vitis_checker import get_xsct_path
xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
xilinx_root=getattr(config, "xilinx_path", ""),
)
if not xsct:
return RebootResult(
+13
View File
@@ -317,6 +317,19 @@ def get_bootgen_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
return _find_executable("bootgen", vitis_path, platform.system().lower(), xilinx_root)
def get_vivado_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
"""Find the Vivado executable (JTAG operations via hw_server).
Args:
vitis_path: Legacy Vitis installation directory.
xilinx_root: Xilinx root directory.
Returns:
Path to vivado executable, or None.
"""
return _find_executable("vivado", vitis_path, platform.system().lower(), xilinx_root)
# ── Tool version detection ───────────────────────────────────────────
+7 -35
View File
@@ -10,13 +10,13 @@ from __future__ import annotations
import os
import re
import shutil
import subprocess
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable
from config_manager import Config
from vitis_checker import get_vivado_path, build_tool_command
@dataclass
@@ -86,9 +86,10 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
if callback:
callback("start", "Connecting to hw_server...")
# Find vivado executable
vivado_path = _get_vivado_path(
config.vitis_path if hasattr(config, 'vitis_path') else ""
# Find vivado executable via Xilinx root scan
vivado_path = get_vivado_path(
vitis_path=getattr(config, "vitis_path", ""),
xilinx_root=getattr(config, "xilinx_path", ""),
)
if not vivado_path:
return ZynqCheckResult(
@@ -110,8 +111,8 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
if callback:
callback("progress", "Scanning JTAG chain...")
# Run Vivado in batch mode
cmd = [vivado_path, "-mode", "batch", "-source", str(tcl_path)]
# Run Vivado in batch mode (with environment setup)
cmd = build_tool_command(vivado_path, "-mode", "batch", "-source", str(tcl_path))
result = subprocess.run(
cmd,
capture_output=True,
@@ -322,35 +323,6 @@ def _parse_zynq_device(device_name: str) -> ZynqDevice | None:
)
def _get_vivado_path(vitis_path: str) -> str | None:
"""Find the Vivado executable path.
Args:
vitis_path: Vitis installation path.
Returns:
Path to Vivado executable, or None if not found.
"""
path = shutil.which("vivado")
if path:
return path
if vitis_path:
candidate = Path(vitis_path) / "bin" / "vivado"
if candidate.exists():
return str(candidate)
candidate = Path(vitis_path) / "bin" / "vivado.exe"
if candidate.exists():
return str(candidate)
common_paths = [
"/opt/xilinx/bin/vivado",
os.path.expanduser("~/Xilinx/Vivado/bin/vivado"),
]
for p in common_paths:
if os.path.isfile(p):
return p
return None
def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
"""Convert a ZynqCheckResult to a serializable dictionary.