live_server now boots fw=vrend410 (the shipped game build) and replays the netdeath battle capture's 53,088-record VelociRender wire through emu860c + the GPU tile path: 8 frames in 5.8s, thousands of commands deep (cmd 6235), 25 tiles/100 sends each. The whole authentic backend -- vpxlog transport, production firmware, C core, GPU raster -- is proven on real game wire. Honest scope: the rendered image is still the bench readout (texu->SMPTE ramp via the texz=x seed), not the battle scene -- live_server uses the bars readout path, not the per-draw effect-primitive extraction (render_fx). The battle geometry is present in the wire and executes; wiring the fx-primitive readout into the live loop is the remaining refinement for real content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
155 lines
5.3 KiB
Python
155 lines
5.3 KiB
Python
"""emu860c differential driver v2: boot via the Python MainRunner
|
|
(authoritative loader), copy the state into the C core, run with hooks
|
|
serviced through a shim, and compare (pc, steps, reg-crc) at HOOK boundaries
|
|
against the hook-aligned reference trace (ref_trace2.pkl)."""
|
|
import sys, os, time, pickle, zlib
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
sys.path.insert(0, os.path.dirname(HERE))
|
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
|
import emu860, emu_main, emu860c
|
|
|
|
emu860.Mem.log = lambda self, *a, **k: None
|
|
SCRATCH = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
|
|
|
|
|
|
class MemShim:
|
|
def r32(self, a): return emu860c.r32(a & 0xffffffff)
|
|
def w32(self, a, v): emu860c.w32(a & 0xffffffff, v & 0xffffffff)
|
|
def load_blob(self, a, b): emu860c.load_blob(a & 0xffffffff, bytes(b))
|
|
|
|
|
|
class CpuShim:
|
|
def __init__(self):
|
|
self.mem = MemShim()
|
|
self.igc = []
|
|
self.board_log = []
|
|
self.RET_SENTINEL = 0xc0de0000
|
|
|
|
@property
|
|
def steps(self):
|
|
return emu860c.getstate()['steps']
|
|
|
|
@property
|
|
def pc(self):
|
|
return emu860c.getstate()['pc']
|
|
|
|
@pc.setter
|
|
def pc(self, v):
|
|
emu860c.setpc(v & 0xffffffff)
|
|
|
|
def rd(self, i):
|
|
return 0 if i == 0 else emu860c.getstate()['r'][i]
|
|
|
|
def wr(self, i, v):
|
|
if i:
|
|
emu860c.setreg(i, v & 0xffffffff, 0)
|
|
|
|
def step(self):
|
|
emu860c.step1()
|
|
return True
|
|
|
|
|
|
def boot(fw='capfw7', queue=None):
|
|
if queue is not None:
|
|
# manual construct with a caller-supplied live queue (no cap file)
|
|
r = emu_main.MainRunner.__new__(emu_main.MainRunner)
|
|
r.map = emu_main.MAPS[fw]
|
|
r.cpu = emu860.I860(trace=0)
|
|
r.cpu.load_mng(r.map['fw'])
|
|
r.cpu.map_control(); r.cpu.map_board()
|
|
for a, v in r.map['seeds']:
|
|
r.cpu.mem.w32(a, v)
|
|
r.queue = queue; r.qi = 0; r.verbose = False
|
|
r.done = {}; r.replies = []; r.console = []
|
|
m = r.map
|
|
r.hooks = {m['bla']: r.h_ret, m['mynode']: lambda c: r.h_writeback(c, 2),
|
|
m['nodes']: lambda c: r.h_writeback(c, 3), m['receive']: r.h_receive,
|
|
m['reply']: r.h_reply, m['alloc']: r.h_allocpages,
|
|
m['v2p']: lambda c: r.h_ret(c, c.rd(16) & ~0xfff),
|
|
m['lockacq']: r.h_ret, m['lockrel']: r.h_ret,
|
|
m['igcwait']: r.h_igcwait}
|
|
r.heap = [0x08010000, 0x0c010000]
|
|
cpu = r.start()
|
|
else:
|
|
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin',
|
|
fw=fw, max_cmds=200000)
|
|
cpu = r.start()
|
|
emu860c.init()
|
|
for pn, page in cpu.mem.pages.items():
|
|
emu860c.load_blob(pn << 16, bytes(page))
|
|
for a in range(0xfffff000, 0x100000000, 4):
|
|
v = cpu.mem.r32(a)
|
|
if v:
|
|
emu860c.w32(a & 0xffffffff, v & 0xffffffff)
|
|
for i in range(32):
|
|
emu860c.setreg(i, cpu.r[i] & 0xffffffff, 0)
|
|
emu860c.setreg(i, cpu.f[i] & 0xffffffff, 1)
|
|
emu860c.setpc(cpu.pc)
|
|
emu860c.set_sentinel(cpu.RET_SENTINEL)
|
|
emu860c.set_hooks(sorted(r.hooks.keys()))
|
|
return r
|
|
|
|
|
|
def crc_state(st):
|
|
b = b''.join(int(v).to_bytes(4, 'little') for v in st['r'])
|
|
b += b''.join(int(v).to_bytes(4, 'little') for v in st['f'])
|
|
return zlib.crc32(b) & 0xffffffff
|
|
|
|
|
|
def main():
|
|
ref = pickle.load(open(os.path.join(SCRATCH, 'ref_trace2.pkl'), 'rb'))
|
|
cps = {n: (pc, steps, crc) for n, pc, steps, crc in ref['cps']}
|
|
max_hook = ref['hookno']
|
|
r = boot()
|
|
shim = CpuShim()
|
|
t0 = time.time()
|
|
hookno = 0
|
|
matched = mismatched = 0
|
|
while hookno < max_hook:
|
|
reason, steps = emu860c.run(200000000)
|
|
if reason == 0:
|
|
st = emu860c.getstate()
|
|
pc = st['pc']
|
|
h = r.hooks.get(pc)
|
|
if h is None:
|
|
print("sentinel at hook %d" % hookno)
|
|
break
|
|
hookno += 1
|
|
want = cps.get(hookno)
|
|
if want:
|
|
got = (pc, st['steps'], crc_state(st))
|
|
if want != got:
|
|
print("DIVERGE at hook %d:" % hookno)
|
|
print(" want pc=%#x steps=%d crc=%08x" % want)
|
|
print(" got pc=%#x steps=%d crc=%08x" % got)
|
|
mismatched += 1
|
|
if mismatched > 3:
|
|
break
|
|
else:
|
|
matched += 1
|
|
if h(shim) == 'done':
|
|
break
|
|
continue
|
|
if reason == 1:
|
|
print("STOP: pc=%#x" % emu860c.getstate()['pc'])
|
|
break
|
|
if reason == 2:
|
|
st = emu860c.getstate()
|
|
print("FP fallback pc=%#x w=%08x" % (st['pc'], emu860c.r32(st['pc'])))
|
|
break
|
|
if reason == 3:
|
|
print("budget exhausted")
|
|
break
|
|
dt = time.time() - t0
|
|
st = emu860c.getstate()
|
|
print("C core: %d steps, %d hooks in %.2fs = %d steps/s; "
|
|
"checkpoints %d/%d matched" %
|
|
(st['steps'], hookno, dt, st['steps'] / max(dt, 1e-9),
|
|
matched, len(cps)))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|