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
+120 -78
View File
@@ -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 ───────────────────────────────────────────────────