version-matched capfw7 replay BUILDS THE SCENE - Pipelined FP model: 3-stage adder / 2-stage(double) multiplier pipes with KR/KI/T registers; pfadd/pfsub/pfmul honor the P-bit; PFAM(0x00-0x0f)/ PFSM(0x10-0x1f) dual-operations implemented from the validated mnemonic grammar (r2pt, ra1p2, m12apm, ...: M-unit/A-unit routing + K/T loading). Survives the capture build's Newton-Raphson boot math. - adds CC = result-negative (sign rule); addu stays carry. Fixes printf's pad-to-width loops and the exit-walk's empty-table branch. - op 0x0d = flush (cache-line flush, store-format split offset, bit0 auto-increment): boot cache-flush loops. - fix/ftrunc: NaN/inf/out-of-range -> IEEE indefinite instead of raising. - emu_main: per-build address maps (sda4 + capfw7); capfw7 map fully derived (main 0xf0403f80, remote_velocirender 0xf040cee0, dN_receive 0xf04024c0, velocirender_init 0xf040c5d8, do_init 0xf0409440, reply 0xf0409320, alloc core 0xf042c628, v2p 0xf042c300, locks 0xf0423360/0xf04233b8). The 860-boot preamble is skipped for capfw7 (the monitor's job -- the app build rejects those actions); the capture build dispatches 42 actions, explaining the unknown actions 29/42 in cap7. - run_to_draw: version-matched marathon from the TRUE ENTRY 0xf0400000 with the boot handshake ([0xfffff728]) satisfied -- crt0/pools/main are all the firmware's own startup, eliminating the seed-slot guesswork. STATE: authentic boot + init (reply=4) + scene build running: 152 commands (31 create, 59 flush, dcs_link, list_add, texmaps) processed and replied before the time cap. First draw_scene sits ~1400 commands ahead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
219 lines
8.5 KiB
Python
219 lines
8.5 KiB
Python
"""Run the render firmware's own main() and feed it real wire captures through
|
|
dN_receive.
|
|
|
|
This is the AUTHENTIC path: the firmware's main() does its own startup
|
|
(newBytes pools, dN_mynode/dN_nodes) and enters remote_velocirender()'s
|
|
while(1) { dN_receive(); dispatch; } loop. We hook only the transputer-LINK
|
|
primitives (they'd block on real hardware) and the page-allocation core
|
|
(whose VM page tables the monitor builds -- Tier-2 = run VRENDMON on a T425);
|
|
everything else -- init, do_init, dispatch, handlers, allocator wrappers --
|
|
is the firmware's own code.
|
|
|
|
Two builds are mapped:
|
|
sda4 -- sda4/RPLIVE/VREND.MNG (text 0x39ec0), our archive copy.
|
|
capfw7 -- capfw7.mng (text 0x31440), extracted from cap7's own wire-boot
|
|
preamble (extract_capfw.py): the EXACT build the game ran, with
|
|
an EXTENDED action set (42 actions vs the SDK enum's 24).
|
|
|
|
Usage: python emu_main.py [capture.raw.bin] [--fw sda4|capfw7]
|
|
[--max-cmds N] [--verbose]
|
|
"""
|
|
import sys, os, struct
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
|
import emu860
|
|
from emu_replay import parse_capture
|
|
from vrboard import A
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
MAPS = {
|
|
'sda4': dict(
|
|
fw=r'C:\VWE\TeslaRel410\sda4\RPLIVE\VREND.MNG',
|
|
main=0xf0403f10, bla=0xf04285f0,
|
|
mynode=0xf0402570, nodes=0xf0402610, receive=0xf0402450,
|
|
vinit=0xf040e300, doinit=0xf040afb0,
|
|
reply=0xf040ae90, sendprim=0xf0402370, putchar=0xf04030c8,
|
|
alloc=0xf04350b8, v2p=0xf0434d90,
|
|
lockacq=0xf042bdf0, lockrel=0xf042be48,
|
|
# create's table routes types 2-5 through the dispatch tail at `chain`
|
|
# (r31=0-on-entry semantics); new_node's init table at chain_tbl.
|
|
chain=0xf040bd0c, chain_tbl=0xece8, chain_tail=0xf040bd1c,
|
|
seeds=[
|
|
(0x1000, 0), # _processorId (monitor-written slot)
|
|
(0x1ecc0, 0), (0x1ecc4, 0), # sbrk break ptrs -> lazy-init
|
|
(0x14fc4, 0x00300000), # shared uncached blocks (locks at +0x18)
|
|
(0x14fc8, 0x00301000),
|
|
(0x14fcc, 0x00302000),
|
|
],
|
|
),
|
|
'capfw7': dict(
|
|
fw=os.path.join(_HERE, 'capfw7.mng'),
|
|
main=0xf0403f80, bla=0xf0420c00,
|
|
mynode=0xf04025e0, nodes=0xf0402680, receive=0xf04024c0,
|
|
vinit=0xf040c5d8, doinit=0xf0409440,
|
|
reply=0xf0409320, sendprim=None, putchar=None,
|
|
alloc=0xf042c628, v2p=0xf042c300,
|
|
lockacq=0xf0423360, lockrel=0xf04233b8,
|
|
chain=None, chain_tbl=None, chain_tail=None,
|
|
seeds=[
|
|
(0x1000, 0), # _processorId
|
|
],
|
|
),
|
|
}
|
|
|
|
ANAME = {int(a): a.name for a in A}
|
|
|
|
|
|
class MainRunner:
|
|
def __init__(self, cap, fw='capfw7', verbose=False, max_cmds=None):
|
|
self.map = MAPS[fw]
|
|
self.cpu = emu860.I860(trace=0)
|
|
self.cpu.load_mng(self.map['fw'])
|
|
self.cpu.map_control(); self.cpu.map_board()
|
|
for addr, val in self.map['seeds']:
|
|
self.cpu.mem.w32(addr, val)
|
|
# The 860-boot preamble (args860/code860/data860/bss860/hspcode) is
|
|
# consumed by the MONITOR on real hardware, not the app -- the capfw7
|
|
# app's dispatch rejects those actions (prints the message and dies).
|
|
# We are the monitor and the image is already loaded: skip them.
|
|
BOOT = {A.hspcode, A.code860, A.data860, A.bss860, A.args860}
|
|
cmds = [(a, p) for a, p in parse_capture(cap) if a not in BOOT]
|
|
if max_cmds: cmds = cmds[:max_cmds]
|
|
self.queue = cmds
|
|
self.qi = 0
|
|
self.verbose = verbose
|
|
self.done = {}
|
|
self.replies = []
|
|
self.console = []
|
|
m = self.map
|
|
self.hooks = {
|
|
m['bla']: self.h_ret,
|
|
m['mynode']: lambda c: self.h_writeback(c, 2),
|
|
m['nodes']: lambda c: self.h_writeback(c, 3),
|
|
m['receive']: self.h_receive,
|
|
m['reply']: self.h_reply,
|
|
m['alloc']: self.h_allocpages,
|
|
m['v2p']: lambda c: self.h_ret(c, c.rd(16) & ~0xfff),
|
|
m['lockacq']: self.h_ret,
|
|
m['lockrel']: self.h_ret,
|
|
}
|
|
if m['sendprim']: self.hooks[m['sendprim']] = self.h_ret
|
|
if m['putchar']: self.hooks[m['putchar']] = self.h_console
|
|
if m['chain']: self.hooks[m['chain']] = self.h_chainfix
|
|
self.heap = [0x08010000, 0x0c010000] # bump allocator over the DRAM banks
|
|
|
|
# ---- hook helpers: emulate a called function returning ----
|
|
def h_ret(self, cpu, ret=None):
|
|
if ret is not None: cpu.wr(16, ret)
|
|
cpu.pc = cpu.rd(1)
|
|
return True
|
|
|
|
def h_writeback(self, cpu, val):
|
|
cpu.mem.w32(cpu.rd(16), val)
|
|
return self.h_ret(cpu)
|
|
|
|
def h_reply(self, cpu):
|
|
self.replies.append(cpu.rd(16))
|
|
return self.h_ret(cpu)
|
|
|
|
def h_console(self, cpu):
|
|
self.console.append(cpu.rd(16) & 0xff)
|
|
return self.h_ret(cpu)
|
|
|
|
def h_allocpages(self, cpu):
|
|
sel = cpu.rd(16)
|
|
bank = 1 if sel == 1 else 0
|
|
size = (cpu.rd(17) & 0xffff) << 12
|
|
ptr = self.heap[bank]
|
|
self.heap[bank] += size
|
|
return self.h_ret(cpu, ptr)
|
|
|
|
def h_chainfix(self, cpu):
|
|
if cpu.rd(31) == cpu.pc: # arrived via a table jump
|
|
idx4 = cpu.rd(30)
|
|
tgt = cpu.mem.r32(self.map['chain_tbl'] + idx4)
|
|
cpu.wr(31, 0)
|
|
cpu.pc = self.map['chain_tail'] if tgt == self.map['chain'] else tgt
|
|
return True
|
|
cpu.step()
|
|
return True
|
|
|
|
def h_receive(self, cpu):
|
|
if self.qi >= len(self.queue):
|
|
return 'done'
|
|
act, pl = self.queue[self.qi]; self.qi += 1
|
|
buf = cpu.rd(18)
|
|
cpu.mem.w32(buf, act)
|
|
cpu.mem.load_blob(buf + 4, pl or b'')
|
|
cpu.mem.w32(cpu.rd(16), 0) # *client
|
|
cpu.mem.w32(cpu.rd(17), 4 + len(pl)) # *receive_bytes
|
|
n = ANAME.get(act, act)
|
|
self.done[n] = self.done.get(n, 0) + 1
|
|
if self.verbose or self.qi % 1000 == 0 or self.qi <= 8:
|
|
print(f" [{self.qi:5}/{len(self.queue)}] {str(n):<22} len={len(pl):6} "
|
|
f"steps={cpu.steps:>13,} igc={len(cpu.igc)} board={len(cpu.board_log)}",
|
|
flush=True)
|
|
return self.h_ret(cpu)
|
|
|
|
def start(self):
|
|
cpu = self.cpu
|
|
cpu.wr(1, cpu.RET_SENTINEL); cpu.wr(2, 0x000c0000)
|
|
cpu.wr(16, 0); cpu.wr(17, 0) # argc, argv
|
|
cpu.pc = self.map['main']
|
|
return cpu
|
|
|
|
def run(self, budget=2_000_000_000):
|
|
cpu = self.start()
|
|
watch = {self.map['vinit']: 'velocirender_init',
|
|
self.map['doinit']: 'do_init'}
|
|
while cpu.steps < budget:
|
|
pc = cpu.pc
|
|
if pc == cpu.RET_SENTINEL:
|
|
print(f"main RETURNED after {cpu.steps:,} steps"); break
|
|
h = self.hooks.get(pc)
|
|
if h:
|
|
r = h(cpu)
|
|
if r == 'done':
|
|
print(f"\ncapture exhausted after {self.qi} commands, "
|
|
f"{cpu.steps:,} steps"); break
|
|
continue
|
|
if pc in watch:
|
|
print(f" >> {watch.pop(pc)} entered (steps={cpu.steps:,})", flush=True)
|
|
if not cpu.step():
|
|
print(f"\nSTOP: {cpu.stopmsg}")
|
|
import dis860
|
|
for tpc, tw in cpu.tail[-12:]:
|
|
m, ops = dis860.decode(tw, tpc)
|
|
print(f" {tpc:#010x}: {tw:08x} {m} {ops}")
|
|
break
|
|
return cpu
|
|
|
|
|
|
def main():
|
|
args = [a for a in sys.argv[1:] if not a.startswith('--')]
|
|
cap = args[0] if args else r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin'
|
|
fw = 'capfw7'
|
|
if '--fw' in sys.argv:
|
|
fw = sys.argv[sys.argv.index('--fw') + 1]
|
|
verbose = '--verbose' in sys.argv
|
|
mc = None
|
|
if '--max-cmds' in sys.argv:
|
|
mc = int(sys.argv[sys.argv.index('--max-cmds') + 1])
|
|
r = MainRunner(cap, fw=fw, verbose=verbose, max_cmds=mc)
|
|
print(f"{os.path.basename(cap)}: {len(r.queue)} wire commands queued (fw={fw})")
|
|
cpu = r.run()
|
|
print(f"\nreplayed: {r.done}")
|
|
print(f"replies: {r.replies[:20]}")
|
|
print(f"IGC writes: {len(cpu.igc)} board-log entries: {len(cpu.board_log)}")
|
|
if cpu.board_log:
|
|
out = os.path.join(_HERE, 'igc_stream.bin')
|
|
with open(out, 'wb') as f:
|
|
for k, a, v in cpu.board_log:
|
|
f.write(struct.pack('<BII', 1 if k == 'w' else 0, a, v))
|
|
print(f"board stream -> {out}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|