mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-20 16:16:25 +03:00
* feat(fpga-development): add design and verification methodology * feat(fpga-development): integrate electronics verification routing
88 lines
4.8 KiB
Python
Executable File
88 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Run the vendor-neutral ready/valid FPGA fixture without touching hardware."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
ASSET = ROOT / "assets"
|
|
|
|
def clean(value):
|
|
if isinstance(value, bytes):
|
|
return value.decode(errors="replace")
|
|
return value or ""
|
|
|
|
def run(cmd, cwd, timeout=30):
|
|
try:
|
|
p = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True, timeout=timeout)
|
|
return {"command": cmd, "returncode": p.returncode, "stdout": clean(p.stdout), "stderr": clean(p.stderr), "status": "ok" if p.returncode == 0 else "failed"}
|
|
except FileNotFoundError as e:
|
|
return {"command": cmd, "returncode": None, "error": str(e), "stdout": "", "stderr": "", "status": "missing_tool"}
|
|
except subprocess.TimeoutExpired as e:
|
|
return {"command": cmd, "returncode": None, "error": "timeout", "stdout": clean(e.stdout), "stderr": clean(e.stderr), "status": "timeout"}
|
|
|
|
def version(tool, flag="--version"):
|
|
if not tool:
|
|
return {"status": "missing_tool"}
|
|
return run([tool, flag], Path.cwd())
|
|
|
|
def source_hashes():
|
|
result = {}
|
|
for path in (ASSET / "rv_buffer.sv", ASSET / "tb_rv_buffer.sv"):
|
|
result[path.name] = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
return result
|
|
|
|
def yq(path):
|
|
return '"' + str(path).replace('\\', '/') .replace('"', '\\"') + '"'
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument("--width", type=int, choices=(1, 8), nargs="+", default=[1, 8])
|
|
ap.add_argument("--seed", type=int, default=20260914)
|
|
ap.add_argument("--json", action="store_true")
|
|
ap.add_argument("--output", type=Path)
|
|
args = ap.parse_args()
|
|
out = (args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix="fpga-fixture-")))
|
|
if args.output and out.exists() and (not out.is_dir() or any(out.iterdir())):
|
|
ap.error(f"output directory must be new or empty: {out}")
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
results = {"fixture": "rv_buffer", "hardware": False, "simulation": [], "synthesis": [], "output": str(out), "source_sha256": source_hashes()}
|
|
iverilog = shutil.which("iverilog")
|
|
vvp = shutil.which("vvp")
|
|
yosys = shutil.which("yosys")
|
|
for width in args.width:
|
|
sim = out / f"sim_w{width}.vvp"
|
|
if not iverilog or not vvp:
|
|
results["simulation"].append({"width": width, "status": "skipped", "reason": "iverilog or vvp unavailable"})
|
|
else:
|
|
sim_versions = {"iverilog": version(iverilog, "-V"), "vvp": version(vvp, "-V")}
|
|
compile_r = run([iverilog, "-g2012", "-s", "tb_rv_buffer", f"-Ptb_rv_buffer.DATA_WIDTH={width}", "-o", str(sim), str(ASSET / "rv_buffer.sv"), str(ASSET / "tb_rv_buffer.sv")], out)
|
|
if compile_r["returncode"] == 0:
|
|
exec_r = run([vvp, str(sim), f"+SEED={args.seed}"], out)
|
|
ok = exec_r["returncode"] == 0 and "PASS" in exec_r.get("stdout", "") and "ERROR" not in exec_r.get("stdout", "") and "FAIL" not in exec_r.get("stdout", "")
|
|
results["simulation"].append({"width": width, "status": "passed" if ok else "failed", "versions": sim_versions, "compile": compile_r, "run": exec_r})
|
|
else:
|
|
results["simulation"].append({"width": width, "status": "failed", "compile": compile_r})
|
|
if not yosys:
|
|
results["synthesis"].append({"width": width, "status": "skipped", "reason": "yosys unavailable"})
|
|
else:
|
|
ys = out / f"synth_w{width}.ys"
|
|
netlist = out / f"synth_w{width}.json"
|
|
ys.write_text(f"read_verilog -sv {yq(ASSET / 'rv_buffer.sv')}\nhierarchy -top rv_buffer -chparam DATA_WIDTH {width}\nproc; opt; check -assert; synth -top rv_buffer; check -assert; write_json {yq(netlist)}; stat\n")
|
|
syn = run([yosys, str(ys)], out)
|
|
results["synthesis"].append({"width": width, "status": "passed" if syn["returncode"] == 0 and netlist.exists() else "failed", "version": version(yosys), "run": syn, "netlist": str(netlist)})
|
|
statuses = [x["status"] for x in results["simulation"] + results["synthesis"]]
|
|
results["status"] = "passed" if statuses and all(x == "passed" for x in statuses) else ("incomplete" if statuses and all(x in ("passed", "skipped") for x in statuses) else "failed")
|
|
payload = json.dumps(results, indent=2)
|
|
if args.output:
|
|
(out / "result.json").write_text(payload + "\n")
|
|
print(payload if args.json else f"{results['status']}: simulation={[(x['width'], x['status']) for x in results['simulation']]} synthesis={[(x['width'], x['status']) for x in results['synthesis']]} output={out}")
|
|
return 0 if results["status"] == "passed" else 1
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|