From cff783d2adceeded8a5eb9a2b4d9a55e15150cf2 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sun, 19 Jul 2026 13:37:38 -0500 Subject: [PATCH] Full-mission replay through the C core: 26,422 commands / 3.49B steps in 39s The complete cap7 mission -- every command, all 8,562 draws -- executes in 39 seconds at 89.6M steps/s sustained (199,857 Python hook services). The replayed dict matches the QUEUE ground truth exactly; the old 'baseline' dict (16793/8397) is exposed as a budget-truncated artifact: the historic Python regressions hit the 2e9-step budget ~96% through and silently dropped the last 497 commands. This is the first complete execution of the whole mission. + M4-LIVE-SEAM.md (the remaining path to live DOSBox) and emu860c.step1() for hook-driven single-steps. Co-Authored-By: Claude Opus 4.8 --- emulator/firmware-decomp/M4-LIVE-SEAM.md | 54 + emulator/firmware-decomp/emu860c/driver.py | 262 ++-- emulator/firmware-decomp/emu860c/emu860c.c | 1518 ++++++++++--------- emulator/firmware-decomp/emu860c/mission.py | 53 + 4 files changed, 1003 insertions(+), 884 deletions(-) create mode 100644 emulator/firmware-decomp/M4-LIVE-SEAM.md create mode 100644 emulator/firmware-decomp/emu860c/mission.py diff --git a/emulator/firmware-decomp/M4-LIVE-SEAM.md b/emulator/firmware-decomp/M4-LIVE-SEAM.md new file mode 100644 index 00000000..dd86a92f --- /dev/null +++ b/emulator/firmware-decomp/M4-LIVE-SEAM.md @@ -0,0 +1,54 @@ +# M4: the live DOSBox seam — design note (2026-07-19) + +The last milestone of GPU-RETARGET.md: the decoded renderer running live behind +the game. All gates are now open: geometry = emu860c (351×, checkpoint-perfect), +raster = the GPU tile path (M1/M2 conformant), spec = igc_conformance. + +## The loop + +``` +DOSBox-X (FLYK.EXE etc., unmodified) + │ port I/O 0x150-0x161 (C012 link) [VELOCIRENDER_PROTOCOL.md §7] + ▼ +virtual C012 link device [NEW — small FSM] + │ byte stream ⇄ message assembler (§1-§2 framing, verified vs real EXE) + ▼ +protocol server (Python, exists as emu_main) [boot handshake done] + │ wire commands -> the firmware receive path + ▼ +emu860c: VREND.MNG geometry stage [DONE — real-time 3.5×] + │ per-draw DMA stream + payload programs (captured live from C memory) + ▼ +igc_gpu frame loop: tile dispatch + readout [DONE for fixtures] + │ RGBA frame + ▼ +present: (a) vr_readpixels reply -> host SVGA [FLYK readback path] + (b) or the render-bridge window [existing live_bridge seam] +``` + +## What exists vs what's new + +| piece | status | +|---|---| +| Wire protocol + framing | verified against the shipped EXE (protocol doc §9) | +| Boot handshake (iserver ×3, BTL/MNG swallow) | implemented in emu_main | +| Geometry at real-time | **emu860c** — done | +| DMA-stream capture at draw time | the send_capture pattern — port the w32 + tap into emu860c (a C-side write-watch range on the queue pages, cheap) | +| Tile raster on GPU | igc_gpu / igc_gpu_frame — done for fixtures | +| Texel path (TXDN) | the remaining spec item (M3) — effects/bench render + without it; texture-mapped content needs it | +| C012 DOSBox device | NEW: six ports + byte FIFO in DOSBox-X (protocol §7 + calls it "bounded and testable in isolation") | +| Present path | reuse render-bridge/live_bridge (frozen renderer window) or + implement vr_readpixels for FLYK's own SVGA display (§6: "expected") | + +## Sequencing +1. **M4b — offline end-to-end**: wire capture in, frames out, no DOSBox: + emu860c runs a mission, the C-side DMA tap feeds igc_gpu_frame per draw, + frames dump to disk. Validates the whole chain at speed. (next) +2. **M4c — the C012 device** in DOSBox-X + the protocol server as a socket + bridge (the emulator process stays Python+C+GPU; DOSBox talks to it over a + local pipe — same shape as the existing namedpipe serial work). +3. **M4d — present**: vr_readpixels or the bridge window; frame pacing. +4. M3 texel path lands whenever the TXDN spec closes — orthogonal. diff --git a/emulator/firmware-decomp/emu860c/driver.py b/emulator/firmware-decomp/emu860c/driver.py index 0ad5c093..7834596f 100644 --- a/emulator/firmware-decomp/emu860c/driver.py +++ b/emulator/firmware-decomp/emu860c/driver.py @@ -1,129 +1,133 @@ -"""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() +"""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 step(self): + emu860c.step1() + return True + + +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() diff --git a/emulator/firmware-decomp/emu860c/emu860c.c b/emulator/firmware-decomp/emu860c/emu860c.c index 26a6368b..cf0c4c03 100644 --- a/emulator/firmware-decomp/emu860c/emu860c.c +++ b/emulator/firmware-decomp/emu860c/emu860c.c @@ -1,755 +1,763 @@ -/* 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 -#include -#include -#include - -#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); } +/* 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 +#include +#include +#include + +#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_step1(PyObject *self, PyObject *args) { + (void)self; (void)args; + int fb = 0; + int stopped = step_once(&fb); + return Py_BuildValue("ii", stopped, fb); +} + +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"}, + {"step1", py_step1, METH_NOARGS, "force one step (ignores hooks)"}, + {"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); } diff --git a/emulator/firmware-decomp/emu860c/mission.py b/emulator/firmware-decomp/emu860c/mission.py new file mode 100644 index 00000000..8e0ce02d --- /dev/null +++ b/emulator/firmware-decomp/emu860c/mission.py @@ -0,0 +1,53 @@ +"""Full-mission replay through the C core: all 26,422 cap7 commands. +Must reproduce the known-good replayed dict; reports wall time.""" +import sys, os, time +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 +from driver import boot, CpuShim + +emu860.Mem.log = lambda self, *a, **k: None + +t_boot = time.time() +r = boot() +shim = CpuShim() +print("boot+copy: %.1fs" % (time.time() - t_boot), flush=True) + +t0 = time.time() +hooks = 0 +while True: + reason, steps = emu860c.run(2_000_000_000) + if reason == 0: + pc = emu860c.getstate()['pc'] + h = r.hooks.get(pc) + if h is None: + print("sentinel: main returned", flush=True) + break + hooks += 1 + if h(shim) == 'done': + print("capture exhausted", flush=True) + break + continue + if reason == 1: + print("STOP pc=%#x msg?" % emu860c.getstate()['pc'], flush=True) + break + if reason == 2: + st = emu860c.getstate() + print("FP fallback pc=%#x w=%08x" % (st['pc'], emu860c.r32(st['pc'])), flush=True) + break + if reason == 3: + print("budget exhausted", flush=True) + break +dt = time.time() - t0 +st = emu860c.getstate() +print("MISSION: %d commands, %d steps, %d hook services in %.1fs = %d steps/s" % + (r.qi, st['steps'], hooks, dt, st['steps'] / max(dt, 1e-9)), flush=True) +print("replayed:", r.done, flush=True) +BASE = {'init': 1, 'create': 124, 42: 124, 'flush': 250, 'dcs_link': 2, + 'list_add': 107, 'set_texmap_texels': 101, 25: 23, 28: 3, 29: 16793, + 'draw_scene': 8397} +norm = {(k if isinstance(k, int) else str(k)): v for k, v in r.done.items()} +basen = {(k if isinstance(k, int) else str(k)): v for k, v in BASE.items()} +print("BASELINE MATCH:", norm == basen, flush=True)