Commit Graph
55 Commits
Author SHA1 Message Date
yuysh 17c8f2d68b refactor: replace all config.vitis_path with config.xilinx_path across codebase
Affected files:
- flash_programmer.py (2 call sites) — get_program_flash_path
- bitstream_programmer.py (3 get_xsct_path + 1 get_vivado_path call sites)
  Also: removed duplicate _get_vivado_path() (now uses shared one from vitis_checker)
  Also: removed unused shutil import
- reboot_manager.py (1 call site) — get_xsct_path

All now use getattr(config, 'xilinx_path', '') consistently.
The only remaining 'config.vitis_path' reference is in vitis_checker.py's
check_vitis() itself (safe legacy fallback for old configs).
2026-06-11 15:33:54 +08:00
yuysh 61f0e6cd2b 🐛 fix: Step 1 JTAG check uses shared vivado path finder + build_tool_command
Root cause (2 bugs):
1. check_zynq_jtag() used config.vitis_path which no longer exists
   (config now uses xilinx_path) → always got empty path → 'Vivado not found'
2. _get_vivado_path() only checked hardcoded Linux paths, no version
   scanning, no .bat support on Windows

Fix:
- Added get_vivado_path() in vitis_checker.py (consistent with
  get_xsct_path, get_program_flash_path, etc.)
- Uses _find_executable() shared logic: scans Vivado/*/bin +
  Vitis/*/bin, picks newest version, supports .bat on Windows
- zynq_checker.py now uses get_vivado_path() + build_tool_command()
  for proper environment setup (settings64 sourcing on Linux)
- Removed obsolete _get_vivado_path() + unused shutil import
2026-06-11 15:30:43 +08:00
yuysh 6d686964e2 🐛 fix: Step 4 boot wait uses UART window monitor data, not just main monitor
Root cause: When UART window is open, self._uart_monitor is None
(the window creates its own UartMonitor). Step 4 boot wait loop
only checked self._uart_monitor.latest_info → always saw empty data
→ fell through to IP scanning even though UART had detected the IP.

Fix:
- UartMonitorWindow.get_latest_info() exposes parsed BootInfo
- Step 4 checks both: self._uart_monitor first, then UART window
- Null guard on info before accessing .ip_address
2026-06-11 15:21:36 +08:00
yuysh da94cc6cdc 🐛 fix: lambda closure crash + .bat version noise on Windows
Bug 1 — UartMonitorWindow crash on serial open failure:
- lambda: self._show_unavailable(str(e))  →  lambda e=e: ...
- Python 3 deletes 'e' after except block; default arg captures it

Bug 2 — xsct version shows 'ECHO 处于关闭状态。' on Windows:
- .bat wrappers output 'ECHO is off' / 'ECHO 处于关闭状态。' noise
- _is_batch_noise() filters known patterns (EN+ZH)
- Version regex now scans all output lines, skipping noise lines
- Path-based fallback (2023.2) catches any remaining undetectable cases
2026-06-11 15:18:08 +08:00
yuysh 70c88815cc feat: auto-discover Zynq IP after reboot (UART or UDP scan)
After Step 4 reboot, the Zynq may obtain a different IP via DHCP.

IP discovery strategy:
1. UART available → parse IP from boot output (fast, zero network traffic)
2. UART unavailable → scan 192.168.100.11–30 via UDP ver() probe
   (port = last_octet - 3, 0.5s timeout each, ~10s max for 20 IPs)

If IP changed → auto-update config.zynq_ip + UI field
2026-06-11 15:11:59 +08:00
yuysh 32b201cf8e feat: Windows .bat support + settings64 env setup for Xilinx tools
Tool discovery:
- On Windows, scan .bat files in addition to .exe
  (Xilinx Windows tools use .bat wrappers, not .exe)
- Prefer .exe over .bat when both exist (same version dir)

Environment setup:
- _find_settings_script(): walk up from bin/ to find settings64.sh/.bat
- build_tool_command(): wrap Linux tool invocation with 'source settings64.sh'
  Windows .bat wrappers handle env internally, no extra wrapping needed
- reboot_manager.py now uses build_tool_command() for proper env

Example output (Linux):
  build_tool_command('/data/xilinx/Vitis/2023.2/bin/xsct', 'script.tcl')
  → ['bash', '-c', 'source .../settings64.sh && .../xsct script.tcl']
2026-06-11 15:08:25 +08:00
yuysh 21acc16c8c feat: smart Xilinx version scanning — pick newest, log all versions
_version discovery:
- _scan_xilinx_root() collects ALL versions under Vitis/*/bin/ and Vivado/*/bin/
- _parse_version_tuple() semantic version comparison (2023.2 > 2018.3)
- Defaults to newest version, Vitis preferred over Vivado for same version
- _find_executable() search order: PATH → legacy vitis_path → scan xilinx_root → common paths

_version reporting:
- _get_tool_version(): tries -version flag first, falls back to path parsing
- _version_from_path(): extracts version from install path (e.g., .../Vitis/2023.2/bin/xsct → 2023.2)
- Step 1 logs each tool with version: ✓ xsct: /path [v2023.2]
- If multiple versions found, lists all: xsct versions found: 2023.2 (Vitis), 2018.3 (Vivado)

Cross-platform:
- Handles Windows path separators via Path()
- Expanded common Windows install paths
2026-06-11 14:56:40 +08:00
yuysh 475042c85c 🐛 fix: JTAG reboot 'Already running' error after rst -system
rst -system inherently resumes ARM execution on Zynq.
The subsequent 'con' command fails with 'Already running'
because the core is already executing after system reset.

Fix: wrap 'con' in 'catch {}' — if core is already running
the error is silently caught; if stopped, con resumes it.
xsct exits 0 (success) in both cases.
2026-06-11 14:48:45 +08:00
yuysh f6cf5de96f feat: UART boot wait, serial validation, xsct logging, layout fix
Config:
- Add boot_wait_delay (default 10s) for post-reboot wait time

Layout:
- Restore row weights for proportional scaling when maximized
- CTkScrollableFrame handles overflow when window is small

Step 3:
- Log xsct/vivado tool output (last 20 lines per result)
  for detailed diagnostics

Step 4:
- UART boot wait: monitor for IP/version via UART after reboot
- If UART unavailable, fall back to config.boot_wait_delay
- Reduces unnecessary wait time when boot is detected early

Serial:
- Real-time port validation on selection change (CTkComboBox command)
- Persistent UART unavailable warning until valid port selected
- Auto-restart UART monitor on port switch
- Graceful cross-platform handling
2026-06-11 14:44:58 +08:00
yuysh 9b07a4d5ca feat: add scrollbar via CTkScrollableFrame for window resize
- Replace main_frame CTkFrame → CTkScrollableFrame for vertical scrollbar
- Remove main_frame.grid_rowconfigure weight — content natural height
- Remove right_frame.grid_rowconfigure weight — prevents squashing
- Left column (workflow + log) retains weight=1 for dominant width
- Right column (config/serial/utility/status) keeps natural narrow width
2026-06-11 14:31:17 +08:00
yuysh 84142bb229 🎨 style: compact 2-col Utility panel, both reboot buttons red
- Reboot (UDP) now uses DANGER_COLOR like Reboot (JTAG)
- 2×2 grid layout: reboot row + version row
- Buttons stretch with sticky=ew, equal column weights
- Title spans both columns
2026-06-11 14:27:48 +08:00
yuysh f04fbe2130 feat: add TFTP download verify, Step 4 sub-steps, and fix UI layout
- Implement _tftp_get() for downloading files from Zynq via TFTP
- Add TftpSubStepFrame widget for Step 4 progress display
- Refactor Step 4 into 5 sub-steps: Upload, Download Verify, CRC Check, Reboot, Boot Verify
- Fix SubStepFrame.reset() bug that caused collapse after execution
- Fix layout overlap: Step 4 sub-step and progress bar now use separate rows
- Improve ProgressIndicator appearance with better styling and spacing
- Fix UART monitor to start regardless of _uart_available flag
- Add CRC32 verification between uploaded and downloaded files
2026-06-11 11:47:35 +08:00
Jeremy Shen 5ce544cd3c 🐛 fix: recover JTAG DAP after flash programming with system reset
- Add preemptive rst -system at Step 3 start to clean post-flash DAP state
- Replace ineffective SLCR write with targets 1 → rst -system recovery
- Exit with clear power-cycle instructions when DAP recovery fails
- Show prominent DAP error guidance in GUI for Step 3 and Run All mode

Verified: Step 1→2→3 now passes end-to-end (340s) without board power cycle.
2026-06-10 18:29:26 +08:00
Jeremy Shen 73b8225a27 feat: DAP error detection and recovery in Step 3 TCL
- TCL now checks for ARM core visibility before attempting flow
- If ARM cores are not found (DAP APB transaction error):
  * Logs WARNING with target list
  * Attempts SLCR system reset (0xF8000200)
  * Reconnects after recovery attempt
- DAP errors detected in xsct output and logged to GUI log
- Result message shows 'DAP error — board needs power cycle'
2026-06-10 17:52:14 +08:00
Jeremy Shen 0568315b83 fix: re-read config.yaml before each workflow run
_reload_config(): re-reads config.yaml and updates all UI selectors
(FSBL, BIT, ELF, BIN, FW BIN) and IP/xilinx_path fields with latest
values from file.

Called at the top of both _execute_step() and _run_all_steps(),
so changing config.yaml externally is picked up before every run.
2026-06-10 17:04:32 +08:00
Jeremy Shen 7067ce273f feat: user TCL flow + parser dedup + Python TFTP
1. bitstream_programmer — combined BIT+ELF flow (user's TCL):
   - targets -set -nocase -filter {name =~ "arm*#0"}
   - rst -processor (bypass BootROM, block Flash boot)
   - dow fsbl → con → stop (FSBL hardware init)
   - fpga -f bit (configure PL)
   - dow app → con (run bootloader)
   - Single xsct session, no ps7_init dependency

2. serial_monitor — parser fixes:
   - Boot mode parsing: 'Boot mode is JTAG' → Boot=JTAG
   - Version noise filter: rejects '3.1','update','mode','loaded'
   - Deduplication: UartMonitor only emits when (boot_mode, IP, ver) changes
   - Fixed false match: 'Boot mode' no longer captured as version

3. tftp_manager — pure Python TFTP client:
   - _tftp_put(): RFC 1350 WRQ/DATA/ACK via UDP socket
   - No external tftp command dependency
   - tftp_download(): documented as not-implemented (needs PC server)
2026-06-10 16:57:54 +08:00
Jeremy Shen 9a7a625c46 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)
2026-06-10 16:20:12 +08:00
Jeremy Shen 47569a0e43 refactor: ELF step uses FSBL instead of ps7_init
Problem: dow app.elf to DDR (0x100000) failed with MMU section
translation fault because the CPU had MMU enabled from previously
executed Flash code.  ps7_init could reset this but user wants
FSBL-based flow.

Fix: run_elf() now downloads FSBL first (to OCM 0x0 — always
accessible), runs it to initialize clocks/DDR/MIO and force JTAG
boot mode, then downloads the app ELF to DDR.

New flow:
  connect → targets 2 → dow fsbl.elf → con → after 3s → stop
  → dow app.elf → mwr 0xF800025C 0x1 → con

Fallback: if no FSBL configured, use direct dow (legacy path).

Removed: _run_elf_with_xsct (2-attempt ps7_init recovery) replaced
by _run_elf_via_fsbl and _run_elf_direct.
2026-06-10 16:09:05 +08:00
Jeremy Shen 8b2fac931c fix: FirmWare version parsing + prevent boot-from-Flash in ELF step
Issue 1 — FirmWare version not parsed:
  Input: [Info] FirmWare:V1.2.1.2.1_11_07_pmu_log3
  Fix:  regex [Ff]irm[Ww]are:?\s*  (was [Ff]irmware\s+ — no : or camelCase)
        Plus broader version capture: [Vv]\d+\.\d+\.\d+[^\s;,\n]*

Issue 2 — ELF loaded from Flash instead of downloaded ELF:
  Root cause: Zynq devcfg.CTRL (0xF800025C) PCAP_PROG_B may trigger
  reconfiguration from Flash after fpga, causing CPU to jump to
  Flash-loaded code instead of our dow'd ELF.

  BIT step: add ps7_post_config after fpga (finalizes PL-PS interfaces)
             add mwr 0xF800025C 0x1 (disables PCAP reconfig)
  ELF step: add mwr 0xF800025C 0x1 after dow, before con
             (both attempt-1 and attempt-2 fallback)
2026-06-10 15:50:31 +08:00
Jeremy Shen 09be20791f feat: forward UART parsed boot info to main log window
UartMonitorWindow now accepts on_log callback. When boot info
(IP, version) is parsed from serial output, it:
- Appears in the UART window's own log (prefix [UART])
- Forwards to main window's log panel via on_log callback
- Unified [UART] prefix across both windows (was [BOOT] in
  UART window, [UART] Boot detected: in main log)
2026-06-10 15:40:09 +08:00
Jeremy Shen b294dfe58a fix: ELF step no longer re-runs ps7_init unnecessarily
Problem: _run_elf_with_xsct() ALWAYS appended ps7_init before dow,
even though PS was already initialized by the prior BIT download step.
Re-running ps7_init after FPGA config resets PS registers (clocks,
DDR, MIO), which breaks subsequent ELF download.

Fix: two-attempt strategy —
  1. First try WITHOUT ps7_init (DAP alive from BIT step):
     connect → targets 2 → dow → con
  2. If that fails, retry WITH ps7_init as DAP recovery:
     connect → targets 1 → source ps7_init.tcl → ps7_init → targets 2 → dow → con

Additional improvements:
- Inner _run() helper deduplicates TCL execution logic
- Better progress messages distinguish attempts
- Extended success phrase detection (adds 'Successfully downloaded')
- Nested TimeoutExpired handling per attempt
2026-06-10 15:30:11 +08:00
Jeremy Shen a47e3b78ae fix: avoid serial port conflict between workflow and UART window
Flow execution's _start_uart_monitor() now checks if a UART window
is already monitoring before starting its own UartMonitor instance.
This prevents two UartMonitor threads from fighting over the same
serial port.

Changes:
- UartMonitorWindow: add is_monitoring property
- MainWindow._uart_window: store reference to open UART window
- _start_uart_monitor(): skip if UART window already monitoring
- _on_close(): destroy UART window on main window close
2026-06-10 15:23:28 +08:00
Jeremy Shen 2695180c8f fix: auto-start UART monitoring on port open success
_on_open_success(): directly call _on_start() instead of just
enabling the button. User no longer needs to click Start —
monitoring begins automatically when the port is available.

Start/Stop buttons remain for manual control.
2026-06-10 15:21:12 +08:00
Jeremy Shen 8130578f70 fix: remove grab_set() to allow main window interaction
UART Monitor window was modal (grab_set) preventing main window
from being used while the UART window is open. Remove grab_set
to make it modeless.
2026-06-10 15:04:39 +08:00
Jeremy Shen 778bee5d19 fix: import DARK_CARD + use hasattr for vitis_path
- widgets.py: add DARK_CARD to gui.styles imports
- zynq_checker.py: wrap config.vitis_path with hasattr
- bitstream_programmer.py: wrap config.vitis_path with hasattr
2026-06-10 15:00:15 +08:00
Jeremy Shen b4126a14ac fix: add missing DARK_CARD import in widgets.py 2026-06-10 14:58:48 +08:00
Jeremy Shen 7afc9051ab fix: defer grab_set() to avoid 'window not viewable' error
CTkToplevel grab_set() fails when called before the window is
fully realized on screen. Defer to after(50ms) so the window
has time to become viewable.
2026-06-10 14:53:22 +08:00
Jeremy Shen d5fd816ff9 feat: UART Monitor standalone window
Replace 'Read Serial' button with 'Open UART' that opens a new
UART Monitor window (CTkToplevel) with:

- Title bar with status indicator (● connecting/ready/monitoring/error)
- Port info display + Start/Stop/Clear buttons
- Large scrollable log area (CTkTextbox) with monospace font
- Auto-scroll to bottom, line counter in status bar
- Boot info parsing (IP, version) shown as [BOOT] prefix
- Background serial monitoring via UartMonitor class

UART unavailable handling:
- On init, tries to open serial port in background thread
- If port can't open → shows 'UART 不可用' state with error message
- If port opens → shows 'Ready', user clicks Start to begin monitoring

Window features:
- Modal (grab_set) — focus stays on monitor window
- Clean up UartMonitor on close (WM_DELETE_WINDOW handler)
- Thread-safe UI updates via after(0, ...)
- Reuses existing UartMonitor from serial_monitor.py
2026-06-10 14:51:13 +08:00
Jeremy Shen 93ab224d6d feat: Xilinx Kit directory selector + multi-version scan
Configuration panel now has a 'Xilinx Kit:' row at the top with:
- Entry field showing current path
- [Browse] button using system directory dialog (cross-platform)
- Select root directory (e.g. /data/xilinx or C:/Xilinx)

vitis_checker.py now scans xilinx_root subdirectories:
  1. System PATH (shutil.which) — works on all platforms
  2. Legacy vitis_path/bin
  3. xilinx_root/Vitis/*/bin and Vivado/*/bin (all versions, newest first)
  4. Common install paths (Windows: C:/Xilinx, Linux: /data/xilinx, /opt/xilinx)

Multi-version: scans all version directories under Vitis/ and Vivado/,
picks the newest version for each tool.

Config: xilinx_path replaces vitis_path (backward compat kept)
2026-06-10 14:27:47 +08:00
Jeremy Shen 699ad82834 🐛 fix: sub-step default expanded, real-time checkbox, startup freeze, color sync
1. SubStepFrame defaults to expanded (▼), not collapsed
2. erase_all checkbox has command=_on_erase_toggle → immediate config update
3. _deferred_init runs _check_vitis/_refresh_ports in background thread
   Fixes UI freeze during startup (subprocess.run for tool versions)
4. Sub-step colors now match StepHeader:
   - pending: gray ACCENT_PENDING (#888888)
   - running: blue CIRCLE_RUNNING (#0078D4) with pulse animation
   - complete: green ACCENT_COMPLETE (#107C10)
   - error: red ACCENT_ERROR (#D13438)
   - Running sub-step pulses between light/dark blue like StepHeader
2026-06-10 14:11:59 +08:00
Jeremy Shen 35bd6e600c Revert sub-step custom colors — use default CTkLabel white text
SubStepFrame now uses default CustomTkinter label colors (white
in dark mode, black in light mode) instead of manually set
CIRCLE_*/ACCENT_* colors that were unreadable on dark backgrounds.
Simplified grid layout — no nested sub-frames.
2026-06-10 13:54:06 +08:00
Jeremy Shen e3e3763dc4 🐛 fix: sub-step readability, erase_all workaround, error state
Fix 1 — SubStepFrame readability:
- FONT_BODY (12pt) instead of FONT_SMALL dots/labels
- Number-circle colored dots (CIRCLE_PENDING/RUNNING/COMPLETE/ERROR)
- Accent-colored labels matching StepHeader (ACCENT_*)
- Percentage label next to running phase name

Fix 2 — erase_all full-chip erase:
- Creates temp zero-filled file of flash capacity (from flash_model)
- Uses -erase_only with dummy file instead of failing -erase_all flag
- Supports s25fl256s1 (32MB) which lacks density info for -erase_all
- _flash_capacity() maps model name→bytes

Fix 3 — Error state:
- set_phase('error') shows ✗ red on all sub-steps
- Only shows 'done' (green ●) on actual success

Fix 4 — Style consistency:
- Sub-step colors match StepHeader: CIRCLE_*/ACCENT_*/SUCCESS/DANGER
2026-06-10 13:51:41 +08:00
Jeremy Shen 95c04d94ae feat: collapsible flash sub-steps UI + erase_all checkbox
Rename erase_before_program → erase_all (semantics fix):
- erase_all: true  → -erase_all -erase_only (entire flash chip)
- erase_all: false → -erase_only (sectors matching BIN size)
Both options always erase; the checkbox controls scope, not yes/no.

UI additions:
- SubStepFrame widget: collapsible Erase/Program/Verify sub-steps
  with status dots (○/◉/●) and colored labels
- Automatically expands on Step 2 start, collapses on completion
- Phase and progress driven by real-time program_flash output parsing
- Checkbox label: '整片Flash擦除 (Erase entire flash)'

subprocess_utils.py phase detection:
- Parses 'Performing Erase/Program/Verify Operation' for phase transitions
- Emits 'phase' callbacks (erase/program/verify/done)
- Progress % tracked within current phase
2026-06-10 13:45:57 +08:00
Jeremy Shen ea3e3b8ac7 feat: flash phase tracking, erase checkbox, flash_model config
Step 2 UI now shows real-time flash operation phases parsed from program_flash output:
- Erase: "Performing Erase Operation..." → sf erase → Erased: OK
- Program: "Performing Program Operation..." → write blocks 0%→100%
- Verify: "Performing Verify Operation..." → read+cmp 0%→100%

Configuration additions:
- erase_before_program checkbox: controls whether to pre-erase before programming
- flash_model: chip type for capacity reference (s25fl256s1 = 32 MiB)

subprocess_utils.py now emits 'phase' callbacks (erase/program/verify/done)
detected from program_flash output transitions.
2026-06-10 13:39:11 +08:00
Jeremy Shen 2c48224aea feat: UART monitor, step timeouts, and non-blocking subprocess output
Based on real hardware timing data from XC7Z100 testing:

UART Monitoring:
- New UartMonitor class in serial_monitor.py: background thread reads
  serial output continuously, parses for IP/version/boot messages
- GUI starts monitor when workflow runs, stops on completion/close
- Serial lines flow into log display in real time

Step Timing:
- New step_timeouts config (experience × 2):
  check_env:30s flash_erase:120s flash_program:600s
  bitstream:60s elf_download:60s tftp_reboot:120s
- GUI shows estimated timeout before each step
- Shows actual elapsed time after each step completes
- All subprocess calls now use config-driven timeouts

Non-blocking Output:
- New subprocess_utils.py: run_streaming() uses Popen + threaded
  stdout/stderr readers, parses WARNING/ERROR/progress% in real time
- flash_programmer and bitstream_programmer use it
- Callbacks receive categorized events (out/err/warn/error/progress/done)
2026-06-10 13:21:32 +08:00
Jeremy Shen ab7264d5c7 🐛 fix(xsct): PS init before FPGA config keeps JTAG DAP alive
Root cause: fpga -file breaks the DAP unless PS is initialized first.
Solution: source ps7_init.tcl + ps7_init + targets 1 BEFORE fpga -file.

Changes:
- _program_with_xsct: init PS (clocks/DDR/MIO) before fpga -file
  → DAP stays alive, subsequent ELF download works
- _run_elf_with_xsct: simplified - no reconnect needed, DAP is healthy
  (kept ps7_init fallback for recovery)
- program_bitstream: auto-detects ps7_init.tcl from BIT file directory

Verified on hardware:
  Session 1 (BIT): connect → ps7_init → fpga → exit  (13s) ✓
  Session 2 (ELF): connect → targets 2 → dow → con    (9s)  ✓
2026-06-10 12:53:02 +08:00
Jeremy Shen 80fb704221 🐛 fix(xsct): TCL file instead of -eval, add ps7_init, remove broken Vivado ELF path
Fixes found during real hardware test:

bitstream_programmer.py:
- _program_with_xsct: use TCL file instead of xsct -eval (quit→exit fix)
- _run_elf_with_xsct: add reconnect + source ps7_init.tcl after PL config,
  use target ID 2 instead of name filter, TCL file instead of -eval
- _find_ps7_init_tcl: auto-detect ps7_init.tcl from BIT file directory
- run_elf: remove Vivado fallback (program_hw_cpu is xsct, not Vivado)
- Remove dead _run_elf_with_vivado (Vivado HM can't program PS CPU)

flash_programmer.py:
- wipe_flash: -erase_only without -erase_all (s25fl256s1 compat fix)
2026-06-10 12:47:59 +08:00
Jeremy Shen cddacc5d9f ♻️ refactor(toolchain): replace impact/bootgen with xsct/program_flash + add fsbl
BREAKING: Replaces obsolete impact (ISE-era) and misused bootgen with
the correct Xilinx 2018.3–2023.2 toolchain.

Toolchain changes:
- vitis_checker: impact → xsct + program_flash + bootgen detection
- flash_programmer: bootgen/impact → program_flash (erase + program + verify)
- bitstream_programmer: impact → xsct fpga/dow (primary), vivado (fallback)
- Removed duplicated _get_impact_path() — now shared from vitis_checker

Config additions:
- fsbl_elf_path: FSBL ELF required by program_flash for QSPI init
- flash_type: QSPI mode (default: qspi-x4-single)

GUI additions:
- FSBL ELF file selector in Configuration panel
- Step 2 validates FSBL ELF is selected
- Step 1 now shows xsct/program_flash/bootgen in tool check
2026-06-10 12:13:02 +08:00
Jeremy Shen 1ad2dcdeb9 fix(config): correct path to project root config.yaml
- _find_config() and _get_user_config_path() now use parent.parent.parent
  to navigate from src/gui/main_window.py to project root
- User config now saves to /data/work/prj/Zynq_Flasher/config.yaml
2026-06-10 11:50:14 +08:00
Jeremy Shen 8f83fa9eae fix(config): save user config to project root config.yaml
- _find_config() now looks for config.yaml at project root first
- _get_user_config_path() returns project root config.yaml
- config/default_config.yaml remains as template (not for saving)
2026-06-10 11:47:43 +08:00
Jeremy Shen 1bbce6e72a 🐛 fix(config): save to config.yaml instead of default_config.yaml
- _find_config() now looks for config.yaml first, then default_config.yaml
- _load_config() sets save path to config.yaml when loading from default
- _save_config() always saves to config/config.yaml
- _on_close() saves to config.yaml on window close
- Added _get_user_config_path() helper method
2026-06-10 11:21:34 +08:00
Jeremy Shen 9de0153854 🐛 fix(gui): fix config save and UI freeze on startup
Config save fix:
- Add _sync_config_from_ui() to sync UI values to Config object
- Store _ip_string_var reference for IP field sync
- Call _sync_config_from_ui() before every save operation
- Fix _on_close to sync before saving

UI freeze fix:
- Defer _check_vitis() and _refresh_ports_initial() to _deferred_init()
- Use self.after(100, ...) to defer until window is displayed
- Window now appears immediately, blocking ops run in background
2026-06-10 10:58:29 +08:00
Jeremy Shen 7ac04a6bea feat(serial): add version test via 'ver()' command
- Add test_serial_version() to serial_monitor.py
- Sends 'ver()' command and parses version response
- Add 'Test Version' button in serial panel
- Displays version in status and log
2026-06-10 10:20:05 +08:00
Jeremy Shen fddcc3689a 🐛 fix(gui): make UART unavailable notification persistent
- Add dedicated UART warning label that doesn't get overwritten
- _show_uart_notification now uses persistent label instead of status display
- Warning message stays visible throughout workflow execution
2026-06-10 10:06:50 +08:00
Jeremy Shen b222bd4586 🐛 fix(gui): allow all steps to be runnable independently
- Remove dependency chain that prevented steps 2,3,4 from running
- All steps now have can_run=True
- Steps can be executed individually without running previous steps
2026-06-10 10:00:28 +08:00
Jeremy Shen f7cf179aa4 🐛 fix(gui): single-step execution now runs only selected step
- Add single_step parameter to _run_steps_worker
- _execute_step now passes single_step=True
- _run_all_steps passes single_step=False explicitly
- Single step execution no longer runs subsequent steps
2026-06-10 09:53:42 +08:00
Jeremy Shen 0af388a8f6 🐛 fix(gui): add time import and fix left panel layout scaling
- Add missing 'import time' for _run_steps_worker
- Fix left panel layout: remove rowspan=2 to prevent overflow
- Left panel now correctly occupies only lower half of window
2026-06-10 09:46:28 +08:00
Jeremy Shen 62609b6e80 feat(jtag): enhance Zynq JTAG detection with PS/PL status
- Remove IP check from Step 1 environment check
- Add PS (ARM DAP) and PL detection to zynq_checker
- Log detailed JTAG chain information in Step 1
- Show PS/PL detection status in GUI logs
- Display JTAG chain devices with type classification
2026-06-10 09:43:02 +08:00
Jeremy Shen 2bde6d869e feat(jtag): add Zynq JTAG presence detection via hw_server
- Add zynq_checker.py module to detect Zynq devices on JTAG chain
- Connects to hw_server via Vivado batch mode
- Parses JTAG device names to extract part number (e.g. XC7Z100)
- Integrates into Step 1 environment check in main_window.py
- Shows JTAG status in header (JTAG: XC7Z100 ✓)
2026-06-09 18:33:50 +08:00
Jeremy Shen 90c3dca8b8 fix(vitis): add xsct fallback for impact detection in Xilinx 2023.2
- vitis_checker: shutil.which() + xsct fallback for impact detection
- flash_programmer: _get_impact_path now checks xsct as fallback
- bitstream_programmer: _get_impact_path now checks xsct as fallback
- GUI: show '(via xsct)' alias note when xsct replaces impact
- ToolStatus: add 'alias' field for tracking which executable was found
2026-06-09 18:07:39 +08:00