#!/usr/bin/env python3 """vu_refcheck.py DECOMP_REPO IMAGE VU [vucap_before.bin vucap_after.bin] Testing aid for tools/vu_recomp.py: a plain interpreter of VU micro code that replays a state the running wrapper captured (UYA_VUCAPTURE=image,n, see host/src/gfx/vu.c) and compares its result with what the translated C produced: registers, flags, Q and data memory. It works out stalls at run time (the translator does it statically), so it also checks that model. The MAC/status flags are only compared for images that read them (the translation skips them otherwise). Needs numpy. """ import os, struct, sys import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from vu_isa import decode_pair, XYZW f32 = np.float32 np.seterr(all="ignore") def load_images(): d = open(os.path.join(sys.argv[1], "baserom", "SCES_509.16"), "rb").read() e_shoff, = struct.unpack_from("= 4 + SZ + msz + 8 else (0, 0) vf = np.frombuffer(st[0:512], dtype=np.uint32).reshape(32, 4).copy() acc = np.frombuffer(st[512:528], dtype=np.uint32).copy() q, i, p, r = struct.unpack_from("<4I", st, 528) vi = list(struct.unpack_from("<16I", st, 544)) clip, status, mac = struct.unpack_from("<3I", st, 608) return dict(pc=pc, vf=vf, acc=acc, q=q, i=i, vi=vi, clip=clip, status=status, mac=mac, mem=mem, top=top, itop=itop) def fl(u): return np.array([u], dtype=np.uint32).view(np.float32)[0] def bits(x): return int(np.array([x], dtype=np.float32).view(np.uint32)[0]) class VU: def __init__(s, st, code, vu): s.vf = st["vf"]; s.acc = st["acc"]; s.vi = st["vi"]; s.q = st["q"]; s.i = st["i"] s.clip = st["clip"]; s.status = st["status"]; s.mac = st["mac"]; s.mem = st["mem"] s.code = code; s.vu = vu; s.qmask = 0x3FF if vu else 0xFF s.cyc = 0; s.ready = {} # (reg, comp) -> cycle s.fpipe = [] # (rdy, mac, status) s.cpipe = [] s.cl_int = s.clip; s.st_int = s.status s.qp = None # (rdy, val) s.kicks = [] s.top, s.itop = st["top"], st["itop"] s.vhist = [] s.targets = set() for pc_, w_ in code.items(): l_ = decode_pair(*w_)[1] if l_ is not None and l_.name in ("b", "bal", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"): s.targets.add((pc_ + 8 + l_.imm * 8) & (0x3FF8 if vu else 0xFF8)) def F(s, r, c): return fl(int(s.vf[r][c])) def setF(s, r, c, x): if r: s.vf[r][c] = bits(x) def memw(s, qa, c): o = ((qa & s.qmask) << 4) + 4 * c; return struct.unpack_from(" maxsteps: raise Exception("too many steps") lo, hi = s.code[pc] u, l, flg, imm = decode_pair(lo, hi) # stalls reads = s.reads(u, l) t = s.cyc + 1 need = max([s.ready.get(x, 0) for x in reads] + [0]) t = max(t, need) if l is not None and l.name in ("waitq", "div", "sqrt", "rsqrt") and s.qp: t = max(t, s.qp[0]) stalled = t > s.cyc + 1 s.cyc = t s.flsync() # upper ures = s.upper(u) # lower nb = None before_vi = list(s.vi) if imm is not None: s.i = imm elif l is not None: # branch VI delay (see tools/vu_recomp.py): the branch reads the value # from before a run of writes to its register right before it patch = {} if l.name in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez", "jr", "jalr") and not stalled and pc not in s.targets: regs = [l.is_ & 15] + ([l.it & 15] if l.name in ("ibeq", "ibne") else []) for x in regs: if not x: continue h = s.vhist if not h or h[-1][0] != x or h[-1][2]: continue k = len(h) - 1 while k > 0 and len(h) - k < 4 and h[k - 1][0] == x and not h[k - 1][2] and not h[k][3] and not h[k][4]: k -= 1 patch[x] = h[k][1] break real = {x: s.vi[x] for x in patch} for x, val in patch.items(): s.vi[x] = val nb = s.lower(l, pc) for x, val in patch.items(): if s.vi[x] == val: s.vi[x] = real[x] # history of VI writes: (reg, value before, reads flags, stalled, branch target) if l is not None and imm is None: wr = vi_written(l) s.vhist.append((wr, before_vi[wr] if wr else 0, l.name in FLAGREAD, stalled, pc in s.targets)) if len(s.vhist) > 8: s.vhist.pop(0) else: s.vhist.append((0, 0, False, stalled, pc in s.targets)) if len(s.vhist) > 8: s.vhist.pop(0) # commit upper if ures: ures() for x in s.writes(u, l): s.ready[x] = s.cyc + 4 # control if ending: return pc + 8 if bpending is not None: tgt = bpending; bpending = None if flg["E"]: ending = 1 pc = tgt continue if flg["E"]: ending = 1 if nb is not None: bpending = nb pc = pc + 8 def reads(s, u, l): r = [] k = u.kind if k == "arith": for c in u.comps: r.append((u.fs, c)) if u.bc in ("x","y","z","w"): r.append((u.ft, XYZW.index(u.bc))) elif u.bc is None: r.append((u.ft, c)) elif k in ("opmula", "opmsub"): for c in range(3): r += [(u.fs, c), (u.ft, c)] elif k in ("itof", "ftoi", "abs"): r += [(u.fs, c) for c in u.comps] elif k == "clip": r += [(u.fs, 0), (u.fs, 1), (u.fs, 2), (u.ft, 3)] if l is not None: n = l.name if n in ("sq", "sqi", "sqd", "move"): r += [(l.fs, c) for c in l.comps] elif n == "mr32": r += [(l.fs, (c + 1) & 3) for c in l.comps] elif n == "mtir": r.append((l.fs, l.fsf)) elif n == "div" or n == "rsqrt": r += [(l.fs, l.fsf), (l.ft, l.ftf)] elif n == "sqrt": r.append((l.ft, l.ftf)) return [x for x in r if x[0]] def writes(s, u, l): w = [] k = u.kind if k == "arith" and not u.acc: w += [(u.fd, c) for c in u.comps] elif k == "opmsub": w += [(u.fd, c) for c in u.comps if c < 3] elif k in ("itof", "ftoi", "abs"): w += [(u.ft, c) for c in u.comps] if l is not None and l.name in ("lq", "lqi", "lqd", "move", "mr32", "mfir"): w += [(l.ft, c) for c in l.comps] return [x for x in w if x[0]] def upper(s, u): k = u.kind if k == "nop": return None if k == "arith": base = u.base if u.acc else u.name def B(c): if u.bc in ("x","y","z","w"): return s.F(u.ft, XYZW.index(u.bc)) if u.bc == "q": return fl(s.q) if u.bc == "i": return fl(s.i) return s.F(u.ft, c) res = {} for c in u.comps: a, b = s.F(u.fs, c), B(c) accv = fl(int(s.acc[c])) if base == "add": x = f32(a + b) elif base == "sub": x = f32(a - b) elif base == "mul": x = f32(a * b) elif base == "madd": x = f32(accv + f32(a * b)) elif base == "msub": x = f32(accv - f32(a * b)) elif base == "max": x = np.fmax(a, b) elif base == "mini": x = np.fmin(a, b) res[c] = x def commit(): for c, x in res.items(): if u.acc: s.acc[c] = bits(x) elif u.fd: s.vf[u.fd][c] = bits(x) if base in ("add", "sub", "mul", "madd", "msub"): s.push_flags(res) return commit if k in ("opmula", "opmsub"): ex = {0: (1, 2), 1: (2, 0), 2: (0, 1)} res = {} for c in [c for c in u.comps if c < 3]: a, b = ex[c] pr = f32(s.F(u.fs, a) * s.F(u.ft, b)) res[c] = pr if k == "opmula" else f32(fl(int(s.acc[c])) - pr) def commit(): for c, x in res.items(): if k == "opmula": s.acc[c] = bits(x) elif u.fd: s.vf[u.fd][c] = bits(x) s.push_flags(res) return commit if k == "itof": res = {c: bits(f32(np.int32(np.uint32(s.vf[u.fs][c]).view(np.int32)) / f32(1 << u.shift))) for c in u.comps} def commit(): for c, x in res.items(): if u.ft: s.vf[u.ft][c] = x return commit if k == "ftoi": res = {} for c in u.comps: x = float(s.F(u.fs, c)) * (1 << u.shift) x = f32(x) if x != x: v = 0 elif x >= 2147483647.0: v = 0x7FFFFFFF elif x <= -2147483648.0: v = -0x80000000 else: v = int(x) res[c] = v & 0xFFFFFFFF def commit(): for c, x in res.items(): if u.ft: s.vf[u.ft][c] = x return commit if k == "abs": res = {c: int(s.vf[u.fs][c]) & 0x7FFFFFFF for c in u.comps} def commit(): for c, x in res.items(): if u.ft: s.vf[u.ft][c] = x return commit if k == "clip": w = abs(s.F(u.ft, 3)); x, y, z = s.F(u.fs, 0), s.F(u.fs, 1), s.F(u.fs, 2) f = (x > w) | ((x < -w) << 1) | ((y > w) << 2) | ((y < -w) << 3) | ((z > w) << 4) | ((z < -w) << 5) def commit(): s.cl_int = ((s.cl_int << 6) | int(f)) & 0xFFFFFF s.cpipe.append((s.cyc + 4, s.cl_int)) return commit raise Exception("upper " + u.name) def push_flags(s, res): mac = 0 for c, x in res.items(): b = bits(x) if (b & 0x7FFFFFFF) == 0: mac |= 8 >> c if b >> 31: mac |= 0x80 >> c st = (s.st_int & 0xFC0) | (1 if mac & 0xF else 0) | (2 if mac & 0xF0 else 0) st |= (st & 3) << 6 s.st_int = st s.fpipe.append((s.cyc + 4, mac, st)) def lower(s, l, pc): n = l.name; vi = s.vi V = lambda r: vi[r & 15] if r & 15 else 0 if n == "move": vals = [int(s.vf[l.fs][c]) for c in range(4)] for c in l.comps: if l.ft: s.vf[l.ft][c] = vals[c] elif n == "mr32": vals = [int(s.vf[l.fs][(c + 1) & 3]) for c in range(4)] for c in l.comps: if l.ft: s.vf[l.ft][c] = vals[c] elif n in ("lq", "lqi", "lqd"): if n == "lqd": s.setvi(l.is_, V(l.is_) - 1) a = V(l.is_) + (l.imm if n == "lq" else 0) for c in l.comps: if l.ft: s.vf[l.ft][c] = s.memw(a, c) if n == "lqi": s.setvi(l.is_, V(l.is_) + 1) elif n in ("sq", "sqi", "sqd"): if n == "sqd": s.setvi(l.it, V(l.it) - 1) a = V(l.it) + (l.imm if n == "sq" else 0) for c in l.comps: s.setmem(a, c, int(s.vf[l.fs][c])) if n == "sqi": s.setvi(l.it, V(l.it) + 1) elif n in ("ilw", "ilwr"): a = V(l.is_) + (l.imm if n == "ilw" else 0) s.setvi(l.it, s.memw(a, l.comps[0])) elif n in ("isw", "iswr"): a = V(l.is_) + (l.imm if n == "isw" else 0) for c in l.comps: s.setmem(a, c, V(l.it)) elif n == "iadd": s.setvi(l.id_, V(l.is_) + V(l.it)) elif n == "isub": s.setvi(l.id_, V(l.is_) - V(l.it)) elif n == "iand": s.setvi(l.id_, V(l.is_) & V(l.it)) elif n == "ior": s.setvi(l.id_, V(l.is_) | V(l.it)) elif n == "iaddi": s.setvi(l.it, V(l.is_) + l.imm) elif n == "iaddiu": s.setvi(l.it, V(l.is_) + l.imm) elif n == "isubiu": s.setvi(l.it, V(l.is_) - l.imm) elif n == "mtir": s.setvi(l.it, int(s.vf[l.fs][l.fsf])) elif n == "mfir": v = V(l.is_); v = v - 0x10000 if v & 0x8000 else v for c in l.comps: if l.ft: s.vf[l.ft][c] = v & 0xFFFFFFFF elif n in ("div", "sqrt", "rsqrt"): if s.qp: s.q = s.qp[1]; s.qp = None if n == "div": a, b = s.F(l.fs, l.fsf), s.F(l.ft, l.ftf) v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if (bits(a) ^ bits(b)) & 0x80000000 else 0x7F7FFFFF) s.qp = (s.cyc + 7, v) elif n == "sqrt": s.qp = (s.cyc + 7, bits(f32(np.sqrt(abs(s.F(l.ft, l.ftf)))))) else: a, b = s.F(l.fs, l.fsf), f32(np.sqrt(abs(s.F(l.ft, l.ftf)))) v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if bits(a) & 0x80000000 else 0x7F7FFFFF) s.qp = (s.cyc + 13, v) elif n == "waitq": pass elif n == "fcand": s.setvi(1, 1 if (s.clip & l.imm) else 0) elif n == "fcor": s.setvi(1, 1 if ((s.clip | l.imm) & 0xFFFFFF) == 0xFFFFFF else 0) elif n == "fceq": s.setvi(1, 1 if (s.clip & 0xFFFFFF) == l.imm else 0) elif n == "fcset": s.cpipe = []; s.clip = s.cl_int = l.imm elif n == "fcget": s.setvi(l.it, s.clip & 0xFFF) elif n == "fsand": s.setvi(l.it, s.status & l.imm) elif n == "fmand": s.setvi(l.it, V(l.is_) & s.mac) elif n == "fmor": s.setvi(l.it, V(l.is_) | s.mac) elif n == "xgkick": s.kicks.append(V(l.is_) & 0x3FF) elif n == "xtop": s.setvi(l.it, s.top) elif n == "xitop": s.setvi(l.it, s.itop) elif n == "b": return (pc + 8 + l.imm * 8) & (0x3FF8 if s.vu else 0xFF8) elif n == "bal": s.setvi(l.it, (pc + 16) // 8); return (pc + 8 + l.imm * 8) elif n == "jr": return (V(l.is_) * 8) elif n == "jalr": t = V(l.is_) * 8; s.setvi(l.it, (pc + 16) // 8); return t elif n in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"): a, b = V(l.it), V(l.is_) sb = b - 0x10000 if b & 0x8000 else b c = {"ibeq": a == b, "ibne": a != b, "ibltz": sb < 0, "ibgtz": sb > 0, "iblez": sb <= 0, "ibgez": sb >= 0}[n] if c: return pc + 8 + l.imm * 8 else: raise Exception("lower " + n) return None def main(): g, vu = sys.argv[2], int(sys.argv[3]) bp = sys.argv[4] if len(sys.argv) > 4 else "vucap_before.bin" ap = sys.argv[5] if len(sys.argv) > 5 else "vucap_after.bin" code = load_images()[g] reads_flags = any(decode_pair(*w)[1] is not None and decode_pair(*w)[1].name in ("fsand", "fsor", "fseq", "fmand", "fmor", "fmeq") for w in code.values()) st = parse(bp, vu); af = parse(ap, vu) m = VU(st, code, vu) m.run(st["pc"]) while m.fpipe: _, m.mac, m.status = m.fpipe.pop(0) while m.cpipe: _, m.clip = m.cpipe.pop(0) if m.qp: m.q = m.qp[1] diffs = 0 for r in range(32): for c in range(4): if int(m.vf[r][c]) != int(af["vf"][r][c]): diffs += 1 if diffs < 30: print("vf%d.%s ref %08X C %08X" % (r, XYZW[c], int(m.vf[r][c]), int(af["vf"][r][c]))) for r in range(16): if m.vi[r] != af["vi"][r]: diffs += 1; print("vi%d ref %04X C %04X" % (r, m.vi[r], af["vi"][r])) for c in range(4): if int(m.acc[c]) != int(af["acc"][c]): diffs += 1; print("acc.%s ref %08X C %08X" % (XYZW[c], int(m.acc[c]), int(af["acc"][c]))) for nm in ("q", "clip") + (("status", "mac") if reads_flags else ()): if getattr(m, nm) != af[nm]: diffs += 1; print("%s ref %08X C %08X" % (nm, getattr(m, nm), af[nm])) md = [k for k in range(0, len(m.mem), 4) if m.mem[k:k+4] != af["mem"][k:k+4]] if md: diffs += len(md) print("memory differs at %d words, first at qw 0x%X" % (len(md), md[0] // 16)) for k in md[:10]: print(" qw %03X.%s ref %08X C %08X" % (k // 16, XYZW[(k // 4) & 3], struct.unpack_from("