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._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
self._log_message(" Checking Zynq JTAG presence...")
jtag_result = check_zynq_jtag(self._config, self._jtag_callback)
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:
self._zynq_jtag_present = True
self._zynq_jtag_info = jtag_result.devices[0]
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._jtag_status.configure(text=f"JTAG: {part}", text_color=SUCCESS_COLOR)
else:
+101 -11
View File
@@ -2,6 +2,8 @@
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.
"""
from __future__ import annotations
@@ -10,13 +12,23 @@ import os
import re
import shutil
import subprocess
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable
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
class ZynqDevice:
"""Information about a detected Zynq device on the JTAG chain."""
@@ -26,6 +38,7 @@ class ZynqDevice:
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
@dataclass
@@ -33,7 +46,10 @@ class ZynqCheckResult:
"""Result of Zynq device detection on the JTAG chain."""
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
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
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
)
# ARM DAP pattern (PS side)
ARM_DAP_PATTERN = re.compile(r"^arm_dap_\d+$", re.IGNORECASE)
# hw_server default port
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.
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:
config: Application configuration.
@@ -71,7 +91,10 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
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",
)
@@ -97,17 +120,28 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
output = result.stdout + result.stderr
# 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 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:
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(
devices=devices,
jtag_chain=jtag_chain,
is_present=len(devices) > 0,
ps_detected=ps_detected,
pl_detected=pl_detected,
hw_server_url=hw_server_url,
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:
return ZynqCheckResult(
devices=[],
jtag_chain=[],
is_present=False,
ps_detected=False,
pl_detected=False,
hw_server_url=hw_server_url,
error="JTAG scan timed out after 30s",
)
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"OS error: {e}",
)
@@ -150,16 +190,16 @@ quit
"""
def _parse_jtag_devices(output: str) -> list[ZynqDevice]:
"""Parse JTAG device information from Vivado output.
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
"""Parse JTAG chain information from Vivado output.
Args:
output: Combined stdout/stderr from Vivado batch execution.
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
devices_section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
@@ -170,13 +210,30 @@ def _parse_jtag_devices(output: str) -> list[ZynqDevice]:
line = line.strip()
if line.startswith("DEVICE:"):
device_name = line[len("DEVICE:"):]
device = _parse_zynq_device(device_name)
if device:
devices.append(device)
device = _classify_jtag_device(device_name)
devices.append(device)
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(
text: str, start_marker: str, end_marker: str
) -> str | None:
@@ -202,6 +259,27 @@ 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.
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:
"""Parse a Zynq device from its JTAG chain name.
@@ -282,8 +360,19 @@ def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
"""
return {
"is_present": result.is_present,
"ps_detected": result.ps_detected,
"pl_detected": result.pl_detected,
"hw_server_url": result.hw_server_url,
"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": [
{
"name": dev.name,
@@ -291,6 +380,7 @@ def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
"family": dev.family,
"speed": dev.speed,
"idcode": dev.idcode,
"is_programmable": dev.is_programmable,
}
for dev in result.devices
],