From 80fb704221399ba893cfe5b9b1f4d3910c1eca71 Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Wed, 10 Jun 2026 12:47:59 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(xsct):=20TCL=20file=20instea?= =?UTF-8?q?d=20of=20-eval,=20add=20ps7=5Finit,=20remove=20broken=20Vivado?= =?UTF-8?q?=20ELF=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/bitstream_programmer.py | 189 +++++++++++++++++++----------------- src/flash_programmer.py | 9 +- 2 files changed, 106 insertions(+), 92 deletions(-) diff --git a/src/bitstream_programmer.py b/src/bitstream_programmer.py index 7390c05..3801b2e 100644 --- a/src/bitstream_programmer.py +++ b/src/bitstream_programmer.py @@ -67,6 +67,38 @@ def _get_vivado_path(vitis_path: str) -> str | None: return None +def _find_ps7_init_tcl(config: Config) -> str: + """Find the ps7_init.tcl file for PS initialization. + + Checks: + 1. Same directory as bootloader BIT file (hw_platform) + 2. ps7_init_tcl_path in config (if added later) + + Args: + config: Application configuration. + + Returns: + Path to ps7_init.tcl, or empty string if not found. + """ + # 1. Check config attribute (if added) + if hasattr(config, 'ps7_init_tcl_path') and config.ps7_init_tcl_path: + p = Path(config.ps7_init_tcl_path) + if p.exists(): + return str(p) + + # 2. Same directory as BIT file (typical hw_platform layout) + if config.bootloader_bit_path: + bit_dir = Path(config.bootloader_bit_path).parent + candidates = [ + bit_dir / "ps7_init.tcl", + ] + for c in candidates: + if c.exists(): + return str(c) + + return "" + + def _run_command( cmd: list[str], timeout: int = 120, @@ -172,21 +204,35 @@ def _program_with_xsct( if callback: callback("progress", "Using xsct to download bitstream...") - # xsct TCL script: connect to hw_server, target FPGA, download bitstream + # xsct TCL: connect to hw_server, target FPGA, download bitstream tcl_script = f""" connect targets fpga -file "{bit_path}" -quit +exit """ + import tempfile, os + with tempfile.NamedTemporaryFile(mode='w', suffix='.tcl', delete=False) as f: + f.write(tcl_script) + tcl_file = f.name + try: - result = _run_command( - [xsct_path, "-eval", tcl_script], + result = subprocess.run( + [xsct_path, tcl_file], + capture_output=True, + text=True, timeout=120, - callback=callback, ) + os.unlink(tcl_file) success = result.returncode == 0 output = (result.stdout + result.stderr).strip() + + if callback: + callback( + "complete" if success else "error", + "Bitstream " + ("loaded" if success else "failed"), + ) + return BitstreamResult( step="bitstream", success=success, @@ -194,6 +240,7 @@ quit output=output[:4000], ) except subprocess.TimeoutExpired: + os.unlink(tcl_file) return BitstreamResult( step="bitstream", success=False, @@ -282,37 +329,38 @@ def run_elf( if callback: callback("start", f"Running ELF: {elf_path.name}...") - # Try xsct first (preferred) + # Use xsct for CPU operations (Vivado HM cannot program PS CPU) xsct = get_xsct_path(config.vitis_path) - if xsct: - return _run_elf_with_xsct(xsct, elf_path, callback) + if not xsct: + return BitstreamResult( + step="elf", + success=False, + message="xsct not found in PATH (required for CPU operations)", + ) - # Fallback to Vivado - vivado = _get_vivado_path(config.vitis_path) - if vivado: - return _run_elf_with_vivado(vivado, elf_path, config, callback) - - return BitstreamResult( - step="elf", - success=False, - message="Neither xsct nor vivado found in PATH", - ) + # Auto-detect ps7_init.tcl from config or BIT file directory + ps7_tcl = _find_ps7_init_tcl(config) + return _run_elf_with_xsct(xsct, elf_path, callback, ps7_tcl) def _run_elf_with_xsct( xsct_path: str, elf_path: Path, callback: ProgressCallback = None, + ps7_init_tcl: str = "", ) -> BitstreamResult: """Download and run ELF using xsct's dow command. - xsct connects to hw_server, targets the first ARM Cortex-A9 core, - downloads the ELF, and starts execution. + After PL configuration (via fpga), the DAP may be disrupted. + This function reconnects, sources ps7_init.tcl to initialize + the PS, then downloads the ELF to Cortex-A9 #0. Args: xsct_path: Path to xsct executable. elf_path: Path to .elf file. callback: Optional progress callback. + ps7_init_tcl: Path to ps7_init.tcl for PS initialization. + Auto-detected from BIT file directory if empty. Returns: BitstreamResult with status. @@ -320,27 +368,45 @@ def _run_elf_with_xsct( if callback: callback("progress", "Using xsct to download ELF...") - # xsct TCL: connect, target ARM core #0, download ELF - tcl_script = f""" -connect -targets -set -filter {{name =~ "Cortex-A9 MPCore #0" || name =~ "ARM*#0"}} -dow "{elf_path}" -con -quit -""" + # Build TCL: reconnect (DAP may be broken after PL config), + # source ps7_init for PS init, then dow + con + lines = ["connect"] + if ps7_init_tcl and Path(ps7_init_tcl).exists(): + lines.append(f'source "{ps7_init_tcl}"') + lines.append("ps7_init") + lines.append("targets 2") + lines.append(f'dow "{elf_path}"') + lines.append("con") + lines.append("exit") + tcl_script = "\n".join(lines) + + import tempfile, os + with tempfile.NamedTemporaryFile(mode='w', suffix='.tcl', delete=False) as f: + f.write(tcl_script) + tcl_file = f.name + try: - result = _run_command( - [xsct_path, "-eval", tcl_script], - timeout=60, - callback=callback, + result = subprocess.run( + [xsct_path, tcl_file], + capture_output=True, + text=True, + timeout=120, ) + os.unlink(tcl_file) + success = result.returncode == 0 output = (result.stdout + result.stderr).strip() - # xsct may return non-zero even if download succeeded (common quirk) - if "Target disconnected" in output and "Success" not in output: + # xsct may return non-zero even if download succeeded + if any(phrase in output for phrase in ("Download successful", "Done", "done")): success = True + if callback: + callback( + "complete" if success else "error", + "ELF " + ("executed" if success else "failed"), + ) + return BitstreamResult( step="elf", success=success, @@ -348,6 +414,7 @@ quit output=output[:4000], ) except subprocess.TimeoutExpired: + os.unlink(tcl_file) return BitstreamResult( step="elf", success=False, @@ -355,60 +422,6 @@ quit ) -def _run_elf_with_vivado( - vivado_path: str, - elf_path: Path, - config: Config, - callback: ProgressCallback = None, -) -> BitstreamResult: - """Download and run ELF using Vivado Hardware Manager. - - Args: - vivado_path: Path to vivado executable. - elf_path: Path to .elf file. - config: Application configuration (for reboot_timeout). - callback: Optional progress callback. - - Returns: - BitstreamResult with status. - """ - if callback: - callback("progress", "Using Vivado Hardware Manager for ELF...") - - run_timeout = config.reboot_timeout * 1000 if config.reboot_timeout else 30000 - tcl_content = f""" -open_hw_manager -connect_hw_server -open_hw_target -current_hw_device [lindex [get_hw_devices] 0] -refresh_hw_device -program_hw_cpu -data_file "{elf_path}" -run_hw_cpu {run_timeout} -quit -""" - tcl_path = elf_path.parent / "temp_run.tcl" - try: - with open(tcl_path, "w") as f: - f.write(tcl_content) - - result = _run_command( - [vivado_path, "-mode", "batch", "-source", str(tcl_path)], - timeout=120, - callback=callback, - ) - success = result.returncode == 0 - output = (result.stdout + result.stderr).strip() - - return BitstreamResult( - step="elf", - success=success, - message="ELF executed successfully" if success else "ELF execution failed", - output=output[:4000], - ) - finally: - tcl_path.unlink(missing_ok=True) - - # ── Full Workflow ─────────────────────────────────────────────────── diff --git a/src/flash_programmer.py b/src/flash_programmer.py index 88b9e8e..ba8d7f0 100644 --- a/src/flash_programmer.py +++ b/src/flash_programmer.py @@ -99,10 +99,11 @@ def wipe_flash( config: Config, callback: ProgressCallback = None, ) -> FlashResult: - """Erase the entire QSPI flash using program_flash. + """Erase QSPI flash sectors matching the bootloader image size. - Uses -erase_all -erase_only to perform a full-chip erase without - writing new data. + Uses -erase_only to erase only the sectors covered by the BIN file. + Avoids -erase_all which fails on some flash chips (e.g. s25fl256s1) + that don't report memory density information. Args: config: Application configuration (needs fsbl_elf_path). @@ -131,7 +132,7 @@ def wipe_flash( callback("start", "Erasing QSPI flash...") cmd = _build_program_flash_cmd_base(flash_tool, config) - cmd += ["-erase_all", "-erase_only"] + cmd += ["-erase_only"] # Erase sectors matching the BIN size (safer than -erase_all) try: result = _run_command(cmd, timeout=300, callback=callback)