diff --git a/emulator/firmware-decomp/dis860.py b/emulator/firmware-decomp/dis860.py index 9bfb48f..8152a5a 100644 --- a/emulator/firmware-decomp/dis860.py +++ b/emulator/firmware-decomp/dis860.py @@ -52,11 +52,16 @@ def dec_fp(w): return (name + fp_suffix(w), f"{FREG[src1]},{FREG[src2]},{FREG[dest]}") # ---- primary decode ---- -# immediate-form rule: for load/store (0x00-0x0b) and arith/logic (0x20-0x3f), -# the ODD opcode of each pair is the 16-bit immediate/const form. -MEM = {0x00: "ld.s", 0x01: "ld.s", 0x04: "ld.l", 0x05: "ld.l", - 0x06: "st.l", 0x07: "st.l", 0x08: "fld.l", 0x09: "fld.l", - 0x0a: "fst.l", 0x0b: "fst.l"} +# load/store encoding (ground truth: AS860 .S<->.O pairs + DNC.O): +# - integer loads (0x00/0x01 ld.b, 0x04/0x05 ld.s|ld.l): dest=bits20:16; +# ODD = 16-bit immediate offset, EVEN = register-indexed EA=src2+src1. +# op 4/5 size flag = instr bit0 (0=.s 16-bit, 1=.l 32-bit). +# - integer stores (0x03 st.b, 0x07 st.s|st.l): source reg = src1 (bits15:11), +# offset SPLIT high=bits20:16 low=bits10:0; bit0 size flag as loads. +# - FP fld (0x08/0x09) / fst (0x0a/0x0b): FP reg = dest field for BOTH; +# ODD = flat s16 imm, EVEN = indexed. Flags: bit0=auto-increment, +# bit1: 1=.l, 0=.d; bit2 (with bit1=0) = .q. +MEM = {0x00, 0x01, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0a, 0x0b} ARITH = {0x20: "addu", 0x22: "subu", 0x24: "adds", 0x26: "subs", 0x28: "shl", 0x2a: "shr", 0x2c: "shrd", 0x2e: "shra", 0x30: "and", 0x32: "andh", 0x34: "andnot", 0x36: "andnoth", @@ -70,15 +75,32 @@ def decode(w, addr): imm = w & 0xffff # loads/stores if op in MEM: - m = MEM[op] - off = s16(imm & 0xfffc) if 'l' in m else s16(imm) - if m.startswith('f'): - rd = FREG[dest] + if op in (0x00, 0x01, 0x04, 0x05): # integer loads + m = "ld.b" if op < 0x04 else ("ld.l" if (w & 1) else "ld.s") + if op & 1: + mask = 0xffff if op == 0x01 else (0xfffc if (w & 1) else 0xfffe) + return m, f"{s16(imm & mask):#x}({REG[src2]}),{REG[dest]}" + return m, f"{REG[src1]}({REG[src2]}),{REG[dest]}" + if op in (0x03, 0x07): # integer stores (split offset) + off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff) + if op == 0x03: + return "st.b", f"{REG[src1]},{s16(off):#x}({REG[src2]})" + m = "st.l" if (off & 1) else "st.s" + off &= 0xfffc if (off & 1) else 0xfffe + return m, f"{REG[src1]},{s16(off):#x}({REG[src2]})" + # FP fld/fst: FP reg in dest field; flags bit0=auto++, bit1:1=.l/0=.d, bit2=.q + fl = w & 7 + sz = ".l" if (fl & 2) else (".q" if (fl & 4) else ".d") + pp = "++" if (fl & 1) else "" + m = ("fld" if op < 0x0a else "fst") + sz + if op & 1: + size = 4 if (fl & 2) else (16 if (fl & 4) else 8) + ea = f"{s16(imm & (0x10000 - size)):#x}({REG[src2]}){pp}" else: - rd = REG[dest] - if m.startswith('st') or m.startswith('fst'): - return m, f"{rd},{off:#x}({REG[src2]})" - return m, f"{off:#x}({REG[src2]}),{rd}" + ea = f"{REG[src1]}({REG[src2]}){pp}" + if op < 0x0a: + return m, f"{ea},{FREG[dest]}" + return m, f"{FREG[dest]},{ea}" if op == 0x02: return "ixfr", f"{REG[src1]},{FREG[dest]}" if op == 0x0c: @@ -92,9 +114,17 @@ def decode(w, addr): if op == 0x12: return dec_fp(w) if op == 0x13: - # core escape: sub-op in low bits; calli common - if (w & 0x7ff) == 0 or True: + # core escape: sub-op in low 5 bits (1=lock, 2=calli, 4=intovr, 7=unlock) + sub = w & 0x1f + if sub == 0x02: return "calli", f"{REG[src1]}" + if sub == 0x01: + return "lock", "" + if sub == 0x07: + return "unlock", "" + if sub == 0x04: + return "intovr", "" + return "esc", f"{sub:#x}" if op in (0x14, 0x15, 0x16, 0x17): base = "btne" if op < 0x16 else "bte" broff = s16(((dest << 11) | (w & 0x7ff)) & 0xffff) @@ -110,6 +140,9 @@ def decode(w, addr): if op in (0x18, 0x19): off = s16(imm & 0xfff8) return "pfld.d", f"{off:#x}({REG[src2]}),{FREG[dest]}" + if op == 0x2d: # bla isrc1,isrc2,sbroff (split offset like bte) + off = s16((((w >> 16) & 0x1f) << 11 | (w & 0x7ff)) & 0xffff) + return "bla", f"{REG[src1]},{REG[src2]},{addr + 4 + off*4:#010x}" if (op & 0x3e) in ARITH: m = ARITH[op & 0x3e] if op & 1: # immediate form diff --git a/emulator/firmware-decomp/emu860.py b/emulator/firmware-decomp/emu860.py index ff374aa..e078849 100644 --- a/emulator/firmware-decomp/emu860.py +++ b/emulator/firmware-decomp/emu860.py @@ -109,7 +109,10 @@ CTRL_NAMES = {0: 'fir', 1: 'psr', 2: 'dirbase', 3: 'db', 4: 'fsr', 5: 'epsr'} class I860: - DATA_BASE = int(os.environ.get('EMU_DATA_BASE', '0'), 0) # .data/.bss link base (experiment: sweep) + # .data link base = 0x1000, DEFINITIVE: VREND.MNG carries its original COFF + # header in the file tail (offset 12+tsize+dsize+16): .data vaddr=0x00001000, + # .bss vaddr=0x0001f940, .text vaddr=0xf0400000, entry=0xf0400000. + DATA_BASE = int(os.environ.get('EMU_DATA_BASE', '0x1000'), 0) def __init__(self, trace=0, logf=None): self.r = [0] * 32 # integer regs (r0 == 0) @@ -140,7 +143,11 @@ class I860: # single-precision float helpers @staticmethod - def f2b(x): return struct.unpack(' +/-inf + return 0xff800000 if x < 0 else 0x7f800000 @staticmethod def b2f(b): return struct.unpack('> 11) & 0x1f imm = w & 0xffff - # ---- 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): - if _INDEXED and not (op & 1): # EVEN opcode = register-indexed (VR_REMOT.S) - ea = self.rd(src2) + self.rd(src1) - else: - m = 0xffff if op < 0x04 else (0xfff8 if op >= 0x08 else 0xfffc) + # ---- integer loads: ld.b (0x00/0x01), ld.s/ld.l (0x04/0x05) ---- + # dest = bits20:16. ODD op = immediate s16 offset; EVEN op = register-indexed + # EA = src2 + src1 (ground truth: DNC.O `ld.b -1(fp),r19`=0473ffff, + # VR_REMOT.S `ld.l r30(r31),r31`, DNC.S `ld.l r0(r10),r16`). + # For op 4/5 the instr bit0 selects size: 0 = .s (16-bit), 1 = .l (32-bit) + # (DNC.O `ld.l 52(fp),r23` = 14770035, imm = 52|1). ld.b/ld.s sign-extend + # (compiled code masks with `and 0xff` right after ld.b). + if op in (0x00, 0x01, 0x04, 0x05): + if op & 1: + m = 0xffff if op == 0x01 else (0xfffc if (w & 1) else 0xfffe) ea = self.rd(src2) + s16(imm & m) - 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 + else: + ea = self.rd(src2) + self.rd(src1) + if op < 0x04: + v = self.mem.r8(ea) + if v & 0x80: v -= 0x100 + elif w & 1: # ld.l + v = self.mem.r32(ea) + else: # ld.s + v = self.mem.r16(ea) + if v & 0x8000: v -= 0x10000 + self.wr(dest, v); return + # ---- integer stores: st.b (0x03), st.s/st.l (0x07) ---- + # source = src1 (bits15:11); the 16-bit offset is SPLIT high=bits20:16, + # low=bits10:0 (DNC.O `st.b r19,-1(fp)` = 0c7f9fff). Offset bit0 selects + # st.s (0) vs st.l (1), same flag as loads. + if op in (0x03, 0x07): + off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff) + if op == 0x03: + self.mem.w8(self.rd(src2) + s16(off), self.rd(src1) & 0xff) + elif off & 1: # st.l + self.mem.w32(self.rd(src2) + s16(off & 0xfffc), self.rd(src1)) + else: # st.s + self.mem.w16(self.rd(src2) + s16(off & 0xfffe), self.rd(src1) & 0xffff) 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) + # ---- FP loads/stores: fld (0x08/0x09), fst (0x0a/0x0b) ---- + # FP reg = dest field for BOTH (load target / store source; fst does NOT use + # the split-store encoding -- `fst.l f20,0x3c88(r31)` = 2ff43c8a, imm=0x3c88|2). + # ODD op = flat s16 immediate offset; EVEN op = register-indexed (src2+src1). + # Flag bits (low bits of the offset field): bit0 = auto-increment (base <- EA), + # bit1: 1 = .l (32-bit), 0 = .d (64-bit); bit2 with bit1=0 = .q (128-bit). + # Derived from AS860 .S<->.O pairs (OPTFLOAT/TRISTRIP/ZBUF32, n>=28 each). + if op in (0x08, 0x09, 0x0a, 0x0b): + fl = w & 7 + size = 4 if (fl & 2) else (16 if (fl & 4) else 8) + if op & 1: + off = s16(imm & (0x10000 - size)) + else: + off = self.rd(src1) 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 + if fl & 1: # auto-increment + self.wr(src2, ea) + b0 = dest & ~((size // 4) - 1) + if op < 0x0a: # fld + for i in range(size // 4): + self.fwr(b0 + i, self.mem.r32(ea + i * 4)) + else: # fst + for i in range(size // 4): + self.mem.w32(ea + i * 4, self.frd(b0 + i)) return if op == 0x0c: # ld.c ctrl,dest self.wr(dest, self.cr.get(src2, 0)); return @@ -229,8 +273,17 @@ class I860: # ---- control transfer ---- if op == 0x10: # bri src1 (indirect, delayed) self.branch(self.rd(src1)); return - if op == 0x13: # calli src1 - self.wr(1, pc + 8); self.branch(self.rd(src1)); return + if op == 0x13: # CORE ESCAPE: sub-op in low bits + sub = w & 0x1f + if sub == 0x02: # calli src1 + self.wr(1, pc + 8); self.branch(self.rd(src1)); return + if sub in (0x01, 0x07): # lock / unlock (bus lock for atomic RMW) + return # single-CPU emulation: no-op + if sub == 0x04: # intovr (trap on overflow) -- ignore + return + self.stop = True + self.stopmsg = f"core-escape sub {sub:#x} @ {pc:#010x} w={w:08x}" + return if op == 0x1a: # br self.branch(pc + 4 + s26(w & 0x03ffffff) * 4); return if op == 0x1b: # call @@ -267,6 +320,25 @@ class I860: else: # subs: src1 - src2 self.wr(dest, i32(a) - i32(b)); self.set_cc(i32(a) < i32(b)) return + if op == 0x2d: # bla isrc1,isrc2,sbroff (loop: branch on LCC + add) + # Canonical idiom (DNC.S/compiler output): adds -1,rN,r18; adds -1,r0,r17; + # bla r17,r18,LOOP; ; LOOP: body...; bla r17,r18,LOOP; + # Semantics: taken = old LCC; src2 += src1; delayed branch if taken. + # LCC rule is SIGN-dependent (i860 manual): src1 < 0 -> LCC = (signed + # sum >= 0); src1 >= 0 -> LCC = unsigned carry. The signed rule makes a + # spent countdown (src1=-1, src2=-1) yield LCC=0, so a stray follow-on + # bla falls through instead of spinning (seen at 0xf041ce68). + a = self.rd(src1); b = self.rd(src2) + taken = getattr(self, 'lcc', 0) + if i32(a) < 0: + self.lcc = 1 if i32(u32(i32(a) + i32(b))) >= 0 else 0 + else: + self.lcc = 1 if (u32(a) + u32(b)) > MASK32 else 0 + self.wr(src2, u32(a) + u32(b)) + if taken: + off = s16((((w >> 16) & 0x1f) << 11 | (w & 0x7ff)) & 0xffff) + self.branch(pc + 4 + off * 4) + return if base in (0x28, 0x2a, 0x2c, 0x2e): # shl shr shrd shra cnt = (s16(imm) & 0x1f) if (op & 1) else (self.rd(src1) & 0x1f) b = self.rd(src2) diff --git a/emulator/firmware-decomp/emu_main.py b/emulator/firmware-decomp/emu_main.py new file mode 100644 index 0000000..907b606 --- /dev/null +++ b/emulator/firmware-decomp/emu_main.py @@ -0,0 +1,238 @@ +"""Run VREND.MNG's own main() and feed it real wire captures through dN_receive. + +This replaces the per-handler harness (emu_replay) with 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 only +hook the transputer-LINK primitives (they'd block on real hardware); everything +else -- init, do_init, dispatch, handlers, allocator -- is the firmware's own code. + +Firmware addresses (source-matched disassembly, VREND.C + VR_REMOT.C): + main 0xf0403f10 (processor-0 path; _processorId in .data) + bla 0xf04285f0 busy-wait (also tail-called with manual r1) + dN_mynode 0xf0402570 *arg = my node id -> 2 (replying node) + dN_nodes 0xf0402610 *arg = total nodes -> 3 (1 view) + dN_receive 0xf0402450 (&client,&bytes,buf,flag) <- wire frames + remote_velocirender 0xf040eea0 + velocirender_init 0xf040e300 (called by the loop on action 0) + do_init 0xf040afb0 (deferred heavy init) + +Usage: python emu_main.py [capture.raw.bin] [--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, MNG, BOOT_ACTIONS +from vrboard import A + +MAIN = 0xf0403f10 +BLA = 0xf04285f0 +DN_MYNODE = 0xf0402570 +DN_NODES = 0xf0402610 +DN_RECEIVE = 0xf0402450 +V_INIT = 0xf040e300 +DO_INIT = 0xf040afb0 + +ANAME = {int(a): a.name for a in A} + + +class MainRunner: + def __init__(self, cap, verbose=False, max_cmds=None): + self.cpu = emu860.I860(trace=0) + self.cpu.load_mng(MNG) + self.cpu.map_control(); self.cpu.map_board() + # Transputer-boot environment: the monitor pokes the processor id into the + # first .data word before starting the app (file ships garbage there). + # main() reads [0x1000] (_processorId) and spins the processor-1 path if != 0. + self.cpu.mem.w32(0x1000, 0) + # Monitor-provided MEMORY environment (we are the transputer/monitor). + # The page allocator (0xf04350b8) reads region descriptors from the shared + # control block: [0x23ae8]=0xfffff800 (DRAM bank 0 @0x08000000) and + # [0x23c84]=0xfffff80c (bank 1 @0x0C000000) -- set by 0xf0434e44, populated + # by the monitor. Layout: desc={+0: first extent, +8: free bytes}; + # extent (in DRAM) ={+0: next extent, +8: extent size}. + RAM0, RAM1, RSIZE = 0x08000000, 0x0C000000, 0x01000000 # 16MB per bank + for desc, base in ((0xfffff800, RAM0), (0xfffff80c, RAM1)): + self.cpu.mem.w32(desc + 0, base) + self.cpu.mem.w32(desc + 4, 0) + self.cpu.mem.w32(desc + 8, RSIZE) + self.cpu.mem.w32(base + 0, 0) + self.cpu.mem.w32(base + 4, 0) + self.cpu.mem.w32(base + 8, RSIZE) + # The bss pointers to those descriptors are normally stored by the meminit + # routine 0xf0434df4 (monitor start path we don't run) -- store them ourselves. + self.cpu.mem.w32(0x23ae8, 0xfffff800) + self.cpu.mem.w32(0x23c84, 0xfffff80c) + # malloc's sbrk break pointers at [0x1ecc0]/[0x1ecc4]: file ships garbage + # (startup-written slots, like _processorId). Zero => sbrk lazy-inits itself + # through the (hooked) page allocator. + self.cpu.mem.w32(0x1ecc0, 0) + self.cpu.mem.w32(0x1ecc4, 0) + # Shared "uncached block" pointers at [0x14fc4]/[0x14fc8]/[0x14fcc] + # (transputer-visible communication blocks with embedded spinlocks at +0x18, + # used by the link/console paths). Monitor-provided; point them at fresh + # zeroed RAM so the locks start released. + self.cpu.mem.w32(0x14fc4, 0x00300000) + self.cpu.mem.w32(0x14fc8, 0x00301000) + self.cpu.mem.w32(0x14fcc, 0x00302000) + # Page-directory base used by the allocator's VM bookkeeping ([0x22590]); + # tables live at 0x76000000 on the real board. We don't translate (flat + # memory), the firmware just needs somewhere writable to keep them. + self.cpu.mem.w32(0x22590, 0x76000000) + # Feed EVERYTHING through the firmware, including the 860-boot actions: + # cap7 starts with args860/code860/data860/bss860 -- the host DOWNLOADS an + # extra i860 module (the PAZ/sfx layer) that installs the runtime handler + # tables and system objects. Skipping them leaves those tables empty. + # (hspcode is transputer-side; keep it out.) + cmds = [(a, p) for a, p in parse_capture(cap) if a != A.hspcode] + if max_cmds: cmds = cmds[:max_cmds] + self.queue = cmds + self.qi = 0 + self.verbose = verbose + self.done = {} + self.stubbed = {} # discovered link primitives -> hit count + # Page allocation: the firmware's own allocator (0xf04350b8) maintains the + # board's two-level VM page tables, which the MONITOR bootstraps (pre-mapped + # DRAM PTEs at 0x76000000, phys->virt bias at [0x23ca0]) -- replicating that + # faithfully is Tier-2 (run VRENDMON on an emulated T425). Until then we hook + # the allocator core with a bump allocator over the DRAM banks; callers are: + # r16 = region selector (-1 = any, 0 = bank0, 1 = bank1), r17 = page count. + # Node addresses are opaque to the scene/render logic, so the IGC stream is + # unaffected by allocator layout. + self.heap = [0x08010000, 0x0c010000] # small offset: skip extent headers + self.hooks = { + BLA: self.h_ret, + DN_MYNODE: lambda c: self.h_writeback(c, 2), + DN_NODES: lambda c: self.h_writeback(c, 3), + DN_RECEIVE: self.h_receive, + 0xf04350b8: self.h_allocpages, + # virt->phys page translator (walks the monitor's page tables, which we + # don't build): flat memory => identity. Returns the PTE for r16's page. + 0xf0434d90: lambda c: self.h_ret(c, c.rd(16) & ~0xfff), + # reply path (replying = me==2): 0xf040ae90 = reply wrapper (tail-called + # from the loop with r1 = loop top), 0xf0402370 = dN_send-style link + # primitive. We log the reply value and return. + 0xf040ae90: self.h_reply, + 0xf0402370: self.h_ret, + # outgoing-message writer (same shared_cntl+0x18 + mutex shape as the + # send primitive; do_init calls it with message-type words like 0x30 + # whose type-nibble has no local jump-table entry). Log + drop. + 0xf04030c8: self.h_outmsg, + # Spinlock acquire/release (i860 lock/unlock bus primitives guarding + # shared blocks). Single-threaded emulation: no-op both, so a skipped + # release inside a hooked path can never deadlock us. + 0xf042bdf0: self.h_ret, + 0xf042be48: self.h_ret, + # Dispatch-tail chain (create's table sends types 2-5 to 0xf040bd0c = + # the `or 0xece8,r31,r31` inside new_node's dispatch, reusing its + # per-type init table). The `or` needs r31=0 on entry; a jump arrives + # with r31 = the entry address itself. Zero it and execute normally. + 0xf040bd0c: self.h_chainfix, + } + self.replies = [] + self.outmsgs = [] + + def h_chainfix(self, cpu): + if cpu.rd(31) == cpu.pc: # arrived via a table jump (r31 = entry addr) + idx4 = cpu.rd(30) # (type-2)*4, set by the dispatching code + tgt = cpu.mem.r32(0xece8 + idx4) + cpu.wr(31, 0) + # entry pointing back at the chain = "no per-type init" -> common tail + cpu.pc = 0xf040bd1c if tgt == 0xf040bd0c else tgt + return True + cpu.step() # normal fall-through execution of the `or` + return True + + def h_outmsg(self, cpu): + self.outmsgs.append((cpu.rd(16), cpu.rd(17))) + return self.h_ret(cpu) + + def h_reply(self, cpu): + self.replies.append(cpu.rd(16)) + 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) + + # ---- 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_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 = 0 + 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 % 2000 == 0 or self.qi <= 20: + print(f" [{self.qi:5}/{len(self.queue)}] {n:<22} len={len(pl):6} " + f"steps={cpu.steps:>12,} igc={len(cpu.igc)}") + return self.h_ret(cpu) + + def run(self, budget=2_000_000_000): + 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 = MAIN + watch = {V_INIT: 'velocirender_init', DO_INIT: '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:,})") + 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' + 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, verbose=verbose, max_cmds=mc) + print(f"{os.path.basename(cap)}: {len(r.queue)} wire commands queued") + cpu = r.run() + print(f"\nreplayed: {r.done}") + print(f"IGC writes: {len(cpu.igc)} board-log entries: {len(cpu.board_log)}") + if cpu.igc: + out = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'igc_stream.bin') + with open(out, 'wb') as f: + for addr, val in cpu.igc: + f.write(struct.pack(' {out}") + + +if __name__ == '__main__': + main()