🐛 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:
+98
-85
@@ -67,6 +67,38 @@ def _get_vivado_path(vitis_path: str) -> str | None:
|
|||||||
return 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(
|
def _run_command(
|
||||||
cmd: list[str],
|
cmd: list[str],
|
||||||
timeout: int = 120,
|
timeout: int = 120,
|
||||||
@@ -172,21 +204,35 @@ def _program_with_xsct(
|
|||||||
if callback:
|
if callback:
|
||||||
callback("progress", "Using xsct to download bitstream...")
|
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"""
|
tcl_script = f"""
|
||||||
connect
|
connect
|
||||||
targets
|
targets
|
||||||
fpga -file "{bit_path}"
|
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:
|
try:
|
||||||
result = _run_command(
|
result = subprocess.run(
|
||||||
[xsct_path, "-eval", tcl_script],
|
[xsct_path, tcl_file],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
timeout=120,
|
timeout=120,
|
||||||
callback=callback,
|
|
||||||
)
|
)
|
||||||
|
os.unlink(tcl_file)
|
||||||
success = result.returncode == 0
|
success = result.returncode == 0
|
||||||
output = (result.stdout + result.stderr).strip()
|
output = (result.stdout + result.stderr).strip()
|
||||||
|
|
||||||
|
if callback:
|
||||||
|
callback(
|
||||||
|
"complete" if success else "error",
|
||||||
|
"Bitstream " + ("loaded" if success else "failed"),
|
||||||
|
)
|
||||||
|
|
||||||
return BitstreamResult(
|
return BitstreamResult(
|
||||||
step="bitstream",
|
step="bitstream",
|
||||||
success=success,
|
success=success,
|
||||||
@@ -194,6 +240,7 @@ quit
|
|||||||
output=output[:4000],
|
output=output[:4000],
|
||||||
)
|
)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
|
os.unlink(tcl_file)
|
||||||
return BitstreamResult(
|
return BitstreamResult(
|
||||||
step="bitstream",
|
step="bitstream",
|
||||||
success=False,
|
success=False,
|
||||||
@@ -282,37 +329,38 @@ def run_elf(
|
|||||||
if callback:
|
if callback:
|
||||||
callback("start", f"Running ELF: {elf_path.name}...")
|
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)
|
xsct = get_xsct_path(config.vitis_path)
|
||||||
if xsct:
|
if not 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)
|
|
||||||
|
|
||||||
return BitstreamResult(
|
return BitstreamResult(
|
||||||
step="elf",
|
step="elf",
|
||||||
success=False,
|
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(
|
def _run_elf_with_xsct(
|
||||||
xsct_path: str,
|
xsct_path: str,
|
||||||
elf_path: Path,
|
elf_path: Path,
|
||||||
callback: ProgressCallback = None,
|
callback: ProgressCallback = None,
|
||||||
|
ps7_init_tcl: str = "",
|
||||||
) -> BitstreamResult:
|
) -> BitstreamResult:
|
||||||
"""Download and run ELF using xsct's dow command.
|
"""Download and run ELF using xsct's dow command.
|
||||||
|
|
||||||
xsct connects to hw_server, targets the first ARM Cortex-A9 core,
|
After PL configuration (via fpga), the DAP may be disrupted.
|
||||||
downloads the ELF, and starts execution.
|
This function reconnects, sources ps7_init.tcl to initialize
|
||||||
|
the PS, then downloads the ELF to Cortex-A9 #0.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
xsct_path: Path to xsct executable.
|
xsct_path: Path to xsct executable.
|
||||||
elf_path: Path to .elf file.
|
elf_path: Path to .elf file.
|
||||||
callback: Optional progress callback.
|
callback: Optional progress callback.
|
||||||
|
ps7_init_tcl: Path to ps7_init.tcl for PS initialization.
|
||||||
|
Auto-detected from BIT file directory if empty.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
BitstreamResult with status.
|
BitstreamResult with status.
|
||||||
@@ -320,27 +368,45 @@ def _run_elf_with_xsct(
|
|||||||
if callback:
|
if callback:
|
||||||
callback("progress", "Using xsct to download ELF...")
|
callback("progress", "Using xsct to download ELF...")
|
||||||
|
|
||||||
# xsct TCL: connect, target ARM core #0, download ELF
|
# Build TCL: reconnect (DAP may be broken after PL config),
|
||||||
tcl_script = f"""
|
# source ps7_init for PS init, then dow + con
|
||||||
connect
|
lines = ["connect"]
|
||||||
targets -set -filter {{name =~ "Cortex-A9 MPCore #0" || name =~ "ARM*#0"}}
|
if ps7_init_tcl and Path(ps7_init_tcl).exists():
|
||||||
dow "{elf_path}"
|
lines.append(f'source "{ps7_init_tcl}"')
|
||||||
con
|
lines.append("ps7_init")
|
||||||
quit
|
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:
|
try:
|
||||||
result = _run_command(
|
result = subprocess.run(
|
||||||
[xsct_path, "-eval", tcl_script],
|
[xsct_path, tcl_file],
|
||||||
timeout=60,
|
capture_output=True,
|
||||||
callback=callback,
|
text=True,
|
||||||
|
timeout=120,
|
||||||
)
|
)
|
||||||
|
os.unlink(tcl_file)
|
||||||
|
|
||||||
success = result.returncode == 0
|
success = result.returncode == 0
|
||||||
output = (result.stdout + result.stderr).strip()
|
output = (result.stdout + result.stderr).strip()
|
||||||
|
|
||||||
# xsct may return non-zero even if download succeeded (common quirk)
|
# xsct may return non-zero even if download succeeded
|
||||||
if "Target disconnected" in output and "Success" not in output:
|
if any(phrase in output for phrase in ("Download successful", "Done", "done")):
|
||||||
success = True
|
success = True
|
||||||
|
|
||||||
|
if callback:
|
||||||
|
callback(
|
||||||
|
"complete" if success else "error",
|
||||||
|
"ELF " + ("executed" if success else "failed"),
|
||||||
|
)
|
||||||
|
|
||||||
return BitstreamResult(
|
return BitstreamResult(
|
||||||
step="elf",
|
step="elf",
|
||||||
success=success,
|
success=success,
|
||||||
@@ -348,6 +414,7 @@ quit
|
|||||||
output=output[:4000],
|
output=output[:4000],
|
||||||
)
|
)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
|
os.unlink(tcl_file)
|
||||||
return BitstreamResult(
|
return BitstreamResult(
|
||||||
step="elf",
|
step="elf",
|
||||||
success=False,
|
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 ───────────────────────────────────────────────────
|
# ── Full Workflow ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,10 +99,11 @@ def wipe_flash(
|
|||||||
config: Config,
|
config: Config,
|
||||||
callback: ProgressCallback = None,
|
callback: ProgressCallback = None,
|
||||||
) -> FlashResult:
|
) -> 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
|
Uses -erase_only to erase only the sectors covered by the BIN file.
|
||||||
writing new data.
|
Avoids -erase_all which fails on some flash chips (e.g. s25fl256s1)
|
||||||
|
that don't report memory density information.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: Application configuration (needs fsbl_elf_path).
|
config: Application configuration (needs fsbl_elf_path).
|
||||||
@@ -131,7 +132,7 @@ def wipe_flash(
|
|||||||
callback("start", "Erasing QSPI flash...")
|
callback("start", "Erasing QSPI flash...")
|
||||||
|
|
||||||
cmd = _build_program_flash_cmd_base(flash_tool, config)
|
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:
|
try:
|
||||||
result = _run_command(cmd, timeout=300, callback=callback)
|
result = _run_command(cmd, timeout=300, callback=callback)
|
||||||
|
|||||||
Reference in New Issue
Block a user