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