🐛 fix(xsct): TCL file instead of -eval, add ps7_init, remove broken Vivado ELF path

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)
This commit is contained in:
Jeremy Shen
2026-06-10 12:47:59 +08:00
parent cddacc5d9f
commit 80fb704221
2 changed files with 106 additions and 92 deletions
+98 -85
View File
@@ -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)
# Fallback to Vivado
vivado = _get_vivado_path(config.vitis_path)
if vivado:
return _run_elf_with_vivado(vivado, elf_path, config, callback)
if not xsct:
return BitstreamResult(
step="elf",
success=False,
message="Neither xsct nor vivado found in PATH",
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)
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 ───────────────────────────────────────────────────
+5 -4
View File
@@ -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)