M4a: emu860c -- the i860 step-core in C, 351x, checkpoint-perfect

Full mechanical port of emu860.py's validated semantics: paged memory,
integer ISA, control flow with delayed branches + bc.t/bnc.t annul, DIM
tracking, and the complete FP model (pipes with per-entry result precision,
PFAM/PFMAM dual-op routing, fdest bypass, raw-retire fix/ftrunc, K/T loads).
Python keeps the hooks via a shim; the C loop returns at hook pcs.

Differential result vs the hook-aligned reference trace (ref_trace2):
  139.7M steps/s (Python: 398K/s = 351x), 35.8M steps, 2195 hooks,
  43/43 checkpoints matched (pc + step count + full reg CRC).
The original i860 ran ~40 MIPS: real-time geometry is cleared 3.5x over --
M4's gate (GPU-RETARGET.md) is open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-19 13:24:43 -05:00
co-authored by Claude Opus 4.8
parent 40817bee91
commit be8f72e731
3 changed files with 889 additions and 0 deletions
@@ -0,0 +1,5 @@
#!/bin/sh
# build emu860c.pyd (MSYS2 MINGW64: pacman -S mingw-w64-x86_64-gcc + python)
PYINC=$(python -c "import sysconfig; print(sysconfig.get_paths()['include'])")
PYVER=$(python -c "import sys; print('%d.%d' % sys.version_info[:2])")
exec gcc -O2 -shared -o emu860c.pyd emu860c.c -I"$PYINC" -LC:/msys64/mingw64/lib -lpython${PYVER/./}
+129
View File
@@ -0,0 +1,129 @@
"""emu860c differential driver v2: boot via the Python MainRunner
(authoritative loader), copy the state into the C core, run with hooks
serviced through a shim, and compare (pc, steps, reg-crc) at HOOK boundaries
against the hook-aligned reference trace (ref_trace2.pkl)."""
import sys, os, time, pickle, zlib
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, os.path.dirname(HERE))
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
import emu860, emu_main, emu860c
emu860.Mem.log = lambda self, *a, **k: None
SCRATCH = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
class MemShim:
def r32(self, a): return emu860c.r32(a & 0xffffffff)
def w32(self, a, v): emu860c.w32(a & 0xffffffff, v & 0xffffffff)
def load_blob(self, a, b): emu860c.load_blob(a & 0xffffffff, bytes(b))
class CpuShim:
def __init__(self):
self.mem = MemShim()
self.igc = []
self.board_log = []
self.RET_SENTINEL = 0xc0de0000
@property
def steps(self):
return emu860c.getstate()['steps']
@property
def pc(self):
return emu860c.getstate()['pc']
@pc.setter
def pc(self, v):
emu860c.setpc(v & 0xffffffff)
def rd(self, i):
return 0 if i == 0 else emu860c.getstate()['r'][i]
def wr(self, i, v):
if i:
emu860c.setreg(i, v & 0xffffffff, 0)
def boot():
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin',
fw='capfw7', max_cmds=200000)
cpu = r.start()
emu860c.init()
for pn, page in cpu.mem.pages.items():
emu860c.load_blob(pn << 16, bytes(page))
for a in range(0xfffff000, 0x100000000, 4):
v = cpu.mem.r32(a)
if v:
emu860c.w32(a & 0xffffffff, v & 0xffffffff)
for i in range(32):
emu860c.setreg(i, cpu.r[i] & 0xffffffff, 0)
emu860c.setreg(i, cpu.f[i] & 0xffffffff, 1)
emu860c.setpc(cpu.pc)
emu860c.set_sentinel(cpu.RET_SENTINEL)
emu860c.set_hooks(sorted(r.hooks.keys()))
return r
def crc_state(st):
b = b''.join(int(v).to_bytes(4, 'little') for v in st['r'])
b += b''.join(int(v).to_bytes(4, 'little') for v in st['f'])
return zlib.crc32(b) & 0xffffffff
def main():
ref = pickle.load(open(os.path.join(SCRATCH, 'ref_trace2.pkl'), 'rb'))
cps = {n: (pc, steps, crc) for n, pc, steps, crc in ref['cps']}
max_hook = ref['hookno']
r = boot()
shim = CpuShim()
t0 = time.time()
hookno = 0
matched = mismatched = 0
while hookno < max_hook:
reason, steps = emu860c.run(200000000)
if reason == 0:
st = emu860c.getstate()
pc = st['pc']
h = r.hooks.get(pc)
if h is None:
print("sentinel at hook %d" % hookno)
break
hookno += 1
want = cps.get(hookno)
if want:
got = (pc, st['steps'], crc_state(st))
if want != got:
print("DIVERGE at hook %d:" % hookno)
print(" want pc=%#x steps=%d crc=%08x" % want)
print(" got pc=%#x steps=%d crc=%08x" % got)
mismatched += 1
if mismatched > 3:
break
else:
matched += 1
if h(shim) == 'done':
break
continue
if reason == 1:
print("STOP: pc=%#x" % emu860c.getstate()['pc'])
break
if reason == 2:
st = emu860c.getstate()
print("FP fallback pc=%#x w=%08x" % (st['pc'], emu860c.r32(st['pc'])))
break
if reason == 3:
print("budget exhausted")
break
dt = time.time() - t0
st = emu860c.getstate()
print("C core: %d steps, %d hooks in %.2fs = %d steps/s; "
"checkpoints %d/%d matched" %
(st['steps'], hookno, dt, st['steps'] / max(dt, 1e-9),
matched, len(cps)))
if __name__ == '__main__':
main()
+755
View File
@@ -0,0 +1,755 @@
/* emu860c.c -- M4a: the i860 step-core in C (CPython extension).
*
* A mechanical port of emu860.py's validated semantics (the golden model):
* paged memory, integer ALU/loads/stores, control flow with delayed branches,
* bc.t/bnc.t annul, dual-instruction-mode tracking, and the FP pipeline model.
*
* Differential contract: emu860c must reproduce emu860.py's (step, pc,
* reg-crc) checkpoints exactly (scratchpad ref_trace / emu860_ref_trace.pkl).
*
* Stage 1 (this file, first landing): memory + integer core + control flow +
* DIM/annul + scalar FP moves/loads/stores + the pipe mechanics for the
* common pipelined ops (pfadd/pfsub/pfmul + fnop). Anything else FP raises
* NotImplemented back to Python (run_until returns reason=2 with the pc), so
* divergence is impossible -- unfinished territory just falls back.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#define MASK32 0xffffffffu
/* ---------------- paged guest memory ---------------- */
#define PAGE_BITS 12
#define PAGE_SIZE (1u << PAGE_BITS)
#define N_PAGES (1u << (32 - PAGE_BITS))
static uint8_t **g_pages; /* N_PAGES pointers, allocated on demand */
static uint8_t *page_for(uint32_t addr, int create) {
uint32_t pi = addr >> PAGE_BITS;
uint8_t *p = g_pages[pi];
if (!p && create) {
p = (uint8_t *)calloc(PAGE_SIZE, 1);
g_pages[pi] = p;
}
return p;
}
static uint32_t mem_r32(uint32_t addr) {
uint32_t off = addr & (PAGE_SIZE - 1);
uint8_t *p = page_for(addr, 0);
if (!p) return 0;
if (off <= PAGE_SIZE - 4) {
uint32_t v;
memcpy(&v, p + off, 4);
return v;
}
/* page-spanning byte fallback */
uint32_t v = 0;
for (int i = 0; i < 4; i++) {
uint8_t *q = page_for(addr + i, 0);
v |= (uint32_t)(q ? q[(addr + i) & (PAGE_SIZE - 1)] : 0) << (8 * i);
}
return v;
}
static void mem_w32(uint32_t addr, uint32_t val) {
uint32_t off = addr & (PAGE_SIZE - 1);
uint8_t *p = page_for(addr, 1);
if (off <= PAGE_SIZE - 4) {
memcpy(p + off, &val, 4);
return;
}
for (int i = 0; i < 4; i++) {
uint8_t *q = page_for(addr + i, 1);
q[(addr + i) & (PAGE_SIZE - 1)] = (uint8_t)(val >> (8 * i));
}
}
static uint32_t mem_r16(uint32_t addr) {
uint8_t *p = page_for(addr, 0);
uint32_t off = addr & (PAGE_SIZE - 1);
if (p && off <= PAGE_SIZE - 2) {
uint16_t v; memcpy(&v, p + off, 2); return v;
}
uint32_t v = 0;
for (int i = 0; i < 2; i++) {
uint8_t *q = page_for(addr + i, 0);
v |= (uint32_t)(q ? q[(addr + i) & (PAGE_SIZE - 1)] : 0) << (8 * i);
}
return v;
}
static void mem_w16(uint32_t addr, uint32_t val) {
for (int i = 0; i < 2; i++) {
uint8_t *q = page_for(addr + i, 1);
q[(addr + i) & (PAGE_SIZE - 1)] = (uint8_t)(val >> (8 * i));
}
}
static uint32_t mem_r8(uint32_t addr) {
uint8_t *p = page_for(addr, 0);
return p ? p[addr & (PAGE_SIZE - 1)] : 0;
}
static void mem_w8(uint32_t addr, uint32_t val) {
uint8_t *p = page_for(addr, 1);
p[addr & (PAGE_SIZE - 1)] = (uint8_t)val;
}
/* ---------------- CPU state ---------------- */
typedef struct {
double v; int rp; int has_raw; uint64_t raw;
} PipeEnt;
typedef struct {
uint32_t r[32];
uint32_t f[32];
uint32_t cr[16];
uint32_t pc;
int lcc;
/* delayed branch */
int br_pending; uint32_t br_target; int br_delayed;
int squash;
/* DIM */
int dim, dim_on, dim_exit, dim_half;
/* FP pipes */
PipeEnt apipe[3], mpipe[3];
uint32_t lpipe_lo[3]; uint32_t lpipe_hi[3]; int lpipe_has_hi[3];
uint32_t gpipe;
double kr, ki, t;
/* control */
int stop;
char stopmsg[256];
uint64_t steps;
uint32_t ret_sentinel;
} Cpu;
static Cpu C;
static uint32_t rd(int i) { return i == 0 ? 0 : C.r[i]; }
static void wr(int i, uint32_t v) { if (i) C.r[i] = v; }
static uint32_t frd(int i) { return i <= 1 ? 0 : C.f[i]; }
static void fwr(int i, uint32_t v) { if (i > 1) C.f[i] = v; }
static int cc(void) { return (C.cr[1] >> 2) & 1; }
static void set_cc(int cond) {
if (cond) C.cr[1] |= 4; else C.cr[1] &= ~4u;
}
static int32_t s16(uint32_t v) { return (int32_t)(int16_t)(v & 0xffff); }
static int32_t s26(uint32_t v) {
return (v & 0x02000000) ? (int32_t)(v | 0xfc000000u) : (int32_t)v;
}
static float b2f(uint32_t b) { float f; memcpy(&f, &b, 4); return f; }
static uint32_t f2b(float f) { uint32_t b; memcpy(&b, &f, 4); return b; }
static double rdf(int reg, int dbl) {
if (dbl) {
int b = reg & ~1;
uint64_t bits = ((uint64_t)frd(b | 1) << 32) | frd(b);
double d; memcpy(&d, &bits, 8); return d;
}
return (double)b2f(frd(reg));
}
static void wrf(int reg, double val, int dbl) {
if (dbl) {
uint64_t bits; memcpy(&bits, &val, 8);
int b = reg & ~1;
fwr(b, (uint32_t)bits); fwr(b | 1, (uint32_t)(bits >> 32));
} else {
fwr(reg, f2b((float)val));
}
}
static double round_rp(double v, int rp) {
return rp ? v : (double)b2f(f2b((float)v));
}
static PipeEnt padv(PipeEnt *pipe, double val, int rp, int depth,
int has_raw, uint64_t raw) {
PipeEnt out = pipe[depth - 1];
for (int i = depth - 1; i > 0; i--) pipe[i] = pipe[i - 1];
pipe[0].v = has_raw ? val : round_rp(val, rp);
pipe[0].rp = rp; pipe[0].has_raw = has_raw; pipe[0].raw = raw;
return out;
}
static void retire(int dest, PipeEnt e) {
if (!e.has_raw) {
wrf(dest, e.v, e.rp);
} else if (e.rp) {
int b0 = dest & ~1;
fwr(b0, (uint32_t)e.raw); fwr(b0 | 1, (uint32_t)(e.raw >> 32));
} else {
fwr(dest, (uint32_t)e.raw);
}
}
static void branch(uint32_t target, int delayed) {
C.br_pending = 1; C.br_target = target; C.br_delayed = delayed;
}
/* returns 0 ok, 1 = stop, 2 = FP-not-implemented (fallback to Python) */
static int exec_fp(uint32_t w, int src1, int src2, int dest);
/* ---------------- the DIM tracker (port of _dim_track) ---------------- */
static void dim_track(uint32_t w, uint32_t pc) {
(void)pc;
int fp = (((w >> 26) & 0x3f) == 0x12) || ((w & 0xfffffdffu) == 0xb0000000u);
int d_bit = (w & 0x200) != 0;
if (!C.dim) {
if (fp && d_bit && !C.dim_on) { C.dim_on = 1; return; }
if (C.dim_on) { C.dim = 1; C.dim_on = 0; C.dim_half = 0; return; }
} else {
/* in DIM: halves alternate positionally */
if (C.dim_half == 0) { /* FP half */
if (fp && !d_bit && !C.dim_exit) C.dim_exit = 1;
else if (C.dim_exit == 1 && fp && !d_bit) C.dim_exit = 1;
C.dim_half = 1;
} else {
C.dim_half = 0;
if (C.dim_exit) {
C.dim_exit++;
if (C.dim_exit >= 3) { C.dim = 0; C.dim_exit = 0; }
}
}
}
}
/* ---------------- execute one instruction word ---------------- */
static int execute(uint32_t w, uint32_t pc) {
int op = (w >> 26) & 0x3f;
int dest = (w >> 16) & 0x1f;
int src2 = (w >> 21) & 0x1f;
int src1 = (w >> 11) & 0x1f;
uint32_t imm = w & 0xffff;
switch (op) {
/* ---- integer loads ---- */
case 0x00: case 0x01: { /* ld.b */
uint32_t ea = (op & 1) ? rd(src2) + s16(imm)
: rd(src2) + rd(src1);
uint32_t b = mem_r8(ea);
wr(dest, (uint32_t)(int32_t)(int8_t)b);
return 0;
}
case 0x04: case 0x05: { /* ld.s / ld.l via bit0 */
uint32_t off = (op & 1) ? (uint32_t)s16(imm & 0xfffe ? imm : imm)
: rd(src1);
int isl = imm & 1;
if (op & 1) {
if (isl) off = (uint32_t)s16(imm & 0xfffc);
else off = (uint32_t)s16(imm & 0xfffe);
} else {
isl = imm & 1; /* register-indexed: bit0 of imm still selects */
}
uint32_t ea = rd(src2) + off;
if (isl) wr(dest, mem_r32(ea));
else wr(dest, (uint32_t)(int32_t)(int16_t)mem_r16(ea));
return 0;
}
/* ---- integer stores (split offset) ---- */
case 0x03: { /* st.b */
uint32_t off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff);
mem_w8(rd(src2) + s16(off), rd(src1) & 0xff);
return 0;
}
case 0x07: { /* st.s / st.l */
uint32_t off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff);
if (off & 1) mem_w32(rd(src2) + s16(off & 0xfffc), rd(src1));
else mem_w16(rd(src2) + s16(off & 0xfffe), rd(src1) & 0xffff);
return 0;
}
/* ---- FP loads/stores ---- */
case 0x08: case 0x09: case 0x0a: case 0x0b: {
int fl = w & 7;
int size = (fl & 2) ? 4 : ((fl & 4) ? 16 : 8);
uint32_t off;
if (op & 1) off = (uint32_t)s16(imm & (0x10000 - size));
else off = rd(src1);
uint32_t ea = rd(src2) + off;
if (fl & 1) wr(src2, ea);
int b0 = dest & ~((size / 4) - 1);
if (op < 0x0a) {
for (int i = 0; i < size / 4; i++) fwr(b0 + i, mem_r32(ea + 4 * i));
} else {
for (int i = 0; i < size / 4; i++) mem_w32(ea + 4 * i, frd(b0 + i));
}
return 0;
}
case 0x18: case 0x19: { /* pfld */
int fl = w & 7;
int size = (fl & 2) ? 4 : 8;
uint32_t off;
if (op & 1) off = (uint32_t)s16(imm & (0x10000 - size));
else off = rd(src1);
uint32_t ea = rd(src2) + off;
if (fl & 1) wr(src2, ea);
uint32_t lo = C.lpipe_lo[2], hi = C.lpipe_hi[2];
int has_hi = C.lpipe_has_hi[2];
C.lpipe_lo[2] = C.lpipe_lo[1]; C.lpipe_hi[2] = C.lpipe_hi[1];
C.lpipe_has_hi[2] = C.lpipe_has_hi[1];
C.lpipe_lo[1] = C.lpipe_lo[0]; C.lpipe_hi[1] = C.lpipe_hi[0];
C.lpipe_has_hi[1] = C.lpipe_has_hi[0];
C.lpipe_lo[0] = mem_r32(ea);
C.lpipe_has_hi[0] = (size == 8);
C.lpipe_hi[0] = (size == 8) ? mem_r32(ea + 4) : 0;
if (!has_hi) {
fwr(dest, lo);
} else {
int b0 = dest & ~1;
fwr(b0, lo); fwr(b0 | 1, hi);
}
return 0;
}
case 0x0d: { /* flush */
uint32_t off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff);
if (off & 1) wr(src2, rd(src2) + s16(off & 0xffe0));
return 0;
}
case 0x0c: wr(dest, C.cr[src2 & 15]); return 0; /* ld.c */
case 0x0e: C.cr[src2 & 15] = rd(src1); return 0; /* st.c */
case 0x02: fwr(dest, rd(src1)); return 0; /* ixfr */
/* ---- control transfer ---- */
case 0x10: branch(rd(src1), 1); return 0; /* bri */
case 0x13: { /* core escape */
int sub = w & 0x1f;
if (sub == 0x02) { wr(1, pc + 8); branch(rd(src1), 1); return 0; }
if (sub == 0x01 || sub == 0x07 || sub == 0x04) return 0;
C.stop = 1;
snprintf(C.stopmsg, sizeof C.stopmsg,
"core-escape sub %#x @ %#x", sub, pc);
return 1;
}
case 0x1a: branch(pc + 4 + s26(w & 0x03ffffff) * 4, 1); return 0; /* br */
case 0x1b: wr(1, pc + 8);
branch(pc + 4 + s26(w & 0x03ffffff) * 4, 1); return 0; /* call */
case 0x1c: case 0x1d: { /* bc / bc.t */
uint32_t tgt = pc + 4 + s26(w & 0x03ffffff) * 4;
if (cc()) branch(tgt, op == 0x1d);
else if (op == 0x1d) C.squash = 1;
return 0;
}
case 0x1e: case 0x1f: { /* bnc / bnc.t */
uint32_t tgt = pc + 4 + s26(w & 0x03ffffff) * 4;
if (!cc()) branch(tgt, op == 0x1f);
else if (op == 0x1f) C.squash = 1;
return 0;
}
case 0x14: case 0x15: case 0x16: case 0x17: { /* btne/bte */
int32_t broff = s16(((dest << 11) | (w & 0x7ff)) & 0xffff);
uint32_t a = (op & 1) ? (uint32_t)src1 : rd(src1);
uint32_t b = rd(src2);
int take = (op < 0x16) ? (a != b) : (a == b);
if (take) branch(pc + 4 + broff * 4, 0);
return 0;
}
case 0x2d: { /* bla */
uint32_t a = rd(src1), b = rd(src2);
int taken = C.lcc;
if ((int32_t)a < 0)
C.lcc = ((int32_t)(a + b) >= 0) ? 1 : 0;
else
C.lcc = ((uint64_t)a + (uint64_t)b > MASK32) ? 1 : 0;
wr(src2, a + b);
if (taken) {
int32_t off = s16(((((w >> 16) & 0x1f) << 11) | (w & 0x7ff)) & 0xffff);
branch(pc + 4 + off * 4, 1);
}
return 0;
}
default: break;
}
/* ---- arithmetic / logic ---- */
{
int base = op & 0x3e;
if (base >= 0x20 && base <= 0x26) { /* addu subu adds subs */
uint32_t b = rd(src2);
uint32_t a = (op & 1) ? (uint32_t)s16(imm) : rd(src1);
if (base == 0x20) {
uint64_t s = (uint64_t)a + b;
wr(dest, (uint32_t)s); set_cc(s > MASK32);
} else if (base == 0x24) {
int64_t s = (int64_t)(int32_t)a + (int32_t)b;
wr(dest, (uint32_t)s); set_cc(((uint32_t)s & 0x80000000u) != 0);
} else if (base == 0x22) {
wr(dest, a - b); set_cc(a < b);
} else {
wr(dest, a - b);
set_cc((int32_t)a < (int32_t)b);
}
return 0;
}
if (base >= 0x28 && base <= 0x2e) { /* shifts (incl shrd) */
int cnt = (op & 1) ? (s16(imm) & 0x1f) : (rd(src1) & 0x1f);
uint32_t b = rd(src2);
uint32_t r;
if (base == 0x28) r = b << cnt;
else if (base == 0x2a) r = b >> cnt;
else if (base == 0x2e) r = (uint32_t)((int32_t)b >> cnt);
else r = b >> cnt;
wr(dest, r);
return 0;
}
if (base >= 0x30) { /* and..xorh */
uint32_t a = (op & 1) ? imm : rd(src1);
int hi = (base == 0x32 || base == 0x36 || base == 0x3a || base == 0x3e);
if (hi && (op & 1)) a = imm << 16;
uint32_t b = rd(src2);
int g = base & 0x3c;
uint32_t r;
if (g == 0x30) r = b & a;
else if (g == 0x34) r = b & ~a;
else if (g == 0x38) r = b | a;
else r = b ^ a;
wr(dest, r);
set_cc(r == 0);
return 0;
}
}
if (op == 0x12) return exec_fp(w, src1, src2, dest);
C.stop = 1;
snprintf(C.stopmsg, sizeof C.stopmsg,
"unimplemented op %#x @ %#x w=%#x", op, pc, w);
return 1;
}
/* ---------------- FP unit: full port of emu860.exec_fp ---------------- */
/* dual-op routing table: {m1, m2, a1, a2, kload, tload}; operand tags:
* 0=s1 1=s2 2=kr 3=ki 4=t 5=PP(flagm) 6=mm */
static const int DUAL[16][6] = {
{2,1, 0,6, 0,0}, {2,1, 4,6, 1,0}, {2,1, 0,5, 0,1}, {2,1, 4,5, 1,1},
{3,1, 0,6, 0,0}, {3,1, 4,6, 1,0}, {3,1, 0,5, 0,1}, {3,1, 4,5, 1,1},
{2,5, 0,1, 0,1}, {0,1, 5,6, 0,0}, {2,5, 0,1, 0,0}, {0,1, 4,5, 0,1},
{3,5, 0,1, 0,1}, {0,1, 4,6, 0,0}, {3,5, 0,1, 0,0}, {0,1, 4,5, 0,0},
};
static int exec_fp(uint32_t w, int src1, int src2, int dest) {
if ((w & 0xfffffdffu) == 0xb0000000u) return 0; /* fnop / d.fnop */
int sub = w & 0x7f;
int sp = (w & 0x100) ? 1 : 0;
int rp = (w & 0x080) ? 1 : 0;
int pbit = (w >> 10) & 1;
if (sub < 0x20) { /* dual ops PF[M]AM / PF[M]SM */
int mdepth = sp ? 2 : 3;
PipeEnt am = C.apipe[2];
PipeEnt mm = C.mpipe[mdepth - 1];
double am_out = am.v, mm_out = mm.v;
double pipe_val = pbit ? am_out : mm_out;
const int *dd = DUAL[sub & 0xf];
double vals_sp[7], vals_rp[7];
vals_sp[0] = rdf(src1, sp); vals_sp[1] = rdf(src2, sp);
vals_rp[0] = rdf(src1, rp); vals_rp[1] = rdf(src2, rp);
vals_sp[2] = vals_rp[2] = C.kr;
vals_sp[3] = vals_rp[3] = C.ki;
vals_sp[4] = vals_rp[4] = C.t;
vals_sp[5] = vals_rp[5] = pipe_val;
vals_sp[6] = vals_rp[6] = mm_out;
double v1 = vals_sp[dd[0]], v2 = vals_sp[dd[1]];
if (dd[1] == 1 && dest && src2 == dest) v2 = pipe_val;
double newm = v1 * v2;
double u1 = vals_rp[dd[2]], u2 = vals_rp[dd[3]];
if (dd[2] == 0 && dest && src1 == dest) u1 = pipe_val;
if (dd[3] == 1 && dest && src2 == dest) u2 = pipe_val;
double newa = (sub & 0x10) ? (u1 - u2) : (u1 + u2);
if (dd[5]) C.t = mm_out;
if (dd[4]) {
if (dd[0] == 3) C.ki = rdf(src1, sp);
else C.kr = rdf(src1, sp);
}
if (pbit) retire(dest, C.apipe[2]);
else retire(dest, C.mpipe[mdepth - 1]);
padv(C.apipe, newa, rp, 3, 0, 0);
padv(C.mpipe, newm, rp, mdepth, 0, 0);
return 0;
}
if (sub == 0x20 || sub == 0x24) { /* fmul / pfmul (0x24 = 3-stage) */
double v1 = rdf(src1, sp), v2 = rdf(src2, sp);
if (pbit) {
int depth = (sub == 0x24) ? 3 : (sp ? 2 : 3);
double out = C.mpipe[depth - 1].v;
if (dest && src1 == dest) v1 = out;
if (dest && src2 == dest) v2 = out;
retire(dest, C.mpipe[depth - 1]);
padv(C.mpipe, v1 * v2, rp, depth, 0, 0);
} else {
wrf(dest, v1 * v2, rp);
}
return 0;
}
if (sub == 0x30 || sub == 0x31 || sub == 0x33) { /* fadd/fsub/famov */
double v1 = rdf(src1, sp), v2 = rdf(src2, sp);
double res;
if (pbit) {
double out = C.apipe[2].v;
if (dest && src1 == dest) v1 = out;
if (dest && src2 == dest) v2 = out;
res = (sub == 0x33) ? v1 : ((sub == 0x31) ? v1 - v2 : v1 + v2);
retire(dest, C.apipe[2]);
padv(C.apipe, res, rp, 3, 0, 0);
} else {
res = (sub == 0x33) ? v1 : ((sub == 0x31) ? v1 - v2 : v1 + v2);
wrf(dest, res, rp);
}
return 0;
}
if (sub == 0x22) { /* frcp */
double b = rdf(src2, sp);
wrf(dest, b != 0.0 ? 1.0 / b : 0.0, rp);
return 0;
}
if (sub == 0x23) { /* frsqr */
double b = rdf(src2, sp);
wrf(dest, b > 0.0 ? 1.0 / sqrt(b) : 0.0, rp);
return 0;
}
if (sub == 0x32 || sub == 0x3a) { /* fix / ftrunc */
double a = rdf(src1, sp);
uint64_t iv;
if (a != a || a > 2147483647.0 || a < -2147483648.0) {
iv = 0x80000000u;
} else {
double r_ = (sub == 0x3a) ? a : (a >= 0 ? a + 0.5 : a - 0.5);
int64_t t_ = (int64_t)r_;
iv = (uint64_t)t_;
}
iv &= rp ? 0xFFFFFFFFFFFFFFFFull : 0xFFFFFFFFull;
if (pbit) {
retire(dest, C.apipe[2]);
padv(C.apipe, (double)(uint32_t)iv, rp, 3, 1, iv);
} else if (rp) {
int b0 = dest & ~1;
fwr(b0, (uint32_t)iv); fwr(b0 | 1, (uint32_t)(iv >> 32));
} else {
fwr(dest, (uint32_t)iv);
}
return 0;
}
if (sub == 0x34 || sub == 0x35) { /* pfgt/pfle (0x34) / pfeq (0x35) */
double v1 = rdf(src1, sp), v2 = rdf(src2, sp);
if (sub == 0x35) set_cc(v1 == v2);
else if (w & 0x80) set_cc(!(v1 <= v2));
else set_cc(v1 > v2);
retire(dest, C.apipe[2]);
padv(C.apipe, 0.0, sp, 3, 0, 0);
return 0;
}
if (sub == 0x21) { /* fmlow.dd */
uint64_t prod = (uint64_t)frd(src1) * (uint64_t)frd(src2);
int b0 = dest & ~1;
fwr(b0, (uint32_t)prod); fwr(b0 | 1, (uint32_t)(prod >> 32));
return 0;
}
if (sub == 0x40) { /* fxfr FP->int */
wr(dest, frd(src1));
return 0;
}
if (sub == 0x49 || sub == 0x4d) { /* fiadd / fisub */
uint64_t a, b, m, r_;
if (sp) {
a = (uint64_t)frd(src1 & ~1) | ((uint64_t)frd((src1 & ~1) | 1) << 32);
b = (uint64_t)frd(src2 & ~1) | ((uint64_t)frd((src2 & ~1) | 1) << 32);
m = 0xFFFFFFFFFFFFFFFFull;
} else {
a = frd(src1); b = frd(src2); m = MASK32;
}
r_ = ((sub == 0x49) ? a + b : a - b) & m;
if (pbit) {
uint64_t out = C.gpipe;
C.gpipe = (uint32_t)r_; /* NB python gpipe stores raw int */
r_ = out;
}
if (sp) {
int b0 = dest & ~1;
fwr(b0, (uint32_t)r_); fwr(b0 | 1, (uint32_t)(r_ >> 32));
} else {
fwr(dest, (uint32_t)r_);
}
return 0;
}
if (sub == 0x5f) return 0; /* fnop */
C.stop = 1;
snprintf(C.stopmsg, sizeof C.stopmsg,
"unimplemented FP sub %#x @ %#x", sub, C.pc);
return 1;
}
/* ---------------- the step loop ---------------- */
/* reasons: 0 = hit hook pc, 1 = stop, 2 = FP fallback (execute w at C.pc via
* Python then call resume()), 3 = max steps */
static uint32_t *g_hooks; static int g_nhooks;
static int is_hook(uint32_t pc) {
for (int i = 0; i < g_nhooks; i++)
if (g_hooks[i] == pc) return 1;
return 0;
}
static int step_once(int *fp_fallback) {
uint32_t pc = C.pc;
uint32_t w = mem_r32(pc);
int dim_at_fetch = C.dim;
C.br_pending = 0;
C.squash = 0;
int rc = execute(w, pc);
if (rc == 2) { *fp_fallback = 1; return 0; } /* pc unchanged; steps not counted */
C.steps++;
dim_track(w, pc);
if (C.stop) return 1;
if (C.br_pending) {
uint32_t target = C.br_target;
if (C.br_delayed) {
int nslots = dim_at_fetch ? 2 : 1;
uint32_t ds = pc + 4;
C.br_pending = 0;
for (int k = 0; k < nslots; k++) {
uint32_t w2 = mem_r32(ds);
int rc2 = execute(w2, ds);
if (rc2 == 2) { *fp_fallback = 2; C.pc = ds; return 0; } /* mid-slot fallback */
C.steps++;
dim_track(w2, ds);
ds += 4;
}
C.pc = C.br_pending ? C.br_target : target;
C.dim_half = 0;
} else {
C.pc = target;
C.dim_half = 0;
}
} else if (C.squash) {
int nslots = dim_at_fetch ? 2 : 1;
C.squash = 0;
C.pc = pc + 4 + nslots * 4;
} else {
C.pc = pc + 4;
}
return C.stop;
}
/* ---------------- Python interface ---------------- */
static PyObject *py_init(PyObject *self, PyObject *args) {
(void)self; (void)args;
if (!g_pages) g_pages = (uint8_t **)calloc(N_PAGES, sizeof(uint8_t *));
memset(&C, 0, sizeof C);
for (int i = 0; i < 3; i++) { C.apipe[i].rp = 1; C.mpipe[i].rp = 1; }
Py_RETURN_NONE;
}
static PyObject *py_load_blob(PyObject *self, PyObject *args) {
(void)self;
Py_buffer buf; unsigned long long addr;
if (!PyArg_ParseTuple(args, "Ky*", &addr, &buf)) return NULL;
const uint8_t *src = (const uint8_t *)buf.buf;
for (Py_ssize_t i = 0; i < buf.len; i++)
mem_w8((uint32_t)(addr + i), src[i]);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
static PyObject *py_set_hooks(PyObject *self, PyObject *args) {
(void)self;
PyObject *lst;
if (!PyArg_ParseTuple(args, "O", &lst)) return NULL;
Py_ssize_t n = PyList_Size(lst);
free(g_hooks);
g_hooks = (uint32_t *)malloc(sizeof(uint32_t) * (size_t)n);
g_nhooks = (int)n;
for (Py_ssize_t i = 0; i < n; i++)
g_hooks[i] = (uint32_t)PyLong_AsUnsignedLong(PyList_GetItem(lst, i));
Py_RETURN_NONE;
}
static PyObject *py_run(PyObject *self, PyObject *args) {
(void)self;
unsigned long long max_steps;
if (!PyArg_ParseTuple(args, "K", &max_steps)) return NULL;
uint64_t start = C.steps;
int reason = 3;
while (C.steps - start < max_steps) {
if (is_hook(C.pc) || C.pc == C.ret_sentinel) { reason = 0; break; }
int fb = 0;
int stopped = step_once(&fb);
if (fb) { reason = 2; break; }
if (stopped) { reason = 1; break; }
}
return Py_BuildValue("iK", reason, (unsigned long long)C.steps);
}
static PyObject *py_getstate(PyObject *self, PyObject *args) {
(void)self; (void)args;
PyObject *regs = PyList_New(32), *fregs = PyList_New(32);
for (int i = 0; i < 32; i++) {
PyList_SetItem(regs, i, PyLong_FromUnsignedLong(C.r[i]));
PyList_SetItem(fregs, i, PyLong_FromUnsignedLong(C.f[i]));
}
return Py_BuildValue("{s:N,s:N,s:k,s:K,s:i}",
"r", regs, "f", fregs, "pc", C.pc,
"steps", (unsigned long long)C.steps, "dim", C.dim);
}
static PyObject *py_setreg(PyObject *self, PyObject *args) {
(void)self;
int i; unsigned long v; int isf;
if (!PyArg_ParseTuple(args, "iki", &i, &v, &isf)) return NULL;
if (isf) C.f[i] = (uint32_t)v; else C.r[i] = (uint32_t)v;
Py_RETURN_NONE;
}
static PyObject *py_setpc(PyObject *self, PyObject *args) {
(void)self;
unsigned long v;
if (!PyArg_ParseTuple(args, "k", &v)) return NULL;
C.pc = (uint32_t)v;
Py_RETURN_NONE;
}
static PyObject *py_setsentinel(PyObject *self, PyObject *args) {
(void)self;
unsigned long v;
if (!PyArg_ParseTuple(args, "k", &v)) return NULL;
C.ret_sentinel = (uint32_t)v;
Py_RETURN_NONE;
}
static PyObject *py_r32(PyObject *self, PyObject *args) {
(void)self;
unsigned long a;
if (!PyArg_ParseTuple(args, "k", &a)) return NULL;
return PyLong_FromUnsignedLong(mem_r32((uint32_t)a));
}
static PyObject *py_w32(PyObject *self, PyObject *args) {
(void)self;
unsigned long a, v;
if (!PyArg_ParseTuple(args, "kk", &a, &v)) return NULL;
mem_w32((uint32_t)a, (uint32_t)v);
Py_RETURN_NONE;
}
static PyMethodDef methods[] = {
{"init", py_init, METH_NOARGS, "reset state"},
{"load_blob", py_load_blob, METH_VARARGS, "load bytes at addr"},
{"set_hooks", py_set_hooks, METH_VARARGS, "set hook pcs"},
{"run", py_run, METH_VARARGS, "run until hook/stop/fallback/max"},
{"getstate", py_getstate, METH_NOARGS, "dump state"},
{"setreg", py_setreg, METH_VARARGS, "set r/f register"},
{"setpc", py_setpc, METH_VARARGS, "set pc"},
{"set_sentinel", py_setsentinel, METH_VARARGS, "set return sentinel"},
{"r32", py_r32, METH_VARARGS, "read mem"},
{"w32", py_w32, METH_VARARGS, "write mem"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef mod = {
PyModuleDef_HEAD_INIT, "emu860c", "i860 step-core in C", -1, methods,
NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC PyInit_emu860c(void) { return PyModule_Create(&mod); }