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 from __future__ import annotations
import os import os
import shutil
import subprocess import subprocess
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Callable from typing import Callable
from config_manager import Config from config_manager import Config
from vitis_checker import get_xsct_path from vitis_checker import get_xsct_path, get_vivado_path
# ── Types ─────────────────────────────────────────────────────────── # ── Types ───────────────────────────────────────────────────────────
@@ -38,35 +37,6 @@ ProgressCallback = Callable[[str, str], None] | None
# ── Helpers ───────────────────────────────────────────────────────── # ── 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: def _find_ps7_init_tcl(config: Config) -> str:
"""Find the ps7_init.tcl file for PS initialization. """Find the ps7_init.tcl file for PS initialization.
@@ -167,16 +137,15 @@ def program_bitstream(
# Try xsct first (preferred) # Try xsct first (preferred)
xsct = get_xsct_path( xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=getattr(config, "xilinx_path", ""),
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
) )
if xsct: if xsct:
timeout = config.step_timeouts.get("bitstream", 60) timeout = config.step_timeouts.get("bitstream", 60)
return _program_with_xsct(xsct, bit_path, callback, ps7_tcl, timeout) return _program_with_xsct(xsct, bit_path, callback, ps7_tcl, timeout)
# Fallback to Vivado # Fallback to Vivado
vivado = _get_vivado_path( vivado = get_vivado_path(
config.vitis_path if hasattr(config, 'vitis_path') else "" xilinx_root=getattr(config, "xilinx_path", ""),
) )
if vivado: if vivado:
return _program_with_vivado(vivado, bit_path, callback) return _program_with_vivado(vivado, bit_path, callback)
@@ -364,8 +333,7 @@ def run_elf(
callback("start", f"Running ELF: {elf_path.name}...") callback("start", f"Running ELF: {elf_path.name}...")
xsct = get_xsct_path( xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=getattr(config, "xilinx_path", ""),
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
) )
if not xsct: if not xsct:
return BitstreamResult( return BitstreamResult(
@@ -579,8 +547,7 @@ def full_bitstream_program(
message=f"FSBL not found: {fsbl_path or '(not configured)'}")] message=f"FSBL not found: {fsbl_path or '(not configured)'}")]
xsct = get_xsct_path( xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=getattr(config, "xilinx_path", ""),
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
) )
if not xsct: if not xsct:
return [BitstreamResult(step="bitstream", success=False, return [BitstreamResult(step="bitstream", success=False,
+2 -4
View File
@@ -113,8 +113,7 @@ def wipe_flash(
FlashResult with status. FlashResult with status.
""" """
flash_tool = get_program_flash_path( flash_tool = get_program_flash_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=getattr(config, "xilinx_path", ""),
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
) )
if not flash_tool: if not flash_tool:
return FlashResult( return FlashResult(
@@ -229,8 +228,7 @@ def program_flash_bin(
) )
flash_tool = get_program_flash_path( flash_tool = get_program_flash_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=getattr(config, "xilinx_path", ""),
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
) )
if not flash_tool: if not flash_tool:
return FlashResult( 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 # Wait for board to boot — UART if available, else config delay
boot_delay = self._config.boot_wait_delay boot_delay = self._config.boot_wait_delay
uart_alive = (self._uart_monitor is not None # Check both main monitor and UART window monitor
and self._uart_monitor.is_running()) 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 = "" 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)...") self._log_message(f" ⏳ Waiting for UART boot signal (max {boot_delay}s)...")
elapsed = 0.0 elapsed = 0.0
while elapsed < boot_delay: while elapsed < boot_delay:
info = self._uart_monitor.latest_info info = _uart_src.latest_info if hasattr(_uart_src, 'latest_info') else _uart_src.get_latest_info()
if info.ip_address or info.version: if info and (info.ip_address or info.version):
self._log_message( self._log_message(
f" ✓ Boot detected after {elapsed:.1f}s" f" ✓ Boot detected after {elapsed:.1f}s"
f" (IP={info.ip_address}, Ver={info.version})" 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.""" """True if the UART monitor thread is currently running."""
return self._monitor is not None and self._monitor.is_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: def _on_close(self) -> None:
"""Cleanup UartMonitor and destroy window.""" """Cleanup UartMonitor and destroy window."""
if self._monitor: if self._monitor:
+1 -2
View File
@@ -186,8 +186,7 @@ def reboot_via_jtag(
from vitis_checker import get_xsct_path from vitis_checker import get_xsct_path
xsct = get_xsct_path( xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=getattr(config, "xilinx_path", ""),
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "",
) )
if not xsct: if not xsct:
return RebootResult( 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) 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 ─────────────────────────────────────────── # ── Tool version detection ───────────────────────────────────────────
+7 -35
View File
@@ -10,13 +10,13 @@ from __future__ import annotations
import os import os
import re import re
import shutil
import subprocess import subprocess
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Callable from typing import Callable
from config_manager import Config from config_manager import Config
from vitis_checker import get_vivado_path, build_tool_command
@dataclass @dataclass
@@ -86,9 +86,10 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
if callback: if callback:
callback("start", "Connecting to hw_server...") callback("start", "Connecting to hw_server...")
# Find vivado executable # Find vivado executable via Xilinx root scan
vivado_path = _get_vivado_path( vivado_path = get_vivado_path(
config.vitis_path if hasattr(config, 'vitis_path') else "" vitis_path=getattr(config, "vitis_path", ""),
xilinx_root=getattr(config, "xilinx_path", ""),
) )
if not vivado_path: if not vivado_path:
return ZynqCheckResult( return ZynqCheckResult(
@@ -110,8 +111,8 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
if callback: if callback:
callback("progress", "Scanning JTAG chain...") callback("progress", "Scanning JTAG chain...")
# Run Vivado in batch mode # Run Vivado in batch mode (with environment setup)
cmd = [vivado_path, "-mode", "batch", "-source", str(tcl_path)] cmd = build_tool_command(vivado_path, "-mode", "batch", "-source", str(tcl_path))
result = subprocess.run( result = subprocess.run(
cmd, cmd,
capture_output=True, 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: def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
"""Convert a ZynqCheckResult to a serializable dictionary. """Convert a ZynqCheckResult to a serializable dictionary.