"""Minimal NMOS 6502 emulator (official opcodes, binary-mode ADC/SBC) with cycle counts. Used to check that a rewritten runtime helper behaves exactly like the original one (same zero page / memory / registers afterwards) and to compare their cycle costs. """ import sys, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from pcopt import DEC, SIZE BASECYC = { 'imp': 2, 'acc': 2, 'imm': 2, 'zp': 3, 'zpx': 4, 'zpy': 4, 'abs': 4, 'abx': 4, 'aby': 4, 'izx': 6, 'izy': 5, 'rel': 2, 'ind': 5, } # read-modify-write and store extra cycles RMW = {'asl', 'lsr', 'rol', 'ror', 'inc', 'dec'} STORE = {'sta', 'stx', 'sty'} class CPU: def __init__(s): s.m = bytearray(65536) s.a = s.x = s.y = 0 s.sp = 0xFF s.pc = 0 s.c = s.z = s.n = s.v = 0 s.cyc = 0 s.i = 0; s.d = 0 s.watch = None # --- helpers def rd(s, a): return s.m[a & 0xFFFF] def wr(s, a, v): s.m[a & 0xFFFF] = v & 255 def push(s, v): s.m[0x100 + s.sp] = v & 255 s.sp = (s.sp - 1) & 255 def pull(s): s.sp = (s.sp + 1) & 255 return s.m[0x100 + s.sp] def nz(s, v): v &= 255 s.z = 1 if v == 0 else 0 s.n = v >> 7 return v def flags(s): return s.c | s.z << 1 | s.i << 2 | s.d << 3 | 0x30 | s.v << 6 | s.n << 7 def setflags(s, p): s.c = p & 1; s.z = p >> 1 & 1; s.i = p >> 2 & 1; s.d = p >> 3 & 1; s.v = p >> 6 & 1; s.n = p >> 7 & 1 def ea(s, md, pc): """effective address, extra-cycle flag""" m = s.m if md == 'zp': return m[pc + 1], 0 if md == 'zpx': return (m[pc + 1] + s.x) & 255, 0 if md == 'zpy': return (m[pc + 1] + s.y) & 255, 0 base = m[pc + 1] | m[pc + 2] << 8 if md == 'abs': return base, 0 if md == 'abx': a = (base + s.x) & 0xFFFF; return a, (a >> 8) != (base >> 8) if md == 'aby': a = (base + s.y) & 0xFFFF; return a, (a >> 8) != (base >> 8) if md == 'izx': p = (m[pc + 1] + s.x) & 255 return m[p] | m[(p + 1) & 255] << 8, 0 if md == 'izy': p = m[pc + 1] b = m[p] | m[(p + 1) & 255] << 8 a = (b + s.y) & 0xFFFF; return a, (a >> 8) != (b >> 8) raise ValueError(md) def step(s): pc = s.pc op = s.m[pc] if op == 0x80: # undocumented NOP #imm (2 bytes, 2 cycles) on the NMOS 6502 s.pc = (pc + 2) & 0xFFFF s.cyc += 2 return if op not in DEC: raise ValueError('bad opcode %02x at %04x' % (op, pc)) mn, md = DEC[op] n = SIZE[md] cyc = BASECYC[md] s.pc = (pc + n) & 0xFFFF if md == 'imm': val = s.m[pc + 1]; addr = None elif md in ('imp', 'acc', 'rel', 'ind'): val = None; addr = None else: addr, cross = s.ea(md, pc) val = None if mn in RMW or mn in STORE: if md in ('abx', 'aby', 'izy'): cyc += 1 # always penalised if mn in RMW: cyc += 2 if mn in STORE and md == 'abs': pass elif cross: cyc += 1 if mn in RMW and md in ('zp', 'zpx', 'abs', 'abx'): pass rdv = lambda: s.m[addr] if addr is not None else val if mn == 'lda': s.a = s.nz(rdv()) elif mn == 'ldx': s.x = s.nz(rdv()) elif mn == 'ldy': s.y = s.nz(rdv()) elif mn == 'sta': s.wr(addr, s.a) elif mn == 'stx': s.wr(addr, s.x) elif mn == 'sty': s.wr(addr, s.y) elif mn == 'tax': s.x = s.nz(s.a) elif mn == 'tay': s.y = s.nz(s.a) elif mn == 'txa': s.a = s.nz(s.x) elif mn == 'tya': s.a = s.nz(s.y) elif mn == 'tsx': s.x = s.nz(s.sp) elif mn == 'txs': s.sp = s.x elif mn == 'adc': b = rdv(); t = s.a + b + s.c s.v = 1 if (~(s.a ^ b) & (s.a ^ t) & 0x80) else 0 s.c = 1 if t > 255 else 0 s.a = s.nz(t) elif mn == 'sbc': b = rdv() ^ 255; t = s.a + b + s.c s.v = 1 if (~(s.a ^ b) & (s.a ^ t) & 0x80) else 0 s.c = 1 if t > 255 else 0 s.a = s.nz(t) elif mn == 'and': s.a = s.nz(s.a & rdv()) elif mn == 'ora': s.a = s.nz(s.a | rdv()) elif mn == 'eor': s.a = s.nz(s.a ^ rdv()) elif mn in ('cmp', 'cpx', 'cpy'): r = {'cmp': s.a, 'cpx': s.x, 'cpy': s.y}[mn] t = r - rdv() s.c = 1 if t >= 0 else 0 s.nz(t) elif mn == 'bit': b = rdv(); s.z = 1 if (s.a & b) == 0 else 0; s.n = b >> 7; s.v = b >> 6 & 1 elif mn in ('asl', 'lsr', 'rol', 'ror'): v = s.a if md == 'acc' else s.m[addr] if mn == 'asl': c = v >> 7; v = v << 1 elif mn == 'lsr': c = v & 1; v = v >> 1 elif mn == 'rol': c = v >> 7; v = v << 1 | s.c else: c = v & 1; v = v >> 1 | s.c << 7 s.c = c; v = s.nz(v) if md == 'acc': s.a = v else: s.wr(addr, v) elif mn == 'inc': s.wr(addr, s.nz(s.m[addr] + 1)) elif mn == 'dec': s.wr(addr, s.nz(s.m[addr] - 1)) elif mn == 'inx': s.x = s.nz(s.x + 1) elif mn == 'dex': s.x = s.nz(s.x - 1) elif mn == 'iny': s.y = s.nz(s.y + 1) elif mn == 'dey': s.y = s.nz(s.y - 1) elif mn == 'clc': s.c = 0 elif mn == 'sec': s.c = 1 elif mn == 'cld': s.d = 0 elif mn == 'sed': s.d = 1 elif mn == 'cli': s.i = 0 elif mn == 'sei': s.i = 1 elif mn == 'clv': s.v = 0 elif mn == 'nop': pass elif mn == 'pha': s.push(s.a); cyc = 3 elif mn == 'pla': s.a = s.nz(s.pull()); cyc = 4 elif mn == 'php': s.push(s.flags()); cyc = 3 elif mn == 'plp': s.setflags(s.pull()); cyc = 4 elif mn in ('bpl', 'bmi', 'bvc', 'bvs', 'bcc', 'bcs', 'bne', 'beq'): cond = {'bpl': not s.n, 'bmi': s.n, 'bvc': not s.v, 'bvs': s.v, 'bcc': not s.c, 'bcs': s.c, 'bne': not s.z, 'beq': s.z}[mn] if cond: off = s.m[pc + 1] if off > 127: off -= 256 t = (s.pc + off) & 0xFFFF cyc += 1 + ((t >> 8) != (s.pc >> 8)) s.pc = t elif mn == 'jmp': if md == 'abs': s.pc = s.m[pc + 1] | s.m[pc + 2] << 8; cyc = 3 else: p = s.m[pc + 1] | s.m[pc + 2] << 8 s.pc = s.m[p] | s.m[(p & 0xFF00) | ((p + 1) & 0xFF)] << 8 elif mn == 'jsr': r = (pc + 2) & 0xFFFF s.push(r >> 8); s.push(r & 255) s.pc = s.m[pc + 1] | s.m[pc + 2] << 8; cyc = 6 elif mn == 'rts': lo = s.pull(); hi = s.pull() s.pc = ((hi << 8 | lo) + 1) & 0xFFFF; cyc = 6 else: raise ValueError('unsupported ' + mn) s.cyc += cyc def call(s, addr, maxsteps=200000): """JSR addr and run until it returns; returns cycles used (excluding the JSR itself)""" SENT = 0xFFF0 s.push((SENT - 1) >> 8); s.push((SENT - 1) & 255) s.pc = addr c0 = s.cyc steps = 0 while s.pc != SENT: s.step() steps += 1 if steps > maxsteps: raise RuntimeError('runaway') return s.cyc - c0