i860 emu: pipelined FP unit + dual-ops, adds CC sign rule, flush op;
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>
This commit is contained in:
@@ -263,6 +263,13 @@ class I860:
|
||||
for i in range(size // 4):
|
||||
self.mem.w32(ea + i * 4, self.frd(b0 + i))
|
||||
return
|
||||
if op == 0x0d: # flush #const(src2)[++] (cache-line flush)
|
||||
# Store-format split offset; cache line = 32B so low 5 bits are free:
|
||||
# bit0 = auto-increment (src2 <- src2+offset). No memory effect for us.
|
||||
off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff)
|
||||
if off & 1:
|
||||
self.wr(src2, self.rd(src2) + s16(off & 0xffe0))
|
||||
return
|
||||
if op == 0x0c: # ld.c ctrl,dest
|
||||
self.wr(dest, self.cr.get(src2, 0)); return
|
||||
if op == 0x0e: # st.c src1,ctrl
|
||||
@@ -311,10 +318,11 @@ class I860:
|
||||
# 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
|
||||
if base == 0x20: # addu: CC = carry out
|
||||
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 == 0x24: # adds: CC = result negative
|
||||
s = i32(a) + i32(b); self.wr(dest, s)
|
||||
self.set_cc(bool(u32(s) & 0x80000000))
|
||||
elif base == 0x22: # subu: src1 - src2
|
||||
self.wr(dest, u32(a) - u32(b)); self.set_cc(u32(a) < u32(b))
|
||||
else: # subs: src1 - src2
|
||||
@@ -386,17 +394,93 @@ class I860:
|
||||
else:
|
||||
self.fwr(reg, self.f2b(val))
|
||||
|
||||
# ---- pipelined FP unit state (functional model, no cycle timing) ----
|
||||
# Adder pipe = 3-stage; multiplier pipe = 2-stage double / 3-stage single;
|
||||
# KR/KI/T = the dual-operation constant/transfer registers.
|
||||
def _fp_pipes(self):
|
||||
if not hasattr(self, '_apipe'):
|
||||
self._apipe = [0.0, 0.0, 0.0]
|
||||
self._mpipe = [0.0, 0.0, 0.0]
|
||||
self._kr = 0.0
|
||||
self._ki = 0.0
|
||||
self._t = 0.0
|
||||
return self._apipe, self._mpipe
|
||||
|
||||
def _padv(self, pipe, val, depth):
|
||||
out = pipe[depth - 1]
|
||||
for i in range(depth - 1, 0, -1):
|
||||
pipe[i] = pipe[i - 1]
|
||||
pipe[0] = val
|
||||
return out
|
||||
|
||||
# PFAM/PFSM dual-op routing, decoded from the validated mnemonic grammar
|
||||
# (AS860 corpus: r2p1, r2pt, r2ap1, r2apt, i2p1.., rat1p2, m12apm, ra1p2,
|
||||
# m12tpm, m12tpa, iat1p2, ia1p2 ...):
|
||||
# M-unit: r2 = KR*src2 | i2 = KI*src2 | m12 = src1*src2 |
|
||||
# ra = KR*Ares | ia = KI*Ares ('t' suffix: T <- Mres)
|
||||
# A-unit: p1 = Mres+src1 | pt = Mres+T | ap1 = Ares+src1 | apt = Ares+T |
|
||||
# apm = Ares+Mres | tpm = T+Mres | tpa = T+Ares | 1p2 = src1+src2
|
||||
# K-load: if src1 is unused by the routing, KR (r-forms) / KI (i-forms)
|
||||
# is loaded from src1. sub 0x10-0x1f = PFSM (A-unit subtracts).
|
||||
# Encoded per DPC as (m1,m2, a1,a2, kload, tload) with operand tags:
|
||||
# s1 s2 kr ki t am (adder-pipe result) mm (mul-pipe result)
|
||||
_DUAL = {
|
||||
0x0: ('kr','s2', 's1','mm', None, False), # r2p1
|
||||
0x1: ('kr','s2', 't', 'mm', 'kr', False), # r2pt
|
||||
0x2: ('kr','s2', 's1','am', None, False), # r2ap1
|
||||
0x3: ('kr','s2', 't', 'am', 'kr', False), # r2apt
|
||||
0x4: ('ki','s2', 's1','mm', None, False), # i2p1
|
||||
0x5: ('ki','s2', 't', 'mm', 'ki', False), # i2pt
|
||||
0x6: ('ki','s2', 's1','am', None, False), # i2ap1
|
||||
0x7: ('ki','s2', 't', 'am', 'ki', False), # i2apt
|
||||
0x8: ('kr','am', 's1','s2', None, True), # rat1p2 (T <- Mres)
|
||||
0x9: ('s1','s2', 'am','mm', None, False), # m12apm
|
||||
0xa: ('kr','am', 's1','s2', None, False), # ra1p2
|
||||
0xb: ('s1','s2', 't', 'am', None, False), # m12tpa (per grammar)
|
||||
0xc: ('ki','am', 's1','s2', None, True), # iat1p2 (T <- Mres)
|
||||
0xd: ('s1','s2', 't', 'mm', None, False), # m12tpm
|
||||
0xe: ('ki','am', 's1','s2', None, False), # ia1p2
|
||||
0xf: ('s1','s2', 't', 'am', None, False), # m12tpa
|
||||
}
|
||||
|
||||
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)
|
||||
pbit = (w >> 10) & 1 # P: pipelined
|
||||
if sub < 0x20: # PFAM (0x00-0x0f) / PFSM (0x10-0x1f)
|
||||
ap, mp = self._fp_pipes()
|
||||
mdepth = 2 if sp else 3
|
||||
am_out = ap[2] # retiring adder result
|
||||
mm_out = mp[mdepth - 1] # retiring multiplier result
|
||||
v = {'s1': self.rdf(src1, sp), 's2': self.rdf(src2, sp),
|
||||
'kr': self._kr, 'ki': self._ki, 't': self._t,
|
||||
'am': am_out, 'mm': mm_out}
|
||||
m1, m2, a1, a2, kload, tload = self._DUAL[sub & 0xf]
|
||||
newm = v[m1] * v[m2]
|
||||
newa = (v[a1] - v[a2]) if (sub & 0x10) else (v[a1] + v[a2])
|
||||
if kload == 'kr': self._kr = v['s1']
|
||||
elif kload == 'ki': self._ki = v['s1']
|
||||
if tload: self._t = mm_out
|
||||
self._padv(ap, newa, 3)
|
||||
self._padv(mp, newm, mdepth)
|
||||
self.wrf(dest, am_out, rp) # fdest <- adder pipe output
|
||||
return
|
||||
if sub == 0x20: # fmul / pfmul
|
||||
r = self.rdf(src1, sp) * self.rdf(src2, sp)
|
||||
if pbit:
|
||||
ap, mp = self._fp_pipes()
|
||||
self.wrf(dest, self._padv(mp, r, 2 if sp else 3), rp)
|
||||
else:
|
||||
self.wrf(dest, r, rp)
|
||||
elif sub in (0x30, 0x31): # fadd/fsub / pfadd/pfsub
|
||||
a = self.rdf(src1, sp); b = self.rdf(src2, sp)
|
||||
r = (a - b) if sub == 0x31 else (a + b)
|
||||
if pbit:
|
||||
ap, mp = self._fp_pipes()
|
||||
self.wrf(dest, self._padv(ap, r, 3), rp)
|
||||
else:
|
||||
self.wrf(dest, r, rp)
|
||||
elif sub == 0x33: # famov (move src1, precision-convert)
|
||||
self.wrf(dest, self.rdf(src1, sp), rp)
|
||||
elif sub == 0x22: # frcp (reciprocal of src2)
|
||||
@@ -408,7 +492,12 @@ class I860:
|
||||
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))
|
||||
if a != a or a in (float('inf'), float('-inf')):
|
||||
iv = 0x80000000 # IEEE indefinite
|
||||
elif abs(a) > 0x7fffffff:
|
||||
iv = 0x80000000
|
||||
else:
|
||||
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))
|
||||
|
||||
@@ -1,163 +1,107 @@
|
||||
"""Run VREND.MNG's own main() and feed it real wire captures through dN_receive.
|
||||
"""Run the render firmware'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.
|
||||
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.
|
||||
|
||||
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)
|
||||
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] [--max-cmds N] [--verbose]
|
||||
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, MNG, BOOT_ACTIONS
|
||||
from emu_replay import parse_capture
|
||||
from vrboard import A
|
||||
|
||||
MAIN = 0xf0403f10
|
||||
BLA = 0xf04285f0
|
||||
DN_MYNODE = 0xf0402570
|
||||
DN_NODES = 0xf0402610
|
||||
DN_RECEIVE = 0xf0402450
|
||||
V_INIT = 0xf040e300
|
||||
DO_INIT = 0xf040afb0
|
||||
_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, verbose=False, max_cmds=None):
|
||||
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(MNG)
|
||||
self.cpu.load_mng(self.map['fw'])
|
||||
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]
|
||||
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.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)
|
||||
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):
|
||||
@@ -169,6 +113,32 @@ class MainRunner:
|
||||
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'
|
||||
@@ -176,21 +146,27 @@ class MainRunner:
|
||||
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(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 % 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)}")
|
||||
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 run(self, budget=2_000_000_000):
|
||||
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 = MAIN
|
||||
watch = {V_INIT: 'velocirender_init', DO_INIT: 'do_init'}
|
||||
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:
|
||||
@@ -203,7 +179,7 @@ class MainRunner:
|
||||
f"{cpu.steps:,} steps"); break
|
||||
continue
|
||||
if pc in watch:
|
||||
print(f" >> {watch.pop(pc)} entered (steps={cpu.steps:,})")
|
||||
print(f" >> {watch.pop(pc)} entered (steps={cpu.steps:,})", flush=True)
|
||||
if not cpu.step():
|
||||
print(f"\nSTOP: {cpu.stopmsg}")
|
||||
import dis860
|
||||
@@ -217,21 +193,25 @@ class MainRunner:
|
||||
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, verbose=verbose, max_cmds=mc)
|
||||
print(f"{os.path.basename(cap)}: {len(r.queue)} wire commands queued")
|
||||
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.igc:
|
||||
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'igc_stream.bin')
|
||||
if cpu.board_log:
|
||||
out = os.path.join(_HERE, 'igc_stream.bin')
|
||||
with open(out, 'wb') as f:
|
||||
for addr, val in cpu.igc:
|
||||
f.write(struct.pack('<II', addr, val))
|
||||
print(f"IGC stream -> {out}")
|
||||
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__':
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
"""Marathon runner: authentic main() + cap7 through the first draw_scene frames.
|
||||
"""Marathon runner: version-matched (capfw7) authentic boot + cap7 replay.
|
||||
|
||||
Writes progress to stdout and dumps the captured IGC/board stream + a state
|
||||
summary when the command budget is reached (or on fault).
|
||||
Runs the capture's own firmware build from its TRUE ENTRY (0xf0400000) with the
|
||||
boot handshake satisfied, so the full startup (crt0, pools, main) is the
|
||||
firmware's own code; then feeds the capture's scene commands via dN_receive.
|
||||
|
||||
Writes the board/IGC stream to igc_stream.bin and prints progress.
|
||||
|
||||
Usage: python run_to_draw.py [capture] [max_cmds] [time_budget_s]
|
||||
"""
|
||||
import sys, os, time, struct
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import emu860, dis860, emu_main
|
||||
|
||||
CAP = sys.argv[1] if len(sys.argv) > 1 else r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin'
|
||||
MAXC = int(sys.argv[2]) if len(sys.argv) > 2 else 1600
|
||||
TIME_BUDGET = float(sys.argv[3]) if len(sys.argv) > 3 else 3300.0
|
||||
MAXC = int(sys.argv[2]) if len(sys.argv) > 2 else 800
|
||||
TIME_BUDGET = float(sys.argv[3]) if len(sys.argv) > 3 else 540.0
|
||||
|
||||
emu860.Mem.log = lambda self, *a, **k: None
|
||||
r = emu_main.MainRunner(CAP, max_cmds=MAXC)
|
||||
r = emu_main.MainRunner(CAP, fw='capfw7', max_cmds=MAXC)
|
||||
cpu = r.cpu
|
||||
cpu.wr(1, cpu.RET_SENTINEL); cpu.wr(2, 0xc0000); cpu.wr(16, 0); cpu.wr(17, 0)
|
||||
cpu.pc = emu_main.MAIN
|
||||
cpu.ctrl[0xfffff728] = 1 # boot handshake: transputer says go
|
||||
cpu.wr(2, 0x000c0000)
|
||||
cpu.pc = 0xf0400000 # true entry: authentic full startup
|
||||
|
||||
t0 = time.time(); last = t0
|
||||
result = 'time'
|
||||
result = 'time-budget'
|
||||
while True:
|
||||
pc = cpu.pc
|
||||
if pc == cpu.RET_SENTINEL:
|
||||
@@ -38,12 +44,13 @@ while True:
|
||||
print(f" .. t={now-t0:6.0f}s steps={cpu.steps:,} cmds={r.qi} "
|
||||
f"igc={len(cpu.igc)} board={len(cpu.board_log)}", flush=True)
|
||||
if now - t0 > TIME_BUDGET:
|
||||
result = 'time-budget'; break
|
||||
break
|
||||
|
||||
print(f"\nRESULT: {result}")
|
||||
print(f"steps={cpu.steps:,} cmds={r.qi}/{len(r.queue)} done={dict(r.done)}")
|
||||
print(f"replies={r.replies[:30]}")
|
||||
print(f"IGC writes={len(cpu.igc)} board-log={len(cpu.board_log)}")
|
||||
print(f"steps={cpu.steps:,} cmds={r.qi}/{len(r.queue)}")
|
||||
print(f"done={dict(r.done)}")
|
||||
print(f"replies (last 12): {r.replies[-12:]}")
|
||||
print(f"IGC={len(cpu.igc)} board-log={len(cpu.board_log)}")
|
||||
if cpu.stopmsg or result.startswith('derail'):
|
||||
for tpc, tw in cpu.tail[-16:]:
|
||||
m, ops = dis860.decode(tw, tpc)
|
||||
|
||||
Reference in New Issue
Block a user