From dd35ab0846e0a2b6455b3878913f49c08627bf5b Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Thu, 11 Jun 2026 16:45:37 +0800 Subject: [PATCH] fix: use xsct 'jtag targets' for raw JTAG chain with IDCODE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/zynq_checker.py | 118 +++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/src/zynq_checker.py b/src/zynq_checker.py index c07147d..bb9b565 100644 --- a/src/zynq_checker.py +++ b/src/zynq_checker.py @@ -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, )