From b294dfe58ad2503387900721ecb476187a718f65 Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Wed, 10 Jun 2026 15:30:11 +0800 Subject: [PATCH] fix: ELF step no longer re-runs ps7_init unnecessarily MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/bitstream_programmer.py | 140 +++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 50 deletions(-) diff --git a/src/bitstream_programmer.py b/src/bitstream_programmer.py index 3ac8a8c..5279a99 100644 --- a/src/bitstream_programmer.py +++ b/src/bitstream_programmer.py @@ -371,74 +371,114 @@ def _run_elf_with_xsct( ) -> BitstreamResult: """Download and run ELF using xsct's dow command. - Assumes PS was already initialized (by a prior BIT download step). - Falls back to PS init if DAP is broken. + PS was already initialized by the prior BIT download step, so the + DAP should be alive. First attempt skips ps7_init to avoid resetting + PS state. If DAP is broken, falls back to re-init PS. Args: xsct_path: Path to xsct executable. elf_path: Path to .elf file. callback: Optional progress callback. ps7_init_tcl: Optional ps7_init.tcl path for recovery. + timeout: Subprocess timeout in seconds. Returns: BitstreamResult with status. """ - if callback: - callback("progress", "Downloading ELF...") - - lines = ["connect"] - # Recovery: if DAP is broken, re-init PS - if ps7_init_tcl and Path(ps7_init_tcl).exists(): - lines.append("targets 1") - 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 = subprocess.run( - [xsct_path, tcl_file], - capture_output=True, - text=True, - timeout=timeout, - ) - os.unlink(tcl_file) + has_ps7 = bool(ps7_init_tcl and Path(ps7_init_tcl).exists()) - success = result.returncode == 0 - output = (result.stdout + result.stderr).strip() - - # 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"), + def _run(tcl: str) -> tuple[bool, str]: + """Run TCL via xsct, return (success, output).""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".tcl", delete=False + ) as tf: + tf.write(tcl) + tcl_file = tf.name + try: + result = subprocess.run( + [xsct_path, tcl_file], + capture_output=True, text=True, timeout=timeout, ) + os.unlink(tcl_file) + out = (result.stdout + result.stderr).strip() + ok = result.returncode == 0 or any( + p in out for p in ( + "Download successful", "Done", "done", + "Successfully downloaded", + ) + ) + return ok, out + except subprocess.TimeoutExpired: + os.unlink(tcl_file) + raise - return BitstreamResult( - step="elf", - success=success, - message="ELF executed successfully" if success else "ELF execution failed", - output=output[:4000], - ) + # ── Attempt 1: no ps7_init (DAP alive from BIT step) ── + if callback: + callback("progress", "Downloading ELF (DAP should be alive)...") + + tcl1 = "\n".join([ + "connect", + "targets 2", + "dow \"%s\"" % elf_path, + "con", + "exit", + ]) + try: + ok1, out1 = _run(tcl1) except subprocess.TimeoutExpired: - os.unlink(tcl_file) - return BitstreamResult( - step="elf", - success=False, - message="xsct ELF download timed out", + return BitstreamResult(step="elf", success=False, + message="ELF download timed out") + + if ok1: + if callback: + callback("complete", "ELF executed") + return BitstreamResult(step="elf", success=True, + message="ELF executed successfully", + output=out1[:4000]) + + # ── Attempt 2: re-init PS to recover DAP ── + if not has_ps7: + if callback: + callback("error", "ELF failed — no ps7_init.tcl for recovery") + return BitstreamResult(step="elf", success=False, + message="ELF execution failed", + output=out1[:4000]) + + if callback: + callback( + "progress", + "DAP broken, re-initializing PS then retrying ELF...", ) + tcl2 = "\n".join([ + "connect", + "targets 1", + "source \"%s\"" % ps7_init_tcl, + "ps7_init", + "targets 2", + "dow \"%s\"" % elf_path, + "con", + "exit", + ]) + try: + ok2, out2 = _run(tcl2) + except subprocess.TimeoutExpired: + return BitstreamResult(step="elf", success=False, + message="ELF download (recovery) timed out") + + if callback: + callback("complete" if ok2 else "error", + "ELF " + ("executed (recovery)" if ok2 else "failed after recovery")) + + return BitstreamResult( + step="elf", + success=ok2, + message=("ELF executed (DAP recovery)" if ok2 else "ELF execution failed"), + output=out2[:4000], + ) + # ── Full Workflow ───────────────────────────────────────────────────