feat: boot mode parsing, FSBL-first flow, UI reorder

1. Boot mode parsing:
   - BootInfo.boot_mode field added
   - Matches 'Boot mode is JTAG', 'Boot Mode: QSPI', etc.
   - Fixed false version match: 'Boot mode is' no longer
     captures 'mode' as a version string

2. UART window cleanup:
   - Parsed boot info (mode/IP/ver) now only goes to main log
   - Raw serial lines still appear in UART window

3. Step 3 FSBL enforcement:
   - BIT step: after fpga -file, CPU is immediately halted (stop)
     to prevent Flash code from enabling MMU
   - ELF step: always dow FSBL → con → stop → dow bootloader

4. UI reorder:
   - FSBL ELF selector moved BEFORE Bootloader BIT
   - New layout: FSBL → BIT → ELF → Bootloader BIN → Flash → FW BIN
   - Fixed grid overlap (FSBL and Flash were both at row 6)
This commit is contained in:
Jeremy Shen
2026-06-10 16:20:12 +08:00
parent 47569a0e43
commit 9a7a625c46
4 changed files with 43 additions and 23 deletions
+19 -18
View File
@@ -433,7 +433,7 @@ class MainWindow(ctk.CTk):
row=0, column=0, sticky="nsew",
padx=PADDING, pady=PADDING,
)
panel.grid_rowconfigure(8, weight=1)
panel.grid_rowconfigure(9, weight=1)
title = ctk.CTkLabel(
panel,
@@ -466,6 +466,17 @@ class MainWindow(ctk.CTk):
self._ip_entry = ctk.CTkEntry(ip_frame, textvariable=self._ip_string_var)
self._ip_entry.grid(row=0, column=1, sticky="nsew", padx=PADDING_SMALL)
# FSBL ELF path (First Stage Bootloader — initialises PS, forces JTAG boot)
self._fsbl_selector = FileSelector(
panel,
label_text="FSBL ELF (.elf):",
file_types=[("ELF files", "*.elf"), ("All files", "*")],
)
if self._config.fsbl_elf_path:
resolved = self._config.resolve_path(self._config.fsbl_elf_path)
self._fsbl_selector.set_path(str(resolved))
self._fsbl_selector.grid(row=3, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# BIT path (Bootloader)
self._bit_selector = FileSelector(
panel,
@@ -475,7 +486,7 @@ class MainWindow(ctk.CTk):
if self._config.bootloader_bit_path:
resolved = self._config.resolve_path(self._config.bootloader_bit_path)
self._bit_selector.set_path(str(resolved))
self._bit_selector.grid(row=3, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._bit_selector.grid(row=4, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# ELF path (Bootloader)
self._elf_selector = FileSelector(
@@ -486,7 +497,7 @@ class MainWindow(ctk.CTk):
if self._config.bootloader_elf_path:
resolved = self._config.resolve_path(self._config.bootloader_elf_path)
self._elf_selector.set_path(str(resolved))
self._elf_selector.grid(row=4, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._elf_selector.grid(row=5, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Bootloader BIN path (Flash programming)
self._bootloader_bin_selector = FileSelector(
@@ -497,22 +508,11 @@ class MainWindow(ctk.CTk):
if self._config.bootloader_bin_path:
resolved = self._config.resolve_path(self._config.bootloader_bin_path)
self._bootloader_bin_selector.set_path(str(resolved))
self._bootloader_bin_selector.grid(row=5, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# FSBL ELF path (required by program_flash for QSPI programming)
self._fsbl_selector = FileSelector(
panel,
label_text="FSBL ELF (.elf):",
file_types=[("ELF files", "*.elf"), ("All files", "*")],
)
if self._config.fsbl_elf_path:
resolved = self._config.resolve_path(self._config.fsbl_elf_path)
self._fsbl_selector.set_path(str(resolved))
self._fsbl_selector.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._bootloader_bin_selector.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Flash options
flash_frame = ctk.CTkFrame(panel)
flash_frame.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
flash_frame.grid(row=7, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
flash_frame.grid_columnconfigure(0, weight=1)
self._erase_cb_var = ctk.BooleanVar(value=self._config.erase_all)
@@ -1164,8 +1164,9 @@ class MainWindow(ctk.CTk):
self._uart_monitor = UartMonitor(port, self._config.serial_baudrate)
self._uart_monitor.on_line = lambda line: self._log_message(f" [UART] {line}")
self._uart_monitor.on_boot_info = lambda info: (
self._log_message(f" [UART] IP={info.ip_address}, Ver={info.version}")
if info.ip_address or info.version else None
self._log_message(
f" [UART] Boot={info.boot_mode}, IP={info.ip_address}, Ver={info.version}"
) if any([info.boot_mode, info.ip_address, info.version]) else None
)
self._uart_monitor.on_error = lambda e: self._log_message(f" [UART] Error: {e}")
if self._uart_monitor.start():
+4 -3
View File
@@ -995,15 +995,16 @@ class UartMonitorWindow(ctk.CTkToplevel):
def _on_boot_info(self, info) -> None:
parts = []
if info.boot_mode:
parts.append(f"Boot={info.boot_mode}")
if info.ip_address:
parts.append(f"IP={info.ip_address}")
if info.version:
parts.append(f"Ver={info.version}")
if parts:
msg = f"[UART] {' '.join(parts)}"
self.after(0, lambda: self._log(f"{msg}\n"))
# Forward to main window log
# Forward to main log only (not UART window)
if self._on_log:
msg = f"[UART] {' '.join(parts)}"
self.after(0, lambda: self._on_log(msg))
def _on_error(self, error: str) -> None: