feat: add CRYPTO-P2 targets and reporting script for synthesis, netlist, and STA validation

This commit is contained in:
maksyuki committed 2026-09-26 15:11:09 +08:00
1 parent 3c63fec8dc
commit 187b090244
5 files changed
+172 -1

No files matched your search

+3
View File
@@ -209,6 +209,9 @@ build/simulation and binds the actual configuration, input snapshot, ELF and
log/result hashes. `crypto_p1.py quality --variant-root <variant>` records
focused formatter and policy results. Report assembly rejects stale inputs
or artifacts; it cannot qualify an earlier binary using current source hashes.
`crypto_p2.py` is the fail-closed P2 aggregator. It requires a clean candidate
revision and records whole-SoC netlist, STA, physical, metrics, warning and
P0/P1 evidence; missing P2 artifacts remain `NOT_RUN`.
Update or add tests in [`../tests`](../tests) for script behavior. Run
`ruff check .` and `python3 -m pytest -q`; build-flow changes also require the
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Fail-closed CRYPTO-P2 synthesis, netlist, STA and physical evidence report."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
ROOT = Path(__file__).resolve().parents[1]
BASELINE_REVISION = "92830d963da2f1e39bb219c0d377bc4889c07755"
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def dump(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n")
def manifest(root: Path) -> dict:
path = root / "meta/manifest.json"
return json.loads(path.read_text()) if path.exists() else {}
def clean_revision() -> tuple[bool, str]:
status = subprocess.check_output(["git", "status", "--porcelain"], cwd=ROOT, text=True)
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()
return not status, head
def artifact(path: Path, expected_status: str | None = None) -> dict:
if not path.exists():
return {"status": "NOT_RUN", "path": str(path)}
try:
value = json.loads(path.read_text())
except (OSError, json.JSONDecodeError) as error:
return {"status": "INVALID", "path": str(path), "error": str(error)}
value.setdefault("path", str(path))
if expected_status is not None and value.get("status") != expected_status:
value["status_check"] = "failed"
return value
def report(args: argparse.Namespace) -> int:
variant = args.variant_root.resolve()
p1 = args.p1_root.resolve()
p0 = args.p0_root.resolve()
is_clean, head = clean_revision()
candidate_manifest = manifest(variant)
p1_report = artifact(p1 / "crypto/p1/p1-report.json")
synth = artifact(p1 / "crypto/p1/candidate-synthesis.json")
netlist = artifact(args.netlist_root.resolve() / "sim/iverilog/netl/result-sim-check.json")
sta = artifact(args.sta_root.resolve() / "sta/opensta/result-sta.json")
physical = artifact(args.physical_root.resolve() / "crypto/p2/physical.json")
metrics = artifact(variant / "meta/metrics.json")
warnings = artifact(variant / "meta/warnings.json")
checks = {
"clean_candidate": is_clean,
"candidate_manifest": candidate_manifest.get("profile") == "ihp130",
"candidate_revision": bool(head) and head != BASELINE_REVISION,
"p0_report": artifact(p0 / "crypto/p0/p0-report.json").get("status") == "passed",
"p1_focused": p1_report.get("focused_gates_status") == "passed",
"block_synthesis": synth.get("status") == "passed",
"whole_soc_netlist": netlist.get("status") == "passed",
"sta": sta.get("status") == "passed",
"physical": physical.get("status") == "passed",
"metrics": metrics.get("status") == "passed",
"warnings": warnings.get("status") in ("passed", "observed"),
}
required = ("clean_candidate", "candidate_manifest", "candidate_revision", "p0_report",
"p1_focused", "block_synthesis", "whole_soc_netlist", "sta", "physical")
passed = all(checks[name] for name in required)
result = {
"schema_version": 1,
"feature": "crypto",
"phase": "CRYPTO-P2",
"status": "passed" if passed else "incomplete",
"verdict": "P2_ACCEPTED" if passed else "NOT_RUN_OR_INCOMPLETE",
"baseline_revision": BASELINE_REVISION,
"candidate_revision": head,
"candidate_dirty": not is_clean,
"profile": candidate_manifest.get("profile", "NOT_RUN"),
"configuration": candidate_manifest.get("configuration", {}),
"checks": checks,
"artifacts": {
"p0_report": str(p0 / "crypto/p0/p0-report.json"),
"p1_report": str(p1 / "crypto/p1/p1-report.json"),
"synthesis": synth,
"netlist": netlist,
"sta": sta,
"physical": physical,
"metrics": metrics,
"warnings": warnings,
},
"gaps": [name for name, value in checks.items() if not value],
"non_goals": ["Pytest under maintainer constraint", "CRYPTO-P3 commercial variants",
"DPA/FIA/physical security signoff"],
}
dump(variant / "crypto/p2/p2-report.json", result)
return 0 if passed else 2
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=("report",))
parser.add_argument("--variant-root", type=Path, required=True)
parser.add_argument("--p0-root", type=Path, required=True)
parser.add_argument("--p1-root", type=Path, required=True)
parser.add_argument("--netlist-root", type=Path)
parser.add_argument("--sta-root", type=Path)
parser.add_argument("--physical-root", type=Path)
args = parser.parse_args()
args.netlist_root = args.netlist_root or args.variant_root
args.sta_root = args.sta_root or args.variant_root
args.physical_root = args.physical_root or args.variant_root
if args.command == "report":
return report(args)
raise AssertionError(args.command)
if __name__ == "__main__":
raise SystemExit(main())