From 62609b6e808f503c83520ac75b28bb4bce74eb5c Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Wed, 10 Jun 2026 09:43:02 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(jtag):=20enhance=20Zynq=20JTAG?= =?UTF-8?q?=20detection=20with=20PS/PL=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/gui/main_window.py | 26 ++++++---- src/zynq_checker.py | 112 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index aa59903..5643488 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -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: diff --git a/src/zynq_checker.py b/src/zynq_checker.py index f7795d0..a7e8569 100644 --- a/src/zynq_checker.py +++ b/src/zynq_checker.py @@ -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 ],