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.
This commit is contained in:
Jeremy Shen
2026-06-10 16:09:05 +08:00
parent 8b2fac931c
commit 47569a0e43
+114 -72
View File
@@ -329,7 +329,9 @@ def run_elf(
) -> BitstreamResult: ) -> BitstreamResult:
"""Download and run a .elf executable on the Zynq PS ARM core. """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: Args:
config: Application configuration. config: Application configuration.
@@ -347,10 +349,15 @@ def run_elf(
message=f"ELF file not found: {elf_path}", 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: if callback:
callback("start", f"Running ELF: {elf_path.name}...") callback("start", f"Running ELF: {elf_path.name}...")
# Use xsct for CPU operations (Vivado HM cannot program PS CPU)
xsct = get_xsct_path( xsct = get_xsct_path(
vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "", vitis_path=config.vitis_path if hasattr(config, 'vitis_path') else "",
xilinx_root=config.xilinx_path if hasattr(config, 'xilinx_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)", 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) 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, xsct_path: str,
elf_path: Path, elf_path: Path,
fsbl_path: str,
callback: ProgressCallback = None, callback: ProgressCallback = None,
ps7_init_tcl: str = "",
timeout: int = 60, timeout: int = 60,
) -> BitstreamResult: ) -> 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 FSBL loads to OCM (0x0) which is always accessible even when
DAP should be alive. First attempt skips ps7_init to avoid resetting MMU is enabled. FSBL initializes clocks/DDR/MIO and forces
PS state. If DAP is broken, falls back to re-init PS. JTAG boot mode, making DDR accessible for the app ELF.
Args: Args:
xsct_path: Path to xsct executable. 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. callback: Optional progress callback.
ps7_init_tcl: Optional ps7_init.tcl path for recovery.
timeout: Subprocess timeout in seconds. timeout: Subprocess timeout in seconds.
Returns: Returns:
@@ -393,99 +402,132 @@ def _run_elf_with_xsct(
""" """
import tempfile, os import tempfile, os
has_ps7 = bool(ps7_init_tcl and Path(ps7_init_tcl).exists()) if callback:
callback("progress", "Downloading FSBL to OCM...")
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",
])
def _run(tcl: str) -> tuple[bool, str]:
"""Run TCL via xsct, return (success, output)."""
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
mode="w", suffix=".tcl", delete=False mode="w", suffix=".tcl", delete=False
) as tf: ) as tf:
tf.write(tcl) tf.write(tcl)
tcl_file = tf.name tcl_file = tf.name
try: try:
result = subprocess.run( result = subprocess.run(
[xsct_path, tcl_file], [xsct_path, tcl_file],
capture_output=True, text=True, timeout=timeout, capture_output=True, text=True, timeout=timeout,
) )
os.unlink(tcl_file) os.unlink(tcl_file)
out = (result.stdout + result.stderr).strip() output = (result.stdout + result.stderr).strip()
ok = result.returncode == 0 or any(
p in out for p in ( success = result.returncode == 0 or any(
"Download successful", "Done", "done", p in output for p in (
"App running", "Download successful",
"Successfully downloaded", "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)...")
tcl1 = "\n".join([
"connect",
"targets 2",
"dow \"%s\"" % elf_path,
"mwr 0xF800025C 0x00000001",
"con",
"exit",
])
try:
ok1, out1 = _run(tcl1)
except subprocess.TimeoutExpired:
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: if callback:
callback( callback(
"progress", "complete" if success else "error",
"DAP broken, re-initializing PS then retrying ELF...", "ELF " + ("executed via FSBL" if success else "failed"),
) )
tcl2 = "\n".join([ 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")
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", "Downloading ELF directly...")
tcl = "\n".join([
"connect", "connect",
"targets 1",
"source \"%s\"" % ps7_init_tcl,
"ps7_init",
"targets 2", "targets 2",
"dow \"%s\"" % elf_path, "dow \"%s\"" % elf_path,
"mwr 0xF800025C 0x00000001", "mwr 0xF800025C 0x00000001",
"con", "con",
"exit", "exit",
]) ])
with tempfile.NamedTemporaryFile(
mode="w", suffix=".tcl", delete=False
) as tf:
tf.write(tcl)
tcl_file = tf.name
try: try:
ok2, out2 = _run(tcl2) result = subprocess.run(
except subprocess.TimeoutExpired: [xsct_path, tcl_file],
return BitstreamResult(step="elf", success=False, capture_output=True, text=True, timeout=timeout,
message="ELF download (recovery) timed out") )
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: if callback:
callback("complete" if ok2 else "error", callback("complete" if success else "error",
"ELF " + ("executed (recovery)" if ok2 else "failed after recovery")) "ELF " + ("executed" if success else "failed"))
return BitstreamResult( return BitstreamResult(
step="elf", step="elf",
success=ok2, success=success,
message=("ELF executed (DAP recovery)" if ok2 else "ELF execution failed"), message="ELF executed" if success else "ELF execution failed",
output=out2[:4000], output=output[:4000],
) )
except subprocess.TimeoutExpired:
os.unlink(tcl_file)
return BitstreamResult(step="elf", success=False,
message="ELF download timed out")
# ── Full Workflow ─────────────────────────────────────────────────── # ── Full Workflow ───────────────────────────────────────────────────