i860 emu: EXACT pipelined FP model (MAME-validated) -- N-R divide passes
unhooked; draw_scene #1 runs with 41K IGC board accesses The functional pipe model is replaced with exact i860 semantics, validated against MAME's i860 core (src_opers[] and insn_dualop/insn_fadd_sub verbatim): - Precision bits: SOURCE = bit8, RESULT = bit7 (swapped from our old reading; .ds is invalid except famov). For DUAL ops: sp = multiplier source prec, rp = adder source prec AND all results. - Dual ops (sub 0x00-0x1f): bit10 selects PFAM (fdest <- A-pipe retire) vs PFMAM (fdest <- M-pipe retire) -- NOT "pipelined". FLAGM operands (the A-pipe entries in the DPC table) read the M-pipe in the PFMAM family. T-loads on DPC 2,3,6,7,8,0xb,0xc; K-loads on DPC 1,3,5,7 (from fsrc1 at mul precision; T from the M-pipe last stage). - fdest BYPASS: pipelined ops whose source register equals fdest read the retiring pipe value instead of the stale register (dual mul: op2 only; dual add + scalar pipelined fadd/fmul: both operands). - Pipe stages carry (value, result-precision); push rounds to single when rp=0 (exact magic-constant float->int bit games); retire encodes with the pusher's precision. Adder = 3 stages; multiplier = 2 (double) / 3 (single); pfld = 3-stage load pipe; graphics fiadd/fisub = 1-stage pipe, 64-bit .dd. ACCEPTANCE: (1) the firmware's own Newton-Raphson integer divide now computes 16/16 = 1 through the pipes -- the intdiv hook is retired (authentic execution); (2) draw_scene #1 from the snapshot executes real render phases: coefficient fld.d bursts, 0x800-byte queue-page initialization, tile enqueue, and the first region flush -- 40,959 IGC/board MMIO accesses (previously 0). Frame still running at the profiling time budget (~240M steps in). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -263,6 +263,28 @@ class I860:
|
||||
for i in range(size // 4):
|
||||
self.mem.w32(ea + i * 4, self.frd(b0 + i))
|
||||
return
|
||||
if op in (0x18, 0x19): # pfld.y (pipelined load: 3-stage load pipe)
|
||||
fl = w & 7
|
||||
size = 4 if (fl & 2) 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)
|
||||
self._fp_pipes()
|
||||
entry = (self.mem.r32(ea), self.mem.r32(ea + 4) if size == 8 else None)
|
||||
out = self._lpipe[2]
|
||||
self._lpipe[2] = self._lpipe[1]; self._lpipe[1] = self._lpipe[0]
|
||||
self._lpipe[0] = entry
|
||||
lo, hi = out
|
||||
if hi is None:
|
||||
self.fwr(dest, lo)
|
||||
else:
|
||||
b0 = dest & ~1
|
||||
self.fwr(b0, lo); self.fwr(b0 | 1, hi)
|
||||
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.
|
||||
@@ -396,21 +418,38 @@ class I860:
|
||||
|
||||
# ---- pipelined FP unit state (functional model, no cycle timing) ----
|
||||
# Adder pipe = 3-stage; multiplier pipe = 2-stage double / 3-stage single;
|
||||
# load pipe (pfld) = 3-stage; graphics pipe (pfiadd/pfisub) = 1-stage.
|
||||
# KR/KI/T = the dual-operation constant/transfer registers.
|
||||
# Each stage carries (value, result-precision): the value is rounded to
|
||||
# SINGLE at push time when the pushing op has rp=0 (this is what makes the
|
||||
# compiler's magic-constant float->int bit games come out exact), and the
|
||||
# retire is ENCODED with the pusher's precision, not the current op's.
|
||||
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
|
||||
if not hasattr(self, '_apipe') or (self._apipe and
|
||||
not isinstance(self._apipe[0], tuple)):
|
||||
# fresh init, or normalize legacy float-only snapshots
|
||||
old_a = getattr(self, '_apipe', [0.0] * 3)
|
||||
old_m = getattr(self, '_mpipe', [0.0] * 3)
|
||||
self._apipe = [(float(v), 1) for v in old_a]
|
||||
self._mpipe = [(float(v), 1) for v in old_m]
|
||||
self._lpipe = [(0, None)] * 3 # pfld: (lo bits, hi bits | None)
|
||||
self._gpipe = [0] # graphics: raw 32-bit int
|
||||
self._kr = getattr(self, '_kr', 0.0)
|
||||
self._ki = getattr(self, '_ki', 0.0)
|
||||
self._t = getattr(self, '_t', 0.0)
|
||||
return self._apipe, self._mpipe
|
||||
|
||||
def _padv(self, pipe, val, depth):
|
||||
def _round_rp(self, val, rp):
|
||||
if rp:
|
||||
return val
|
||||
return self.b2f(self.f2b(val)) # round to single
|
||||
|
||||
def _padv(self, pipe, val, rp, depth):
|
||||
"""Push (val,rp) into stage 0; return the (val,rp) leaving stage depth-1."""
|
||||
out = pipe[depth - 1]
|
||||
for i in range(depth - 1, 0, -1):
|
||||
pipe[i] = pipe[i - 1]
|
||||
pipe[0] = val
|
||||
pipe[0] = (self._round_rp(val, rp), rp)
|
||||
return out
|
||||
|
||||
# PFAM/PFSM dual-op routing, decoded from the validated mnemonic grammar
|
||||
@@ -424,65 +463,95 @@ class I860:
|
||||
# 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-operation DPC routing, verbatim from the i860 spec (validated against
|
||||
# MAME's i860 core src_opers[] table): (m1, m2, a1, a2, kload, tload).
|
||||
# 'PP' = the FLAGM operand: A-pipe last stage for the PFAM family (P=1),
|
||||
# M-pipe last stage for the PFMAM family (P=0).
|
||||
_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
|
||||
0x0: ('kr','s2', 's1','mm', False, False), # r2p1
|
||||
0x1: ('kr','s2', 't', 'mm', True, False), # r2pt
|
||||
0x2: ('kr','s2', 's1','PP', False, True), # r2ap1
|
||||
0x3: ('kr','s2', 't', 'PP', True, True), # r2apt
|
||||
0x4: ('ki','s2', 's1','mm', False, False), # i2p1
|
||||
0x5: ('ki','s2', 't', 'mm', True, False), # i2pt
|
||||
0x6: ('ki','s2', 's1','PP', False, True), # i2ap1
|
||||
0x7: ('ki','s2', 't', 'PP', True, True), # i2apt
|
||||
0x8: ('kr','PP', 's1','s2', False, True), # rat1p2
|
||||
0x9: ('s1','s2', 'PP','mm', False, False), # m12apm
|
||||
0xa: ('kr','PP', 's1','s2', False, False), # ra1p2
|
||||
0xb: ('s1','s2', 't', 'PP', False, True), # m12ttpa
|
||||
0xc: ('ki','PP', 's1','s2', False, True), # iat1p2
|
||||
0xd: ('s1','s2', 't', 'mm', False, False), # m12tpm
|
||||
0xe: ('ki','PP', 's1','s2', False, False), # ia1p2
|
||||
0xf: ('s1','s2', 't', 'PP', False, 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
|
||||
pbit = (w >> 10) & 1 # P: pipelined
|
||||
if sub < 0x20: # PFAM (0x00-0x0f) / PFSM (0x10-0x1f)
|
||||
# MAME-validated bit assignments: SOURCE precision = bit8, RESULT = bit7.
|
||||
sp = 1 if (w & 0x100) else 0
|
||||
rp = 1 if (w & 0x080) else 0
|
||||
pbit = (w >> 10) & 1 # pipelined (scalar ops) / PFAM-vs-PFMAM (dual ops)
|
||||
if sub < 0x20: # dual ops: PF[M]AM (bit4=0) / PF[M]SM (bit4=1)
|
||||
# sp = MULTIPLIER source precision; rp = ADDER source AND all results.
|
||||
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}
|
||||
am_out, am_rp = ap[2]
|
||||
mm_out, mm_rp = mp[mdepth - 1]
|
||||
pipe_val = am_out if pbit else mm_out # FLAGM operand + fdest bypass
|
||||
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
|
||||
def val(tag, prec):
|
||||
if tag == 's1': return self.rdf(src1, prec)
|
||||
if tag == 's2': return self.rdf(src2, prec)
|
||||
if tag == 'kr': return self._kr
|
||||
if tag == 'ki': return self._ki
|
||||
if tag == 't': return self._t
|
||||
if tag == 'mm': return mm_out
|
||||
return pipe_val # 'PP'
|
||||
v1 = val(m1, sp); v2 = val(m2, sp)
|
||||
if m2 == 's2' and dest and src2 == dest: # fdest bypass (mul op2 only)
|
||||
v2 = pipe_val
|
||||
newm = v1 * v2
|
||||
u1 = val(a1, rp); u2 = val(a2, rp)
|
||||
if a1 == 's1' and dest and src1 == dest: u1 = pipe_val
|
||||
if a2 == 's2' and dest and src2 == dest: u2 = pipe_val
|
||||
newa = (u1 - u2) if (sub & 0x10) else (u1 + u2)
|
||||
if tload: self._t = mm_out # T <- M-pipe last stage
|
||||
if kload: # K <- fsrc1 (mul precision)
|
||||
if m1 == 'ki': self._ki = self.rdf(src1, sp)
|
||||
else: self._kr = self.rdf(src1, sp)
|
||||
if pbit: self.wrf(dest, am_out, am_rp) # PFAM: fdest <- A retire
|
||||
else: self.wrf(dest, mm_out, mm_rp) # PFMAM: fdest <- M retire
|
||||
self._padv(ap, newa, rp, 3)
|
||||
self._padv(mp, newm, rp, mdepth)
|
||||
return
|
||||
if sub == 0x20: # fmul / pfmul
|
||||
r = self.rdf(src1, sp) * self.rdf(src2, sp)
|
||||
if sub in (0x20, 0x24): # fmul / pfmul (0x24 = pfmul3: 3-stage)
|
||||
v1 = self.rdf(src1, sp); v2 = self.rdf(src2, sp)
|
||||
if pbit:
|
||||
ap, mp = self._fp_pipes()
|
||||
self.wrf(dest, self._padv(mp, r, 2 if sp else 3), rp)
|
||||
depth = 3 if sub == 0x24 else (2 if sp else 3)
|
||||
out, orp = mp[depth - 1]
|
||||
if dest and src1 == dest: v1 = out # fdest bypass
|
||||
if dest and src2 == dest: v2 = out
|
||||
self.wrf(dest, out, orp)
|
||||
self._padv(mp, v1 * v2, rp, depth)
|
||||
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)
|
||||
self.wrf(dest, v1 * v2, rp)
|
||||
elif sub in (0x30, 0x31, 0x33): # fadd/fsub/famov (+ pipelined forms)
|
||||
v1 = self.rdf(src1, sp); v2 = self.rdf(src2, sp)
|
||||
if pbit:
|
||||
ap, mp = self._fp_pipes()
|
||||
self.wrf(dest, self._padv(ap, r, 3), rp)
|
||||
out, orp = ap[2]
|
||||
if dest and src1 == dest: v1 = out # fdest bypass
|
||||
if dest and src2 == dest: v2 = out
|
||||
if sub == 0x33: r = v1
|
||||
else: r = (v1 - v2) if sub == 0x31 else (v1 + v2)
|
||||
self.wrf(dest, out, orp)
|
||||
self._padv(ap, r, rp, 3)
|
||||
else:
|
||||
if sub == 0x33: r = v1
|
||||
else: r = (v1 - v2) if sub == 0x31 else (v1 + v2)
|
||||
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)
|
||||
b = self.rdf(src2, sp)
|
||||
self.wrf(dest, (1.0 / b) if b else 0.0, rp)
|
||||
@@ -513,10 +582,25 @@ class I860:
|
||||
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 in (0x49, 0x4d): # fiadd/fisub (graphics-unit int add/sub;
|
||||
# .ss = 32-bit, .dd = 64-bit across register pairs; P = 1-stage pipe)
|
||||
if sp:
|
||||
a = self.frd(src1 & ~1) | (self.frd((src1 & ~1) | 1) << 32)
|
||||
b = self.frd(src2 & ~1) | (self.frd((src2 & ~1) | 1) << 32)
|
||||
m = (1 << 64) - 1
|
||||
else:
|
||||
a = self.frd(src1); b = self.frd(src2); m = MASK32
|
||||
r = ((a + b) if sub == 0x49 else (a - b)) & m
|
||||
if pbit:
|
||||
self._fp_pipes()
|
||||
out = self._gpipe[0]
|
||||
self._gpipe[0] = r
|
||||
r = out
|
||||
if sp:
|
||||
b0 = dest & ~1
|
||||
self.fwr(b0, r & MASK32); self.fwr(b0 | 1, (r >> 32) & MASK32)
|
||||
else:
|
||||
self.fwr(dest, r)
|
||||
elif sub == 0x5f: # fnop
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -56,7 +56,10 @@ MAPS = {
|
||||
alloc=0xf042c628, v2p=0xf042c300,
|
||||
lockacq=0xf0423360, lockrel=0xf04233b8,
|
||||
chain=None, chain_tbl=None, chain_tail=None,
|
||||
intdiv=0xf042ee00, # runtime int divide (r22 = r22/r23)
|
||||
# intdiv hook retired 2026-07-15: the exact MAME-validated pipeline model
|
||||
# (PFAM/PFMAM families, FLAGM->MPIPE, sp/rp bit swap, fdest bypass) makes
|
||||
# the firmware's own Newton-Raphson divide compute correctly.
|
||||
intdiv=None,
|
||||
seeds=[
|
||||
(0x1000, 0), # _processorId
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user