diff --git a/src/bitstream_programmer.py b/src/bitstream_programmer.py index fc5ab09..337138c 100644 --- a/src/bitstream_programmer.py +++ b/src/bitstream_programmer.py @@ -329,7 +329,9 @@ def run_elf( ) -> BitstreamResult: """Download and run a .elf executable on the Zynq PS ARM core. - Uses xsct's `dow` (download) command, with Vivado as fallback. + First downloads FSBL (First Stage Bootloader) to OCM (0x0) to + initialize clocks, DDR, MIO and force JTAG boot mode. Then + downloads the target ELF to DDR. Args: config: Application configuration. @@ -347,10 +349,15 @@ def run_elf( message=f"ELF file not found: {elf_path}", ) + fsbl_path = config.fsbl_elf_path if hasattr(config, 'fsbl_elf_path') else "" + if fsbl_path and not Path(fsbl_path).exists(): + if callback: + callback("warning", f"FSBL not found: {fsbl_path}, will try direct dow") + fsbl_path = "" + if callback: callback("start", f"Running ELF: {elf_path.name}...") - # Use xsct for CPU operations (Vivado HM cannot program PS CPU) xsct = get_xsct_path( vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_path') else "", @@ -362,30 +369,32 @@ def run_elf( message="xsct not found in PATH (required for CPU operations)", ) - # Auto-detect ps7_init.tcl from config or BIT file directory - ps7_tcl = _find_ps7_init_tcl(config) timeout = config.step_timeouts.get("elf_download", 60) - return _run_elf_with_xsct(xsct, elf_path, callback, ps7_tcl, timeout) + + if fsbl_path: + return _run_elf_via_fsbl(xsct, elf_path, fsbl_path, callback, timeout) + else: + return _run_elf_direct(xsct, elf_path, callback, timeout) -def _run_elf_with_xsct( +def _run_elf_via_fsbl( xsct_path: str, elf_path: Path, + fsbl_path: str, callback: ProgressCallback = None, - ps7_init_tcl: str = "", timeout: int = 60, ) -> BitstreamResult: - """Download and run ELF using xsct's dow command. + """Download and run ELF via FSBL bootloader. - 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. + FSBL loads to OCM (0x0) which is always accessible even when + MMU is enabled. FSBL initializes clocks/DDR/MIO and forces + JTAG boot mode, making DDR accessible for the app ELF. Args: xsct_path: Path to xsct executable. - elf_path: Path to .elf file. + elf_path: Path to target .elf file. + fsbl_path: Path to FSBL .elf file. callback: Optional progress callback. - ps7_init_tcl: Optional ps7_init.tcl path for recovery. timeout: Subprocess timeout in seconds. Returns: @@ -393,99 +402,132 @@ def _run_elf_with_xsct( """ import tempfile, os - has_ps7 = bool(ps7_init_tcl and Path(ps7_init_tcl).exists()) - - 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 - - # ── Attempt 1: no ps7_init (DAP alive from BIT step) ── if callback: - callback("progress", "Downloading ELF (DAP should be alive)...") + callback("progress", "Downloading FSBL to OCM...") - tcl1 = "\n".join([ + tcl = "\n".join([ "connect", "targets 2", + "dow \"%s\"" % fsbl_path, + "puts \"FSBL downloaded\"", + "con", + "after 3000", + "stop", + "puts \"FSBL done, CPU halted\"", "dow \"%s\"" % elf_path, "mwr 0xF800025C 0x00000001", "con", + "puts \"App running\"", "exit", ]) + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".tcl", delete=False + ) as tf: + tf.write(tcl) + tcl_file = tf.name + try: - ok1, out1 = _run(tcl1) + result = subprocess.run( + [xsct_path, tcl_file], + capture_output=True, text=True, timeout=timeout, + ) + os.unlink(tcl_file) + output = (result.stdout + result.stderr).strip() + + success = result.returncode == 0 or any( + p in output for p in ( + "App running", "Download successful", + "Successfully downloaded", + ) + ) + + if callback: + callback( + "complete" if success else "error", + "ELF " + ("executed via FSBL" if success else "failed"), + ) + + return BitstreamResult( + step="elf", + success=success, + message="ELF executed via FSBL" if success else "ELF execution failed", + output=output[:4000], + ) except subprocess.TimeoutExpired: + os.unlink(tcl_file) 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]) +def _run_elf_direct( + xsct_path: str, + elf_path: Path, + callback: ProgressCallback = None, + timeout: int = 60, +) -> BitstreamResult: + """Download and run ELF directly (no FSBL, assumes DAP alive). + + Used when no FSBL ELF is configured. Attempts direct dow + which only works if the CPU is in a clean state with MMU off. + + Args: + xsct_path: Path to xsct executable. + elf_path: Path to .elf file. + callback: Optional progress callback. + timeout: Subprocess timeout in seconds. + + Returns: + BitstreamResult with status. + """ + import tempfile, os if callback: - callback( - "progress", - "DAP broken, re-initializing PS then retrying ELF...", - ) + callback("progress", "Downloading ELF directly...") - tcl2 = "\n".join([ + tcl = "\n".join([ "connect", - "targets 1", - "source \"%s\"" % ps7_init_tcl, - "ps7_init", "targets 2", "dow \"%s\"" % elf_path, "mwr 0xF800025C 0x00000001", "con", "exit", ]) + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".tcl", delete=False + ) as tf: + tf.write(tcl) + tcl_file = tf.name + try: - ok2, out2 = _run(tcl2) + result = subprocess.run( + [xsct_path, tcl_file], + capture_output=True, text=True, timeout=timeout, + ) + os.unlink(tcl_file) + output = (result.stdout + result.stderr).strip() + + success = result.returncode == 0 or any( + p in output for p in ( + "Download successful", "Successfully downloaded", + ) + ) + + if callback: + callback("complete" if success else "error", + "ELF " + ("executed" if success else "failed")) + + return BitstreamResult( + step="elf", + success=success, + message="ELF executed" if success else "ELF execution failed", + output=output[:4000], + ) except subprocess.TimeoutExpired: + os.unlink(tcl_file) 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], - ) + message="ELF download timed out") # ── Full Workflow ───────────────────────────────────────────────────