Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
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>
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Score a Ch357 ZSCHED framebuffer dump.
|
||||
|
||||
The z/paint/replay owner modes compare against the dump-derived texture oracle.
|
||||
The replay-color mode is the hard board/RTL content check: it replays the emitted
|
||||
fragment trace colors through the same clamp16 GEQUAL persistent-Z rule and
|
||||
compares the framebuffer colors directly.
|
||||
|
||||
Usage:
|
||||
tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem
|
||||
tools/analyze_zsched_fb.py <fb.mem> --owner paint
|
||||
tools/analyze_zsched_fb.py <fb.mem> --owner replay --frags sim/traces/rtl/zsched_frags.txt
|
||||
tools/analyze_zsched_fb.py <fb.mem> --owner replay-color --frags sim/traces/rtl/zsched_frags.txt
|
||||
tools/analyze_zsched_fb.py <fb.mem> --owner replay --radius 8
|
||||
tools/analyze_zsched_fb.py <fb.mem> --maps
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
|
||||
|
||||
def load_mem(path):
|
||||
vals = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if not s or s.startswith("//"):
|
||||
continue
|
||||
vals.append(int(s, 16) & 0xFFFFFFFF)
|
||||
return vals
|
||||
|
||||
|
||||
def parse_params(tag):
|
||||
path = os.path.join(DATA, f"sh3_{tag}_params.vh")
|
||||
txt = open(path).read()
|
||||
out = {}
|
||||
for key in ("FBPXW", "FBH", "TW", "TH", "N_EPOCHS"):
|
||||
m = re.search(rf"localparam\s+int\s+{key}\s*=\s*([0-9]+)\s*;", txt)
|
||||
if not m:
|
||||
raise SystemExit(f"[analyze] missing {key} in {path}")
|
||||
out[key] = int(m.group(1))
|
||||
return out
|
||||
|
||||
|
||||
def cell(idx, pal, tw, u, v):
|
||||
lin = v * tw + u
|
||||
w = idx[lin // 4]
|
||||
ix = (w >> (8 * (lin % 4))) & 0xFF
|
||||
return pal[ix] & 0xFFFFFF
|
||||
|
||||
|
||||
def match_radius(fbc, idx, pal, tw, th, tu, tv, max_radius):
|
||||
for rad in range(max_radius + 1):
|
||||
for du in range(-rad, rad + 1):
|
||||
for dv in range(-rad, rad + 1):
|
||||
if max(abs(du), abs(dv)) != rad:
|
||||
continue
|
||||
uu = tu + du
|
||||
vv = tv + dv
|
||||
if 0 <= uu < tw and 0 <= vv < th and fbc == cell(idx, pal, tw, uu, vv):
|
||||
return rad
|
||||
return None
|
||||
|
||||
|
||||
def write_maps(prefix, w, h, fb, status):
|
||||
try:
|
||||
from PIL import Image
|
||||
except Exception:
|
||||
print("[analyze] PIL not available; skipping maps")
|
||||
return
|
||||
|
||||
colors = {
|
||||
0: (0, 0, 0), # uncovered
|
||||
1: (0, 180, 0), # owner exact
|
||||
2: (170, 170, 0), # owner <=1 texel
|
||||
3: (0, 80, 220), # wrong owner/other covering epoch
|
||||
4: (220, 0, 0), # no covering epoch match
|
||||
5: (255, 0, 255), # no palette
|
||||
}
|
||||
img = Image.new("RGB", (w, h))
|
||||
for o, st in enumerate(status):
|
||||
img.putpixel((o % w, o // w), colors.get(st, (255, 255, 255)))
|
||||
out = f"{prefix}_class.png"
|
||||
img.resize((w * 3, h * 3), Image.NEAREST).save(out)
|
||||
print(f"[analyze] wrote {out}")
|
||||
|
||||
diff = Image.new("RGB", (w, h))
|
||||
for o, px in enumerate(fb[:w * h]):
|
||||
if status[o] in (4, 5):
|
||||
diff.putpixel((o % w, o // w), (255, 0, 0))
|
||||
else:
|
||||
diff.putpixel((o % w, o // w), (px & 0xFF, (px >> 8) & 0xFF, (px >> 16) & 0xFF))
|
||||
out = f"{prefix}_bad_overlay.png"
|
||||
diff.resize((w * 3, h * 3), Image.NEAREST).save(out)
|
||||
print(f"[analyze] wrote {out}")
|
||||
|
||||
|
||||
def replay_owner(frags, npx, w):
|
||||
zbuf = [-1] * npx
|
||||
z_owner = [-1] * npx
|
||||
paint_owner = [-1] * npx
|
||||
cov = [0] * npx
|
||||
with open(frags) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) < 5:
|
||||
continue
|
||||
ep = int(p[0])
|
||||
x = int(p[1])
|
||||
y = int(p[2])
|
||||
z = int(p[3])
|
||||
o = y * w + x
|
||||
if o < 0 or o >= npx:
|
||||
continue
|
||||
zq = 0xFFFF if z > 0xFFFF else (0 if z < 0 else z)
|
||||
cov[o] = 1
|
||||
paint_owner[o] = ep
|
||||
if zq >= zbuf[o]:
|
||||
zbuf[o] = zq
|
||||
z_owner[o] = ep
|
||||
return cov, z_owner, paint_owner, zbuf
|
||||
|
||||
|
||||
def replay_color(frags, npx, w):
|
||||
zbuf = [-1] * npx
|
||||
cov = [0] * npx
|
||||
col = [0] * npx
|
||||
frag_count = 0
|
||||
pass_count = 0
|
||||
with open(frags) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) < 5:
|
||||
continue
|
||||
x = int(p[1])
|
||||
y = int(p[2])
|
||||
z = int(p[3])
|
||||
c = int(p[4], 16) & 0xFFFFFF
|
||||
o = y * w + x
|
||||
if o < 0 or o >= npx:
|
||||
continue
|
||||
frag_count += 1
|
||||
zq = 0xFFFF if z > 0xFFFF else (0 if z < 0 else z)
|
||||
if zq >= zbuf[o]:
|
||||
zbuf[o] = zq
|
||||
cov[o] = 1
|
||||
col[o] = c
|
||||
pass_count += 1
|
||||
return cov, col, frag_count, pass_count
|
||||
|
||||
|
||||
def replay_color_check(fb_path, fb, frags, w, h):
|
||||
npx = w * h
|
||||
cov, exp, frag_count, pass_count = replay_color(frags, npx, w)
|
||||
covered = sum(cov)
|
||||
mismatches = []
|
||||
stray = 0
|
||||
for o in range(npx):
|
||||
got = fb[o] & 0xFFFFFF
|
||||
want = exp[o] & 0xFFFFFF
|
||||
if cov[o]:
|
||||
if got != want:
|
||||
if len(mismatches) < 12:
|
||||
mismatches.append((o, got, want, "covered"))
|
||||
elif got != 0:
|
||||
stray += 1
|
||||
if len(mismatches) < 12:
|
||||
mismatches.append((o, got, want, "uncovered"))
|
||||
|
||||
mismatch_count = 0
|
||||
for o in range(npx):
|
||||
got = fb[o] & 0xFFFFFF
|
||||
want = exp[o] & 0xFFFFFF
|
||||
if (cov[o] and got != want) or ((not cov[o]) and got != 0):
|
||||
mismatch_count += 1
|
||||
|
||||
print(f"[analyze] fb={fb_path}")
|
||||
print(f"[analyze] owner=replay-color FB={w}x{h}")
|
||||
print(f"[analyze] replay_frags={frags}")
|
||||
print(f"[analyze] replay fragments={frag_count} zpass_updates={pass_count} covered={covered} stray_uncovered={stray}")
|
||||
print(f"[analyze] replay-color exact {npx - mismatch_count}/{npx} ({100.0 * (npx - mismatch_count) / npx:.2f}%) mismatches={mismatch_count}")
|
||||
for o, got, want, kind in mismatches:
|
||||
print(f"[analyze] bad {kind}: px={o} x={o % w} y={o // w} got={got:06x} exp={want:06x}")
|
||||
return 0 if mismatch_count == 0 else 1
|
||||
|
||||
|
||||
def main(argv):
|
||||
args = argv[1:]
|
||||
fb_path = args[0] if args and not args[0].startswith("--") else os.path.join(DATA, "sh3_zsched_board_fb.mem")
|
||||
tag = args[args.index("--tag") + 1] if "--tag" in args else "zsched"
|
||||
owner_mode = args[args.index("--owner") + 1] if "--owner" in args else "z"
|
||||
frags = args[args.index("--frags") + 1] if "--frags" in args else os.path.join(ROOT, "sim", "traces", "rtl", "zsched_frags.txt")
|
||||
max_radius = int(args[args.index("--radius") + 1]) if "--radius" in args else 1
|
||||
emit_maps = "--maps" in args
|
||||
if owner_mode not in ("z", "paint", "replay", "replay-color"):
|
||||
raise SystemExit("[analyze] --owner must be z, paint, replay, or replay-color")
|
||||
|
||||
p = parse_params(tag)
|
||||
w, h, tw, th, ne = p["FBPXW"], p["FBH"], p["TW"], p["TH"], p["N_EPOCHS"]
|
||||
npx = w * h
|
||||
|
||||
fb = load_mem(fb_path)
|
||||
if len(fb) != npx:
|
||||
raise SystemExit(f"[analyze] {fb_path}: {len(fb)} words != expected {npx}")
|
||||
|
||||
if owner_mode == "replay-color":
|
||||
return replay_color_check(fb_path, fb, frags, w, h)
|
||||
|
||||
idx = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_idx.mem")) for e in range(ne)]
|
||||
pal = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_pal.mem")) for e in range(ne)]
|
||||
ref = load_mem(os.path.join(DATA, f"sh3_{tag}_refmap.mem"))
|
||||
rep = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_refmap.mem")) for e in range(ne)]
|
||||
zowner_path = os.path.join(DATA, f"sh3_{tag}_zowner.mem")
|
||||
zowner = load_mem(zowner_path) if os.path.exists(zowner_path) else None
|
||||
if owner_mode == "z" and zowner is None:
|
||||
raise SystemExit(f"[analyze] missing {zowner_path}; use --owner paint or regenerate z oracle")
|
||||
replay = replay_owner(frags, npx, w) if owner_mode == "replay" else None
|
||||
|
||||
palette_set = set()
|
||||
for e in range(ne):
|
||||
palette_set.update(x & 0xFFFFFF for x in pal[e])
|
||||
|
||||
totals = {
|
||||
"covered": 0, "interior": 0, "multi": 0, "reject": 0,
|
||||
"owner_r0": 0, "owner_r1": 0, "any_r1": 0,
|
||||
"interior_owner_r1": 0, "multi_owner_r1": 0, "reject_owner_r1": 0,
|
||||
"palette_bad": 0, "owner_missing_ref": 0,
|
||||
}
|
||||
status = [0] * npx
|
||||
owner_counts = [0] * ne
|
||||
by_owner = [{"cov": 0, "r0": 0, "r1": 0, "multi": 0, "multi_r1": 0, "reject": 0, "reject_r1": 0} for _ in range(ne)]
|
||||
radius_hist = [0] * (max_radius + 1)
|
||||
bad_examples = []
|
||||
|
||||
for o in range(npx):
|
||||
if owner_mode == "z":
|
||||
zw = zowner[o]
|
||||
cov = (zw >> 31) & 1
|
||||
if not cov:
|
||||
continue
|
||||
owner = ((zw >> 24) & 0xF) - 1
|
||||
paint_owner = (zw & 0xF) - 1
|
||||
reject = owner != paint_owner
|
||||
multi = (ref[o] >> 28) & 1
|
||||
interior = (rep[owner][o] >> 30) & 1 if 0 <= owner < ne else 0
|
||||
elif owner_mode == "replay":
|
||||
covs, owners, paint_owners, _zq = replay
|
||||
if not covs[o]:
|
||||
continue
|
||||
owner = owners[o]
|
||||
paint_owner = paint_owners[o]
|
||||
reject = owner != paint_owner
|
||||
multi = (ref[o] >> 28) & 1
|
||||
interior = (rep[owner][o] >> 30) & 1 if 0 <= owner < ne else 0
|
||||
else:
|
||||
rw = ref[o]
|
||||
cov = (rw >> 31) & 1
|
||||
if not cov:
|
||||
continue
|
||||
owner = (rw >> 24) & 0x7
|
||||
reject = False
|
||||
multi = (rw >> 28) & 1
|
||||
interior = (rw >> 30) & 1
|
||||
|
||||
totals["covered"] += 1
|
||||
totals["interior"] += int(interior)
|
||||
totals["multi"] += int(multi)
|
||||
totals["reject"] += int(reject)
|
||||
if 0 <= owner < ne:
|
||||
owner_counts[owner] += 1
|
||||
by_owner[owner]["cov"] += 1
|
||||
by_owner[owner]["multi"] += int(multi)
|
||||
by_owner[owner]["reject"] += int(reject)
|
||||
|
||||
fbc = fb[o] & 0xFFFFFF
|
||||
if fbc not in palette_set:
|
||||
totals["palette_bad"] += 1
|
||||
status[o] = 5
|
||||
if len(bad_examples) < 12:
|
||||
bad_examples.append((o, owner, fbc, "palette"))
|
||||
continue
|
||||
|
||||
owner_rad = None
|
||||
if 0 <= owner < ne and (rep[owner][o] >> 31):
|
||||
rm = rep[owner][o]
|
||||
tu = (rm >> 9) & 0x1FF
|
||||
tv = rm & 0x1FF
|
||||
owner_rad = match_radius(fbc, idx[owner], pal[owner], tw, th, tu, tv, max_radius)
|
||||
else:
|
||||
totals["owner_missing_ref"] += 1
|
||||
|
||||
if owner_rad == 0:
|
||||
radius_hist[0] += 1
|
||||
totals["owner_r0"] += 1
|
||||
totals["owner_r1"] += 1
|
||||
totals["interior_owner_r1"] += int(interior)
|
||||
totals["multi_owner_r1"] += int(multi)
|
||||
totals["reject_owner_r1"] += int(reject)
|
||||
if 0 <= owner < ne:
|
||||
by_owner[owner]["r0"] += 1
|
||||
by_owner[owner]["r1"] += 1
|
||||
by_owner[owner]["multi_r1"] += int(multi)
|
||||
by_owner[owner]["reject_r1"] += int(reject)
|
||||
status[o] = 1
|
||||
continue
|
||||
if owner_rad == 1:
|
||||
radius_hist[1] += 1
|
||||
totals["owner_r1"] += 1
|
||||
totals["interior_owner_r1"] += int(interior)
|
||||
totals["multi_owner_r1"] += int(multi)
|
||||
totals["reject_owner_r1"] += int(reject)
|
||||
if 0 <= owner < ne:
|
||||
by_owner[owner]["r1"] += 1
|
||||
by_owner[owner]["multi_r1"] += int(multi)
|
||||
by_owner[owner]["reject_r1"] += int(reject)
|
||||
status[o] = 2
|
||||
continue
|
||||
|
||||
if owner_rad is not None:
|
||||
radius_hist[owner_rad] += 1
|
||||
totals["owner_r1"] += 1
|
||||
totals["interior_owner_r1"] += int(interior)
|
||||
totals["multi_owner_r1"] += int(multi)
|
||||
totals["reject_owner_r1"] += int(reject)
|
||||
if 0 <= owner < ne:
|
||||
by_owner[owner]["r1"] += 1
|
||||
by_owner[owner]["multi_r1"] += int(multi)
|
||||
by_owner[owner]["reject_r1"] += int(reject)
|
||||
status[o] = 2
|
||||
continue
|
||||
|
||||
any_ok = False
|
||||
for e in range(ne):
|
||||
if not (rep[e][o] >> 31):
|
||||
continue
|
||||
rm = rep[e][o]
|
||||
tu = (rm >> 9) & 0x1FF
|
||||
tv = rm & 0x1FF
|
||||
if match_radius(fbc, idx[e], pal[e], tw, th, tu, tv, max_radius) is not None:
|
||||
any_ok = True
|
||||
break
|
||||
if any_ok:
|
||||
totals["any_r1"] += 1
|
||||
status[o] = 3
|
||||
else:
|
||||
status[o] = 4
|
||||
if len(bad_examples) < 12:
|
||||
bad_examples.append((o, owner, fbc, "nomatch"))
|
||||
|
||||
def pct(num, den):
|
||||
return 100.0 * num / den if den else 0.0
|
||||
|
||||
print(f"[analyze] fb={fb_path}")
|
||||
print(f"[analyze] tag={tag} owner={owner_mode} FB={w}x{h} epochs={ne} radius={max_radius}")
|
||||
if owner_mode == "replay":
|
||||
print(f"[analyze] replay_frags={frags}")
|
||||
print(f"[analyze] oracle covered={totals['covered']} interior={totals['interior']} multi={totals['multi']} reject={totals['reject']}")
|
||||
print("[analyze] owner counts: " + " ".join(f"e{e}={owner_counts[e]}" for e in range(ne)))
|
||||
for e in range(ne):
|
||||
b = by_owner[e]
|
||||
rlbl = "<=1" if max_radius == 1 else f"<=R{max_radius}"
|
||||
print(f"[analyze] e{e}: {rlbl} {b['r1']}/{b['cov']} ({pct(b['r1'], b['cov']):.2f}%) "
|
||||
f"multi{rlbl} {b['multi_r1']}/{b['multi']} ({pct(b['multi_r1'], b['multi']):.2f}%) "
|
||||
f"reject{rlbl} {b['reject_r1']}/{b['reject']} ({pct(b['reject_r1'], b['reject']):.2f}%)")
|
||||
print(f"[analyze] owner exact {totals['owner_r0']}/{totals['covered']} ({pct(totals['owner_r0'], totals['covered']):.2f}%)")
|
||||
if max_radius == 1:
|
||||
print(f"[analyze] owner <=1 texel {totals['owner_r1']}/{totals['covered']} ({pct(totals['owner_r1'], totals['covered']):.2f}%)")
|
||||
print(f"[analyze] interior <=1 {totals['interior_owner_r1']}/{totals['interior']} ({pct(totals['interior_owner_r1'], totals['interior']):.2f}%)")
|
||||
print(f"[analyze] multi <=1 {totals['multi_owner_r1']}/{totals['multi']} ({pct(totals['multi_owner_r1'], totals['multi']):.2f}%)")
|
||||
else:
|
||||
print(f"[analyze] owner <=R texel {totals['owner_r1']}/{totals['covered']} ({pct(totals['owner_r1'], totals['covered']):.2f}%)")
|
||||
print(f"[analyze] interior <=R {totals['interior_owner_r1']}/{totals['interior']} ({pct(totals['interior_owner_r1'], totals['interior']):.2f}%)")
|
||||
print(f"[analyze] multi <=R {totals['multi_owner_r1']}/{totals['multi']} ({pct(totals['multi_owner_r1'], totals['multi']):.2f}%)")
|
||||
print("[analyze] owner radius hist: " + " ".join(f"r{r}={radius_hist[r]}" for r in range(max_radius + 1)))
|
||||
if owner_mode == "z":
|
||||
print(f"[analyze] reject <=1 {totals['reject_owner_r1']}/{totals['reject']} ({pct(totals['reject_owner_r1'], totals['reject']):.2f}%)")
|
||||
print(f"[analyze] wrong-owner <=1 {totals['any_r1']}/{totals['covered']} ({pct(totals['any_r1'], totals['covered']):.2f}%)")
|
||||
print(f"[analyze] palette_bad={totals['palette_bad']} owner_missing_ref={totals['owner_missing_ref']}")
|
||||
if bad_examples:
|
||||
for o, owner, fbc, kind in bad_examples:
|
||||
print(f"[analyze] bad {kind}: px={o} x={o % w} y={o // w} owner=e{owner} fb={fbc:06x}")
|
||||
|
||||
if emit_maps:
|
||||
prefix = os.path.splitext(fb_path)[0] + f"_{owner_mode}oracle"
|
||||
write_maps(prefix, w, h, fb, status)
|
||||
|
||||
return 0 if totals["palette_bad"] == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch356: composition == isolated RTL bit-for-bit check.
|
||||
|
||||
Renders each epoch ALONE (tb +ONLY=<k> +FBDUMP) over a precleared-black FB, then composites the N isolation dumps in
|
||||
paint order (epoch 0, 1, ..., N-1; DECAL/overwrite) and asserts the result equals the joint ALL-mode render BIT-FOR-BIT.
|
||||
This is Codex's Ch356 accumulation-correctness proof: the scheduler drawing the epochs together must produce exactly the
|
||||
same framebuffer as compositing the individually-rendered epochs (no cache bleed, no stale pixels, correct rebind).
|
||||
|
||||
Composition operator (matches the RTL: precleared black bg, each epoch overwrites where it draws):
|
||||
compose[px] = the LAST epoch (highest k) that DREW px (isolation dump non-zero); else black.
|
||||
Note: an epoch is DECAL/opaque, so its written value replaces whatever is underneath. A genuine opaque-BLACK texel
|
||||
(dump==0 where the epoch drew) is indistinguishable from unwritten — but the joint ALL render is likewise black there
|
||||
(black-over-anything = black in this content), so nonzero-wins composition still matches ALL bit-for-bit. Do NOT use
|
||||
reference coverage to pick the owner: the RTL edge coverage differs slightly from the float reference, and a
|
||||
reference-covered-but-RTL-unwritten pixel must fall through to the epoch that ACTUALLY drew it.
|
||||
|
||||
Usage: compose_sched.py <fb_ALL.hex> <fb_0.hex> <fb_1.hex> ...
|
||||
Each *.hex file = W*H lines of 8-hex-digit PSMCT32 words (row-major), as emitted by +FBDUMP.
|
||||
"""
|
||||
import sys, os
|
||||
|
||||
def load(fn):
|
||||
out=[]
|
||||
with open(fn) as f:
|
||||
for ln in f:
|
||||
s=ln.strip()
|
||||
if not s or s.startswith("//"): continue
|
||||
out.append(int(s,16)&0xFFFFFFFF)
|
||||
return out
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]
|
||||
W=384
|
||||
if "--width" in a: i=a.index("--width"); W=int(a[i+1]); del a[i:i+2]
|
||||
if len(a)<3: sys.exit("usage: compose_sched.py <fb_ALL.hex> <fb_0.hex> <fb_1.hex> ...")
|
||||
all_fb=load(a[0]); eps=[load(x) for x in a[1:]]; N=len(eps)
|
||||
npx=len(all_fb)
|
||||
for k,e in enumerate(eps):
|
||||
if len(e)!=npx: sys.exit(f"[compose] epoch {k} dump {len(e)} px != ALL {npx}")
|
||||
|
||||
comp=[0]*npx
|
||||
for px in range(npx):
|
||||
for k in range(N-1,-1,-1):
|
||||
if eps[k][px]!=0: comp[px]=eps[k][px]; break # last epoch that actually DREW (DECAL nonzero-wins)
|
||||
|
||||
mism=[px for px in range(npx) if comp[px]!=all_fb[px]]
|
||||
black_all=sum(1 for px in mism if all_fb[px]==0)
|
||||
black_cmp=sum(1 for px in mism if comp[px]==0)
|
||||
print(f"[compose] {N} epochs, {npx} px. composition vs ALL: {npx-len(mism)} exact, {len(mism)} mismatch "
|
||||
f"({100.0*(npx-len(mism))/npx:.4f}% exact)")
|
||||
if mism:
|
||||
print(f"[compose] of the {len(mism)} mismatches: ALL==black:{black_all} compose==black:{black_cmp}")
|
||||
for px in mism[:12]:
|
||||
print(f"[compose] px {px} (x={px%W},y={px//W}): compose={comp[px]:08x} ALL={all_fb[px]:08x}")
|
||||
if not mism:
|
||||
print("[compose] PASS: joint ALL render == composited isolation dumps BIT-FOR-BIT (accumulation exact)")
|
||||
return 0
|
||||
print("[compose] FAIL: composition differs from ALL")
|
||||
return 1
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare captured RTL Z fragments against the fixture's software Z planes.
|
||||
|
||||
This is intentionally per-fragment, not per-final-frame. It reconstructs the
|
||||
same clipped triangles used by the ZSCHED feeder fixture, then asks whether each
|
||||
captured RTL fragment's Z is close to the expected screen-linear interpolated Z
|
||||
at that pixel.
|
||||
|
||||
Usage:
|
||||
tools/diagnose_zsched_frag_z.py [sim/traces/rtl/zsched_frags.txt]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, HERE)
|
||||
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
|
||||
|
||||
def load_epochs(tag="zsched"):
|
||||
path = os.path.join(DATA, f"sh3_{tag}_epochs.txt")
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if not s or s.startswith("#") or s.startswith("META"):
|
||||
continue
|
||||
p = s.split()
|
||||
out.append(int(p[1]))
|
||||
return out
|
||||
|
||||
|
||||
def find_dump():
|
||||
import glob
|
||||
c = glob.glob(os.path.join(ROOT, "captures", "gs", "silenthill3", "*224139*.gs.zst"))
|
||||
if not c:
|
||||
raise SystemExit("[fragz] no SH3 224139 dump found")
|
||||
return c[0]
|
||||
|
||||
|
||||
def edge(ax, ay, bx, by, px, py):
|
||||
return (px - ax) * (by - ay) - (py - ay) * (bx - ax)
|
||||
|
||||
|
||||
def clip_rect_z(tri, w, h):
|
||||
def lerp(p1, p2, al):
|
||||
return {k: (p1[k] + al * (p2[k] - p1[k])) for k in ("x", "y", "z", "s", "t", "q")}
|
||||
|
||||
def clip_edge(poly, inside, isect):
|
||||
out = []
|
||||
n = len(poly)
|
||||
for i in range(n):
|
||||
a = poly[i]
|
||||
b = poly[(i + 1) % n]
|
||||
ina = inside(a)
|
||||
inb = inside(b)
|
||||
if ina:
|
||||
out.append(a)
|
||||
if ina != inb:
|
||||
out.append(isect(a, b))
|
||||
return out
|
||||
|
||||
poly = [dict(x=v["x"], y=v["y"], z=v.get("z", 0.0), s=v["s"], t=v["t"], q=v["q"]) for v in tri]
|
||||
poly = clip_edge(poly, lambda p: p["x"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["x"]) / (b["x"] - a["x"])))
|
||||
if not poly:
|
||||
return []
|
||||
poly = clip_edge(poly, lambda p: p["x"] <= w, lambda a, b: lerp(a, b, (w - a["x"]) / (b["x"] - a["x"])))
|
||||
if not poly:
|
||||
return []
|
||||
poly = clip_edge(poly, lambda p: p["y"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["y"]) / (b["y"] - a["y"])))
|
||||
if not poly:
|
||||
return []
|
||||
poly = clip_edge(poly, lambda p: p["y"] <= h, lambda a, b: lerp(a, b, (h - a["y"]) / (b["y"] - a["y"])))
|
||||
if len(poly) < 3:
|
||||
return []
|
||||
return [(poly[0], poly[k], poly[k + 1]) for k in range(1, len(poly) - 1)]
|
||||
|
||||
|
||||
def tri_z_at(tri, x, y):
|
||||
v0, v1, v2 = tri
|
||||
ar = edge(v0["x"], v0["y"], v1["x"], v1["y"], v2["x"], v2["y"])
|
||||
if abs(ar) < 1e-9:
|
||||
return None
|
||||
inv = 1.0 / ar
|
||||
w0 = edge(v1["x"], v1["y"], v2["x"], v2["y"], x, y) * inv
|
||||
w1 = edge(v2["x"], v2["y"], v0["x"], v0["y"], x, y) * inv
|
||||
w2 = 1.0 - w0 - w1
|
||||
if w0 < -0.001 or w1 < -0.001 or w2 < -0.001:
|
||||
return None
|
||||
return w0 * v0["z"] + w1 * v1["z"] + w2 * v2["z"]
|
||||
|
||||
|
||||
def build_epoch_tris(draw_idxs, fbw, fbh):
|
||||
got, _ = MD.load_draws(find_dump(), draw_idxs)
|
||||
eps = [got[i] for i in draw_idxs]
|
||||
ox = int(min(min(v["x"] for v in e["verts"]) for e in eps))
|
||||
oy = int(min(min(v["y"] for v in e["verts"]) for e in eps))
|
||||
out = []
|
||||
for e in eps:
|
||||
fv = [dict(x=v["x"] - ox, y=v["y"] - oy, z=v["z"], s=v["s"], t=v["t"], q=v["q"]) for v in e["verts"]]
|
||||
raw = [(fv[i - 2], fv[i - 1], fv[i]) for i in range(2, len(fv))]
|
||||
tris = []
|
||||
for tri in raw:
|
||||
tris.extend(clip_rect_z(tri, fbw, fbh))
|
||||
out.append(tris)
|
||||
return out
|
||||
|
||||
|
||||
def best_expected(tris, x, y, sample_center):
|
||||
sx = x + 0.5 if sample_center else float(x)
|
||||
sy = y + 0.5 if sample_center else float(y)
|
||||
vals = []
|
||||
for tri in tris:
|
||||
z = tri_z_at(tri, sx, sy)
|
||||
if z is not None:
|
||||
vals.append(z)
|
||||
return vals
|
||||
|
||||
|
||||
def main(argv):
|
||||
frags = argv[1] if len(argv) > 1 else os.path.join(ROOT, "sim", "traces", "rtl", "zsched_frags.txt")
|
||||
draw_idxs = load_epochs("zsched")
|
||||
fbw, fbh = 256, 210
|
||||
tris_by_ep = build_epoch_tris(draw_idxs, fbw, fbh)
|
||||
|
||||
stats = {
|
||||
"n": 0, "nocov_center": 0, "nocov_corner": 0,
|
||||
"center_le0": 0, "center_le1": 0, "center_le16": 0, "center_le256": 0,
|
||||
"corner_le0": 0, "corner_le1": 0, "corner_le16": 0, "corner_le256": 0,
|
||||
}
|
||||
abs_sum_center = 0.0
|
||||
abs_sum_corner = 0.0
|
||||
ratios = []
|
||||
examples = []
|
||||
|
||||
with open(frags) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) < 5:
|
||||
continue
|
||||
ep, x, y, rz = int(p[0]), int(p[1]), int(p[2]), int(p[3])
|
||||
stats["n"] += 1
|
||||
cvals = best_expected(tris_by_ep[ep], x, y, True)
|
||||
kvals = best_expected(tris_by_ep[ep], x, y, False)
|
||||
if not cvals:
|
||||
stats["nocov_center"] += 1
|
||||
cdiff = None
|
||||
else:
|
||||
cz = min(cvals, key=lambda z: abs(rz - z))
|
||||
cdiff = abs(rz - cz)
|
||||
abs_sum_center += cdiff
|
||||
if cz:
|
||||
ratios.append(rz / cz)
|
||||
if cdiff <= 0.5:
|
||||
stats["center_le0"] += 1
|
||||
if cdiff <= 1:
|
||||
stats["center_le1"] += 1
|
||||
if cdiff <= 16:
|
||||
stats["center_le16"] += 1
|
||||
if cdiff <= 256:
|
||||
stats["center_le256"] += 1
|
||||
if not kvals:
|
||||
stats["nocov_corner"] += 1
|
||||
kdiff = None
|
||||
else:
|
||||
kz = min(kvals, key=lambda z: abs(rz - z))
|
||||
kdiff = abs(rz - kz)
|
||||
abs_sum_corner += kdiff
|
||||
if kdiff <= 0.5:
|
||||
stats["corner_le0"] += 1
|
||||
if kdiff <= 1:
|
||||
stats["corner_le1"] += 1
|
||||
if kdiff <= 16:
|
||||
stats["corner_le16"] += 1
|
||||
if kdiff <= 256:
|
||||
stats["corner_le256"] += 1
|
||||
if len(examples) < 16 and ((cdiff is None or cdiff > 256) and (kdiff is None or kdiff > 256)):
|
||||
examples.append((ep, x, y, rz, cdiff, kdiff, cvals[:3], kvals[:3]))
|
||||
|
||||
n = stats["n"]
|
||||
def pct(k):
|
||||
return 100.0 * stats[k] / n if n else 0.0
|
||||
|
||||
print(f"[fragz] fragments={n} draw_idxs={draw_idxs}")
|
||||
print(f"[fragz] center coverage misses={stats['nocov_center']} corner coverage misses={stats['nocov_corner']}")
|
||||
print(f"[fragz] center absdiff: <=0.5 {stats['center_le0']} ({pct('center_le0'):.2f}%) <=1 {stats['center_le1']} ({pct('center_le1'):.2f}%) <=16 {stats['center_le16']} ({pct('center_le16'):.2f}%) <=256 {stats['center_le256']} ({pct('center_le256'):.2f}%)")
|
||||
print(f"[fragz] corner absdiff: <=0.5 {stats['corner_le0']} ({pct('corner_le0'):.2f}%) <=1 {stats['corner_le1']} ({pct('corner_le1'):.2f}%) <=16 {stats['corner_le16']} ({pct('corner_le16'):.2f}%) <=256 {stats['corner_le256']} ({pct('corner_le256'):.2f}%)")
|
||||
if n - stats["nocov_center"] > 0:
|
||||
print(f"[fragz] mean center absdiff={abs_sum_center / (n - stats['nocov_center']):.2f}")
|
||||
if n - stats["nocov_corner"] > 0:
|
||||
print(f"[fragz] mean corner absdiff={abs_sum_corner / (n - stats['nocov_corner']):.2f}")
|
||||
if ratios:
|
||||
ratios.sort()
|
||||
mid = ratios[len(ratios) // 2]
|
||||
print(f"[fragz] raw/expected center ratio: p10={ratios[len(ratios)//10]:.4f} median={mid:.4f} p90={ratios[(len(ratios)*9)//10]:.4f}")
|
||||
for ep, x, y, rz, cdiff, kdiff, cvals, kvals in examples:
|
||||
print(f"[fragz] bad ep{ep} ({x},{y}) rtl_z={rz} center_diff={cdiff} corner_diff={kdiff} center_z={[round(v,1) for v in cvals]} corner_z={[round(v,1) for v in kvals]}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,522 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the SH3 ZSCHED fixture with the RTL's fixed-point perspective path.
|
||||
|
||||
The existing dump-derived reference is intentionally float-ish: it uses clipped
|
||||
float vertices and computes S/Q at a reference sample point. This tool answers a
|
||||
different question: if we mirror the RTL's rounded XYZ2 vertices, Q16.16
|
||||
gradient setup, top-left edge rule, gs_persp_uv reciprocal, and GEQUAL Z RMW,
|
||||
does the framebuffer line up with the board/zint dump?
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, HERE)
|
||||
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
|
||||
FBW = 256
|
||||
FBH = 210
|
||||
TW = 512
|
||||
TH = 512
|
||||
PERSP_FRAC = 12
|
||||
PSCALE = 4096
|
||||
S24_MAX = (1 << 23) - 1
|
||||
|
||||
|
||||
def load_mem(path):
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if s and not s.startswith("//"):
|
||||
out.append(int(s, 16) & 0xFFFFFFFF)
|
||||
return out
|
||||
|
||||
|
||||
def load_mem64(path):
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if s and not s.startswith("//"):
|
||||
out.append(int(s, 16) & 0xFFFFFFFFFFFFFFFF)
|
||||
return out
|
||||
|
||||
|
||||
def load_epochs(tag="zsched"):
|
||||
out = []
|
||||
with open(os.path.join(DATA, f"sh3_{tag}_epochs.txt")) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if s and not s.startswith("#") and not s.startswith("META"):
|
||||
out.append(int(s.split()[1]))
|
||||
return out
|
||||
|
||||
|
||||
def find_dump():
|
||||
c = glob.glob(os.path.join(ROOT, "captures", "gs", "silenthill3", "*224139*.gs.zst"))
|
||||
if not c:
|
||||
raise SystemExit("[persp] no SH3 224139 dump found")
|
||||
return c[0]
|
||||
|
||||
|
||||
def clip_rect_z(tri, w, h):
|
||||
def lerp(a, b, t):
|
||||
return {k: (a[k] + t * (b[k] - a[k])) for k in ("x", "y", "z", "s", "t", "q")}
|
||||
|
||||
def clip(poly, inside, cross):
|
||||
out = []
|
||||
for i, a in enumerate(poly):
|
||||
b = poly[(i + 1) % len(poly)]
|
||||
ina, inb = inside(a), inside(b)
|
||||
if ina:
|
||||
out.append(a)
|
||||
if ina != inb:
|
||||
out.append(cross(a, b))
|
||||
return out
|
||||
|
||||
poly = [dict(x=v["x"], y=v["y"], z=v["z"], s=v["s"], t=v["t"], q=v["q"]) for v in tri]
|
||||
poly = clip(poly, lambda p: p["x"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["x"]) / (b["x"] - a["x"])))
|
||||
if not poly:
|
||||
return []
|
||||
poly = clip(poly, lambda p: p["x"] <= w, lambda a, b: lerp(a, b, (w - a["x"]) / (b["x"] - a["x"])))
|
||||
if not poly:
|
||||
return []
|
||||
poly = clip(poly, lambda p: p["y"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["y"]) / (b["y"] - a["y"])))
|
||||
if not poly:
|
||||
return []
|
||||
poly = clip(poly, lambda p: p["y"] <= h, lambda a, b: lerp(a, b, (h - a["y"]) / (b["y"] - a["y"])))
|
||||
if len(poly) < 3:
|
||||
return []
|
||||
return [(poly[0], poly[i], poly[i + 1]) for i in range(1, len(poly) - 1)]
|
||||
|
||||
|
||||
def pack_vertex(v):
|
||||
s = round(v["s"] * TW * (1 << PERSP_FRAC) * PSCALE)
|
||||
t = round(v["t"] * TH * (1 << PERSP_FRAC) * PSCALE)
|
||||
q = round(v["q"] * (1 << PERSP_FRAC) * PSCALE)
|
||||
if abs(s) > S24_MAX or abs(t) > S24_MAX:
|
||||
raise SystemExit(f"[persp] ST overflow s={s} t={t}")
|
||||
if q < 0 or q > 0xFFFFFF:
|
||||
raise SystemExit(f"[persp] Q out of RTL 24-bit range q={q}")
|
||||
return {
|
||||
"x": max(0, min(FBW - 1, int(round(v["x"])))),
|
||||
"y": max(0, min(FBH - 1, int(round(v["y"])))),
|
||||
"z": max(0, min(0xFFFFFF, int(round(v["z"])))),
|
||||
"s": s & 0xFFFFFF,
|
||||
"t": t & 0xFFFFFF,
|
||||
"q": q & 0xFFFFFF,
|
||||
}
|
||||
|
||||
|
||||
def edge(px, py, ax, ay, bx, by):
|
||||
return (px - ax) * (by - ay) - (py - ay) * (bx - ax)
|
||||
|
||||
|
||||
def top_or_left(ax, ay, bx, by):
|
||||
dx = bx - ax
|
||||
dy = by - ay
|
||||
return (dy > 0) or (dy == 0 and dx > 0)
|
||||
|
||||
|
||||
def trunc_div(num, den):
|
||||
if den == 0:
|
||||
return 0
|
||||
sign = -1 if (num < 0) ^ (den < 0) else 1
|
||||
return sign * (abs(num) // abs(den))
|
||||
|
||||
|
||||
def grad_num_dadx(a0, a1, a2, x0, y0, x1, y1, x2, y2):
|
||||
return ((a1 - a0) * (y2 - y0) - (a2 - a0) * (y1 - y0)) << 16
|
||||
|
||||
|
||||
def grad_num_dady(a0, a1, a2, x0, y0, x1, y1, x2, y2):
|
||||
return ((a2 - a0) * (x1 - x0) - (a1 - a0) * (x2 - x0)) << 16
|
||||
|
||||
|
||||
def interp_wide(base, dadx, dady, x, y, x0, y0):
|
||||
step = dadx * (x - x0) + dady * (y - y0)
|
||||
return (base + (step >> 16)) & 0xFFFFFF
|
||||
|
||||
|
||||
def interp_z(base, dadx, dady, x, y, x0, y0):
|
||||
step = dadx * (x - x0) + dady * (y - y0)
|
||||
return max(0, min(0xFFFFFFFF, base + (step >> 16)))
|
||||
|
||||
|
||||
def recip_lut(q, idx_bits=11, scale=24):
|
||||
out_max = (1 << (scale + 1)) - 1
|
||||
if q <= 0:
|
||||
return out_max
|
||||
top = idx_bits - 1
|
||||
e = q.bit_length() - 1
|
||||
m = (q >> (e - top)) if e >= top else (q << (top - e))
|
||||
m &= (1 << idx_bits) - 1
|
||||
if m == 0:
|
||||
return out_max
|
||||
r = ((1 << (scale + top)) // m) >> e
|
||||
return min(r, out_max)
|
||||
|
||||
|
||||
def persp_uv(s, t, q):
|
||||
r = recip_lut(q)
|
||||
u = (s * r) >> 24
|
||||
v = (t * r) >> 24
|
||||
if u > 2047:
|
||||
u = 2047
|
||||
if v > 2047:
|
||||
v = 2047
|
||||
return u, v
|
||||
|
||||
|
||||
def texel(idx_words, pal, u, v):
|
||||
lin = v * TW + u
|
||||
if lin < 0 or lin >= len(idx_words) * 4:
|
||||
return 0
|
||||
ci = (idx_words[lin // 4] >> (8 * (lin % 4))) & 0xFF
|
||||
return pal[ci] & 0xFFFFFF
|
||||
|
||||
|
||||
def prep_tri(tri):
|
||||
verts = [pack_vertex(v) for v in tri]
|
||||
x0, y0 = verts[0]["x"], verts[0]["y"]
|
||||
x1, y1 = verts[1]["x"], verts[1]["y"]
|
||||
x2, y2 = verts[2]["x"], verts[2]["y"]
|
||||
det = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)
|
||||
if det == 0:
|
||||
return None
|
||||
if det < 0:
|
||||
verts = [verts[0], verts[2], verts[1]]
|
||||
det = -det
|
||||
v0, v1, v2 = verts
|
||||
x0, y0 = v0["x"], v0["y"]
|
||||
x1, y1 = v1["x"], v1["y"]
|
||||
x2, y2 = v2["x"], v2["y"]
|
||||
bias = [
|
||||
0 if top_or_left(x0, y0, x1, y1) else 1,
|
||||
0 if top_or_left(x1, y1, x2, y2) else 1,
|
||||
0 if top_or_left(x2, y2, x0, y0) else 1,
|
||||
]
|
||||
|
||||
def grad(attr):
|
||||
a0, a1, a2 = v0[attr], v1[attr], v2[attr]
|
||||
return (
|
||||
trunc_div(grad_num_dadx(a0, a1, a2, x0, y0, x1, y1, x2, y2), det),
|
||||
trunc_div(grad_num_dady(a0, a1, a2, x0, y0, x1, y1, x2, y2), det),
|
||||
)
|
||||
|
||||
return {
|
||||
"v": verts,
|
||||
"det": det,
|
||||
"bias": bias,
|
||||
"ds": grad("s"),
|
||||
"dt": grad("t"),
|
||||
"dq": grad("q"),
|
||||
"dz": grad("z"),
|
||||
}
|
||||
|
||||
|
||||
def prep_tri_packed(verts):
|
||||
verts = [dict(v) for v in verts]
|
||||
x0, y0 = verts[0]["x"], verts[0]["y"]
|
||||
x1, y1 = verts[1]["x"], verts[1]["y"]
|
||||
x2, y2 = verts[2]["x"], verts[2]["y"]
|
||||
det = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)
|
||||
if det == 0:
|
||||
return None
|
||||
if det < 0:
|
||||
verts = [verts[0], verts[2], verts[1]]
|
||||
det = -det
|
||||
v0, v1, v2 = verts
|
||||
x0, y0 = v0["x"], v0["y"]
|
||||
x1, y1 = v1["x"], v1["y"]
|
||||
x2, y2 = v2["x"], v2["y"]
|
||||
bias = [
|
||||
0 if top_or_left(x0, y0, x1, y1) else 1,
|
||||
0 if top_or_left(x1, y1, x2, y2) else 1,
|
||||
0 if top_or_left(x2, y2, x0, y0) else 1,
|
||||
]
|
||||
|
||||
def grad(attr):
|
||||
a0, a1, a2 = v0[attr], v1[attr], v2[attr]
|
||||
return (
|
||||
trunc_div(grad_num_dadx(a0, a1, a2, x0, y0, x1, y1, x2, y2), det),
|
||||
trunc_div(grad_num_dady(a0, a1, a2, x0, y0, x1, y1, x2, y2), det),
|
||||
)
|
||||
|
||||
return {
|
||||
"v": verts,
|
||||
"det": det,
|
||||
"bias": bias,
|
||||
"ds": grad("s"),
|
||||
"dt": grad("t"),
|
||||
"dq": grad("q"),
|
||||
"dz": grad("z"),
|
||||
}
|
||||
|
||||
|
||||
def build_tris(draw_idxs):
|
||||
got, _ = MD.load_draws(find_dump(), draw_idxs)
|
||||
eps = [got[i] for i in draw_idxs]
|
||||
ox = int(min(min(v["x"] for v in e["verts"]) for e in eps))
|
||||
oy = int(min(min(v["y"] for v in e["verts"]) for e in eps))
|
||||
out = []
|
||||
for e in eps:
|
||||
rawv = [dict(x=v["x"] - ox, y=v["y"] - oy, z=v["z"], s=v["s"], t=v["t"], q=v["q"]) for v in e["verts"]]
|
||||
raw = [(rawv[i - 2], rawv[i - 1], rawv[i]) for i in range(2, len(rawv))]
|
||||
tris = []
|
||||
for tri in raw:
|
||||
for ct in clip_rect_z(tri, FBW, FBH):
|
||||
qs = [v["q"] for v in ct]
|
||||
if all(q < 0 for q in qs):
|
||||
ct = tuple(dict(x=v["x"], y=v["y"], z=v["z"], s=-v["s"], t=-v["t"], q=-v["q"]) for v in ct)
|
||||
elif not all(q > 0 for q in qs):
|
||||
raise SystemExit(f"[persp] mixed/zero Q in idx{e['first_idx']}: {qs}")
|
||||
pt = prep_tri(ct)
|
||||
if pt is not None:
|
||||
tris.append(pt)
|
||||
out.append(tris)
|
||||
return out
|
||||
|
||||
|
||||
def build_tris_from_feeder(tag="zsched"):
|
||||
out = []
|
||||
ep = 0
|
||||
while True:
|
||||
path = os.path.join(DATA, f"feeder_sh3_{tag}{ep}.mem")
|
||||
if not os.path.exists(path):
|
||||
break
|
||||
words = load_mem64(path)
|
||||
ntris = words[0] & 0xFFFF
|
||||
tris = []
|
||||
off = 7
|
||||
for _ in range(ntris):
|
||||
verts = []
|
||||
for _v in range(3):
|
||||
rgbaq = words[off]
|
||||
st = words[off + 1]
|
||||
xyz = words[off + 2]
|
||||
verts.append({
|
||||
"x": (xyz >> 4) & 0xFFF,
|
||||
"y": (xyz >> 20) & 0xFFF,
|
||||
"z": (xyz >> 32) & 0xFFFFFFFF,
|
||||
"s": st & 0xFFFFFF,
|
||||
"t": (st >> 32) & 0xFFFFFF,
|
||||
"q": (rgbaq >> 32) & 0xFFFFFF,
|
||||
})
|
||||
off += 3
|
||||
pt = prep_tri_packed(verts)
|
||||
if pt is not None:
|
||||
tris.append(pt)
|
||||
out.append(tris)
|
||||
ep += 1
|
||||
return out
|
||||
|
||||
|
||||
def render(tag="zsched"):
|
||||
draw_idxs = load_epochs(tag)
|
||||
idx = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_idx.mem")) for e in range(len(draw_idxs))]
|
||||
pal = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_pal.mem")) for e in range(len(draw_idxs))]
|
||||
tris_by_ep = build_tris_from_feeder(tag)
|
||||
if len(tris_by_ep) != len(draw_idxs):
|
||||
tris_by_ep = build_tris(draw_idxs)
|
||||
|
||||
fb = [0] * (FBW * FBH)
|
||||
zbuf = [-1] * (FBW * FBH)
|
||||
owner = [-1] * (FBW * FBH)
|
||||
emitted = [0] * len(draw_idxs)
|
||||
accepted = [0] * len(draw_idxs)
|
||||
frags = []
|
||||
for ep, tris in enumerate(tris_by_ep):
|
||||
for tri in tris:
|
||||
v0, v1, v2 = tri["v"]
|
||||
minx = max(0, min(v0["x"], v1["x"], v2["x"]))
|
||||
maxx = min(FBW - 1, max(v0["x"], v1["x"], v2["x"]))
|
||||
miny = max(0, min(v0["y"], v1["y"], v2["y"]))
|
||||
maxy = min(FBH - 1, max(v0["y"], v1["y"], v2["y"]))
|
||||
for y in range(miny, maxy + 1):
|
||||
for x in range(minx, maxx + 1):
|
||||
e0 = edge(x, y, v0["x"], v0["y"], v1["x"], v1["y"]) + tri["bias"][0]
|
||||
e1 = edge(x, y, v1["x"], v1["y"], v2["x"], v2["y"]) + tri["bias"][1]
|
||||
e2 = edge(x, y, v2["x"], v2["y"], v0["x"], v0["y"]) + tri["bias"][2]
|
||||
if e0 > 0 or e1 > 0 or e2 > 0:
|
||||
continue
|
||||
s = interp_wide(v0["s"], tri["ds"][0], tri["ds"][1], x, y, v0["x"], v0["y"])
|
||||
t = interp_wide(v0["t"], tri["dt"][0], tri["dt"][1], x, y, v0["x"], v0["y"])
|
||||
q = interp_wide(v0["q"], tri["dq"][0], tri["dq"][1], x, y, v0["x"], v0["y"])
|
||||
z = interp_z(v0["z"], tri["dz"][0], tri["dz"][1], x, y, v0["x"], v0["y"])
|
||||
u, v = persp_uv(s, t, q)
|
||||
col = texel(idx[ep], pal[ep], u, v)
|
||||
o = y * FBW + x
|
||||
emitted[ep] += 1
|
||||
frags.append((ep, x, y, z, col, u, v, s, t, q))
|
||||
zq = min(z, 0xFFFF)
|
||||
if zq >= zbuf[o]:
|
||||
zbuf[o] = zq
|
||||
fb[o] = col
|
||||
owner[o] = ep
|
||||
accepted[ep] += 1
|
||||
return draw_idxs, tris_by_ep, fb, owner, emitted, accepted, frags
|
||||
|
||||
|
||||
def nearest_color(idx_words, pal, u, v, color, max_radius=64):
|
||||
for rad in range(max_radius + 1):
|
||||
for dv in range(-rad, rad + 1):
|
||||
for du in range(-rad, rad + 1):
|
||||
if max(abs(du), abs(dv)) != rad:
|
||||
continue
|
||||
uu = u + du
|
||||
vv = v + dv
|
||||
if 0 <= uu < TW and 0 <= vv < TH and texel(idx_words, pal, uu, vv) == color:
|
||||
return rad, du, dv
|
||||
return None
|
||||
|
||||
|
||||
def compare_trace(frags, path, idx, pal):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
got = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) >= 5:
|
||||
got.append((int(p[0]), int(p[1]), int(p[2]), int(p[3]), int(p[4], 16) & 0xFFFFFF))
|
||||
n = min(len(frags), len(got))
|
||||
coord_miss = color_miss = z_miss256 = 0
|
||||
near_bins = {"r1": 0, "r8": 0, "r32": 0, "r64": 0, "miss": 0}
|
||||
by_ep = {}
|
||||
examples = []
|
||||
for i in range(n):
|
||||
me = frags[i]
|
||||
hw = got[i]
|
||||
ep = hw[0]
|
||||
st = by_ep.setdefault(ep, {"n": 0, "coord": 0, "color": 0, "z256": 0})
|
||||
st["n"] += 1
|
||||
if me[:3] != hw[:3]:
|
||||
coord_miss += 1
|
||||
st["coord"] += 1
|
||||
if (me[4] & 0xFFFFFF) != hw[4]:
|
||||
color_miss += 1
|
||||
st["color"] += 1
|
||||
near = nearest_color(idx[ep], pal[ep], me[5], me[6], hw[4])
|
||||
if near is None:
|
||||
near_bins["miss"] += 1
|
||||
else:
|
||||
rad, _du, _dv = near
|
||||
if rad <= 1:
|
||||
near_bins["r1"] += 1
|
||||
if rad <= 8:
|
||||
near_bins["r8"] += 1
|
||||
if rad <= 32:
|
||||
near_bins["r32"] += 1
|
||||
if rad <= 64:
|
||||
near_bins["r64"] += 1
|
||||
if abs(me[3] - hw[3]) > 256:
|
||||
z_miss256 += 1
|
||||
st["z256"] += 1
|
||||
if len(examples) < 12 and (me[:3] != hw[:3] or (me[4] & 0xFFFFFF) != hw[4] or abs(me[3] - hw[3]) > 256):
|
||||
near = nearest_color(idx[ep], pal[ep], me[5], me[6], hw[4]) if (me[4] & 0xFFFFFF) != hw[4] else None
|
||||
examples.append((i, me, hw, near))
|
||||
print(f"[persp] trace={path} model_frags={len(frags)} trace_frags={len(got)} compared={n}")
|
||||
print(f"[persp] trace_miss coord={coord_miss} color={color_miss} z_gt256={z_miss256}")
|
||||
print(f"[persp] trace color-near model_uv: <=1 {near_bins['r1']} <=8 {near_bins['r8']} <=32 {near_bins['r32']} <=64 {near_bins['r64']} >64/notfound {near_bins['miss']}")
|
||||
for ep in sorted(by_ep):
|
||||
st = by_ep[ep]
|
||||
print(f"[persp] trace e{ep}: n={st['n']} coord={st['coord']} color={st['color']} z_gt256={st['z256']}")
|
||||
for i, me, hw, near in examples:
|
||||
ns = "near=none" if near is None else f"near r={near[0]} du={near[1]} dv={near[2]}"
|
||||
print(f"[persp] trace miss#{i}: model ep{me[0]} ({me[1]},{me[2]}) z={me[3]} uv=({me[5]},{me[6]}) col={me[4]&0xFFFFFF:06x} "
|
||||
f"trace ep{hw[0]} ({hw[1]},{hw[2]}) z={hw[3]} col={hw[4]:06x} {ns}")
|
||||
|
||||
|
||||
def compare_issue(frags, path):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
got = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) >= 7:
|
||||
got.append((int(p[0]), int(p[1]), int(p[2]), int(p[3]), int(p[4]), int(p[5]), int(p[6])))
|
||||
n = min(len(frags), len(got))
|
||||
by_ep = {}
|
||||
s_miss = t_miss = u_miss = v_miss = recip_miss = 0
|
||||
examples = []
|
||||
for i in range(n):
|
||||
me = frags[i]
|
||||
hw = got[i]
|
||||
ep = hw[0]
|
||||
st = by_ep.setdefault(ep, {"n": 0, "s": 0, "t": 0, "u": 0, "v": 0, "recip": 0})
|
||||
st["n"] += 1
|
||||
mr = recip_lut(me[9])
|
||||
if me[7] != hw[1]:
|
||||
s_miss += 1
|
||||
st["s"] += 1
|
||||
if me[8] != hw[2]:
|
||||
t_miss += 1
|
||||
st["t"] += 1
|
||||
if mr != hw[3]:
|
||||
recip_miss += 1
|
||||
st["recip"] += 1
|
||||
if me[5] != hw[4]:
|
||||
u_miss += 1
|
||||
st["u"] += 1
|
||||
if me[6] != hw[5]:
|
||||
v_miss += 1
|
||||
st["v"] += 1
|
||||
if len(examples) < 12 and (me[7] != hw[1] or me[8] != hw[2] or mr != hw[3] or me[5] != hw[4] or me[6] != hw[5]):
|
||||
examples.append((i, me, hw, mr))
|
||||
print(f"[persp] issue={path} model_frags={len(frags)} issue_rows={len(got)} compared={n}")
|
||||
print(f"[persp] issue_miss s={s_miss} t={t_miss} recip={recip_miss} u={u_miss} v={v_miss}")
|
||||
for ep in sorted(by_ep):
|
||||
st = by_ep[ep]
|
||||
print(f"[persp] issue e{ep}: n={st['n']} s={st['s']} t={st['t']} recip={st['recip']} u={st['u']} v={st['v']}")
|
||||
for i, me, hw, mr in examples:
|
||||
print(f"[persp] issue miss#{i}: model ep{me[0]} ({me[1]},{me[2]}) s={me[7]} t={me[8]} q={me[9]} recip={mr} uv=({me[5]},{me[6]}) "
|
||||
f"rtl ep{hw[0]} s={hw[1]} t={hw[2]} recip={hw[3]} uv=({hw[4]},{hw[5]}) valid={hw[6]}")
|
||||
|
||||
|
||||
def main(argv):
|
||||
args = argv[1:]
|
||||
fb_path = args[0] if args and not args[0].startswith("--") else os.path.join(DATA, "sh3_zsched_board_fb.mem")
|
||||
board = load_mem(fb_path)
|
||||
trace_path = args[args.index("--trace") + 1] if "--trace" in args else os.path.join(ROOT, "sim", "traces", "rtl", "zsched_frags.txt")
|
||||
draw_idxs, tris_by_ep, fb, owner, emitted, accepted, frags = render()
|
||||
if len(board) != len(fb):
|
||||
raise SystemExit(f"[persp] {fb_path}: {len(board)} words != expected {len(fb)}")
|
||||
mism = []
|
||||
cov = sum(1 for x in owner if x >= 0)
|
||||
for i, (a, b) in enumerate(zip(board, fb)):
|
||||
if (a & 0xFFFFFF) != (b & 0xFFFFFF):
|
||||
mism.append(i)
|
||||
by_owner = [0] * len(draw_idxs)
|
||||
by_miss = [0] * len(draw_idxs)
|
||||
for o in range(len(fb)):
|
||||
ep = owner[o]
|
||||
if ep >= 0:
|
||||
by_owner[ep] += 1
|
||||
miss_set = set(mism)
|
||||
for o in miss_set:
|
||||
ep = owner[o]
|
||||
if ep >= 0:
|
||||
by_miss[ep] += 1
|
||||
|
||||
print(f"[persp] fb={fb_path}")
|
||||
print(f"[persp] draw_idxs={draw_idxs} FB={FBW}x{FBH} covered={cov} mismatches={len(mism)}/{len(fb)}")
|
||||
for ep, idx in enumerate(draw_idxs):
|
||||
print(f"[persp] e{ep} idx{idx}: tris={len(tris_by_ep[ep])} emitted={emitted[ep]} accepted={accepted[ep]} final_owner={by_owner[ep]} miss={by_miss[ep]}")
|
||||
for o in mism[:12]:
|
||||
print(f"[persp] miss x={o % FBW} y={o // FBW} owner=e{owner[o]} model={fb[o] & 0xFFFFFF:06x} board={board[o] & 0xFFFFFF:06x}")
|
||||
trace_idxs = [load_mem(os.path.join(DATA, f"sh3_zsched{e}_idx.mem")) for e in range(len(draw_idxs))]
|
||||
trace_pals = [load_mem(os.path.join(DATA, f"sh3_zsched{e}_pal.mem")) for e in range(len(draw_idxs))]
|
||||
compare_trace(frags, trace_path, trace_idxs, trace_pals)
|
||||
compare_issue(frags, os.path.join(ROOT, "sim", "traces", "rtl", "zsched_issue.txt"))
|
||||
return 0 if not mism else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
# Convert a linear PSMCT32 framebuffer dump ($fwrite "%08x") to a PNG for visual triage.
|
||||
# Word layout = PS2 PSMCT32 0xAABBGGRR (low byte R). Usage:
|
||||
# gs_fb_to_png.py <fb.mem> <out.png> [width] [height] [scale]
|
||||
# Defaults to the SH3 real-draw FB (256x120). No-op (exit 0) if input missing or PIL absent,
|
||||
# so it can be chained after a sim without ever breaking the build.
|
||||
import sys
|
||||
def main():
|
||||
a = sys.argv
|
||||
src = a[1] if len(a) > 1 else "sim/data/top_psmct32_raster_demo/sh3_real_fb_out.mem"
|
||||
dst = a[2] if len(a) > 2 else "sim/data/top_psmct32_raster_demo/sh3_real_fb_rtl.png"
|
||||
W = int(a[3]) if len(a) > 3 else 256
|
||||
H = int(a[4]) if len(a) > 4 else 120
|
||||
SC = int(a[5]) if len(a) > 5 else 3
|
||||
try:
|
||||
from PIL import Image
|
||||
except Exception:
|
||||
print("[fb_to_png] PIL not available; skipping PNG"); return 0
|
||||
try:
|
||||
words = []
|
||||
with open(src) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if not s or s.startswith("/"): continue
|
||||
words.append(int(s, 16) & 0xFFFFFFFF)
|
||||
except FileNotFoundError:
|
||||
print(f"[fb_to_png] {src} not found; skipping"); return 0
|
||||
img = Image.new("RGB", (W, H))
|
||||
for i, w in enumerate(words[:W*H]):
|
||||
img.putpixel((i % W, i // W), (w & 0xFF, (w >> 8) & 0xFF, (w >> 16) & 0xFF))
|
||||
img.resize((W*SC, H*SC), Image.NEAREST).save(dst)
|
||||
print(f"[fb_to_png] wrote {dst} ({W}x{H} x{SC})")
|
||||
return 0
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — quantitative fidelity metric: our rendered frame vs the real PCSX2 frame (reference C).
|
||||
|
||||
Turns "does it look like SH3?" into numbers. Consumes any 640x480 RGB(A) PNG we produce (software composite B
|
||||
from gs_sh3_frame_ref.py, or a framebuffer dumped from an RTL sim) and a PCSX2 screenshot (C), and reports:
|
||||
- GLOBAL: MSE, PSNR, mean-abs-error, %pixels within a per-channel tolerance.
|
||||
- PER-REGION: a GxH tile grid of per-tile MSE, the worst-N tiles ranked, and a diff heatmap PNG so the
|
||||
deficit is ATTRIBUTED (which part of the screen, how badly) instead of eyeballed.
|
||||
- Optionally restricted to PAINTED pixels only (alpha>0 in ours) so background fill doesn't dilute the score.
|
||||
|
||||
Reference C is the display output of the real GS; our composite is opaque-textures-only (no GS blend/fog/light),
|
||||
so a perfect score is NOT expected. The value is the ATTRIBUTION: a high-error tile localized to, say, the
|
||||
alpha-blended fog band tells us blending is the next rung; uniformly high error would implicate reciprocal/UV.
|
||||
|
||||
Alignment: C is resized to 640x480. PCSX2 NTSC output may be cropped/offset a few px vs GS screen space; use
|
||||
--search P to brute-force the best integer (dx,dy) shift in [-P..P] (minimising global MSE) before scoring.
|
||||
|
||||
Usage:
|
||||
gs_frame_metric.py --ours frameN_composite.png --ref pcsx2_ref.png [--grid 16x12] [--worst 12]
|
||||
[--tol 16] [--painted-only] [--search 4] [--out DIR]
|
||||
"""
|
||||
import sys, os
|
||||
|
||||
def load_rgb(path, resize=None):
|
||||
from PIL import Image
|
||||
img = Image.open(path).convert("RGBA")
|
||||
if resize and img.size != resize:
|
||||
img = img.resize(resize, Image.BILINEAR)
|
||||
return img
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 2 or "--ours" not in argv or "--ref" not in argv:
|
||||
print(__doc__); return 2
|
||||
def opt(n, dv=None): return argv[argv.index(n)+1] if n in argv else dv
|
||||
ours_p = opt("--ours"); ref_p = opt("--ref")
|
||||
gx, gy = (int(v) for v in opt("--grid", "16x12").lower().split("x"))
|
||||
worst_n = int(opt("--worst", "12")); tol = int(opt("--tol", "16"))
|
||||
search = int(opt("--search", "0")); painted_only = ("--painted-only" in argv)
|
||||
outdir = opt("--out", os.path.dirname(os.path.abspath(ours_p)))
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
|
||||
from PIL import Image
|
||||
ours = load_rgb(ours_p)
|
||||
W, H = ours.size
|
||||
ref = load_rgb(ref_p, resize=(W, H))
|
||||
op = ours.load(); rp = ref.load()
|
||||
|
||||
def scored_pixels(dx, dy):
|
||||
"""Yield (x,y,(or,og,ob),(rr,rg,rb)) for pixels compared at shift (dx,dy)."""
|
||||
for y in range(H):
|
||||
ry = y + dy
|
||||
if ry < 0 or ry >= H: continue
|
||||
for x in range(W):
|
||||
rx = x + dx
|
||||
if rx < 0 or rx >= W: continue
|
||||
o = op[x, y]
|
||||
if painted_only and o[3] == 0: continue
|
||||
yield x, y, o[:3], rp[rx, ry][:3]
|
||||
|
||||
def mse_at(dx, dy):
|
||||
s = n = 0
|
||||
for _, _, o, r in scored_pixels(dx, dy):
|
||||
s += (o[0]-r[0])**2 + (o[1]-r[1])**2 + (o[2]-r[2])**2; n += 1
|
||||
return (s/(3*n) if n else float("inf")), n
|
||||
|
||||
# optional integer-shift alignment
|
||||
bdx = bdy = 0
|
||||
if search > 0:
|
||||
best = None
|
||||
for dy in range(-search, search+1):
|
||||
for dx in range(-search, search+1):
|
||||
m, n = mse_at(dx, dy)
|
||||
if best is None or m < best[0]: best = (m, dx, dy)
|
||||
_, bdx, bdy = best
|
||||
print(f"[metric] best alignment shift dx={bdx} dy={bdy} (searched +/-{search})")
|
||||
|
||||
# global stats + per-tile accumulation at the chosen shift
|
||||
tiles_se = [[0]*gx for _ in range(gy)]
|
||||
tiles_n = [[0]*gx for _ in range(gy)]
|
||||
tw, th = W/gx, H/gy
|
||||
S = Nabs = 0; N = 0; within = 0
|
||||
diff = Image.new("RGB", (W, H))
|
||||
dp = diff.load()
|
||||
for x, y, o, r in scored_pixels(bdx, bdy):
|
||||
e = [abs(o[i]-r[i]) for i in range(3)]
|
||||
se = e[0]**2 + e[1]**2 + e[2]**2
|
||||
S += se; Nabs += e[0]+e[1]+e[2]; N += 1
|
||||
if max(e) <= tol: within += 1
|
||||
gxi = min(gx-1, int(x/tw)); gyi = min(gy-1, int(y/th))
|
||||
tiles_se[gyi][gxi] += se; tiles_n[gyi][gxi] += 1
|
||||
# heatmap: brighter red = larger per-pixel error (scaled so 0..441 -> 0..255)
|
||||
mag = min(255, int((se**0.5)))
|
||||
dp[x, y] = (mag, 0, 128-min(128, mag//2))
|
||||
|
||||
if N == 0:
|
||||
print("[metric] no comparable pixels (painted-only with empty frame?)"); return 1
|
||||
mse = S/(3*N); import math
|
||||
psnr = 10*math.log10((255.0**2)/mse) if mse > 0 else float("inf")
|
||||
print(f"[metric] compared {N} px ({'painted-only' if painted_only else 'all'}), grid {gx}x{gy}, tol +/-{tol}/ch")
|
||||
print(f"[metric] MSE={mse:.2f} PSNR={psnr:.2f} dB MAE={Nabs/(3*N):.2f}/ch within-tol={100*within/N:.1f}%")
|
||||
|
||||
tile_mse = []
|
||||
for gyi in range(gy):
|
||||
for gxi in range(gx):
|
||||
n = tiles_n[gyi][gxi]
|
||||
if n: tile_mse.append((tiles_se[gyi][gxi]/(3*n), gxi, gyi, n))
|
||||
tile_mse.sort(reverse=True)
|
||||
print(f"[metric] worst {min(worst_n,len(tile_mse))} tiles (col,row of {gx}x{gy} grid) by MSE:")
|
||||
for m, gxi, gyi, n in tile_mse[:worst_n]:
|
||||
px0, py0 = int(gxi*tw), int(gyi*th)
|
||||
print(f" tile(c{gxi:2d},r{gyi:2d}) screen x[{px0}..{px0+int(tw)}] y[{py0}..{py0+int(th)}] MSE={m:.1f} n={n}")
|
||||
|
||||
base = os.path.splitext(os.path.basename(ours_p))[0]
|
||||
hm = os.path.join(outdir, f"{base}_vs_ref_heatmap.png"); diff.save(hm)
|
||||
# triptych: ours | ref | heatmap
|
||||
trip = Image.new("RGB", (W*3+16, H), (30, 30, 30))
|
||||
trip.paste(ours.convert("RGB"), (0, 0)); trip.paste(ref, (W+8, 0)); trip.paste(diff, (W*2+16, 0))
|
||||
tp = os.path.join(outdir, f"{base}_vs_ref_triptych.png"); trip.save(tp)
|
||||
print(f"[metric] wrote {hm}\n[metric] wrote {tp} (ours | C | error-heatmap)")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -13,6 +13,9 @@ Address convention matches the codebase: VRAM byte base = PTR*256 (TBP0/DBP/CBP
|
||||
and PTRs are page-aligned (multiple of 32) so page_index*8192 composes correctly off that base.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
# block grid is shared by PSMCT32 and PSMT8 (4 rows x 8 cols), value = block index within page
|
||||
BLOCK = [
|
||||
[ 0, 1, 4, 5,16,17,20,21],
|
||||
@@ -52,6 +55,40 @@ def psmt8_addr(tbp, fbw, x, y):
|
||||
block_idx = BLOCK[(y >> 4) & 3][(x >> 4) & 7]
|
||||
return tbp*256 + page_index*8192 + block_idx*256 + COL8[y & 15][x & 15]
|
||||
|
||||
_COL4 = None
|
||||
_BLOCK4 = (
|
||||
(0,2,8,10), (1,3,9,11), (4,6,12,14), (5,7,13,15),
|
||||
(16,18,24,26), (17,19,25,27), (20,22,28,30), (21,23,29,31),
|
||||
)
|
||||
|
||||
def _col4_table():
|
||||
"""Load the canonical PSMT4 permutation from the RTL contract."""
|
||||
global _COL4
|
||||
if _COL4 is not None:
|
||||
return _COL4
|
||||
rtl = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"rtl", "gif_gs", "gs_swizzle_psmt4_stub.sv")
|
||||
text = open(rtl, encoding="utf-8").read()
|
||||
lo = text.index("function automatic logic [8:0] col_idx_psmt4")
|
||||
hi = text.index("endfunction", lo)
|
||||
tab = [None] * 512
|
||||
for key, value in re.findall(r"9'd(\d+)\s*:\s*return\s+9'd(\d+)", text[lo:hi]):
|
||||
tab[int(key)] = int(value)
|
||||
tab[511] = 511 # explicit default arm in the RTL function
|
||||
if any(v is None for v in tab) or sorted(tab) != list(range(512)):
|
||||
raise RuntimeError("PSMT4 RTL column table is incomplete or non-bijective")
|
||||
_COL4 = tuple(tab)
|
||||
return _COL4
|
||||
|
||||
def psmt4_addr(tbp, fbw, x, y):
|
||||
"""Return (byte address, high-nibble select) for a PSMT4 texel."""
|
||||
if fbw & 1:
|
||||
raise ValueError(f"PSMT4 TBW must be even, got {fbw}")
|
||||
page_index = (y >> 7) * (fbw >> 1) + (x >> 7)
|
||||
block_idx = _BLOCK4[(y >> 4) & 7][(x >> 5) & 3]
|
||||
nib = _col4_table()[((y & 15) << 5) | (x & 31)]
|
||||
return tbp*256 + page_index*8192 + block_idx*256 + (nib >> 1), bool(nib & 1)
|
||||
|
||||
class LocalMem:
|
||||
"""4 MiB GS VRAM. Seed from the dump's initial snapshot, then replay host->local uploads in order."""
|
||||
SIZE = 0x400000
|
||||
@@ -82,6 +119,17 @@ class LocalMem:
|
||||
out[r+x] = self.m[a] if 0 <= a < self.SIZE else 0
|
||||
return out
|
||||
|
||||
def read_psmt4(self, tbp, fbw, tw, th):
|
||||
"""Return one unpacked 0..15 index byte per raster-order texel."""
|
||||
out = bytearray(tw*th)
|
||||
for y in range(th):
|
||||
r = y*tw
|
||||
for x in range(tw):
|
||||
a, hi = psmt4_addr(tbp, fbw, x, y)
|
||||
b = self.m[a] if 0 <= a < self.SIZE else 0
|
||||
out[r+x] = ((b >> 4) & 0xF) if hi else (b & 0xF)
|
||||
return out
|
||||
|
||||
def read_ct32_word(self, dbp, dbw, x, y):
|
||||
a = ct32_addr(dbp, dbw, x, y)
|
||||
return int.from_bytes(self.m[a:a+4], "little") if a+4 <= self.SIZE else 0
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
# Ch352 diagnostic (Codex's test): replace the SH3 texture indices with a LARGE, UNMISTAKABLE
|
||||
# 16x16-cell pattern using strongly-contrasting indices from the REAL SH3 CSM1 palette, and emit it
|
||||
# through the EXACT same linear LPDDR texture file the uploader streams. Geometry / CLUT / feeder are
|
||||
# untouched. If the cells render as clean flat colors -> the board photo is authentic high-frequency
|
||||
# SH3 texture, NOT timing corruption. If the flat cells speckle -> real corruption.
|
||||
#
|
||||
# LOCAL/gitignored (palette is dump-derived). Backs up the real texture to *.REAL.bak first.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from gs_localmem import ct32_addr # CT32 grid de-swizzle (CSM1 palette layout)
|
||||
|
||||
DATA = os.path.join(os.path.dirname(__file__), "..", "sim", "data", "top_psmct32_raster_demo")
|
||||
TEX = os.path.join(DATA, "sh3_real_tex_lpddr.mem")
|
||||
CLUT = os.path.join(DATA, "sh3_real_clut.mem")
|
||||
TW = TH = 512
|
||||
CELL = 16
|
||||
NCHOSEN = 8
|
||||
|
||||
def load_words(path):
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
s = ln.strip()
|
||||
if not s or s.startswith("/"):
|
||||
continue
|
||||
out.append(int(s, 16) & 0xFFFFFFFF)
|
||||
return out
|
||||
|
||||
# ---- 1) de-grid the CSM1 palette to index order, get each index's RGB ----
|
||||
clut_words = load_words(CLUT)
|
||||
clut_bytes = bytearray()
|
||||
for w in clut_words:
|
||||
clut_bytes += (w & 0xFFFFFFFF).to_bytes(4, "little")
|
||||
pal = [0]*256
|
||||
for i in range(256):
|
||||
a = ct32_addr(0, 1, i & 15, i >> 4) # palette entry i sits at grid (i%16, i//16)
|
||||
pal[i] = int.from_bytes(clut_bytes[a:a+4], "little") if a+4 <= len(clut_bytes) else 0
|
||||
rgb = [(p & 0xFF, (p >> 8) & 0xFF, (p >> 16) & 0xFF) for p in pal] # PSMCT32 low byte = R
|
||||
|
||||
# ---- 2) greedy farthest-point pick of NCHOSEN maximally-contrasting indices ----
|
||||
def d2(a, b): return sum((a[k]-b[k])**2 for k in range(3))
|
||||
chosen = [max(range(256), key=lambda i: rgb[i][0]+rgb[i][1]+rgb[i][2])] # start brightest
|
||||
while len(chosen) < NCHOSEN:
|
||||
nxt = max(range(256), key=lambda i: min(d2(rgb[i], rgb[c]) for c in chosen))
|
||||
chosen.append(nxt)
|
||||
print("[banded] chosen indices + RGB:", [(c, rgb[c]) for c in chosen])
|
||||
|
||||
# ---- 3) build the 16x16-cell pattern as LINEAR raster indices (PSMT8_SWIZZLE=0) ----
|
||||
idx = bytearray(TW*TH)
|
||||
for y in range(TH):
|
||||
cy = y // CELL
|
||||
for x in range(TW):
|
||||
cx = x // CELL
|
||||
idx[y*TW + x] = chosen[(cx + cy) % NCHOSEN] # diagonal bands of contrasting flat cells
|
||||
|
||||
# ---- 4) pack 4 indices / LE word, write through the exact uploader format ----
|
||||
if os.path.exists(TEX) and not os.path.exists(TEX + ".REAL.bak"):
|
||||
os.rename(TEX, TEX + ".REAL.bak")
|
||||
print("[banded] backed up real texture ->", os.path.basename(TEX) + ".REAL.bak")
|
||||
nwords = (TW*TH)//4
|
||||
with open(TEX, "w") as f:
|
||||
f.write("// Ch352 DIAGNOSTIC banded 16x16-cell texture (contrasting SH3 palette indices). "
|
||||
"LINEAR de-swizzled, PSMT8_SWIZZLE=0. Restore from sh3_real_tex_lpddr.mem.REAL.bak. gitignored.\n")
|
||||
sm = 0
|
||||
for w in range(nwords):
|
||||
word = idx[4*w] | (idx[4*w+1] << 8) | (idx[4*w+2] << 16) | (idx[4*w+3] << 24)
|
||||
sm = (sm + word) & 0xFFFFFFFF
|
||||
f.write("%08x\n" % word)
|
||||
print(f"[banded] wrote {nwords} words to {TEX} sum32=0x{sm:08x}")
|
||||
print("[banded] upload as usual; on HDMI expect a perspective-warped grid of FLAT contrasting cells.")
|
||||
print("[banded] CLEAN flat cells => authentic texture (not corruption). SPECKLED cells => real corruption.")
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Emit SH3's two authentic PSMCT32 full-frame darkening sprites.
|
||||
|
||||
The source frame builds a 256x256 direct-color intermediate at TBP 11264,
|
||||
then draws it over the 512-wide display as two ABE sprites (indices 196167
|
||||
and 196177). The production texture cache is exactly 256 KiB, so the
|
||||
intermediate can be reconstructed from GS local memory, linearized, and
|
||||
replayed without first implementing the two off-screen render targets.
|
||||
"""
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, HERE)
|
||||
sys.path.insert(0, DATA)
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
import gs_sh3_recon as RC
|
||||
import bake
|
||||
|
||||
DRAW_IDS = (196167, 196177)
|
||||
W, H, FBW = 640, 480, 10
|
||||
TW = TH = 256
|
||||
NEW_TBP = 1024
|
||||
TEX_BYTES = TW * TH * 4
|
||||
N_BEATS = TEX_BYTES // 32
|
||||
STG_WORDS = 2048
|
||||
CAPACITY = 14500
|
||||
PERSP_FRAC = bake.PERSP_FRAC
|
||||
PSCALE = 4
|
||||
|
||||
|
||||
def wmem(name, words, banner):
|
||||
with open(os.path.join(DATA, name), "w") as f:
|
||||
f.write(f"// {banner}\n")
|
||||
for word in words:
|
||||
f.write(f"{word & 0xffffffff:08x}\n")
|
||||
|
||||
|
||||
def vertex_words(v):
|
||||
s_fp = round(v["s"] * TW * (1 << PERSP_FRAC) * PSCALE)
|
||||
t_fp = round(v["t"] * TH * (1 << PERSP_FRAC) * PSCALE)
|
||||
q_fp = round(v["q"] * (1 << PERSP_FRAC) * PSCALE)
|
||||
if not (-(1 << 23) <= s_fp < (1 << 23) and -(1 << 23) <= t_fp < (1 << 23)):
|
||||
raise ValueError(f"S/T overflow: {s_fp},{t_fp}")
|
||||
x = max(0, min(W - 1, round(v["x"])))
|
||||
y = max(0, min(H - 1, round(v["y"])))
|
||||
rgba = v["rgba"]
|
||||
# Preserve the authentic RGBAQ alpha. bake.rgbaq_with_q() is a legacy
|
||||
# opaque helper (A=0xff), but these MODULATE+TCC passes depend on the
|
||||
# captured 0x50 vertex alpha: As = At*Av/128.
|
||||
return [((q_fp & 0xffffffff) << 32) | (rgba & 0xffffffff),
|
||||
bake.st_data(s_fp & 0xffffff, t_fp & 0xffffff),
|
||||
bake.xyz2_dataz(x, y, v["z"])]
|
||||
|
||||
|
||||
def sprite_bands(draw):
|
||||
a, b = draw["verts"]
|
||||
if not (a.get("kick", True) and b.get("kick", True)):
|
||||
return []
|
||||
y0 = max(0.0, a["y"])
|
||||
y1 = min(float(H), b["y"])
|
||||
if y1 <= y0:
|
||||
return []
|
||||
out = []
|
||||
for lo in range(int(y0), int(y1), 16):
|
||||
hi = min(int(y1), lo + 16)
|
||||
fa = (lo - a["y"]) / (b["y"] - a["y"])
|
||||
fb = (hi - a["y"]) / (b["y"] - a["y"])
|
||||
top = dict(a, y=float(lo), t=a["t"] + fa * (b["t"] - a["t"]),
|
||||
q=a["q"] + fa * (b["q"] - a["q"]))
|
||||
bot = dict(b, y=float(hi), t=a["t"] + fb * (b["t"] - a["t"]),
|
||||
q=a["q"] + fb * (b["q"] - a["q"]))
|
||||
tl = dict(top, x=a["x"], s=a["s"])
|
||||
tr = dict(top, x=b["x"], s=b["s"])
|
||||
bl = dict(bot, x=a["x"], s=a["s"])
|
||||
br = dict(bot, x=b["x"], s=b["s"])
|
||||
tris = ((tl, tr, bl), (tr, br, bl))
|
||||
coverage = max(0, round(b["x"]) - round(a["x"])) * (hi - lo)
|
||||
out.append((tris, coverage))
|
||||
return out
|
||||
|
||||
|
||||
def main(argv):
|
||||
tag = argv[argv.index("--tag") + 1] if "--tag" in argv else "zsrt139f11d"
|
||||
dump = next((x for x in argv[1:] if x.endswith(".gs.zst")), None)
|
||||
if dump is None:
|
||||
dump = next(iter(glob.glob(os.path.join(ROOT, "captures", "gs", "silenthill3", "*224139*.gs.zst"))), None)
|
||||
if dump is None:
|
||||
raise SystemExit("no 224139 dump found")
|
||||
|
||||
got, _ = MD.load_draws(dump, DRAW_IDS)
|
||||
if sorted(got) != list(DRAW_IDS):
|
||||
raise SystemExit(f"missing draws: {sorted(set(DRAW_IDS) - set(got))}")
|
||||
ref = got[DRAW_IDS[0]]["state"]
|
||||
for idx in DRAW_IDS:
|
||||
st = got[idx]["state"]
|
||||
t0, pr = st["tex0"], st["prim"]
|
||||
if (t0["tbp"], t0["tbw"], t0["psm"], t0["tw"], t0["th"], t0["tfx"]) != (11264, 4, 0, 256, 256, 0):
|
||||
raise SystemExit(f"idx{idx}: unexpected TEX0 {t0}")
|
||||
if (pr["type"], pr["tme"], pr["fst"], pr["abe"]) != (6, 1, 0, 1):
|
||||
raise SystemExit(f"idx{idx}: unexpected PRIM {pr}")
|
||||
for key in ("frame", "test", "zbuf", "alpha", "clamp"):
|
||||
if st[key] != ref[key]:
|
||||
raise SystemExit(f"idx{idx}: {key} differs")
|
||||
|
||||
mem, *_ = RC.build_localmem_to(dump, DRAW_IDS[0])
|
||||
tex = [mem.read_ct32_word(11264, 4, x, y) for y in range(TH) for x in range(TW)]
|
||||
crc = sum(tex) & 0xffffffff
|
||||
wmem(f"sh3_{tag}0_tex_lpddr.mem", tex,
|
||||
f"Ch414 authentic 256x256 PSMCT32 intermediate, linearized; crc=0x{crc:08x}")
|
||||
|
||||
pieces = []
|
||||
for idx in DRAW_IDS:
|
||||
pieces.extend(sprite_bands(got[idx]))
|
||||
epochs, cur, cov = [], [], 0
|
||||
for tris, npx in pieces:
|
||||
if cur and cov + npx > CAPACITY:
|
||||
epochs.append((cur, cov)); cur, cov = [], 0
|
||||
cur.extend(tris); cov += npx
|
||||
if cur:
|
||||
epochs.append((cur, cov))
|
||||
|
||||
tex0 = bake.tex0_pack(NEW_TBP, 4, psm=0, tw=8, th=8, tfx=0)
|
||||
prim = 3 | (1 << 4) | (1 << 6) # independent TRIANGLE records, TME, ABE
|
||||
# Preserve authentic RGB-only FBMSK while changing only FBP/FBW.
|
||||
frame = bake.frame_1_psmct32(FBW) | (ref["frame"] & 0xffffffff00000000)
|
||||
for k, (tris, npx) in enumerate(epochs):
|
||||
stg = [len(tris) | (1 << 32) | (1 << 34), frame, ref["alpha"],
|
||||
ref["test"], ref["zbuf"], tex0, ref["clamp"], prim]
|
||||
for tri in tris:
|
||||
for v in tri:
|
||||
stg.extend(vertex_words(v))
|
||||
if len(stg) > STG_WORDS:
|
||||
raise SystemExit(f"epoch{k}: staging overflow {len(stg)}")
|
||||
bake.write_feeder_stg_mem(f"feeder_sh3_{tag}{k}.mem", stg,
|
||||
f"Ch414 authentic CT32 darken epoch{k}, {len(tris)} tris, coverage {npx}", total=STG_WORDS)
|
||||
|
||||
with open(os.path.join(DATA, f"sh3_{tag}_epochs.txt"), "w") as f:
|
||||
f.write("# Ch414 authentic PSMCT32 intermediate darkening sprites\n")
|
||||
f.write("# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32\n")
|
||||
f.write(f"META n_epochs {len(epochs)} fbpxw {W} fbh {H} fbwords {W*H} lpddr_tex 0x200000 tex_words {len(tex)} n_beats {N_BEATS}\n")
|
||||
for k, (tris, _npx) in enumerate(epochs):
|
||||
f.write(f"{k} {DRAW_IDS[0]} 11264 0 sh3_{tag}0_tex_lpddr.mem 0x200000 {TEX_BYTES} 0x{crc:08x} "
|
||||
f"feeder_sh3_{tag}{k}.mem {8 + 9*len(tris)} {len(tris)} {0 if k == 0 else 1} - 0x00000000\n")
|
||||
print(f"[Ch414] PASS: {len(DRAW_IDS)} CT32 sprites -> {len(pieces)} bands -> {len(epochs)} epochs; "
|
||||
f"max coverage={max(x[1] for x in epochs)} crc=0x{crc:08x}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch354 Brick 1: MULTI-DRAW SH3 fixture.
|
||||
|
||||
Composite N authentic SH3 draws that SHARE one texture+CLUT into ONE LPDDR framebuffer, proving the FB path is
|
||||
scene-capable (a real draw LIST, not one selected draw). ONE new variable vs Ch353: multiple authentic draws
|
||||
accumulating into one FB. Explicitly NOT in Brick 1 (Codex): multi-texture residency, full 640x480, large Z-buffer.
|
||||
|
||||
Codex guardrails enforced (fail-CLOSED, before any integration sim):
|
||||
#1 report each draw's clipped tri count + total staging words; FAIL if > FEEDER_STG_WORDS.
|
||||
#2 prove ALL feeder-visible state matches: TEX0(all fields), FRAME, PRIM/FST/TME/ABE, TEST/Z, CLAMP, ALPHA, TEXA,
|
||||
texture dims/format.
|
||||
#3 texture/CLUT check is CONTENT-based (epoch-aware): a same-byte re-upload is fine; a changed payload FAILS.
|
||||
#4 draws justified MECHANICALLY (same frame, contiguous run of the same texture key), not visually.
|
||||
#5 (emit stage) the oracle is generated INDEPENDENTLY from reconstructed GS local memory, NOT from feeder records.
|
||||
#6 (emit stage) preserve dump order; explicitly SCORE the overlap regions (where accumulation is actually proven).
|
||||
|
||||
Usage: gs_make_sh3_multidraw_fixture.py [dump.gs.zst] --draw-list 89548,89761,89974 [--emit]
|
||||
LOCAL/gitignored outputs (dump-derived). This tool NEVER touches the Ch353 single-draw fixture (byte-stable).
|
||||
"""
|
||||
import sys, os, glob
|
||||
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
|
||||
import gs_sh3_draw_census as C
|
||||
import gs_sh3_recon as RC
|
||||
import gs_texture_residency as R
|
||||
sys.path.insert(0, DATA); import bake
|
||||
|
||||
# address map — identical to the Ch353 single-draw fixture (128 KiB VRAM / CBP=480 / texture in LPDDR, Codex option A)
|
||||
CBP = 0x1E000//256 # 480 CLUT base (grid bytes)
|
||||
NEW_TBP = 0x40000//256 # 1024 texture VRAM base (cache-intercepted)
|
||||
TEX_BYTES = 512*512 # 262144 PSMT8
|
||||
STG_WORDS = 2048 # FEEDER_STG_WORDS for the multi-draw PROFILE (Codex-approved; crop/Ch353 keep 768).
|
||||
# Fail-CLOSED gate: a combined list beyond this must raise the param again (board-BRAM),
|
||||
# never silently truncate. 16-bit tri count + 12-bit staging addr cover 2047 words.
|
||||
FBW_MAX = 10 # 640 px hard ceiling for Brick 1 (no full-frame work); union wider than this = fail
|
||||
|
||||
def f32(bits):
|
||||
import struct; return struct.unpack("<f", struct.pack("<I", bits & 0xFFFFFFFF))[0]
|
||||
|
||||
def edge(ax,ay,bx,by,px,py): return (px-ax)*(by-ay)-(py-ay)*(bx-ax)
|
||||
|
||||
def load_draws(dump, want_idxs, collected=None, allow_untextured=False):
|
||||
"""Walk the GS event stream once; for each selected draw (by first primitive idx) capture the FULL feeder-visible
|
||||
state latched at its first kick + its vertices. Mirrors gs_sh3_draw_census.census but captures ALL render state."""
|
||||
d, h, events, uploads, runs, vram = collected if collected is not None else R.collect(dump, 0)
|
||||
st = dict(PRIM=None, TEX0_1=None, TEX0_2=None, FRAME_1=None, FRAME_2=None, TEST_1=None, TEST_2=None,
|
||||
ZBUF_1=None, ZBUF_2=None, CLAMP_1=None, CLAMP_2=None, SCISSOR_1=None, SCISSOR_2=None,
|
||||
ALPHA_1=None, ALPHA_2=None, TEXA=None, FOGCOL=0)
|
||||
prim = dict(type=7, tme=0, fge=0, fst=0, abe=0, ctxt=0)
|
||||
ofx={1:0.0,2:0.0}; ofy={1:0.0,2:0.0}; cur_st=(0.0,0.0,1.0); cur_uv=(0.0,0.0); cur_rgba=0
|
||||
cur=None; cur_frame=0; want=set(want_idxs); got={}
|
||||
|
||||
def ctx(): return 1 if prim["ctxt"]==0 else 2
|
||||
def texkey():
|
||||
t0=st["TEX0_1" if prim["ctxt"]==0 else "TEX0_2"]
|
||||
if t0 is None: return None
|
||||
return (t0["tbp"], t0["psm"], t0["tbw"], prim["type"], prim["tme"], prim["abe"])
|
||||
def snapshot_state():
|
||||
c=ctx()
|
||||
return dict(tex0=dict(st["TEX0_1" if c==1 else "TEX0_2"]), prim=dict(prim),
|
||||
frame=st["FRAME_%d"%c], test=st["TEST_%d"%c], zbuf=st["ZBUF_%d"%c],
|
||||
clamp=st["CLAMP_%d"%c], scissor=st["SCISSOR_%d"%c],
|
||||
alpha=st["ALPHA_%d"%c], texa=st["TEXA"], fogcol=st["FOGCOL"], ctxt=c)
|
||||
def close():
|
||||
nonlocal cur
|
||||
if cur is not None and cur["nprim"]>=1 and cur["first_idx"] in want:
|
||||
got[cur["first_idx"]]=cur
|
||||
cur=None
|
||||
def open_draw(idx):
|
||||
nonlocal cur
|
||||
cur=dict(first_idx=idx, frame=cur_frame, key=texkey(), state=snapshot_state(), nprim=0, verts=[])
|
||||
|
||||
for e in events:
|
||||
if e.kind=="FRAME_BOUNDARY": cur_frame=e.frame+1; continue
|
||||
if e.kind!="GSREG": continue
|
||||
cur_frame=e.frame; r,v=e.reg,e.value
|
||||
if r=="PRIM": close(); prim=dict(type=v&7, tme=(v>>4)&1, fge=(v>>5)&1,
|
||||
fst=(v>>8)&1, abe=(v>>6)&1, ctxt=(v>>9)&1)
|
||||
elif r in ("TEX0_1","TEX0_2"): st[r]=R.dec_tex0(v)
|
||||
elif r=="XYOFFSET_1": ofx[1]=(v&0xFFFF)/16.0; ofy[1]=((v>>32)&0xFFFF)/16.0
|
||||
elif r=="XYOFFSET_2": ofx[2]=(v&0xFFFF)/16.0; ofy[2]=((v>>32)&0xFFFF)/16.0
|
||||
elif r in st: st[r]=v # FRAME/TEST/ZBUF/CLAMP/ALPHA/TEXA raw qwords (compared verbatim)
|
||||
elif r=="RGBAQ": cur_rgba=v&0xFFFFFFFF
|
||||
elif r=="ST":
|
||||
s=f32(v&0xFFFFFFFF); t=f32((v>>32)&0xFFFFFFFF); q=f32(e.info.get("q_stq",0x3F800000)); cur_st=(s,t,q)
|
||||
elif r=="UV": cur_uv=((v&0x3FFF)/16.0, ((v>>16)&0x3FFF)/16.0)
|
||||
elif r in ("XYZF2","XYZ2","XYZF3","XYZ3"):
|
||||
xf=v&0xFFFF; yf=(v>>16)&0xFFFF; c=ctx(); x=xf/16.0-ofx[c]; y=yf/16.0-ofy[c]
|
||||
is_xyzf = r in ("XYZF2", "XYZF3")
|
||||
z=(v>>32)&0xFFFFFF if is_xyzf else (v>>32)&0xFFFFFFFF
|
||||
fog=(v>>56)&0xFF if is_xyzf else 0xFF
|
||||
if (not prim["tme"] and not allow_untextured) or texkey() is None: continue
|
||||
if cur is None or cur["key"]!=texkey(): close(); open_draw(e.idx)
|
||||
t0=cur["state"]["tex0"]
|
||||
# S/T/Q are architecturally ignored when TME=0. Canonicalize
|
||||
# them so untextured fixture generation never depends on stale
|
||||
# ST/UV registers or trips the perspective fixed-point gates.
|
||||
stq=((0.0,0.0,1.0) if not prim["tme"] else
|
||||
((cur_uv[0]/t0["tw"],cur_uv[1]/t0["th"],1.0) if prim["fst"] else cur_st))
|
||||
# XYZ2/XYZF2 normally perform a drawing kick, but PACKED XYZ2/F2
|
||||
# carries ADC in bit 111. gs_parse exposes that as note="adc"
|
||||
# while retaining the XYZ2/F2 register name. ADC and XYZ3/F3
|
||||
# both feed the primitive assembler without drawing the triangle
|
||||
# completed by this vertex. Retain the vertex for strip
|
||||
# continuity, but mark it as non-kicking.
|
||||
cur["verts"].append(dict(x=x,y=y,z=z,fog=fog,s=stq[0],t=stq[1],q=stq[2],rgba=cur_rgba,
|
||||
kick=(r in ("XYZF2","XYZ2") and e.info.get("note") != "adc")))
|
||||
cur["nprim"]+=1
|
||||
close()
|
||||
return got, vram
|
||||
|
||||
def clip_rect(tri, W, H):
|
||||
def lerp(p1,p2,a): return {k:(p1[k]+a*(p2[k]-p1[k])) for k in ("x","y","s","t","q")}
|
||||
def clip_edge(poly, inside, isect):
|
||||
out=[]; n=len(poly)
|
||||
for i in range(n):
|
||||
a=poly[i]; b=poly[(i+1)%n]; ina=inside(a); inb=inside(b)
|
||||
if ina: out.append(a)
|
||||
if ina!=inb: out.append(isect(a,b))
|
||||
return out
|
||||
poly=[dict(x=v["x"],y=v["y"],s=v["s"],t=v["t"],q=v["q"]) for v in tri]
|
||||
poly=clip_edge(poly, lambda p:p["x"]>=0.0, lambda a,b:lerp(a,b,(0.0-a["x"])/(b["x"]-a["x"])))
|
||||
if not poly: return []
|
||||
poly=clip_edge(poly, lambda p:p["x"]<=W, lambda a,b:lerp(a,b,(W-a["x"])/(b["x"]-a["x"])))
|
||||
if not poly: return []
|
||||
poly=clip_edge(poly, lambda p:p["y"]>=0.0, lambda a,b:lerp(a,b,(0.0-a["y"])/(b["y"]-a["y"])))
|
||||
if not poly: return []
|
||||
poly=clip_edge(poly, lambda p:p["y"]<=H, lambda a,b:lerp(a,b,(H-a["y"])/(b["y"]-a["y"])))
|
||||
if len(poly)<3: return []
|
||||
return [(poly[0],poly[k],poly[k+1]) for k in range(1,len(poly)-1)]
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
||||
if "--draw-list" not in a: sys.exit("need --draw-list idx,idx,...")
|
||||
idxs=[int(x) for x in a[a.index("--draw-list")+1].split(",")]
|
||||
tag = a[a.index("--tag")+1] if "--tag" in a else "multi" # output-file suffix (diagnostic isolation runs)
|
||||
only = int(a[a.index("--only")+1]) if "--only" in a else None # emit ONLY this draw's tris (keep full-set union)
|
||||
if dump is None:
|
||||
c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
||||
dump=c[0]
|
||||
print(f"[Ch354] dump={os.path.basename(dump)} draw-list={idxs}")
|
||||
|
||||
got, vram = load_draws(dump, idxs)
|
||||
missing=[i for i in idxs if i not in got]
|
||||
if missing: sys.exit(f"[Ch354] FAIL: draw idx {missing} not found as textured draws")
|
||||
draws=[got[i] for i in idxs] # dump order = the order the user passed (verify below)
|
||||
|
||||
# ---- Guardrail #4: MECHANICAL justification — same frame, contiguous run of ONE texture key ----
|
||||
frames={d["frame"] for d in draws}
|
||||
keys={d["key"] for d in draws}
|
||||
order_ok = idxs==sorted(idxs)
|
||||
print(f"[Ch354] frames={sorted(frames)} texkeys={keys} dump-order(ascending idx)={order_ok}")
|
||||
if len(frames)!=1: sys.exit(f"[Ch354] FAIL(#4): draws span multiple frames {sorted(frames)} — not one scene/run")
|
||||
if len(keys)!=1: sys.exit(f"[Ch354] FAIL(#4): draws have different texture keys {keys} — not one shared texture")
|
||||
if not order_ok: sys.exit(f"[Ch354] FAIL(#4): --draw-list not in ascending dump order {idxs}")
|
||||
|
||||
# ---- Guardrail #2: ALL feeder-visible state must match across the selected draws ----
|
||||
ref=draws[0]["state"]; t0r=ref["tex0"]
|
||||
for fld in ("tbp","tbw","psm","tw","th","tcc","tfx","cbp","cpsm","cld"):
|
||||
vals={d["state"]["tex0"][fld] for d in draws}
|
||||
if len(vals)!=1: sys.exit(f"[Ch354] FAIL(#2): TEX0.{fld} differs across draws: {vals}")
|
||||
for fld in ("type","fst","tme","abe"):
|
||||
vals={d["state"]["prim"][fld] for d in draws}
|
||||
if len(vals)!=1: sys.exit(f"[Ch354] FAIL(#2): PRIM.{fld} differs: {vals}")
|
||||
for reg in ("frame","test","zbuf","clamp","alpha","texa"):
|
||||
vals={d["state"][reg] for d in draws}
|
||||
if len(vals)!=1: sys.exit(f"[Ch354] FAIL(#2): {reg.upper()} state differs across draws: {vals}")
|
||||
assert t0r["tw"]==512 and t0r["th"]==512 and t0r["psm"]==0x13, f"unexpected TEX0 {t0r}"
|
||||
print(f"[Ch354] #2 OK: all feeder-visible state identical — TEX0 tbp={t0r['tbp']} cbp={t0r['cbp']} psm=0x{t0r['psm']:02x} "
|
||||
f"{t0r['tw']}x{t0r['th']}; PRIM type={ref['prim']['type']} fst={ref['prim']['fst']} tme={ref['prim']['tme']} "
|
||||
f"abe={ref['prim']['abe']}; TEST/ZBUF/CLAMP/ALPHA/TEXA match")
|
||||
|
||||
# ---- Guardrail #3: CONTENT-based (epoch-aware) texture + CLUT check. Reconstruct local memory to EACH draw and
|
||||
# compare the texture bytes @tbp + CLUT bytes @cbp. Same-byte re-upload -> identical -> OK; changed -> FAIL. ----
|
||||
tbp=t0r["tbp"]; cbp=t0r["cbp"]; texref=None; clutref=None
|
||||
for d in draws:
|
||||
mem, *_ = RC.build_localmem_to(dump, d["first_idx"])
|
||||
if mem is None: sys.exit(f"[Ch354] FAIL(#3): VRAM snapshot absent at idx{d['first_idx']}")
|
||||
tb=bytes(mem.m[tbp*256 : tbp*256+TEX_BYTES]); cb=bytes(mem.m[cbp*256 : cbp*256+1024])
|
||||
if texref is None: texref=tb; clutref=cb; anchor=d["first_idx"]
|
||||
else:
|
||||
if tb!=texref: sys.exit(f"[Ch354] FAIL(#3): texture @tbp={tbp} CHANGED between idx{anchor} and idx{d['first_idx']} "
|
||||
f"(payload differs — needs multi-texture residency, deferred past Brick 1)")
|
||||
if cb!=clutref: sys.exit(f"[Ch354] FAIL(#3): CLUT @cbp={cbp} CHANGED between idx{anchor} and idx{d['first_idx']}")
|
||||
print(f"[Ch354] #3 OK: texture @tbp={tbp} ({TEX_BYTES} B) + CLUT @cbp={cbp} (1024 B) byte-identical across all "
|
||||
f"{len(draws)} draws (content-compared; same-byte re-uploads are fine)")
|
||||
|
||||
# ---- Guardrail #3: deterministic UNION bbox -> FB origin/size/stride ----
|
||||
OX=int(min(min(v["x"] for v in d["verts"]) for d in draws))
|
||||
OY=int(min(min(v["y"] for v in d["verts"]) for d in draws))
|
||||
UXMAX=max(max(v["x"] for v in d["verts"]) for d in draws)
|
||||
UYMAX=max(max(v["y"] for v in d["verts"]) for d in draws)
|
||||
W=int(UXMAX)-OX+1; H=int(UYMAX)-OY+1
|
||||
FBW=(W+63)//64; FBPXW=FBW*64
|
||||
if FBW>FBW_MAX: sys.exit(f"[Ch354] FAIL: union width {W}px (FBW={FBW}) exceeds Brick-1 ceiling {FBW_MAX*64}px")
|
||||
STRIDE=FBPXW*4
|
||||
print(f"[Ch354] #3 UNION bbox: origin=({OX},{OY}) size={W}x{H}px -> FB {FBPXW}x{H} (FBW={FBW}) stride={STRIDE}B "
|
||||
f"= {FBPXW*H*4} B ({FBPXW*H*4//1024} KiB)")
|
||||
|
||||
# ---- Guardrail #1: per-draw CLIPPED tri count + total staging words, fail-CLOSED on FEEDER_STG_WORDS ----
|
||||
HEADER_WORDS=7 # ntris|flag, FRAME, ALPHA, TEST, ZBUF, TEX0, PRIM
|
||||
total_tris=0; per=[]
|
||||
for d in draws:
|
||||
fv=[dict(x=v["x"]-OX, y=v["y"]-OY, s=v["s"], t=v["t"], q=v["q"]) for v in d["verts"]]
|
||||
raw=[(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))] # TRI_STRIP -> triangles
|
||||
clipped=[]
|
||||
for tri in raw: clipped += clip_rect(tri, FBPXW, H)
|
||||
per.append((d["first_idx"], len(fv), len(raw), len(clipped))); total_tris+=len(clipped)
|
||||
words=HEADER_WORDS + total_tris*9
|
||||
print(f"[Ch354] #1 staging capacity (FEEDER_STG_WORDS={STG_WORDS}):")
|
||||
for (idx,nv,nraw,ncl) in per:
|
||||
print(f" idx{idx}: {nv} verts -> {nraw} strip-tris -> {ncl} clipped-tris")
|
||||
print(f" TOTAL {total_tris} clipped tris -> {words} staging words "
|
||||
f"({'OK' if words<=STG_WORDS else 'OVER by %d'%(words-STG_WORDS)})")
|
||||
if words>STG_WORDS:
|
||||
sys.exit(f"[Ch354] FAIL-CLOSED(#1): combined list {words} words > FEEDER_STG_WORDS={STG_WORDS}. "
|
||||
f"Raise the PROFILE FEEDER_STG_WORDS (board-BRAM cost) to >= {words} (e.g. {1<<(words-1).bit_length()}) "
|
||||
f"OR reduce the draw set. Do NOT discover this in integration sim.")
|
||||
|
||||
print(f"[Ch354] all fail-closed gates PASS.")
|
||||
if "--emit" not in a:
|
||||
print("[Ch354] (validation only; pass --emit to generate the fixture)"); return 0
|
||||
|
||||
# ================= --emit: combined feeder list + INDEPENDENT oracle (guardrails #5, #6) =================
|
||||
STG_WORDS_MULTI=2048 # Codex-approved profile-only staging RAM (crop/Ch353 keep 768)
|
||||
PERSP_FRAC=bake.PERSP_FRAC; PSCALE=4096; TW=512; TH=512; TW_LOG=TH_LOG=9; TBW_TEX=8
|
||||
NEW_TBP=0x40000//256; TEX_VRAM_BASE=NEW_TBP*256; LPDDR_TEX_BASE=0x00200000; VRAM_BYTES=0x20000
|
||||
S24_MAX=(1<<23)-1
|
||||
def tex0_real(tbp2,cbp2):
|
||||
v=bake.tex0_pack(tbp2,TBW_TEX,psm=0x13,tw=TW_LOG,th=TH_LOG,tfx=1)
|
||||
v|=(cbp2&0x3FFF)<<37; v|=(0&0xF)<<51; v|=(0&0x1)<<55; v|=(0&0x1F)<<56; v|=(1&0x7)<<61
|
||||
return v
|
||||
def vert_words(v):
|
||||
s_fp=round(v["s"]*TW*(1<<PERSP_FRAC)*PSCALE); t_fp=round(v["t"]*TH*(1<<PERSP_FRAC)*PSCALE)
|
||||
q_fp=round(v["q"]*(1<<PERSP_FRAC)*PSCALE)
|
||||
if abs(s_fp)>S24_MAX or abs(t_fp)>S24_MAX: sys.exit(f"[Ch354] ST overflow {s_fp},{t_fp} (lower PSCALE)")
|
||||
if abs(q_fp)>0x7FFFFFFF: sys.exit(f"[Ch354] Q overflow {q_fp}")
|
||||
sx=max(0,min(FBPXW-1,int(round(v["x"])))); sy=max(0,min(H-1,int(round(v["y"]))))
|
||||
return [bake.rgbaq_with_q(0,0,0,q_fp&0xFFFFFFFF), bake.st_data(s_fp&0xFFFFFF,t_fp&0xFFFFFF),
|
||||
bake.xyz2_dataz(sx,sy,0x0000_5000)]
|
||||
def draw_tris(d):
|
||||
fv=[dict(x=v["x"]-OX,y=v["y"]-OY,s=v["s"],t=v["t"],q=v["q"]) for v in d["verts"]]
|
||||
raw=[(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))]
|
||||
out=[]
|
||||
for tri in raw: out += clip_rect(tri, FBPXW, H)
|
||||
return out
|
||||
|
||||
# shared texture/CLUT (tbp=9216) reconstructed ONCE — feeds BOTH the LPDDR upload AND the independent oracle
|
||||
mem0, *_ = RC.build_localmem_to(dump, draws[0]["first_idx"])
|
||||
idx = mem0.read_psmt8(tbp, t0r["tbw"], TW, TH)
|
||||
pal = RC.read_clut32(mem0, cbp, order="grid")
|
||||
clut_bytes = bytes(mem0.m[cbp*256 : cbp*256+1024])
|
||||
idx_words = [idx[i*4]|(idx[i*4+1]<<8)|(idx[i*4+2]<<16)|(idx[i*4+3]<<24) for i in range(TW*TH//4)]
|
||||
|
||||
# render set: the FULL list, or a SINGLE draw (--only) for the isolation/composition diagnostic (same union frame)
|
||||
render_set = [got[only]] if only is not None else draws
|
||||
if only is not None and only not in got: sys.exit(f"[Ch354] --only {only} not in --draw-list")
|
||||
print(f"[Ch354] tag='{tag}' render_set={[d['first_idx'] for d in render_set]} (union from full {idxs})")
|
||||
# combined feeder list: ONE shared-state header + the render-set's clipped tris in DUMP ORDER (#6)
|
||||
all_tris=[]
|
||||
for d in render_set: all_tris += draw_tris(d)
|
||||
ntris=len(all_tris)
|
||||
stg=[ntris|(1<<32), bake.frame_1_psmct32(FBW), bake.alpha_pack(0,1,0,1), 0, bake.zbuf1_pack(0,zmsk=1),
|
||||
tex0_real(NEW_TBP,CBP), 3|(1<<4)]
|
||||
for (v0,v1,v2) in all_tris:
|
||||
for v in (v0,v1,v2): stg += vert_words(v)
|
||||
if len(stg)>STG_WORDS_MULTI: sys.exit(f"[Ch354] staging {len(stg)} > {STG_WORDS_MULTI}")
|
||||
max_addr=len(stg)-1
|
||||
print(f"[Ch354] combined feeder list: {ntris} tris -> {len(stg)} words (max staging addr {max_addr} < {STG_WORDS_MULTI}); records_emitted should == {ntris}")
|
||||
|
||||
# INDEPENDENT oracle (#5): rasterize the RECONSTRUCTED geometry+texture in DUMP ORDER (paint-order, DECAL/no-Z,
|
||||
# matching the RTL flush order), per FB pixel — NOT parsed from feeder records. Track DISTINCT draw count per pixel
|
||||
# -> OVERLAP regions (#6). refmap word: [31]covered [30]interior [28]overlap [17:9]tu [8:0]tv.
|
||||
refmap=[0]*(FBPXW*H); refpix=[(0,0,0)]*(FBPXW*H); ndraw=[0]*(FBPXW*H); lastdraw=[-1]*(FBPXW*H)
|
||||
for di,d in enumerate(render_set):
|
||||
for (v0,v1,v2) in draw_tris(d):
|
||||
x0,y0=v0["x"],v0["y"]; x1,y1=v1["x"],v1["y"]; x2,y2=v2["x"],v2["y"]
|
||||
ar=edge(x0,y0,x1,y1,x2,y2)
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=max(0,int(min(x0,x1,x2))); maxx=min(FBPXW-1,int(max(x0,x1,x2))+1)
|
||||
miny=max(0,int(min(y0,y1,y2))); maxy=min(H-1,int(max(y0,y1,y2))+1)
|
||||
for py in range(miny,maxy+1):
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(x1,y1,x2,y2,cx,cy)*inv; w1=edge(x2,y2,x0,y0,cx,cy)*inv; w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
a0=edge(x1,y1,x2,y2,float(px),float(py))*inv; a1=edge(x2,y2,x0,y0,float(px),float(py))*inv; a2=1.0-a0-a1
|
||||
S=a0*v0["s"]+a1*v1["s"]+a2*v2["s"]; T=a0*v0["t"]+a1*v1["t"]+a2*v2["t"]; Q=a0*v0["q"]+a1*v1["q"]+a2*v2["q"]
|
||||
if abs(Q)<1e-12: continue
|
||||
tu=int((S/Q)*TW)%TW; tv=int((T/Q)*TH)%TH
|
||||
if tu<0: tu+=TW
|
||||
if tv<0: tv+=TH
|
||||
o=py*FBPXW+px; mw=min(w0,w1,w2); interior=1 if mw>0.04 else 0
|
||||
if lastdraw[o]!=di: ndraw[o]+=1; lastdraw[o]=di # DISTINCT draws covering this pixel
|
||||
refmap[o]=(1<<31)|(interior<<30)|((tu&0x1FF)<<9)|(tv&0x1FF) # later draw overwrites (paint-order)
|
||||
p=pal[idx[tv*TW+tu]&0xFF]; refpix[o]=(p&0xFF,(p>>8)&0xFF,(p>>16)&0xFF)
|
||||
overlap_px=0
|
||||
for o in range(FBPXW*H):
|
||||
if ndraw[o]>1: refmap[o]|=(1<<28); overlap_px+=1
|
||||
covered=sum(1 for w in refmap if w>>31)
|
||||
print(f"[Ch354] #5/#6 independent oracle: {covered} covered px, {overlap_px} OVERLAP px (>1 distinct draw) [refmap bit28]")
|
||||
if only is None and overlap_px==0:
|
||||
sys.exit("[Ch354] FAIL(#6): NO overlap pixels — the draws don't accumulate; multi-draw not proven")
|
||||
|
||||
# OWNER map (Codex diagnostic): the FULL-set winning draw index per pixel, in DUMP ORDER (last covering draw wins).
|
||||
# Used to compose the isolated single-draw RTL framebuffers and compare to the combined RTL render.
|
||||
owner=[255]*(FBPXW*H) # 255 = uncovered
|
||||
for di,d in enumerate(draws): # ALWAYS the full set (independent of --only)
|
||||
for (v0,v1,v2) in draw_tris(d):
|
||||
x0,y0=v0["x"],v0["y"]; x1,y1=v1["x"],v1["y"]; x2,y2=v2["x"],v2["y"]
|
||||
ar=edge(x0,y0,x1,y1,x2,y2)
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=max(0,int(min(x0,x1,x2))); maxx=min(FBPXW-1,int(max(x0,x1,x2))+1)
|
||||
miny=max(0,int(min(y0,y1,y2))); maxy=min(H-1,int(max(y0,y1,y2))+1)
|
||||
for py in range(miny,maxy+1):
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(x1,y1,x2,y2,cx,cy)*inv; w1=edge(x2,y2,x0,y0,cx,cy)*inv; w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
owner[py*FBPXW+px]=di # later draw overwrites -> final owner (paint order)
|
||||
|
||||
# ---- emit LOCAL fixtures (tag 'multi'; NEVER touch the Ch353 sh3_real_* single-draw fixture) ----
|
||||
def wmem(name, words, banner):
|
||||
with open(os.path.join(DATA,name),"w") as f:
|
||||
f.write(f"// {banner}\n")
|
||||
for x in words: f.write(f"{x & 0xFFFFFFFF:08x}\n")
|
||||
wmem(f"sh3_{tag}_idx.mem", idx_words, "Ch354 LOCAL shared SH3 512x512 de-swizzled indices (4/word). gitignored.")
|
||||
wmem(f"sh3_{tag}_tex_lpddr.mem", idx_words, "Ch354 LOCAL shared SH3 512x512 LINEAR indices -> LPDDR (PSMT8_SWIZZLE=0). gitignored.")
|
||||
wmem(f"sh3_{tag}_clut.mem", [int.from_bytes(clut_bytes[i*4:i*4+4],'little') for i in range(256)],
|
||||
"Ch354 LOCAL shared SH3 CSM1 CLUT (grid bytes @cbp) -> BRAM. gitignored.")
|
||||
wmem(f"sh3_{tag}_pal.mem", [p & 0xFFFFFFFF for p in pal], "Ch354 LOCAL de-gridded palette pal[i] for the TB. gitignored.")
|
||||
wmem(f"sh3_{tag}_refmap.mem", refmap, "Ch354 LOCAL per-FB-pixel covered|interior|overlap|tu|tv oracle (render_set). gitignored.")
|
||||
wmem(f"sh3_{tag}_owner.mem", owner, "Ch354 LOCAL per-FB-pixel FULL-set winning draw index (dump order; 255=uncovered). gitignored.")
|
||||
bake.write_feeder_stg_mem(f"feeder_sh3_{tag}.mem", stg,
|
||||
f"Ch354 LOCAL multi-draw SH3 (render {[d['first_idx'] for d in render_set]} of {idxs}) feeder staging: {ntris}-tri list + TEX0(PSMT8,CSM1,DECAL). gitignored.",
|
||||
total=STG_WORDS_MULTI) # padded to 2048 (Codex)
|
||||
|
||||
# setup bootlet: CSM1 CLUT 256x1 BITBLT -> CBP (identical pattern to Ch352; shared CLUT), DISPLAY1 = FBPXW x H
|
||||
clut_words_b=[int.from_bytes(clut_bytes[i*4:i*4+4],"little") for i in range(256)]
|
||||
RAM_QWORDS=512; pay=[]
|
||||
pay.append(bake.giftag(1,0,0,4,int('E'*4,16)))
|
||||
pay.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(CBP,1,0x00)))
|
||||
pay.append(bake.aplusd(bake.R_TRXPOS, bake.trxpos_pack(0,0)))
|
||||
pay.append(bake.aplusd(bake.R_TRXREG, bake.trxreg_pack(256,1)))
|
||||
pay.append(bake.aplusd(bake.R_TRXDIR, bake.trxdir_pack(0)))
|
||||
pay.append(bake.giftag(256//4,1,2,0,0))
|
||||
for q in range(256//4):
|
||||
word=0
|
||||
for lane in range(4): word|=(clut_words_b[q*4+lane]&0xFFFFFFFF)<<(32*lane)
|
||||
pay.append(word)
|
||||
qwc=len(pay); disp_hi=((H-1)<<12)|(FBPXW-1)
|
||||
with open(os.path.join(DATA,f"payload_sh3_{tag}.mem"),"w") as f:
|
||||
f.write(f"// Ch354 LOCAL multi-draw setup payload (CSM1 CLUT -> CBP={CBP}). gitignored. QWC={qwc}.\n")
|
||||
for _ in range(16): f.write(f"{0:032x}\n")
|
||||
for x in pay: f.write(f"{x&((1<<128)-1):032x}\n")
|
||||
for _ in range(RAM_QWORDS-16-qwc): f.write(f"{0:032x}\n")
|
||||
bake.write_bios_mem(f"bios_sh3_{tag}.mem", bake.build_textured_demo_bootlet_disp(qwc, disp_hi, FBW),
|
||||
f"Ch354 LOCAL multi-draw setup bootlet (QWC={qwc}, DISPLAY1={FBPXW}x{H}). gitignored.")
|
||||
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_params.vh"),"w") as f:
|
||||
f.write("// Ch354 LOCAL generated params for the multi-draw integration TB. gitignored.\n")
|
||||
f.write(f"localparam int FBW = {FBW};\n")
|
||||
f.write(f"localparam int FBPXW = {FBPXW};\n")
|
||||
f.write(f"localparam int FBH = {H};\n")
|
||||
f.write(f"localparam int VRAM_BYTES_P = {VRAM_BYTES};\n")
|
||||
f.write(f"localparam int CLUT_CBP = {CBP};\n")
|
||||
f.write(f"localparam int NEW_TBP = {NEW_TBP};\n")
|
||||
f.write(f"localparam int TEX_VRAM_BASE = {TEX_VRAM_BASE};\n")
|
||||
f.write(f"localparam int TEX_BYTES = {TW*TH};\n")
|
||||
f.write(f"localparam [29:0] LPDDR_TEX_BASE = 30'h{LPDDR_TEX_BASE:07x};\n")
|
||||
f.write(f"localparam int N_BEATS = {TW*TH//32};\n")
|
||||
f.write(f"localparam int STG_WORDS = {STG_WORDS_MULTI};\n")
|
||||
f.write(f"localparam int TW = {TW};\n")
|
||||
f.write(f"localparam int TH = {TH};\n")
|
||||
f.write(f"localparam int NDRAWS = {len(draws)};\n")
|
||||
f.write(f"localparam int NTRIS = {ntris};\n")
|
||||
f.write(f"localparam int UNION_OX = {OX};\n")
|
||||
f.write(f"localparam int UNION_OY = {OY};\n")
|
||||
try:
|
||||
from PIL import Image
|
||||
im=Image.new("RGB",(FBPXW,H)); im.putdata(refpix)
|
||||
im.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon",f"sh3_{tag}_ref.png"))
|
||||
print(f"[Ch354] wrote sh3_{tag}_ref.png")
|
||||
except Exception as ex: print("(PIL skipped:", ex, ")")
|
||||
tex_sum=sum(idx_words)&0xFFFFFFFF; tex_xor=0
|
||||
for w in idx_words: tex_xor^=w
|
||||
print(f"[Ch354] emitted multi-draw fixtures -> {DATA}. FB {FBPXW}x{H} stride {FBPXW*4}B; feeder {len(stg)}w (pad {STG_WORDS_MULTI}); "
|
||||
f"tex sum32=0x{tex_sum:08x} xor32=0x{tex_xor:08x} @LPDDR 0x{LPDDR_TEX_BASE:07x}")
|
||||
return 0
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch355 Brick 1: MULTI-TEXTURE composition.
|
||||
|
||||
Two authentic SH3 draw groups with DIFFERENT TEX0/CLUT accumulate into ONE LPDDR framebuffer via SCENE-LEVEL texture
|
||||
rebind + staged-list retriggering (Codex). Dump order (authentic): A={19562} tbp=11264/cbp=14080 THEN B={89761}
|
||||
tbp=9216/cbp=13952. NOT in Brick 1: per-primitive TEX0, multi-resident cache, 640x480, cross-draw Z.
|
||||
|
||||
Preflight fail-CLOSED gates (this file, run first):
|
||||
- both draws same frame; overlap on screen; DIFFERENT texture keys.
|
||||
- each group's texture + CLUT independently RESIDENT at ITS draw-time epoch (per-draw local-memory reconstruction).
|
||||
- the two textures are DIFFERENT content, and the two CLUTs are DIFFERENT content (real multi-texture, not aliasing).
|
||||
- CLUTs RELOCATED to distinct, non-overlapping BRAM CBPs (CBP_A/CBP_B); reported for the TEX0-selects-CBP check.
|
||||
|
||||
Usage: gs_make_sh3_multitex_fixture.py [dump.gs.zst] [--emit] (draws A/B fixed to the authentic pair)
|
||||
LOCAL/gitignored. Reuses gs_make_sh3_multidraw_fixture.load_draws (same event-walk + full-state capture).
|
||||
"""
|
||||
import sys, os
|
||||
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
|
||||
import gs_sh3_recon as RC
|
||||
import gs_make_sh3_multidraw_fixture as MD # reuse load_draws + edge + clip_rect
|
||||
sys.path.insert(0, DATA); import bake
|
||||
|
||||
DRAW_A = 19562 # dump order: A first
|
||||
DRAW_B = 89761
|
||||
TEX_BYTES = 512*512
|
||||
# relocated CLUT bases (distinct, non-overlapping). CLUT = 256 words = 1 KiB = 4 VRAM blocks (256 B each).
|
||||
CBP_A = 0x1E000//256 # 480 (bytes 0x1E000..0x1E3FF)
|
||||
CBP_B = 0x1E400//256 # 484 (bytes 0x1E400..0x1E7FF) — +1 KiB, no overlap with CBP_A
|
||||
|
||||
def bbox(dr):
|
||||
xs=[v["x"] for v in dr["verts"]]; ys=[v["y"] for v in dr["verts"]]
|
||||
return (min(xs),min(ys),max(xs),max(ys))
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
||||
if dump is None:
|
||||
import glob; c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
||||
dump=c[0]
|
||||
print(f"[Ch355] dump={os.path.basename(dump)} A=idx{DRAW_A} (first) -> B=idx{DRAW_B}")
|
||||
|
||||
got, vram = MD.load_draws(dump, [DRAW_A, DRAW_B])
|
||||
for i in (DRAW_A, DRAW_B):
|
||||
if i not in got: sys.exit(f"[Ch355] FAIL: draw idx{i} not found as a textured draw")
|
||||
drA, drB = got[DRAW_A], got[DRAW_B]
|
||||
tA, tB = drA["state"]["tex0"], drB["state"]["tex0"]
|
||||
|
||||
# ---- gate: authentic dump order (A before B) ----
|
||||
if not (DRAW_A < DRAW_B): sys.exit(f"[Ch355] FAIL: A idx{DRAW_A} not before B idx{DRAW_B} (dump order)")
|
||||
# ---- gate: same frame ----
|
||||
if drA["frame"]!=drB["frame"]: sys.exit(f"[Ch355] FAIL: A frame {drA['frame']} != B frame {drB['frame']}")
|
||||
# ---- gate: DIFFERENT texture keys ----
|
||||
if drA["key"]==drB["key"]: sys.exit(f"[Ch355] FAIL: A and B share the same texture key {drA['key']} — not multi-texture")
|
||||
print(f"[Ch355] A: TEX0 tbp={tA['tbp']} cbp={tA['cbp']} psm=0x{tA['psm']:02x} {tA['tw']}x{tA['th']} key={drA['key']}")
|
||||
print(f"[Ch355] B: TEX0 tbp={tB['tbp']} cbp={tB['cbp']} psm=0x{tB['psm']:02x} {tB['tw']}x{tB['th']} key={drB['key']}")
|
||||
# ---- gate: overlap on screen ----
|
||||
ax0,ay0,ax1,ay1=bbox(drA); bx0,by0,bx1,by1=bbox(drB)
|
||||
ox0=max(ax0,bx0); oy0=max(ay0,by0); ox1=min(ax1,bx1); oy1=min(ay1,by1)
|
||||
if not (ox1>ox0 and oy1>oy0): sys.exit(f"[Ch355] FAIL: A bbox {bbox(drA)} and B bbox {bbox(drB)} do not overlap on screen")
|
||||
print(f"[Ch355] frame f{drA['frame']}; screen A[{ax0:.0f}..{ax1:.0f}]x[{ay0:.0f}..{ay1:.0f}] B[{bx0:.0f}..{bx1:.0f}]x[{by0:.0f}..{by1:.0f}] -> overlap [{ox0:.0f}..{ox1:.0f}]x[{oy0:.0f}..{oy1:.0f}]")
|
||||
|
||||
# ---- gate: each group's texture + CLUT INDEPENDENTLY RESIDENT at its draw-time epoch; the two are DIFFERENT ----
|
||||
memA,*_ = RC.build_localmem_to(dump, DRAW_A)
|
||||
memB,*_ = RC.build_localmem_to(dump, DRAW_B)
|
||||
if memA is None or memB is None: sys.exit("[Ch355] FAIL: VRAM snapshot absent for a draw")
|
||||
texA=bytes(memA.m[tA['tbp']*256:tA['tbp']*256+TEX_BYTES]); clutA=bytes(memA.m[tA['cbp']*256:tA['cbp']*256+1024])
|
||||
texB=bytes(memB.m[tB['tbp']*256:tB['tbp']*256+TEX_BYTES]); clutB=bytes(memB.m[tB['cbp']*256:tB['cbp']*256+1024])
|
||||
def nz(b): return any(x for x in b)
|
||||
if not nz(texA): sys.exit(f"[Ch355] FAIL: texture A @tbp={tA['tbp']} not resident at idx{DRAW_A}")
|
||||
if not nz(clutA): sys.exit(f"[Ch355] FAIL: CLUT A @cbp={tA['cbp']} not resident at idx{DRAW_A}")
|
||||
if not nz(texB): sys.exit(f"[Ch355] FAIL: texture B @tbp={tB['tbp']} not resident at idx{DRAW_B}")
|
||||
if not nz(clutB): sys.exit(f"[Ch355] FAIL: CLUT B @cbp={tB['cbp']} not resident at idx{DRAW_B}")
|
||||
if texA==texB: sys.exit("[Ch355] FAIL: texture A == texture B (aliasing, not multi-texture)")
|
||||
if clutA==clutB: sys.exit("[Ch355] FAIL: CLUT A == CLUT B (aliasing)")
|
||||
crcA=sum(int.from_bytes(texA[i*4:i*4+4],'little') for i in range(TEX_BYTES//4))&0xFFFFFFFF
|
||||
crcB=sum(int.from_bytes(texB[i*4:i*4+4],'little') for i in range(TEX_BYTES//4))&0xFFFFFFFF
|
||||
print(f"[Ch355] texture A resident (sum32=0x{crcA:08x}) + CLUT A resident; texture B resident (sum32=0x{crcB:08x}) + CLUT B resident; A!=B (real multi-texture)")
|
||||
|
||||
# ---- gate: CLUT relocation to distinct, non-overlapping BRAM CBPs ----
|
||||
if abs(CBP_A-CBP_B)<4: sys.exit(f"[Ch355] FAIL: relocated CBP_A={CBP_A} CBP_B={CBP_B} overlap (<4 blocks = 1 KiB)")
|
||||
print(f"[Ch355] CLUT relocation: A -> CBP={CBP_A} (0x{CBP_A*256:x}), B -> CBP={CBP_B} (0x{CBP_B*256:x}); distinct, non-overlapping (each 1 KiB)")
|
||||
|
||||
# ---- gate: relocated CLUT ranges within BRAM (VRAM 128 KiB = block 0..511; each CLUT = 4 blocks) ----
|
||||
VRAM_BLOCKS = (128*1024)//256
|
||||
for nm,cb in (("A",CBP_A),("B",CBP_B)):
|
||||
if cb+4 > VRAM_BLOCKS: sys.exit(f"[Ch355] FAIL: relocated CLUT {nm} @CBP={cb} (+4 blocks) exceeds VRAM {VRAM_BLOCKS} blocks")
|
||||
print(f"[Ch355] both relocated CLUTs within BRAM (VRAM {VRAM_BLOCKS} blocks; A@{CBP_A}+4, B@{CBP_B}+4)")
|
||||
|
||||
print("[Ch355] PREFLIGHT PASS: authentic order, same frame, on-screen overlap, two independently-resident "
|
||||
"textures+CLUTs (different content), relocatable to distinct in-BRAM CBPs.")
|
||||
if "--emit" not in a:
|
||||
print("[Ch355] (preflight only; --emit stage pending)"); return 0
|
||||
|
||||
# ================= --emit: two feeder lists + two textures/CLUTs + independent A->B reference =================
|
||||
NEW_TBP=0x40000//256; TEX_VRAM_BASE=NEW_TBP*256; LPDDR_TEX_BASE=0x00200000; VRAM_BYTES=0x20000
|
||||
PERSP_FRAC=bake.PERSP_FRAC; PSCALE=4096; TW=512; TH=512; TW_LOG=TH_LOG=9; TBW_TEX=8; S24_MAX=(1<<23)-1
|
||||
edge=MD.edge; clip_rect=MD.clip_rect
|
||||
def tex0_word(cbp):
|
||||
v=bake.tex0_pack(NEW_TBP,TBW_TEX,psm=0x13,tw=TW_LOG,th=TH_LOG,tfx=1)
|
||||
v|=(cbp&0x3FFF)<<37; v|=(0&0xF)<<51; v|=(0&0x1)<<55; v|=(0&0x1F)<<56; v|=(1&0x7)<<61
|
||||
return v
|
||||
# union bbox over BOTH groups (Codex: ~288x381 -> FBW=5/320x381)
|
||||
allx=[v["x"] for d in (drA,drB) for v in d["verts"]]; ally=[v["y"] for d in (drA,drB) for v in d["verts"]]
|
||||
OX=int(min(allx)); OY=int(min(ally)); W=int(max(allx))-OX+1; H=int(max(ally))-OY+1
|
||||
FBW=(W+63)//64; FBPXW=FBW*64; STRIDE=FBPXW*4
|
||||
print(f"[Ch355] UNION origin=({OX},{OY}) content={W}x{H} -> stored FB {FBPXW}x{H} (FBW={FBW}) stride={STRIDE}B "
|
||||
f"size={FBPXW*H*4} (0x{FBPXW*H*4:x}) scanout={STRIDE//32*H} beats/frame; HDMI shows all {FBPXW} cols "
|
||||
f"({FBPXW-W} right cols precleared-black beyond the {W}px content)")
|
||||
|
||||
def vert_words(v):
|
||||
s_fp=round(v["s"]*TW*(1<<PERSP_FRAC)*PSCALE); t_fp=round(v["t"]*TH*(1<<PERSP_FRAC)*PSCALE); q_fp=round(v["q"]*(1<<PERSP_FRAC)*PSCALE)
|
||||
if abs(s_fp)>S24_MAX or abs(t_fp)>S24_MAX: sys.exit(f"[Ch355] ST overflow {s_fp},{t_fp}")
|
||||
if abs(q_fp)>0x7FFFFFFF: sys.exit(f"[Ch355] Q overflow {q_fp}")
|
||||
sx=max(0,min(FBPXW-1,int(round(v["x"])))); sy=max(0,min(H-1,int(round(v["y"]))))
|
||||
return [bake.rgbaq_with_q(0,0,0,q_fp&0xFFFFFFFF), bake.st_data(s_fp&0xFFFFFF,t_fp&0xFFFFFF), bake.xyz2_dataz(sx,sy,0x0000_5000)]
|
||||
def group_tris(dr):
|
||||
fv=[dict(x=v["x"]-OX,y=v["y"]-OY,s=v["s"],t=v["t"],q=v["q"]) for v in dr["verts"]]
|
||||
raw=[(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))]
|
||||
out=[]
|
||||
for tri in raw: out += clip_rect(tri, FBPXW, H)
|
||||
return out
|
||||
# Ch355 (Codex): the RTL perspective reciprocal is UNSIGNED — negative Q is unsupported. Canonicalize each triangle
|
||||
# to positive Q BEFORE packing: all-Q-positive unchanged; all-Q-negative -> negate every vertex's S,T,Q (EXACT:
|
||||
# (-s)/(-q)=s/q and the negation of a linear attribute interpolates identically); mixed-sign/zero Q -> FAIL CLOSED.
|
||||
def canon_tri(v0,v1,v2):
|
||||
qs=[v0["q"],v1["q"],v2["q"]]
|
||||
if all(q>0 for q in qs): return (v0,v1,v2)
|
||||
if all(q<0 for q in qs):
|
||||
neg=lambda v: dict(x=v["x"],y=v["y"],s=-v["s"],t=-v["t"],q=-v["q"])
|
||||
return (neg(v0),neg(v1),neg(v2))
|
||||
sys.exit(f"[Ch355] FAIL-CLOSED: mixed-sign or zero Q in a triangle (qs={qs}) — signed-Q not supported (later platform extension)")
|
||||
def feeder_list(dr, cbp):
|
||||
tris=group_tris(dr); stg=[len(tris)|(1<<32), bake.frame_1_psmct32(FBW), bake.alpha_pack(0,1,0,1), 0,
|
||||
bake.zbuf1_pack(0,zmsk=1), tex0_word(cbp), 3|(1<<4)]
|
||||
for tri in tris:
|
||||
cv0,cv1,cv2=canon_tri(*tri)
|
||||
for v in (cv0,cv1,cv2): stg += vert_words(v)
|
||||
return stg, len(tris)
|
||||
# SYNTHETIC GATE (Codex): a positive-Q triangle and its ALL-NEGATED twin must canonicalize to BIT-IDENTICAL staging.
|
||||
def _pack(tri):
|
||||
c=canon_tri(*tri); out=[]
|
||||
for v in c: out+=vert_words(v)
|
||||
return out
|
||||
_tp=[dict(x=10,y=20,s=0.00030,t=0.00040,q=0.0020), dict(x=30,y=25,s=0.00050,t=0.00010,q=0.0030), dict(x=15,y=40,s=0.00020,t=0.00060,q=0.0025)]
|
||||
_tn=[dict(x=v["x"],y=v["y"],s=-v["s"],t=-v["t"],q=-v["q"]) for v in _tp]
|
||||
if _pack(_tp)!=_pack(_tn): sys.exit("[Ch355] FAIL: canonicalization not bit-exact (positive-Q tri vs its all-negated twin differ)")
|
||||
print("[Ch355] canonicalization self-test PASS: positive-Q triangle and its all-negated twin pack BIT-IDENTICALLY (exact ratio/interp preservation)")
|
||||
|
||||
STG_WORDS=2048
|
||||
stgA,ntA = feeder_list(drA, CBP_A); stgB,ntB = feeder_list(drB, CBP_B)
|
||||
for nm,stg in (("A",stgA),("B",stgB)):
|
||||
if len(stg)>STG_WORDS: sys.exit(f"[Ch355] list {nm} {len(stg)} > STG_WORDS {STG_WORDS}")
|
||||
if max(ntA,ntB)*3 >= (1<<12): sys.exit(f"[Ch355] staging addr exceeds 12-bit bridge range")
|
||||
print(f"[Ch355] list A: {ntA} tris -> {len(stgA)} words (max addr {len(stgA)-1}); list B: {ntB} tris -> {len(stgB)} words (max addr {len(stgB)-1}); both < 12-bit {1<<12}")
|
||||
|
||||
# per-group de-swizzled texture (LINEAR indices -> LPDDR) + CRC ; relocated CLUT bytes + de-gridded palette
|
||||
idxA=memA.read_psmt8(tA['tbp'], tA['tbw'], TW, TH); idxB=memB.read_psmt8(tB['tbp'], tB['tbw'], TW, TH)
|
||||
palA=RC.read_clut32(memA, tA['cbp'], order="grid"); palB=RC.read_clut32(memB, tB['cbp'], order="grid")
|
||||
idxwA=[idxA[i*4]|(idxA[i*4+1]<<8)|(idxA[i*4+2]<<16)|(idxA[i*4+3]<<24) for i in range(TW*TH//4)]
|
||||
idxwB=[idxB[i*4]|(idxB[i*4+1]<<8)|(idxB[i*4+2]<<16)|(idxB[i*4+3]<<24) for i in range(TW*TH//4)]
|
||||
crcTA=sum(idxwA)&0xFFFFFFFF; crcTB=sum(idxwB)&0xFFFFFFFF
|
||||
print(f"[Ch355] LPDDR texture A sum32=0x{crcTA:08x} ; texture B sum32=0x{crcTB:08x} (the two cache-fill CRCs)")
|
||||
|
||||
# independent A->B composed reference (dump order, paint-order DECAL). refmap: [31]cov [30]int [28]overlap [17:9]tu [8:0]tv
|
||||
# ALSO per-group refmaps (refmapA/refmapB) so the isolated single-group RTL renders can be scored independently.
|
||||
refmap=[0]*(FBPXW*H); refpix=[(0,0,0)]*(FBPXW*H); ndraw=[0]*(FBPXW*H)
|
||||
refmapA=[0]*(FBPXW*H); refmapB=[0]*(FBPXW*H)
|
||||
for gi,(dr,idxg,palg) in enumerate([(drA,idxA,palA),(drB,idxB,palB)]):
|
||||
for (v0,v1,v2) in group_tris(dr):
|
||||
x0,y0=v0["x"],v0["y"]; x1,y1=v1["x"],v1["y"]; x2,y2=v2["x"],v2["y"]; ar=edge(x0,y0,x1,y1,x2,y2)
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=max(0,int(min(x0,x1,x2))); maxx=min(FBPXW-1,int(max(x0,x1,x2))+1)
|
||||
miny=max(0,int(min(y0,y1,y2))); maxy=min(H-1,int(max(y0,y1,y2))+1)
|
||||
for py in range(miny,maxy+1):
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(x1,y1,x2,y2,cx,cy)*inv; w1=edge(x2,y2,x0,y0,cx,cy)*inv; w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
a0=edge(x1,y1,x2,y2,float(px),float(py))*inv; a1=edge(x2,y2,x0,y0,float(px),float(py))*inv; a2=1.0-a0-a1
|
||||
S=a0*v0["s"]+a1*v1["s"]+a2*v2["s"]; T=a0*v0["t"]+a1*v1["t"]+a2*v2["t"]; Q=a0*v0["q"]+a1*v1["q"]+a2*v2["q"]
|
||||
if abs(Q)<1e-12: continue
|
||||
tu=int((S/Q)*TW)%TW; tv=int((T/Q)*TH)%TH
|
||||
if tu<0: tu+=TW
|
||||
if tv<0: tv+=TH
|
||||
o=py*FBPXW+px; mw=min(w0,w1,w2); interior=1 if mw>0.04 else 0
|
||||
prev_cov = refmap[o]>>31
|
||||
rm=(1<<31)|(interior<<30)|((tu&0x1FF)<<9)|(tv&0x1FF)
|
||||
if gi==0: refmapA[o]=rm
|
||||
else: refmapB[o]=rm
|
||||
refmap[o]=rm # later group (B) overwrites (paint order)
|
||||
p=palg[idxg[tv*TW+tu]&0xFF]; refpix[o]=(p&0xFF,(p>>8)&0xFF,(p>>16)&0xFF)
|
||||
if gi==1 and prev_cov: refmap[o]|=(1<<28); ndraw[o]=2 # B over A -> overlap
|
||||
overlap_px=sum(1 for o in range(FBPXW*H) if refmap[o]&(1<<28))
|
||||
covered=sum(1 for w in refmap if w>>31)
|
||||
print(f"[Ch355] independent A->B reference: {covered} covered px, {overlap_px} A&B overlap px [bit28]")
|
||||
if overlap_px<500: sys.exit(f"[Ch355] FAIL: only {overlap_px} overlap px — multi-texture accumulation not exercised")
|
||||
|
||||
# ---- emit ----
|
||||
def wmem(name, words, banner):
|
||||
with open(os.path.join(DATA,name),"w") as f:
|
||||
f.write(f"// {banner}\n")
|
||||
for x in words: f.write(f"{x&0xFFFFFFFF:08x}\n")
|
||||
wmem("sh3_mtA_tex_lpddr.mem", idxwA, "Ch355 LOCAL texture A (idx19562/tbp=11264) LINEAR -> LPDDR. gitignored.")
|
||||
wmem("sh3_mtB_tex_lpddr.mem", idxwB, "Ch355 LOCAL texture B (idx89761/tbp=9216) LINEAR -> LPDDR. gitignored.")
|
||||
wmem("sh3_mtA_idx.mem", idxwA, "Ch355 LOCAL idx A. gitignored."); wmem("sh3_mtB_idx.mem", idxwB, "Ch355 LOCAL idx B. gitignored.")
|
||||
wmem("sh3_mtA_pal.mem", [p&0xFFFFFFFF for p in palA], "Ch355 LOCAL de-gridded palette A. gitignored.")
|
||||
wmem("sh3_mtB_pal.mem", [p&0xFFFFFFFF for p in palB], "Ch355 LOCAL de-gridded palette B. gitignored.")
|
||||
wmem("sh3_mt_refmap.mem", refmap, "Ch355 LOCAL A->B composed per-pixel covered|interior|overlap|tu|tv reference. gitignored.")
|
||||
wmem("sh3_mtA_refmap.mem", refmapA, "Ch355 LOCAL A-only (idx19562) per-pixel covered|interior|tu|tv reference. gitignored.")
|
||||
wmem("sh3_mtB_refmap.mem", refmapB, "Ch355 LOCAL B-only (idx89761) per-pixel covered|interior|tu|tv reference. gitignored.")
|
||||
bake.write_feeder_stg_mem("feeder_sh3_mtA.mem", stgA, f"Ch355 LOCAL list A (idx{DRAW_A}, TEX0 CBP={CBP_A}) {ntA} tris. gitignored.", total=STG_WORDS)
|
||||
bake.write_feeder_stg_mem("feeder_sh3_mtB.mem", stgB, f"Ch355 LOCAL list B (idx{DRAW_B}, TEX0 CBP={CBP_B}) {ntB} tris. gitignored.", total=STG_WORDS)
|
||||
|
||||
# bootlet: upload BOTH relocated CLUTs (A@CBP_A, B@CBP_B) via two 256x1 BITBLTs; DISPLAY1 = FBPXW x H
|
||||
clutwA=[int.from_bytes(clutA[i*4:i*4+4],'little') for i in range(256)]
|
||||
clutwB=[int.from_bytes(clutB[i*4:i*4+4],'little') for i in range(256)]
|
||||
RAM_QWORDS=512; pay=[]
|
||||
for cbp,clutw in ((CBP_A,clutwA),(CBP_B,clutwB)):
|
||||
pay.append(bake.giftag(1,0,0,4,int('E'*4,16)))
|
||||
pay.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(cbp,1,0x00)))
|
||||
pay.append(bake.aplusd(bake.R_TRXPOS, bake.trxpos_pack(0,0)))
|
||||
pay.append(bake.aplusd(bake.R_TRXREG, bake.trxreg_pack(256,1)))
|
||||
pay.append(bake.aplusd(bake.R_TRXDIR, bake.trxdir_pack(0)))
|
||||
pay.append(bake.giftag(256//4,1,2,0,0))
|
||||
for q in range(256//4):
|
||||
word=0
|
||||
for lane in range(4): word|=(clutw[q*4+lane]&0xFFFFFFFF)<<(32*lane)
|
||||
pay.append(word)
|
||||
qwc=len(pay); disp_hi=((H-1)<<12)|(FBPXW-1)
|
||||
with open(os.path.join(DATA,"payload_sh3_mt.mem"),"w") as f:
|
||||
f.write(f"// Ch355 LOCAL two-CLUT setup payload (A->CBP={CBP_A}, B->CBP={CBP_B}). gitignored. QWC={qwc}.\n")
|
||||
for _ in range(16): f.write(f"{0:032x}\n")
|
||||
for x in pay: f.write(f"{x&((1<<128)-1):032x}\n")
|
||||
for _ in range(RAM_QWORDS-16-qwc): f.write(f"{0:032x}\n")
|
||||
bake.write_bios_mem("bios_sh3_mt.mem", bake.build_textured_demo_bootlet_disp(qwc, disp_hi, FBW),
|
||||
f"Ch355 LOCAL two-CLUT bootlet (QWC={qwc}, DISPLAY1={FBPXW}x{H}). gitignored.")
|
||||
|
||||
with open(os.path.join(DATA,"sh3_mt_params.vh"),"w") as f:
|
||||
f.write("// Ch355 LOCAL generated params for the multi-texture integration TB. gitignored.\n")
|
||||
for k,v in (("FBW",FBW),("FBPXW",FBPXW),("FBH",H),("VRAM_BYTES_P",VRAM_BYTES),("CBP_A",CBP_A),("CBP_B",CBP_B),
|
||||
("NEW_TBP",NEW_TBP),("TEX_VRAM_BASE",TEX_VRAM_BASE),("TEX_BYTES",TW*TH),("N_BEATS",TW*TH//32),
|
||||
("STG_WORDS",STG_WORDS),("TW",TW),("TH",TH),("NTRIS_A",ntA),("NTRIS_B",ntB),
|
||||
("UNION_OX",OX),("UNION_OY",OY),("CONTENT_W",W)):
|
||||
f.write(f"localparam int {k:<14}= {v};\n")
|
||||
f.write(f"localparam [29:0] LPDDR_TEX_BASE = 30'h{LPDDR_TEX_BASE:07x};\n")
|
||||
f.write(f"localparam [31:0] CRC_TEX_A = 32'h{crcTA:08x};\n")
|
||||
f.write(f"localparam [31:0] CRC_TEX_B = 32'h{crcTB:08x};\n")
|
||||
try:
|
||||
from PIL import Image
|
||||
im=Image.new("RGB",(FBPXW,H)); im.putdata(refpix); im.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon","sh3_mt_ref.png"))
|
||||
print("[Ch355] wrote sh3_mt_ref.png")
|
||||
except Exception as ex: print("(PIL skipped:", ex, ")")
|
||||
print(f"[Ch355] emitted multi-texture fixtures -> {DATA}. FB {FBPXW}x{H} stride {STRIDE}; texA crc=0x{crcTA:08x} texB crc=0x{crcTB:08x}")
|
||||
return 0
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Emit the authentic SH3 4x4 PSMT4 post-process tile pass as scheduler epochs.
|
||||
|
||||
The dump expresses this pass as sixteen 128x128 FST SPRITEs. The runtime
|
||||
feeder is triangle-record based, so each sprite is expanded to two textured
|
||||
triangles and split into ordered 16-row bands. Consecutive bands are packed
|
||||
under the proven 14,500-fragment request-FIFO safety bound. The PSMT4 asset
|
||||
is de-swizzled to the texture cache's linear packed-nibble layout and padded
|
||||
to its fixed 256 KiB residency window.
|
||||
"""
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, HERE)
|
||||
sys.path.insert(0, DATA)
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
import gs_sh3_recon as RC
|
||||
import bake
|
||||
|
||||
DRAW_IDS = [198945,198954,198963,198972,198981,198990,198999,199008,
|
||||
199017,199026,199035,199044,199053,199062,199071,199080]
|
||||
FBW, W, H = 10, 640, 480
|
||||
NEW_TBP, CBP = 1024, 480
|
||||
TEX_CACHE_BYTES = 512*512
|
||||
STG_WORDS = 2048
|
||||
CAPACITY = 14500
|
||||
PERSP_FRAC = bake.PERSP_FRAC
|
||||
PSCALE = 1/64
|
||||
|
||||
|
||||
def wmem(path, words, banner, width=8):
|
||||
with open(os.path.join(DATA, path), "w") as f:
|
||||
f.write(f"// {banner}\n")
|
||||
for word in words:
|
||||
f.write(f"{word & ((1 << (4*width))-1):0{width}x}\n")
|
||||
|
||||
|
||||
def vwords(v):
|
||||
s_fp = round(v["s"] * 128 * (1 << PERSP_FRAC) * PSCALE)
|
||||
t_fp = round(v["t"] * 128 * (1 << PERSP_FRAC) * PSCALE)
|
||||
q_fp = round((1 << PERSP_FRAC) * PSCALE)
|
||||
x = max(0, min(W-1, round(v["x"])))
|
||||
y = max(0, min(H-1, round(v["y"])))
|
||||
return [bake.rgbaq_with_q(0x80, 0x80, 0x80, q_fp),
|
||||
bake.st_data(s_fp, t_fp), bake.xyz2_dataz(x, y, 16)]
|
||||
|
||||
|
||||
def main(argv):
|
||||
tag = argv[argv.index("--tag")+1] if "--tag" in argv else "zsrt139e1"
|
||||
dump = next((x for x in argv[1:] if x.endswith(".gs.zst")), None)
|
||||
if dump is None:
|
||||
found = glob.glob(os.path.join(ROOT, "captures", "gs", "silenthill3", "*224139*.gs.zst"))
|
||||
if not found:
|
||||
sys.exit("no dump 224139 found")
|
||||
dump = found[0]
|
||||
|
||||
got, _ = MD.load_draws(dump, DRAW_IDS)
|
||||
if sorted(got) != DRAW_IDS:
|
||||
sys.exit(f"missing overlay draws: {sorted(set(DRAW_IDS)-set(got))}")
|
||||
states = [got[i]["state"] for i in DRAW_IDS]
|
||||
ref = states[0]
|
||||
for i, state in zip(DRAW_IDS, states):
|
||||
t0, pr = state["tex0"], state["prim"]
|
||||
if (t0["tbp"],t0["tbw"],t0["psm"],t0["tw"],t0["th"],t0["cbp"]) != (13376,2,0x14,128,128,14276):
|
||||
sys.exit(f"idx{i}: unexpected TEX0 {t0}")
|
||||
if (pr["type"],pr["tme"],pr["fst"],pr["abe"]) != (6,1,1,1):
|
||||
sys.exit(f"idx{i}: unexpected PRIM {pr}")
|
||||
for key in ("test","zbuf","clamp","scissor","alpha","texa"):
|
||||
if state[key] != ref[key]:
|
||||
sys.exit(f"idx{i}: {key} differs")
|
||||
if ref["scissor"] != 0x01ff000001ff0000:
|
||||
sys.exit(f"unexpected SCISSOR 0x{ref['scissor']:016x}")
|
||||
|
||||
mem, *_ = RC.build_localmem_to(dump, DRAW_IDS[0])
|
||||
idx = mem.read_psmt4(13376, 2, 128, 128)
|
||||
packed = bytearray(128*128//2)
|
||||
for n in range(0, len(idx), 2):
|
||||
packed[n//2] = (idx[n]&15) | ((idx[n+1]&15)<<4)
|
||||
packed.extend(bytes(TEX_CACHE_BYTES-len(packed)))
|
||||
tex_words = [int.from_bytes(packed[n:n+4], "little") for n in range(0,TEX_CACHE_BYTES,4)]
|
||||
crc = sum(tex_words) & 0xFFFFFFFF
|
||||
pal = RC.read_clut32(mem, 14276, order="grid")
|
||||
|
||||
# Convert each authentic sprite to 16-row triangle bands. The sampled V
|
||||
# coordinate is intentionally constant zero in the dump; U spans 0..128.
|
||||
bands = []
|
||||
for draw_id in DRAW_IDS:
|
||||
vv = got[draw_id]["verts"]
|
||||
if len(vv) != 2:
|
||||
sys.exit(f"idx{draw_id}: expected two sprite vertices, got {len(vv)}")
|
||||
a,b = vv
|
||||
x0,x1 = a["x"],b["x"]
|
||||
y0,y1 = a["y"],min(b["y"],float(H))
|
||||
if y1 <= 0 or y0 >= H:
|
||||
continue
|
||||
for lo in range(max(0,int(round(y0))), int(round(y1)), 16):
|
||||
hi = min(int(round(y1)), lo+16)
|
||||
tl=dict(x=x0,y=lo,s=a["s"],t=a["t"])
|
||||
tr=dict(x=x1,y=lo,s=b["s"],t=a["t"])
|
||||
bl=dict(x=x0,y=hi,s=a["s"],t=b["t"])
|
||||
br=dict(x=x1,y=hi,s=b["s"],t=b["t"])
|
||||
cov=max(0,round(x1)-round(x0))*max(0,hi-lo)
|
||||
bands.append((draw_id,[(tl,tr,bl),(tr,br,bl)],cov))
|
||||
|
||||
epochs=[]; cur=[]; cov=0
|
||||
for draw_id,tris,npx in bands:
|
||||
if cur and cov+npx > CAPACITY:
|
||||
epochs.append((cur,cov)); cur=[]; cov=0
|
||||
cur.extend(tris); cov += npx
|
||||
if cur:
|
||||
epochs.append((cur,cov))
|
||||
if max(x[1] for x in epochs) > CAPACITY:
|
||||
sys.exit("capacity pack failure")
|
||||
|
||||
tex0 = bake.tex0_pack(NEW_TBP,2,psm=0x14,tw=7,th=7,tfx=0)
|
||||
tex0 |= (CBP&0x3FFF)<<37 # runtime CLUT: CLD=0
|
||||
prim = 3 | (1<<4) | (1<<6) # TRIANGLE, TME, ABE; FST converted to ST/Q
|
||||
for k,(tris,npx) in enumerate(epochs):
|
||||
stg=[len(tris)|(1<<32)|(1<<34), bake.frame_1_psmct32(FBW), ref["alpha"],
|
||||
ref["test"], ref["zbuf"], tex0, ref["clamp"], prim]
|
||||
for tri in tris:
|
||||
for v in tri:
|
||||
stg.extend(vwords(v))
|
||||
if len(stg)>STG_WORDS:
|
||||
sys.exit(f"epoch{k}: staging overflow {len(stg)}")
|
||||
bake.write_feeder_stg_mem(f"feeder_sh3_{tag}{k}.mem",stg,
|
||||
f"Ch408 authentic PSMT4 overlay epoch{k}, {len(tris)} tris, capacity {npx}. gitignored.",total=STG_WORDS)
|
||||
wmem(f"sh3_{tag}{k}_tex_lpddr.mem",tex_words,
|
||||
f"Ch408 PSMT4 packed-linear texture padded to fixed 256 KiB cache; crc=0x{crc:08x}. gitignored.")
|
||||
wmem(f"sh3_{tag}{k}_pal.mem",pal,
|
||||
f"Ch408 authentic PSMT4 CSM1 palette, runtime staged. gitignored.")
|
||||
|
||||
n=len(epochs)
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_epochs.txt"),"w") as f:
|
||||
f.write("# Ch408 authentic PSMT4 post-process overlay scheduler fixture\n")
|
||||
f.write("# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32\n")
|
||||
f.write(f"META n_epochs {n} fbpxw {W} fbh {H} fbwords {W*H} lpddr_tex 0x200000 tex_words {TEX_CACHE_BYTES//4} n_beats {TEX_CACHE_BYTES//32}\n")
|
||||
for k,(tris,npx) in enumerate(epochs):
|
||||
words=8+9*len(tris)
|
||||
f.write(f"{k} {DRAW_IDS[0]} 13376 {CBP} sh3_{tag}{k}_tex_lpddr.mem 0x200000 {TEX_CACHE_BYTES} 0x{crc:08x} "
|
||||
f"feeder_sh3_{tag}{k}.mem {words} {len(tris)} {0 if k==0 else 1} sh3_{tag}{k}_pal.mem 0x{sum(pal)&0xFFFFFFFF:08x}\n")
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_params.vh"),"w") as f:
|
||||
f.write("// Ch408 generated params. gitignored.\n")
|
||||
for key,val in (("FBW",FBW),("FBPXW",W),("FBH",H),("VRAM_BYTES_P",128*1024),("NEW_TBP",NEW_TBP),
|
||||
("TEX_VRAM_BASE",NEW_TBP*256),("TEX_BYTES",TEX_CACHE_BYTES),("N_BEATS",TEX_CACHE_BYTES//32),
|
||||
("STG_WORDS",STG_WORDS),("TW",128),("TH",128),("N_EPOCHS",n),("UNION_OX",0),("UNION_OY",0)):
|
||||
f.write(f"localparam int {key:<14}= {val};\n")
|
||||
f.write("localparam [29:0] LPDDR_TEX_BASE = 30'h0200000;\n")
|
||||
for k,(tris,npx) in enumerate(epochs):
|
||||
f.write(f"localparam int EP{k}_NTRIS={len(tris)}; localparam int EP{k}_CBP={CBP}; "
|
||||
f"localparam [31:0] EP{k}_CRC=32'h{crc:08x}; localparam bit EP{k}_REUSE=1'b{0 if k==0 else 1};\n")
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_epoch_table.vh"),"w") as f:
|
||||
for k in range(n):
|
||||
f.write(f"EP_CRC[{k}]=EP{k}_CRC; EP_REC[{k}]=EP{k}_NTRIS; EP_REUSE[{k}]=EP{k}_REUSE;\n")
|
||||
# Runtime CLUT mode uses an empty boot payload.
|
||||
qwc=0; disp=((H-1)<<12)|(W-1)
|
||||
with open(os.path.join(DATA,f"payload_sh3_{tag}.mem"),"w") as f:
|
||||
f.write(f"// Ch408 runtime-CLUT empty payload (CBPs [{CBP}]). gitignored. QWC={qwc}.\n")
|
||||
for _ in range(512): f.write("00000000000000000000000000000000\n")
|
||||
bake.write_bios_mem(f"bios_sh3_{tag}.mem",bake.build_textured_demo_bootlet_disp(qwc,disp,FBW),
|
||||
f"Ch408 runtime PSMT4 overlay bootlet DISPLAY1={W}x{H}. gitignored.")
|
||||
print(f"[Ch408] PASS: {len(DRAW_IDS)} sprites -> {len(bands)} bands -> {n} epochs; max coverage={max(x[1] for x in epochs)}")
|
||||
print(f"[Ch408] PSMT4 indices={sorted(set(idx))}; texture cache crc=0x{crc:08x}; palette sum=0x{sum(pal)&0xFFFFFFFF:08x}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -80,10 +80,15 @@ def tex0_real(tbp, cbp):
|
||||
return v
|
||||
|
||||
def main(argv):
|
||||
global CH # Ch353 — full-frame mode reassigns the FB height
|
||||
dump = None; draw_idx = 89761
|
||||
a = argv[1:]
|
||||
if a and not a[0].startswith("--"): dump = a[0]
|
||||
if "--draw-idx" in a: draw_idx = int(a[a.index("--draw-idx")+1])
|
||||
# Ch353 — --full-frame renders the WHOLE 256xfull_h draw bounding box (no crop) for the LPDDR framebuffer.
|
||||
# Crop-dependent outputs are written with a `full` tag so the Ch352 cropped build (sh3_real_*) is untouched.
|
||||
full_frame = "--full-frame" in a
|
||||
tag = "full" if full_frame else "real"
|
||||
if dump is None:
|
||||
c = glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
||||
@@ -132,6 +137,8 @@ def main(argv):
|
||||
s = sum(row_cov[cy0:cy0+CH])
|
||||
if s > best_sum: best_sum, best_cy0 = s, cy0
|
||||
CY0 = best_cy0; CX0 = 0
|
||||
if full_frame:
|
||||
CH = full_h; CY0 = 0; CX0 = 0 # Ch353 full-frame: render the whole 256xfull_h bounding box (no crop)
|
||||
# apply the viewport crop: shift Y by -CY0 (ST/Q UNCHANGED — only the framebuffer window moves), then
|
||||
# CLIP each triangle to the crop rect [0,FBPXW]x[0,CH] (Sutherland-Hodgman, interpolating S/T/Q linearly in
|
||||
# screen space — correct since S,T,Q are already premultiplied by 1/w). This is the VIEWPORT scissor done at
|
||||
@@ -274,17 +281,17 @@ def main(argv):
|
||||
covered = sum(1 for w in refmap if w>>31)
|
||||
print(f"[Ch350] host reference: {covered} covered FB pixels")
|
||||
# emit the RTL-faithful refmap + PNG for the Ch351 oracle
|
||||
with open(os.path.join(DATA,"sh3_real_refmap_recip.mem"),"w") as f:
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_refmap_recip.mem"),"w") as f:
|
||||
f.write("// Ch351 RTL-faithful (8-bit reciprocal) per-pixel texel map. gitignored.\n")
|
||||
for x in refmap_rec: f.write(f"{x & 0xFFFFFFFF:08x}\n")
|
||||
with open(os.path.join(DATA,"sh3_real_refmap_affine.mem"),"w") as f:
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_refmap_affine.mem"),"w") as f:
|
||||
f.write("// Ch351 AFFINE (per-vertex texel, linear interp) per-pixel texel map. gitignored.\n")
|
||||
for x in refmap_aff: f.write(f"{x & 0xFFFFFFFF:08x}\n")
|
||||
try:
|
||||
from PIL import Image
|
||||
Image.new("RGB",(FBPXW,CH)).copy() # noop guard
|
||||
im2=Image.new("RGB",(FBPXW,CH)); im2.putdata(refpix_rec)
|
||||
im2.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon","sh3_real_ref_recip.png"))
|
||||
im2.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon",f"STALE_idx8_sh3_{tag}_ref_recip.png"))
|
||||
except Exception as e:
|
||||
print("(PIL skip recip png:", e, ")")
|
||||
|
||||
@@ -308,12 +315,12 @@ def main(argv):
|
||||
pay.append(word)
|
||||
qwc=len(pay)
|
||||
disp_hi=((CH-1)<<12)|(FBPXW-1)
|
||||
with open(os.path.join(DATA,"payload_sh3_real.mem"),"w") as f:
|
||||
with open(os.path.join(DATA,f"payload_sh3_{tag}.mem"),"w") as f:
|
||||
f.write(f"// Ch352 LOCAL SH3 real-draw setup payload (CSM1 CLUT 256x1 -> CBP={CBP}, grid bytes verbatim). gitignored. QWC={qwc}.\n")
|
||||
for _ in range(16): f.write(f"{0:032x}\n")
|
||||
for x in pay: f.write(f"{x&((1<<128)-1):032x}\n")
|
||||
for _ in range(RAM_QWORDS-16-qwc): f.write(f"{0:032x}\n")
|
||||
bake.write_bios_mem("bios_sh3_real.mem",
|
||||
bake.write_bios_mem(f"bios_sh3_{tag}.mem",
|
||||
bake.build_textured_demo_bootlet_disp(qwc, disp_hi, FBW),
|
||||
f"Ch352 LOCAL SH3 real-draw setup bootlet (QWC={qwc}, DISPLAY1={FBPXW}x{CH}). gitignored.")
|
||||
print(f"[Ch352] setup bootlet: payload {qwc} qw (CSM1 CLUT 256x1 upload to CBP={CBP}).")
|
||||
@@ -345,12 +352,12 @@ def main(argv):
|
||||
# de-gridded palette pal[i] (what the HW CSM1 grid-read produces) -> TB reference expected colors
|
||||
wmem("sh3_real_pal.mem", [p & 0xFFFFFFFF for p in pal], 8,
|
||||
"Ch350 LOCAL SH3 de-gridded palette pal[i] (grid-read of the CBP bytes) for the TB reference. gitignored.")
|
||||
bake.write_feeder_stg_mem("feeder_sh3_real.mem", stg,
|
||||
bake.write_feeder_stg_mem(f"feeder_sh3_{tag}.mem", stg,
|
||||
"Ch350 LOCAL SH3 REAL draw (idx89761) feeder staging: triangle list + TEX0(PSMT8,CSM1,CLD=1,DECAL). gitignored.",
|
||||
total=STG_WORDS)
|
||||
wmem("sh3_real_refmap.mem", refmap, 8, "Ch350 LOCAL per-FB-pixel covered|interior|tu|tv reference map. gitignored.")
|
||||
wmem(f"sh3_{tag}_refmap.mem", refmap, 8, "Ch350 LOCAL per-FB-pixel covered|interior|tu|tv reference map. gitignored.")
|
||||
# params include for the TB
|
||||
with open(os.path.join(DATA,"sh3_real_params.vh"),"w") as f:
|
||||
with open(os.path.join(DATA,f"sh3_{tag}_params.vh"),"w") as f:
|
||||
f.write("// Ch350 LOCAL generated params for tb_top_psmct32_sh3_real_draw_demo. gitignored.\n")
|
||||
f.write(f"localparam int FBW = {FBW};\n")
|
||||
f.write(f"localparam int FBPXW = {FBPXW};\n")
|
||||
@@ -371,8 +378,8 @@ def main(argv):
|
||||
try:
|
||||
from PIL import Image
|
||||
im=Image.new("RGB",(FBPXW,CH)); im.putdata(refpix)
|
||||
im.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon","sh3_real_ref.png"))
|
||||
print("[Ch350] wrote sh3_real_ref.png")
|
||||
im.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon",f"sh3_{tag}_ref.png"))
|
||||
print(f"[Ch350] wrote sh3_{tag}_ref.png")
|
||||
except Exception as e:
|
||||
print("(PIL skipped:", e, ")")
|
||||
print(f"[Ch350] emitted fixtures -> {DATA} (LOCAL). TEX_VRAM_BASE=0x{TEX_VRAM_BASE:x} TBP={NEW_TBP} CBP={CBP} "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build one scheduler bootlet that preloads the distinct CLUTs required by several fixture tags.
|
||||
|
||||
Usage: gs_merge_scheduler_bootlets.py <output-tag> <input-tag> [<input-tag> ...]
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, DATA)
|
||||
import bake
|
||||
|
||||
RAM_QWORDS = 512
|
||||
HEADER_QWORDS = 16
|
||||
FBW = 10
|
||||
FBPXW = 640
|
||||
FBH = 480
|
||||
|
||||
|
||||
def read_payload(tag):
|
||||
path = os.path.join(DATA, f"payload_sh3_{tag}.mem")
|
||||
with open(path) as f:
|
||||
lines = f.readlines()
|
||||
header = next((line for line in lines if line.startswith("//")), "")
|
||||
match = re.search(r"QWC=(\d+)", header)
|
||||
if not match:
|
||||
raise ValueError(f"{path}: missing QWC metadata")
|
||||
cbp_match = re.search(r"CBPs \[([^]]*)\]", header)
|
||||
if not cbp_match:
|
||||
raise ValueError(f"{path}: missing CBP metadata")
|
||||
cbps = [int(x.strip()) for x in cbp_match.group(1).split(",") if x.strip()]
|
||||
words = [int(line.strip(), 16) for line in lines if line.strip() and not line.startswith("//")]
|
||||
if len(words) != RAM_QWORDS:
|
||||
raise ValueError(f"{path}: expected {RAM_QWORDS} qwords, got {len(words)}")
|
||||
qwc = int(match.group(1))
|
||||
if qwc > RAM_QWORDS - HEADER_QWORDS:
|
||||
raise ValueError(f"{path}: QWC {qwc} exceeds payload capacity")
|
||||
return qwc, cbps, words[HEADER_QWORDS:HEADER_QWORDS + qwc]
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 4:
|
||||
print(__doc__.strip())
|
||||
return 2
|
||||
output_tag, input_tags = argv[1], argv[2:]
|
||||
active = []
|
||||
seen_cbps = set()
|
||||
for tag in input_tags:
|
||||
qwc, cbps, words = read_payload(tag)
|
||||
for cbp in set(cbps):
|
||||
if cbp in seen_cbps:
|
||||
raise ValueError(f"duplicate relocated CBP {cbp}: fixture {tag} aliases an earlier input")
|
||||
seen_cbps.add(cbp)
|
||||
if len(words) != qwc:
|
||||
raise ValueError(f"payload word count mismatch for {tag}")
|
||||
active.extend(words)
|
||||
if len(active) > RAM_QWORDS - HEADER_QWORDS:
|
||||
raise ValueError(f"combined QWC {len(active)} exceeds {RAM_QWORDS - HEADER_QWORDS}")
|
||||
|
||||
payload_name = f"payload_sh3_{output_tag}.mem"
|
||||
bios_name = f"bios_sh3_{output_tag}.mem"
|
||||
with open(os.path.join(DATA, payload_name), "w") as f:
|
||||
f.write(f"// combined scheduler CLUT preload tags={input_tags} CBPs={sorted(seen_cbps)} QWC={len(active)}. gitignored.\n")
|
||||
for _ in range(HEADER_QWORDS):
|
||||
f.write("00000000000000000000000000000000\n")
|
||||
for word in active:
|
||||
f.write(f"{word:032x}\n")
|
||||
for _ in range(RAM_QWORDS - HEADER_QWORDS - len(active)):
|
||||
f.write("00000000000000000000000000000000\n")
|
||||
display1_hi = ((FBH - 1) << 12) | (FBPXW - 1)
|
||||
bake.write_bios_mem(
|
||||
bios_name,
|
||||
bake.build_textured_demo_bootlet_disp(len(active), display1_hi, FBW),
|
||||
f"combined scheduler CLUT preload tags={input_tags} QWC={len(active)} DISPLAY1={FBPXW}x{FBH}. gitignored.",
|
||||
)
|
||||
print(f"[bootlet] emitted {bios_name} + {payload_name}: QWC={len(active)} CBPs={sorted(seen_cbps)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
Executable
+180
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Plan a chronological, staging-safe SH3 PSMT8 coverage fixture.
|
||||
|
||||
This is the bridge from the draw census to gs_make_sh3_scheduler_fixture.py:
|
||||
Select supported frame-1 textured draws, reconstruct texture/CLUT
|
||||
identity in one local-memory replay, and group only consecutive draws whose
|
||||
complete feeder state and sampled assets are identical. The raw-triangle
|
||||
word bound is conservative; the generator repeats the exact clipped check.
|
||||
The default remains the historical opaque-strip plan. Explicit switches can
|
||||
also admit authentic ABE draws, triangle lists, and sprites for later fidelity
|
||||
chapters.
|
||||
"""
|
||||
import argparse, hashlib, json, os, shlex, sys
|
||||
|
||||
HERE=os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
sys.path.insert(0,HERE)
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
import gs_sh3_draw_census as C
|
||||
import gs_sh3_recon as RC
|
||||
import gs_texture_residency as R
|
||||
|
||||
STG_WORDS=2048
|
||||
HEADER_WORDS=8
|
||||
|
||||
def normalized_state(e):
|
||||
s={k:e["state"].get(k) for k in ("prim","tex0","test","zbuf","clamp","alpha","texa","fogcol")}
|
||||
s=json.loads(json.dumps(s,sort_keys=True))
|
||||
t=s["tex0"]; clamp=s["clamp"]
|
||||
wms=clamp&3; wmt=(clamp>>2)&3
|
||||
minu=(clamp>>4)&0x3ff; maxu=(clamp>>14)&0x3ff
|
||||
minv=(clamp>>24)&0x3ff; maxv=(clamp>>34)&0x3ff
|
||||
if wms==2 and minu==0 and maxu==t["tw"]-1: clamp=(clamp&~3)|1
|
||||
if wmt==2 and minv==0 and maxv==t["th"]-1: clamp=(clamp&~12)|4
|
||||
s["clamp"]=clamp
|
||||
return json.dumps(s,sort_keys=True,separators=(",",":"))
|
||||
|
||||
def main():
|
||||
ap=argparse.ArgumentParser()
|
||||
ap.add_argument("dump",nargs="?")
|
||||
ap.add_argument("--census",default="/tmp/sh3_census.json")
|
||||
ap.add_argument("--frame",type=int,default=1,
|
||||
help="capture frame to select (default: 1)")
|
||||
ap.add_argument("--tag",default="zsrt139f1")
|
||||
ap.add_argument("--out",default="/tmp/sh3_opaque_coverage_plan.json")
|
||||
ap.add_argument("--include-abe",action="store_true")
|
||||
ap.add_argument("--untextured-only",action="store_true",
|
||||
help="select only TME=0 draws and emit the opt-in untextured feeder path")
|
||||
ap.add_argument("--prim-types",default="4",
|
||||
help="comma-separated GS primitive types (3=triangles, 4=strip, 5=fan, 6=sprite)")
|
||||
ap.add_argument("--psms",default="0x13",
|
||||
help="comma-separated texture PSMs (0x00=PSMCT32, 0x13=PSMT8, 0x14=PSMT4)")
|
||||
ap.add_argument("--min-idx",type=int)
|
||||
ap.add_argument("--max-idx",type=int)
|
||||
ap.add_argument("--native-fidelity",action="store_true",
|
||||
help="emit the proven fog, bilinear, pixel-center, and native-12.4 flags")
|
||||
ap.add_argument("--fast-fit-scale",action="store_true",
|
||||
help="choose the largest representable STQ scale instead of optimizing per-triangle UV error")
|
||||
ap.add_argument("--capacity-epoch-pixels",type=int,default=14500,
|
||||
help="software coverage cap used by the 16-row capacity splitter")
|
||||
ap.add_argument("--chapter",default="Ch409")
|
||||
ns=ap.parse_args()
|
||||
dump=ns.dump
|
||||
if not dump:
|
||||
import glob
|
||||
hits=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not hits: sys.exit("no 224139 dump found")
|
||||
dump=hits[0]
|
||||
|
||||
collected=R.collect(dump,0)
|
||||
if os.path.exists(ns.census) and not ns.untextured_only:
|
||||
census=json.load(open(ns.census))
|
||||
else:
|
||||
census,_,_=C.census(dump,frame_filter=ns.frame,collected=collected,
|
||||
include_untextured=ns.untextured_only)
|
||||
prim_types={int(x) for x in ns.prim_types.split(",")}
|
||||
if not prim_types or not prim_types.issubset({3,4,5,6}):
|
||||
sys.exit(f"--prim-types must be a nonempty subset of 3,4,5,6; got {sorted(prim_types)}")
|
||||
psms={int(x,0) for x in ns.psms.split(",")}
|
||||
if not psms or not psms.issubset({0x00,0x13,0x14}):
|
||||
sys.exit(f"--psms must be a nonempty subset of 0x00,0x13,0x14; got {sorted(psms)}")
|
||||
def supported_texture(d):
|
||||
if ns.untextured_only:
|
||||
return True
|
||||
t=d["tex0"]
|
||||
return (t["psm"] in psms and
|
||||
((t["psm"]==0x00 and t["tw"]==64 and t["th"]==64 and t["tbw"]==1)
|
||||
or (t["psm"]==0x13 and t["tw"] in (128,256,512) and t["th"] in (128,256,512))
|
||||
or (t["psm"]==0x14 and t["tw"]==512 and t["th"]==1024)))
|
||||
selected=sorted(d["first_idx"] for d in census
|
||||
if d["frame"]==ns.frame and d.get("tex_resident")
|
||||
and d["prim"]["type"] in prim_types
|
||||
and d["prim"]["tme"]==(0 if ns.untextured_only else 1)
|
||||
and (ns.untextured_only or d["prim"]["fst"]==0)
|
||||
and d["prim"]["ctxt"]==0
|
||||
and (ns.include_abe or d["prim"]["abe"]==0)
|
||||
and (ns.min_idx is None or d["first_idx"]>=ns.min_idx)
|
||||
and (ns.max_idx is None or d["first_idx"]<=ns.max_idx)
|
||||
and supported_texture(d))
|
||||
got,_=MD.load_draws(dump,selected,collected=collected,
|
||||
allow_untextured=ns.untextured_only)
|
||||
missing=sorted(set(selected)-set(got))
|
||||
if missing: sys.exit(f"capture lost selected draws: {missing[:10]}")
|
||||
|
||||
rows=[]
|
||||
for idx,mem in RC.iter_localmem_at(dump,selected,collected=collected):
|
||||
e=got[idx]; t=e["state"]["tex0"]
|
||||
if ns.untextured_only:
|
||||
tex=bytes(512*512); clut=bytes(1024)
|
||||
elif t["psm"]==0x00:
|
||||
tex=b"".join(mem.read_ct32_word(t["tbp"],t["tbw"],x,y).to_bytes(4,"little")
|
||||
for y in range(t["th"]) for x in range(t["tw"]))
|
||||
elif t["psm"]==0x13:
|
||||
tex=bytes(mem.read_psmt8(t["tbp"],t["tbw"],512,512))
|
||||
else:
|
||||
unpacked=mem.read_psmt4(t["tbp"],t["tbw"],t["tw"],t["th"])
|
||||
tex=bytes((unpacked[n]&15)|((unpacked[n+1]&15)<<4)
|
||||
for n in range(0,len(unpacked),2))
|
||||
if not ns.untextured_only:
|
||||
clut=(b"" if t["psm"]==0x00 else
|
||||
bytes(mem.m[t["cbp"]*256:t["cbp"]*256+1024]))
|
||||
asset=hashlib.sha256(tex+clut).hexdigest()
|
||||
ptype=e["state"]["prim"]["type"]
|
||||
if ptype==3:
|
||||
ntri=len(e["verts"])//3
|
||||
elif ptype==4:
|
||||
ntri=max(0,len(e["verts"])-2)
|
||||
elif ptype==5:
|
||||
ntri=max(0,len(e["verts"])-2)
|
||||
else:
|
||||
# One GS sprite is a pair of opposing corners and expands into
|
||||
# two independent triangles in the scheduler fixture.
|
||||
ntri=len(e["verts"])
|
||||
words=HEADER_WORDS+9*ntri
|
||||
if words>STG_WORDS:
|
||||
sys.exit(f"idx{idx}: single draw raw bound {words}>{STG_WORDS}")
|
||||
rows.append(dict(idx=idx,ntri=ntri,state=normalized_state(e),asset=asset))
|
||||
|
||||
groups=[]; cur=[]; cur_tris=0; cur_key=None
|
||||
for r in rows:
|
||||
key=(r["state"],r["asset"])
|
||||
if cur and (key!=cur_key or HEADER_WORDS+9*(cur_tris+r["ntri"])>STG_WORDS):
|
||||
groups.append(cur); cur=[]; cur_tris=0
|
||||
cur.append(r); cur_tris+=r["ntri"]; cur_key=key
|
||||
if cur: groups.append(cur)
|
||||
|
||||
idxarg=",".join(str(r["idx"]) for r in rows)
|
||||
grouparg=",".join(str(len(g)) for g in groups)
|
||||
cmd=["python3","tools/gs_make_sh3_scheduler_fixture.py",dump,
|
||||
"--draw-list",idxarg,"--group-sizes",grouparg,"--authz","--fb640",
|
||||
"--tag",ns.tag,"--runtime-clut","--allow-abe","--auth-color-tfx",
|
||||
"--emit-clamp-header","--normalize-full-region-clamp","--clip-fb-guardband","--auth-scissor-clip",
|
||||
"--capacity-epochs",str(ns.capacity_epoch_pixels),
|
||||
"--pscale",("1" if ns.untextured_only else "auto"),
|
||||
"--skip-mixed-q-tris"]
|
||||
if ns.fast_fit_scale:
|
||||
cmd += ["--fast-fit-scale"]
|
||||
if ns.untextured_only:
|
||||
cmd += ["--allow-untextured","--coalesce-solid-sprites","--half-open-sprites"]
|
||||
if ns.native_fidelity:
|
||||
cmd += ["--auth-fog-black-fold","--ref-bilinear","--ref-sample","center","--subpixel-xy"]
|
||||
cmd += ["--chapter",ns.chapter,"--emit"]
|
||||
out=dict(dump=dump,tag=ns.tag,n_draws=len(rows),n_groups=len(groups),
|
||||
max_group_draws=max(map(len,groups)),max_group_raw_words=max(HEADER_WORDS+9*sum(r["ntri"] for r in g) for g in groups),
|
||||
include_abe=ns.include_abe,prim_types=sorted(prim_types),psms=sorted(psms),
|
||||
untextured_only=ns.untextured_only,
|
||||
frame=ns.frame,
|
||||
min_idx=ns.min_idx,max_idx=ns.max_idx,
|
||||
native_fidelity=ns.native_fidelity,
|
||||
capacity_epoch_pixels=ns.capacity_epoch_pixels,
|
||||
draw_ids=[r["idx"] for r in rows],group_sizes=[len(g) for g in groups],command=cmd)
|
||||
with open(ns.out,"w") as f: json.dump(out,f,indent=2); f.write("\n")
|
||||
mode="opaque" if not ns.include_abe else "opaque+ABE"
|
||||
print(f"[{ns.chapter}] {len(rows)} {mode} indexed PSM {sorted(hex(x) for x in psms)} type {sorted(prim_types)} draws -> {len(groups)} exact-state/asset groups")
|
||||
print(f"[{ns.chapter}] max group {out['max_group_draws']} draws; conservative max {out['max_group_raw_words']}/{STG_WORDS} words")
|
||||
print(f"[{ns.chapter}] plan {ns.out}")
|
||||
print(shlex.join(cmd))
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -29,10 +29,10 @@ def f32(bits):
|
||||
PRIMT = {0:"POINT",1:"LINE",2:"LINE_STRIP",3:"TRIANGLE",4:"TRI_STRIP",5:"TRI_FAN",6:"SPRITE",7:"INVALID"}
|
||||
VERTS_PER = {0:1,1:2,2:2,3:3,4:3,5:3,6:2,7:0} # min verts to kick a primitive
|
||||
|
||||
def census(dump, frame_filter=None, min_prims=1, collected=None):
|
||||
def census(dump, frame_filter=None, min_prims=1, collected=None, include_untextured=False):
|
||||
d, h, events, uploads, runs, vram = collected if collected is not None else R.collect(dump, 0)
|
||||
# live GS state we latch as we walk
|
||||
prim = dict(type=7, tme=0, fst=0, abe=0, ctxt=0)
|
||||
prim = dict(type=7, tme=0, fge=0, fst=0, abe=0, ctxt=0)
|
||||
tex0 = {1:None, 2:None}
|
||||
ofx = {1:0.0, 2:0.0}; ofy = {1:0.0, 2:0.0}
|
||||
cur_st = (0.0, 0.0, 1.0) # S, T, Q
|
||||
@@ -72,7 +72,8 @@ def census(dump, frame_filter=None, min_prims=1, collected=None):
|
||||
r, v = e.reg, e.value
|
||||
if r == "PRIM":
|
||||
close()
|
||||
prim = dict(type=v&7, tme=(v>>4)&1, fst=(v>>8)&1, abe=(v>>6)&1, ctxt=(v>>9)&1)
|
||||
prim = dict(type=v&7, tme=(v>>4)&1, fge=(v>>5)&1,
|
||||
fst=(v>>8)&1, abe=(v>>6)&1, ctxt=(v>>9)&1)
|
||||
vqueue = []
|
||||
elif r == "PRMODE": # PRIM-less prim mode (rare); ignore topology change w/o reset
|
||||
pass
|
||||
@@ -94,13 +95,14 @@ def census(dump, frame_filter=None, min_prims=1, collected=None):
|
||||
z = (v>>32) & 0xFFFFFF
|
||||
else:
|
||||
z = (v>>32) & 0xFFFFFFFF
|
||||
if not prim["tme"]: # only textured draws are Ch349 candidates
|
||||
fogv = ((v>>56) & 0xFF) if r in ("XYZF2","XYZF3") else 255
|
||||
if not prim["tme"] and not include_untextured:
|
||||
continue
|
||||
if newkey() is None:
|
||||
continue
|
||||
if cur is None or cur["key"] != newkey():
|
||||
close(); open_draw()
|
||||
vtx = dict(x=x, y=y, z=z, s=cur_st[0], t=cur_st[1], q=cur_st[2], rgba=cur_rgba)
|
||||
vtx = dict(x=x, y=y, z=z, fog=fogv, s=cur_st[0], t=cur_st[1], q=cur_st[2], rgba=cur_rgba)
|
||||
cur["verts"].append(vtx); cur["nvert"] += 1
|
||||
cur["nprim"] += 1 # each kick completes one primitive in fan/strip/list
|
||||
cur["xmin"]=min(cur["xmin"],x); cur["xmax"]=max(cur["xmax"],x)
|
||||
@@ -119,9 +121,10 @@ def census(dump, frame_filter=None, min_prims=1, collected=None):
|
||||
if dr["nprim"] < min_prims: continue
|
||||
if frame_filter is not None and dr["frame"] != frame_filter: continue
|
||||
t0 = dr["tex0"]
|
||||
snap = R.snapshot_present(vram, t0["tbp"], nb=512)
|
||||
snap = (True if not dr["prim"]["tme"] else
|
||||
R.snapshot_present(vram, t0["tbp"], nb=512))
|
||||
clut = None
|
||||
if t0["psm"] in R.INDEXED_PSMS:
|
||||
if dr["prim"]["tme"] and t0["psm"] in R.INDEXED_PSMS:
|
||||
csnap = R.snapshot_present(vram, t0["cbp"], nb=1024, min_nz=64)
|
||||
clut = dict(cbp=t0["cbp"], cpsm=t0["cpsm"], cld=t0["cld"], resident=bool(csnap),
|
||||
distinct=(csnap["distinct"] if csnap else None))
|
||||
@@ -156,6 +159,10 @@ def _score(dr):
|
||||
rectangle in perspective. Reward on-screen containment + sampled-texel span, NOT guard-band area."""
|
||||
t0 = dr["tex0"]
|
||||
s = 0.0
|
||||
if not dr["prim"]["tme"]:
|
||||
return (100.0 * dr["onscreen_frac"] +
|
||||
min(dr["onscreen_area"]/200.0,120.0) +
|
||||
min(dr["nprim"],48))
|
||||
if not dr["tex_resident"]: return -1.0 # must be reconstructable
|
||||
if t0["psm"] in R.INDEXED_PSMS:
|
||||
if dr["clut"] and dr["clut"]["resident"]: s += 40.0 # indexed + resident CLUT == the real SH3 path
|
||||
@@ -204,7 +211,8 @@ def main(argv):
|
||||
def opt(n, dv=None): return argv[argv.index(n)+1] if n in argv else dv
|
||||
top = int(opt("--top","25")); minp = int(opt("--min-prims","1"))
|
||||
ff = int(opt("--frame")) if "--frame" in argv else None
|
||||
draws, h, vram = census(dump, frame_filter=ff, min_prims=minp)
|
||||
draws, h, vram = census(dump, frame_filter=ff, min_prims=minp,
|
||||
include_untextured="--include-untextured" in argv)
|
||||
print(f"# Ch349 draw census: {os.path.basename(dump)}")
|
||||
print(f"# textured draws (>= {minp} prim): {len(draws)} vram_snapshot={'present' if vram is not None else 'ABSENT'}")
|
||||
cand = [d for d in draws if d["score"] > 0]
|
||||
|
||||
@@ -53,6 +53,10 @@ def main(argv):
|
||||
def opt(n, dv=None): return argv[argv.index(n)+1] if n in argv else dv
|
||||
frame = int(opt("--frame","1")); maxd = int(opt("--max-draws","100000"))
|
||||
min_area = float(opt("--min-area","0"))
|
||||
no_z = ("--no-z" in argv) # diagnostic: paint in submission order, last draw wins (bypass z-buffer)
|
||||
dofog = ("--fog" in argv) # apply authentic GS per-vertex fog (FOGCOL=0 black => color*F/256) on FGE draws
|
||||
only_tbp = int(opt("--only-tbp","-1")) # diagnostic: render only draws bound to this texture base
|
||||
only_idx = int(opt("--only-idx","-1")) # diagnostic: render only the draw with this first_idx
|
||||
outdir = opt("--out", os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"captures","gs","silenthill3","extracted","recon"))
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
@@ -62,7 +66,9 @@ def main(argv):
|
||||
d, h, events, uploads, runs, vram = collected
|
||||
draws, _, _ = C.census(dump, frame_filter=frame, min_prims=1, collected=collected)
|
||||
draws = [dr for dr in draws if dr["prim"]["tme"] and dr["tex0"]["psm"] in (0x13,0x00)
|
||||
and dr["onscreen_area"] >= min_area]
|
||||
and dr["onscreen_area"] >= min_area
|
||||
and (only_tbp < 0 or dr["tex0"]["tbp"] == only_tbp)
|
||||
and (only_idx < 0 or dr["first_idx"] == only_idx)]
|
||||
draws.sort(key=lambda x: x["first_idx"])
|
||||
print(f"[frameref] frame {frame}: {len(draws)} textured (PSMT8/CT32) draws, min_area={min_area}; "
|
||||
f"replaying uploads incrementally")
|
||||
@@ -97,6 +103,7 @@ def main(argv):
|
||||
if kind is None: continue
|
||||
tw, th = t0["tw"], t0["th"]
|
||||
verts = dr["verts"]; pt = dr["prim"]["type"]
|
||||
draw_fge = dofog and bool(dr["prim"].get("fge",0))
|
||||
def tri(i0,i1,i2):
|
||||
nonlocal painted_total
|
||||
v0,v1,v2 = verts[i0],verts[i1],verts[i2]
|
||||
@@ -120,8 +127,14 @@ def main(argv):
|
||||
u=(S/Q)*tw; vv=(T/Q)*th
|
||||
z=int(b0*v0["z"]+b1*v1["z"]+b2*v2["z"])
|
||||
o=base+px
|
||||
if z>=zb[o]:
|
||||
zb[o]=z; fb[o]=sample(tex,pal,kind,tw,th,u,vv)+(255,); painted_total+=1
|
||||
if no_z or z>=zb[o]:
|
||||
rgb=sample(tex,pal,kind,tw,th,u,vv)
|
||||
if draw_fge:
|
||||
F=int(b0*v0["fog"]+b1*v1["fog"]+b2*v2["fog"]) # affine per-vertex fog; FOGCOL=0
|
||||
if F<0: F=0
|
||||
elif F>255: F=255
|
||||
rgb=((rgb[0]*F)>>8, (rgb[1]*F)>>8, (rgb[2]*F)>>8)
|
||||
zb[o]=z; fb[o]=rgb+(255,); painted_total+=1
|
||||
if pt==4:
|
||||
for i in range(2,len(verts)): tri(i-2,i-1,i)
|
||||
elif pt==5:
|
||||
|
||||
@@ -41,6 +41,31 @@ def build_localmem_to(dump, draw_idx):
|
||||
replayed.append(u)
|
||||
return mem, replayed, uploads, events, vram
|
||||
|
||||
def iter_localmem_at(dump, draw_idxs, collected=None):
|
||||
"""Yield one mutable LocalMem at each ascending draw index in one replay.
|
||||
|
||||
Callers must consume/copy the texture and CLUT bytes before advancing the
|
||||
iterator. This is equivalent to repeated build_localmem_to() calls but
|
||||
parses the dump once and applies each upload once, which makes full-frame
|
||||
fixture generation practical.
|
||||
"""
|
||||
targets=list(draw_idxs)
|
||||
if targets != sorted(targets) or len(targets) != len(set(targets)):
|
||||
raise ValueError("draw_idxs must be unique and ascending")
|
||||
d, h, events, uploads, runs, vram = collected if collected is not None else R.collect(dump,0)
|
||||
if vram is None:
|
||||
return
|
||||
mem=LM.LocalMem(vram); up=0
|
||||
for draw_idx in targets:
|
||||
while up < len(uploads) and uploads[up]["idx"] < draw_idx:
|
||||
u=uploads[up]; up+=1
|
||||
if u["dpsm"] != 0x00:
|
||||
continue
|
||||
off,end=u["blob_range"]; blob=d[off:end]
|
||||
words=[int.from_bytes(blob[i:i+4],"little") for i in range(0,len(blob)//4*4,4)]
|
||||
mem.write_image_ct32(u["dbp"],u["dbw"],u["dx"],u["dy"],u["w"],u["h"],words)
|
||||
yield draw_idx,mem
|
||||
|
||||
def read_clut32(mem, cbp, order="grid"):
|
||||
"""Read a 256-entry PSMCT32 CLUT from the modelled VRAM. 'grid' = read as a 16x16 CT32 surface based at
|
||||
cbp (dbw=1) — the layout a CSM1 8-bit palette occupies; 'linear' = raw contiguous i*4 from cbp*256.
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch357 follow-up: Z-usage CENSUS (read-only; no RTL, no board).
|
||||
|
||||
Codex: "Census Z usage afterward; no speculative Z work." Measures whether the scheduler's draws actually rely on depth
|
||||
testing, and whether cross-draw Z persistence would change the composite vs the paint-order we already ship.
|
||||
|
||||
Reports, from the AUTHENTIC dump (NOT the flattened-Z fixtures):
|
||||
* per target epoch: TEST.ZTE / TEST.ZTST, ZBUF.ZMSK / ZBP / PSM, and the real per-vertex Z range.
|
||||
* scene-wide tally: for every textured draw kick, the active (ZTE, ZTST, ZMSK) — how much of the scene uses real Z.
|
||||
* if any target epoch does REAL depth testing (ZTE=1, ZTST in {GEQUAL,GREATER}, ZMSK=0): a pairwise screen-overlap +
|
||||
depth-order analysis — at pixels where a later draw overlaps an earlier one, does Z ever REJECT the later fragment
|
||||
(i.e. would Z reorder the composite away from paint-order)?
|
||||
* verdict: is cross-draw Z NEEDED for these draws, or is paint-order sufficient?
|
||||
|
||||
Usage: gs_sh3_z_census.py [dump.gs.zst] [--draw-list i0,i1,i2]
|
||||
"""
|
||||
import sys, os
|
||||
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
import gs_texture_residency as R # R.collect walks the GS event stream (same as the fixture tooling)
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
|
||||
DEFAULT_DRAWS=[11671, 19562, 89761]
|
||||
ZTST_NAME={0:"NEVER",1:"ALWAYS",2:"GEQUAL",3:"GREATER"}
|
||||
ZPSM_NAME={0x00:"PSMZ32",0x01:"PSMZ24",0x02:"PSMZ16",0x0A:"PSMZ16S"}
|
||||
|
||||
def dec_test(t): return dict(ate=t&1, atst=(t>>1)&7, zte=(t>>16)&1, ztst=(t>>17)&3)
|
||||
def dec_zbuf(z): return dict(zbp=z&0x1FF, psm=(z>>24)&0xF, zmsk=(z>>32)&1)
|
||||
|
||||
def real_z(test,zbuf):
|
||||
"""A draw does REAL depth rejection iff Z-test is on, the method can actually reject (GEQUAL/GREATER), and Z is
|
||||
being written (ZMSK=0). ZTE=0 or ZTST=ALWAYS/NEVER, or ZMSK=1 -> Z cannot reorder overlapping draws."""
|
||||
te=dec_test(test); zb=dec_zbuf(zbuf)
|
||||
return te["zte"]==1 and te["ztst"] in (2,3) and zb["zmsk"]==0
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
||||
idxs=[int(x) for x in a[a.index("--draw-list")+1].split(",")] if "--draw-list" in a else list(DEFAULT_DRAWS)
|
||||
if dump is None:
|
||||
import glob; c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
||||
dump=c[0]
|
||||
print(f"[Zcensus] dump={os.path.basename(dump)} target epochs={idxs}")
|
||||
|
||||
# ---- scene-wide tally: active (ZTE,ZTST,ZMSK) at every textured draw kick ----
|
||||
d,h,events,uploads,runs,vram = R.collect(dump, 0)
|
||||
st=dict(PRIM=None, TEST_1=None, TEST_2=None, ZBUF_1=None, ZBUF_2=None)
|
||||
prim=dict(tme=0,ctxt=0); cur_key=None
|
||||
from collections import Counter
|
||||
tally=Counter(); tally_f1=Counter(); nkick=0; nkick_f1=0; frame=0
|
||||
def ctx(): return 1 if prim["ctxt"]==0 else 2
|
||||
for e in events:
|
||||
if e.kind=="FRAME_BOUNDARY": frame=e.frame+1; continue
|
||||
if e.kind!="GSREG": continue
|
||||
frame=e.frame; r,v=e.reg,e.value
|
||||
if r=="PRIM": prim=dict(tme=(v>>4)&1, ctxt=(v>>9)&1); cur_key=None
|
||||
elif r in ("TEST_1","TEST_2","ZBUF_1","ZBUF_2"): st[r]=v
|
||||
elif r in ("XYZF2","XYZ2","XYZF3","XYZ3"):
|
||||
if not prim["tme"]: continue
|
||||
c=ctx(); test=st["TEST_%d"%c]; zbuf=st["ZBUF_%d"%c]
|
||||
if test is None or zbuf is None: continue
|
||||
# count ONE record per draw run (state latched at first kick of the run)
|
||||
k=(id(e),) # unused; we key on run transitions via cur_key below
|
||||
if cur_key is None:
|
||||
te=dec_test(test); zb=dec_zbuf(zbuf)
|
||||
key=(te["zte"], te["ztst"], zb["zmsk"])
|
||||
tally[key]+=1; nkick+=1
|
||||
if frame==1: tally_f1[key]+=1; nkick_f1+=1
|
||||
cur_key=key
|
||||
def fmt_tally(t,n):
|
||||
out=[]
|
||||
for (zte,ztst,zmsk),c in sorted(t.items(), key=lambda kv:-kv[1]):
|
||||
lbl = "Z-OFF" if zte==0 else f"ZTE zt={ZTST_NAME[ztst]} zmsk={zmsk}"
|
||||
real = "REAL-DEPTH" if (zte==1 and ztst in (2,3) and zmsk==0) else "no-reorder"
|
||||
out.append(f" {c:5d} ({100.0*c/max(n,1):4.1f}%) {lbl:24s} -> {real}")
|
||||
return "\n".join(out)
|
||||
print(f"\n[Zcensus] SCENE-WIDE draw-run tally (all frames, {nkick} runs):")
|
||||
print(fmt_tally(tally,nkick))
|
||||
print(f"\n[Zcensus] frame f1 only ({nkick_f1} runs):")
|
||||
print(fmt_tally(tally_f1,nkick_f1))
|
||||
|
||||
# ---- per-target-epoch detail ----
|
||||
got,_=MD.load_draws(dump, idxs)
|
||||
for i in idxs:
|
||||
if i not in got: sys.exit(f"[Zcensus] FAIL: idx{i} not found as a textured draw")
|
||||
eps=[got[i] for i in idxs]
|
||||
print(f"\n[Zcensus] TARGET epochs (authentic depth state + per-vertex Z):")
|
||||
print(f" {'idx':>7} {'ZTE':>3} {'ZTST':>8} {'ZMSK':>4} {'ZBP':>4} {'ZPSM':>8} {'z_min':>10} {'z_max':>10} {'z_span':>8} real-depth?")
|
||||
any_real=False
|
||||
for e in eps:
|
||||
te=dec_test(e["state"]["test"]); zb=dec_zbuf(e["state"]["zbuf"])
|
||||
zs=[v["z"] for v in e["verts"]]; zmin=min(zs); zmax=max(zs)
|
||||
rz = real_z(e["state"]["test"], e["state"]["zbuf"]); any_real|=rz
|
||||
print(f" {e['first_idx']:>7} {te['zte']:>3} {ZTST_NAME[te['ztst']]:>8} {zb['zmsk']:>4} {zb['zbp']:>4} "
|
||||
f"{ZPSM_NAME.get(zb['psm'],hex(zb['psm'])):>8} {zmin:>10} {zmax:>10} {zmax-zmin:>8} {'YES' if rz else 'no'}")
|
||||
|
||||
# ---- verdict ----
|
||||
print()
|
||||
if not any_real:
|
||||
# explain WHY paint-order is exactly the hardware behaviour for these draws
|
||||
reasons=set()
|
||||
for e in eps:
|
||||
te=dec_test(e["state"]["test"]); zb=dec_zbuf(e["state"]["zbuf"])
|
||||
if te["zte"]==0: reasons.add("ZTE=0 (Z-test disabled)")
|
||||
elif te["ztst"]==1: reasons.add("ZTST=ALWAYS (test never rejects)")
|
||||
elif te["ztst"]==0: reasons.add("ZTST=NEVER")
|
||||
if zb["zmsk"]==1: reasons.add("ZMSK=1 (Z buffer read-only, no depth written)")
|
||||
print(f"[Zcensus] VERDICT: cross-draw Z is NOT needed for these draws — {', '.join(sorted(reasons))}.")
|
||||
print(f"[Zcensus] The GS applies no depth rejection here, so paint-order (dump order) IS the hardware result.")
|
||||
print(f"[Zcensus] Recommendation (matches Codex 'no speculative Z'): do NOT add Z for this scheduler scene.")
|
||||
return 0
|
||||
|
||||
# real depth present -> pairwise screen-overlap + depth-order analysis (does Z reorder vs paint-order?)
|
||||
print(f"[Zcensus] REAL depth testing present -> checking whether Z would REORDER the composite vs paint-order...")
|
||||
edge=MD.edge
|
||||
def raster_z(e):
|
||||
"""per-pixel {(x,y): z_interp} for a draw, using its authentic vertex Z (barycentric)."""
|
||||
cov={}
|
||||
fv=e["verts"]
|
||||
for i in range(2,len(fv)):
|
||||
v0,v1,v2=fv[i-2],fv[i-1],fv[i]
|
||||
ar=edge(v0["x"],v0["y"],v1["x"],v1["y"],v2["x"],v2["y"])
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=int(min(v0["x"],v1["x"],v2["x"])); maxx=int(max(v0["x"],v1["x"],v2["x"]))+1
|
||||
miny=int(min(v0["y"],v1["y"],v2["y"])); maxy=int(max(v0["y"],v1["y"],v2["y"]))+1
|
||||
for py in range(miny,maxy+1):
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(v1["x"],v1["y"],v2["x"],v2["y"],cx,cy)*inv
|
||||
w1=edge(v2["x"],v2["y"],v0["x"],v0["y"],cx,cy)*inv
|
||||
w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
z=w0*v0["z"]+w1*v1["z"]+w2*v2["z"]
|
||||
cov[(px,py)]=z # last tri wins within a draw (fine for a coverage/Z sample)
|
||||
return cov
|
||||
zmaps=[raster_z(e) for e in eps]
|
||||
reorder_total=0
|
||||
for bi in range(len(eps)):
|
||||
for ai in range(bi): # A earlier (ai<bi) in dump order, B later
|
||||
A=zmaps[ai]; B=zmaps[bi]; diff=0; ov=0
|
||||
for p,zb in B.items():
|
||||
if p in A:
|
||||
ov+=1
|
||||
# GS: larger Z = nearer. GEQUAL/GREATER: later B passes iff zB >= (or >) zA. If it would be REJECTED
|
||||
# (zB < zA) then Z keeps A -> differs from paint-order (which always takes B).
|
||||
if zb < A[p]: diff+=1
|
||||
reorder_total+=diff
|
||||
print(f"[Zcensus] pair A=idx{eps[ai]['first_idx']} over-drawn by B=idx{eps[bi]['first_idx']}: "
|
||||
f"overlap {ov}px, Z-would-reject-B {diff}px ({100.0*diff/max(ov,1):.2f}% of overlap)")
|
||||
print()
|
||||
if reorder_total==0:
|
||||
print(f"[Zcensus] VERDICT: real Z state present, but Z would reject the later draw at 0 overlapping pixels -> the "
|
||||
f"depth order MATCHES paint-order for these draws. Cross-draw Z would NOT change the composite.")
|
||||
print(f"[Zcensus] Recommendation: paint-order is sufficient for THIS scene; no Z needed (revisit only if a chosen "
|
||||
f"scene shows reorder>0).")
|
||||
else:
|
||||
print(f"[Zcensus] VERDICT: Z WOULD reorder the composite at ~{reorder_total} pixels -> cross-draw Z IS needed to "
|
||||
f"match the hardware for these draws. This justifies (non-speculative) Z work as the next rung.")
|
||||
print(f"[Zcensus] CAVEAT: the exact px count uses a RAW vertex-Z compare; the precise value depends on the "
|
||||
f"PSMZ16S quantization this census does not model. The ORDERING (near vs far cluster) is robust to that.")
|
||||
return 0
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch357 (re-scoped): PSMZ16S-accurate persistent-Z software ORACLE at the 384x381 scheduler geometry.
|
||||
|
||||
Codex pre-RTL gate: build a PSMZ16S-accurate oracle (authentic per-vertex Z interpolation, quantization/storage, GEQUAL,
|
||||
ZTE, ZMSK), replace constant-Z flattening with authentic XYZ2 Z, and produce an expected reject/owner map quantifying how
|
||||
the Z-tested framebuffer must differ from paint order.
|
||||
|
||||
MODEL (empirically grounded — see gs_sh3_z_census.py):
|
||||
* geometry: the EXISTING Ch356 scheduler union FB, 384x381 (OX,OY union origin), draws translated into it.
|
||||
* Z is interpolated LINEARLY in screen space (GS Z is screen-linear, NOT perspective-corrected).
|
||||
* PSMZ16S quantization: Zq = (24-bit vertex Z) >> 8 -> the high 16 bits (the whole scene uses the full 24-bit range;
|
||||
>>8 maps it onto 16 bits coherently; & 0xFFFF would wrap a surface). The signed "S" bias is order-preserving, so the
|
||||
GEQUAL reject/owner outcome is invariant to it.
|
||||
* shared PERSISTENT 16-bit Z buffer across ALL 3 epochs (cleared once to 0 = farthest). Per fragment:
|
||||
ZTE=1: pass iff Zq >= zbuf[px] (GEQUAL, larger Z = nearer on PS2).
|
||||
on pass: write owner=epoch; ZMSK=0 -> zbuf[px]=Zq.
|
||||
* paint-order (what we ship today) = same coverage but ALWAYS overwrite (no Z test).
|
||||
|
||||
Outputs (to sim/data/top_psmct32_raster_demo/, LOCAL/gitignored):
|
||||
* sh3_zsched_zowner.mem — per pixel: [31]cov [30]rejected-vs-paint [26:24]z_owner_epoch [23:8]zq16 [ ... ]
|
||||
* sh3_zsched_reject.mem — per pixel: paint_owner vs z_owner diff mask (the far-late rejects)
|
||||
* a quantified report: covered px, pixels where Z rejects the paint-order winner, per-epoch owned counts (paint vs Z).
|
||||
|
||||
Usage: gs_sh3_z_oracle.py [dump.gs.zst] [--draw-list i0,i1,i2] [--emit]
|
||||
"""
|
||||
import sys, os
|
||||
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
|
||||
import gs_sh3_recon as RC
|
||||
import gs_make_sh3_multidraw_fixture as MD # load_draws (verts incl z) + edge
|
||||
|
||||
DEFAULT_DRAWS=[11671, 19562, 89761]
|
||||
|
||||
# PSMZ16S 24/32-bit -> 16-bit reduction. NOT yet authoritatively pinned (Codex precondition): candidate hardware models
|
||||
# give DIFFERENT reject maps, so the oracle is parameterized until ground-truth (PCSX2 dump replay) decides.
|
||||
# shift8 : Zq = Z >> 8 (high 16 of the scene's 24-bit range; coherent, no wrap)
|
||||
# mask16 : Zq = Z & 0xFFFF (low 16 — what PCSX2 WritePixel16 stores; WRAPS a 24-bit surface)
|
||||
# clamp16: Zq = min(Z, 0xFFFF) (near draws collapse to 0xFFFF)
|
||||
def zquant(z, model):
|
||||
z=int(z)
|
||||
if model=="mask16": return z & 0xFFFF
|
||||
if model=="clamp16": return 0xFFFF if z>0xFFFF else (0 if z<0 else z)
|
||||
q = z >> 8 # shift8 (default)
|
||||
return 0xFFFF if q>0xFFFF else (0 if q<0 else q)
|
||||
|
||||
def bbox(dr):
|
||||
xs=[v["x"] for v in dr["verts"]]; ys=[v["y"] for v in dr["verts"]]; return (min(xs),min(ys),max(xs),max(ys))
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
||||
idxs=[int(x) for x in a[a.index("--draw-list")+1].split(",")] if "--draw-list" in a else list(DEFAULT_DRAWS)
|
||||
ZMODEL = a[a.index("--zmodel")+1] if "--zmodel" in a else "clamp16"
|
||||
# KNOWN VECTORS pinned against the PCSX2 SOFTWARE RASTER path (authoritative; NOT the bare memory write):
|
||||
# GSLocalMemory.cpp:199 PSMZ16S -> PSM_FMT_16
|
||||
# GSRendererSW.cpp:1439 z_max = 0xffffffff >> (fmt*8) = 0xFFFF
|
||||
# GSDrawScanline…:1129 source_z = CLAMP(interpolated_z, z_max) ("Clamp Z to ZPSM_FMT_MAX")
|
||||
# GSDrawScanline…:1157 dest_z = stored_z & 0xFFFF ; pass = source_z >= dest_z (GEQUAL)
|
||||
# => the model is CLAMP16 (min with 0xFFFF), NOT mask16. WritePixel16((u16)c) is only the final store, after the clamp.
|
||||
# The "S" in PSMZ16S = swizzled STORAGE layout, not signed depth (Codex) — address swizzle only, not the value reduction.
|
||||
for zin,exp in ((0x00FED407,0xFFFF),(0x00015534,0xFFFF),(0x0000B2C2,0xB2C2),(0xFFFFFFFF,0xFFFF),(0x00008000,0x8000),(0,0)):
|
||||
assert zquant(zin,"clamp16")==exp, f"clamp16 vector fail 0x{zin:x}->0x{zquant(zin,'clamp16'):x} exp 0x{exp:x}"
|
||||
print("[Zoracle] PSMZ16S known-vector self-test PASS (clamp16 == PCSX2 SW raster source-clamp to 0xFFFF)")
|
||||
if dump is None:
|
||||
import glob; c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
||||
dump=c[0]
|
||||
print(f"[Zoracle] dump={os.path.basename(dump)} epochs={idxs} (PSMZ16S zmodel={ZMODEL}, GEQUAL, persistent)")
|
||||
|
||||
got,_=MD.load_draws(dump, idxs)
|
||||
for i in idxs:
|
||||
if i not in got: sys.exit(f"[Zoracle] FAIL: idx{i} not found as a textured draw")
|
||||
eps=[got[i] for i in idxs]
|
||||
|
||||
# depth state gate: all real GEQUAL, ZMSK=0, shared ZBP (the census precondition)
|
||||
for e in eps:
|
||||
t=e["state"]["test"]; z=e["state"]["zbuf"]
|
||||
zte=(t>>16)&1; ztst=(t>>17)&3; zmsk=(z>>32)&1
|
||||
if not (zte==1 and ztst==2 and zmsk==0):
|
||||
sys.exit(f"[Zoracle] FAIL: idx{e['first_idx']} not real GEQUAL depth (zte={zte} ztst={ztst} zmsk={zmsk})")
|
||||
zbps={ (e['state']['zbuf']>>0)&0x1FF for e in eps }
|
||||
print(f"[Zoracle] all epochs GEQUAL/ZMSK=0; shared ZBP={zbps} (single Z buffer)")
|
||||
|
||||
# union geometry = the EXISTING Ch356 scheduler FB (384x381)
|
||||
OX=int(min(bbox(e)[0] for e in eps)); OY=int(min(bbox(e)[1] for e in eps))
|
||||
UX=max(bbox(e)[2] for e in eps); UY=max(bbox(e)[3] for e in eps)
|
||||
W=int(UX)-OX+1; H=int(UY)-OY+1; FBW=(W+63)//64; FBPXW=FBW*64
|
||||
print(f"[Zoracle] union origin=({OX},{OY}) -> FB {FBPXW}x{H} (FBW={FBW}) [existing Ch356 scheduler geometry]")
|
||||
NPX=FBPXW*H; edge=MD.edge
|
||||
|
||||
# per-epoch texture idx + palette (authentic), for color/owner readout
|
||||
TEX=512*512; epdata=[]
|
||||
for e in eps:
|
||||
t0=e["state"]["tex0"]; mem,*_=RC.build_localmem_to(dump, e["first_idx"])
|
||||
idx=mem.read_psmt8(t0['tbp'], t0['tbw'], 512, 512)
|
||||
pal=RC.read_clut32(mem, t0['cbp'], order="grid")
|
||||
epdata.append(dict(idx=idx, pal=pal))
|
||||
|
||||
def tris(e):
|
||||
fv=[dict(x=v["x"]-OX,y=v["y"]-OY,z=v["z"],s=v["s"],t=v["t"],q=v["q"]) for v in e["verts"]]
|
||||
return [(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))]
|
||||
|
||||
# buffers
|
||||
zbuf=[-1]*NPX # stored Zq (16-bit); -1 = uninitialised (cleared "farther than any" => first frag passes)
|
||||
z_owner=[-1]*NPX; z_zq=[0]*NPX # Z-tested owner epoch + its Zq
|
||||
paint_owner=[-1]*NPX # paint-order owner (last covering, no Z)
|
||||
cov=[0]*NPX
|
||||
|
||||
for k,e in enumerate(eps):
|
||||
for (v0,v1,v2) in tris(e):
|
||||
ar=edge(v0["x"],v0["y"],v1["x"],v1["y"],v2["x"],v2["y"])
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=max(0,int(min(v0["x"],v1["x"],v2["x"]))); maxx=min(FBPXW-1,int(max(v0["x"],v1["x"],v2["x"]))+1)
|
||||
miny=max(0,int(min(v0["y"],v1["y"],v2["y"]))); maxy=min(H-1,int(max(v0["y"],v1["y"],v2["y"]))+1)
|
||||
for py in range(miny,maxy+1):
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(v1["x"],v1["y"],v2["x"],v2["y"],cx,cy)*inv
|
||||
w1=edge(v2["x"],v2["y"],v0["x"],v0["y"],cx,cy)*inv
|
||||
w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
o=py*FBPXW+px; cov[o]=1
|
||||
zf=w0*v0["z"]+w1*v1["z"]+w2*v2["z"] # screen-linear Z interp (24-bit)
|
||||
zq=zquant(zf, ZMODEL) # PSMZ16S reduction (model-parameterized, see zquant)
|
||||
paint_owner[o]=k # paint-order always overwrites
|
||||
if zq >= zbuf[o]: # GEQUAL (>= handles uninit -1 too)
|
||||
zbuf[o]=zq; z_owner[o]=k; z_zq[o]=zq # ZTE pass + ZMSK=0 write
|
||||
|
||||
covered=sum(cov)
|
||||
# reject vs paint-order: covered pixels where the Z-tested owner != the paint-order owner (far-late fragment rejected)
|
||||
reject=[1 if (cov[o] and z_owner[o]!=paint_owner[o]) else 0 for o in range(NPX)]
|
||||
nreject=sum(reject)
|
||||
paint_ct=[0]*len(eps); z_ct=[0]*len(eps)
|
||||
for o in range(NPX):
|
||||
if cov[o]:
|
||||
if paint_owner[o]>=0: paint_ct[paint_owner[o]]+=1
|
||||
if z_owner[o]>=0: z_ct[z_owner[o]]+=1
|
||||
|
||||
print(f"\n[Zoracle] covered px={covered}")
|
||||
print(f"[Zoracle] per-epoch OWNED pixels (final, after all 3 epochs):")
|
||||
for k,e in enumerate(eps):
|
||||
print(f" epoch {k} idx{e['first_idx']:>6}: paint-order={paint_ct[k]:>7} Z-tested={z_ct[k]:>7} (delta {z_ct[k]-paint_ct[k]:+d})")
|
||||
print(f"[Zoracle] pixels where Z REJECTS the paint-order winner (final FB MUST differ here): {nreject} "
|
||||
f"({100.0*nreject/max(covered,1):.2f}% of covered)")
|
||||
if nreject==0:
|
||||
print("[Zoracle] (no difference — paint order would already match; Z not needed. Unexpected for this scene.)")
|
||||
else:
|
||||
print(f"[Zoracle] => the far-late draw is depth-rejected in the overlaps; paint-order output demonstrably differs.")
|
||||
|
||||
if "--emit" in a:
|
||||
def wmem(name, words, banner):
|
||||
with open(os.path.join(DATA,name),"w") as f:
|
||||
f.write(f"// {banner}\n")
|
||||
for x in words: f.write(f"{x&0xFFFFFFFF:08x}\n")
|
||||
# z-owner map: [31]cov [30]reject-vs-paint [27:24]z_owner(+1,0=none) [23:8]zq16 [3:0]paint_owner(+1)
|
||||
owner=[]
|
||||
for o in range(NPX):
|
||||
zo=(z_owner[o]+1)&0xF; po=(paint_owner[o]+1)&0xF
|
||||
owner.append((cov[o]<<31)|(reject[o]<<30)|(zo<<24)|((z_zq[o]&0xFFFF)<<8)|po)
|
||||
wmem("sh3_zsched_zowner.mem", owner, "Ch357 LOCAL persistent-Z oracle: cov|reject|z_owner|zq16|paint_owner. gitignored.")
|
||||
wmem("sh3_zsched_reject.mem", reject, "Ch357 LOCAL per-pixel reject-vs-paint mask (far-late Z rejects). gitignored.")
|
||||
print(f"[Zoracle] emitted sh3_zsched_zowner.mem + sh3_zsched_reject.mem -> {DATA}")
|
||||
return 0
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch357: rank authentic supported draw GROUPS by clamp16 depth-rejection strength.
|
||||
|
||||
Codex: "Before LPDDR-Z RTL, rank authentic supported draw groups using the corrected clamp16 model and select one with
|
||||
stronger depth rejection if available. Keep the current trio as regression coverage."
|
||||
|
||||
The reject map (z-tested owner != paint-order owner) depends ONLY on coverage + Z, not on the textures — so we rank fast
|
||||
from the census verts (no per-epoch texture reconstruction). Model = clamp16 (PCSX2 SW raster: source_z=min(z,0xFFFF),
|
||||
dest=stored&0xFFFF, GEQUAL). A strong acceptance group has heavy overlap where LATER-drawn draws are FARTHER (smaller Z)
|
||||
so GEQUAL rejects them — paint-order and depth-order disagree.
|
||||
|
||||
Constraints (scheduler-supported, like gs_make_sh3_scheduler_fixture): same frame f1; >=3 DISTINCT textures AND distinct
|
||||
CLUTs; PSMT8 512x512 perspective TME (fst=0); resident tex+CLUT; on-screen; union bbox that fits the 384x381 rung.
|
||||
|
||||
Usage: gs_sh3_z_rank.py [dump.gs.zst] [--topk N] [--max-fb 384x480]
|
||||
"""
|
||||
import sys, os, itertools
|
||||
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
import gs_sh3_draw_census as C
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
edge=MD.edge
|
||||
|
||||
def clamp16(z): z=int(z); return 0xFFFF if z>0xFFFF else (0 if z<0 else z)
|
||||
|
||||
def bbox_overlap(a,b):
|
||||
ox=min(a[2],b[2])-max(a[0],b[0]); oy=min(a[3],b[3])-max(a[1],b[1])
|
||||
return max(0.0,ox)*max(0.0,oy)
|
||||
|
||||
def tris_of(verts, OX, OY):
|
||||
fv=[dict(x=v["x"]-OX,y=v["y"]-OY,z=v["z"]) for v in verts]
|
||||
return [(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))]
|
||||
|
||||
def score_group(draws):
|
||||
"""clamp16 persistent-Z reject% over a group (dump order). Returns (covered, nreject, W, H)."""
|
||||
OX=int(min(d["xmin"] for d in draws)); OY=int(min(d["ymin"] for d in draws))
|
||||
UX=max(d["xmax"] for d in draws); UY=max(d["ymax"] for d in draws)
|
||||
W=int(UX)-OX+1; H=int(UY)-OY+1
|
||||
if W<=0 or H<=0 or W*H>700000: return (0,0,W,H) # skip degenerate/huge
|
||||
NPX=W*H
|
||||
zbuf=[-1]*NPX; z_owner=[-1]*NPX; paint_owner=[-1]*NPX; cov=bytearray(NPX)
|
||||
for k,d in enumerate(draws):
|
||||
for (v0,v1,v2) in tris_of(d["verts"],OX,OY):
|
||||
ar=edge(v0["x"],v0["y"],v1["x"],v1["y"],v2["x"],v2["y"])
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=max(0,int(min(v0["x"],v1["x"],v2["x"]))); maxx=min(W-1,int(max(v0["x"],v1["x"],v2["x"]))+1)
|
||||
miny=max(0,int(min(v0["y"],v1["y"],v2["y"]))); maxy=min(H-1,int(max(v0["y"],v1["y"],v2["y"]))+1)
|
||||
for py in range(miny,maxy+1):
|
||||
base=py*W
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(v1["x"],v1["y"],v2["x"],v2["y"],cx,cy)*inv
|
||||
w1=edge(v2["x"],v2["y"],v0["x"],v0["y"],cx,cy)*inv
|
||||
w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
o=base+px; cov[o]=1
|
||||
zq=clamp16(w0*v0["z"]+w1*v1["z"]+w2*v2["z"])
|
||||
paint_owner[o]=k
|
||||
if zq>=zbuf[o]: zbuf[o]=zq; z_owner[o]=k
|
||||
covered=sum(cov); nreject=sum(1 for o in range(NPX) if cov[o] and z_owner[o]!=paint_owner[o])
|
||||
return (covered, nreject, W, H)
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
||||
topk=int(a[a.index("--topk")+1]) if "--topk" in a else 12
|
||||
maxfb=a[a.index("--max-fb")+1] if "--max-fb" in a else "448x480"
|
||||
MW,MH=(int(x) for x in maxfb.split("x"))
|
||||
if dump is None:
|
||||
import glob; c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found");
|
||||
dump=c[0]
|
||||
print(f"[Zrank] dump={os.path.basename(dump)} clamp16 depth-rejection ranking (fits<= {MW}x{MH})")
|
||||
|
||||
draws,h,vram = C.census(dump, frame_filter=1, min_prims=8)
|
||||
cand=[]
|
||||
for d in draws:
|
||||
t0=d["tex0"]
|
||||
if not (t0["psm"]==0x13 and t0.get("tw")==512 and t0.get("th")==512): continue
|
||||
if d["prim"]["tme"]!=1 or d["prim"]["fst"]!=0: continue
|
||||
if not d["tex_resident"]: continue
|
||||
if not (d["clut"] and d["clut"]["resident"]): continue
|
||||
if d["onscreen_frac"]<0.5: continue
|
||||
zc=[clamp16(v["z"]) for v in d["verts"]]
|
||||
d["_funcl"]=sum(1 for z in zc if z<0xFFFF)/len(zc); d["_zmed"]=sorted(zc)[len(zc)//2]
|
||||
d["_bbox"]=(d["xmin"],d["ymin"],d["xmax"],d["ymax"]); d["_area"]=(d["xmax"]-d["xmin"])*(d["ymax"]-d["ymin"])
|
||||
if d["_funcl"]>=0.5: cand.append(d)
|
||||
print(f"[Zrank] {len(cand)} rich-depth supported candidates; textures={sorted(set(d['tex0']['tbp'] for d in cand))}")
|
||||
|
||||
# per texture, keep a manageable set of the biggest/on-screen draws to bound the combinatorics
|
||||
by_tex={}
|
||||
for d in cand: by_tex.setdefault(d["tex0"]["tbp"], []).append(d)
|
||||
for tb in by_tex: by_tex[tb]=sorted(by_tex[tb], key=lambda d:-d["_area"])[:8]
|
||||
texs=sorted(by_tex)
|
||||
|
||||
# enumerate distinct-texture triples with pairwise bbox overlap + a spread of depths; score with clamp16 raster
|
||||
seen=set(); scored=[]
|
||||
for t3 in itertools.combinations(texs,3):
|
||||
for a0 in by_tex[t3[0]]:
|
||||
for a1 in by_tex[t3[1]]:
|
||||
if bbox_overlap(a0["_bbox"],a1["_bbox"])<200: continue
|
||||
for a2 in by_tex[t3[2]]:
|
||||
if bbox_overlap(a0["_bbox"],a2["_bbox"])<200 and bbox_overlap(a1["_bbox"],a2["_bbox"])<200: continue
|
||||
grp=sorted([a0,a1,a2], key=lambda d:d["first_idx"]) # dump order = paint order
|
||||
# distinct CLUTs required
|
||||
if len({g["tex0"]["cbp"] for g in grp})<3: continue
|
||||
key=tuple(g["first_idx"] for g in grp)
|
||||
if key in seen: continue
|
||||
seen.add(key)
|
||||
OX=int(min(g["xmin"] for g in grp)); OY=int(min(g["ymin"] for g in grp))
|
||||
W=int(max(g["xmax"] for g in grp))-OX+1; Hh=int(max(g["ymax"] for g in grp))-OY+1
|
||||
if W>MW or Hh>MH: continue # must fit the target rung
|
||||
cov,nrej,Wr,Hr=score_group(grp)
|
||||
if cov<2000: continue
|
||||
scored.append((nrej/max(cov,1), nrej, cov, key, [g["tex0"]["tbp"] for g in grp], Wr, Hr))
|
||||
scored.sort(reverse=True)
|
||||
print(f"[Zrank] scored {len(scored)} distinct-texture overlapping triples (fit rung). TOP {topk} by reject-fraction:")
|
||||
print(f" {'reject%':>8} {'reject':>7} {'covered':>8} {'FB':>9} draws (dump order) / textures")
|
||||
for frac,nrej,cov,key,tbps,Wr,Hr in scored[:topk]:
|
||||
print(f" {100*frac:7.2f}% {nrej:>7} {cov:>8} {Wr:>4}x{Hr:<4} {list(key)} tbp={tbps}")
|
||||
# reference: the current trio
|
||||
trio=[d for d in draws if d["first_idx"] in (11671,19562,89761)]
|
||||
if len(trio)==3:
|
||||
cov,nrej,Wr,Hr=score_group(sorted(trio,key=lambda d:d["first_idx"]))
|
||||
print(f"[Zrank] current regression trio [11671,19562,89761]: {100*nrej/max(cov,1):.2f}% ({nrej}/{cov}) FB {Wr}x{Hr}")
|
||||
return 0
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Concatenate runtime-CLUT scheduler fixtures without regenerating accepted epochs.
|
||||
|
||||
Usage: merge_runtime_sched.py [--reference-inputs] OUTPUT_TAG INPUT_TAG[@START:END] [...]
|
||||
|
||||
All inputs must use the same framebuffer and fixed 512x512 cache geometry. The
|
||||
tool renumbers epochs and emits the descriptor/parameter headers consumed by
|
||||
the shared RTL testbench and host. By default it copies generated assets under
|
||||
OUTPUT_TAG. With --reference-inputs, the host descriptor retains the original
|
||||
asset names (so repeated resident textures are staged only once), while cheap
|
||||
local hard-link aliases satisfy the shared RTL testbench's epoch-local naming
|
||||
contract.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
|
||||
|
||||
def parse_table(tag):
|
||||
path = os.path.join(DATA, f"sh3_{tag}_epochs.txt")
|
||||
meta = None
|
||||
rows = []
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
if line.startswith("META "):
|
||||
meta = line.strip()
|
||||
elif line and line[0].isdigit():
|
||||
fields = line.split()
|
||||
if len(fields) != 14:
|
||||
raise ValueError(f"{path}: expected 14 fields, got {len(fields)}")
|
||||
rows.append(fields)
|
||||
if meta is None:
|
||||
raise ValueError(f"{path}: no META row")
|
||||
return meta, rows
|
||||
|
||||
|
||||
def parse_spec(spec):
|
||||
if "@" not in spec:
|
||||
tag=spec; bounds=None
|
||||
else:
|
||||
tag,bounds=spec.rsplit("@",1)
|
||||
meta,rows=parse_table(tag)
|
||||
if bounds is not None:
|
||||
a,b=bounds.split(":",1)
|
||||
start=int(a) if a else 0; end=int(b) if b else len(rows)
|
||||
if start<0 or end<start or end>len(rows):
|
||||
raise ValueError(f"{spec}: invalid row slice for {len(rows)} rows")
|
||||
rows=rows[start:end]
|
||||
return tag,meta,rows
|
||||
|
||||
|
||||
def meta_dict(line):
|
||||
p = line.split()[1:]
|
||||
return dict(zip(p[0::2], p[1::2]))
|
||||
|
||||
|
||||
def link_or_copy(src, dst):
|
||||
"""Create a local testbench alias without duplicating large texture files."""
|
||||
try:
|
||||
os.unlink(dst)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
os.link(src, dst)
|
||||
except OSError:
|
||||
shutil.copyfile(src, dst)
|
||||
|
||||
|
||||
def write_zero_pal(dst):
|
||||
"""Satisfy the shared RTL harness for direct-color epochs.
|
||||
|
||||
Host descriptors keep '-' so no runtime CLUT upload occurs. The generic
|
||||
testbench still opens an epoch-local palette filename before TEX0.PSM is
|
||||
known; a deterministic unused zero table avoids a misleading readmem
|
||||
warning without changing the fixture contract.
|
||||
"""
|
||||
with open(dst, "w") as f:
|
||||
f.write("// unused direct-color palette placeholder\n")
|
||||
for _ in range(256):
|
||||
f.write("00000000\n")
|
||||
|
||||
|
||||
def main(argv):
|
||||
reference_inputs = "--reference-inputs" in argv
|
||||
args = [x for x in argv[1:] if x != "--reference-inputs"]
|
||||
if len(args) < 3:
|
||||
print(__doc__.strip())
|
||||
return 2
|
||||
out, tags = args[0], args[1:]
|
||||
parsed = [(spec, *parse_spec(spec)) for spec in tags]
|
||||
metas = [meta_dict(x[2]) for x in parsed]
|
||||
fixed = ("fbpxw", "fbh", "fbwords", "lpddr_tex", "tex_words", "n_beats")
|
||||
for key in fixed:
|
||||
vals = {m[key] for m in metas}
|
||||
if len(vals) != 1:
|
||||
raise ValueError(f"input fixtures disagree on {key}: {sorted(vals)}")
|
||||
|
||||
out_rows = []
|
||||
nk = 0
|
||||
for spec, tag, _meta, rows in parsed:
|
||||
for old in rows:
|
||||
oldk = int(old[0])
|
||||
tex_src = os.path.join(DATA, old[4])
|
||||
list_src = os.path.join(DATA, old[8])
|
||||
has_pal = old[12] != "-"
|
||||
pal_src = os.path.join(DATA, old[12]) if has_pal else None
|
||||
tex_alias = f"sh3_{out}{nk}_tex_lpddr.mem"
|
||||
list_alias = f"feeder_sh3_{out}{nk}.mem"
|
||||
pal_alias = f"sh3_{out}{nk}_pal.mem"
|
||||
if reference_inputs:
|
||||
tex_name, list_name, pal_name = old[4], old[8], old[12]
|
||||
assets = [(tex_src, tex_alias), (list_src, list_alias)]
|
||||
if has_pal:
|
||||
assets.append((pal_src, pal_alias))
|
||||
for src, name in assets:
|
||||
link_or_copy(src, os.path.join(DATA, name))
|
||||
if not has_pal:
|
||||
write_zero_pal(os.path.join(DATA, pal_alias))
|
||||
else:
|
||||
tex_name, list_name = tex_alias, list_alias
|
||||
pal_name = pal_alias if has_pal else "-"
|
||||
assets = [(tex_src, tex_name), (list_src, list_name)]
|
||||
if has_pal:
|
||||
assets.append((pal_src, pal_name))
|
||||
for src, name in assets:
|
||||
shutil.copyfile(src, os.path.join(DATA, name))
|
||||
if not has_pal:
|
||||
write_zero_pal(os.path.join(DATA, pal_alias))
|
||||
row = list(old)
|
||||
row[0] = str(nk)
|
||||
row[4], row[8], row[12] = tex_name, list_name, pal_name
|
||||
# A sliced input can begin with reuse=1 even though the preceding
|
||||
# output row came from another fixture and holds another texture.
|
||||
# Never carry that stale residency promise across a merge seam.
|
||||
if row[11] == "1" and (not out_rows or out_rows[-1][7] != row[7]):
|
||||
row[11] = "0"
|
||||
out_rows.append(row)
|
||||
nk += 1
|
||||
|
||||
m = metas[0]
|
||||
with open(os.path.join(DATA, f"sh3_{out}_epochs.txt"), "w") as f:
|
||||
f.write(f"# merged runtime-CLUT scheduler fixture: {tags}\n")
|
||||
f.write("# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32\n")
|
||||
f.write("META n_epochs %d fbpxw %s fbh %s fbwords %s lpddr_tex %s tex_words %s n_beats %s\n" %
|
||||
(nk, m["fbpxw"], m["fbh"], m["fbwords"], m["lpddr_tex"], m["tex_words"], m["n_beats"]))
|
||||
for row in out_rows:
|
||||
f.write(" ".join(row) + "\n")
|
||||
|
||||
first_tag=parsed[0][1]
|
||||
base_params = open(os.path.join(DATA, f"sh3_{first_tag}_params.vh")).read()
|
||||
globals_only = re.split(r"localparam int\s+EP0_NTRIS", base_params, maxsplit=1)[0]
|
||||
globals_only = re.sub(r"(localparam int N_EPOCHS\s*=\s*)\d+", rf"\g<1>{nk}", globals_only)
|
||||
with open(os.path.join(DATA, f"sh3_{out}_params.vh"), "w") as f:
|
||||
f.write(f"// merged runtime scheduler params: {tags}\n")
|
||||
f.write(globals_only)
|
||||
for k, row in enumerate(out_rows):
|
||||
f.write(f"localparam int EP{k}_NTRIS = {row[10]};\n")
|
||||
f.write(f"localparam int EP{k}_CBP = {row[3]};\n")
|
||||
f.write(f"localparam [31:0] EP{k}_CRC = 32'h{int(row[7], 16):08x};\n")
|
||||
f.write(f"localparam bit EP{k}_REUSE = 1'b{row[11]};\n")
|
||||
with open(os.path.join(DATA, f"sh3_{out}_epoch_table.vh"), "w") as f:
|
||||
f.write(f"// merged runtime scheduler table: {tags}\n")
|
||||
for k in range(nk):
|
||||
f.write(f"EP_CRC[{k}] = EP{k}_CRC; EP_REC[{k}] = EP{k}_NTRIS; EP_REUSE[{k}] = EP{k}_REUSE;\n")
|
||||
|
||||
for stem in ("bios", "payload"):
|
||||
shutil.copyfile(os.path.join(DATA, f"{stem}_sh3_{first_tag}.mem"),
|
||||
os.path.join(DATA, f"{stem}_sh3_{out}.mem"))
|
||||
print(f"[merge_sched] {tags} -> {out}: {nk} epochs ({'referenced inputs' if reference_inputs else 'copied assets'})")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Clone a runtime scheduler fixture while patching TEST.ZTST in selected epochs.
|
||||
|
||||
This is a diagnostic fixture transform. It leaves accepted generated assets
|
||||
untouched, creates cheap local aliases for the shared RTL harness, and rewrites
|
||||
only the selected feeder lists.
|
||||
|
||||
Usage: patch_runtime_sched_test.py OUT IN --epochs START:END --ztst N
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
ROOT=os.path.normpath(os.path.join(os.path.dirname(__file__),".."))
|
||||
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
|
||||
|
||||
|
||||
def link_or_copy(src,dst):
|
||||
try: os.unlink(dst)
|
||||
except FileNotFoundError: pass
|
||||
try: os.link(src,dst)
|
||||
except OSError: shutil.copyfile(src,dst)
|
||||
|
||||
|
||||
def read_table(tag):
|
||||
path=os.path.join(DATA,f"sh3_{tag}_epochs.txt")
|
||||
comments=[]; meta=None; rows=[]
|
||||
for line in open(path):
|
||||
if line.startswith("META "): meta=line.strip()
|
||||
elif line and line[0].isdigit(): rows.append(line.split())
|
||||
else: comments.append(line)
|
||||
if meta is None: raise ValueError(f"{path}: missing META")
|
||||
return comments,meta,rows
|
||||
|
||||
|
||||
def patch_list(src,dst,ztst):
|
||||
lines=open(src).readlines(); word_lines=[]
|
||||
for i,line in enumerate(lines):
|
||||
s=line.strip()
|
||||
if s and not s.startswith("//"):
|
||||
word_lines.append(i)
|
||||
if len(word_lines)<4: raise ValueError(f"{src}: feeder list shorter than TEST header")
|
||||
li=word_lines[3]; test=int(lines[li].strip(),16)
|
||||
test=(test&~(3<<17))|((ztst&3)<<17)
|
||||
lines[li]=f"{test:016x}\n"
|
||||
with open(dst,"w") as f: f.writelines(lines)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv)<7 or argv[3]!="--epochs" or argv[5]!="--ztst":
|
||||
print(__doc__.strip()); return 2
|
||||
out,src_tag=argv[1],argv[2]
|
||||
a,b=argv[4].split(":",1); start=int(a); end=int(b); ztst=int(argv[6],0)
|
||||
if ztst not in range(4): raise ValueError("ZTST must be 0..3")
|
||||
comments,meta,rows=read_table(src_tag)
|
||||
if start<0 or end<start or end>len(rows):
|
||||
raise ValueError(f"invalid epoch slice {start}:{end} for {len(rows)} rows")
|
||||
|
||||
out_rows=[]
|
||||
for row in rows:
|
||||
k=int(row[0]); old=list(row)
|
||||
tex_src=os.path.join(DATA,old[4]); list_src=os.path.join(DATA,old[8])
|
||||
tex_alias=f"sh3_{out}{k}_tex_lpddr.mem"
|
||||
list_alias=f"feeder_sh3_{out}{k}.mem"
|
||||
pal_alias=f"sh3_{out}{k}_pal.mem"
|
||||
link_or_copy(tex_src,os.path.join(DATA,tex_alias))
|
||||
if start<=k<end:
|
||||
patch_list(list_src,os.path.join(DATA,list_alias),ztst)
|
||||
else:
|
||||
link_or_copy(list_src,os.path.join(DATA,list_alias))
|
||||
if old[12]!="-":
|
||||
link_or_copy(os.path.join(DATA,old[12]),os.path.join(DATA,pal_alias))
|
||||
else:
|
||||
with open(os.path.join(DATA,pal_alias),"w") as f:
|
||||
f.write("// unused direct-color palette placeholder\n")
|
||||
f.writelines("00000000\n" for _ in range(256))
|
||||
old[4]=tex_alias; old[8]=list_alias
|
||||
old[12]=pal_alias if old[12]!="-" else "-"
|
||||
out_rows.append(old)
|
||||
|
||||
with open(os.path.join(DATA,f"sh3_{out}_epochs.txt"),"w") as f:
|
||||
f.write(f"# diagnostic clone of {src_tag}: epochs {start}:{end} TEST.ZTST={ztst}\n")
|
||||
f.write("# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32\n")
|
||||
f.write(meta+"\n")
|
||||
for row in out_rows: f.write(" ".join(row)+"\n")
|
||||
|
||||
params=open(os.path.join(DATA,f"sh3_{src_tag}_params.vh")).read()
|
||||
params=re.sub(r"^//.*\n",f"// diagnostic TEST.ZTST={ztst} clone of {src_tag}\n",params,count=1)
|
||||
with open(os.path.join(DATA,f"sh3_{out}_params.vh"),"w") as f: f.write(params)
|
||||
table=open(os.path.join(DATA,f"sh3_{src_tag}_epoch_table.vh")).read()
|
||||
with open(os.path.join(DATA,f"sh3_{out}_epoch_table.vh"),"w") as f: f.write(table)
|
||||
for stem in ("bios","payload"):
|
||||
link_or_copy(os.path.join(DATA,f"{stem}_sh3_{src_tag}.mem"),
|
||||
os.path.join(DATA,f"{stem}_sh3_{out}.mem"))
|
||||
print(f"[patch_test] {src_tag} -> {out}: epochs {start}:{end} ZTST={ztst}; {len(rows)} total")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Backfill authentic TEST_1/ZBUF_1 words into an existing scheduler fixture.
|
||||
|
||||
Ch357-Ch404 fixtures used placeholder words because the board wrapper supplied
|
||||
ZTE/ZMSK constants to the external LPDDR ROP. Ch405 carries that state from
|
||||
the feeder list, so accepted fixture geometry can be retained byte-for-byte
|
||||
while only header words 3 and 4 are restored from the capture.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, HERE)
|
||||
import gs_make_sh3_multidraw_fixture as MD
|
||||
|
||||
|
||||
def rows(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
s = line.strip()
|
||||
if s and not s.startswith("#") and not s.startswith("META"):
|
||||
fields = s.split()
|
||||
if fields[0].isdigit():
|
||||
yield int(fields[1]), fields[8]
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 3:
|
||||
raise SystemExit("usage: patch_runtime_sched_zstate.py dump.gs.zst epochs.txt [epochs.txt ...]")
|
||||
dump = argv[1]
|
||||
tables = argv[2:]
|
||||
entries = [(idx, name) for table in tables for idx, name in rows(table)]
|
||||
got, _ = MD.load_draws(dump, [idx for idx, _ in entries])
|
||||
for idx, name in entries:
|
||||
if idx not in got:
|
||||
raise SystemExit(f"missing captured draw idx{idx}")
|
||||
path = os.path.join(DATA, name)
|
||||
with open(path, encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
slots = [i for i, line in enumerate(lines)
|
||||
if line.strip() and not line.lstrip().startswith("//")]
|
||||
if len(slots) < 5:
|
||||
raise SystemExit(f"{name}: truncated staging image")
|
||||
state = got[idx]["state"]
|
||||
lines[slots[3]] = f"{state['test'] & 0xffffffffffffffff:016x}\n"
|
||||
lines[slots[4]] = f"{state['zbuf'] & 0xffffffffffffffff:016x}\n"
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
print(f"[zstate] {name}: idx{idx} TEST={state['test']:016x} ZBUF={state['zbuf']:016x}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Preview and score the Ch418 captured SH3 scanout presentation map.
|
||||
|
||||
RTL maps source_x=floor(output_x*4/5) and
|
||||
source_y=32+floor(output_y*14/15). This host utility applies the same
|
||||
integer mapping to a 640x480 framebuffer PNG, saves the expected HDMI
|
||||
presentation, and reports RGB MAE/RMSE against a 640x480 reference.
|
||||
"""
|
||||
import math
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def score(a, b):
|
||||
ae = 0
|
||||
se = 0
|
||||
n = a.width * a.height * 3
|
||||
for pa, pb in zip(a.getdata(), b.getdata()):
|
||||
for ca, cb in zip(pa, pb):
|
||||
d = int(ca) - int(cb)
|
||||
ae += abs(d)
|
||||
se += d * d
|
||||
return ae / n, math.sqrt(se / n)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 4:
|
||||
raise SystemExit("usage: preview_scanout_ch418.py <fb.png> <reference.png> <out.png>")
|
||||
src = Image.open(argv[1]).convert("RGB")
|
||||
if src.size != (640, 480):
|
||||
raise SystemExit(f"expected 640x480 framebuffer, got {src.size}")
|
||||
ref = Image.open(argv[2]).convert("RGB").resize((640, 480), Image.Resampling.BILINEAR)
|
||||
mapped = Image.new("RGB", (640, 480))
|
||||
mapped.putdata([
|
||||
src.getpixel(((x * 4) // 5, 32 + (y * 14) // 15))
|
||||
for y in range(480) for x in range(640)
|
||||
])
|
||||
mapped.save(argv[3])
|
||||
raw_mae, raw_rmse = score(src, ref)
|
||||
map_mae, map_rmse = score(mapped, ref)
|
||||
print(f"raw MAE={raw_mae:.4f} RMSE={raw_rmse:.4f}")
|
||||
print(f"mapped MAE={map_mae:.4f} RMSE={map_rmse:.4f}")
|
||||
print(f"delta MAE={map_mae-raw_mae:+.4f} RMSE={map_rmse-raw_rmse:+.4f}")
|
||||
print(f"wrote {argv[3]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Preview the exact Ch436 vertical-linear SH3 scanout map."""
|
||||
import math
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def blend(a, b, frac, denom):
|
||||
return tuple(((denom - frac) * x + frac * y + denom // 2) // denom
|
||||
for x, y in zip(a, b))
|
||||
|
||||
|
||||
def score(a, b):
|
||||
ae = 0
|
||||
se = 0
|
||||
n = a.width * a.height * 3
|
||||
for pa, pb in zip(a.getdata(), b.getdata()):
|
||||
for ca, cb in zip(pa, pb):
|
||||
d = int(ca) - int(cb)
|
||||
ae += abs(d)
|
||||
se += d * d
|
||||
return ae / n, math.sqrt(se / n)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 4:
|
||||
raise SystemExit(
|
||||
"usage: preview_scanout_ch436.py <fb.png> <reference.png> <out.png>"
|
||||
)
|
||||
|
||||
src = Image.open(argv[1]).convert("RGB")
|
||||
if src.size != (640, 480):
|
||||
raise SystemExit(f"expected 640x480 framebuffer, got {src.size}")
|
||||
ref = Image.open(argv[2]).convert("RGB").resize(
|
||||
(640, 480), Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
out = Image.new("RGB", (640, 480))
|
||||
px = out.load()
|
||||
for y in range(480):
|
||||
yn = y * 14
|
||||
y0 = 32 + yn // 15
|
||||
yf = yn % 15
|
||||
y1 = min(y0 + 1, 479)
|
||||
for x in range(640):
|
||||
x0 = (x * 4) // 5
|
||||
px[x, y] = blend(src.getpixel((x0, y0)),
|
||||
src.getpixel((x0, y1)), yf, 15)
|
||||
|
||||
out.save(argv[3])
|
||||
mae, rmse = score(out, ref)
|
||||
print(f"linear MAE={mae:.4f} RMSE={rmse:.4f}")
|
||||
print(f"wrote {argv[3]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Preview the exact Ch437 separable-linear SH3 scanout map."""
|
||||
import math
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def blend(a, b, frac, denom):
|
||||
return tuple(((denom - frac) * x + frac * y + denom // 2) // denom
|
||||
for x, y in zip(a, b))
|
||||
|
||||
|
||||
def score(a, b):
|
||||
ae = 0
|
||||
se = 0
|
||||
n = a.width * a.height * 3
|
||||
for pa, pb in zip(a.getdata(), b.getdata()):
|
||||
for ca, cb in zip(pa, pb):
|
||||
d = int(ca) - int(cb)
|
||||
ae += abs(d)
|
||||
se += d * d
|
||||
return ae / n, math.sqrt(se / n)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 4:
|
||||
raise SystemExit(
|
||||
"usage: preview_scanout_ch437.py <fb.png> <reference.png> <out.png>"
|
||||
)
|
||||
|
||||
src = Image.open(argv[1]).convert("RGB")
|
||||
if src.size != (640, 480):
|
||||
raise SystemExit(f"expected 640x480 framebuffer, got {src.size}")
|
||||
ref = Image.open(argv[2]).convert("RGB").resize(
|
||||
(640, 480), Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
out = Image.new("RGB", (640, 480))
|
||||
px = out.load()
|
||||
for y in range(480):
|
||||
yn = y * 14
|
||||
y0 = 32 + yn // 15
|
||||
yf = yn % 15
|
||||
y1 = min(y0 + 1, 479)
|
||||
for x in range(640):
|
||||
xn = x * 4
|
||||
x0 = xn // 5
|
||||
xf = xn % 5
|
||||
x1 = min(x0 + 1, 511)
|
||||
left = blend(src.getpixel((x0, y0)),
|
||||
src.getpixel((x0, y1)), yf, 15)
|
||||
right = blend(src.getpixel((x1, y0)),
|
||||
src.getpixel((x1, y1)), yf, 15)
|
||||
px[x, y] = blend(left, right, xf, 5)
|
||||
|
||||
out.save(argv[3])
|
||||
mae, rmse = score(out, ref)
|
||||
print(f"bilinear MAE={mae:.4f} RMSE={rmse:.4f}")
|
||||
print(f"wrote {argv[3]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Preview the exact Ch438 source-space 3x3 binomial scanout."""
|
||||
import math
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def binom3(a, b, c):
|
||||
return tuple((x + 2 * y + z + 2) // 4 for x, y, z in zip(a, b, c))
|
||||
|
||||
|
||||
def score(a, b):
|
||||
ae = 0
|
||||
se = 0
|
||||
n = a.width * a.height * 3
|
||||
for pa, pb in zip(a.getdata(), b.getdata()):
|
||||
for ca, cb in zip(pa, pb):
|
||||
d = int(ca) - int(cb)
|
||||
ae += abs(d)
|
||||
se += d * d
|
||||
return ae / n, math.sqrt(se / n)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 4:
|
||||
raise SystemExit(
|
||||
"usage: preview_scanout_ch438.py <fb.png> <reference.png> <out.png>"
|
||||
)
|
||||
|
||||
src = Image.open(argv[1]).convert("RGB")
|
||||
if src.size != (640, 480):
|
||||
raise SystemExit(f"expected 640x480 framebuffer, got {src.size}")
|
||||
ref = Image.open(argv[2]).convert("RGB").resize(
|
||||
(640, 480), Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
# RTL rounds after each separable pass, so mirror that order exactly.
|
||||
horizontal = Image.new("RGB", src.size)
|
||||
hp = horizontal.load()
|
||||
for y in range(480):
|
||||
for x in range(512):
|
||||
xm1 = max(0, x - 1)
|
||||
xp1 = min(511, x + 1)
|
||||
hp[x, y] = binom3(src.getpixel((xm1, y)),
|
||||
src.getpixel((x, y)),
|
||||
src.getpixel((xp1, y)))
|
||||
|
||||
filtered = Image.new("RGB", src.size)
|
||||
fp = filtered.load()
|
||||
for y in range(32, 480):
|
||||
ym1 = max(32, y - 1)
|
||||
yp1 = min(479, y + 1)
|
||||
for x in range(512):
|
||||
fp[x, y] = binom3(horizontal.getpixel((x, ym1)),
|
||||
horizontal.getpixel((x, y)),
|
||||
horizontal.getpixel((x, yp1)))
|
||||
|
||||
out = Image.new("RGB", (640, 480))
|
||||
px = out.load()
|
||||
for y in range(480):
|
||||
sy = 32 + (y * 14) // 15
|
||||
for x in range(640):
|
||||
sx = (x * 4) // 5
|
||||
px[x, y] = filtered.getpixel((sx, sy))
|
||||
|
||||
out.save(argv[3])
|
||||
mae, rmse = score(out, ref)
|
||||
print(f"binomial3x3 MAE={mae:.4f} RMSE={rmse:.4f}")
|
||||
print(f"wrote {argv[3]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,170 @@
|
||||
// retroDE_ps2 — ps2_sh3_multitex (Ch355 Brick 1 host two-group RUNTIME-STAGING flow)
|
||||
//
|
||||
// Composites two authentic SH3 draws with DIFFERENT textures/CLUTs (A=idx19562, B=idx89761) into ONE LPDDR
|
||||
// framebuffer (320x381) via scene-level texture rebind + staged-list retriggering:
|
||||
// preclear ONCE -> fill+CRC tex A -> STREAM list A -> GO A + fresh drain -> REFILL+CRC tex B -> STREAM list B ->
|
||||
// GO B + frame_drained high->low->high (anti-stale) -> enable scanout after the 2nd fresh drain.
|
||||
// Requires the frame_drained diagnostic bit (0x02C[6]) — bundled since Ch354's fit.
|
||||
//
|
||||
// Build on the board: gcc -O2 -o ps2_sh3_multitex ps2_sh3_multitex.c
|
||||
// Run (after fit+boot): sudo ./ps2_sh3_multitex sh3_mtA_tex_lpddr.mem sh3_mtB_tex_lpddr.mem feeder_sh3_mtA.mem feeder_sh3_mtB.mem
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define OFF_LPDDR_CTRL 0x018 // W: [0]arm [1]canary [2]video_src [3]scanout_lb
|
||||
#define OFF_LPDDR_FBBASE 0x01C
|
||||
#define OFF_LPDDR_STATUS 0x02C // R: [0]idle [6]frame_drained (STABLE ordered ack)
|
||||
#define OFF_LPDDR_BYTES 0x030
|
||||
#define OFF_WRADDR 0x04C
|
||||
#define OFF_WRDATA 0x050
|
||||
#define OFF_TEX_FILL 0x054 // W[0] arm fill ; R[0] fill_done [2] write_pending
|
||||
#define OFF_TEX_BEATS 0x058
|
||||
#define OFF_TEX_BYTES 0x05C
|
||||
#define OFF_TEX_RDERRS 0x068
|
||||
#define OFF_WR_ERRS 0x06C
|
||||
#define OFF_TEX_CRC 0x070
|
||||
#define OFF_STG_STATUS 0x0D8 // R[0] feeder ready ; W reset staging write address
|
||||
#define OFF_STG_LO 0x0DC // W low32 ; R current staging addr
|
||||
#define OFF_STG_HI 0x0E4 // W high32 (commits {hi,lo}, ++addr) ; R records emitted
|
||||
#define OFF_GO 0x0E8 // W[0] trigger feeder
|
||||
#define FRAME_DRAINED 0x40 // 0x02C[6]
|
||||
#define IDLE_BIT 0x01
|
||||
#define WR_PENDING 0x04 // 0x054[2]
|
||||
#define READY_BIT 0x01
|
||||
|
||||
#define N_TEX 65536 // 512x512 PSMT8 / 4
|
||||
#define TEX_BYTES 262144
|
||||
#define N_BEATS 8192
|
||||
#define STG_MAX 2048
|
||||
#define FB_WORDS (320*381) // 121920 ; bytes = 0x77100
|
||||
|
||||
typedef struct { volatile uint8_t *base; int dry; } br_t;
|
||||
static void wr32(br_t*b,int o,uint32_t v){ if(!b->dry) *(volatile uint32_t*)(b->base+o)=v; }
|
||||
static uint32_t rd32(br_t*b,int o){ return b->dry?0:*(volatile uint32_t*)(b->base+o); }
|
||||
|
||||
// NOTE: buffer must exceed the longest banner line; skip any line whose first non-space char isn't a hex digit
|
||||
// (comments/blank) — a too-small buffer that splits a long comment must NOT parse the tail as a data word.
|
||||
static int load32(const char*p, uint32_t*a, int n){
|
||||
FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;}
|
||||
int k=0; char ln[512];
|
||||
while(k<n && fgets(ln,sizeof ln,f)){ char*s=ln; while(*s==' '||*s=='\t')s++; if(!isxdigit((unsigned char)*s))continue; a[k++]=(uint32_t)strtoul(s,NULL,16);}
|
||||
fclose(f); return k;
|
||||
}
|
||||
static int load64(const char*p, uint64_t*a, int n){
|
||||
FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;}
|
||||
int k=0; char ln[512];
|
||||
while(k<n && fgets(ln,sizeof ln,f)){ char*s=ln; while(*s==' '||*s=='\t')s++; if(!isxdigit((unsigned char)*s))continue; a[k++]=(uint64_t)strtoull(s,NULL,16);}
|
||||
fclose(f); return k;
|
||||
}
|
||||
|
||||
// preclear the whole 320x381 FB once (write-probe), poll write_pending, check BRESP
|
||||
static int preclear_fb(br_t*b){
|
||||
wr32(b, OFF_WRADDR, 0);
|
||||
for (unsigned i=0;i<FB_WORDS;i++){ wr32(b, OFF_WRDATA, 0);
|
||||
if(!b->dry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} }
|
||||
uint32_t e=rd32(b,OFF_WR_ERRS);
|
||||
printf("[mt] preclear FB %u words (0..0x%x) wr_bresp_errs=%u\n", FB_WORDS, FB_WORDS*4, e);
|
||||
return e?-1:0;
|
||||
}
|
||||
// upload a texture to LPDDR (write-probe), then arm a FRESH cache fill and verify crc/beats/bytes/rd_errs.
|
||||
static int upload_and_fill(br_t*b, const char*nm, uint32_t*tex, uint32_t exp_crc){
|
||||
wr32(b, OFF_WRADDR, 0x200000u);
|
||||
for (int i=0;i<N_TEX;i++){ wr32(b, OFF_WRDATA, tex[i]);
|
||||
if(!b->dry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} }
|
||||
uint32_t werr=rd32(b,OFF_WR_ERRS);
|
||||
// FRESH fill: arm, then require fill_done low->high (cache rearm), not a lingering high.
|
||||
wr32(b, OFF_TEX_FILL, 0x1);
|
||||
if(!b->dry){ int g=0; while((rd32(b,OFF_TEX_FILL)&0x1)&&g<2000)g++; // drops (busy)
|
||||
g=0; while(!(rd32(b,OFF_TEX_FILL)&0x1)&&g<400000)g++; } // rises (done)
|
||||
uint32_t crc=rd32(b,OFF_TEX_CRC), beats=rd32(b,OFF_TEX_BEATS), bytes=rd32(b,OFF_TEX_BYTES), rderr=rd32(b,OFF_TEX_RDERRS);
|
||||
printf("[mt] tex %s: upload wr_errs=%u ; fill crc=0x%08x (exp 0x%08x) beats=%u (exp %d) bytes=%u (exp %d) rd_errs=%u\n",
|
||||
nm, werr, crc, exp_crc, beats, N_BEATS, bytes, TEX_BYTES, rderr);
|
||||
if(b->dry) return 0;
|
||||
if(werr || crc!=exp_crc || beats!=N_BEATS || bytes!=TEX_BYTES || rderr){ fprintf(stderr," FAIL tex %s fill mismatch\n", nm); return -1; }
|
||||
return 0;
|
||||
}
|
||||
static int wait_ready(br_t*b){ if(b->dry)return 0; for(int i=0;i<3000000;i++) if(rd32(b,OFF_STG_STATUS)&READY_BIT) return 0;
|
||||
fprintf(stderr," FAIL: feeder never reached C_READY\n"); return -1; }
|
||||
// stream a 64-bit staging list over the bridge; report words + the 12-bit address bound.
|
||||
static int stream_list(br_t*b, const char*nm, uint64_t*list, int nwords){
|
||||
if (nwords > 4095){ fprintf(stderr," FAIL list %s %d words exceeds 12-bit bridge addr\n", nm, nwords); return -1; }
|
||||
if (wait_ready(b)) return -1; // feeder in C_READY before staging
|
||||
wr32(b, OFF_STG_STATUS, 0); // reset staging write address to 0 (value MUST be 0 — matches ps2_feeder)
|
||||
for (int i=0;i<nwords;i++){ wr32(b, OFF_STG_LO, (uint32_t)(list[i]&0xFFFFFFFF)); wr32(b, OFF_STG_HI, (uint32_t)(list[i]>>32)); }
|
||||
if (wait_ready(b)) return -1; // staging accepted
|
||||
uint32_t addr=rd32(b,OFF_STG_LO);
|
||||
printf("[mt] staged list %s: %d words written, staged_addr=%u (exp %d, < 4096)\n", nm, nwords, addr, nwords);
|
||||
if (!b->dry && addr!=(uint32_t)nwords) fprintf(stderr," warn: staged_addr=%u != %d (addressing off)\n", addr, nwords);
|
||||
return 0;
|
||||
}
|
||||
// GO + await a fresh ordered drain. expect_stale: frame_drained is HIGH from a prior scene -> require high->low->high.
|
||||
// After the drain, the feeder returns to C_READY (whole scene drained); records_emitted then reads the tri count.
|
||||
static int go_await_drain(br_t*b, const char*nm, int exp_records, int expect_stale){
|
||||
wr32(b, OFF_GO, 0x1);
|
||||
if(b->dry){ printf("[mt] scene %s: GO (dry-run)\n", nm); return 0; }
|
||||
int g;
|
||||
if (expect_stale){ g=0; while((rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED)&&g<40000000)g++; // wait the FALL (fresh render)
|
||||
if (rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED){ fprintf(stderr," FAIL %s: frame_drained never fell — STALE drain\n", nm); return -1; } }
|
||||
g=0; while(!(rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED)&&g<40000000)g++; // wait the RISE (drained)
|
||||
if (wait_ready(b)) return -1; // feeder back to C_READY
|
||||
uint32_t fd=rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED, by=rd32(b,OFF_LPDDR_BYTES), recs=rd32(b,OFF_STG_HI);
|
||||
printf("[mt] scene %s: GO -> fresh drain frame_drained=%u, records_emitted=%u (exp %d), FB beats written=%u\n",
|
||||
nm, fd?1:0, recs, exp_records, by);
|
||||
if (fd==0 || by==0){ fprintf(stderr," FAIL %s: empty render (frame_drained=%u beats=%u)\n", nm, fd?1:0, by); return -1; }
|
||||
if (!b->dry && (int)recs!=exp_records) fprintf(stderr," warn %s: records=%u != %d\n", nm, recs, exp_records);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char**argv){
|
||||
unsigned long base=0x40000000UL; int dry=0; char*env=getenv("PS2_BRIDGE_BASE"); if(env) base=strtoul(env,NULL,0);
|
||||
const char *fa="sh3_mtA_tex_lpddr.mem",*fb="sh3_mtB_tex_lpddr.mem",*la="feeder_sh3_mtA.mem",*lb="feeder_sh3_mtB.mem";
|
||||
int ai=1;
|
||||
for (int i=1;i<argc;i++){ if(!strcmp(argv[i],"--dry-run"))dry=1;
|
||||
else if(!strcmp(argv[i],"--base")&&i+1<argc)base=strtoul(argv[++i],NULL,0);
|
||||
else if(argv[i][0]!='-'){ if(ai==1)fa=argv[i]; else if(ai==2)fb=argv[i]; else if(ai==3)la=argv[i]; else if(ai==4)lb=argv[i]; ai++; } }
|
||||
static uint32_t texA[N_TEX], texB[N_TEX]; static uint64_t listA[STG_MAX], listB[STG_MAX];
|
||||
if (load32(fa,texA,N_TEX)!=N_TEX||load32(fb,texB,N_TEX)!=N_TEX){fprintf(stderr,"texture load failed\n");return 1;}
|
||||
int na=load64(la,listA,STG_MAX), nb=load64(lb,listB,STG_MAX);
|
||||
if(na<8||nb<8){fprintf(stderr,"list load failed\n");return 1;}
|
||||
// trim trailing zero padding to the actual list length (header word0 low16 = ntris; words = 7 + ntris*9)
|
||||
int ntA=(int)(listA[0]&0xFFFF), ntB=(int)(listB[0]&0xFFFF); int nwA=7+ntA*9, nwB=7+ntB*9;
|
||||
uint32_t crcA=0,crcB=0; for(int i=0;i<N_TEX;i++){crcA+=texA[i];crcB+=texB[i];}
|
||||
printf("[mt] texA sum32=0x%08x (%d tris/%d words) texB sum32=0x%08x (%d tris/%d words) bridge 0x%lx%s\n",
|
||||
crcA,ntA,nwA, crcB,ntB,nwB, base, dry?" DRY":"");
|
||||
|
||||
br_t br={0,dry}; int fd=-1; void*map=NULL;
|
||||
if(!dry){ fd=open("/dev/mem",O_RDWR|O_SYNC); if(fd<0){perror("open /dev/mem (root?)");return 1;}
|
||||
map=mmap(NULL,0x2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,(off_t)base);
|
||||
if(map==MAP_FAILED){perror("mmap");close(fd);return 1;} br.base=map; }
|
||||
|
||||
// (1) wait feeder ready (setup + relocated-CLUT bootlet done)
|
||||
if(!dry){ int g=0; while(!(rd32(&br,OFF_STG_STATUS)&READY_BIT)&&g<20000000)g++; }
|
||||
printf("[mt] feeder ready=%d\n", dry?1:((rd32(&br,OFF_STG_STATUS)&READY_BIT)?1:0));
|
||||
int rc=0;
|
||||
wr32(&br, OFF_LPDDR_FBBASE, 0); // FB base 0
|
||||
// (2) preclear ONCE
|
||||
rc|=preclear_fb(&br);
|
||||
// (3) scene A: fill tex A, stream list A, arm writer, GO A, fresh drain (A is first -> not stale)
|
||||
rc|=upload_and_fill(&br,"A",texA,crcA);
|
||||
rc|=stream_list(&br,"A",listA,nwA);
|
||||
wr32(&br, OFF_LPDDR_CTRL, 0x1); // arm writer (video_src OFF)
|
||||
rc|=go_await_drain(&br,"A",ntA,0);
|
||||
// (4) scene B: REFILL tex B (cache rearm), stream list B, GO B, high->low->high fresh drain (stale from A)
|
||||
rc|=upload_and_fill(&br,"B",texB,crcB);
|
||||
rc|=stream_list(&br,"B",listB,nwB);
|
||||
rc|=go_await_drain(&br,"B",ntB,1);
|
||||
// (5) enable scanout ONLY after the 2nd fresh drain
|
||||
wr32(&br, OFF_LPDDR_CTRL, 0x1|0x4); // arm + video_src
|
||||
printf("[mt] video_src=1 -> HDMI sources the LPDDR line-buffer scanout (320x381, A+B composited)\n");
|
||||
|
||||
if(!dry){ munmap(map,0x2000); close(fd); }
|
||||
printf("[mt] DONE rc=%d %s\n", rc, rc?"(a gate failed above)":"(all gates passed)");
|
||||
return rc?1:0;
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
// retroDE_ps2 — ps2_sh3_sched (Ch356 N-TEXTURE SCHEDULER host, data-driven epoch descriptors)
|
||||
//
|
||||
// Generalizes Ch355's hard-coded two-group flow (ps2_sh3_multitex) to a DATA-DRIVEN scheduler that reads an epoch
|
||||
// descriptor table (sh3_sched_epochs.txt) and iterates it: N authentic SH3 draws with DIFFERENT textures/CLUTs
|
||||
// composite into ONE LPDDR framebuffer via scene-level texture rebind + staged-list retriggering. The CLUT table is
|
||||
// preloaded by the bootlet (all N relocated CLUTs) — runtime CLUT upload is OUT of scope; the scheduler SELECTS a
|
||||
// preloaded palette per epoch (via each list's TEX0 CBP).
|
||||
//
|
||||
// wait ready -> FB base 0 -> preclear ONCE -> for each epoch k:
|
||||
// upload tex_k -> LPDDR (single region) -> FRESH cache fill + verify CRC/beats/bytes/rd_errs
|
||||
// STREAM list_k over the bridge (0x0D8=0 reset, 0x0DC/0x0E4 commit) -> arm writer (k==0) -> GO
|
||||
// await FRESH ordered drain (k==0: low->high; k>0: high->low->high anti-stale) -> records == tris
|
||||
// -> enable scanout (video_src) ONLY after the LAST fresh drain.
|
||||
// Requires the frame_drained diagnostic bit (0x02C[6]).
|
||||
//
|
||||
// Build on the board: gcc -O2 -o ps2_sh3_sched ps2_sh3_sched.c
|
||||
// Run (after fit+boot): sudo ./ps2_sh3_sched (reads sh3_sched_epochs.txt in the cwd)
|
||||
// or: sudo ./ps2_sh3_sched <epochs.txt> [--datadir DIR]
|
||||
// Optional framebuffer readback:
|
||||
// sudo ./ps2_sh3_sched --zbuf sh3_zsched_epochs.txt --dump-fb sh3_zsched_board_fb.mem
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
#define OFF_LPDDR_CTRL 0x018 // W: [0]arm [1]canary [2]video_src [3]scanout_lb
|
||||
#define OFF_LPDDR_FBBASE 0x01C
|
||||
#define OFF_LPDDR_STATUS 0x02C // R: [0]idle [3]rd_pending [6]frame_drained (STABLE ordered ack)
|
||||
#define OFF_LPDDR_BYTES 0x030
|
||||
#define OFF_LPDDR_RDADDR 0x03C // W: read byte addr + trigger ; R: latched read data
|
||||
#define OFF_WRADDR 0x04C
|
||||
#define OFF_WRDATA 0x050
|
||||
#define OFF_TEX_FILL 0x054 // W[0] arm fill ; R[0] fill_done [2] write_pending
|
||||
#define OFF_TEX_BEATS 0x058
|
||||
#define OFF_TEX_BYTES 0x05C
|
||||
#define OFF_TEX_RDERRS 0x068
|
||||
#define OFF_WR_ERRS 0x06C
|
||||
#define OFF_TEX_CRC 0x070
|
||||
#define OFF_STG_STATUS 0x0D8 // R[0] feeder ready ; W reset staging write address (value MUST be 0)
|
||||
#define OFF_STG_LO 0x0DC // W low32 ; R current staging addr
|
||||
#define OFF_STG_HI 0x0E4 // W high32 (commits {hi,lo}, ++addr) ; R records emitted
|
||||
#define OFF_GO 0x0E8 // W[0] trigger feeder
|
||||
#define OFF_CLUT_CTRL 0x1E0 // R[0] busy R[1] done-toggle; W[0] commit staged palette
|
||||
#define OFF_CLUT_EXPECT 0x1E4 // W expected palette sum32; R readback
|
||||
#define OFF_CLUT_RESULT 0x1E8 // R completed palette sum32
|
||||
#define OFF_CLUT_STAGE 0x200 // W 256 x 32-bit palette entries (0x200..0x5FC)
|
||||
#define FRAME_DRAINED 0x40 // 0x02C[6]
|
||||
#define CLEAR_DONE 0x80 // 0x02C[7] — Ch357 persistent-Z preclear complete (host waits before first GO)
|
||||
#define DROPS_NONZERO 0x100 // 0x02C[8] — Ch357 sticky: a fragment was EVER dropped (fail-closed)
|
||||
#define OFF_FRAG_DROPS 0x0EC // R: Ch357 persistent-Z dropped-fragment snapshot (per-scene delta must be 0)
|
||||
#define RD_PENDING 0x08 // 0x02C[3]
|
||||
#define WR_PENDING 0x04 // 0x054[2]
|
||||
#define READY_BIT 0x01
|
||||
#define CLUT_BUSY 0x01
|
||||
#define CLUT_DONE 0x02
|
||||
|
||||
#define MAX_EPOCHS 8192 // descriptors are small; epoch payloads are streamed from disk one at a time
|
||||
#define STG_MAX 2048
|
||||
#define TEX_MAX 65536 // 512x512 PSMT8 / 4 = 65536 words
|
||||
#define MAX_SEQUENCE_SCENES 16
|
||||
|
||||
typedef struct { volatile uint8_t *base; int dry; } br_t;
|
||||
static void wr32(br_t*b,int o,uint32_t v){ if(!b->dry) *(volatile uint32_t*)(b->base+o)=v; }
|
||||
static uint32_t rd32(br_t*b,int o){ return b->dry?0:*(volatile uint32_t*)(b->base+o); }
|
||||
|
||||
// Polling the lightweight HPS bridge flat-out can consume the entire host-side
|
||||
// transaction window while a large LPDDR scene is draining. Use elapsed time,
|
||||
// not a CPU/bridge-speed-dependent read count, and yield briefly between reads.
|
||||
static int wait_status_level(br_t*b, uint32_t mask, int want_set, int timeout_ms){
|
||||
struct timespec t0, tn;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||
for(;;){
|
||||
if (!!(rd32(b,OFF_LPDDR_STATUS)&mask) == !!want_set) return 0;
|
||||
usleep(10);
|
||||
clock_gettime(CLOCK_MONOTONIC, &tn);
|
||||
int64_t elapsed_ms=(int64_t)(tn.tv_sec-t0.tv_sec)*1000 + (tn.tv_nsec-t0.tv_nsec)/1000000;
|
||||
if(elapsed_ms >= timeout_ms) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// buffer must exceed the longest banner line; skip any line whose first non-space char isn't a hex digit.
|
||||
static int load32(const char*p, uint32_t*a, int n){
|
||||
FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;}
|
||||
int k=0; char ln[512];
|
||||
while(k<n && fgets(ln,sizeof ln,f)){ char*s=ln; while(*s==' '||*s=='\t')s++; if(!isxdigit((unsigned char)*s))continue; a[k++]=(uint32_t)strtoul(s,NULL,16);}
|
||||
fclose(f); return k;
|
||||
}
|
||||
static int load64(const char*p, uint64_t*a, int n){
|
||||
FILE*f=fopen(p,"r"); if(!f){fprintf(stderr,"open %s: %s\n",p,strerror(errno));return -1;}
|
||||
int k=0; char ln[512];
|
||||
while(k<n && fgets(ln,sizeof ln,f)){ char*s=ln; while(*s==' '||*s=='\t')s++; if(!isxdigit((unsigned char)*s))continue; a[k++]=(uint64_t)strtoull(s,NULL,16);}
|
||||
fclose(f); return k;
|
||||
}
|
||||
|
||||
typedef struct { int k, idx, tbp, cbp; char tex[128], list[128], pal[128]; unsigned long lpddr; uint32_t crc, pal_crc; int words, records, reuse; } epoch_t;
|
||||
typedef struct { int n_epochs, fbpxw, fbh, fbwords, tex_words, n_beats; unsigned long lpddr_tex; epoch_t ep[MAX_EPOCHS]; } sched_t;
|
||||
|
||||
// parse the descriptor table: a META line + one row per epoch.
|
||||
static int parse_epochs(const char*path, sched_t*s){
|
||||
FILE*f=fopen(path,"r"); if(!f){fprintf(stderr,"open %s: %s\n",path,strerror(errno));return -1;}
|
||||
memset(s,0,sizeof *s); char ln[512]; int ne=0;
|
||||
while(fgets(ln,sizeof ln,f)){
|
||||
if(!strncmp(ln,"META",4)){
|
||||
char *p; if((p=strstr(ln,"n_epochs"))) sscanf(p+8," %d",&s->n_epochs);
|
||||
if((p=strstr(ln,"fbpxw"))) sscanf(p+5," %d",&s->fbpxw);
|
||||
if((p=strstr(ln,"fbh"))) sscanf(p+3," %d",&s->fbh);
|
||||
if((p=strstr(ln,"fbwords"))) sscanf(p+7," %d",&s->fbwords);
|
||||
if((p=strstr(ln,"tex_words"))) sscanf(p+9," %d",&s->tex_words);
|
||||
if((p=strstr(ln,"n_beats"))) sscanf(p+7," %d",&s->n_beats);
|
||||
if((p=strstr(ln,"lpddr_tex"))) sscanf(p+9," %lx",&s->lpddr_tex);
|
||||
continue;
|
||||
}
|
||||
char*c=ln; while(*c==' '||*c=='\t')c++; if(*c=='#'||*c=='\n'||*c==0)continue; // comment/blank
|
||||
if(ne>=MAX_EPOCHS){fprintf(stderr,"too many epochs\n");fclose(f);return -1;}
|
||||
epoch_t*e=&s->ep[ne];
|
||||
// k idx tbp cbp_reloc tex_file lpddr size crc list_file words records [reuse [pal_file pal_sum32]]
|
||||
// Ch359 — trailing EXPLICIT reuse flag (1 = texture cache already resident; SKIP upload+fill, VERIFY the
|
||||
// resident CRC register instead). Older tables (zsched/zs640) have no column -> reuse=0 (back-compatible).
|
||||
e->reuse=0; snprintf(e->pal,sizeof e->pal,"-"); e->pal_crc=0;
|
||||
int got=sscanf(ln,"%d %d %d %d %127s %lx %*d %x %127s %d %d %d %127s %x",
|
||||
&e->k,&e->idx,&e->tbp,&e->cbp,e->tex,&e->lpddr,&e->crc,e->list,&e->words,&e->records,&e->reuse,e->pal,&e->pal_crc);
|
||||
if(got!=10 && got!=11 && got!=13){fprintf(stderr,"bad epoch row (got %d): %s",got,ln);fclose(f);return -1;}
|
||||
if(e->reuse && ne==0){fprintf(stderr,"epoch 0 cannot be reuse=1 (nothing resident yet)\n");fclose(f);return -1;}
|
||||
ne++;
|
||||
}
|
||||
fclose(f);
|
||||
if(s->n_epochs==0) s->n_epochs=ne;
|
||||
if(s->n_epochs!=ne){fprintf(stderr,"META n_epochs=%d != %d rows\n",s->n_epochs,ne);return -1;}
|
||||
if(s->fbwords<=0||s->tex_words<=0||s->n_beats<=0){fprintf(stderr,"META missing fbwords/tex_words/n_beats\n");return -1;}
|
||||
return ne;
|
||||
}
|
||||
// Ch367 -- stage exactly one PSMCT32 CLUT and wait for the design-clock
|
||||
// copier's done toggle. `pal_sum` is a sum32 over the 256 host words, the
|
||||
// same simple integrity contract used by the LPDDR texture-fill gate.
|
||||
static int upload_palette(br_t*b, int k, const uint32_t *pal, uint32_t pal_sum){
|
||||
uint32_t before=rd32(b,OFF_CLUT_CTRL)&CLUT_DONE;
|
||||
if(!b->dry && (rd32(b,OFF_CLUT_CTRL)&CLUT_BUSY)){
|
||||
fprintf(stderr," FAIL epoch %d: CLUT copier busy before palette commit\n",k); return -1;
|
||||
}
|
||||
wr32(b,OFF_CLUT_EXPECT,pal_sum);
|
||||
for(int i=0;i<256;i++) wr32(b,OFF_CLUT_STAGE+i*4,pal[i]);
|
||||
wr32(b,OFF_CLUT_CTRL,1);
|
||||
if(!b->dry){
|
||||
int g=0; while(((rd32(b,OFF_CLUT_CTRL)&CLUT_DONE)==before) && g<400000) g++;
|
||||
if((rd32(b,OFF_CLUT_CTRL)&CLUT_DONE)==before){
|
||||
fprintf(stderr," FAIL epoch %d: CLUT copier completion timeout\n",k); return -1;
|
||||
}
|
||||
}
|
||||
uint32_t got=rd32(b,OFF_CLUT_RESULT);
|
||||
printf("[sched] epoch %d pal: runtime copy sum32=0x%08x (exp 0x%08x)\n",k,got,pal_sum);
|
||||
if(!b->dry && got!=pal_sum){ fprintf(stderr," FAIL epoch %d: CLUT palette sum mismatch\n",k); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int preclear_range(br_t*b, uint32_t base, int words, const char*tag){
|
||||
wr32(b, OFF_WRADDR, base); // write-probe auto-increments WRADDR per WRDATA write
|
||||
for (int i=0;i<words;i++){ wr32(b, OFF_WRDATA, 0);
|
||||
if(!b->dry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} }
|
||||
uint32_t e=rd32(b,OFF_WR_ERRS);
|
||||
printf("[sched] preclear %s %d words (0x%x..0x%x) wr_bresp_errs=%u\n", tag, words, base, base+words*4, e);
|
||||
return e?-1:0;
|
||||
}
|
||||
static int preclear_fb(br_t*b, int fbwords){ return preclear_range(b, 0, fbwords, "COLOR"); }
|
||||
static int upload_and_fill(br_t*b, int k, uint32_t*tex, int texw, unsigned long lpddr, uint32_t exp_crc, int nbeats){
|
||||
wr32(b, OFF_WRADDR, (uint32_t)lpddr);
|
||||
for (int i=0;i<texw;i++){ wr32(b, OFF_WRDATA, tex[i]);
|
||||
if(!b->dry){int g=0; while((rd32(b,OFF_TEX_FILL)&WR_PENDING)&&g<2000000)g++;} }
|
||||
uint32_t werr=rd32(b,OFF_WR_ERRS);
|
||||
wr32(b, OFF_TEX_FILL, 0x1); // FRESH fill: low->high rearm
|
||||
if(!b->dry){ int g=0; while((rd32(b,OFF_TEX_FILL)&0x1)&&g<2000)g++;
|
||||
g=0; while(!(rd32(b,OFF_TEX_FILL)&0x1)&&g<400000)g++; }
|
||||
uint32_t crc=rd32(b,OFF_TEX_CRC), beats=rd32(b,OFF_TEX_BEATS), bytes=rd32(b,OFF_TEX_BYTES), rderr=rd32(b,OFF_TEX_RDERRS);
|
||||
printf("[sched] epoch %d tex: upload wr_errs=%u ; fill crc=0x%08x (exp 0x%08x) beats=%u (exp %d) bytes=%u rd_errs=%u\n",
|
||||
k, werr, crc, exp_crc, beats, nbeats, bytes, rderr);
|
||||
if(b->dry) return 0;
|
||||
if(werr || crc!=exp_crc || beats!=(uint32_t)nbeats || rderr){ fprintf(stderr," FAIL epoch %d fill mismatch\n", k); return -1; }
|
||||
return 0;
|
||||
}
|
||||
static int wait_ready(br_t*b){ if(b->dry)return 0; for(int i=0;i<3000000;i++) if(rd32(b,OFF_STG_STATUS)&READY_BIT) return 0;
|
||||
fprintf(stderr," FAIL: feeder never reached C_READY\n"); return -1; }
|
||||
static int stream_list(br_t*b, int k, uint64_t*list, int nwords){
|
||||
if (nwords > 4095){ fprintf(stderr," FAIL epoch %d %d words exceeds 12-bit bridge addr\n", k, nwords); return -1; }
|
||||
if (wait_ready(b)) return -1;
|
||||
wr32(b, OFF_STG_STATUS, 0); // reset staging addr to 0
|
||||
for (int i=0;i<nwords;i++){ wr32(b, OFF_STG_LO, (uint32_t)(list[i]&0xFFFFFFFF)); wr32(b, OFF_STG_HI, (uint32_t)(list[i]>>32)); }
|
||||
if (wait_ready(b)) return -1;
|
||||
uint32_t addr=rd32(b,OFF_STG_LO);
|
||||
printf("[sched] epoch %d: streamed %d words, staged_addr=%u (exp %d, < 4096)\n", k, nwords, addr, nwords);
|
||||
if (!b->dry && addr!=(uint32_t)nwords) fprintf(stderr," warn epoch %d: staged_addr=%u != %d\n", k, addr, nwords);
|
||||
return 0;
|
||||
}
|
||||
// Ch357 — wait for the persistent-Z preclear (z_rmw clear_start) to finish before the FIRST GO. The host arms EARLY so
|
||||
// this overlaps the texture uploads, but it must be EXPLICITLY confirmed (a broken clear-wait was already found in sim).
|
||||
static int wait_clear_done(br_t*b){
|
||||
if(b->dry){ printf("[sched] zbuf: clear_done wait (dry-run)\n"); return 0; }
|
||||
int g=0; while(!(rd32(b,OFF_LPDDR_STATUS)&CLEAR_DONE)&&g<40000000)g++;
|
||||
if(!(rd32(b,OFF_LPDDR_STATUS)&CLEAR_DONE)){ fprintf(stderr," FAIL: persistent-Z clear_done never rose (Z preclear stuck)\n"); return -1; }
|
||||
printf("[sched] zbuf: Z preclear complete (0x02C[7]) — safe to GO\n");
|
||||
return 0;
|
||||
}
|
||||
// Ch357 — fail-closed drop gate: the per-scene snapshot delta MUST be 0 and the sticky flag MUST be clear. Any dropped
|
||||
// fragment (request-FIFO overflow) fails the run here rather than silently corrupting the frame.
|
||||
static int check_no_drops(br_t*b, int k, uint32_t before){
|
||||
if(b->dry) return 0;
|
||||
uint32_t after=rd32(b,OFF_FRAG_DROPS), st=rd32(b,OFF_LPDDR_STATUS);
|
||||
if(after!=before || (st&DROPS_NONZERO)){
|
||||
fprintf(stderr," FAIL epoch %d: %u fragment(s) DROPPED this scene (snap %u->%u, sticky=%u)\n",
|
||||
k, after-before, before, after, (st&DROPS_NONZERO)?1:0);
|
||||
return -1;
|
||||
}
|
||||
printf("[sched] epoch %d: zero fragment drops (snap=%u, sticky=0)\n", k, after);
|
||||
return 0;
|
||||
}
|
||||
static int go_await_drain(br_t*b, int k, int exp_records, int expect_stale){
|
||||
uint32_t recs_before=b->dry?0:rd32(b,OFF_STG_HI);
|
||||
uint32_t beats_before=b->dry?0:rd32(b,OFF_LPDDR_BYTES);
|
||||
wr32(b, OFF_GO, 0x1);
|
||||
if(b->dry){ printf("[sched] epoch %d: GO (dry-run)\n", k); return 0; }
|
||||
if (expect_stale && wait_status_level(b,FRAME_DRAINED,0,1000)){
|
||||
// A very short draw can clear and reassert frame_drained entirely
|
||||
// between two HPS bridge reads. Accept that missed-low case only
|
||||
// when the per-GO completion counters prove that new work finished;
|
||||
// otherwise retain the fail-closed stale-drain gate.
|
||||
uint32_t st=rd32(b,OFF_LPDDR_STATUS), recs=rd32(b,OFF_STG_HI), beats=rd32(b,OFF_LPDDR_BYTES);
|
||||
if (!(st&FRAME_DRAINED) || recs!=(uint32_t)exp_records ||
|
||||
(recs==recs_before && beats==beats_before)) {
|
||||
fprintf(stderr," FAIL epoch %d: frame_drained never fell — STALE drain"
|
||||
" (records %u->%u, FB beats %u->%u)\n",
|
||||
k,recs_before,recs,beats_before,beats);
|
||||
return -1;
|
||||
}
|
||||
printf("[sched] epoch %d: drain low pulse completed between host polls;"
|
||||
" counters advanced (records %u->%u, FB beats %u->%u)\n",
|
||||
k,recs_before,recs,beats_before,beats);
|
||||
}
|
||||
if (wait_status_level(b,FRAME_DRAINED,1,120000))
|
||||
fprintf(stderr," FAIL epoch %d: frame_drained rise timeout after 120 s\n", k);
|
||||
if (wait_ready(b)) return -1;
|
||||
uint32_t fd=rd32(b,OFF_LPDDR_STATUS)&FRAME_DRAINED, by=rd32(b,OFF_LPDDR_BYTES), recs=rd32(b,OFF_STG_HI);
|
||||
printf("[sched] epoch %d: GO -> fresh drain frame_drained=%u, records=%u (exp %d), FB beats=%u\n",
|
||||
k, fd?1:0, recs, exp_records, by);
|
||||
if (fd==0 || by==0){ fprintf(stderr," FAIL epoch %d: empty render (frame_drained=%u beats=%u)\n", k, fd?1:0, by); return -1; }
|
||||
if ((int)recs!=exp_records) fprintf(stderr," warn epoch %d: records=%u != %d\n", k, recs, exp_records);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Optional board evidence: dump the rendered linear PSMCT32 LPDDR framebuffer at base 0 before HDMI scanout is enabled.
|
||||
// This uses the same HPS read-probe contract as ps2_sh3_tex_upload.c: write byte address, wait rd_pending clear,
|
||||
// then read the latched word back from OFF_LPDDR_RDADDR.
|
||||
static int dump_fb(br_t*b, const char*path, int fbwords, int fbpxw, int fbh){
|
||||
if(!path) return 0;
|
||||
if(b->dry){ printf("[sched] dump-fb %s (%d words, dry-run)\n", path, fbwords); return 0; }
|
||||
FILE*f=fopen(path,"w");
|
||||
if(!f){ fprintf(stderr," FAIL: open dump-fb %s: %s\n", path, strerror(errno)); return -1; }
|
||||
|
||||
uint32_t sum=0, xr=0, nonzero=0;
|
||||
int minx=-1, miny=-1, maxx=-1, maxy=-1, timeouts=0;
|
||||
for(int i=0;i<fbwords;i++){
|
||||
wr32(b, OFF_LPDDR_RDADDR, (uint32_t)i*4u);
|
||||
// The bridge-to-EMIF request and return both cross clock domains. RD_PENDING
|
||||
// may assert and clear between two Linux MMIO polls, so observing its rising
|
||||
// edge is not reliable. Wait longer than the complete CDC/one-beat path, then
|
||||
// retain the pending-low timeout guard before consuming the returned word.
|
||||
usleep(10);
|
||||
int g=0; while((rd32(b,OFF_LPDDR_STATUS)&RD_PENDING) && g<1000000) g++;
|
||||
if(g==1000000) { timeouts++; continue; }
|
||||
uint32_t v=rd32(b, OFF_LPDDR_RDADDR);
|
||||
fprintf(f,"%08x\n", v);
|
||||
sum += v; xr ^= v;
|
||||
if(v){
|
||||
nonzero++;
|
||||
if(fbpxw>0){
|
||||
int x=i%fbpxw, y=i/fbpxw;
|
||||
if(minx<0 || x<minx) minx=x;
|
||||
if(maxx<0 || x>maxx) maxx=x;
|
||||
if(miny<0 || y<miny) miny=y;
|
||||
if(maxy<0 || y>maxy) maxy=y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(fclose(f)){ fprintf(stderr," FAIL: close dump-fb %s: %s\n", path, strerror(errno)); return -1; }
|
||||
printf("[sched] dump-fb: wrote %d words -> %s sum32=0x%08x xor32=0x%08x nonzero=%u",
|
||||
fbwords, path, sum, xr, nonzero);
|
||||
if(fbpxw>0 && fbh>0 && minx>=0) printf(" bounds=(%d,%d)..(%d,%d) FB=%dx%d", minx,miny,maxx,maxy,fbpxw,fbh);
|
||||
if(timeouts) printf(" rd_pending_timeouts=%d", timeouts);
|
||||
printf("\n");
|
||||
return timeouts ? -1 : 0;
|
||||
}
|
||||
|
||||
// Run one complete descriptor table as a frame. A scene boundary deliberately drops arm/video before preclear so
|
||||
// the following arm rise invalidates and clears persistent Z again; prior_scene makes the first GO demand a fresh
|
||||
// high->low->high drain rather than accepting the preceding frame's stale completion.
|
||||
static int run_scene(br_t*br, const char*epf, const char*datadir, int zbuf, const char*dump_fb_path,
|
||||
int prior_scene, int present_ms){
|
||||
static sched_t S; if (parse_epochs(epf,&S)<0) return 1;
|
||||
printf("[sched] scene %s: %d epochs, FB %d words, tex %d words/%d beats%s\n",
|
||||
epf, S.n_epochs, S.fbwords, S.tex_words, S.n_beats, prior_scene?" (fresh after prior frame)":"");
|
||||
|
||||
// Keep memory bounded as coverage scales into thousands of epochs. The
|
||||
// old [MAX_EPOCHS][TEX_MAX] preload consumed 128 MiB at 512 epochs and
|
||||
// would exceed 2 GiB at 8192. Validate with one scratch set, then reload
|
||||
// that epoch immediately before it is sent to the board.
|
||||
static uint32_t tex[TEX_MAX], pal[256]; static uint64_t list[STG_MAX];
|
||||
uint32_t last_fill_crc=0;
|
||||
for (int k=0;k<S.n_epochs;k++){
|
||||
char pt[256], pl[256]; snprintf(pt,sizeof pt,"%s/%s",datadir,S.ep[k].tex); snprintf(pl,sizeof pl,"%s/%s",datadir,S.ep[k].list);
|
||||
int n=load64(pl,list,STG_MAX); if(n<8){fprintf(stderr,"list load failed epoch %d\n",k);return 1;}
|
||||
int nt=(int)(list[0]&0xFFFF);
|
||||
int hdr_words=(list[0]&(1ULL<<34)) ? 8 : 7;
|
||||
int nw=hdr_words+nt*9;
|
||||
if(n<nw){fprintf(stderr,"list truncated epoch %d: loaded %d need %d\n",k,n,nw);return 1;}
|
||||
if (S.ep[k].reuse){
|
||||
if (S.ep[k].crc!=last_fill_crc){fprintf(stderr," FAIL epoch %d: reuse=1 but crc 0x%08x != resident 0x%08x\n",k,S.ep[k].crc,last_fill_crc);return 1;}
|
||||
printf("[sched] epoch %d: idx%d tbp%d cbp%d REUSE resident tex (crc 0x%08x) %d tris/%d words\n",
|
||||
k,S.ep[k].idx,S.ep[k].tbp,S.ep[k].cbp, S.ep[k].crc, nt,nw);
|
||||
} else {
|
||||
if (load32(pt,tex,S.tex_words)!=S.tex_words){fprintf(stderr,"tex load failed epoch %d\n",k);return 1;}
|
||||
uint32_t crc=0; for(int i=0;i<S.tex_words;i++)crc+=tex[i];
|
||||
printf("[sched] epoch %d: idx%d tbp%d cbp%d tex crc=0x%08x (exp 0x%08x) %d tris/%d words\n",
|
||||
k,S.ep[k].idx,S.ep[k].tbp,S.ep[k].cbp, crc,S.ep[k].crc, nt,nw);
|
||||
if (crc!=S.ep[k].crc){fprintf(stderr," FAIL epoch %d local CRC mismatch\n",k);return 1;}
|
||||
last_fill_crc=S.ep[k].crc;
|
||||
}
|
||||
if (strcmp(S.ep[k].pal,"-")) {
|
||||
char pp[256]; snprintf(pp,sizeof pp,"%s/%s",datadir,S.ep[k].pal);
|
||||
if(load32(pp,pal,256)!=256){fprintf(stderr,"palette load failed epoch %d\n",k);return 1;}
|
||||
uint32_t sum=0; for(int i=0;i<256;i++) sum+=pal[i];
|
||||
if(sum!=S.ep[k].pal_crc){fprintf(stderr," FAIL epoch %d local palette sum mismatch\n",k);return 1;}
|
||||
}
|
||||
if (nt!=S.ep[k].records) fprintf(stderr," warn epoch %d header tris=%d != descriptor records=%d\n",k,nt,S.ep[k].records);
|
||||
}
|
||||
|
||||
// A frame transition is an explicit ARM falling edge followed by the rise below. This keeps scanout off while
|
||||
// color/Z are cleared and guarantees zc_clear_start is a new pulse instead of relying on a stale armed state.
|
||||
wr32(br, OFF_LPDDR_CTRL, 0);
|
||||
if(!br->dry) usleep(1000);
|
||||
int rc=0;
|
||||
wr32(br, OFF_LPDDR_FBBASE, 0);
|
||||
rc|=preclear_fb(br, S.fbwords);
|
||||
if (zbuf){
|
||||
rc|=preclear_range(br, 0x140000, S.fbwords/2, "Z");
|
||||
wr32(br, OFF_LPDDR_CTRL, 0x1);
|
||||
printf("[sched] zbuf: armed early for fresh frame Z-preclear\n");
|
||||
}
|
||||
for (int k=0;k<S.n_epochs;k++){
|
||||
char pt[256], pl[256];
|
||||
snprintf(pt,sizeof pt,"%s/%s",datadir,S.ep[k].tex);
|
||||
snprintf(pl,sizeof pl,"%s/%s",datadir,S.ep[k].list);
|
||||
int n=load64(pl,list,STG_MAX);
|
||||
if(n<8){fprintf(stderr,"list reload failed epoch %d\n",k);return 1;}
|
||||
int nt=(int)(list[0]&0xFFFF);
|
||||
int nw=((list[0]&(1ULL<<34)) ? 8 : 7)+nt*9;
|
||||
if(n<nw){fprintf(stderr,"list reload truncated epoch %d: loaded %d need %d\n",k,n,nw);return 1;}
|
||||
if (S.ep[k].reuse){
|
||||
uint32_t rcrc=rd32(br,OFF_TEX_CRC), rbeats=rd32(br,OFF_TEX_BEATS);
|
||||
printf("[sched] epoch %d: REUSE resident texture (no upload/fill): crc=0x%08x (exp 0x%08x) beats=%u (exp %d)\n",
|
||||
k, rcrc, S.ep[k].crc, rbeats, S.n_beats);
|
||||
if(!br->dry && (rcrc!=S.ep[k].crc || rbeats!=(uint32_t)S.n_beats)){
|
||||
fprintf(stderr," FAIL epoch %d: resident texture cache disturbed (residency broken)\n",k); rc|=1; }
|
||||
} else {
|
||||
if(load32(pt,tex,S.tex_words)!=S.tex_words){fprintf(stderr,"tex reload failed epoch %d\n",k);return 1;}
|
||||
rc|=upload_and_fill(br,k,tex,S.tex_words,S.ep[k].lpddr,S.ep[k].crc,S.n_beats);
|
||||
}
|
||||
if (strcmp(S.ep[k].pal,"-")) {
|
||||
char pp[256]; snprintf(pp,sizeof pp,"%s/%s",datadir,S.ep[k].pal);
|
||||
if(load32(pp,pal,256)!=256){fprintf(stderr,"palette reload failed epoch %d\n",k);return 1;}
|
||||
rc|=upload_palette(br,k,pal,S.ep[k].pal_crc);
|
||||
}
|
||||
rc|=stream_list(br,k,list,nw);
|
||||
if (k==0 && !zbuf) wr32(br, OFF_LPDDR_CTRL, 0x1);
|
||||
if (zbuf && k==0) rc|=wait_clear_done(br);
|
||||
uint32_t drops0 = (zbuf && !br->dry) ? rd32(br,OFF_FRAG_DROPS) : 0;
|
||||
rc|=go_await_drain(br,k,S.ep[k].records,(k>0)||prior_scene);
|
||||
if (zbuf) rc|=check_no_drops(br,k,drops0);
|
||||
}
|
||||
rc|=dump_fb(br, dump_fb_path, S.fbwords, S.fbpxw, S.fbh);
|
||||
wr32(br, OFF_LPDDR_CTRL, 0x1|0x4);
|
||||
printf("[sched] video_src=1 -> HDMI sources the LPDDR line-buffer scanout (%d-epoch composite)\n", S.n_epochs);
|
||||
if (present_ms>0 && !br->dry){ printf("[sched] presenting for %d ms\n",present_ms); usleep((useconds_t)present_ms*1000u); }
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char**argv){
|
||||
unsigned long base=0x40000000UL; int dry=0; char*env=getenv("PS2_BRIDGE_BASE"); if(env) base=strtoul(env,NULL,0);
|
||||
const char *epf="sh3_sched_epochs.txt", *datadir=".", *dump_fb_path=NULL, *dump_prefix=NULL, *sequence=NULL;
|
||||
int loops=1, frame_ms=100;
|
||||
int zbuf=0; // Ch357 GS_SH3_LPDDR_FB_Z: also preclear the LPDDR Z region + ARM EARLY (so z_rmw's clear_start
|
||||
// Z-preclear overlaps the ~1.6ms texture uploads and completes long before the first GO — no drops).
|
||||
for (int i=1;i<argc;i++){ if(!strcmp(argv[i],"--dry-run"))dry=1;
|
||||
else if(!strcmp(argv[i],"--base")&&i+1<argc)base=strtoul(argv[++i],NULL,0);
|
||||
else if(!strcmp(argv[i],"--datadir")&&i+1<argc)datadir=argv[++i];
|
||||
else if(!strcmp(argv[i],"--zbuf"))zbuf=1;
|
||||
else if(!strcmp(argv[i],"--dump-fb")){
|
||||
dump_fb_path="board_fb.mem";
|
||||
if(i+1<argc && argv[i+1][0]!='-') dump_fb_path=argv[++i];
|
||||
}
|
||||
else if(!strncmp(argv[i],"--dump-fb=",10)){
|
||||
dump_fb_path=argv[i]+10;
|
||||
if(!*dump_fb_path) dump_fb_path="board_fb.mem";
|
||||
}
|
||||
else if(!strcmp(argv[i],"--dump-prefix")&&i+1<argc) dump_prefix=argv[++i];
|
||||
else if(!strncmp(argv[i],"--dump-prefix=",14)){
|
||||
dump_prefix=argv[i]+14;
|
||||
if(!*dump_prefix) dump_prefix="board_frame";
|
||||
}
|
||||
else if(!strcmp(argv[i],"--sequence")&&i+1<argc) sequence=argv[++i];
|
||||
else if(!strcmp(argv[i],"--loops")&&i+1<argc) loops=atoi(argv[++i]);
|
||||
else if(!strcmp(argv[i],"--frame-ms")&&i+1<argc) frame_ms=atoi(argv[++i]);
|
||||
else if(argv[i][0]!='-') epf=argv[i]; }
|
||||
|
||||
br_t br={0,dry}; int fd=-1; void*map=NULL;
|
||||
if(!dry){ fd=open("/dev/mem",O_RDWR|O_SYNC); if(fd<0){perror("open /dev/mem (root?)");return 1;}
|
||||
map=mmap(NULL,0x2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,(off_t)base);
|
||||
if(map==MAP_FAILED){perror("mmap");close(fd);return 1;} br.base=map; }
|
||||
|
||||
if(!dry){ int g=0; while(!(rd32(&br,OFF_STG_STATUS)&READY_BIT)&&g<20000000)g++; }
|
||||
printf("[sched] feeder ready=%d\n", dry?1:((rd32(&br,OFF_STG_STATUS)&READY_BIT)?1:0));
|
||||
|
||||
int rc=0;
|
||||
if(sequence){
|
||||
char seq_buf[2048], *scenes[MAX_SEQUENCE_SCENES]; int nscenes=0;
|
||||
snprintf(seq_buf,sizeof seq_buf,"%s",sequence);
|
||||
for(char *p=seq_buf;;){
|
||||
if(nscenes==MAX_SEQUENCE_SCENES){ fprintf(stderr,"--sequence supports at most %d epoch tables\n",MAX_SEQUENCE_SCENES); rc=1; break; }
|
||||
scenes[nscenes++]=p;
|
||||
char *comma=strchr(p,',');
|
||||
if(!comma) break;
|
||||
*comma=0; p=comma+1;
|
||||
}
|
||||
if(!rc && (nscenes<2 || !scenes[0][0] || !scenes[nscenes-1][0])){ fprintf(stderr,"--sequence requires two or more non-empty comma-separated epoch tables\n"); rc=1; }
|
||||
for(int s=0;!rc && s<nscenes;s++) {
|
||||
if(!scenes[s][0]){ fprintf(stderr,"--sequence contains an empty epoch-table entry\n"); rc=1; }
|
||||
}
|
||||
if(!rc && (!zbuf || loops<1 || frame_ms<0)){ fprintf(stderr,"--sequence requires --zbuf, --loops >= 1, and --frame-ms >= 0\n"); rc=1; }
|
||||
if(!rc) {
|
||||
for(int n=0;n<loops;n++){
|
||||
for(int s=0;s<nscenes;s++){
|
||||
int scene_no=n*nscenes+s, final_scene=(n==loops-1 && s==nscenes-1);
|
||||
char scene_dump[512]; const char *scene_dump_path=NULL;
|
||||
if(dump_prefix){
|
||||
snprintf(scene_dump,sizeof scene_dump,"%s_%03d.mem",dump_prefix,scene_no);
|
||||
scene_dump_path=scene_dump;
|
||||
} else if(final_scene) scene_dump_path=dump_fb_path;
|
||||
rc|=run_scene(&br,scenes[s],datadir,zbuf,scene_dump_path,scene_no>0,
|
||||
final_scene ? 0 : frame_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rc|=run_scene(&br,epf,datadir,zbuf,dump_fb_path,0,0);
|
||||
}
|
||||
|
||||
if(!dry){ munmap(map,0x2000); close(fd); }
|
||||
printf("[sched] DONE rc=%d %s\n", rc, rc?"(a gate failed above)":"(all gates passed)");
|
||||
return rc?1:0;
|
||||
}
|
||||
@@ -35,10 +35,21 @@
|
||||
#define OFF_TEX_RD_ERRS 0x068 // R: fill non-OKAY read responses (expect 0)
|
||||
#define OFF_TEX_FILL_CRC 0x070 // R: sum32 of EVERY word the cache wrote into tex_mem (must == file sum32)
|
||||
#define OFF_FEEDER_GO 0x0E8 // W[0]: trigger/retrigger the feeder
|
||||
// Ch353 — GS_SH3_LPDDR_FB (--lpddr-fb) additions: preclear the LPDDR framebuffer + drive Codex's host-start sequence.
|
||||
#define OFF_LPDDR_CTRL 0x018 // W: [0]arm [1]canary [2]video_src [3]scanout_lb (arm/base/canary latch on write)
|
||||
#define OFF_LPDDR_FB_BASE 0x01C // W: FB base byte addr (writer fb_base); set BEFORE the 0x018 arm-commit
|
||||
#define OFF_LPDDR_STATUS 0x02C // R: [0]idle (writer drained, stable post-render) [3]rd_pending
|
||||
#define OFF_LPDDR_BYTES 0x030 // R: FB writer beats written (snapped at idle) — non-zero after a real render
|
||||
#define OFF_FEEDER_STATUS 0x0D8 // R: [0] feeder control FSM in C_READY (setup + CLUT done)
|
||||
#define IDLE_BIT 0x1 // 0x02C bit0 — writer idle (TRANSIENT: pulses between feeder render batches)
|
||||
#define FRAME_DRAINED_BIT 0x40 // 0x02C bit6 — Ch353 STABLE ordered drain ack (poll THIS, not idle)
|
||||
#define READY_BIT 0x1 // 0x0D8 bit0
|
||||
|
||||
#define N_WORDS 65536 // 512*512 PSMT8 / 4
|
||||
#define TEX_BYTES 262144
|
||||
#define N_BEATS 8192 // TEX_BYTES / 32
|
||||
#define FB_WORDS 85504 // Ch353 — 256*334 PSMCT32 framebuffer (0x000000..0x0537FF)
|
||||
#define FB_BYTES 342016
|
||||
|
||||
typedef struct { volatile uint8_t *base; int dry; } bridge_t;
|
||||
static void wr32(bridge_t *b, int off, uint32_t v){ if(!b->dry) *(volatile uint32_t*)(b->base+off)=v; }
|
||||
@@ -48,7 +59,7 @@ int main(int argc, char **argv){
|
||||
unsigned long base = 0x40000000UL; // PS2 HPS-bridge base (override --base or PS2_BRIDGE_BASE)
|
||||
unsigned long lpddr_base = 0x00200000; // EMIF byte base where the texture is staged (= TEX_LPDDR_BASE RTL)
|
||||
const char *texfile = "sh3_real_tex_lpddr.mem";
|
||||
int dry=0, do_fill=1, do_retrig=1;
|
||||
int dry=0, do_fill=1, do_retrig=1, lpddr_fb=0, fb_rows=334; // fb_rows: 334 (Ch353 single) / 338 (Ch354 multi)
|
||||
char *env = getenv("PS2_BRIDGE_BASE"); if (env) base = strtoul(env,NULL,0);
|
||||
for (int i=1;i<argc;i++){
|
||||
if (!strcmp(argv[i],"--base") && i+1<argc) base = strtoul(argv[++i],NULL,0);
|
||||
@@ -56,8 +67,10 @@ int main(int argc, char **argv){
|
||||
else if (!strcmp(argv[i],"--dry-run")) dry=1;
|
||||
else if (!strcmp(argv[i],"--no-fill")) do_fill=0;
|
||||
else if (!strcmp(argv[i],"--no-retrigger")) do_retrig=0;
|
||||
else if (!strcmp(argv[i],"--lpddr-fb")) lpddr_fb=1; // Ch353 — preclear FB + Codex host-start sequence
|
||||
else if (!strcmp(argv[i],"--fb-rows") && i+1<argc) fb_rows = (int)strtoul(argv[++i],NULL,0); // Ch354 — 338 for multi
|
||||
else if (argv[i][0] != '-') texfile = argv[i];
|
||||
else { fprintf(stderr,"usage: %s [tex.mem] [--base 0x40000000] [--lpddr-base 0x200000] [--dry-run] [--no-fill] [--no-retrigger]\n", argv[0]); return 2; }
|
||||
else { fprintf(stderr,"usage: %s [tex.mem] [--base 0x40000000] [--lpddr-base 0x200000] [--dry-run] [--no-fill] [--no-retrigger] [--lpddr-fb]\n", argv[0]); return 2; }
|
||||
}
|
||||
|
||||
// ---- load the texture hex (.mem: one 32-bit word per line) ----
|
||||
@@ -88,6 +101,26 @@ int main(int argc, char **argv){
|
||||
br.base=(volatile uint8_t*)map;
|
||||
}
|
||||
|
||||
// ---- Ch353 GS_SH3_LPDDR_FB (Codex host sequence, steps 1-2): wait for the feeder to reach C_READY (setup +
|
||||
// CLUT complete; with FEEDER_AUTOSTART=0 this happens WITHOUT a boot render), then PRECLEAR the LPDDR
|
||||
// framebuffer 0x000000..0x0537FF (85504 words) via the same proven write-probe. This is the "no hardware clear
|
||||
// engine" path (Codex): ~30% more traffic than the 65536-word texture upload, poll write_pending + check BRESP. ----
|
||||
if (lpddr_fb && !dry){
|
||||
int g=0; while(!(rd32(&br,OFF_FEEDER_STATUS)&READY_BIT) && g<20000000) g++;
|
||||
printf("[ps2_sh3_tex_upload] (lpddr-fb) feeder ready=%d (setup+CLUT done, no boot render)\n",
|
||||
(rd32(&br,OFF_FEEDER_STATUS)&READY_BIT)?1:0);
|
||||
unsigned fb_words = 256u*(unsigned)fb_rows; // 256x334=85504 (Ch353) ; 256x338=86528=0x54800 (Ch354)
|
||||
wr32(&br, OFF_LPDDR_WRADDR, 0x00000000u); // FB base 0
|
||||
for (unsigned i=0;i<fb_words;i++){
|
||||
wr32(&br, OFF_LPDDR_WRDATA, 0u);
|
||||
{ int h=0; while ((rd32(&br,OFF_TEX_FILL_CTRL)&WR_PENDING_BIT) && h<2000000) h++; }
|
||||
}
|
||||
uint32_t perr = rd32(&br, OFF_LPDDR_WR_ERRS);
|
||||
printf("[ps2_sh3_tex_upload] (lpddr-fb) precleared FB %u words (%u rows, %u KiB) @0..0x%x wr_bresp_errs=%u (exp 0)\n",
|
||||
fb_words, (unsigned)fb_rows, fb_words*4/1024, fb_words*4, perr);
|
||||
if (perr) fprintf(stderr,"WARN: %u preclear BRESP errors.\n", perr);
|
||||
}
|
||||
|
||||
// ---- (1) upload: set WRADDR then stream WRDATA. CRITICAL: poll wr_busy (0x054 bit1) clear after each word
|
||||
// so the write-probe actually COMMITS before the next write — otherwise the fast mmap writes outrun the
|
||||
// CDC/AXI commit and get DROPPED (the bug: most words read back as 0). The Ch322 devmem script got away with
|
||||
@@ -138,8 +171,27 @@ int main(int argc, char **argv){
|
||||
fprintf(stderr,"WARN: cache fill_crc mismatch — tex_mem corrupt on board (NOT a divider/sampler issue).\n");
|
||||
}
|
||||
|
||||
// ---- (4) retrigger the feeder so the scene re-renders with the warm cache ----
|
||||
if (do_retrig && !dry){ wr32(&br, OFF_FEEDER_GO, 0x1); printf("[ps2_sh3_tex_upload] feeder retriggered.\n"); }
|
||||
// ---- Ch353 GS_SH3_LPDDR_FB (Codex host sequence, steps 5-8): base 0 + canary off + ARM the writer, GO, await
|
||||
// the ordered drain, then enable the scanout. The render epoch (RTL) emits exactly ONE EOF on the post-GO
|
||||
// feeder_ready rise -> ONE frame_drained, which HARD-gates the line-buffer scanout (no timeout; failure stays
|
||||
// black). Confirm the drain via LPDDR_STATUS[0] idle (stable post-render) + LPDDR_BYTES!=0 (a frame was written). ----
|
||||
if (lpddr_fb && !dry){
|
||||
wr32(&br, OFF_LPDDR_FB_BASE, 0x00000000u); // step 5: FB base 0 ...
|
||||
wr32(&br, OFF_LPDDR_CTRL, 0x1); // ... canary off, ARM the writer (commit snapshots base/arm)
|
||||
wr32(&br, OFF_FEEDER_GO, 0x1); // step 6: GO -> render epoch -> one EOF -> one frame_drained
|
||||
printf("[ps2_sh3_tex_upload] (lpddr-fb) writer armed (base 0, canary off), feeder GO issued.\n");
|
||||
// step 7: await the ORDERED drain ack (0x02C[6] frame_drained) — STABLE, asserts only after the EOF marker's
|
||||
// last BRESP. Do NOT poll [0]idle: it pulses between the feeder's render batches and reads a mid-render count.
|
||||
{ int g=0; while(!(rd32(&br,OFF_LPDDR_STATUS)&FRAME_DRAINED_BIT) && g<40000000) g++; }
|
||||
uint32_t fd = (rd32(&br,OFF_LPDDR_STATUS)&FRAME_DRAINED_BIT)?1:0, fbb = rd32(&br,OFF_LPDDR_BYTES);
|
||||
printf("[ps2_sh3_tex_upload] (lpddr-fb) drain: frame_drained=%u, FB beats written=%u (exp ~6500 for the full frame)\n", fd, fbb);
|
||||
if (!fd || fbb==0)
|
||||
fprintf(stderr,"WARN: drain not confirmed (frame_drained=%u beats=%u) — display stays BLACK (hard gate, no timeout).\n", fd, fbb);
|
||||
wr32(&br, OFF_LPDDR_CTRL, 0x1|0x4); // step 8: arm=1 + video_src=1 -> HDMI sources the LPDDR scanout
|
||||
printf("[ps2_sh3_tex_upload] (lpddr-fb) video_src=1 -> HDMI now sources the LPDDR line-buffer scanout (256x%u).\n", (unsigned)fb_rows);
|
||||
}
|
||||
// ---- (4) retrigger the feeder so the scene re-renders with the warm cache (crop profile path) ----
|
||||
else if (do_retrig && !dry){ wr32(&br, OFF_FEEDER_GO, 0x1); printf("[ps2_sh3_tex_upload] feeder retriggered.\n"); }
|
||||
|
||||
if (!dry){ munmap(map,0x1000); close(fd); }
|
||||
printf("[ps2_sh3_tex_upload] DONE — check HDMI vs the crop reference (recon/sh3_real_ref.png).\n");
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rank one-triangle runtime-scheduler epochs by 640x480 pixel coverage.
|
||||
|
||||
Usage: rank_sched_triangle_coverage.py TAG [--top N]
|
||||
|
||||
The generated feeder format stores an eight-word header followed by three
|
||||
{RGBAQ, ST, XYZ2} vertex triplets. This is a capacity preflight: it uses the
|
||||
same center-sample inside test as the fixture reference and reports epochs
|
||||
whose unthrottled fragment burst may exceed the production request FIFO.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
|
||||
|
||||
def edge(ax, ay, bx, by, px, py):
|
||||
return (px - ax) * (by - ay) - (py - ay) * (bx - ax)
|
||||
|
||||
|
||||
def coverage(vertices, width=640, height=480):
|
||||
(x0, y0), (x1, y1), (x2, y2) = vertices
|
||||
area = edge(x0, y0, x1, y1, x2, y2)
|
||||
if not area:
|
||||
return 0
|
||||
inv = 1.0 / area
|
||||
minx = max(0, int(min(x0, x1, x2)))
|
||||
maxx = min(width - 1, int(max(x0, x1, x2)) + 1)
|
||||
miny = max(0, int(min(y0, y1, y2)))
|
||||
maxy = min(height - 1, int(max(y0, y1, y2)) + 1)
|
||||
count = 0
|
||||
for py in range(miny, maxy + 1):
|
||||
cy = py + 0.5
|
||||
for px in range(minx, maxx + 1):
|
||||
cx = px + 0.5
|
||||
w0 = edge(x1, y1, x2, y2, cx, cy) * inv
|
||||
w1 = edge(x2, y2, x0, y0, cx, cy) * inv
|
||||
w2 = 1.0 - w0 - w1
|
||||
if w0 >= -0.001 and w1 >= -0.001 and w2 >= -0.001:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 2:
|
||||
print(__doc__.strip())
|
||||
return 2
|
||||
tag = argv[1]
|
||||
top = int(argv[argv.index("--top") + 1]) if "--top" in argv else 12
|
||||
rows = []
|
||||
table = os.path.join(DATA, f"sh3_{tag}_epochs.txt")
|
||||
with open(table) as source:
|
||||
for line in source:
|
||||
fields = line.split()
|
||||
if not fields or not fields[0].isdigit():
|
||||
continue
|
||||
epoch, list_name, ntris = int(fields[0]), fields[8], int(fields[10])
|
||||
words = []
|
||||
with open(os.path.join(DATA, list_name)) as feeder:
|
||||
for text in feeder:
|
||||
text = text.strip()
|
||||
if text and not text.startswith("//"):
|
||||
words.append(int(text, 16))
|
||||
total = 0
|
||||
peak = 0
|
||||
peak_vertices = None
|
||||
for ti in range(ntris):
|
||||
base = 8 + ti * 9
|
||||
xyz = [words[base + 2], words[base + 5], words[base + 8]]
|
||||
vertices = [((word >> 4) & 0xFFF, (word >> 20) & 0xFFF) for word in xyz]
|
||||
count = coverage(vertices)
|
||||
total += count
|
||||
if count > peak:
|
||||
peak = count
|
||||
peak_vertices = vertices
|
||||
rows.append((total, epoch, ntris, peak, peak_vertices))
|
||||
rows.sort(reverse=True)
|
||||
print(f"[coverage] tag={tag} epochs={len(rows)}")
|
||||
for count, epoch, ntris, peak, vertices in rows[:top]:
|
||||
print(f"[coverage] epoch={epoch:4d} pixels={count:6d} tris={ntris:3d} peak_tri={peak:6d} vertices={vertices}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compact ZSCHED trace-vs-model summary.
|
||||
|
||||
Use after tb_top_psmct32_sh3_zsched regenerates sim/traces/rtl/zsched_*.txt.
|
||||
It compares the current feeder-derived fixed-point model against the RTL issue
|
||||
and fragment traces, split by epoch.
|
||||
"""
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
sys.path.insert(0, HERE)
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
import diagnose_zsched_persp as D
|
||||
|
||||
|
||||
def load_frags(path):
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) >= 5:
|
||||
out.append((int(p[0]), int(p[1]), int(p[2]), int(p[3]), int(p[4], 16) & 0xFFFFFF))
|
||||
return out
|
||||
|
||||
|
||||
def load_issue(path):
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for ln in f:
|
||||
p = ln.split()
|
||||
if len(p) >= 7:
|
||||
out.append((int(p[0]), int(p[1]), int(p[2]), int(p[3]), int(p[4]), int(p[5]), int(p[6])))
|
||||
return out
|
||||
|
||||
|
||||
def pct(ok, n):
|
||||
return 100.0 * ok / n if n else 0.0
|
||||
|
||||
|
||||
def main(argv):
|
||||
trace_dir = os.path.join(ROOT, "sim", "traces", "rtl")
|
||||
frag_path = argv[1] if len(argv) > 1 else os.path.join(trace_dir, "zsched_frags.txt")
|
||||
issue_path = argv[2] if len(argv) > 2 else os.path.join(trace_dir, "zsched_issue.txt")
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
_draw_idxs, _tris, _fb, _owner, _emitted, _accepted, model = D.render("zsched")
|
||||
frags = load_frags(frag_path)
|
||||
issue = load_issue(issue_path)
|
||||
n = min(len(model), len(frags), len(issue))
|
||||
|
||||
by_ep = {}
|
||||
coord_miss = z_miss = 0
|
||||
for i in range(n):
|
||||
me = model[i]
|
||||
hwf = frags[i]
|
||||
hwi = issue[i]
|
||||
ep = hwf[0]
|
||||
st = by_ep.setdefault(ep, {
|
||||
"n": 0, "color": 0, "u": 0, "v": 0, "uv": 0,
|
||||
"u_ge512": 0, "v_ge512": 0,
|
||||
})
|
||||
st["n"] += 1
|
||||
if me[:3] != hwf[:3]:
|
||||
coord_miss += 1
|
||||
if abs(me[3] - hwf[3]) > 256:
|
||||
z_miss += 1
|
||||
if (me[4] & 0xFFFFFF) != hwf[4]:
|
||||
st["color"] += 1
|
||||
if me[5] != hwi[4]:
|
||||
st["u"] += 1
|
||||
if me[6] != hwi[5]:
|
||||
st["v"] += 1
|
||||
if me[5] != hwi[4] or me[6] != hwi[5]:
|
||||
st["uv"] += 1
|
||||
if hwi[4] >= 512:
|
||||
st["u_ge512"] += 1
|
||||
if hwi[5] >= 512:
|
||||
st["v_ge512"] += 1
|
||||
|
||||
print(f"[uv] model={len(model)} frags={len(frags)} issue={len(issue)} compared={n} coord_miss={coord_miss} z_gt256={z_miss}")
|
||||
for ep in sorted(by_ep):
|
||||
st = by_ep[ep]
|
||||
n_ep = st["n"]
|
||||
u_ok = n_ep - st["u"]
|
||||
v_ok = n_ep - st["v"]
|
||||
uv_ok = n_ep - st["uv"]
|
||||
c_ok = n_ep - st["color"]
|
||||
print(
|
||||
f"[uv] e{ep}: n={n_ep} "
|
||||
f"color_ok={c_ok}/{n_ep} ({pct(c_ok,n_ep):.2f}%) "
|
||||
f"u_ok={u_ok}/{n_ep} ({pct(u_ok,n_ep):.2f}%) "
|
||||
f"v_ok={v_ok}/{n_ep} ({pct(v_ok,n_ep):.2f}%) "
|
||||
f"uv_ok={uv_ok}/{n_ep} ({pct(uv_ok,n_ep):.2f}%) "
|
||||
f"rtl_u_ge512={st['u_ge512']} rtl_v_ge512={st['v_ge512']}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Score the feeder-derived fixed-point ZSCHED model with the TB oracle rule."""
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
||||
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
||||
sys.path.insert(0, HERE)
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
import diagnose_zsched_persp as D
|
||||
|
||||
|
||||
def load_mem(path):
|
||||
out = []
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
s = line.strip()
|
||||
if s and not s.startswith("//"):
|
||||
out.append(int(s, 16) & 0xFFFFFFFF)
|
||||
return out
|
||||
|
||||
|
||||
def cell(idx_words, pal, u, v):
|
||||
if not (0 <= u < D.TW and 0 <= v < D.TH):
|
||||
return None
|
||||
lin = v * D.TW + u
|
||||
ci = (idx_words[lin // 4] >> (8 * (lin % 4))) & 0xFF
|
||||
return pal[ci] & 0xFFFFFF
|
||||
|
||||
|
||||
def main(argv):
|
||||
tag = argv[1] if len(argv) > 1 else "zsched"
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
draw_idxs, _tris, fb, _owner, _emitted, _accepted, _frags = D.render(tag)
|
||||
|
||||
idx = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_idx.mem")) for e in range(len(draw_idxs))]
|
||||
pal = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_pal.mem")) for e in range(len(draw_idxs))]
|
||||
refmap = load_mem(os.path.join(DATA, f"sh3_{tag}_refmap.mem"))
|
||||
ref_ep = [load_mem(os.path.join(DATA, f"sh3_{tag}{e}_refmap.mem")) for e in range(len(draw_idxs))]
|
||||
|
||||
all_tot = all_ok = multi_tot = multi_ok = clut_bad = 0
|
||||
for o, rw in enumerate(refmap):
|
||||
if not (rw >> 31):
|
||||
continue
|
||||
fbc = fb[o] & 0xFFFFFF
|
||||
all_tot += 1
|
||||
matched = False
|
||||
for e in range(len(draw_idxs)):
|
||||
ew = ref_ep[e][o]
|
||||
if not (ew >> 31):
|
||||
continue
|
||||
tu = (ew >> 9) & 0x1FF
|
||||
tv = ew & 0x1FF
|
||||
for rad in range(2):
|
||||
for du in range(-rad, rad + 1):
|
||||
for dv in range(-rad, rad + 1):
|
||||
if max(abs(du), abs(dv)) != rad:
|
||||
continue
|
||||
if cell(idx[e], pal[e], tu + du, tv + dv) == fbc:
|
||||
matched = True
|
||||
if matched:
|
||||
all_ok += 1
|
||||
if rw & (1 << 28):
|
||||
multi_tot += 1
|
||||
if matched:
|
||||
multi_ok += 1
|
||||
if fbc != 0:
|
||||
in_pal = any((p & 0xFFFFFF) == fbc for pp in pal for p in pp)
|
||||
if not in_pal:
|
||||
clut_bad += 1
|
||||
|
||||
all_pct = 100.0 * all_ok / all_tot if all_tot else 0.0
|
||||
multi_pct = 100.0 * multi_ok / multi_tot if multi_tot else 0.0
|
||||
print(f"[model-oracle] tag={tag} ALL={all_ok}/{all_tot} ({all_pct:.2f}%) "
|
||||
f"MULTI={multi_ok}/{multi_tot} ({multi_pct:.2f}%) clut_bad={clut_bad}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
# retroDE_ps2 — copy the Ch357 (native 640x480) board-side files to the DE25.
|
||||
#
|
||||
# One scp invocation => a single password prompt. Copies to the home dir so the host runs from ~ and the descriptor
|
||||
# table (sh3_s640_epochs.txt) finds the tex/list files by their relative names.
|
||||
#
|
||||
# ./tools/scp_ch357_to_board.sh # default terasic@192.168.50.161:~
|
||||
# ./tools/scp_ch357_to_board.sh user@host:dir # override destination
|
||||
#
|
||||
# On the board afterward:
|
||||
# gcc -O2 -o ps2_sh3_sched ps2_sh3_sched.c
|
||||
# sudo ./ps2_sh3_sched sh3_s640_epochs.txt
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_s640_epochs.txt
|
||||
$DATA/sh3_s6400_tex_lpddr.mem
|
||||
$DATA/sh3_s6401_tex_lpddr.mem
|
||||
$DATA/sh3_s6402_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_s6400.mem
|
||||
$DATA/feeder_sh3_s6401.mem
|
||||
$DATA/feeder_sh3_s6402.mem
|
||||
"
|
||||
|
||||
# fail early with a clear message if anything is missing (e.g. fixtures not emitted yet)
|
||||
missing=0
|
||||
for f in $FILES; do
|
||||
if [ ! -f "$f" ]; then echo "MISSING: $f" >&2; missing=1; fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Regenerate the LOCAL 640 fixtures first:" >&2
|
||||
echo " python3 tools/gs_make_sh3_scheduler_fixture.py --fb640 --emit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying 8 files -> $DEST (one password prompt):"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
# retroDE_ps2 — copy the Ch358 persistent-Z NATIVE-640x480 (GS_SH3_LPDDR_FB_Z + GS_SH3_LPDDR_FB_640) board files to the DE25.
|
||||
#
|
||||
# This is the file set for the ZS640 profile (sh3_zs640_epochs.txt, fbpxw 640 fbh 480) — the strong-reject persistent-Z
|
||||
# scene at authentic native screen coordinates, built by the GS_SH3_LPDDR_FB_Z+_640 RBF. (NOT sh3_zsched=256x210 or the
|
||||
# paint-order sh3_s640; those are other profiles.) Copies to the home dir so the host runs from ~ and the epoch
|
||||
# descriptor finds the tex/list files by their relative names.
|
||||
#
|
||||
# ./tools/scp_zs640_to_board.sh # default terasic@192.168.50.161:~
|
||||
# ./tools/scp_zs640_to_board.sh user@host:dir # override destination
|
||||
#
|
||||
# STEP 0 — LOAD THE FRESH CORE FIRST. This script copies the 8 host/data files ONLY, NOT the RBF.
|
||||
# Deploy output_files/retroDE_ps2.core.rbf (verify its timestamp is from the signoff-clean fit!) via the usual
|
||||
# core loader and confirm it is LIVE before trusting any host run: the Ch358 core scans out 640x480 (Ch357's
|
||||
# zsched core was 256x210), and the host dump must report bounds=(407,97)..(619,306).
|
||||
#
|
||||
# On the board afterward (persistent-Z needs --zbuf: preclear the LPDDR Z region + arm EARLY):
|
||||
# gcc -O2 -o ps2_sh3_sched ps2_sh3_sched.c
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zs640_epochs.txt
|
||||
#
|
||||
# Board framebuffer readback for the Ch358 board proof (PSMCT32 linear, 640x480 for this profile):
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zs640_epochs.txt --dump-fb sh3_zs640_board_fb.mem
|
||||
# scp terasic@192.168.50.161:~/sh3_zs640_board_fb.mem sim/data/top_psmct32_raster_demo/
|
||||
# python3 tools/gs_fb_to_png.py sim/data/top_psmct32_raster_demo/sh3_zs640_board_fb.mem \
|
||||
# sim/data/top_psmct32_raster_demo/sh3_zs640_board_fb.png 640 480 3
|
||||
# make -C sim sh3_zs640_board_compare
|
||||
#
|
||||
# Exact emitted-fragment color replay (also run by sh3_zs640_board_compare):
|
||||
# python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zs640_board_fb.mem \
|
||||
# --tag zs640 --owner replay-color --frags sim/traces/rtl/zs640_frags.txt
|
||||
#
|
||||
# Regenerate the LOCAL zs640 fixtures with the signoff reference contract:
|
||||
# make -C sim sh3_zs640_fixture
|
||||
# Equivalent explicit emit:
|
||||
# python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 8634,12757,145742 --authz --fb640 --tag zs640 \
|
||||
# --pscale auto,auto,384 --xy-quant round,round,round --ref-xy-quant --emit
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zs640_epochs.txt
|
||||
$DATA/sh3_zs6400_tex_lpddr.mem
|
||||
$DATA/sh3_zs6401_tex_lpddr.mem
|
||||
$DATA/sh3_zs6402_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zs6400.mem
|
||||
$DATA/feeder_sh3_zs6401.mem
|
||||
$DATA/feeder_sh3_zs6402.mem
|
||||
"
|
||||
|
||||
missing=0
|
||||
for f in $FILES; do
|
||||
if [ ! -f "$f" ]; then echo "MISSING: $f" >&2; missing=1; fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Regenerate the LOCAL zs640 fixtures first (dump-derived, local-only):" >&2
|
||||
echo " make -C sim sh3_zs640_fixture" >&2
|
||||
echo " # or:" >&2
|
||||
echo " python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 8634,12757,145742 --authz --fb640 --tag zs640 \\" >&2
|
||||
echo " --pscale auto,auto,384 --xy-quant round,round,round --ref-xy-quant --emit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying 8 files -> $DEST (one password prompt):"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
# retroDE_ps2 — copy the Ch360 shared-texture persistent-Z NATIVE-640x480 board files to the DE25.
|
||||
#
|
||||
# This is the C12 file set (sh3_zs640c12_epochs.txt, fbpxw 640 fbh 480): twelve authentic SH3 draws in FOUR ordered,
|
||||
# shared-texture epochs at native screen coordinates. The C12 bootlet words are byte-identical to the loaded C6 RBF's
|
||||
# bootlet words, so no re-fit is needed. Copies go to the home dir so the host can resolve relative texture/list paths.
|
||||
#
|
||||
# ./tools/scp_zs640c12_to_board.sh # default terasic@192.168.50.161:~
|
||||
# ./tools/scp_zs640c12_to_board.sh user@host:dir # override destination
|
||||
#
|
||||
# STEP 0 — CONFIRM THE C6-SIGNOFF CORE IS LIVE. This script copies the 7 host/data files ONLY, NOT the RBF.
|
||||
# C12 reuses its exact bootlet memory words. The host run must report four epochs, one fill, three reuses, and zero drops.
|
||||
#
|
||||
# On the board afterward (persistent-Z needs --zbuf: preclear the LPDDR Z region + arm EARLY):
|
||||
# gcc -O2 -o ps2_sh3_sched ps2_sh3_sched.c
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zs640c12_epochs.txt
|
||||
#
|
||||
# Board framebuffer readback for the Ch360 C12 board proof (PSMCT32 linear, 640x480):
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zs640c12_epochs.txt --dump-fb sh3_zs640c12_board_fb.mem
|
||||
# scp terasic@192.168.50.161:~/sh3_zs640c12_board_fb.mem sim/data/top_psmct32_raster_demo/
|
||||
# python3 tools/gs_fb_to_png.py sim/data/top_psmct32_raster_demo/sh3_zs640c12_board_fb.mem \
|
||||
# sim/data/top_psmct32_raster_demo/sh3_zs640c12_board_fb.png 640 480 3
|
||||
# make -C sim sh3_zs640c12_board_compare
|
||||
#
|
||||
# Exact emitted-fragment color replay (also run by sh3_zs640c12_board_compare):
|
||||
# python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zs640c12_board_fb.mem \
|
||||
# --tag zs640c12 --owner replay-color --frags sim/traces/rtl/zs640c12_frags.txt
|
||||
#
|
||||
# Regenerate the LOCAL zs640c12 fixtures with the signoff reference contract:
|
||||
# make -C sim sh3_zs640c12_fixture
|
||||
# Equivalent explicit emit:
|
||||
# python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 119471,119684,119897,120110,120323,120536,120749,120962,121175,121388,121601,121814 --group-size 3 --authz --fb640 --tag zs640c12 \
|
||||
# --pscale 1024,1024,1024,1024 --xy-quant round,round,round,round --ref-xy-quant --emit
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zs640c12_epochs.txt
|
||||
$DATA/sh3_zs640c120_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zs640c120.mem
|
||||
$DATA/feeder_sh3_zs640c121.mem
|
||||
$DATA/feeder_sh3_zs640c122.mem
|
||||
$DATA/feeder_sh3_zs640c123.mem
|
||||
"
|
||||
|
||||
missing=0
|
||||
for f in $FILES; do
|
||||
if [ ! -f "$f" ]; then echo "MISSING: $f" >&2; missing=1; fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Regenerate the LOCAL zs640c12 fixtures first (dump-derived, local-only):" >&2
|
||||
echo " make -C sim sh3_zs640c12_fixture" >&2
|
||||
echo " # or:" >&2
|
||||
echo " python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 119471,119684,119897,120110,120323,120536,120749,120962,121175,121388,121601,121814 --group-size 3 --authz --fb640 --tag zs640c12 \\" >&2
|
||||
echo " --pscale 1024,1024,1024,1024 --xy-quant round,round,round,round --ref-xy-quant --emit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying 7 files -> $DEST (one password prompt):"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch361 C18 host data only. Its bootlet words are byte-identical to the loaded C6 core; no RBF is copied.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zs640c18_epochs.txt
|
||||
$DATA/sh3_zs640c180_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zs640c180.mem
|
||||
$DATA/feeder_sh3_zs640c181.mem
|
||||
$DATA/feeder_sh3_zs640c182.mem
|
||||
$DATA/feeder_sh3_zs640c183.mem
|
||||
$DATA/feeder_sh3_zs640c184.mem
|
||||
$DATA/feeder_sh3_zs640c185.mem
|
||||
"
|
||||
|
||||
missing=0
|
||||
for f in $FILES; do
|
||||
if [ ! -f "$f" ]; then echo "MISSING: $f" >&2; missing=1; fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Regenerate C18 first: make -C sim sh3_zs640c18_fixture" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying 9 C18 host/data files -> $DEST"
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
# retroDE_ps2 — copy the Ch359 shared-texture persistent-Z NATIVE-640x480 board files to the DE25.
|
||||
#
|
||||
# This is the C6 file set (sh3_zs640c6_epochs.txt, fbpxw 640 fbh 480): six authentic SH3 draws in two ordered,
|
||||
# shared-texture epochs at native screen coordinates. It requires the sh3_lpddr_fb_z640c6 RBF so the bootlet
|
||||
# preloads the matching C6 CLUT. Copies go to the home dir so the host can resolve relative texture/list paths.
|
||||
#
|
||||
# ./tools/scp_zs640c6_to_board.sh # default terasic@192.168.50.161:~
|
||||
# ./tools/scp_zs640c6_to_board.sh user@host:dir # override destination
|
||||
#
|
||||
# STEP 0 — LOAD THE FRESH CORE FIRST. This script copies the 5 host/data files ONLY, NOT the RBF.
|
||||
# Deploy output_files/retroDE_ps2.core.rbf (verify its timestamp is from the signoff-clean fit!) via the usual
|
||||
# core loader and confirm it is LIVE before trusting any host run. The C6 host dump must report
|
||||
# bounds=(84,101)..(340,301); a different bound means the wrong fixture or core is live.
|
||||
#
|
||||
# On the board afterward (persistent-Z needs --zbuf: preclear the LPDDR Z region + arm EARLY):
|
||||
# gcc -O2 -o ps2_sh3_sched ps2_sh3_sched.c
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zs640c6_epochs.txt
|
||||
#
|
||||
# Board framebuffer readback for the Ch359 C6 board proof (PSMCT32 linear, 640x480):
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zs640c6_epochs.txt --dump-fb sh3_zs640c6_board_fb.mem
|
||||
# scp terasic@192.168.50.161:~/sh3_zs640c6_board_fb.mem sim/data/top_psmct32_raster_demo/
|
||||
# python3 tools/gs_fb_to_png.py sim/data/top_psmct32_raster_demo/sh3_zs640c6_board_fb.mem \
|
||||
# sim/data/top_psmct32_raster_demo/sh3_zs640c6_board_fb.png 640 480 3
|
||||
# make -C sim sh3_zs640c6_board_compare
|
||||
#
|
||||
# Exact emitted-fragment color replay (also run by sh3_zs640c6_board_compare):
|
||||
# python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zs640c6_board_fb.mem \
|
||||
# --tag zs640c6 --owner replay-color --frags sim/traces/rtl/zs640c6_frags.txt
|
||||
#
|
||||
# Regenerate the LOCAL zs640c6 fixtures with the signoff reference contract:
|
||||
# make -C sim sh3_zs640c6_fixture
|
||||
# Equivalent explicit emit:
|
||||
# python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 119471,119684,119897,120110,120323,120536 --group-size 3 --authz --fb640 --tag zs640c6 \
|
||||
# --pscale auto,auto --xy-quant round,round --ref-xy-quant --emit
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zs640c6_epochs.txt
|
||||
$DATA/sh3_zs640c60_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zs640c60.mem
|
||||
$DATA/feeder_sh3_zs640c61.mem
|
||||
"
|
||||
|
||||
missing=0
|
||||
for f in $FILES; do
|
||||
if [ ! -f "$f" ]; then echo "MISSING: $f" >&2; missing=1; fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Regenerate the LOCAL zs640c6 fixtures first (dump-derived, local-only):" >&2
|
||||
echo " make -C sim sh3_zs640c6_fixture" >&2
|
||||
echo " # or:" >&2
|
||||
echo " python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 119471,119684,119897,120110,120323,120536 --group-size 3 --authz --fb640 --tag zs640c6 \\" >&2
|
||||
echo " --pscale auto,auto --xy-quant round,round --ref-xy-quant --emit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying 5 files -> $DEST (one password prompt):"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch364's mixed-residency scheduler host data. The fitted MT5 RBF is valid: bootlet words match exactly.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zs640m28_epochs.txt"
|
||||
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10 11; do
|
||||
FILES="$FILES $DATA/sh3_zs640m28${k}_tex_lpddr.mem $DATA/feeder_sh3_zs640m28${k}.mem"
|
||||
done
|
||||
|
||||
for f in $FILES; do
|
||||
test -f "$f" || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying Ch364 mixed-residency host/data files -> $DEST"
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Copy the Ch365 A-to-B motion scheduler inputs to the DE25 board.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zs640m28_epochs.txt $DATA/sh3_zs640b24_epochs.txt $DATA/sh3_zs640c24c_epochs.txt"
|
||||
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10 11; do
|
||||
FILES="$FILES $DATA/sh3_zs640m28${k}_tex_lpddr.mem $DATA/feeder_sh3_zs640m28${k}.mem"
|
||||
done
|
||||
|
||||
for k in 0 1 2 3 4 5 6 7; do
|
||||
FILES="$FILES $DATA/sh3_zs640b24${k}_tex_lpddr.mem $DATA/feeder_sh3_zs640b24${k}.mem"
|
||||
done
|
||||
|
||||
for k in 0 1 2 3 4 5 6 7; do
|
||||
FILES="$FILES $DATA/sh3_zs640c24c${k}_tex_lpddr.mem $DATA/feeder_sh3_zs640c24c${k}.mem"
|
||||
done
|
||||
|
||||
for f in $FILES; do
|
||||
test -f "$f" || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying Ch365 motion host/data files -> $DEST"
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch363's five-texture scheduler host data. Load the matching RBF first.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zs640mt5_epochs.txt
|
||||
$DATA/sh3_zs640mt50_tex_lpddr.mem
|
||||
$DATA/sh3_zs640mt51_tex_lpddr.mem
|
||||
$DATA/sh3_zs640mt52_tex_lpddr.mem
|
||||
$DATA/sh3_zs640mt53_tex_lpddr.mem
|
||||
$DATA/sh3_zs640mt54_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zs640mt50.mem
|
||||
$DATA/feeder_sh3_zs640mt51.mem
|
||||
$DATA/feeder_sh3_zs640mt52.mem
|
||||
$DATA/feeder_sh3_zs640mt53.mem
|
||||
$DATA/feeder_sh3_zs640mt54.mem
|
||||
"
|
||||
|
||||
for f in $FILES; do
|
||||
test -f "$f" || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying Ch363 MT5 host/data files -> $DEST"
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
# retroDE_ps2 — copy the Ch357 persistent-Z (256x210, GS_SH3_LPDDR_FB_Z) board-side files to the DE25.
|
||||
#
|
||||
# This is the file set for the ZSCHED profile (sh3_zsched_epochs.txt, fbpxw 256 fbh 210) — the one built by the
|
||||
# GS_SH3_LPDDR_FB_Z RBF. (NOT sh3_sched=384 or sh3_s640=640; those are other profiles.) Copies to the home dir so the
|
||||
# host runs from ~ and the epoch descriptor finds the tex/list files by their relative names.
|
||||
#
|
||||
# ./tools/scp_zsched_to_board.sh # default terasic@192.168.50.161:~
|
||||
# ./tools/scp_zsched_to_board.sh user@host:dir # override destination
|
||||
#
|
||||
# On the board afterward (persistent-Z needs --zbuf: preclear the LPDDR Z region + arm EARLY):
|
||||
# gcc -O2 -o ps2_sh3_sched ps2_sh3_sched.c
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zsched_epochs.txt
|
||||
#
|
||||
# Optional board framebuffer readback (PSMCT32 linear, 256x210 for this profile):
|
||||
# sudo ./ps2_sh3_sched --zbuf sh3_zsched_epochs.txt --dump-fb sh3_zsched_board_fb.mem
|
||||
# scp terasic@192.168.50.161:~/sh3_zsched_board_fb.mem sim/data/top_psmct32_raster_demo/
|
||||
# python3 tools/gs_fb_to_png.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem \
|
||||
# sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.png 256 210 3
|
||||
# make -C sim sh3_zsched_board_compare
|
||||
#
|
||||
# Exact emitted-fragment color replay (also run by sh3_zsched_board_compare):
|
||||
# python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem \
|
||||
# --owner replay-color --frags sim/traces/rtl/zsched_frags.txt
|
||||
#
|
||||
# Optional texture/refmap oracle diagnostic maps:
|
||||
# make -C sim tb_top_psmct32_sh3_zsched
|
||||
# python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem \
|
||||
# --owner replay --frags sim/traces/rtl/zsched_frags.txt --maps
|
||||
#
|
||||
# Regenerate the LOCAL zsched fixtures with the signoff reference contract:
|
||||
# make -C sim sh3_zsched_fixture
|
||||
# Equivalent explicit emit:
|
||||
# python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 8634,12757,145742 --authz --tag zsched \
|
||||
# --pscale auto,auto,384 --xy-quant round,round,round --ref-xy-quant --emit
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsched_epochs.txt
|
||||
$DATA/sh3_zsched0_tex_lpddr.mem
|
||||
$DATA/sh3_zsched1_tex_lpddr.mem
|
||||
$DATA/sh3_zsched2_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsched0.mem
|
||||
$DATA/feeder_sh3_zsched1.mem
|
||||
$DATA/feeder_sh3_zsched2.mem
|
||||
"
|
||||
|
||||
missing=0
|
||||
for f in $FILES; do
|
||||
if [ ! -f "$f" ]; then echo "MISSING: $f" >&2; missing=1; fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Regenerate the LOCAL zsched fixtures first (dump-derived, local-only):" >&2
|
||||
echo " make -C sim sh3_zsched_fixture" >&2
|
||||
echo " # or:" >&2
|
||||
echo " python3 tools/gs_make_sh3_scheduler_fixture.py --draw-list 8634,12757,145742 --authz --tag zsched \\" >&2
|
||||
echo " --pscale auto,auto,384 --xy-quant round,round,round --ref-xy-quant --emit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying 8 files -> $DEST (one password prompt):"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Ch395: wider 224139 opaque A1 upload set for ps2_sh3_sched.c.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a1_epochs.txt"
|
||||
for k in 0 1 2 3 4; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a1${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a1${k}.mem $DATA/sh3_zsrt139a1${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Ch396: wider 224139 opaque A2 upload set for ps2_sh3_sched.c.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a2_epochs.txt"
|
||||
for k in 0 1 2 3 4 5; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a2${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a2${k}.mem $DATA/sh3_zsrt139a2${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Ch397: first authentic LPDDR-alpha 224139 upload set for ps2_sh3_sched.c.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a3_epochs.txt"
|
||||
for k in 0 1 2; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a3${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a3${k}.mem $DATA/sh3_zsrt139a3${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Ch398: six authentic LPDDR-alpha draws in five ordered epochs.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a4_epochs.txt"
|
||||
for k in 0 1 2 3 4; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a4${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a4${k}.mem $DATA/sh3_zsrt139a4${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch399: full first in-bounds authentic LPDDR-alpha family, 16 epochs.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a5_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 16 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a5${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a5${k}.mem $DATA/sh3_zsrt139a5${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch400: chronological in-bounds base geometry plus bounded alpha tail.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a6_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 20 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a6${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a6${k}.mem $DATA/sh3_zsrt139a6${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch401: fuller 56-draw chronological/high-coverage static frame.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a7_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 28 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a7${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a7${k}.mem $DATA/sh3_zsrt139a7${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch402: Ch401 scene plus four authentic character epochs.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a8_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 40 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a8${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a8${k}.mem $DATA/sh3_zsrt139a8${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch403: Ch402 geometry with authentic vertex lighting modulation.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139a9_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 40 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139a9${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139a9${k}.mem $DATA/sh3_zsrt139a9${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch404: accepted Ch403 base plus chronological character-detail tail.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139b2_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 75 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139b2${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139b2${k}.mem $DATA/sh3_zsrt139b2${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch405: bounded-burst Ch404 base plus authentic Z-read-only alpha-fan overlays.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139c4_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 287 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139c4${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139c4${k}.mem $DATA/sh3_zsrt139c4${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch407: Ch405 base plus remaining supported PSMT8 alpha-fan continuation.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139d2_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 454 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139d2${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139d2${k}.mem $DATA/sh3_zsrt139d2${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch407: accepted Ch405 base plus capacity-safe PSMT8 alpha-fan continuation.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139d4_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 474 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139d4${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139d4${k}.mem $DATA/sh3_zsrt139d4${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Ch407: accepted Ch405 base plus authentic-scissor, capacity-safe continuation.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139d6_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 434 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139d6${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139d6${k}.mem $DATA/sh3_zsrt139d6${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Ch408: D6 scene plus authentic capacity-safe PSMT4 overlay. The board
|
||||
# already has all D6 assets, so upload only the reference-input merged table
|
||||
# and the 18 new E1 epochs rather than duplicating 434 texture files.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139e3_epochs.txt"
|
||||
k=0
|
||||
while [ "$k" -lt 18 ]; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139e1${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139e1${k}.mem $DATA/sh3_zsrt139e1${k}_pal.mem"
|
||||
k=$((k + 1))
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# Ch415: authentic vertex-fog fold plus indexed perspective bilinear fixture.
|
||||
# Upload each shared texture/palette asset only once, while retaining every
|
||||
# epoch's distinct feeder list.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f16_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Ch416: full native-12.4 scene fixture and runtime assets.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f17_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Ch419: accepted Ch417 scene plus the bounded rabbit/bench completion tail.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f18_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Ch424: complete 766-draw grouped scene with native 12.4 coverage.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f19_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Ch426: Ch424 scene + CT32 darken + corrected authentic-alpha fog fans.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f23_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Ch427: Ch424 + CT32 + all 93 corrected authentic-alpha fog fans.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f28_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch428: accepted Ch427 baseline plus the recovered 256x256 PSMT8
|
||||
# character-detail triangle-list family. No RBF change is required.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f33_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch429: f33 plus the native 512x1024 PSMT4 foreground-character strips.
|
||||
# Uses the timing-clean Ch423 RBF; fixture assets only.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f36_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch431: f36 plus two chronology-correct PSMCT32 character highlights.
|
||||
# Uses the timing-clean Ch423 RBF; fixture assets only.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f42_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch433: f19 draw population with per-triangle UV-error-optimized STQ scale.
|
||||
# Assets only; runs on the accepted, timing-clean Ch432 RBF.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f43_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch433: complete f42 composition with the UV-optimized f43 base slices.
|
||||
# Assets only; runs on the accepted, timing-clean Ch432 RBF.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f44_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch434: Ch433 complete composition plus authentic TME=0 additive/subtractive
|
||||
# light-volume geometry. Assets only; runs on the accepted clean Ch432 RBF.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f46_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch434: Ch433 scene plus the reconstructed current-frame light-buffer
|
||||
# composite and the existing textured-alpha tail. Assets only; unchanged RBF.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f48_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch434: topology-correct current-frame light-buffer composite plus the
|
||||
# accepted Ch433 scene and textured-alpha tail. Assets only; unchanged RBF.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f49_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch435: current-frame light-buffer composite with FRAME.FBMSK-correct alpha
|
||||
# at the static reconstruction boundary. Assets only; unchanged Ch432 RBF.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139f52_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Ch435: native FRAME.FBMSK acceptance fixture. Draw 195973 preserves the
|
||||
# destination alpha byte produced by draw 195957.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
EPOCHS="$DATA/sh3_zsrt139l3f_epochs.txt"
|
||||
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $EPOCHS"
|
||||
add_file() {
|
||||
case " $FILES " in
|
||||
*" $1 "*) ;;
|
||||
*) FILES="$FILES $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while read -r k idx tbp cbp tex lpddr size crc list words records reuse pal palsum rest; do
|
||||
case "$k" in ''|'#'*) continue ;; META) continue ;; esac
|
||||
add_file "$DATA/$tex"
|
||||
add_file "$DATA/$list"
|
||||
[ "$pal" = "-" ] || add_file "$DATA/$pal"
|
||||
done < "$EPOCHS"
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Ch394: 224139 opaque A0 upload set for ps2_sh3_sched.c.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrt139o6_epochs.txt"
|
||||
for k in 0 1 2; do
|
||||
FILES="$FILES $DATA/sh3_zsrt139o6${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrt139o6${k}.mem $DATA/sh3_zsrt139o6${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch367's 24-draw runtime-CLUT fixture. Run only after the GUI-fitted
|
||||
# core with REQ_DEPTH=2048 has a clean STA report.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrt24_epochs.txt
|
||||
"
|
||||
for k in 0 1 2 3 4 5 6 7; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrt24${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrt24${k}.mem
|
||||
$DATA/sh3_zsrt24${k}_pal.mem"
|
||||
done
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying Ch367 runtime-CLUT 24-draw fixture files -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Copy the Ch367 runtime-CLUT proof fixture. The matching GUI-fit core must
|
||||
# use the sh3_lpddr_fb_z640_runtime_clut profile before this host run.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrt3_epochs.txt
|
||||
"
|
||||
for k in 0 1 2; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrt3${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrt3${k}.mem
|
||||
$DATA/sh3_zsrt3${k}_pal.mem"
|
||||
done
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying runtime-CLUT fixture files -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch370's runtime-CLUT Frame C fixture to the DE25.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrtc24_epochs.txt
|
||||
"
|
||||
for k in 0 1 2 3 4 5 6 7; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrtc24${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrtc24${k}.mem
|
||||
$DATA/sh3_zsrtc24${k}_pal.mem"
|
||||
done
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying Ch370 runtime-CLUT Frame C files -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch374's earlier-capture eleven-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrte11_epochs.txt
|
||||
"
|
||||
for k in 0 1 2 3; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrte11${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrte11${k}.mem
|
||||
$DATA/sh3_zsrte11${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
echo "Copying Ch374 earlier-capture eleven-draw fixture -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch375's earlier-capture fourteen-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte14_epochs.txt"
|
||||
for k in 0 1 2 3 4; do FILES="$FILES $DATA/sh3_zsrte14${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte14${k}.mem $DATA/sh3_zsrte14${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch376's seventeen-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte17_epochs.txt"
|
||||
for k in 0 1 2 3 4 5; do FILES="$FILES $DATA/sh3_zsrte17${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte17${k}.mem $DATA/sh3_zsrte17${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch377's eighteen-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte18_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6; do FILES="$FILES $DATA/sh3_zsrte18${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte18${k}.mem $DATA/sh3_zsrte18${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch378's nineteen-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte19_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7; do FILES="$FILES $DATA/sh3_zsrte19${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte19${k}.mem $DATA/sh3_zsrte19${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch379's twenty-two-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte22_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8; do FILES="$FILES $DATA/sh3_zsrte22${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte22${k}.mem $DATA/sh3_zsrte22${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch380's twenty-five-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte25_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9; do FILES="$FILES $DATA/sh3_zsrte25${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte25${k}.mem $DATA/sh3_zsrte25${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch381's twenty-eight-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte28_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10; do FILES="$FILES $DATA/sh3_zsrte28${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte28${k}.mem $DATA/sh3_zsrte28${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch382's thirty-one-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte31_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10 11; do FILES="$FILES $DATA/sh3_zsrte31${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte31${k}.mem $DATA/sh3_zsrte31${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch383's thirty-four-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte34_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10 11 12; do FILES="$FILES $DATA/sh3_zsrte34${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte34${k}.mem $DATA/sh3_zsrte34${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch371's earlier-capture runtime-CLUT probe to the DE25.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrte3_epochs.txt
|
||||
"
|
||||
for k in 0 1; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrte3${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrte3${k}.mem
|
||||
$DATA/sh3_zsrte3${k}_pal.mem"
|
||||
done
|
||||
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "Copying Ch371 earlier-capture runtime-CLUT probe -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch384's forty-three-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte43_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do FILES="$FILES $DATA/sh3_zsrte43${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte43${k}.mem $DATA/sh3_zsrte43${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch385's forty-five-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrte45_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do FILES="$FILES $DATA/sh3_zsrte45${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrte45${k}.mem $DATA/sh3_zsrte45${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch372's earlier-capture six-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrte6_epochs.txt
|
||||
"
|
||||
for k in 0 1; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrte6${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrte6${k}.mem
|
||||
$DATA/sh3_zsrte6${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
echo "Copying Ch372 earlier-capture six-draw fixture -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch373's earlier-capture eight-draw runtime-CLUT fixture to the DE25.
|
||||
set -eu
|
||||
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="
|
||||
$ROOT/tools/ps2_sh3_sched.c
|
||||
$DATA/sh3_zsrte8_epochs.txt
|
||||
"
|
||||
for k in 0 1 2; do
|
||||
FILES="$FILES
|
||||
$DATA/sh3_zsrte8${k}_tex_lpddr.mem
|
||||
$DATA/feeder_sh3_zsrte8${k}.mem
|
||||
$DATA/sh3_zsrte8${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
echo "Copying Ch373 earlier-capture eight-draw fixture -> $DEST:"
|
||||
for f in $FILES; do echo " $(basename "$f")"; done
|
||||
# shellcheck disable=SC2086
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Copy Ch388's frame-3 thirty-draw runtime-CLUT composition to the DE25.
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrtf3c30_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8; do FILES="$FILES $DATA/sh3_zsrtf3c30${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrtf3c30${k}.mem $DATA/sh3_zsrtf3c30${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrtf3c34_epochs.txt"
|
||||
for k in 0 1 2 3 4 5 6 7 8 9; do FILES="$FILES $DATA/sh3_zsrtf3c34${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrtf3c34${k}.mem $DATA/sh3_zsrtf3c34${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrtf3c41_epochs.txt"
|
||||
for k in 0 1; do
|
||||
FILES="$FILES $DATA/sh3_zsrtf3c41${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrtf3c41${k}.mem $DATA/sh3_zsrtf3c41${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrtf3c49_epochs.txt"
|
||||
for k in 0 1 2 3; do
|
||||
FILES="$FILES $DATA/sh3_zsrtf3c49${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrtf3c49${k}.mem $DATA/sh3_zsrtf3c49${k}_pal.mem"
|
||||
done
|
||||
for f in $FILES; do
|
||||
[ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }
|
||||
done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrtf3c57_epochs.txt"
|
||||
for k in 0 1 2 3; do FILES="$FILES $DATA/sh3_zsrtf3c57${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrtf3c57${k}.mem $DATA/sh3_zsrtf3c57${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
DEST="${1:-terasic@192.168.50.161:~}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA="$ROOT/sim/data/top_psmct32_raster_demo"
|
||||
FILES="$ROOT/tools/ps2_sh3_sched.c $DATA/sh3_zsrtf3c57x_epochs.txt"
|
||||
for k in $(seq 0 19); do FILES="$FILES $DATA/sh3_zsrtf3c57x${k}_tex_lpddr.mem $DATA/feeder_sh3_zsrtf3c57x${k}.mem $DATA/sh3_zsrtf3c57x${k}_pal.mem"; done
|
||||
for f in $FILES; do [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; }; done
|
||||
exec scp $FILES "$DEST"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user