fix: remove manual hw_server management — let Vivado handle it internally

External hw_server start via Popen was unreliable on Windows (Vivado
has its own hw_server lifecycle via open_hw_manager). Simplified to
just run Vivado TCL directly — tested working on Linux (6.9s).

Also bumped default check_env timeout from 120s to 180s for Windows
where Vivado cold-start can take significantly longer.

Removed: _port_open, socket import, manual hw_server Popen/kill logic.
This commit is contained in:
2026-06-11 17:09:21 +08:00
parent 954aed7c0d
commit 743703c79e
2 changed files with 7 additions and 71 deletions
+1 -1
View File
@@ -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": 120, "check_env": 180,
"flash_erase": 120, "flash_erase": 120,
"flash_program": 600, "flash_program": 600,
"bitstream": 60, "bitstream": 60,
+6 -70
View File
@@ -7,16 +7,14 @@ returning device names, IDCODEs, and PS/PL presence.
from __future__ import annotations from __future__ import annotations
import re import re
import socket
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, get_hw_server_path, build_tool_command from vitis_checker import get_vivado_path, build_tool_command
@dataclass @dataclass
@@ -68,17 +66,6 @@ _ARM_DAP_RE = re.compile(r"^arm_dap", re.IGNORECASE)
HW_SERVER_PORT = 3121 HW_SERVER_PORT = 3121
# ── Helpers ───────────────────────────────────────────────────────────
def _port_open(host: str = "localhost", port: int = HW_SERVER_PORT) -> bool:
"""Check if a TCP port is accepting connections."""
try:
with socket.create_connection((host, port), timeout=1):
return True
except OSError:
return False
def _idcode_to_hex(binary_str: str) -> str: def _idcode_to_hex(binary_str: str) -> str:
"""Convert Vivado's binary IDCODE to uppercase hex.""" """Convert Vivado's binary IDCODE to uppercase hex."""
try: try:
@@ -94,10 +81,9 @@ def check_zynq_jtag(
) -> ZynqCheckResult: ) -> ZynqCheckResult:
"""Check for Zynq devices on the JTAG chain via Vivado + hw_server. """Check for Zynq devices on the JTAG chain via Vivado + hw_server.
Lifecycle: Vivado manages hw_server internally — open_hw_manager auto-starts
1. If hw_server is already running (port 3121 open) → reuse it the hardware server and connect_hw_server connects to it. No manual
2. If not running → start it, kill it when done hw_server lifecycle needed.
3. Run Vivado batch TCL to enumerate JTAG devices
Args: Args:
config: Application configuration. config: Application configuration.
@@ -112,7 +98,7 @@ def check_zynq_jtag(
if callback: if callback:
callback("start", "Connecting to hw_server...") callback("start", "Connecting to hw_server...")
# ── 1. Find tools ────────────────────────────────────────── # ── 1. Find Vivado ──────────────────────────────────────────
vivado_path = get_vivado_path(xilinx_root=xilinx_root) vivado_path = get_vivado_path(xilinx_root=xilinx_root)
if not vivado_path: if not vivado_path:
return ZynqCheckResult( return ZynqCheckResult(
@@ -122,46 +108,7 @@ def check_zynq_jtag(
error="Vivado not found", error="Vivado not found",
) )
hw_server_path = get_hw_server_path(xilinx_root=xilinx_root) # ── 2. Run Vivado TCL (manages hw_server internally) ────────
hw_server_was_running = _port_open()
# ── 2. Start hw_server if needed ───────────────────────────
hw_proc: subprocess.Popen | None = None
if not hw_server_was_running:
if not hw_server_path:
return ZynqCheckResult(
devices=[], jtag_chain=[], is_present=False,
ps_detected=False, pl_detected=False,
hw_server_url=hw_server_url,
error="hw_server not found — install Vivado/Vitis",
)
try:
hw_proc = subprocess.Popen(
build_tool_command(hw_server_path),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
# Wait for port to become available
for _ in range(30): # 3s max
if _port_open():
break
time.sleep(0.1)
else:
return ZynqCheckResult(
devices=[], jtag_chain=[], is_present=False,
ps_detected=False, pl_detected=False,
hw_server_url=hw_server_url,
error="hw_server started but port 3121 not open",
)
except OSError as e:
return ZynqCheckResult(
devices=[], jtag_chain=[], is_present=False,
ps_detected=False, pl_detected=False,
hw_server_url=hw_server_url,
error=f"Failed to start hw_server: {e}",
)
# ── 3. Run Vivado TCL ──────────────────────────────────────
try: try:
tcl = _build_jtag_query_tcl() tcl = _build_jtag_query_tcl()
if callback: if callback:
@@ -216,17 +163,6 @@ def check_zynq_jtag(
hw_server_url=hw_server_url, hw_server_url=hw_server_url,
error=f"OS error: {e}", error=f"OS error: {e}",
) )
finally:
# ── 4. Kill hw_server only if WE started it ────────────
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
# ── TCL builder ──────────────────────────────────────────────────────── # ── TCL builder ────────────────────────────────────────────────────────