🐛 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:
+101
-88
@@ -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)
|
||||
if not xsct:
|
||||
return BitstreamResult(
|
||||
step="elf",
|
||||
success=False,
|
||||
message="xsct not found in PATH (required for CPU operations)",
|
||||
)
|
||||
|
||||
# Fallback to Vivado
|
||||
vivado = _get_vivado_path(config.vitis_path)
|
||||
if vivado:
|
||||
return _run_elf_with_vivado(vivado, elf_path, config, callback)
|
||||
|
||||
return BitstreamResult(
|
||||
step="elf",
|
||||
success=False,
|
||||
message="Neither xsct nor vivado found in PATH",
|
||||
)
|
||||
# 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 ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user