#!/usr/bin/env python3 """Two emulators, one decoder: MAME's cycles against px68k's C68K core. python3 tools/bench/c68k/compare.py [--mame tmp/mame_timed.log] [--c68k tmp/c68k.log] WHY THIS EXISTS. Every 68000 cycle figure in FINDINGS 24-35 comes from one instrument. This puts a second, structurally different one next to it: MAME 0.277 M68000 is the microcode core (src/devices/cpu/m68000/m68000.lst + m68000gen.py), NOT Musashi -- timing emerges from the 68000's modelled micro-sequence and 4-clock bus cycles. C68K a static per-instruction cycle table hand-transcribed from the Motorola manual (ORI_CLOCKS_* / EA_CLOCKS_* in c68kmacro.h). Those are two different ways of being right, so agreement is evidence and disagreement localises to whichever instruction the anchors separate. NEITHER charges GVRAM wait states, so both are the same lower bound on real hardware. """ import argparse, re, sys ap = argparse.ArgumentParser() ap.add_argument("--mame", default="tmp/mame_timed.log") ap.add_argument("--c68k", default="tmp/c68k.log") ap.add_argument("--meta", default="tmp/decode_meta.lua") a = ap.parse_args() meta = open(a.meta).read() fps = int(re.search(r"fps=(\d+)", meta).group(1)) budget = 10_000_000 / fps # anchor name -> stream offset, so the two logs can be joined: decode.lua # reports by name, the C68K harness by offset. names = {int(o): n for n, o in re.findall(r'name="([^"]+)", off=(\d+)', meta)} mame = {} txt = open(a.mame, errors="replace").read() for nm, cyc in re.findall(r"\[DEC\] frame @ (.+?)\n.*?->\s+(\d+) cycles/frame", txt): mame[nm.strip()] = int(cyc) m_seq = re.search(r"full \d+-frame pass.*?\n.*?->\s+(\d+) cycles/frame", txt) c68k, c_seq = {}, None for line in open(a.c68k, errors="replace"): m = re.search(r"anchor off=(\d+)\s+(\d+) cyc", line) if m and int(m.group(1)) in names: c68k[names[int(m.group(1))]] = int(m.group(2)) m = re.search(r"sequential pass = (\d+) cyc, mean (\d+)", line) if m: c_seq = int(m.group(2)) if not mame: sys.exit(f"no MAME anchor timings in {a.mame} -- run decode.lua WITHOUT " f"DLX_VERIFY_ONLY=1 and give -seconds_to_run enough to finish") w = max(len(n) for n in c68k) + 2 print(f"{'anchor':<{w}}{'MAME':>10}{'C68K':>10}{'delta':>9} {'MAME':>7}{'C68K':>7} of a {fps}fps frame") rows = [] for nm, c in c68k.items(): m = mame.get(nm) if m is None: print(f"{nm:<{w}}{'--':>10}{c:>10}{'':>9} {'--':>7}{100*c/budget:>6.1f}% (MAME run did not reach it)") continue d = 100 * (c - m) / m rows.append(d) print(f"{nm:<{w}}{m:>10}{c:>10}{d:>+8.2f}% {100*m/budget:>6.1f}%{100*c/budget:>6.1f}%") if m_seq and c_seq: m, c = int(m_seq.group(1)), c_seq d = 100 * (c - m) / m print(f"{'MEAN over the window':<{w}}{m:>10}{c:>10}{d:>+8.2f}% " f"{100*m/budget:>6.1f}%{100*c/budget:>6.1f}%") if rows: print(f"\nspread over {len(rows)} anchors: {min(rows):+.2f}% .. {max(rows):+.2f}%") print("C68K reads HIGH throughout." if min(rows) > 0 else "C68K reads high on some anchors and low on others.") print("Neither instrument charges GVRAM wait states, so both are the same\n" "LOWER BOUND: this bounds cycle-table error, not the distance to a\n" "real X68000 (docs/BENCHMARK.md Tier 3).")