Tier 0 emulator: wire-command replay harness (emu_replay.py)
Feeds the real captured VelociRender wire (dpl3-revive/patha/*.raw.bin, from the
soft-renderer work) to the firmware's own command handlers in emu860 -- the path
to building a scene and rendering a frame with the board's own code.
- Parses captures via vrboard's Assembler (skips the .BTL boot, frames
action+payload). cap7 = a clean small scene (17 zones / 22 instances / 24
geometry / 1 view).
- Calls each command's handler directly with the payload pointer in r16 --
verified valid: create's prologue reads [r16+0]=type, [r16+4]=handle, i.e. the
raw wire layout (no CCB wrapping needed). Bypasses the un-emulated transputer.
- Confirmed WIRE-action -> handler map by function identity (create/flush/
dcs_link/list_add/draw_scene/statistics/set_texmap_texels).
Two blockers identified for a full frame (documented in the memory + README):
1. The dispatch jump table is indexed by an INTERNAL command code, not the
wire action (velocirender_input remaps wire->internal first, e.g. draw=9
->12, flush=3->30); the high-frequency per-frame commands (0x09, 0x1d
artics, 0x2a) still need that remap reversed.
2. init(0) must run first to set up the allocator/name-table/scene-root --
cold create() faults without it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
|||||||
|
"""Replay a captured VelociRender wire through the REAL firmware in emu860.
|
||||||
|
|
||||||
|
Feeds each captured (action, payload) command to the firmware's own handler
|
||||||
|
(payload pointer in r16, per the PGI calling convention), building the scene up
|
||||||
|
in the emulated i860's memory, then runs draw_scene -- capturing the IGC
|
||||||
|
coefficient stream the firmware emits (the Tier-1 handoff).
|
||||||
|
|
||||||
|
Bypasses the transputer/CCB/interrupt path (see HARDWARE-ARCHITECTURE.md): we
|
||||||
|
call the handlers directly, which is valid because they read the raw wire payload.
|
||||||
|
|
||||||
|
python emu_replay.py [capture.raw.bin] [--verbose]
|
||||||
|
"""
|
||||||
|
import sys, os, struct
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, HERE)
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
import emu860
|
||||||
|
from vrboard import Assembler, A, BOOT_ACTIONS
|
||||||
|
|
||||||
|
MNG = r'C:\VWE\TeslaRel410\sda4\RPLIVE\VREND.MNG'
|
||||||
|
|
||||||
|
def s26(v): return v - 0x4000000 if v & 0x2000000 else v
|
||||||
|
|
||||||
|
# Confirmed WIRE-action -> handler map (vaddr). IMPORTANT: the firmware's
|
||||||
|
# dispatch jump table (file 0x47cb8) is indexed by an INTERNAL command code, NOT
|
||||||
|
# the wire action -- velocirender_input remaps wire->internal first (e.g. wire
|
||||||
|
# draw_scene=9 -> internal index 12; flush=3 -> 30; statistics=15 -> 31; while
|
||||||
|
# create/list_add/dcs_link are identity). So we can't index that table by the
|
||||||
|
# capture's action. These are matched by function identity instead (create's
|
||||||
|
# prologue verified reads type/handle; statistics verified returns; draw_scene,
|
||||||
|
# flush, list_add, dcs_link identified in the dispatch analysis).
|
||||||
|
# TODO: reverse the wire->internal remap in velocirender_input to complete the
|
||||||
|
# map for the high-frequency per-frame commands (0x09 draw, 0x1d artics, 0x2a).
|
||||||
|
WIRE_HANDLERS = {
|
||||||
|
A.create: 0xf040c180,
|
||||||
|
A.flush: 0xf040cfd8,
|
||||||
|
A.dcs_link: 0xf040dc70,
|
||||||
|
A.list_add: 0xf040d4d0,
|
||||||
|
A.draw_scene: 0xf040e340,
|
||||||
|
A.statistics: 0xf040e940,
|
||||||
|
A.set_texmap_texels: 0xf040a030,
|
||||||
|
# A.init handler + the 0x1d/0x2a artic handlers: need the remap (see TODO).
|
||||||
|
}
|
||||||
|
|
||||||
|
def extract_handlers(cpu):
|
||||||
|
return dict(WIRE_HANDLERS)
|
||||||
|
|
||||||
|
def parse_capture(path):
|
||||||
|
data = open(path, 'rb').read()
|
||||||
|
start = None
|
||||||
|
for i in range(0, len(data) - 8):
|
||||||
|
w = struct.unpack_from('<I', data, i)[0]
|
||||||
|
if (w & 0xffff0000) == 0x40ff0000 and 8 <= (w & 0xffff) <= 4096:
|
||||||
|
act = struct.unpack_from('<I', data, i + 4)[0]
|
||||||
|
if act in (A.init, A.code860, A.args860, A.data860, A.bss860):
|
||||||
|
start = i; break
|
||||||
|
if start is None:
|
||||||
|
raise SystemExit("no framed region found")
|
||||||
|
asm = Assembler(); asm.feed(data[start:])
|
||||||
|
return [(m.action, m.payload) for m in asm if not m.iserver]
|
||||||
|
|
||||||
|
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'
|
||||||
|
verbose = '--verbose' in sys.argv
|
||||||
|
|
||||||
|
cpu = emu860.I860(trace=0)
|
||||||
|
cpu.load_mng(MNG)
|
||||||
|
cpu.map_control(); cpu.map_board()
|
||||||
|
handlers = extract_handlers(cpu)
|
||||||
|
print(f"handlers extracted: {len(handlers)} "
|
||||||
|
f"(draw_scene={handlers.get(A.draw_scene):#x} create={handlers.get(A.create):#x})")
|
||||||
|
|
||||||
|
cmds = parse_capture(cap)
|
||||||
|
print(f"{os.path.basename(cap)}: {len(cmds)} wire commands")
|
||||||
|
|
||||||
|
ANAME = {int(a): a.name for a in A}
|
||||||
|
pbuf = 0x00200000 # fresh payload buffer per command (advances)
|
||||||
|
done = {}
|
||||||
|
for idx, (act, pl) in enumerate(cmds):
|
||||||
|
if act in BOOT_ACTIONS or act == A.init:
|
||||||
|
continue # skip firmware download; init handled below
|
||||||
|
h = handlers.get(act)
|
||||||
|
if h is None:
|
||||||
|
done[act] = done.get(act, 0) + 1
|
||||||
|
continue
|
||||||
|
cpu.mem.load_blob(pbuf, pl or b'\0')
|
||||||
|
r = cpu.call(h, args=(pbuf,), maxsteps=5_000_000)
|
||||||
|
pbuf += (len(pl) + 15) & ~15
|
||||||
|
done[ANAME.get(act, act)] = done.get(ANAME.get(act, act), 0) + 1
|
||||||
|
if cpu.stopmsg:
|
||||||
|
print(f"\n[cmd {idx}] action {ANAME.get(act,act)} FAULTED after building "
|
||||||
|
f"{sum(v for k,v in done.items())} cmds:\n {cpu.stopmsg}")
|
||||||
|
import dis860
|
||||||
|
for pc, w in cpu.tail[-6:]:
|
||||||
|
m, ops = dis860.decode(w, pc); print(f" {pc:#010x}: {w:08x} {m} {ops}")
|
||||||
|
break
|
||||||
|
if verbose and act == A.draw_scene:
|
||||||
|
print(f"[cmd {idx}] draw_scene ok IGC writes so far: {len(cpu.igc)}")
|
||||||
|
print("\nreplayed:", done)
|
||||||
|
print(f"IGC/board writes captured: {len(cpu.board_log)}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user