sync: WIN-BOWEI 2026-08-10 fpga_flash 增 Zynq BOOT.bin/QSPI 脚本,rtl-ip-replace 与 sim-rtl-flow 更新
- fpga_flash: 新增 build_zynq_boot.py / program_zynq_qspi.py(bootgen + program_flash, 含备份、SHA-256/容量/marker 守卫);vivado_env.py 增 resolve_sdk_dir(SDK_ROOT/SDK_PATH); SKILL.md 补 Zynq 烧写流程,示例去掉写死的 D:\Xilinx\SDK\2018.1,改由 skills_env.md 解析。 - rtl-ip-replace: gen_ip.tcl / namespace_ip.py 更新。 - sim-rtl-flow: SKILL.md 与 run_sim_rtl_flow.py 更新。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a guarded Zynq-7000 BOOT.bin from FSBL + PL bit + PS ELF.
|
||||
|
||||
The output is generated as a candidate, checked for capacity and required
|
||||
ASCII build markers, then atomically promoted. Existing BOOT.bin/BIF files are
|
||||
backed up before replacement. This script does not program hardware.
|
||||
"""
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from vivado_env import resolve_sdk_dir
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest().upper()
|
||||
|
||||
|
||||
def require_file(label: str, value: str) -> Path:
|
||||
path = Path(value).expanduser().resolve()
|
||||
if not path.is_file():
|
||||
raise SystemExit(f"ERROR: missing {label}: {path}")
|
||||
return path
|
||||
|
||||
|
||||
def bif_path(path: Path) -> str:
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build guarded Zynq BOOT.bin (boot FSBL + PL bit + PS ELF)"
|
||||
)
|
||||
parser.add_argument("--sdk-dir", help="Xilinx SDK root containing bin/bootgen.bat")
|
||||
parser.add_argument("--boot-fsbl", required=True, help="FSBL executed at cold boot")
|
||||
parser.add_argument("--bit-file", required=True, help="implemented PL .bit file")
|
||||
parser.add_argument("--app-elf", required=True, help="PS application ELF")
|
||||
parser.add_argument("--boot-bin", required=True, help="final BOOT.bin path")
|
||||
parser.add_argument("--bif-file", help="persistent BIF path; default beside BOOT.bin")
|
||||
parser.add_argument("--capacity-bytes", type=int, default=32 * 1024 * 1024)
|
||||
parser.add_argument(
|
||||
"--require-marker", action="append", default=[],
|
||||
help="ASCII marker that must exist in the generated image; repeatable",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backup-dir",
|
||||
help="backup root; default <BOOT.bin directory>/flash_backups",
|
||||
)
|
||||
parser.add_argument("--dry-run", action="store_true", help="validate and print only")
|
||||
args = parser.parse_args()
|
||||
|
||||
boot_fsbl = require_file("boot FSBL", args.boot_fsbl)
|
||||
bit_file = require_file("PL bitstream", args.bit_file)
|
||||
app_elf = require_file("PS application ELF", args.app_elf)
|
||||
boot_bin = Path(args.boot_bin).expanduser().resolve()
|
||||
bif_file = (
|
||||
Path(args.bif_file).expanduser().resolve()
|
||||
if args.bif_file else boot_bin.with_suffix(".bif")
|
||||
)
|
||||
if args.capacity_bytes <= 0:
|
||||
raise SystemExit("ERROR: --capacity-bytes must be positive")
|
||||
try:
|
||||
marker_bytes = [(marker, marker.encode("ascii")) for marker in args.require_marker]
|
||||
except UnicodeEncodeError:
|
||||
raise SystemExit("ERROR: --require-marker values must be ASCII")
|
||||
app_bytes = app_elf.read_bytes()
|
||||
missing_app_markers = [marker for marker, raw in marker_bytes if raw not in app_bytes]
|
||||
if missing_app_markers:
|
||||
raise SystemExit(
|
||||
"ERROR: PS application ELF lacks required markers: " + repr(missing_app_markers)
|
||||
)
|
||||
|
||||
sdk_dir = Path(resolve_sdk_dir(args.sdk_dir)).expanduser().resolve()
|
||||
bootgen = sdk_dir / "bin" / "bootgen.bat"
|
||||
if not bootgen.is_file():
|
||||
raise SystemExit(f"ERROR: bootgen not found: {bootgen}")
|
||||
|
||||
bif_text = (
|
||||
"the_ROM_image:\n"
|
||||
"{\n"
|
||||
f" [bootloader]{bif_path(boot_fsbl)}\n"
|
||||
f" {bif_path(bit_file)}\n"
|
||||
f" {bif_path(app_elf)}\n"
|
||||
"}\n"
|
||||
)
|
||||
candidate = boot_bin.with_name(f"{boot_bin.name}.candidate.{os.getpid()}")
|
||||
cmd = [
|
||||
str(bootgen), "-image", str(bif_file), "-arch", "zynq",
|
||||
"-o", str(candidate), "-w", "on",
|
||||
]
|
||||
print(f"[build_zynq_boot] boot FSBL: {boot_fsbl} sha256={sha256(boot_fsbl)}")
|
||||
print(f"[build_zynq_boot] PL bit : {bit_file} sha256={sha256(bit_file)}")
|
||||
print(f"[build_zynq_boot] PS ELF : {app_elf} sha256={sha256(app_elf)}")
|
||||
print(f"[build_zynq_boot] output : {boot_bin}")
|
||||
print(f"[build_zynq_boot] capacity : {args.capacity_bytes}")
|
||||
print("[build_zynq_boot] command : " + subprocess.list2cmdline(cmd))
|
||||
if args.dry_run:
|
||||
print("DRY_RUN_OK: no files changed and bootgen was not executed")
|
||||
return
|
||||
|
||||
boot_bin.parent.mkdir(parents=True, exist_ok=True)
|
||||
bif_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
stamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_root = (
|
||||
Path(args.backup_dir).expanduser().resolve()
|
||||
if args.backup_dir else boot_bin.parent / "flash_backups"
|
||||
)
|
||||
existing = [path for path in (boot_bin, bif_file) if path.exists()]
|
||||
if existing:
|
||||
backup = backup_root / stamp
|
||||
backup.mkdir(parents=True, exist_ok=False)
|
||||
for path in existing:
|
||||
shutil.copy2(path, backup / path.name)
|
||||
print(f"[build_zynq_boot] backup : {backup}")
|
||||
|
||||
bif_file.write_text(bif_text, encoding="utf-8", newline="\n")
|
||||
try:
|
||||
result = subprocess.run(cmd, check=False)
|
||||
if result.returncode != 0 or not candidate.is_file():
|
||||
raise RuntimeError(f"bootgen failed, rc={result.returncode}")
|
||||
size = candidate.stat().st_size
|
||||
if size >= args.capacity_bytes:
|
||||
raise RuntimeError(
|
||||
f"candidate size {size} is not below capacity {args.capacity_bytes}"
|
||||
)
|
||||
image = candidate.read_bytes()
|
||||
missing = [marker for marker, raw in marker_bytes if raw not in image]
|
||||
if missing:
|
||||
raise RuntimeError("candidate lacks required markers: " + repr(missing))
|
||||
candidate_hash = sha256(candidate)
|
||||
os.replace(candidate, boot_bin)
|
||||
except Exception as exc:
|
||||
if candidate.exists():
|
||||
candidate.unlink()
|
||||
raise SystemExit(f"ERROR: {exc}")
|
||||
|
||||
manifest = {
|
||||
"format": "zynq-7000-boot-bin",
|
||||
"created": dt.datetime.now().astimezone().isoformat(),
|
||||
"capacity_bytes": args.capacity_bytes,
|
||||
"required_markers": args.require_marker,
|
||||
"components": {
|
||||
"boot_fsbl": {"path": str(boot_fsbl), "sha256": sha256(boot_fsbl)},
|
||||
"pl_bit": {"path": str(bit_file), "sha256": sha256(bit_file)},
|
||||
"ps_elf": {"path": str(app_elf), "sha256": sha256(app_elf)},
|
||||
},
|
||||
"boot_bin": {"path": str(boot_bin), "size": size, "sha256": candidate_hash},
|
||||
}
|
||||
manifest_path = boot_bin.with_suffix(boot_bin.suffix + ".manifest.json")
|
||||
manifest_path.write_text(
|
||||
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
print(f"ZYNQ_BOOTGEN_PASS size={size} sha256={candidate_hash}")
|
||||
print(f"MANIFEST={manifest_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user