Files
TeslaRel410/emulator/firmware-decomp/emu860.py
T
CydandClaude Opus 4.8 a688cc1c30 i860 emu: ISA round 2 (ground-truth encodings) + authentic-main runner; firmware
processes the full wire boot incl. the downloaded PAZ/sfx module

ISA fixes, all derived from the toolchain's own .S<->.O pairs (AS860.ZIP:
OPTFLOAT/TRISTRIP/ZBUF32, plus DNC.O) and the firmware's linked COFF header:

- DATA_BASE = 0x1000 DEFINITIVE: VREND.MNG carries its original COFF header in
  the file tail (.data vaddr 0x1000, .bss 0x1f940, entry 0xf0400000).
- Integer loads: even opcodes are register-indexed (EA = src2 + src1); op 4/5
  size flag = instr bit0 (0 = ld.s 16-bit, 1 = ld.l 32-bit); ld.b/ld.s
  sign-extend.
- Integer stores: st.s/st.l selected by offset bit0, same split-offset rule.
- FP loads/stores: FP register lives in the DEST field for both fld and fst
  (fst does NOT use the integer split-store encoding); flag bits: bit0 =
  auto-increment (base <- EA), bit1 1=.l/0=.d, bit2 = .q; .d/.q span register
  pairs/quads. ~450 fld.d + ~300 fst.d were previously read/written 32-bit.
- bla (op 0x2d, was misdecoded as shrd): branch-on-LCC-and-add with the
  sign-dependent LCC rule (src1<0 -> signed sum >= 0), so spent countdown
  loops terminate. 335 bla instructions in the firmware.
- CORE ESCAPE (op 0x13): sub-op 1 = lock, 2 = calli, 7 = unlock. Previously
  everything decoded as calli, so every spinlock acquire jumped to address 0 --
  this was the phantom "exit stub" behind most earlier derails.
- f2b: IEEE overflow -> +/-inf instead of raising.

emu_main.py (new): runs the firmware's OWN main() (0xf0403f10) and feeds real
wire captures through a hooked dN_receive, so init/do_init/dispatch/handlers
all execute authentically. Provides the transputer-monitor environment
(processor id, DRAM region descriptors in the shared control block, sbrk/
shared-block seed slots) and hooks only the link primitives (bla busy-wait,
dN_mynode/dN_nodes/dN_receive/dN_send, putchar path, spinlocks, page allocator
+ virt->phys translator pending Tier-2 VRENDMON).

KEY DISCOVERY: the capture's args860/code860/data860/bss860 preamble is the
host DOWNLOADING an additional i860 module (the PAZ/sfx renderer layer, banner
"i860 50MHz") which installs the runtime handler tables and system objects.
Feeding it through the firmware's own handlers, the module loads and makes the
first IGC board-register writes. State: 834+ wire commands processed (module
download + init + create); first draw_scene sits at command 1568.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:26:10 -05:00

536 lines
24 KiB
Python

"""Intel i860 interpreter -- Tier 0 of the VelociRender emulator.
Goal: execute the real firmware (VREND.MNG) so the board's own code produces the
render, rather than us reinterpreting the wire. Reuses the validated decoder
(dis860) for tracing; execution semantics are implemented here.
Status: FOUNDATION. Core integer / load-store / branch / basic FP, delay slots,
sparse memory with MMIO traps, VREND.MNG loader. Run it to see how far the
firmware gets and which peripherals/instructions it needs next.
python emu860.py <VREND.MNG> [--trace N] [--steps N]
"""
import sys, os, struct
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import dis860
PAGE = 1 << 16
MASK32 = 0xFFFFFFFF
_INDEXED = os.environ.get('EMU_INDEXED', '0') == '1' # experiment: register-indexed even loads
def s16(v): return v - 0x10000 if v & 0x8000 else v
def s26(v): return v - 0x4000000 if v & 0x2000000 else v
def u32(v): return v & MASK32
def i32(v):
v &= MASK32
return v - (1 << 32) if v & 0x80000000 else v
class Mem:
"""Sparse page memory. RAM pages autocreate on write; reads of unmapped RAM
return 0 (logged once). MMIO ranges dispatch to callbacks."""
def __init__(self, log):
self.pages = {}
self.mmio = [] # (lo, hi, read_cb, write_cb, name)
self.log = log
self._warned = set()
def map_mmio(self, lo, hi, rd, wr, name):
self.mmio.append((lo, hi, rd, wr, name))
def _page(self, addr, create):
pn = addr >> 16
p = self.pages.get(pn)
if p is None and create:
p = self.pages[pn] = bytearray(PAGE)
return p
def load_blob(self, addr, data):
for i, b in enumerate(data):
p = self._page(addr + i, True)
p[(addr + i) & 0xFFFF] = b
def _mmio(self, addr):
for lo, hi, rd, wr, name in self.mmio:
if lo <= addr < hi:
return (rd, wr, name)
return None
def r32(self, addr):
addr = u32(addr)
m = self._mmio(addr)
if m: return u32(m[0](addr))
off = addr & 0xFFFF
if off <= 0xFFFC:
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('<I', p, off)[0]
return (self.r8(addr) | (self.r8(addr+1) << 8) |
(self.r8(addr+2) << 16) | (self.r8(addr+3) << 24))
def w32(self, addr, val):
addr = u32(addr); val = u32(val)
m = self._mmio(addr)
if m: return m[1](addr, val)
off = addr & 0xFFFF
if off <= 0xFFFC:
struct.pack_into('<I', self._page(addr, True), off, val)
else:
for i in range(4): self.w8(addr + i, (val >> (8 * i)) & 0xFF)
def r16(self, addr):
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('<H', p, addr & 0xFFFF)[0]
def r8(self, addr):
addr = u32(addr); p = self._page(addr, False)
return p[addr & 0xFFFF] if p else 0
def w16(self, addr, val):
addr = u32(addr)
if (addr & 0xFFFF) == 0xFFFF:
self.w8(addr, val & 0xFF); self.w8(addr + 1, (val >> 8) & 0xFF)
else:
struct.pack_into('<H', self._page(addr, True), addr & 0xFFFF, val & 0xFFFF)
def w8(self, addr, val):
self._page(u32(addr), True)[addr & 0xFFFF] = val & 0xFF
CTRL_NAMES = {0: 'fir', 1: 'psr', 2: 'dirbase', 3: 'db', 4: 'fsr', 5: 'epsr'}
class I860:
# .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)
self.f = [0] * 32 # FP regs as raw 32-bit (f0,f1 == 0)
self.cr = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0} # control regs
self.pc = 0
self.mem = Mem(self.log)
self.trace = trace
self.logf = logf or sys.stdout
self.steps = 0
self.stop = False
self.stopmsg = ''
self._branch = None # (target, delayed?)
self.tailn = 60 # ring buffer of last-N executed
self.tail = []
self.stopat = set() # halt when pc first reaches one of these
def log(self, s):
self.logf.write(s + "\n")
# ---- register helpers (r0 hardwired 0) ----
def rd(self, i): return self.r[i] if i else 0
def wr(self, i, v):
if i: self.r[i] = u32(v)
def frd(self, i): return 0 if i < 2 else self.f[i]
def fwr(self, i, v):
if i >= 2: self.f[i] = u32(v)
# single-precision float helpers
@staticmethod
def f2b(x):
try:
return struct.unpack('<I', struct.pack('<f', x))[0]
except OverflowError: # IEEE overflow -> +/-inf
return 0xff800000 if x < 0 else 0x7f800000
@staticmethod
def b2f(b): return struct.unpack('<f', struct.pack('<I', u32(b)))[0]
def set_cc(self, cond):
# psr bit 2 = CC (condition code)
if cond: self.cr[1] |= 0x4
else: self.cr[1] &= ~0x4
def cc(self): return (self.cr[1] >> 2) & 1
# ------------- one instruction -------------
def step(self):
pc = self.pc
if pc in self.stopat:
self.stop = True; self.stopmsg = f"stopat {pc:#010x}"; return False
w = self.mem.r32(pc)
if self.tailn:
self.tail.append((pc, w))
if len(self.tail) > self.tailn: self.tail.pop(0)
if self.trace and self.steps < self.trace:
m, ops = dis860.decode(w, pc)
self.log(f"{pc:#010x}: {w:08x} {m:<10} {ops}")
self._branch = None
self.execute(w, pc)
self.steps += 1
if self.stop:
return False
if self._branch is not None:
target, delayed = self._branch
if delayed:
# execute one delay-slot instruction, then jump
ds = pc + 4
w2 = self.mem.r32(ds)
if self.trace and self.steps < self.trace:
m, ops = dis860.decode(w2, ds)
self.log(f"{ds:#010x}: {w2:08x} {m:<10} {ops} ; [delay slot]")
self._branch = None
self.execute(w2, ds)
self.steps += 1
self.pc = self._branch[0] if self._branch else target
else:
self.pc = target
else:
self.pc = pc + 4
return not self.stop
def branch(self, target, delayed=True):
self._branch = (u32(target), delayed)
def execute(self, w, pc):
op = (w >> 26) & 0x3f
src2 = (w >> 21) & 0x1f
dest = (w >> 16) & 0x1f
src1 = (w >> 11) & 0x1f
imm = w & 0xffff
# ---- 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)
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
# ---- 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 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
if op == 0x0e: # st.c src1,ctrl
self.cr[src2] = self.rd(src1); return
if op == 0x02: # ixfr src1 -> fdest (int->FP reg move)
self.fwr(dest, self.rd(src1)); return
# ---- control transfer ----
if op == 0x10: # bri src1 (indirect, delayed)
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
self.wr(1, pc + 8); self.branch(pc + 4 + s26(w & 0x03ffffff) * 4); return
if op in (0x1c, 0x1d): # bc / bc.t (branch if CC)
tgt = pc + 4 + s26(w & 0x03ffffff) * 4
if self.cc(): self.branch(tgt, delayed=(op == 0x1d))
return
if op in (0x1e, 0x1f): # bnc / bnc.t (branch if !CC)
tgt = pc + 4 + s26(w & 0x03ffffff) * 4
if not self.cc(): self.branch(tgt, delayed=(op == 0x1f))
return
if op in (0x14, 0x15, 0x16, 0x17): # btne / bte (compare & branch)
broff = s16(((dest << 11) | (w & 0x7ff)) & 0xffff)
a = src1 if (op & 1) else self.rd(src1) # even = 5-bit const
b = self.rd(src2)
take = (a != b) if op < 0x16 else (a == b)
if take: self.branch(pc + 4 + broff * 4, delayed=False)
return
# ---- 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: # 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 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; <delay>; LOOP: body...; bla r17,r18,LOOP; <delay>
# 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)
if base == 0x28: r = b << cnt # shl
elif base == 0x2a: r = b >> cnt # shr (logical)
elif base == 0x2e: r = i32(b) >> cnt # shra
else: r = b >> cnt # shrd (approx)
self.wr(dest, r); return
if base in (0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e): # and..xorh
a = imm if (op & 1) else self.rd(src1)
hi = base in (0x32, 0x36, 0x3a, 0x3e)
if hi and (op & 1): a = imm << 16
b = self.rd(src2)
g = base & 0x3c
if g == 0x30: r = b & a # and/andh
elif g == 0x34: r = b & ~a # andnot/andnoth
elif g == 0x38: r = b | a # or/orh
else: r = b ^ a # xor/xorh
self.wr(dest, r)
self.set_cc((r & MASK32) == 0) # i860 logicals set CC = (result == 0)
return
# ---- FP unit (opcode 0x12) ----
if op == 0x12:
self.exec_fp(w, src1, src2, dest); return
# ---- unhandled ----
m, ops = dis860.decode(w, pc)
self.stop = True
self.stopmsg = f"unimplemented op {op:#04x} ({m} {ops}) @ {pc:#010x} w={w:#010x}"
# precision-aware FP register access (doubles occupy register PAIRS:
# f[N]=low32, f[N+1]=high32, little-endian)
def rdf(self, reg, dbl):
if dbl:
b = reg & ~1
return struct.unpack('<d', struct.pack('<II', self.frd(b), self.frd(b | 1)))[0]
return self.b2f(self.frd(reg))
def wrf(self, reg, val, dbl):
if dbl:
lo, hi = struct.unpack('<II', struct.pack('<d', val))
b = reg & ~1
self.fwr(b, lo); self.fwr(b | 1, hi)
else:
self.fwr(reg, self.f2b(val))
def exec_fp(self, w, src1, src2, dest):
sub = w & 0x7f
sp = (w >> 7) & 1 # source precision (1 = double)
rp = (w >> 8) & 1 # result precision
# NOTE: pf* pipelined ops are treated as non-pipelined (functional) for now.
if sub == 0x20: # fmul
self.wrf(dest, self.rdf(src1, sp) * self.rdf(src2, sp), rp)
elif sub == 0x30: # fadd
self.wrf(dest, self.rdf(src1, sp) + self.rdf(src2, sp), rp)
elif sub == 0x31: # fsub
self.wrf(dest, self.rdf(src1, sp) - self.rdf(src2, sp), rp)
elif sub == 0x33: # famov (move src1, precision-convert)
self.wrf(dest, self.rdf(src1, sp), rp)
elif sub == 0x22: # frcp (reciprocal of src2)
b = self.rdf(src2, sp)
self.wrf(dest, (1.0 / b) if b else 0.0, rp)
elif sub == 0x23: # frsqr (recip sqrt of src2)
import math
b = self.rdf(src2, sp)
self.wrf(dest, (1.0 / math.sqrt(b)) if b > 0 else 0.0, rp)
elif sub in (0x32, 0x3a): # fix (round) / ftrunc -> int in FP reg
a = self.rdf(src1, sp)
iv = int(a) if sub == 0x3a else int(a + (0.5 if a >= 0 else -0.5))
self.fwr(dest & ~1 if rp else dest, iv & 0xFFFFFFFF)
elif sub == 0x34: # fgt: CC = src1 > src2
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)
self.fwr(dest, self.frd(src1) + self.frd(src2))
elif sub == 0x4d: # fisub
self.fwr(dest, self.frd(src1) - self.frd(src2))
elif sub == 0x5f: # fnop
pass
else:
self.stop = True
self.stopmsg = f"unimplemented FP sub {sub:#04x} @ {self.pc:#010x}"
# ------------- board control / CCB region (0xFFFFxxxx) -------------
def map_control(self, cfg=None):
"""Model the transputer<->i860 control/config + CCB region.
cfg: {addr: value} overrides for the 0xFFFFF7xx config registers."""
self.ctrl = {0xfffff720: 2, # must read 2 (IO_ACK) for normal boot
0xfffff70c: 1} # board-config select (1 = CCB @0xffffe000 path)
if cfg: self.ctrl.update(cfg)
self.ctrl_log = []
def rd(a):
v = self.ctrl.get(a, 0)
self.ctrl_log.append(('r', a, v))
return v
def wr(a, v):
self.ctrl[a] = v
self.ctrl_log.append(('w', a, v))
self.mem.map_mmio(0xfff00000, 0x100000000, rd, wr, 'ctrl/ccb')
# ------------- board / IGC MMIO (logged) -------------
def map_board(self):
"""Log accesses to the board-register (0x8380_xxxx) and IGC/DMA regions,
and capture the IGC coefficient stream (Tier-1 handoff)."""
self.board_log = []
self.igc = [] # captured (addr,val) IGC coefficient writes
def brd_rd(a): self.board_log.append(('r', a, 0)); return 0
def brd_wr(a, v): self.board_log.append(('w', a, v))
self.mem.map_mmio(0x83000000, 0x84000000, brd_rd, brd_wr, 'board')
# ------------- call harness (direct handler invocation) -------------
RET_SENTINEL = 0xbadca110
def call(self, addr, args=(), sp=0x000c0000, maxsteps=2_000_000):
"""Invoke a firmware function directly (PGI conv: args r16.., ret r16,
r1=return, r2=sp). Runs until it returns to the sentinel. Returns r16."""
self.wr(1, self.RET_SENTINEL)
self.wr(2, sp)
for i, a in enumerate(args):
self.wr(16 + i, a)
self.pc = u32(addr)
self.stop = False; self.stopmsg = ''
n0 = self.steps
while self.steps - n0 < maxsteps:
if self.pc == self.RET_SENTINEL:
return self.rd(16)
if not self.step():
return None # faulted (stopmsg set)
self.stop = True; self.stopmsg = f"call {addr:#x} ran {maxsteps} steps (no return)"
return None
# ------------- loader -------------
def load_mng(self, path, base=0xf0400000):
d = open(path, 'rb').read()
tsize, dsize, bsize = struct.unpack_from('<III', d, 0)
text = d[0x0c:0x0c + tsize]
data = d[0x0c + tsize:0x0c + tsize + dsize]
# text at `base` (high, ~0xf0400000); entry = base (the veneer).
# .data/.bss are linked LOW -- the code builds global addresses like
# 0xec10 / 0x22644 (< 0x30000). Load .data at data_base; .bss (zero-init)
# follows and autocreates on write (reads of unwritten bss return 0).
self.mem.load_blob(base, text)
self.mem.load_blob(self.DATA_BASE, data)
self.text_base, self.tsize = base, tsize
self.data_base, self.dsize = self.DATA_BASE, dsize
self.bss_base, self.bss = self.DATA_BASE + dsize, bsize
self.pc = base
# a stack somewhere sane
self.wr(2, 0x00080000) # r2 = sp (guess; low RAM)
return tsize, dsize, bsize
def main():
args = [a for a in sys.argv[1:] if not a.startswith('--')]
mng = args[0] if args else r'C:\VWE\TeslaRel410\sda4\RPLIVE\VREND.MNG'
trace = 40; steps = 2000
for i, a in enumerate(sys.argv):
if a == '--trace': trace = int(sys.argv[i + 1])
if a == '--steps': steps = int(sys.argv[i + 1])
cpu = I860(trace=trace)
t, dd, b = cpu.load_mng(mng)
cpu.map_control()
print(f"loaded {os.path.basename(mng)}: text={t:#x} data={dd:#x} bss={b:#x} "
f"entry={cpu.pc:#010x}\n--- trace ---")
while cpu.steps < steps and cpu.step():
pass
print("--- halt ---")
print(f"steps={cpu.steps} pc={cpu.pc:#010x}")
if cpu.stopmsg: print("STOP:", cpu.stopmsg)
print("regs:", " ".join(f"r{i}={cpu.r[i]:#x}" for i in range(8)))
print("ctrl/ccb accesses (last 15):")
for k, a, v in cpu.ctrl_log[-15:]:
print(f" {k} {a:#010x} = {v:#x}")
if '--tail' in sys.argv:
print("--- last executed (tail) ---")
seen = {}
for pc, w in cpu.tail:
m, ops = dis860.decode(w, pc)
print(f" {pc:#010x}: {w:08x} {m:<10} {ops}")
if __name__ == '__main__':
main()