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)
This commit is contained in:
Jeremy Shen
2026-06-10 14:27:47 +08:00
parent 699ad82834
commit 93ab224d6d
7 changed files with 132 additions and 69 deletions
+35 -8
View File
@@ -182,6 +182,8 @@ class MainWindow(ctk.CTk):
# Sync IP address
if self._ip_string_var:
self._config.zynq_ip = self._ip_string_var.get()
# Sync Xilinx Kit path
self._config.xilinx_path = self._xilinx_path_var.get()
# Sync serial port
self._config.serial_port = self._port_var.get()
# Sync file paths from FileSelectors
@@ -219,6 +221,17 @@ class MainWindow(ctk.CTk):
if self._config:
self._config.erase_all = self._erase_cb_var.get()
def _browse_xilinx_path(self) -> None:
"""Open directory dialog to select Xilinx root folder."""
from tkinter import filedialog
path = filedialog.askdirectory(title="Select Xilinx Root Directory")
if path:
self._xilinx_path_var.set(path)
if self._config:
self._config.xilinx_path = path
# Re-check tools with new path
self._check_vitis()
# ── UI Construction ────────────────────────────────────────
def _build_ui(self) -> None:
@@ -418,7 +431,7 @@ class MainWindow(ctk.CTk):
row=0, column=0, sticky="nsew",
padx=PADDING, pady=PADDING,
)
panel.grid_rowconfigure(7, weight=1)
panel.grid_rowconfigure(8, weight=1)
title = ctk.CTkLabel(
panel,
@@ -427,9 +440,23 @@ class MainWindow(ctk.CTk):
)
title.grid(row=0, column=0, padx=PADDING, pady=PADDING)
# ── Xilinx Kit path ──
xilinx_frame = ctk.CTkFrame(panel)
xilinx_frame.grid(row=1, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
xilinx_frame.grid_columnconfigure(1, weight=1)
ctk.CTkLabel(xilinx_frame, text="Xilinx Kit:", font=FONT_BODY).grid(row=0, column=0, sticky="w")
self._xilinx_path_var = ctk.StringVar(value=self._config.xilinx_path)
self._xilinx_entry = ctk.CTkEntry(xilinx_frame, textvariable=self._xilinx_path_var)
self._xilinx_entry.grid(row=0, column=1, sticky="nsew", padx=PADDING_SMALL)
ctk.CTkButton(
xilinx_frame, text="Browse", font=FONT_SMALL, width=70,
command=self._browse_xilinx_path,
).grid(row=0, column=2, padx=(0, PADDING_SMALL))
# IP address
ip_frame = ctk.CTkFrame(panel)
ip_frame.grid(row=1, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
ip_frame.grid(row=2, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
ip_frame.grid_columnconfigure(1, weight=1)
ctk.CTkLabel(ip_frame, text="Zynq IP:", font=FONT_BODY).grid(row=0, column=0, sticky="w")
@@ -446,7 +473,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=2, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._bit_selector.grid(row=3, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# ELF path (Bootloader)
self._elf_selector = FileSelector(
@@ -457,7 +484,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=3, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._elf_selector.grid(row=4, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Bootloader BIN path (Flash programming)
self._bootloader_bin_selector = FileSelector(
@@ -468,7 +495,7 @@ 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=4, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
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(
@@ -479,7 +506,7 @@ class MainWindow(ctk.CTk):
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=5, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._fsbl_selector.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Flash options
flash_frame = ctk.CTkFrame(panel)
@@ -512,7 +539,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=7, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._firmware_bin_selector.grid(row=8, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Save button
save_btn = ctk.CTkButton(
@@ -521,7 +548,7 @@ class MainWindow(ctk.CTk):
font=FONT_SMALL,
command=self._save_config,
)
save_btn.grid(row=8, column=0, padx=PADDING, pady=PADDING)
save_btn.grid(row=9, column=0, padx=PADDING, pady=PADDING)
def _build_serial_panel(self, parent: ctk.CTkFrame) -> None:
"""Build the serial monitor panel."""