feat(jtag): enhance Zynq JTAG detection with PS/PL status

- Remove IP check from Step 1 environment check
- Add PS (ARM DAP) and PL detection to zynq_checker
- Log detailed JTAG chain information in Step 1
- Show PS/PL detection status in GUI logs
- Display JTAG chain devices with type classification
This commit is contained in:
Jeremy Shen
2026-06-10 09:43:02 +08:00
parent 2bde6d869e
commit 62609b6e80
2 changed files with 116 additions and 22 deletions
+15 -11
View File
@@ -509,25 +509,29 @@ class MainWindow(ctk.CTk):
self._vitis_status.configure(text="Vitis: Partial", text_color=WARNING_COLOR) self._vitis_status.configure(text="Vitis: Partial", text_color=WARNING_COLOR)
self._log_message(f" Issues: {'; '.join(result.errors)}") self._log_message(f" Issues: {'; '.join(result.errors)}")
# Check Zynq IP connectivity
ip = self._config.zynq_ip
self._log_message(f" Checking Zynq IP {ip}...")
if ping_ip(ip, self._config.ping_count, self._config.ping_timeout):
self._log_message(f"{ip} is reachable")
self._ip_status.configure(text=f"IP: {ip}", text_color=SUCCESS_COLOR)
else:
self._log_message(f"{ip} is not reachable")
self._ip_status.configure(text=f"IP: {ip}", text_color=DANGER_COLOR)
return False
# Check Zynq JTAG presence # Check Zynq JTAG presence
self._log_message(" Checking Zynq JTAG presence...") self._log_message(" Checking Zynq JTAG presence...")
jtag_result = check_zynq_jtag(self._config, self._jtag_callback) jtag_result = check_zynq_jtag(self._config, self._jtag_callback)
jtag_dict = get_zynq_status_dict(jtag_result) jtag_dict = get_zynq_status_dict(jtag_result)
# Log JTAG chain information
if jtag_result.jtag_chain:
self._log_message(f" JTAG chain: {len(jtag_result.jtag_chain)} device(s)")
for jtag_dev in jtag_result.jtag_chain:
dev_type = "ARM DAP (PS)" if jtag_dev.is_arm_dap else "PL" if jtag_dev.is_pl_device else "Other"
self._log_message(f" - {jtag_dev.name} ({dev_type})")
if jtag_result.is_present: if jtag_result.is_present:
self._zynq_jtag_present = True self._zynq_jtag_present = True
self._zynq_jtag_info = jtag_result.devices[0] self._zynq_jtag_info = jtag_result.devices[0]
part = jtag_result.devices[0].part_number part = jtag_result.devices[0].part_number
# Log PS and PL detection status
ps_status = "" if jtag_result.ps_detected else ""
pl_status = "" if jtag_result.pl_detected else ""
self._log_message(f" PS (ARM DAP): {ps_status}")
self._log_message(f" PL: {pl_status}")
self._log_message(f" ✓ Zynq {part} detected on JTAG chain") self._log_message(f" ✓ Zynq {part} detected on JTAG chain")
self._jtag_status.configure(text=f"JTAG: {part}", text_color=SUCCESS_COLOR) self._jtag_status.configure(text=f"JTAG: {part}", text_color=SUCCESS_COLOR)
else: else:
+101 -11
View File
@@ -2,6 +2,8 @@
Connects to hw_server via Vivado and scans the JTAG chain to detect Connects to hw_server via Vivado and scans the JTAG chain to detect
Zynq devices, returning chip model and presence information. Zynq devices, returning chip model and presence information.
Checks for both PS (Processing System) and PL (Programmable Logic)
recognition on the JTAG chain.
""" """
from __future__ import annotations from __future__ import annotations
@@ -10,13 +12,23 @@ import os
import re import re
import shutil import shutil
import subprocess import subprocess
from dataclasses import dataclass 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
@dataclass
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
@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."""
@@ -26,6 +38,7 @@ class ZynqDevice:
family: str # Family (e.g. "zynq7") family: str # Family (e.g. "zynq7")
speed: str # Speed grade (e.g. "1", "2", "-1", "-2") speed: str # Speed grade (e.g. "1", "2", "-1", "-2")
idcode: str = "" # JTAG IDCODE if available idcode: str = "" # JTAG IDCODE if available
is_programmable: bool = False # Whether the device is programmable
@dataclass @dataclass
@@ -33,7 +46,10 @@ 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] # All detected Zynq devices
jtag_chain: list[JtagDevice] # All devices on JTAG chain
is_present: bool # Whether any Zynq device was found is_present: bool # Whether any Zynq device was found
ps_detected: bool # Whether PS (ARM DAP) is detected
pl_detected: bool # Whether PL is detected
hw_server_url: str # hw_server connection URL used hw_server_url: str # hw_server connection URL used
error: str = "" # Error message if detection failed error: str = "" # Error message if detection failed
@@ -44,6 +60,9 @@ ZYNQ_PART_PATTERN = re.compile(
r"^(xc\d+z\d+[a-z]?\d+)_\d+$", re.IGNORECASE r"^(xc\d+z\d+[a-z]?\d+)_\d+$", re.IGNORECASE
) )
# ARM DAP pattern (PS side)
ARM_DAP_PATTERN = re.compile(r"^arm_dap_\d+$", re.IGNORECASE)
# hw_server default port # hw_server default port
HW_SERVER_PORT = 3121 HW_SERVER_PORT = 3121
@@ -52,7 +71,8 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
"""Check for Zynq devices on the JTAG chain via hw_server. """Check for Zynq devices on the JTAG chain via hw_server.
Connects to hw_server using Vivado batch mode, scans the JTAG chain, Connects to hw_server using Vivado batch mode, scans the JTAG chain,
and identifies Zynq devices by their JTAG device names. and identifies Zynq devices by their JTAG device names. Also checks
for PS (ARM DAP) and PL (Programmable Logic) recognition.
Args: Args:
config: Application configuration. config: Application configuration.
@@ -71,7 +91,10 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
if not vivado_path: if not vivado_path:
return ZynqCheckResult( return ZynqCheckResult(
devices=[], devices=[],
jtag_chain=[],
is_present=False, is_present=False,
ps_detected=False,
pl_detected=False,
hw_server_url=hw_server_url, hw_server_url=hw_server_url,
error="Vivado not found", error="Vivado not found",
) )
@@ -97,17 +120,28 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
output = result.stdout + result.stderr output = result.stdout + result.stderr
# Parse JTAG device information from output # Parse JTAG device information from output
devices = _parse_jtag_devices(output) jtag_chain = _parse_jtag_chain(output)
devices = _filter_zynq_devices(jtag_chain)
if callback: if callback:
if devices: if devices:
callback("complete", f"Found {len(devices)} Zynq device(s)") ps_note = "PS" if any(d.is_arm_dap for d in jtag_chain) else ""
pl_note = "PL" if any(d.is_pl_device for d in jtag_chain) else ""
notes = ", ".join(filter(None, [ps_note, pl_note]))
callback("complete", f"Found {len(devices)} Zynq device(s) [{notes}]")
else: else:
callback("error", "No Zynq device found on JTAG chain") callback("error", "No Zynq device found on JTAG chain")
# Check PS and PL detection
ps_detected = any(d.is_arm_dap for d in jtag_chain)
pl_detected = any(d.is_pl_device for d in jtag_chain)
return ZynqCheckResult( return ZynqCheckResult(
devices=devices, devices=devices,
jtag_chain=jtag_chain,
is_present=len(devices) > 0, is_present=len(devices) > 0,
ps_detected=ps_detected,
pl_detected=pl_detected,
hw_server_url=hw_server_url, hw_server_url=hw_server_url,
error="" if devices else "No Zynq device found on JTAG chain", error="" if devices else "No Zynq device found on JTAG chain",
) )
@@ -115,14 +149,20 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
return ZynqCheckResult( return ZynqCheckResult(
devices=[], devices=[],
jtag_chain=[],
is_present=False, is_present=False,
ps_detected=False,
pl_detected=False,
hw_server_url=hw_server_url, hw_server_url=hw_server_url,
error="JTAG scan timed out after 30s", error="JTAG scan timed out after 30s",
) )
except OSError as e: except OSError as e:
return ZynqCheckResult( return ZynqCheckResult(
devices=[], devices=[],
jtag_chain=[],
is_present=False, is_present=False,
ps_detected=False,
pl_detected=False,
hw_server_url=hw_server_url, hw_server_url=hw_server_url,
error=f"OS error: {e}", error=f"OS error: {e}",
) )
@@ -150,16 +190,16 @@ quit
""" """
def _parse_jtag_devices(output: str) -> list[ZynqDevice]: def _parse_jtag_chain(output: str) -> list[JtagDevice]:
"""Parse JTAG device information from Vivado output. """Parse JTAG chain information from Vivado output.
Args: Args:
output: Combined stdout/stderr from Vivado batch execution. output: Combined stdout/stderr from Vivado batch execution.
Returns: Returns:
List of ZynqDevice objects for detected Zynq chips. List of JtagDevice objects for all devices on the JTAG chain.
""" """
devices: list[ZynqDevice] = [] devices: list[JtagDevice] = []
# Extract device list from output # Extract device list from output
devices_section = _extract_section(output, "===JTAG_START===", "===JTAG_END===") devices_section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
@@ -170,13 +210,30 @@ def _parse_jtag_devices(output: str) -> list[ZynqDevice]:
line = line.strip() line = line.strip()
if line.startswith("DEVICE:"): if line.startswith("DEVICE:"):
device_name = line[len("DEVICE:"):] device_name = line[len("DEVICE:"):]
device = _parse_zynq_device(device_name) device = _classify_jtag_device(device_name)
if device: devices.append(device)
devices.append(device)
return devices return devices
def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
"""Filter Zynq devices from JTAG chain.
Args:
jtag_chain: List of all JTAG devices.
Returns:
List of ZynqDevice objects.
"""
zynq_devices: list[ZynqDevice] = []
for jtag_dev in jtag_chain:
if jtag_dev.is_zynq:
zynq_dev = _parse_zynq_device(jtag_dev.name)
if zynq_dev:
zynq_devices.append(zynq_dev)
return zynq_devices
def _extract_section( def _extract_section(
text: str, start_marker: str, end_marker: str text: str, start_marker: str, end_marker: str
) -> str | None: ) -> str | None:
@@ -202,6 +259,27 @@ 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:
"""Classify a JTAG device by its name.
Args:
device_name: Device name from JTAG chain.
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_pl_device = is_zynq and not is_arm_dap
return JtagDevice(
name=device_name,
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: def _parse_zynq_device(device_name: str) -> ZynqDevice | None:
"""Parse a Zynq device from its JTAG chain name. """Parse a Zynq device from its JTAG chain name.
@@ -282,8 +360,19 @@ def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
""" """
return { return {
"is_present": result.is_present, "is_present": result.is_present,
"ps_detected": result.ps_detected,
"pl_detected": result.pl_detected,
"hw_server_url": result.hw_server_url, "hw_server_url": result.hw_server_url,
"error": result.error, "error": result.error,
"jtag_chain": [
{
"name": dev.name,
"is_zynq": dev.is_zynq,
"is_arm_dap": dev.is_arm_dap,
"is_pl_device": dev.is_pl_device,
}
for dev in result.jtag_chain
],
"devices": [ "devices": [
{ {
"name": dev.name, "name": dev.name,
@@ -291,6 +380,7 @@ def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
"family": dev.family, "family": dev.family,
"speed": dev.speed, "speed": dev.speed,
"idcode": dev.idcode, "idcode": dev.idcode,
"is_programmable": dev.is_programmable,
} }
for dev in result.devices for dev in result.devices
], ],