diff --git a/emulator/firmware-decomp/emu860.py b/emulator/firmware-decomp/emu860.py index 88eef96..7c6d643 100644 --- a/emulator/firmware-decomp/emu860.py +++ b/emulator/firmware-decomp/emu860.py @@ -59,22 +59,33 @@ class Mem: addr = u32(addr) m = self._mmio(addr) if m: return u32(m[0](addr)) - p = self._page(addr, False) - if p is None: - if (addr >> 16) not in self._warned: - self._warned.add(addr >> 16) - self.log(f" [mem] read unmapped {addr:#010x} -> 0") - return 0 - return struct.unpack_from('> 16) not in self._warned: + self._warned.add(addr >> 16) + self.log(f" [mem] read unmapped {addr:#010x} -> 0") + return 0 + return struct.unpack_from('> (8 * i)) & 0xFF) def r16(self, addr): - addr = u32(addr); p = self._page(addr, False) + addr = u32(addr) + if (addr & 0xFFFF) == 0xFFFF: + return self.r8(addr) | (self.r8(addr + 1) << 8) + p = self._page(addr, False) if p is None: return 0 return struct.unpack_from('> 8) & 0xFF) + else: + struct.pack_into('> 11) & 0x1f imm = w & 0xffff - # ---- loads / stores ---- - if op in (0x04, 0x05): # ld.l off(src2),dest - off = s16(imm & 0xfffc) - self.wr(dest, self.mem.r32(self.rd(src2) + off)); return - if op in (0x06, 0x07): # st.l dest? -> src? : st.l r,off(src2) - off = s16(imm & 0xfffc) - # st.l isrc1(dest field is src), src2 base : encoding stores rdest-field - self.mem.w32(self.rd(src2) + off, self.rd(dest)); return - if op in (0x00, 0x01): # ld.s (16-bit) -- sign? treat unsigned - off = s16(imm & 0xfffe) - self.wr(dest, self.mem.r16(self.rd(src2) + off)); return - if op in (0x08, 0x09): # fld.l off(src2),fdest - off = s16(imm & 0xfff8 if op == 0x09 else imm) - self.fwr(dest, self.mem.r32(self.rd(src2) + off)); return - if op in (0x0a, 0x0b): # fst.l fdest,off(src2) - off = s16(imm & 0xfff8 if op == 0x0b else imm) - self.mem.w32(self.rd(src2) + off, self.frd(dest)); return + # ---- loads ---- i860 integer loads are IMMEDIATE-offset (both even/odd of a + # pair): EA = base(src2) + s16(offset). Indexing is done by pre-computing + # base+index into a register, then loading at offset 0. dest = bits20:16. + if op in (0x00, 0x01, 0x04, 0x05, 0x08, 0x09): + m = 0xffff if op < 0x04 else (0xfff8 if op >= 0x08 else 0xfffc) + off = s16(imm & m) + ea = self.rd(src2) + off + if op in (0x00, 0x01): self.wr(dest, self.mem.r8(ea)) # ld.b (byte) + elif op in (0x04, 0x05): self.wr(dest, self.mem.r32(ea)) # ld.l + else: self.fwr(dest, self.mem.r32(ea)) # fld.l + return + # ---- stores ---- i860 stores differ: source reg = src1 (bits15:11), and the + # 16-bit offset is SPLIT high=bits20:16, low=bits10:0 (the src1 field displaced it). + if op in (0x03, 0x06, 0x07, 0x0a, 0x0b): + m = 0xffff if op == 0x03 else (0xfff8 if op >= 0x0a else 0xfffc) + off = s16((((w >> 16) & 0x1f) << 11 | (w & 0x7ff)) & m) + ea = self.rd(src2) + off + if op == 0x03: self.mem.w8(ea, self.rd(src1) & 0xff) # st.b (byte) + elif op in (0x06, 0x07): self.mem.w32(ea, self.rd(src1)) # st.l + else: self.mem.w32(ea, self.frd(src1)) # fst.l + return if op == 0x0c: # ld.c ctrl,dest self.wr(dest, self.cr.get(src2, 0)); return if op == 0x0e: # st.c src1,ctrl @@ -232,15 +251,18 @@ class I860: # ---- arithmetic / logic (odd opcode = 16-bit immediate) ---- base = op & 0x3e if base in (0x20, 0x22, 0x24, 0x26): # addu subu adds subs + # i860: dest = SRC1 (op) SRC2, where src1 = imm (immediate form) or + # rd(src1) (register form). Subtraction is src1 - src2 (NOT reversed). b = self.rd(src2) a = s16(imm) if (op & 1) else self.rd(src1) - if base == 0x20: r = a + b # addu - elif base == 0x22: r = b - a # subu - elif base == 0x24: r = i32(a) + i32(b) # adds - else: r = i32(b) - i32(a) # subs - self.wr(dest, r) - # i860 sets CC on add/sub (borrow/carry); approximate signed compare use - self.set_cc((r & MASK32) == 0 if False else (i32(b) - i32(a)) >= 0 if base in (0x22, 0x26) else True) + if base == 0x20: # addu + s = u32(a) + u32(b); self.wr(dest, s); self.set_cc(s > MASK32) + elif base == 0x24: # adds + s = i32(a) + i32(b); self.wr(dest, s); self.set_cc(u32(a) + u32(b) > MASK32) + elif base == 0x22: # subu: src1 - src2 + self.wr(dest, u32(a) - u32(b)); self.set_cc(u32(a) < u32(b)) + else: # subs: src1 - src2 + self.wr(dest, i32(a) - i32(b)); self.set_cc(i32(a) < i32(b)) return if base in (0x28, 0x2a, 0x2c, 0x2e): # shl shr shrd shra cnt = (s16(imm) & 0x1f) if (op & 1) else (self.rd(src1) & 0x1f) @@ -261,7 +283,7 @@ class I860: elif g == 0x38: r = b | a # or/orh else: r = b ^ a # xor/xorh self.wr(dest, r) - self.set_cc((r & MASK32) == 0) # logicals set CC = (result==0) + self.set_cc((r & MASK32) == 0) # i860 logicals set CC = (result == 0) return # ---- FP unit (opcode 0x12) ---- @@ -317,6 +339,14 @@ class I860: self.set_cc(self.rdf(src1, sp) > self.rdf(src2, sp)) elif sub == 0x35: # feq: CC = src1 == src2 self.set_cc(self.rdf(src1, sp) == self.rdf(src2, sp)) + elif sub == 0x21: # fmlow.dd -- i860 FP-unit INTEGER multiply. + # The i860 has no imul: ints are ixfr'd into FP regs, fmlow.dd multiplies, + # and the low 32 bits (fdest) are fxfr/fst'd back. Operands are the low + # words (32-bit); result is the 64-bit product across the fdest pair. + a = self.frd(src1); b = self.frd(src2) + prod = a * b + b0 = dest & ~1 + self.fwr(b0, prod & 0xFFFFFFFF); self.fwr(b0 | 1, (prod >> 32) & 0xFFFFFFFF) elif sub == 0x40: # fxfr FP->int self.wr(dest, self.frd(src1)) elif sub == 0x49: # fiadd (integer add in FP unit, 32-bit) diff --git a/emulator/firmware-decomp/emu_replay.py b/emulator/firmware-decomp/emu_replay.py index e39fc43..8d5731f 100644 --- a/emulator/firmware-decomp/emu_replay.py +++ b/emulator/firmware-decomp/emu_replay.py @@ -21,25 +21,25 @@ 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). +# From VR_REMOT.C remote_velocirender(): the wire `action` (== the vr_action enum, +# which matches vrboard's) drives a switch. init(0) is special -> velocirender_init +# + deferred do_init(); every other action indexes a jump table by (action-1). +INIT_FN = 0xf040e300 # velocirender_init(param_data) [action 0] +DO_INIT = 0xf040afb0 # do_init() -- deferred, before first real command +# vr_action -> handler vaddr. Handler addresses matched by function identity from +# the case blocks (create verified: reads [p+0]=type,[p+4]=handle; draw_scene and +# statistics verified). The switch jump-table indexing is a compiler idiom we +# don't need since remote_velocirender's source gives handler-per-action directly. WIRE_HANDLERS = { A.create: 0xf040c180, + A.delete: 0xf040c3b8, A.flush: 0xf040cfd8, A.dcs_link: 0xf040dc70, A.list_add: 0xf040d4d0, A.draw_scene: 0xf040e340, A.statistics: 0xf040e940, + A.set_geom_verts: 0xf040a9a8, A.set_texmap_texels: 0xf040a030, - # A.init handler + the 0x1d/0x2a artic handlers: need the remap (see TODO). } def extract_handlers(cpu): @@ -68,35 +68,46 @@ def main(): 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})") + print(f"handlers: create={handlers.get(A.create):#x} flush={handlers.get(A.flush):#x} " + f"list_add={handlers.get(A.list_add):#x} draw_scene={handlers.get(A.draw_scene):#x} " + f"statistics={handlers.get(A.statistics):#x}") cmds = parse_capture(cap) print(f"{os.path.basename(cap)}: {len(cmds)} wire commands") + import dis860 + def report(idx, label): + print(f"\n[cmd {idx}] {label} FAULT: {cpu.stopmsg}") + for pc, w in cpu.tail[-8:]: + m, ops = dis860.decode(w, pc); print(f" {pc:#010x}: {w:08x} {m} {ops}") + ANAME = {int(a): a.name for a in A} pbuf = 0x00200000 # fresh payload buffer per command (advances) done = {} + init_pending = False; did_init = False for idx, (act, pl) in enumerate(cmds): - if act in BOOT_ACTIONS or act == A.init: - continue # skip firmware download; init handled below + if act in BOOT_ACTIONS: + continue # skip firmware-download frames + ppl = pbuf; cpu.mem.load_blob(ppl, pl or b'\0'); pbuf += (len(pl) + 15) & ~15 + if act == A.init: + cpu.call(INIT_FN, args=(ppl,), maxsteps=8_000_000) + init_pending = True; done['init'] = done.get('init', 0) + 1 + if cpu.stopmsg: report(idx, 'velocirender_init'); break + continue + if init_pending and not did_init: # deferred heavy init before 1st real cmd + cpu.call(DO_INIT, maxsteps=8_000_000); did_init = True + print(f"do_init() -> {'FAULT' if cpu.stopmsg else 'ok'} (steps={cpu.steps})") + if cpu.stopmsg: report(idx, 'do_init'); break h = handlers.get(act) if h is None: - done[act] = done.get(act, 0) + 1 + done[str(ANAME.get(act, act)) + '?'] = done.get(str(ANAME.get(act, 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 + cpu.call(h, args=(ppl,), maxsteps=8_000_000) 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 + report(idx, f"action {ANAME.get(act,act)} (built {sum(done.values())} cmds)"); break if verbose and act == A.draw_scene: - print(f"[cmd {idx}] draw_scene ok IGC writes so far: {len(cpu.igc)}") + print(f"[cmd {idx}] draw_scene ok IGC writes: {len(cpu.igc)}") print("\nreplayed:", done) print(f"IGC/board writes captured: {len(cpu.board_log)}")