🐛 fix(xsct): PS init before FPGA config keeps JTAG DAP alive

Root cause: fpga -file breaks the DAP unless PS is initialized first.
Solution: source ps7_init.tcl + ps7_init + targets 1 BEFORE fpga -file.

Changes:
- _program_with_xsct: init PS (clocks/DDR/MIO) before fpga -file
  → DAP stays alive, subsequent ELF download works
- _run_elf_with_xsct: simplified - no reconnect needed, DAP is healthy
  (kept ps7_init fallback for recovery)
- program_bitstream: auto-detects ps7_init.tcl from BIT file directory

Verified on hardware:
  Session 1 (BIT): connect → ps7_init → fpga → exit  (13s) ✓
  Session 2 (ELF): connect → targets 2 → dow → con    (9s)  ✓
This commit is contained in:
Jeremy Shen
2026-06-10 12:53:02 +08:00
parent 80fb704221
commit ab7264d5c7
+30 -21
View File
@@ -144,8 +144,8 @@ def program_bitstream(
) -> BitstreamResult: ) -> BitstreamResult:
"""Download a .bit bitstream to the Zynq PL (FPGA fabric). """Download a .bit bitstream to the Zynq PL (FPGA fabric).
Attempts xsct first (fast, lightweight), then falls back to Vivado Inits PS (ps7_init) before FPGA config to keep the JTAG DAP alive,
batch mode. allowing subsequent ELF download without reconnection issues.
Args: Args:
config: Application configuration. config: Application configuration.
@@ -166,10 +166,13 @@ def program_bitstream(
if callback: if callback:
callback("start", f"Loading bitstream: {bit_path.name}...") callback("start", f"Loading bitstream: {bit_path.name}...")
# Find ps7_init.tcl for PS initialization
ps7_tcl = _find_ps7_init_tcl(config)
# Try xsct first (preferred) # Try xsct first (preferred)
xsct = get_xsct_path(config.vitis_path) xsct = get_xsct_path(config.vitis_path)
if xsct: if xsct:
return _program_with_xsct(xsct, bit_path, callback) return _program_with_xsct(xsct, bit_path, callback, ps7_tcl)
# Fallback to Vivado # Fallback to Vivado
vivado = _get_vivado_path(config.vitis_path) vivado = _get_vivado_path(config.vitis_path)
@@ -187,30 +190,38 @@ def _program_with_xsct(
xsct_path: str, xsct_path: str,
bit_path: Path, bit_path: Path,
callback: ProgressCallback = None, callback: ProgressCallback = None,
ps7_init_tcl: str = "",
) -> BitstreamResult: ) -> BitstreamResult:
"""Program bitstream using xsct's fpga command. """Program bitstream using xsct's fpga command.
xsct connects to hw_server and uses the `fpga` command to download Inits the PS (clocks, DDR, MIO) before FPGA configuration to keep
the bitstream to the PL fabric. the JTAG DAP alive. Without PS init, the DAP breaks after fpga,
preventing subsequent ELF download.
Args: Args:
xsct_path: Path to xsct executable. xsct_path: Path to xsct executable.
bit_path: Path to .bit file. bit_path: Path to .bit file.
callback: Optional progress callback. callback: Optional progress callback.
ps7_init_tcl: Path to ps7_init.tcl for PS initialization.
Returns: Returns:
BitstreamResult with status. BitstreamResult with status.
""" """
if callback: if callback:
callback("progress", "Using xsct to download bitstream...") callback("progress", "Initializing PS then programming FPGA...")
# Build TCL: PS init first, then FPGA config
lines = ["connect"]
if ps7_init_tcl and Path(ps7_init_tcl).exists():
lines.append("targets 1")
lines.append(f'source "{ps7_init_tcl}"')
lines.append("ps7_init")
if callback:
callback("progress", "PS initialized (clocks, DDR, MIO)")
lines.append(f'fpga -file "{bit_path}"')
lines.append("exit")
tcl_script = "\n".join(lines)
# xsct TCL: connect to hw_server, target FPGA, download bitstream
tcl_script = f"""
connect
targets
fpga -file "{bit_path}"
exit
"""
import tempfile, os import tempfile, os
with tempfile.NamedTemporaryFile(mode='w', suffix='.tcl', delete=False) as f: with tempfile.NamedTemporaryFile(mode='w', suffix='.tcl', delete=False) as f:
f.write(tcl_script) f.write(tcl_script)
@@ -351,27 +362,25 @@ def _run_elf_with_xsct(
) -> BitstreamResult: ) -> BitstreamResult:
"""Download and run ELF using xsct's dow command. """Download and run ELF using xsct's dow command.
After PL configuration (via fpga), the DAP may be disrupted. Assumes PS was already initialized (by a prior BIT download step).
This function reconnects, sources ps7_init.tcl to initialize Falls back to PS init if DAP is broken.
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. ps7_init_tcl: Optional ps7_init.tcl path for recovery.
Auto-detected from BIT file directory if empty.
Returns: Returns:
BitstreamResult with status. BitstreamResult with status.
""" """
if callback: if callback:
callback("progress", "Using xsct to download ELF...") callback("progress", "Downloading ELF...")
# Build TCL: reconnect (DAP may be broken after PL config),
# source ps7_init for PS init, then dow + con
lines = ["connect"] lines = ["connect"]
# Recovery: if DAP is broken, re-init PS
if ps7_init_tcl and Path(ps7_init_tcl).exists(): if ps7_init_tcl and Path(ps7_init_tcl).exists():
lines.append("targets 1")
lines.append(f'source "{ps7_init_tcl}"') lines.append(f'source "{ps7_init_tcl}"')
lines.append("ps7_init") lines.append("ps7_init")
lines.append("targets 2") lines.append("targets 2")