✨ 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:
+15
-11
@@ -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
@@ -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
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user