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:
2026-07-20 19:56:46 -04:00
parent ec82764bef
commit ba74bbd5aa
476 changed files with 696247 additions and 130119 deletions
+395
View File
@@ -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))
+62
View File
@@ -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))
+205
View File
@@ -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))
+522
View File
@@ -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))
+35
View File
@@ -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())
+123
View File
@@ -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))
+48
View File
@@ -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
+72
View File
@@ -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.")
+154
View File
@@ -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))
+395
View File
@@ -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))
+259
View File
@@ -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))
+172
View File
@@ -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))
+17 -10
View File
@@ -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
+84
View File
@@ -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))
+180
View File
@@ -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()
+16 -8
View File
@@ -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]
+16 -3
View File
@@ -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:
+25
View File
@@ -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.
+164
View File
@@ -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))
+166
View File
@@ -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))
+128
View File
@@ -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))
+178
View File
@@ -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))
+101
View File
@@ -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))
+56
View File
@@ -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))
+48
View File
@@ -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)
+58
View File
@@ -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)
+64
View File
@@ -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)
+74
View File
@@ -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)
+170
View File
@@ -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;
}
+462
View File
@@ -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;
}
+56 -4
View File
@@ -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");
+86
View File
@@ -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))
+104
View File
@@ -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))
+84
View File
@@ -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))
+45
View File
@@ -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"
+71
View File
@@ -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"
+67
View File
@@ -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"
+31
View File
@@ -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"
+67
View File
@@ -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"
+20
View File
@@ -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"
+28
View File
@@ -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"
+29
View File
@@ -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"
+70
View File
@@ -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"
+12
View File
@@ -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"
+12
View File
@@ -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"
+12
View File
@@ -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"
+12
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+14
View File
@@ -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"
+16
View File
@@ -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"
+29
View File
@@ -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"
+27
View File
@@ -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"
+27
View File
@@ -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"
+27
View File
@@ -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"
+27
View File
@@ -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"
+27
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+12
View File
@@ -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"
+28
View File
@@ -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"
+28
View File
@@ -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"
+27
View File
@@ -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"
+22
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+27
View File
@@ -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"
+10
View File
@@ -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"
+10
View File
@@ -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"
+22
View File
@@ -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"
+22
View File
@@ -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"
+10
View File
@@ -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"
+9
View File
@@ -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"
+13
View File
@@ -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"
+13
View File
@@ -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"
+9
View File
@@ -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"
+9
View File
@@ -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