ba74bbd5aa
Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog TBs, fidelity attribution tooling. Functional baseline before removing the dead bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Fail closed if a runtime-CLUT fit has timed out a required fabric branch."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
DEFAULT_OUTPUT = Path(
|
|
"synth/de25_nano/top_psmct32_raster_demo/output_files"
|
|
)
|
|
REVISION = "de25_nano_psmct32_raster_demo_top"
|
|
|
|
|
|
def read(path: Path) -> str:
|
|
try:
|
|
return path.read_text(encoding="utf-8", errors="replace")
|
|
except OSError as exc:
|
|
raise SystemExit(f"FAIL: cannot read {path}: {exc}") from exc
|
|
|
|
|
|
def summary_value(text: str, label: str) -> int:
|
|
match = re.search(rf"^{re.escape(label)}\s*:\s*([0-9,]+)\s*/", text, re.M)
|
|
if not match:
|
|
raise SystemExit(f"FAIL: missing '{label}' in fit summary")
|
|
return int(match.group(1).replace(",", ""))
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT)
|
|
args = parser.parse_args()
|
|
|
|
fit_summary = args.output_dir / f"{REVISION}.fit.summary"
|
|
fit_report = args.output_dir / f"{REVISION}.fit.rpt"
|
|
sta_summary = args.output_dir / f"{REVISION}.sta.summary"
|
|
summary = read(fit_summary)
|
|
fit = read(fit_report)
|
|
sta = read(sta_summary)
|
|
|
|
failures: list[str] = []
|
|
if "Fitter Status : Successful" not in summary:
|
|
failures.append("fitter did not report success")
|
|
|
|
alms = summary_value(summary, "Logic utilization (in ALMs)")
|
|
rams = summary_value(summary, "Total RAM Blocks")
|
|
# A 640x480 persistent-Z runtime scene normally consumes roughly 79% ALMs
|
|
# and 74% M20Ks. These deliberately loose floors catch path pruning, not
|
|
# healthy placement variation.
|
|
if alms < 20_000:
|
|
failures.append(f"only {alms:,} ALMs; runtime raster fabric appears pruned")
|
|
if rams < 150:
|
|
failures.append(f"only {rams} RAM blocks; runtime texture/Z fabric appears pruned")
|
|
|
|
for name in ("u_demo|g_feeder", "u_texcache|tex_mem", "u_zc_emit|u_req"):
|
|
if name not in fit:
|
|
failures.append(f"required fitted hierarchy missing: {name}")
|
|
|
|
for warning in (
|
|
"feeder staging RAM dest EMPTY",
|
|
"tex_mem dst matched 0 keepers",
|
|
):
|
|
if warning in fit:
|
|
failures.append(f"runtime SDC endpoint missing: {warning}")
|
|
|
|
emif_setup = re.search(
|
|
r"Type\s*:\s*Setup 'u_emif_lpddr4b\|iopll\|iopll_0_outclk0'\s*\n"
|
|
r"Slack\s*:\s*([+-]?[0-9.]+)\s*\n"
|
|
r"TNS\s*:\s*([+-]?[0-9.]+)",
|
|
sta,
|
|
)
|
|
if not emif_setup:
|
|
failures.append("missing 310 MHz EMIF setup summary")
|
|
else:
|
|
setup_slack, setup_tns = map(float, emif_setup.groups())
|
|
if setup_slack < 0 or setup_tns < 0:
|
|
failures.append(
|
|
f"310 MHz setup fails: slack {setup_slack:+.3f} ns, TNS {setup_tns:+.3f} ns"
|
|
)
|
|
|
|
if failures:
|
|
print("RUNTIME FABRIC FIT: FAIL")
|
|
for failure in failures:
|
|
print(f"- {failure}")
|
|
return 1
|
|
|
|
print(
|
|
"RUNTIME FABRIC FIT: PASS "
|
|
f"({alms:,} ALMs, {rams} RAM blocks, feeder/texture/Z present, 310 MHz setup met)"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|