diff --git a/config.yaml b/config.yaml index ef1a5d4..3f4bc4e 100644 --- a/config.yaml +++ b/config.yaml @@ -3,6 +3,8 @@ bootloader_bit_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/pl_arm_wrap bootloader_elf_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/app.elf firmware_bin_path: /home/ly0kos/work/Verify/FW/V1.3.1.3.3_06_01_pmu_cpld_check/V1.3.1.3.3_06_01_pmu_cpld_check.bin flash_type: qspi-x4-single +flash_model: s25fl256s1 +erase_before_program: true fsbl_elf_path: /home/ly0kos/work/Verify/FPGA/Xilinx/Z100/fsbl_qspi_bypass.elf inter_step_delay: 2 ping_count: 3 @@ -10,15 +12,14 @@ ping_timeout: 5 reboot_timeout: 30 serial_baudrate: 115200 serial_port: /dev/ttyUSB0 -tftp_upload_name: z7bin -uart_delay: 3 -inter_step_delay: 2 step_timeouts: + bitstream: 60 check_env: 30 + elf_download: 60 flash_erase: 120 flash_program: 600 - bitstream: 60 - elf_download: 60 tftp_reboot: 120 -vitis_path: "" +tftp_upload_name: z7bin +uart_delay: 3 +vitis_path: '' zynq_ip: 192.168.100.11 diff --git a/config/default_config.yaml b/config/default_config.yaml index 19d4bcc..039fdd7 100644 --- a/config/default_config.yaml +++ b/config/default_config.yaml @@ -22,9 +22,13 @@ bootloader_elf_path: "" # bootloader_bin_path is the bootloader image to program into QSPI flash # fsbl_elf_path is the FSBL (First Stage Boot Loader) required by program_flash # flash_type sets the QSPI interface mode (qspi-x4-single, qspi-x8-dual_parallel, etc.) +# flash_model determines chip capacity: s25fl256s1=32MiB, s25fl128s=16MiB, etc. +# erase_before_program: if true, erase flash sectors before programming bootloader_bin_path: "" fsbl_elf_path: "" flash_type: "qspi-x4-single" +flash_model: "s25fl256s1" +erase_before_program: true # Firmware upload (Step 4: Load Main Program via TFTP) firmware_bin_path: "" diff --git a/src/config_manager.py b/src/config_manager.py index 879e07e..d997b1a 100644 --- a/src/config_manager.py +++ b/src/config_manager.py @@ -25,6 +25,8 @@ DEFAULT_CONFIG: dict[str, Any] = { "bootloader_bin_path": "", "fsbl_elf_path": "", "flash_type": "qspi-x4-single", + "flash_model": "s25fl256s1", + "erase_before_program": True, "firmware_bin_path": "", "reboot_timeout": 30, "ping_timeout": 5, @@ -60,6 +62,8 @@ class Config: bootloader_bin_path: str = "" fsbl_elf_path: str = "" flash_type: str = "qspi-x4-single" + flash_model: str = "s25fl256s1" + erase_before_program: bool = True firmware_bin_path: str = "" reboot_timeout: int = 30 ping_timeout: int = 5 @@ -166,6 +170,8 @@ class Config: "bootloader_bin_path": self._relative_path(self.bootloader_bin_path), "fsbl_elf_path": self._relative_path(self.fsbl_elf_path), "flash_type": self.flash_type, + "flash_model": self.flash_model, + "erase_before_program": self.erase_before_program, "firmware_bin_path": self._relative_path(self.firmware_bin_path), "reboot_timeout": self.reboot_timeout, "ping_timeout": self.ping_timeout, diff --git a/src/flash_programmer.py b/src/flash_programmer.py index d31473f..76707d9 100644 --- a/src/flash_programmer.py +++ b/src/flash_programmer.py @@ -279,7 +279,11 @@ def full_flash_program( bin_path: str | Path, callback: ProgressCallback = None, ) -> list[FlashResult]: - """Execute full flash workflow: wipe → program → verify. + """Execute full flash workflow: (optional wipe) → program → verify. + + If config.erase_before_program is True, erases flash sectors before + programming. Otherwise, program_flash handles sector erase during + programming (per-sector, not full chip). Args: config: Application configuration. @@ -291,10 +295,17 @@ def full_flash_program( """ results: list[FlashResult] = [] - # Step 1: Erase the entire flash - results.append(wipe_flash(config, callback)) - if not results[-1].success: - return results + # Step 1: Erase (optional — controlled by config.erase_before_program) + if getattr(config, 'erase_before_program', True): + results.append(wipe_flash(config, callback)) + if not results[-1].success: + return results + else: + results.append(FlashResult( + step="wipe", + success=True, + message="Erase skipped (handled by program_flash)", + )) # Step 2: Program and verify (program_flash -verify) results.append(program_flash_bin(config, bin_path, callback)) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index ef00f97..a3c78a6 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -80,6 +80,7 @@ class MainWindow(ctk.CTk): self._zynq_jtag_present: bool = False self._zynq_jtag_info = None self._uart_monitor: UartMonitor | None = None + self._flash_phase: str = "" # StringVar references for config sync self._ip_string_var: ctk.StringVar | None = None @@ -186,6 +187,7 @@ class MainWindow(ctk.CTk): self._config.bootloader_elf_path = files["bootloader_elf_path"] self._config.bootloader_bin_path = files["bootloader_bin_path"] self._config.fsbl_elf_path = files["fsbl_elf_path"] + self._config.erase_before_program = self._erase_cb_var.get() self._config.firmware_bin_path = files["firmware_bin_path"] def _save_config(self) -> None: @@ -398,7 +400,7 @@ class MainWindow(ctk.CTk): row=0, column=0, sticky="nsew", padx=PADDING, pady=PADDING, ) - panel.grid_rowconfigure(6, weight=1) + panel.grid_rowconfigure(7, weight=1) title = ctk.CTkLabel( panel, @@ -461,6 +463,27 @@ class MainWindow(ctk.CTk): self._fsbl_selector.set_path(str(resolved)) self._fsbl_selector.grid(row=5, 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_columnconfigure(0, weight=1) + + self._erase_cb_var = ctk.BooleanVar(value=self._config.erase_before_program) + self._erase_cb = ctk.CTkCheckBox( + flash_frame, + text="Erase flash before programming", + variable=self._erase_cb_var, + font=FONT_SMALL, + ) + self._erase_cb.grid(row=0, column=0, sticky="w", padx=PADDING_SMALL) + + flash_model_label = ctk.CTkLabel( + flash_frame, + text=f"Flash: {self._config.flash_model} (32 MiB)", + font=FONT_SMALL, + ) + flash_model_label.grid(row=1, column=0, sticky="w", padx=PADDING_SMALL, pady=(2, 0)) + # Firmware BIN path (TFTP upload) self._firmware_bin_selector = FileSelector( panel, @@ -470,7 +493,7 @@ class MainWindow(ctk.CTk): if self._config.firmware_bin_path: resolved = self._config.resolve_path(self._config.firmware_bin_path) self._firmware_bin_selector.set_path(str(resolved)) - self._firmware_bin_selector.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL) + self._firmware_bin_selector.grid(row=7, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL) # Save button save_btn = ctk.CTkButton( @@ -479,7 +502,7 @@ class MainWindow(ctk.CTk): font=FONT_SMALL, command=self._save_config, ) - save_btn.grid(row=7, column=0, padx=PADDING, pady=PADDING) + save_btn.grid(row=8, column=0, padx=PADDING, pady=PADDING) def _build_serial_panel(self, parent: ctk.CTkFrame) -> None: """Build the serial monitor panel.""" @@ -725,7 +748,21 @@ class MainWindow(ctk.CTk): return False def _flash_callback(self, status: str, message: str) -> None: - """Callback for flash programming progress.""" + """Callback for flash programming progress — tracks phases.""" + if status == "phase": + # Track current flash phase for UI + self._flash_phase = message # 'erase', 'program', 'verify', 'done' + phase_names = {"erase": "Erasing", "program": "Programming", "verify": "Verifying", "done": "Done"} + label = phase_names.get(message, message) + self._log_message(f" ▸ {label}") + return + if status == "progress": + pct = message.rstrip('%') + # Show progress within current phase + phase_label = {"erase": "Erase", "program": "Program", "verify": "Verify"}.get( + self._flash_phase, "Flash") + self._log_message(f" [{phase_label}] {pct}%") + return self._log_message(f" [{status}] {message}") def _jtag_callback(self, status: str, message: str) -> None: diff --git a/src/subprocess_utils.py b/src/subprocess_utils.py index 11d0ae6..0f2403c 100644 --- a/src/subprocess_utils.py +++ b/src/subprocess_utils.py @@ -1,7 +1,8 @@ """Non-blocking subprocess execution with real-time output parsing. Provides run_streaming() which streams stdout/stderr to callbacks -in real time, parsing for warnings, errors, and progress indicators. +in real time, parsing for warnings, errors, progress indicators, +and flash operation phase transitions (erase/program/verify). Used by flash_programmer and bitstream_programmer. """ @@ -15,7 +16,7 @@ from typing import Callable StreamCallback = Callable[[str, str], None] | None """Callback type: (category, message) where category is one of: - 'out', 'err', 'warn', 'error', 'progress', 'done'""" + 'out', 'err', 'warn', 'error', 'progress', 'phase', 'done'""" # Regex patterns for parsing tool output _WARNING_RE = re.compile(r"WARNING\s*[:\[].*", re.IGNORECASE) @@ -24,6 +25,12 @@ _PROGRESS_RE = re.compile(r"(\d{1,3})\s*%") _INFO_KEYWORDS = ["connected", "downloading", "running", "initialization", "verif", "write", "erase", "flash operation"] +# Phase transition patterns for program_flash output +_PHASE_ERASE_RE = re.compile(r"Performing Erase Operation", re.IGNORECASE) +_PHASE_PROGRAM_RE = re.compile(r"Performing Program Operation", re.IGNORECASE) +_PHASE_VERIFY_RE = re.compile(r"Performing Verify Operation", re.IGNORECASE) +_PHASE_DONE_RE = re.compile(r"(Erase|Program|Verify) Operation successful", re.IGNORECASE) + def run_streaming( cmd: list[str], @@ -133,6 +140,24 @@ def _parse_and_callback( callback("warn", line) return + # Check for phase transitions (program_flash output) + if _PHASE_ERASE_RE.search(line): + callback("phase", "erase") + callback("out", line) + return + if _PHASE_PROGRAM_RE.search(line): + callback("phase", "program") + callback("out", line) + return + if _PHASE_VERIFY_RE.search(line): + callback("phase", "verify") + callback("out", line) + return + if _PHASE_DONE_RE.search(line): + callback("phase", "done") + callback("out", line) + return + # Check for progress percentage m = _PROGRESS_RE.search(line) if m: