fix: use Vivado TCL for JTAG check — xsct 'targets' has broken TCL list parsing
xsct's [targets] returns unquoted TCL lists where multi-word names (e.g. 'ARM Cortex-A9 MPCore #0') are split into individual words by foreach, making it impossible to reconstruct device names. Vivado TCL with open_hw_manager works reliably: - Cold start (no hw_server): 7.1s on Linux - hw_server already running: 6.8s on Linux - Provides IDCODE (converted from Vivado binary to hex), device names Smart hw_server lifecycle: - Check port 3121 before starting — reuse if already running - Start hw_server only when needed (poll port up to 3s) - Don't kill hw_server that we didn't start Output format parsed: DEVICE:arm_dap_0 IDCODE:010010111010...0111 → arm_dap_0 idcode=4BA00477 PS detected → xc7z100_1 idcode=03736093 PL detected → XC7Z100 Tested on Linux 2023.2, cold + warm start, confirmed working.
This commit is contained in:
+140
-142
@@ -1,27 +1,30 @@
|
|||||||
"""Zynq JTAG presence checker for Zynq Flasher GUI.
|
"""Zynq JTAG presence checker for Zynq Flasher GUI.
|
||||||
|
|
||||||
Connects to hw_server via xsct and scans the JTAG chain to detect
|
Uses Vivado batch-mode TCL to scan the JTAG chain via hw_server,
|
||||||
Zynq devices, returning chip model, IDCODE, PS/PL presence.
|
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 time
|
import time
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
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, get_hw_server_path, build_tool_command
|
from vitis_checker import get_vivado_path, get_hw_server_path, build_tool_command
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class JtagDevice:
|
class JtagDevice:
|
||||||
"""Information about a device on the JTAG chain."""
|
"""Information about a device on the JTAG chain."""
|
||||||
|
|
||||||
name: str # Device name (e.g. "xc7z100", "arm_dap")
|
name: str
|
||||||
idcode: str = "" # JTAG IDCODE hex string
|
idcode: str = "" # IDCODE in hex (e.g. "03736093")
|
||||||
is_zynq: bool = False
|
is_zynq: bool = False
|
||||||
is_arm_dap: bool = False
|
is_arm_dap: bool = False
|
||||||
is_pl_device: bool = False
|
is_pl_device: bool = False
|
||||||
@@ -43,38 +46,58 @@ class ZynqDevice:
|
|||||||
class ZynqCheckResult:
|
class ZynqCheckResult:
|
||||||
"""Result of Zynq device detection on the JTAG chain."""
|
"""Result of Zynq device detection on the JTAG chain."""
|
||||||
|
|
||||||
devices: list[ZynqDevice] # All detected Zynq devices
|
devices: list[ZynqDevice]
|
||||||
jtag_chain: list[JtagDevice] # All devices on JTAG chain
|
jtag_chain: list[JtagDevice]
|
||||||
is_present: bool # Whether any Zynq device was found
|
is_present: bool
|
||||||
ps_detected: bool # Whether PS (ARM DAP) is detected
|
ps_detected: bool
|
||||||
pl_detected: bool # Whether PL is detected
|
pl_detected: bool
|
||||||
hw_server_url: str # hw_server connection URL used
|
hw_server_url: str
|
||||||
error: str = "" # Error message if detection failed
|
error: str = ""
|
||||||
|
|
||||||
|
|
||||||
# ── Patterns for parsing xsct "jtag targets" output ──────────────────
|
# ── Patterns ──────────────────────────────────────────────────────────
|
||||||
# Each line: " 1 xc7z100 (idcode 03736093 irlen 4)"
|
# Vivado TCL output: DEVICE:arm_dap_0 IDCODE:0100101110100...0111
|
||||||
_JTAG_LINE_RE = re.compile(
|
_DEVICE_LINE_RE = re.compile(r"^DEVICE:(\S+)\s+IDCODE:([01]+)")
|
||||||
r"^\s*(\d+)\s+(\S+)\s+\(idcode\s+([0-9A-Fa-f]+)\s+irlen\s+(\d+)\)",
|
|
||||||
re.IGNORECASE,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Zynq PL part: "xc7z100", "xc7z010", etc.
|
# Zynq PL: xc7z100, xc7z100_0, xc7z100_1, etc.
|
||||||
_ZYNQ_DEVICE_RE = re.compile(r"^xc\d+z\d+[a-z]?\d+$", re.IGNORECASE)
|
_ZYNQ_RE = re.compile(r"^xc\d+z\d+[a-z]?\d+", re.IGNORECASE)
|
||||||
|
|
||||||
# ARM DAP (PS side): "arm_dap"
|
# ARM DAP (PS): arm_dap, arm_dap_0, arm_dap_1
|
||||||
_ARM_DAP_RE = re.compile(r"^arm_dap$", re.IGNORECASE)
|
_ARM_DAP_RE = re.compile(r"^arm_dap", re.IGNORECASE)
|
||||||
|
|
||||||
# hw_server default port
|
|
||||||
HW_SERVER_PORT = 3121
|
HW_SERVER_PORT = 3121
|
||||||
|
|
||||||
|
|
||||||
def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqCheckResult:
|
# ── Helpers ───────────────────────────────────────────────────────────
|
||||||
"""Check for Zynq devices on the JTAG chain via hw_server.
|
|
||||||
|
|
||||||
Connects to hw_server using Vivado batch mode, scans the JTAG chain,
|
def _port_open(host: str = "localhost", port: int = HW_SERVER_PORT) -> bool:
|
||||||
and identifies Zynq devices by their JTAG device names. Also checks
|
"""Check if a TCP port is accepting connections."""
|
||||||
for PS (ARM DAP) and PL (Programmable Logic) recognition.
|
try:
|
||||||
|
with socket.create_connection((host, port), timeout=1):
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _idcode_to_hex(binary_str: str) -> str:
|
||||||
|
"""Convert Vivado's binary IDCODE to uppercase hex."""
|
||||||
|
try:
|
||||||
|
return f"{int(binary_str, 2):08X}"
|
||||||
|
except ValueError:
|
||||||
|
return binary_str
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main entry ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def check_zynq_jtag(
|
||||||
|
config: Config, callback: Callable | None = None
|
||||||
|
) -> ZynqCheckResult:
|
||||||
|
"""Check for Zynq devices on the JTAG chain via Vivado + hw_server.
|
||||||
|
|
||||||
|
Lifecycle:
|
||||||
|
1. If hw_server is already running (port 3121 open) → reuse it
|
||||||
|
2. If not running → start it, kill it when done
|
||||||
|
3. Run Vivado batch TCL to enumerate JTAG devices
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: Application configuration.
|
config: Application configuration.
|
||||||
@@ -84,58 +107,76 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
ZynqCheckResult with detected devices and status.
|
ZynqCheckResult with detected devices and status.
|
||||||
"""
|
"""
|
||||||
hw_server_url = f"localhost:{HW_SERVER_PORT}"
|
hw_server_url = f"localhost:{HW_SERVER_PORT}"
|
||||||
|
xilinx_root = getattr(config, "xilinx_path", "")
|
||||||
|
|
||||||
if callback:
|
if callback:
|
||||||
callback("start", "Connecting to hw_server...")
|
callback("start", "Connecting to hw_server...")
|
||||||
|
|
||||||
xilinx_root = getattr(config, "xilinx_path", "")
|
# ── 1. Find tools ──────────────────────────────────────────
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|
||||||
# ── 1. Find and start hw_server ──────────────────────────
|
|
||||||
hw_server_path = get_hw_server_path(xilinx_root=xilinx_root)
|
hw_server_path = get_hw_server_path(xilinx_root=xilinx_root)
|
||||||
|
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:
|
if not hw_server_path:
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
ps_detected=False, pl_detected=False,
|
ps_detected=False, pl_detected=False,
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error="hw_server not found — install Vivado/Vitis hardware server",
|
error="hw_server not found — install Vivado/Vitis",
|
||||||
)
|
)
|
||||||
|
|
||||||
hw_proc: subprocess.Popen | None = None
|
|
||||||
try:
|
try:
|
||||||
# Start hw_server in the background (needed for xsct connect)
|
|
||||||
hw_proc = subprocess.Popen(
|
hw_proc = subprocess.Popen(
|
||||||
build_tool_command(hw_server_path),
|
build_tool_command(hw_server_path),
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
time.sleep(2) # let hw_server bind its port
|
# Wait for port to become available
|
||||||
|
for _ in range(30): # 3s max
|
||||||
# ── 2. Find xsct (much lighter than Vivado for JTAG ops) ──
|
if _port_open():
|
||||||
xsct_path = get_xsct_path(xilinx_root=xilinx_root)
|
break
|
||||||
if not xsct_path:
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
ps_detected=False, pl_detected=False,
|
ps_detected=False, pl_detected=False,
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error="xsct not found",
|
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 JTAG query TCL via xsct ────────────────────
|
# ── 3. Run Vivado TCL ──────────────────────────────────────
|
||||||
tcl_content = _build_jtag_query_tcl()
|
try:
|
||||||
|
tcl = _build_jtag_query_tcl()
|
||||||
if callback:
|
if callback:
|
||||||
callback("progress", "Scanning JTAG chain...")
|
callback("progress", "Scanning JTAG chain...")
|
||||||
|
|
||||||
cmd = build_tool_command(
|
|
||||||
xsct_path, "-eval", tcl_content,
|
|
||||||
)
|
|
||||||
python_timeout = config.step_timeouts.get("check_env", 120)
|
python_timeout = config.step_timeouts.get("check_env", 120)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
build_tool_command(vivado_path, "-mode", "batch", "-source", tcl),
|
||||||
capture_output=True,
|
capture_output=True, text=True, timeout=python_timeout,
|
||||||
text=True,
|
|
||||||
timeout=python_timeout,
|
|
||||||
)
|
)
|
||||||
|
# Clean up temp TCL file
|
||||||
|
try:
|
||||||
|
Path(tcl).unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
output = result.stdout + result.stderr
|
||||||
jtag_chain = _parse_jtag_chain(output)
|
jtag_chain = _parse_jtag_chain(output)
|
||||||
@@ -176,7 +217,7 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
error=f"OS error: {e}",
|
error=f"OS error: {e}",
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
# ── 4. Kill hw_server ─────────────────────────────────
|
# ── 4. Kill hw_server only if WE started it ────────────
|
||||||
if hw_proc is not None and hw_proc.poll() is None:
|
if hw_proc is not None and hw_proc.poll() is None:
|
||||||
try:
|
try:
|
||||||
hw_proc.terminate()
|
hw_proc.terminate()
|
||||||
@@ -185,40 +226,44 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
hw_proc.kill()
|
hw_proc.kill()
|
||||||
hw_proc.wait()
|
hw_proc.wait()
|
||||||
except OSError:
|
except OSError:
|
||||||
pass # already dead
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ── TCL builder ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _build_jtag_query_tcl() -> str:
|
def _build_jtag_query_tcl() -> str:
|
||||||
"""Build TCL script for xsct JTAG device query.
|
"""Build Vivado TCL script file for JTAG device enumeration.
|
||||||
|
|
||||||
Uses 'jtag targets' (not 'targets') to get raw JTAG chain devices
|
|
||||||
with IDCODE, irlen — the same info that hw_server exposes.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
TCL script content as string (semicolon-delimited for -eval mode).
|
Path to the temporary TCL script file.
|
||||||
"""
|
"""
|
||||||
return (
|
content = """\
|
||||||
"connect; "
|
open_hw_manager
|
||||||
'puts "===JTAG_START==="; '
|
connect_hw_server
|
||||||
"jtag targets; "
|
open_hw_target
|
||||||
'puts "===JTAG_END==="; '
|
puts "===JTAG_START==="
|
||||||
"disconnect; "
|
foreach dev [get_hw_devices] {
|
||||||
"quit"
|
set name [get_property NAME $dev]
|
||||||
)
|
set idcode [get_property IDCODE $dev]
|
||||||
|
puts "DEVICE:$name IDCODE:$idcode"
|
||||||
|
}
|
||||||
|
puts "===JTAG_END==="
|
||||||
|
quit
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
path = Path(tempfile.gettempdir()) / f"zynq_jtag_{os.getpid()}.tcl"
|
||||||
|
path.write_text(content)
|
||||||
|
return str(path)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Parsers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
||||||
"""Parse xsct 'jtag targets' output into JtagDevice list.
|
"""Parse Vivado TCL output into JtagDevice list.
|
||||||
|
|
||||||
Expected format (one line per JTAG device):
|
Expected format:
|
||||||
1 xc7z100 (idcode 03736093 irlen 4)
|
DEVICE:arm_dap_0 IDCODE:01001011101000000000010001110111
|
||||||
2 arm_dap (idcode 4BA00477 irlen 4)
|
DEVICE:xc7z100_1 IDCODE:00000011011100110110000010010011
|
||||||
|
|
||||||
Args:
|
|
||||||
output: Combined stdout/stderr from xsct.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of JtagDevice objects for all devices on the JTAG chain.
|
|
||||||
"""
|
"""
|
||||||
devices: list[JtagDevice] = []
|
devices: list[JtagDevice] = []
|
||||||
section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
|
section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
|
||||||
@@ -229,25 +274,17 @@ def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
|||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
match = _JTAG_LINE_RE.match(line)
|
match = _DEVICE_LINE_RE.match(line)
|
||||||
if match:
|
if match:
|
||||||
_idx = match.group(1)
|
name = match.group(1)
|
||||||
name = match.group(2)
|
idcode_hex = _idcode_to_hex(match.group(2))
|
||||||
idcode = match.group(3)
|
devices.append(_classify_jtag_device(name, idcode_hex))
|
||||||
devices.append(_classify_jtag_device(name, idcode))
|
|
||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|
||||||
def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
|
def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
|
||||||
"""Filter Zynq devices from JTAG chain, propagating IDCODE.
|
"""Filter Zynq devices from JTAG chain, propagating IDCODE."""
|
||||||
|
|
||||||
Args:
|
|
||||||
jtag_chain: List of all JTAG devices.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of ZynqDevice objects.
|
|
||||||
"""
|
|
||||||
zynq_devices: list[ZynqDevice] = []
|
zynq_devices: list[ZynqDevice] = []
|
||||||
for jtag_dev in jtag_chain:
|
for jtag_dev in jtag_chain:
|
||||||
if jtag_dev.is_zynq:
|
if jtag_dev.is_zynq:
|
||||||
@@ -257,24 +294,8 @@ def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
|
|||||||
return zynq_devices
|
return zynq_devices
|
||||||
|
|
||||||
|
|
||||||
def _extract_section(
|
def _extract_section(text: str, start_marker: str, end_marker: str) -> str | None:
|
||||||
text: str, start_marker: str, end_marker: str
|
"""Extract text between last occurrence of two markers."""
|
||||||
) -> str | None:
|
|
||||||
"""Extract text between two markers, handling duplicate markers.
|
|
||||||
|
|
||||||
Finds the last occurrence of each marker to avoid matching
|
|
||||||
markers inside TCL script source code.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text: Full output text.
|
|
||||||
start_marker: Starting marker string.
|
|
||||||
end_marker: Ending marker string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Text between markers, or None if not found.
|
|
||||||
"""
|
|
||||||
# Find last occurrence of each marker to avoid matching
|
|
||||||
# markers inside TCL script source code
|
|
||||||
start_idx = text.rfind(start_marker)
|
start_idx = text.rfind(start_marker)
|
||||||
end_idx = text.rfind(end_marker)
|
end_idx = text.rfind(end_marker)
|
||||||
if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
|
if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
|
||||||
@@ -283,16 +304,8 @@ def _extract_section(
|
|||||||
|
|
||||||
|
|
||||||
def _classify_jtag_device(device_name: str, idcode: str = "") -> JtagDevice:
|
def _classify_jtag_device(device_name: str, idcode: str = "") -> JtagDevice:
|
||||||
"""Classify a JTAG device by its name and IDCODE.
|
"""Classify a JTAG device by name (Vivado format: xc7z100_1, arm_dap_0)."""
|
||||||
|
is_zynq = bool(_ZYNQ_RE.match(device_name))
|
||||||
Args:
|
|
||||||
device_name: Device name from 'jtag targets' (e.g. "xc7z100", "arm_dap").
|
|
||||||
idcode: JTAG IDCODE hex string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
JtagDevice with classification information.
|
|
||||||
"""
|
|
||||||
is_zynq = bool(_ZYNQ_DEVICE_RE.match(device_name))
|
|
||||||
is_arm_dap = bool(_ARM_DAP_RE.match(device_name))
|
is_arm_dap = bool(_ARM_DAP_RE.match(device_name))
|
||||||
is_pl_device = is_zynq and not is_arm_dap
|
is_pl_device = is_zynq and not is_arm_dap
|
||||||
|
|
||||||
@@ -306,32 +319,21 @@ def _classify_jtag_device(device_name: str, idcode: str = "") -> JtagDevice:
|
|||||||
|
|
||||||
|
|
||||||
def _parse_zynq_device(device_name: str, idcode: str = "") -> ZynqDevice | None:
|
def _parse_zynq_device(device_name: str, idcode: str = "") -> ZynqDevice | None:
|
||||||
"""Parse a Zynq device from its JTAG chain name and IDCODE.
|
"""Parse Zynq device name (Vivado format: xc7z100_1)."""
|
||||||
|
match = _ZYNQ_RE.match(device_name)
|
||||||
Args:
|
|
||||||
device_name: Device name from JTAG chain (e.g. "xc7z100").
|
|
||||||
idcode: JTAG IDCODE hex string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ZynqDevice if it's a Zynq chip, None otherwise.
|
|
||||||
"""
|
|
||||||
match = _ZYNQ_DEVICE_RE.match(device_name)
|
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
part_number = device_name.upper()
|
# Strip index suffix: xc7z100_1 → XC7Z100
|
||||||
|
base = device_name.rsplit("_", 1)[0] if "_" in device_name else device_name
|
||||||
|
part_number = base.upper()
|
||||||
|
|
||||||
# Extract family and speed from part number
|
|
||||||
family = "zynq7"
|
family = "zynq7"
|
||||||
speed = "-1" # Default speed grade
|
speed = "-1"
|
||||||
|
|
||||||
speed_match = re.search(r"(\d+)$", part_number)
|
speed_match = re.search(r"(\d+)$", part_number)
|
||||||
if speed_match:
|
if speed_match:
|
||||||
speed_num = int(speed_match.group(1))
|
speed_num = int(speed_match.group(1))
|
||||||
if speed_num >= 2:
|
speed = "-2" if speed_num >= 2 else "-1"
|
||||||
speed = "-2"
|
|
||||||
elif speed_num >= 1:
|
|
||||||
speed = "-1"
|
|
||||||
|
|
||||||
return ZynqDevice(
|
return ZynqDevice(
|
||||||
name=device_name,
|
name=device_name,
|
||||||
@@ -343,15 +345,10 @@ def _parse_zynq_device(device_name: str, idcode: str = "") -> ZynqDevice | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Status dict ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
|
def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
|
||||||
"""Convert a ZynqCheckResult to a serializable dictionary.
|
"""Convert ZynqCheckResult to a serializable dict."""
|
||||||
|
|
||||||
Args:
|
|
||||||
result: ZynqCheckResult from check_zynq_jtag().
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary suitable for JSON serialization or GUI display.
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
"is_present": result.is_present,
|
"is_present": result.is_present,
|
||||||
"ps_detected": result.ps_detected,
|
"ps_detected": result.ps_detected,
|
||||||
@@ -361,6 +358,7 @@ def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
|
|||||||
"jtag_chain": [
|
"jtag_chain": [
|
||||||
{
|
{
|
||||||
"name": dev.name,
|
"name": dev.name,
|
||||||
|
"idcode": dev.idcode,
|
||||||
"is_zynq": dev.is_zynq,
|
"is_zynq": dev.is_zynq,
|
||||||
"is_arm_dap": dev.is_arm_dap,
|
"is_arm_dap": dev.is_arm_dap,
|
||||||
"is_pl_device": dev.is_pl_device,
|
"is_pl_device": dev.is_pl_device,
|
||||||
|
|||||||
Reference in New Issue
Block a user