Files
retroDE_ps2/tools/gs_ch351_oracle.py
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

91 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""retroDE_ps2 — Ch351 step 1: isolate reciprocal quantization from S1 under-interpolation banding.
Compares the RTL FB dump (sh3_real_fb_out.mem, written by tb_top_psmct32_sh3_real_draw_demo) against TWO host
references over the identical crop geometry:
FLOAT — ideal perspective (the Codex pixel-diff oracle).
RECIP-8b — RTL-FAITHFUL: fixed-point vertex attrs + the 8-bit gs_reciprocal_stub (gs_make_sh3_real_draw_fixture).
Verdict:
- RTL ≈ RECIP-8b (tight) AND RECIP-8b ≠ FLOAT (loose) -> the residual is RECIPROCAL QUANTIZATION. Widening
gs_reciprocal_stub IDX_BITS should fix it (Ch351 step 2).
- RTL ≠ RECIP-8b (still loose) -> S1 attribute UNDER-INTERPOLATION beyond the reciprocal.
Usage: gs_ch351_oracle.py (run gs_make_sh3_real_draw_fixture.py + the TB first to produce the .mem files)
"""
import sys, os
ROOT=os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),".."))
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
FBPXW, CH, TW, TH = 256, 120, 512, 512
def loadmem(name):
return [int(l,16) for l in open(os.path.join(DATA,name)) if l.strip() and not l.strip().startswith("//")]
def main():
fb = loadmem("sh3_real_fb_out.mem")
rmF = loadmem("sh3_real_refmap.mem")
rmR = loadmem("sh3_real_refmap_recip.mem")
idxw = loadmem("sh3_real_idx.mem")
pal = loadmem("sh3_real_pal.mem")
def sh3_idx(u,v):
lin=v*TW+u; return (idxw[lin>>2] >> (8*(lin&3))) & 0xFF
def exp_cell(u,v):
return pal[sh3_idx(u,v)] & 0xFFFFFF
palset = set(p & 0xFFFFFF for p in pal)
def histo(refmap, label):
hist=[0]*5; ihist=[0]*5; tot=0; itot=0; exact_color=0; clut_bad=0
sdu=sdv=0.0; nd=0
for o in range(FBPXW*CH):
rm=refmap[o]
if not (rm>>31): continue
interior=(rm>>30)&1; tu=(rm>>9)&0x1FF; tv=rm&0x1FF
col = fb[o] & 0xFFFFFF
tot+=1
if col not in palset: clut_bad+=1
if col == exp_cell(tu,tv): exact_color+=1
D=99; mdu=mdv=0
for rad in range(0,4):
for du in range(-rad,rad+1):
for dv in range(-rad,rad+1):
if max(abs(du),abs(dv))!=rad or D!=99: continue
ttu=tu+du; ttv=tv+dv
if 0<=ttu<TW and 0<=ttv<TH and col==exp_cell(ttu,ttv):
D=rad; mdu=du; mdv=dv
hist[D if D<=3 else 4]+=1
if D<=3: sdu+=mdu; sdv+=mdv; nd+=1
if interior: itot+=1; ihist[D if D<=3 else 4]+=1
print(f"== {label} == covered={tot} interior={itot}")
print(f" ALL hist D0={hist[0]} D1={hist[1]} D2={hist[2]} D3={hist[3]} none={hist[4]} "
f"<=1tex={100.0*(hist[0]+hist[1])/max(1,tot):.1f}%")
print(f" INT hist D0={ihist[0]} D1={ihist[1]} D2={ihist[2]} D3={ihist[3]} none={ihist[4]} "
f"<=1tex={100.0*(ihist[0]+ihist[1])/max(1,itot):.1f}%")
print(f" exact-color-match={100.0*exact_color/max(1,tot):.1f}% clut_bad={clut_bad} "
f"mean-delta=({sdu/max(1,nd):.3f},{sdv/max(1,nd):.3f})")
return ihist
rmA = loadmem("sh3_real_refmap_affine.mem")
print("RTL FB dump vs the references (same geometry):\n")
histo(rmF, "RTL-vs-FLOAT (ideal perspective)")
print()
ih = histo(rmR, "RTL-vs-RECIP8b (perspective + 8-bit reciprocal)")
print()
iha = histo(rmA, "RTL-vs-AFFINE (per-vertex divide + linear u,v)")
print()
# also: how different are the two references themselves (FLOAT vs RECIP8b) — the reciprocal's own error
diff=sum(1 for o in range(FBPXW*CH) if (rmF[o]>>31) and (rmF[o]&0x3FFFF)!=(rmR[o]&0x3FFFF))
covered=sum(1 for o in range(FBPXW*CH) if rmF[o]>>31)
print(f"FLOAT vs RECIP8b reference texel disagreement: {diff}/{covered} ({100.0*diff/max(1,covered):.1f}%) "
f"— the reciprocal LUT's intrinsic error on this geometry")
print()
rtl_tight = (ih[2]+ih[3]+ih[4]) < 0.05*max(1,sum(ih))
if rtl_tight:
print("VERDICT: RTL matches the RTL-faithful 8-bit-reciprocal reference -> residual is RECIPROCAL")
print(" QUANTIZATION. Ch351 step 2: widen gs_reciprocal_stub IDX_BITS (10/11/12).")
else:
print("VERDICT: RTL does NOT match even the 8-bit-reciprocal reference -> S1 attribute UNDER-")
print(" INTERPOLATION banding beyond the reciprocal. Ch351: fix S1 interpolation.")
return 0
if __name__ == "__main__":
sys.exit(main())