fix: stream Vivado output live, remove broken normalize_port
JTAG: Start hw_server explicitly (Windows needs it), then run Vivado TCL with Popen + line-by-line stdout streaming via callback so user can see Vivado progress in real-time (not just on failure). COM: Remove normalize_port — pyserial >= 3.0 handles COM>=10 prefix internally. The manual \\.\ prefix was causing PermissionError(13) on Windows where pyserial's own handling conflicted.
This commit is contained in:
+1
-2
@@ -1075,8 +1075,7 @@ class UartMonitorWindow(ctk.CTkToplevel):
|
|||||||
def _bg_open() -> None:
|
def _bg_open() -> None:
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
from serial_monitor import normalize_port
|
ser = serial.Serial(self._port, self._baudrate, timeout=1)
|
||||||
ser = serial.Serial(normalize_port(self._port), self._baudrate, timeout=1)
|
|
||||||
ser.close()
|
ser.close()
|
||||||
self.after(0, self._on_open_success)
|
self.after(0, self._on_open_success)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -125,10 +125,9 @@ def reboot_via_serial(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
from serial_monitor import normalize_port
|
|
||||||
|
|
||||||
with serial.Serial(
|
with serial.Serial(
|
||||||
normalize_port(config.serial_port),
|
config.serial_port,
|
||||||
config.serial_baudrate,
|
config.serial_baudrate,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
) as ser:
|
) as ser:
|
||||||
|
|||||||
+5
-26
@@ -7,7 +7,6 @@ Provides UartMonitor for continuous background monitoring.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -18,26 +17,6 @@ import serial
|
|||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
def normalize_port(port: str) -> str:
|
|
||||||
"""Normalize a serial port name for the current platform.
|
|
||||||
|
|
||||||
On Windows, COM ports numbered >= 10 require the NT namespace
|
|
||||||
prefix (\\\\.\\COM11). Without it, CreateFile fails with
|
|
||||||
'Access denied' or 'file not found'.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port: Raw port name from config or detection.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Normalized port name suitable for serial.Serial().
|
|
||||||
"""
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
match = re.match(r"^COM(\d+)$", port, re.IGNORECASE)
|
|
||||||
if match and int(match.group(1)) >= 10:
|
|
||||||
return rf"\\.\{port}"
|
|
||||||
return port
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BootInfo:
|
class BootInfo:
|
||||||
"""Parsed information extracted from Zynq boot output."""
|
"""Parsed information extracted from Zynq boot output."""
|
||||||
@@ -190,7 +169,7 @@ def check_uart_available(
|
|||||||
|
|
||||||
# 3. Try to open and read
|
# 3. Try to open and read
|
||||||
try:
|
try:
|
||||||
with serial.Serial(normalize_port(port), baudrate, timeout=timeout) as ser:
|
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
while ser.in_waiting:
|
while ser.in_waiting:
|
||||||
@@ -228,7 +207,7 @@ def read_serial_stream(
|
|||||||
serial.SerialException: If the port cannot be opened.
|
serial.SerialException: If the port cannot be opened.
|
||||||
"""
|
"""
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
with serial.Serial(normalize_port(port), baudrate, timeout=timeout) as ser:
|
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
||||||
# Clear any pending data
|
# Clear any pending data
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
while ser.in_waiting:
|
while ser.in_waiting:
|
||||||
@@ -264,7 +243,7 @@ def test_serial_version(
|
|||||||
import serial
|
import serial
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with serial.Serial(normalize_port(port), baudrate, timeout=timeout) as ser:
|
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
ser.reset_output_buffer()
|
ser.reset_output_buffer()
|
||||||
|
|
||||||
@@ -405,7 +384,7 @@ class UartMonitor:
|
|||||||
|
|
||||||
# Quick port check before starting thread
|
# Quick port check before starting thread
|
||||||
try:
|
try:
|
||||||
test_ser = serial.Serial(normalize_port(self._port), self._baudrate, timeout=1)
|
test_ser = serial.Serial(self._port, self._baudrate, timeout=1)
|
||||||
test_ser.close()
|
test_ser.close()
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
if self.on_error:
|
if self.on_error:
|
||||||
@@ -432,7 +411,7 @@ class UartMonitor:
|
|||||||
"""Main read loop running in background thread."""
|
"""Main read loop running in background thread."""
|
||||||
try:
|
try:
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
normalize_port(self._port),
|
self._port,
|
||||||
self._baudrate,
|
self._baudrate,
|
||||||
timeout=self._timeout,
|
timeout=self._timeout,
|
||||||
)
|
)
|
||||||
|
|||||||
+147
-177
@@ -1,13 +1,13 @@
|
|||||||
"""Zynq JTAG presence checker for Zynq Flasher GUI.
|
"""Zynq JTAG presence checker for Zynq Flasher GUI.
|
||||||
|
|
||||||
Uses Vivado batch-mode TCL to scan the JTAG chain via hw_server,
|
Starts hw_server explicitly, then uses Vivado batch-mode TCL to scan
|
||||||
returning device names, IDCODEs, and PS/PL presence.
|
the JTAG chain. On failure, Vivado's stdout/stderr is returned in the
|
||||||
|
error message for debugging.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -24,7 +24,7 @@ class JtagDevice:
|
|||||||
"""Information about a device on the JTAG chain."""
|
"""Information about a device on the JTAG chain."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
idcode: str = "" # IDCODE in hex (e.g. "03736093")
|
idcode: str = ""
|
||||||
is_zynq: bool = False
|
is_zynq: bool = False
|
||||||
is_arm_dap: bool = False
|
is_arm_dap: bool = False
|
||||||
is_pl_device: bool = False
|
is_pl_device: bool = False
|
||||||
@@ -35,9 +35,9 @@ class ZynqDevice:
|
|||||||
"""Information about a detected Zynq device on the JTAG chain."""
|
"""Information about a detected Zynq device on the JTAG chain."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
part_number: str # e.g. "XC7Z100"
|
part_number: str
|
||||||
family: str # e.g. "zynq7"
|
family: str
|
||||||
speed: str # e.g. "-1"
|
speed: str
|
||||||
idcode: str = ""
|
idcode: str = ""
|
||||||
is_programmable: bool = False
|
is_programmable: bool = False
|
||||||
|
|
||||||
@@ -56,31 +56,13 @@ class ZynqCheckResult:
|
|||||||
|
|
||||||
|
|
||||||
# ── Patterns ──────────────────────────────────────────────────────────
|
# ── Patterns ──────────────────────────────────────────────────────────
|
||||||
# Vivado TCL output: DEVICE:arm_dap_0 IDCODE:0100101110100...0111
|
|
||||||
_DEVICE_LINE_RE = re.compile(r"^DEVICE:(\S+)\s+IDCODE:([01]+)")
|
_DEVICE_LINE_RE = re.compile(r"^DEVICE:(\S+)\s+IDCODE:([01]+)")
|
||||||
|
|
||||||
# Zynq PL: xc7z100, xc7z100_0, xc7z100_1, etc.
|
|
||||||
_ZYNQ_RE = re.compile(r"^xc\d+z\d+[a-z]?\d+", re.IGNORECASE)
|
_ZYNQ_RE = re.compile(r"^xc\d+z\d+[a-z]?\d+", re.IGNORECASE)
|
||||||
|
|
||||||
# ARM DAP (PS): arm_dap, arm_dap_0, arm_dap_1
|
|
||||||
_ARM_DAP_RE = re.compile(r"^arm_dap", re.IGNORECASE)
|
_ARM_DAP_RE = re.compile(r"^arm_dap", re.IGNORECASE)
|
||||||
|
|
||||||
HW_SERVER_PORT = 3121
|
HW_SERVER_PORT = 3121
|
||||||
|
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def _port_open(host: str = "localhost", port: int = HW_SERVER_PORT) -> bool:
|
|
||||||
"""Check if a TCP port is accepting connections."""
|
|
||||||
try:
|
|
||||||
with socket.create_connection((host, port), timeout=1):
|
|
||||||
return True
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _idcode_to_hex(binary_str: str) -> str:
|
def _idcode_to_hex(binary_str: str) -> str:
|
||||||
"""Convert Vivado's binary IDCODE to uppercase hex."""
|
|
||||||
try:
|
try:
|
||||||
return f"{int(binary_str, 2):08X}"
|
return f"{int(binary_str, 2):08X}"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -92,12 +74,11 @@ def _idcode_to_hex(binary_str: str) -> str:
|
|||||||
def check_zynq_jtag(
|
def check_zynq_jtag(
|
||||||
config: Config, callback: Callable | None = None
|
config: Config, callback: Callable | None = None
|
||||||
) -> ZynqCheckResult:
|
) -> ZynqCheckResult:
|
||||||
"""Check for Zynq devices on the JTAG chain via Vivado + hw_server.
|
"""Check for Zynq devices on the JTAG chain.
|
||||||
|
|
||||||
Lifecycle:
|
1. Start hw_server (Vivado may not auto-start on Windows)
|
||||||
1. If hw_server is already running (port 3121 open) → reuse it
|
2. Run Vivado batch TCL to enumerate JTAG devices
|
||||||
2. If not running → start it, kill it when done
|
3. On failure, include Vivado output in error for debugging
|
||||||
3. Run Vivado batch TCL to enumerate JTAG devices
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: Application configuration.
|
config: Application configuration.
|
||||||
@@ -115,128 +96,150 @@ def check_zynq_jtag(
|
|||||||
# ── 1. Find tools ──────────────────────────────────────────
|
# ── 1. Find tools ──────────────────────────────────────────
|
||||||
vivado_path = get_vivado_path(xilinx_root=xilinx_root)
|
vivado_path = get_vivado_path(xilinx_root=xilinx_root)
|
||||||
if not vivado_path:
|
if not vivado_path:
|
||||||
return ZynqCheckResult(
|
return _fail("Vivado not found", hw_server_url)
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
|
||||||
ps_detected=False, pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
|
||||||
error="Vivado not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
hw_server_path = get_hw_server_path(xilinx_root=xilinx_root)
|
hw_server_path = get_hw_server_path(xilinx_root=xilinx_root)
|
||||||
hw_server_was_running = _port_open()
|
if not hw_server_path:
|
||||||
|
return _fail("hw_server not found", hw_server_url)
|
||||||
|
|
||||||
# ── 2. Start hw_server if needed ───────────────────────────
|
# ── 2. Start hw_server ─────────────────────────────────────
|
||||||
hw_proc: subprocess.Popen | None = None
|
hw_proc: subprocess.Popen | None = None
|
||||||
if not hw_server_was_running:
|
|
||||||
if not hw_server_path:
|
|
||||||
return ZynqCheckResult(
|
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
|
||||||
ps_detected=False, pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
|
||||||
error="hw_server not found — install Vivado/Vitis",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
hw_proc = subprocess.Popen(
|
|
||||||
build_tool_command(hw_server_path),
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
# Wait for port to become available
|
|
||||||
for _ in range(30): # 3s max
|
|
||||||
if _port_open():
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
return ZynqCheckResult(
|
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
|
||||||
ps_detected=False, pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
|
||||||
error="hw_server started but port 3121 not open",
|
|
||||||
)
|
|
||||||
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"Failed to start hw_server: {e}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 3. Run Vivado TCL ──────────────────────────────────────
|
|
||||||
try:
|
try:
|
||||||
tcl = _build_jtag_query_tcl()
|
hw_proc = subprocess.Popen(
|
||||||
if callback:
|
build_tool_command(hw_server_path),
|
||||||
callback("progress", "Scanning JTAG chain...")
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
python_timeout = config.step_timeouts.get("check_env", 120)
|
|
||||||
result = subprocess.run(
|
|
||||||
build_tool_command(vivado_path, "-mode", "batch", "-source", tcl),
|
|
||||||
capture_output=True, text=True, timeout=python_timeout,
|
|
||||||
)
|
)
|
||||||
# Clean up temp TCL file
|
# Wait up to 10s for hw_server to bind
|
||||||
|
started = False
|
||||||
|
for _ in range(100):
|
||||||
|
time.sleep(0.1)
|
||||||
|
if _port_listening():
|
||||||
|
started = True
|
||||||
|
break
|
||||||
|
if not started:
|
||||||
|
return _fail("hw_server did not bind port 3121 within 10s", hw_server_url)
|
||||||
|
except OSError as e:
|
||||||
|
return _fail(f"Failed to start hw_server: {e}", hw_server_url)
|
||||||
|
|
||||||
|
if callback:
|
||||||
|
callback("progress", "Scanning JTAG chain...")
|
||||||
|
|
||||||
|
# ── 3. Run Vivado TCL (stream output to callback) ──────────
|
||||||
|
tcl = _build_jtag_query_tcl()
|
||||||
|
python_timeout = config.step_timeouts.get("check_env", 120)
|
||||||
|
output_lines: list[str] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
build_tool_command(vivado_path, "-mode", "batch", "-source", tcl),
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
# Read line by line so user sees real-time progress
|
||||||
|
deadline = time.monotonic() + python_timeout
|
||||||
|
while True:
|
||||||
|
line = proc.stdout.readline() if proc.stdout else ""
|
||||||
|
if not line:
|
||||||
|
if proc.poll() is not None:
|
||||||
|
break
|
||||||
|
if time.monotonic() > deadline:
|
||||||
|
proc.kill()
|
||||||
|
proc.wait()
|
||||||
|
return _fail(
|
||||||
|
f"JTAG scan timed out after {python_timeout}s",
|
||||||
|
hw_server_url,
|
||||||
|
)
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
line = line.rstrip("\n\r")
|
||||||
|
output_lines.append(line)
|
||||||
|
if callback and line.strip():
|
||||||
|
callback("progress", line)
|
||||||
|
except OSError as e:
|
||||||
|
return _fail(f"Failed to run Vivado: {e}", hw_server_url)
|
||||||
|
finally:
|
||||||
try:
|
try:
|
||||||
Path(tcl).unlink(missing_ok=True)
|
Path(tcl).unlink(missing_ok=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
output = "\n".join(output_lines)
|
||||||
jtag_chain = _parse_jtag_chain(output)
|
|
||||||
devices = _filter_zynq_devices(jtag_chain)
|
|
||||||
|
|
||||||
|
# Parse results
|
||||||
|
jtag_chain = _parse_jtag_chain(output)
|
||||||
|
devices = _filter_zynq_devices(jtag_chain)
|
||||||
|
|
||||||
|
if devices:
|
||||||
if callback:
|
if callback:
|
||||||
if devices:
|
ps_note = "PS" if any(d.is_arm_dap for d in jtag_chain) else ""
|
||||||
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 ""
|
||||||
pl_note = "PL" if any(d.is_pl_device for d in jtag_chain) else ""
|
notes = ", ".join(filter(None, [ps_note, pl_note]))
|
||||||
notes = ", ".join(filter(None, [ps_note, pl_note]))
|
callback("complete", f"Found {len(devices)} Zynq device(s) [{notes}]")
|
||||||
callback("complete", f"Found {len(devices)} Zynq device(s) [{notes}]")
|
else:
|
||||||
else:
|
# Include Vivado output so user can debug
|
||||||
callback("error", "No Zynq device found on JTAG chain")
|
err_detail = _extract_relevant(output)
|
||||||
|
msg = "No Zynq device found on JTAG chain"
|
||||||
ps_detected = any(d.is_arm_dap for d in jtag_chain)
|
if err_detail:
|
||||||
pl_detected = any(d.is_pl_device for d in jtag_chain)
|
msg += f" | Vivado: {err_detail[:300]}"
|
||||||
|
if callback:
|
||||||
|
callback("error", msg)
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=devices, jtag_chain=jtag_chain,
|
devices=[], jtag_chain=jtag_chain,
|
||||||
is_present=len(devices) > 0,
|
is_present=False,
|
||||||
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",
|
|
||||||
)
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
return ZynqCheckResult(
|
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
|
||||||
ps_detected=False, pl_detected=False,
|
ps_detected=False, pl_detected=False,
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error=f"JTAG scan timed out after {config.step_timeouts.get('check_env', 120)}s",
|
error=msg,
|
||||||
)
|
)
|
||||||
except OSError as e:
|
|
||||||
return ZynqCheckResult(
|
ps_detected = any(d.is_arm_dap for d in jtag_chain)
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
pl_detected = any(d.is_pl_device for d in jtag_chain)
|
||||||
ps_detected=False, pl_detected=False,
|
|
||||||
hw_server_url=hw_server_url,
|
return ZynqCheckResult(
|
||||||
error=f"OS error: {e}",
|
devices=devices, jtag_chain=jtag_chain,
|
||||||
)
|
is_present=True,
|
||||||
finally:
|
ps_detected=ps_detected, pl_detected=pl_detected,
|
||||||
# ── 4. Kill hw_server only if WE started it ────────────
|
hw_server_url=hw_server_url,
|
||||||
if hw_proc is not None and hw_proc.poll() is None:
|
error="",
|
||||||
try:
|
)
|
||||||
hw_proc.terminate()
|
|
||||||
hw_proc.wait(timeout=3)
|
|
||||||
except subprocess.TimeoutExpired:
|
# ── Helpers ───────────────────────────────────────────────────────────
|
||||||
hw_proc.kill()
|
|
||||||
hw_proc.wait()
|
def _fail(error: str, url: str) -> ZynqCheckResult:
|
||||||
except OSError:
|
return ZynqCheckResult(
|
||||||
pass
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
|
ps_detected=False, pl_detected=False,
|
||||||
|
hw_server_url=url, error=error,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _port_listening(port: int = HW_SERVER_PORT) -> bool:
|
||||||
|
"""Check if hw_server is listening on its TCP port."""
|
||||||
|
import socket
|
||||||
|
try:
|
||||||
|
with socket.create_connection(("localhost", port), timeout=1):
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_relevant(output: str) -> str:
|
||||||
|
"""Extract error/warning lines from Vivado output for user display."""
|
||||||
|
lines = []
|
||||||
|
for line in output.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if any(kw in line for kw in ("ERROR", "WARNING", "CRITICAL", "FAIL", "cannot", "No")):
|
||||||
|
lines.append(line)
|
||||||
|
return " | ".join(lines[-5:]) # Last 5 relevant lines
|
||||||
|
|
||||||
|
|
||||||
# ── TCL builder ────────────────────────────────────────────────────────
|
# ── TCL builder ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _build_jtag_query_tcl() -> str:
|
def _build_jtag_query_tcl() -> str:
|
||||||
"""Build Vivado TCL script file for JTAG device enumeration.
|
"""Build Vivado TCL script file for JTAG device enumeration."""
|
||||||
|
|
||||||
Returns:
|
|
||||||
Path to the temporary TCL script file.
|
|
||||||
"""
|
|
||||||
content = """\
|
content = """\
|
||||||
open_hw_manager
|
open_hw_manager
|
||||||
connect_hw_server
|
connect_hw_server
|
||||||
@@ -259,17 +262,11 @@ quit
|
|||||||
# ── Parsers ────────────────────────────────────────────────────────────
|
# ── Parsers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
||||||
"""Parse Vivado TCL output into JtagDevice list.
|
"""Parse Vivado TCL output into JtagDevice list."""
|
||||||
|
|
||||||
Expected format:
|
|
||||||
DEVICE:arm_dap_0 IDCODE:01001011101000000000010001110111
|
|
||||||
DEVICE:xc7z100_1 IDCODE:00000011011100110110000010010011
|
|
||||||
"""
|
|
||||||
devices: list[JtagDevice] = []
|
devices: list[JtagDevice] = []
|
||||||
section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
|
section = _extract_section(output, "===JTAG_START===", "===JTAG_END===")
|
||||||
if not section:
|
if not section:
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
for line in section.splitlines():
|
for line in section.splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
@@ -279,12 +276,10 @@ def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
|||||||
name = match.group(1)
|
name = match.group(1)
|
||||||
idcode_hex = _idcode_to_hex(match.group(2))
|
idcode_hex = _idcode_to_hex(match.group(2))
|
||||||
devices.append(_classify_jtag_device(name, idcode_hex))
|
devices.append(_classify_jtag_device(name, idcode_hex))
|
||||||
|
|
||||||
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, propagating IDCODE."""
|
|
||||||
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:
|
||||||
@@ -295,7 +290,6 @@ def _filter_zynq_devices(jtag_chain: list[JtagDevice]) -> list[ZynqDevice]:
|
|||||||
|
|
||||||
|
|
||||||
def _extract_section(text: str, start_marker: str, end_marker: str) -> str | None:
|
def _extract_section(text: str, start_marker: str, end_marker: str) -> str | None:
|
||||||
"""Extract text between last occurrence of two markers."""
|
|
||||||
start_idx = text.rfind(start_marker)
|
start_idx = text.rfind(start_marker)
|
||||||
end_idx = text.rfind(end_marker)
|
end_idx = text.rfind(end_marker)
|
||||||
if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
|
if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
|
||||||
@@ -304,51 +298,36 @@ def _extract_section(text: str, start_marker: str, end_marker: str) -> str | Non
|
|||||||
|
|
||||||
|
|
||||||
def _classify_jtag_device(device_name: str, idcode: str = "") -> JtagDevice:
|
def _classify_jtag_device(device_name: str, idcode: str = "") -> JtagDevice:
|
||||||
"""Classify a JTAG device by name (Vivado format: xc7z100_1, arm_dap_0)."""
|
|
||||||
is_zynq = bool(_ZYNQ_RE.match(device_name))
|
is_zynq = bool(_ZYNQ_RE.match(device_name))
|
||||||
is_arm_dap = bool(_ARM_DAP_RE.match(device_name))
|
is_arm_dap = bool(_ARM_DAP_RE.match(device_name))
|
||||||
is_pl_device = is_zynq and not is_arm_dap
|
|
||||||
|
|
||||||
return JtagDevice(
|
return JtagDevice(
|
||||||
name=device_name,
|
name=device_name,
|
||||||
idcode=idcode,
|
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_zynq and not is_arm_dap,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_zynq_device(device_name: str, idcode: str = "") -> ZynqDevice | None:
|
def _parse_zynq_device(device_name: str, idcode: str = "") -> ZynqDevice | None:
|
||||||
"""Parse Zynq device name (Vivado format: xc7z100_1)."""
|
|
||||||
match = _ZYNQ_RE.match(device_name)
|
match = _ZYNQ_RE.match(device_name)
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Strip index suffix: xc7z100_1 → XC7Z100
|
|
||||||
base = device_name.rsplit("_", 1)[0] if "_" in device_name else device_name
|
base = device_name.rsplit("_", 1)[0] if "_" in device_name else device_name
|
||||||
part_number = base.upper()
|
part_number = base.upper()
|
||||||
|
|
||||||
family = "zynq7"
|
family = "zynq7"
|
||||||
speed = "-1"
|
speed = "-1"
|
||||||
speed_match = re.search(r"(\d+)$", part_number)
|
speed_match = re.search(r"(\d+)$", part_number)
|
||||||
if speed_match:
|
if speed_match:
|
||||||
speed_num = int(speed_match.group(1))
|
speed = "-2" if int(speed_match.group(1)) >= 2 else "-1"
|
||||||
speed = "-2" if speed_num >= 2 else "-1"
|
|
||||||
|
|
||||||
return ZynqDevice(
|
return ZynqDevice(
|
||||||
name=device_name,
|
name=device_name, part_number=part_number,
|
||||||
part_number=part_number,
|
family=family, speed=speed,
|
||||||
family=family,
|
idcode=idcode, is_programmable=True,
|
||||||
speed=speed,
|
|
||||||
idcode=idcode,
|
|
||||||
is_programmable=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ── Status dict ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
|
def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
|
||||||
"""Convert ZynqCheckResult to a serializable dict."""
|
|
||||||
return {
|
return {
|
||||||
"is_present": result.is_present,
|
"is_present": result.is_present,
|
||||||
"ps_detected": result.ps_detected,
|
"ps_detected": result.ps_detected,
|
||||||
@@ -356,24 +335,15 @@ def get_zynq_status_dict(result: ZynqCheckResult) -> dict:
|
|||||||
"hw_server_url": result.hw_server_url,
|
"hw_server_url": result.hw_server_url,
|
||||||
"error": result.error,
|
"error": result.error,
|
||||||
"jtag_chain": [
|
"jtag_chain": [
|
||||||
{
|
{"name": d.name, "idcode": d.idcode,
|
||||||
"name": dev.name,
|
"is_zynq": d.is_zynq, "is_arm_dap": d.is_arm_dap,
|
||||||
"idcode": dev.idcode,
|
"is_pl_device": d.is_pl_device}
|
||||||
"is_zynq": dev.is_zynq,
|
for d in result.jtag_chain
|
||||||
"is_arm_dap": dev.is_arm_dap,
|
|
||||||
"is_pl_device": dev.is_pl_device,
|
|
||||||
}
|
|
||||||
for dev in result.jtag_chain
|
|
||||||
],
|
],
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{"name": d.name, "part_number": d.part_number,
|
||||||
"name": dev.name,
|
"family": d.family, "speed": d.speed,
|
||||||
"part_number": dev.part_number,
|
"idcode": d.idcode, "is_programmable": d.is_programmable}
|
||||||
"family": dev.family,
|
for d in result.devices
|
||||||
"speed": dev.speed,
|
|
||||||
"idcode": dev.idcode,
|
|
||||||
"is_programmable": dev.is_programmable,
|
|
||||||
}
|
|
||||||
for dev in result.devices
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user