feat: initial Zynq XC7Z100 Flasher GUI project
Add cross-platform GUI application for programming Zynq devices with step-by-step workflow: - CustomTkinter GUI with dark/light theme support - 4-step workflow: Check Env → Flash → Bootloader → Main Program - YAML configuration with relative path resolution - Serial monitoring and boot log parsing - TFTP upload with CRC32 verification - SSH/serial reboot management - Vivado/Impact tool auto-detection Project structure: - app.py: Entry point - src/: Core modules (config, flash, bitstream, tftp, serial, gui) - config/: Default configuration template - .flasher_env/: Python virtual environment
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
"""Boot verification module for Zynq Flasher GUI.
|
||||
|
||||
Verifies that the Zynq device has booted successfully after a
|
||||
reboot operation, by checking network connectivity and serial output.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
from config_manager import Config
|
||||
from ip_verifier import ping_ip, ping_ip_verbose
|
||||
from serial_monitor import detect_serial_ports, parse_boot_output, read_serial_stream
|
||||
|
||||
|
||||
@dataclass
|
||||
class BootVerificationResult:
|
||||
"""Result of a boot verification check."""
|
||||
|
||||
success: bool
|
||||
message: str
|
||||
ip_reachable: bool = False
|
||||
serial_found: bool = False
|
||||
boot_info: dict | None = None
|
||||
|
||||
|
||||
ProgressCallback = Callable[[str, str], None] | None
|
||||
|
||||
|
||||
def verify_boot(
|
||||
config: Config,
|
||||
timeout: int | None = None,
|
||||
max_retries: int = 5,
|
||||
callback: ProgressCallback = None,
|
||||
) -> BootVerificationResult:
|
||||
"""Verify the Zynq device has booted successfully.
|
||||
|
||||
Performs multiple checks:
|
||||
1. Ping the device IP to verify network connectivity
|
||||
2. Check serial port for boot messages
|
||||
3. Parse boot output for IP, version, and status
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
timeout: Seconds to wait per check.
|
||||
max_retries: Maximum number of retry attempts.
|
||||
callback: Optional progress callback.
|
||||
|
||||
Returns:
|
||||
BootVerificationResult with verification status.
|
||||
"""
|
||||
timeout = timeout or config.ping_timeout
|
||||
ip = config.zynq_ip
|
||||
|
||||
if callback:
|
||||
callback("start", f"Verifying boot on {ip}...")
|
||||
|
||||
# Check 1: Network connectivity
|
||||
ip_reachable = False
|
||||
for attempt in range(1, max_retries + 1):
|
||||
if callback:
|
||||
callback("progress", f"Ping attempt {attempt}/{max_retries}...")
|
||||
|
||||
if ping_ip(ip, 1, timeout):
|
||||
ip_reachable = True
|
||||
if callback:
|
||||
callback("complete", f"Device {ip} is reachable")
|
||||
break
|
||||
time.sleep(2)
|
||||
|
||||
# Check 2: Serial port boot messages
|
||||
serial_found = False
|
||||
boot_info: dict = {}
|
||||
|
||||
ports = detect_serial_ports()
|
||||
if ports and config.serial_port:
|
||||
for port_info in ports:
|
||||
if port_info.device == config.serial_port:
|
||||
try:
|
||||
lines = read_serial_stream(
|
||||
config.serial_port,
|
||||
config.serial_baudrate,
|
||||
timeout=5,
|
||||
)
|
||||
parsed = parse_boot_output(lines)
|
||||
if parsed.ip_address or parsed.boot_message or parsed.version:
|
||||
serial_found = True
|
||||
boot_info = {
|
||||
"ip_address": parsed.ip_address,
|
||||
"version": parsed.version,
|
||||
"boot_message": parsed.boot_message,
|
||||
"raw_lines": lines[-20:], # Last 20 lines
|
||||
}
|
||||
if callback:
|
||||
callback("complete", "Boot messages detected on serial")
|
||||
break
|
||||
except Exception as e:
|
||||
if callback:
|
||||
callback("progress", f"Serial read error on {port_info.device}: {e}")
|
||||
|
||||
# Overall result
|
||||
success = ip_reachable or serial_found
|
||||
message_parts = []
|
||||
if ip_reachable:
|
||||
message_parts.append(f"IP {ip} reachable")
|
||||
if serial_found:
|
||||
message_parts.append("serial boot info found")
|
||||
if not success:
|
||||
message_parts.append("no boot indicators detected")
|
||||
|
||||
message = "; ".join(message_parts) if message_parts else "Verification inconclusive"
|
||||
|
||||
if callback:
|
||||
callback("complete" if success else "error", message)
|
||||
|
||||
return BootVerificationResult(
|
||||
success=success,
|
||||
message=message,
|
||||
ip_reachable=ip_reachable,
|
||||
serial_found=serial_found,
|
||||
boot_info=boot_info if boot_info else {},
|
||||
)
|
||||
|
||||
|
||||
def wait_for_boot(
|
||||
config: Config,
|
||||
timeout: int | None = None,
|
||||
poll_interval: float = 2.0,
|
||||
callback: ProgressCallback = None,
|
||||
) -> BootVerificationResult:
|
||||
"""Wait for the Zynq device to complete boot sequence.
|
||||
|
||||
Continuously checks for boot indicators until timeout or success.
|
||||
|
||||
Args:
|
||||
config: Application configuration.
|
||||
timeout: Maximum seconds to wait.
|
||||
poll_interval: Seconds between checks.
|
||||
callback: Optional progress callback.
|
||||
|
||||
Returns:
|
||||
BootVerificationResult with final verification status.
|
||||
"""
|
||||
timeout = timeout or (config.reboot_timeout + 30)
|
||||
start_time = time.time()
|
||||
|
||||
if callback:
|
||||
callback("start", f"Waiting for boot (timeout: {timeout}s)...")
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
remaining = int(timeout - (time.time() - start_time))
|
||||
if callback:
|
||||
callback("progress", f"Waiting... {remaining}s remaining")
|
||||
|
||||
result = verify_boot(config, callback=callback)
|
||||
if result.success:
|
||||
return result
|
||||
|
||||
time.sleep(poll_interval)
|
||||
|
||||
if callback:
|
||||
callback("error", f"Boot verification timed out after {timeout}s")
|
||||
|
||||
return BootVerificationResult(
|
||||
success=False,
|
||||
message=f"Boot verification timed out after {timeout}s",
|
||||
)
|
||||
Reference in New Issue
Block a user