diff --git a/src/bitstream_programmer.py b/src/bitstream_programmer.py index 337138c..9b45e38 100644 --- a/src/bitstream_programmer.py +++ b/src/bitstream_programmer.py @@ -214,7 +214,7 @@ def _program_with_xsct( if callback: callback("progress", "Initializing PS then programming FPGA...") - # Build TCL: PS init first, then FPGA config, then post-config + # Build TCL: PS init → FPGA config → post-config → halt CPU → disable Flash boot lines = ["connect"] if ps7_init_tcl and Path(ps7_init_tcl).exists(): lines.append("targets 1") @@ -227,6 +227,11 @@ def _program_with_xsct( lines.append("ps7_post_config") if callback: callback("progress", "PS post-config (PL-PS interfaces)") + # After FPGA config, CPU may be reset — halt it to prevent Flash code + # from enabling MMU before the ELF step can download FSBL + lines.append("targets 2") + lines.append("after 100") + lines.append("stop") # Prevent boot from Flash by disabling PCAP reconfiguration lines.append("mwr 0xF800025C 0x00000001") lines.append("exit") diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 1349ad7..719031f 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -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(): diff --git a/src/gui/widgets.py b/src/gui/widgets.py index 106927e..9230fb4 100644 --- a/src/gui/widgets.py +++ b/src/gui/widgets.py @@ -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: diff --git a/src/serial_monitor.py b/src/serial_monitor.py index cef79dc..42cac59 100644 --- a/src/serial_monitor.py +++ b/src/serial_monitor.py @@ -23,6 +23,7 @@ class BootInfo: ip_address: str = "" version: str = "" boot_message: str = "" + boot_mode: str = "" raw_lines: list[str] | None = None @@ -73,7 +74,7 @@ def parse_boot_output(lines: list[str]) -> BootInfo: ip_pattern = re.compile(r"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b") version_patterns = [ re.compile(r"[Vv]ersion[:\s]+([^\s;,\n]+)", re.IGNORECASE), - re.compile(r"[Bb]oot\s+([^\s;,\n]+)", re.IGNORECASE), + re.compile(r"[Bb]oot\s+(?:version\s+)?([Vv]?\d+\.\d+[^\s;,\n]*)", re.IGNORECASE), re.compile(r"[Ff]irm[Ww]are:?\s*([^\s;,\n]+)", re.IGNORECASE), re.compile(r"(?:^|[\s:=])([Vv]\d+\.\d+\.\d+[^\s;,\n]*)", re.IGNORECASE), ] @@ -84,6 +85,10 @@ def parse_boot_output(lines: list[str]) -> BootInfo: re.compile(r"Linux\s+booted", re.IGNORECASE), re.compile(r"QSYS\s+ready", re.IGNORECASE), ] + boot_mode_patterns = [ + re.compile(r"[Bb]oot\s*[Mm]ode\s*(?:is)?\s*[:\[=\s]*(\w+)"), + re.compile(r"BootMode\s*[:\[=\s]*(\w+)", re.IGNORECASE), + ] for line in lines: # Extract IP address @@ -107,6 +112,14 @@ def parse_boot_output(lines: list[str]) -> BootInfo: info.boot_message = line.strip() break + # Extract boot mode + if not info.boot_mode: + for pattern in boot_mode_patterns: + match = pattern.search(line) + if match: + info.boot_mode = match.group(1) + break + return info