fix: use xsct 'jtag targets' for raw JTAG chain with IDCODE

The previous TCL used 'targets' which returns XSCT target IDs (numbers
like 1, 2, APU, xc7z100) — each word was parsed as a separate device,
producing '1 (Other)', 'APU (Other)', etc.

Switch to 'jtag targets' which outputs raw JTAG chain devices:
  1  xc7z100 (idcode 03736093 irlen 4)
  2  arm_dap (idcode 4BA00477 irlen 4)

Changes:
- New _JTAG_LINE_RE pattern for parsing jtag targets output
- Zynq pattern relaxed for xsct names (xc7z100 not xc7z100_1)
- ARM DAP pattern: arm_dap (not arm_dap_N)
- JtagDevice now carries idcode field
- ZynqDevice propagates idcode + is_programmable=True
- Removed unused tempfile/Path/os imports
This commit is contained in:
2026-06-11 16:45:37 +08:00
parent 86d65641cc
commit dd35ab0846
+62 -56
View File
@@ -1,9 +1,7 @@
"""Zynq JTAG presence checker for Zynq Flasher GUI.
Connects to hw_server via Vivado and scans the JTAG chain to detect
Zynq devices, returning chip model and presence information.
Checks for both PS (Processing System) and PL (Programmable Logic)
recognition on the JTAG chain.
Connects to hw_server via xsct and scans the JTAG chain to detect
Zynq devices, returning chip model, IDCODE, PS/PL presence.
"""
from __future__ import annotations
@@ -22,22 +20,23 @@ from vitis_checker import get_xsct_path, get_hw_server_path, build_tool_command
class JtagDevice:
"""Information about a device on the JTAG chain."""
name: str # Device name from JTAG chain (e.g. "xc7z100_1")
is_zynq: bool # Whether this is a Zynq device
is_arm_dap: bool # Whether this is an ARM DAP (PS side)
is_pl_device: bool # Whether this is a PL device
name: str # Device name (e.g. "xc7z100", "arm_dap")
idcode: str = "" # JTAG IDCODE hex string
is_zynq: bool = False
is_arm_dap: bool = False
is_pl_device: bool = False
@dataclass
class ZynqDevice:
"""Information about a detected Zynq device on the JTAG chain."""
name: str # Device name from JTAG chain (e.g. "xc7z100_1")
part_number: str # Extracted part number (e.g. "XC7Z100")
family: str # Family (e.g. "zynq7")
speed: str # Speed grade (e.g. "1", "2", "-1", "-2")
idcode: str = "" # JTAG IDCODE if available
is_programmable: bool = False # Whether the device is programmable
name: str
part_number: str # e.g. "XC7Z100"
family: str # e.g. "zynq7"
speed: str # e.g. "-1"
idcode: str = ""
is_programmable: bool = False
@dataclass
@@ -53,14 +52,18 @@ class ZynqCheckResult:
error: str = "" # Error message if detection failed
# Zynq part number patterns from JTAG device names
# Examples: xc7z010_1, xc7z020_1, xc7z100_1, xc7z030_1
ZYNQ_PART_PATTERN = re.compile(
r"^(xc\d+z\d+[a-z]?\d+)_\d+$", re.IGNORECASE
# ── Patterns for parsing xsct "jtag targets" output ──────────────────
# Each line: " 1 xc7z100 (idcode 03736093 irlen 4)"
_JTAG_LINE_RE = re.compile(
r"^\s*(\d+)\s+(\S+)\s+\(idcode\s+([0-9A-Fa-f]+)\s+irlen\s+(\d+)\)",
re.IGNORECASE,
)
# ARM DAP pattern (PS side)
ARM_DAP_PATTERN = re.compile(r"^arm_dap_\d+$", re.IGNORECASE)
# Zynq PL part: "xc7z100", "xc7z010", etc.
_ZYNQ_DEVICE_RE = re.compile(r"^xc\d+z\d+[a-z]?\d+$", re.IGNORECASE)
# ARM DAP (PS side): "arm_dap"
_ARM_DAP_RE = re.compile(r"^arm_dap$", re.IGNORECASE)
# hw_server default port
HW_SERVER_PORT = 3121
@@ -188,10 +191,8 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
def _build_jtag_query_tcl() -> str:
"""Build TCL script for xsct JTAG device query.
Uses xsct (not Vivado) because:
- xsct is much lighter (no GUI infrastructure)
- xsct JTAG reboot already works on this system
- connect + targets lists all devices on the JTAG chain
Uses 'jtag targets' (not 'targets') to get raw JTAG chain devices
with IDCODE, irlen — the same info that hw_server exposes.
Returns:
TCL script content as string (semicolon-delimited for -eval mode).
@@ -199,8 +200,7 @@ def _build_jtag_query_tcl() -> str:
return (
"connect; "
'puts "===JTAG_START==="; '
"set all [targets]; "
'foreach t $all { puts "DEVICE:$t" }; '
"jtag targets; "
'puts "===JTAG_END==="; '
"disconnect; "
"quit"
@@ -208,33 +208,39 @@ def _build_jtag_query_tcl() -> str:
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
"""Parse JTAG chain information from Vivado output.
"""Parse xsct 'jtag targets' output into JtagDevice list.
Expected format (one line per JTAG device):
1 xc7z100 (idcode 03736093 irlen 4)
2 arm_dap (idcode 4BA00477 irlen 4)
Args:
output: Combined stdout/stderr from Vivado batch execution.
output: Combined stdout/stderr from xsct.
Returns:
List of JtagDevice objects for all devices on the JTAG chain.
"""
devices: list[JtagDevice] = []
# Extract device list from output
devices_section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
if not devices_section:
section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
if not section:
return devices
for line in devices_section.splitlines():
for line in section.splitlines():
line = line.strip()
if line.startswith("DEVICE:"):
device_name = line[len("DEVICE:"):]
device = _classify_jtag_device(device_name)
devices.append(device)
if not line:
continue
match = _JTAG_LINE_RE.match(line)
if match:
_idx = match.group(1)
name = match.group(2)
idcode = match.group(3)
devices.append(_classify_jtag_device(name, idcode))
return devices
def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
"""Filter Zynq devices from JTAG chain.
"""Filter Zynq devices from JTAG chain, propagating IDCODE.
Args:
jtag_chain: List of all JTAG devices.
@@ -245,7 +251,7 @@ def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
zynq_devices: list[ZynqDevice] = []
for jtag_dev in jtag_chain:
if jtag_dev.is_zynq:
zynq_dev = _parse_zynq_device(jtag_dev.name)
zynq_dev = _parse_zynq_device(jtag_dev.name, jtag_dev.idcode)
if zynq_dev:
zynq_devices.append(zynq_dev)
return zynq_devices
@@ -276,54 +282,52 @@ def _extract_section(
return text[start_idx + len(start_marker):end_idx].strip()
def _classify_jtag_device(device_name: str) -> JtagDevice:
"""Classify a JTAG device by its name.
def _classify_jtag_device(device_name: str, idcode: str = "") -> JtagDevice:
"""Classify a JTAG device by its name and IDCODE.
Args:
device_name: Device name from JTAG chain.
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_PART_PATTERN.match(device_name))
is_arm_dap = bool(ARM_DAP_PATTERN.match(device_name))
is_zynq = bool(_ZYNQ_DEVICE_RE.match(device_name))
is_arm_dap = bool(_ARM_DAP_RE.match(device_name))
is_pl_device = is_zynq and not is_arm_dap
return JtagDevice(
name=device_name,
idcode=idcode,
is_zynq=is_zynq,
is_arm_dap=is_arm_dap,
is_pl_device=is_pl_device,
)
def _parse_zynq_device(device_name: str) -> ZynqDevice | None:
"""Parse a Zynq device from its JTAG chain name.
def _parse_zynq_device(device_name: str, idcode: str = "") -> ZynqDevice | None:
"""Parse a Zynq device from its JTAG chain name and IDCODE.
Args:
device_name: Device name from JTAG chain (e.g. "xc7z100_1").
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_PART_PATTERN.match(device_name)
match = _ZYNQ_DEVICE_RE.match(device_name)
if not match:
return None
part_base = match.group(1)
# Convert to uppercase for part number
part_number = part_base.upper()
part_number = device_name.upper()
# Extract family and speed from part number
# Examples: XC7Z010 -> family=zynq7, speed=1; XC7Z100 -> family=zynq7, speed=1
family = "zynq7"
speed = "1" # Default speed grade
speed = "-1" # Default speed grade
# Parse speed grade from the numeric suffix
speed_match = re.search(r"(\d+)$", part_base)
speed_match = re.search(r"(\d+)$", part_number)
if speed_match:
speed_num = int(speed_match.group(1))
# Map numeric speed to speed grade string
if speed_num >= 2:
speed = "-2"
elif speed_num >= 1:
@@ -334,6 +338,8 @@ def _parse_zynq_device(device_name: str) -> ZynqDevice | None:
part_number=part_number,
family=family,
speed=speed,
idcode=idcode,
is_programmable=True,
)