fix: start hw_server explicitly before Vivado JTAG scan
Previously the TCL script relied on Vivado's connect_hw_server to auto-start hw_server. On Windows this either never worked (hw_server never shows in Task Manager) or hung indefinitely during Vivado's slow batch-mode initialisation. Root cause: hw_server was never started. Changes: - vitis_checker.py: add 'hw_server' to TOOL_ALIASES + get_hw_server_path() - zynq_checker.py: start hw_server via Popen before Vivado, kill in finally - 2s sleep for port binding after start - timeout from config.step_timeouts.check_env (default bumped to 120s) - clean terminate/kill sequence in finally - config_manager.py: check_env default 30 → 120s
This commit is contained in:
@@ -35,7 +35,7 @@ DEFAULT_CONFIG: dict[str, Any] = {
|
|||||||
"inter_step_delay": 2,
|
"inter_step_delay": 2,
|
||||||
"boot_wait_delay": 10,
|
"boot_wait_delay": 10,
|
||||||
"step_timeouts": {
|
"step_timeouts": {
|
||||||
"check_env": 30,
|
"check_env": 120,
|
||||||
"flash_erase": 120,
|
"flash_erase": 120,
|
||||||
"flash_program": 600,
|
"flash_program": 600,
|
||||||
"bitstream": 60,
|
"bitstream": 60,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ TOOL_ALIASES: dict[str, list[str]] = {
|
|||||||
"xsct": ["xsct"],
|
"xsct": ["xsct"],
|
||||||
"program_flash": ["program_flash"],
|
"program_flash": ["program_flash"],
|
||||||
"bootgen": ["bootgen"],
|
"bootgen": ["bootgen"],
|
||||||
|
"hw_server": ["hw_server"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Product subdirectories under xilinx_root to scan (in preference order)
|
# Product subdirectories under xilinx_root to scan (in preference order)
|
||||||
@@ -330,6 +331,24 @@ def get_vivado_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
|
|||||||
return _find_executable("vivado", vitis_path, platform.system().lower(), xilinx_root)
|
return _find_executable("vivado", vitis_path, platform.system().lower(), xilinx_root)
|
||||||
|
|
||||||
|
|
||||||
|
def get_hw_server_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
|
||||||
|
"""Find the hw_server executable (Xilinx hardware server).
|
||||||
|
|
||||||
|
hw_server must be running before Vivado/xsct can connect to the
|
||||||
|
JTAG chain. This finder searches the same Vitis/Vivado bin dirs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vitis_path: Legacy Vitis installation directory.
|
||||||
|
xilinx_root: Xilinx root directory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to hw_server executable, or None.
|
||||||
|
"""
|
||||||
|
return _find_executable(
|
||||||
|
"hw_server", vitis_path, platform.system().lower(), xilinx_root
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ── Tool version detection ───────────────────────────────────────────
|
# ── Tool version detection ───────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+56
-37
@@ -12,12 +12,13 @@ import os
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
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
|
from vitis_checker import get_vivado_path, get_hw_server_path, build_tool_command
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -87,42 +88,60 @@ 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 via Xilinx root scan
|
xilinx_root = getattr(config, "xilinx_path", "")
|
||||||
vivado_path = get_vivado_path(
|
|
||||||
xilinx_root=getattr(config, "xilinx_path", ""),
|
# ── 1. Find and start hw_server ──────────────────────────
|
||||||
)
|
hw_server_path = get_hw_server_path(xilinx_root=xilinx_root)
|
||||||
if not vivado_path:
|
if not hw_server_path:
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=[],
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
jtag_chain=[],
|
ps_detected=False, pl_detected=False,
|
||||||
is_present=False,
|
|
||||||
ps_detected=False,
|
|
||||||
pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error="Vivado not found",
|
error="hw_server not found — install Vivado/Vitis hardware server",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create temporary TCL script
|
hw_proc: subprocess.Popen | None = None
|
||||||
tcl_content = _build_jtag_query_tcl()
|
|
||||||
tcl_path = Path(tempfile.gettempdir()) / f"zynq_jtag_check_{os.getpid()}.tcl"
|
|
||||||
try:
|
try:
|
||||||
|
# Start hw_server in the background (it daemonises own I/O)
|
||||||
|
hw_proc = subprocess.Popen(
|
||||||
|
build_tool_command(hw_server_path),
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
# Give hw_server a moment to bind its port
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# ── 2. Find vivado ────────────────────────────────────
|
||||||
|
vivado_path = get_vivado_path(xilinx_root=xilinx_root)
|
||||||
|
if not vivado_path:
|
||||||
|
return ZynqCheckResult(
|
||||||
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
|
ps_detected=False, pl_detected=False,
|
||||||
|
hw_server_url=hw_server_url,
|
||||||
|
error="Vivado not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 3. Create and run JTAG query TCL ──────────────────
|
||||||
|
tcl_content = _build_jtag_query_tcl()
|
||||||
|
tcl_path = Path(tempfile.gettempdir()) / f"zynq_jtag_check_{os.getpid()}.tcl"
|
||||||
tcl_path.write_text(tcl_content)
|
tcl_path.write_text(tcl_content)
|
||||||
|
|
||||||
if callback:
|
if callback:
|
||||||
callback("progress", "Scanning JTAG chain...")
|
callback("progress", "Scanning JTAG chain...")
|
||||||
|
|
||||||
# Run Vivado in batch mode (with environment setup)
|
cmd = build_tool_command(
|
||||||
cmd = build_tool_command(vivado_path, "-mode", "batch", "-source", str(tcl_path))
|
vivado_path, "-mode", "batch", "-source", str(tcl_path),
|
||||||
|
)
|
||||||
|
python_timeout = config.step_timeouts.get("check_env", 120)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=30,
|
timeout=python_timeout,
|
||||||
)
|
)
|
||||||
|
tcl_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
output = result.stdout + result.stderr
|
||||||
|
|
||||||
# Parse JTAG device information from output
|
|
||||||
jtag_chain = _parse_jtag_chain(output)
|
jtag_chain = _parse_jtag_chain(output)
|
||||||
devices = _filter_zynq_devices(jtag_chain)
|
devices = _filter_zynq_devices(jtag_chain)
|
||||||
|
|
||||||
@@ -135,42 +154,42 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
else:
|
else:
|
||||||
callback("error", "No Zynq device found on JTAG chain")
|
callback("error", "No Zynq device found on JTAG chain")
|
||||||
|
|
||||||
# Check PS and PL detection
|
|
||||||
ps_detected = any(d.is_arm_dap for d in jtag_chain)
|
ps_detected = any(d.is_arm_dap for d in jtag_chain)
|
||||||
pl_detected = any(d.is_pl_device for d in jtag_chain)
|
pl_detected = any(d.is_pl_device for d in jtag_chain)
|
||||||
|
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=devices,
|
devices=devices, jtag_chain=jtag_chain,
|
||||||
jtag_chain=jtag_chain,
|
|
||||||
is_present=len(devices) > 0,
|
is_present=len(devices) > 0,
|
||||||
ps_detected=ps_detected,
|
ps_detected=ps_detected, pl_detected=pl_detected,
|
||||||
pl_detected=pl_detected,
|
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error="" if devices else "No Zynq device found on JTAG chain",
|
error="" if devices else "No Zynq device found on JTAG chain",
|
||||||
)
|
)
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=[],
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
jtag_chain=[],
|
ps_detected=False, pl_detected=False,
|
||||||
is_present=False,
|
|
||||||
ps_detected=False,
|
|
||||||
pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error="JTAG scan timed out after 30s",
|
error=f"JTAG scan timed out after {config.step_timeouts.get('check_env', 120)}s",
|
||||||
)
|
)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=[],
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
jtag_chain=[],
|
ps_detected=False, pl_detected=False,
|
||||||
is_present=False,
|
|
||||||
ps_detected=False,
|
|
||||||
pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error=f"OS error: {e}",
|
error=f"OS error: {e}",
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
tcl_path.unlink(missing_ok=True)
|
# ── 4. Kill hw_server ─────────────────────────────────
|
||||||
|
if hw_proc is not None and hw_proc.poll() is None:
|
||||||
|
try:
|
||||||
|
hw_proc.terminate()
|
||||||
|
hw_proc.wait(timeout=3)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
hw_proc.kill()
|
||||||
|
hw_proc.wait()
|
||||||
|
except OSError:
|
||||||
|
pass # already dead
|
||||||
|
|
||||||
|
|
||||||
def _build_jtag_query_tcl() -> str:
|
def _build_jtag_query_tcl() -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user