|
- """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
|