Skip to content

Instantly share code, notes, and snippets.

@zserge
Created December 28, 2024 19:28
Show Gist options
  • Save zserge/e5a18c954ab18b5b07be7ff516b5387a to your computer and use it in GitHub Desktop.
Save zserge/e5a18c954ab18b5b07be7ff516b5387a to your computer and use it in GitHub Desktop.
tiny 6502 emulator in python
import base64
OPSZ = [1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0]
WOZMON = base64.b64decode('2Figf4wS0KmnjRHQjRPQyd/wE8mb8APIEA+p3CDv/6mNIO//oAGIMPatEdAQ+60Q0JkAAiDv/8mN0NSg/6kAqgqFK8i5AALJjfDUya6Q9PDwybrw68nS8DuGKIYphCq5AAJJsMkKkAZpiMn6kBEKCgoKogQKJigmKcrQ+MjQ4MQq8JckK1AQpSiBJuYm0LXmJ0xE/2wkADArogK1J5UllSPK0PfQFKmNIO//pSUg3P+lJCDc/6m6IO//qaAg7/+hJCDc/4YrpSTFKKUl5SmwweYk0ALmJaUkKQcQyEhKSkpKIOX/aCkPCbDJupACaQYsEtAw+40S0GAAAAAPAP8AAA==')
WOZACI = base64.b64decode('qaog7/+pjSDv/6D/yK0R0BD7rRDQmQACIO//yZvw4cmN0Omi/6kAhSSFJYUmhSfovQACydLwVsnX8DXJrvAnyY3wIMmg8OhJsMkKkAZpiMn6kK0KCgoKoAQKJiQmJYjQ+PDMTBr/pSSFJqUlhSewv6lAIMzBiKIAoSaiEAog28HQ+iDxwaAekOymKLCYILzBqRYgzMEgvMGgHyC/wbD5IL/BoDqiCEggvMFoKqA5ytD1gSYg8cGgNZDqsM0gv8GIrYHAxSnw+IUpwIBghiigQiDgwdD5af6w9aAeIODBoCyI0P2QBaAviND9vADAoCnKYKUmxSSlJ+Ul5ibQAuYnYA==')
INTBASIC = base64.b64decode('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')
ABS, ABX, ABY, ACC, IMM, IND, IZX, IZY, REL, ZPG, ZPX, ZPY = range(12)
def u16(n): return n & 0xFFFF
def u8(n): return n & 0xFF
def to16(lo, hi): return lo + hi * 0x100
class MOS6502:
def __init__(self, bus):
self.bus = bus
self.a, self.x, self.y = 0, 0, 0
self.s, self.f, self.pc = 0xFD, 0x20, to16(bus[0xFFFC], bus[0xFFFD])
def fetch(self, offset): return self.bus[u16(self.pc + offset)]
def fetch16(self, offset): return to16(self.fetch(offset), self.fetch(u16(offset + 1)))
def push(self, value): self.bus[self.s | 0x100] = value; self.s = u8(self.s - 1)
def pop(self): self.s = u8(self.s + 1); return self.bus[self.s | 0x100]
def push16(self, value): self.push(u8(value >> 8)); self.push(u8(value))
def pop16(self): return to16(self.pop(), self.pop())
def zp16(self, addr): return to16(self.bus[addr], self.bus[u16(addr + 1)])
def addr(self, mode):
if mode == ABS: return to16(self.fetch(1), self.fetch(2))
elif mode == ABX: return u16(self.addr(ABS) + self.x)
elif mode == ABY: return u16(self.addr(ABS) + self.y)
elif mode == IMM: return u16(self.pc + 1)
elif mode == IZX: return self.zp16(u8(self.fetch(1) + self.x))
elif mode == IZY: return u16(self.zp16(self.fetch(1)) + self.y)
elif mode == REL: return (self.fetch(1) ^ 0x80) - 0x80
elif mode == ZPG: return self.fetch(1)
elif mode == ZPX: return u8(self.fetch(1) + self.x)
elif mode == ZPY: return u8(self.fetch(1) + self.y)
elif mode == IND:
addr = self.fetch16(1);
return to16(self.bus[addr], self.bus[(addr & 0xff00) | u8(addr + 1)])
def read(self, mode): return self.a if mode == ACC else self.bus[self.addr(mode)]
def write(self, mode, value):
if mode == ACC: self.a = value
else: self.bus[self.addr(mode)] = value
def nz(self, x):
self.f = (self.f & 0x7D) | ((int(x == 0)) << 1) | (x & 0x80)
return x
def flags(self, mode):
v = self.read(mode)
self.f = (self.f & 0x3D) | (0xC0 & v) | ((int((v & self.a) == 0)) << 1)
def inc(self, mode, n):
value = self.nz(u8(self.read(mode) + n))
self.write(mode, value)
def cmp(self, mode, n):
m = self.read(mode)
self.f = (self.f & 0xFE) | (int(n >= m))
self.nz(u8(n - m))
def bit(self, and_val, or_val, xor_val):
self.a = self.nz(u8(((self.a & and_val) | or_val) ^ xor_val))
def shift(self, mode, left, rot):
b = self.read(mode)
if left:
r = u8((b << 1) | ((self.f & 1) * rot))
f = (self.f & 0xFE) | (b >> 7)
else:
r = u8((b >> 1) | (((self.f & 1) << 7) * rot))
f = (self.f & 0xFE) | (b & 1)
self.f = f
self.nz(r)
self.write(mode, r)
def adc(self, mode, neg):
n = self.read(mode)
if self.f & 8 == 0:
r = self.a + u16(n ^ (0xFF * neg)) + (self.f & 1)
v = u16((0xFF * (1 - neg)) ^ (self.a ^ n)) & (self.a ^ r) & 0x80
self.f = self.f & 0xBE | ((r & 0x100) >> 8) | u8(v >> 1)
self.a = self.nz(u8(r))
else:
if neg:
nd = ((n >> 4) * 10) + (n & 0x0F) + (1 - (self.f & 1))
val = ((self.a >> 4) * 10) + (self.a & 0x0F) - nd
self.f = self.f | 1
if val < 0:
self.f = self.f & 0xFE
val = val + 100
self.a = u8(self.nz((int(val / 10) << 4) + (val % 10)))
else:
p1 = (self.a & 0xF) + (n & 0xF) + (self.f & 1)
p2 = int(p1 >= 10) + (self.a >> 4) + (n >> 4)
self.f = self.f & 0xFE
if p1 >= 10:
p1 = p1 - 10
if p2 >= 10:
self.f = self.f | 1
p2 = p2 - 10
self.a = u8(self.nz((p2 << 4) + p1))
def step(self):
op = self.fetch(0)
next_pc = self.pc + OPSZ[op]
branch = lambda cond: self.addr(REL) if cond else 0
if op == 0x00: # BRK,IMP,1,7,cziDBvn
self.push16(next_pc + 1)
self.push(self.f | 0x30)
self.f = self.f | 4
next_pc = to16(self.bus[0xFFFE], self.bus[0xFFFF])
elif op == 0x20: self.push16(u16(next_pc - 1)); next_pc = self.addr(ABS) # JSR,ABS,3,6,czidbvn
elif op == 0x69: self.adc(IMM, 0) # ADC,IMM,2,2,CZidbVN
elif op == 0x65: self.adc(ZPG, 0) # ADC,ZP,2,3,CZidbVN
elif op == 0x75: self.adc(ZPX, 0) # ADC,ZPX,2,4,CZidbVN
elif op == 0x6D: self.adc(ABS, 0) # ADC,ABS,3,4,CZidbVN
elif op == 0x7D: self.adc(ABX, 0) # ADC,AX,3,4,CZidbVN
elif op == 0x79: self.adc(ABY, 0) # ADC,AY,3,4,CZidbVN
elif op == 0x61: self.adc(IZX, 0) # ADC,ZIX,2,6,CZidbVN
elif op == 0x71: self.adc(IZY, 0) # ADC,ZIY,2,5,CZidbVN
elif op == 0xE9: self.adc(IMM, 1) # SBC,IMM,2,2,CZidbVN
elif op == 0xE5: self.adc(ZPG, 1) # SBC,ZP,2,3,CZidbVN
elif op == 0xF5: self.adc(ZPX, 1) # SBC,ZPX,2,4,CZidbVN
elif op == 0xED: self.adc(ABS, 1) # SBC,ABS,3,4,CZidbVN
elif op == 0xFD: self.adc(ABX, 1) # SBC,AX,3,4,CZidbVN
elif op == 0xF9: self.adc(ABY, 1) # SBC,AY,3,4,CZidbVN
elif op == 0xE1: self.adc(IZX, 1) # SBC,ZIX,2,6,CZidbVN
elif op == 0xF1: self.adc(IZY, 1) # SBC,ZIY,2,5,CZidbVN
elif op == 0x29: self.bit(self.read(IMM), 0, 0) # AND,IMM,2,2,cZidbvN
elif op == 0x25: self.bit(self.read(ZPG), 0, 0) # AND,ZP,2,3,cZidbvN
elif op == 0x35: self.bit(self.read(ZPX), 0, 0) # AND,ZPX,2,4,cZidbvN
elif op == 0x2D: self.bit(self.read(ABS), 0, 0) # AND,ABS,3,4,cZidbvN
elif op == 0x3D: self.bit(self.read(ABX), 0, 0) # AND,AX,3,4,cZidbvN
elif op == 0x39: self.bit(self.read(ABY), 0, 0) # AND,AY,3,4,cZidbvN
elif op == 0x21: self.bit(self.read(IZX), 0, 0) # AND,ZIX,2,6,cZidbvN
elif op == 0x31: self.bit(self.read(IZY), 0, 0) # AND,ZIY,2,5,cZidbvN
elif op == 0x09: self.bit(0xFF, self.read(IMM), 0) # ORA,IMM,2,2,cZidbvN
elif op == 0x05: self.bit(0xFF, self.read(ZPG), 0) # ORA,ZP,2,3,cZidbvN
elif op == 0x15: self.bit(0xFF, self.read(ZPX), 0) # ORA,ZPX,2,4,cZidbvN
elif op == 0x0D: self.bit(0xFF, self.read(ABS), 0) # ORA,ABS,3,4,cZidbvN
elif op == 0x1D: self.bit(0xFF, self.read(ABX), 0) # ORA,AX,3,4,cZidbvN
elif op == 0x19: self.bit(0xFF, self.read(ABY), 0) # ORA,AY,3,4,cZidbvN
elif op == 0x01: self.bit(0xFF, self.read(IZX), 0) # ORA,ZIX,2,6,cZidbvN
elif op == 0x11: self.bit(0xFF, self.read(IZY), 0) # ORA,ZIY,2,5,cZidbvN
elif op == 0x49: self.bit(0xFF, 0, self.read(IMM)) # EOR,IMM,2,2,cZidbvN
elif op == 0x45: self.bit(0xFF, 0, self.read(ZPG)) # EOR,ZP,2,3,cZidbvN
elif op == 0x55: self.bit(0xFF, 0, self.read(ZPX)) # EOR,ZPX,2,4,cZidbvN
elif op == 0x4D: self.bit(0xFF, 0, self.read(ABS)) # EOR,ABS,3,4,cZidbvN
elif op == 0x5D: self.bit(0xFF, 0, self.read(ABX)) # EOR,AX,3,4,cZidbvN
elif op == 0x59: self.bit(0xFF, 0, self.read(ABY)) # EOR,AY,3,4,cZidbvN
elif op == 0x41: self.bit(0xFF, 0, self.read(IZX)) # EOR,ZIX,2,6,cZidbvN
elif op == 0x51: self.bit(0xFF, 0, self.read(IZY)) # EOR,ZIY,2,5,cZidbvN
elif op == 0x0A: self.shift(ACC, True, 0) # ASL,ACC,1,2,CZidbvN
elif op == 0x06: self.shift(ZPG, True, 0) # ASL,ZP,2,5,CZidbvN
elif op == 0x16: self.shift(ZPX, True, 0) # ASL,ZPX,2,6,CZidbvN
elif op == 0x0E: self.shift(ABS, True, 0) # ASL,ABS,3,6,CZidbvN
elif op == 0x1E: self.shift(ABX, True, 0) # ASL,AX,3,6/7,CZidbvN
elif op == 0x4A: self.shift(ACC, False, 0) # LSR,ACC,1,2,cZidbvN
elif op == 0x46: self.shift(ZPG, False, 0) # LSR,ZP,2,5,cZidbvN
elif op == 0x56: self.shift(ZPX, False, 0) # LSR,ZPX,2,6,cZidbvN
elif op == 0x4E: self.shift(ABS, False, 0) # LSR,ABS,3,6,cZidbvN
elif op == 0x5E: self.shift(ABX, False, 0) # LSR,AX,3,6/7,cZidbvN
elif op == 0x2A: self.shift(ACC, True, 1) # ROL,ACC,1,2,CZidbvN
elif op == 0x26: self.shift(ZPG, True, 1) # ROL,ZP,2,5,CZidbvN
elif op == 0x36: self.shift(ZPX, True, 1) # ROL,ZPX,2,6,CZidbvN
elif op == 0x2E: self.shift(ABS, True, 1) # ROL,ABS,3,6,CZidbvN
elif op == 0x3E: self.shift(ABX, True, 1) # ROL,AX,3,6/7,CZidbvN
elif op == 0x6A: self.shift(ACC, False, 1) # ROR,ACC,1,2,CZidbvN
elif op == 0x66: self.shift(ZPG, False, 1) # ROR,ZP,2,5,CZidbvN
elif op == 0x76: self.shift(ZPX, False, 1) # ROR,ZPX,2,6,CZidbvN
elif op == 0x7E: self.shift(ABX, False, 1) # ROR,ABS,3,6,CZidbvN
elif op == 0x6E: self.shift(ABS, False, 1) # ROR,AX,3,6/7,CZidbvN
elif op == 0xCA: self.x = self.nz(u8(self.x - 1)) # DEX,IMP,1,2,cZidbvN
elif op == 0x88: self.y = self.nz(u8(self.y - 1)) # DEY,IMP,1,2,cZidbvN
elif op == 0xE8: self.x = self.nz(u8(self.x + 1)) # INX,IMP,1,2,cZidbvN
elif op == 0xC8: self.y = self.nz(u8(self.y + 1)) # INY,IMP,1,2,cZidbvN
elif op == 0xE6: self.inc(ZPG, 1) # INC,ZP,2,5,cZidbvN
elif op == 0xF6: self.inc(ZPX, 1) # INC,ZPX,2,6,cZidbvN
elif op == 0xEE: self.inc(ABS, 1) # INC,ABS,3,6,cZidbvN
elif op == 0xFE: self.inc(ABX, 1) # INC,AX,3,7,cZidbvN
elif op == 0xC6: self.inc(ZPG, 0xFF) # DEC,ZP,2,5,cZidbvN
elif op == 0xD6: self.inc(ZPX, 0xFF) # DEC,ZPX,2,6,cZidbvN
elif op == 0xCE: self.inc(ABS, 0xFF) # DEC,ABS,3,6,cZidbvN
elif op == 0xDE: self.inc(ABX, 0xFF) # DEC,AX,3,7,cZidbvN
elif op == 0xC9: self.cmp(IMM, self.a) # CMP,IMM,2,2,CZidbvN
elif op == 0xC5: self.cmp(ZPG, self.a) # CMP,ZP,2,3,CZidbvN
elif op == 0xD5: self.cmp(ZPX, self.a) # CMP,ZPX,2,4,CZidbvN
elif op == 0xCD: self.cmp(ABS, self.a) # CMP,ABS,3,4,CZidbvN
elif op == 0xDD: self.cmp(ABX, self.a) # CMP,AX,3,4,CZidbvN
elif op == 0xD9: self.cmp(ABY, self.a) # CMP,AY,3,4,CZidbvN
elif op == 0xC1: self.cmp(IZX, self.a) # CMP,ZIX,2,6,CZidbvN
elif op == 0xD1: self.cmp(IZY, self.a) # CMP,ZIY,2,5,CZidbvN
elif op == 0xE0: self.cmp(IMM, self.x) # CPX,IMM,2,2,CZidbvN
elif op == 0xE4: self.cmp(ZPG, self.x) # CPX,ZP,2,3,CZidbvN
elif op == 0xEC: self.cmp(ABS, self.x) # CPX,ABS,3,4,CZidbvN
elif op == 0xC0: self.cmp(IMM, self.y) # CPY,IMM,2,2,CZidbvN
elif op == 0xC4: self.cmp(ZPG, self.y) # CPY,ZP,2,3,CZidbvN
elif op == 0xCC: self.cmp(ABS, self.y) # CPY,ABS,3,4,CZidbvN
elif op == 0x18: self.f = self.f & 0xFE # CLC,IMP,1,2,Czidbvn
elif op == 0xD8: self.f = self.f & 0xF7 # CLD,IMP,1,2,cziDbvn
elif op == 0x58: self.f = self.f & 0xFB # CLI,IMP,1,2,czIdbvn
elif op == 0xB8: self.f = self.f & 0xBF # CLV,IMP,1,2,czidbVn
elif op == 0x38: self.f = self.f | 0x01 # SEC,IMP,1,2,Czidbvn
elif op == 0xF8: self.f = self.f | 0x08 # SED,IMP,1,2,cziDbvn
elif op == 0x78: self.f = self.f | 0x04 # SEI,IMP,1,2,czIdbvn
elif op == 0x24: self.flags(ZPG), # BIT,ZP,2,3,cZidbVN
elif op == 0x2C: self.flags(ABS), # BIT,ABS,3,4,cZidbVN
elif op == 0x90: next_pc += branch((self.f & 0x01) == 0) # BCC,REL,2,2/3,czidbvn
elif op == 0xB0: next_pc += branch((self.f & 0x01) != 0) # BCS,REL,2,2/3,czidbvn
elif op == 0xF0: next_pc += branch((self.f & 0x02) != 0) # BEQ,REL,2,2/3,czidbvn
elif op == 0x30: next_pc += branch((self.f & 0x80) != 0) # BMI,REL,2,2/3,czidbvn
elif op == 0xD0: next_pc += branch((self.f & 0x02) == 0) # BNE,REL,2,2/3,czidbvn
elif op == 0x10: next_pc += branch((self.f & 0x80) == 0) # BPL,REL,2,2/3,czidbvn
elif op == 0x50: next_pc += branch((self.f & 0x40) == 0) # BVC,REL,2,2/3,czidbvn
elif op == 0x70: next_pc += branch((self.f & 0x40) != 0) # BVS,REL,2,2/3,czidbvn
elif op == 0x4C: next_pc = self.addr(ABS) # JMP,ABS,3,3,czidbvn
elif op == 0x6C: next_pc = self.addr(IND) # JMP,IND,3,5,czidbvn
elif op == 0x48: self.push(self.a) # PHA,IMP,1,3,czidbvn
elif op == 0x68: n = self.pop(); self.a = self.nz(n) # PLA,IMP,1,4,cZidbvN
elif op == 0x08: self.push(self.f | 0x30) # PHP,IMP,1,3,czidbvn
elif op == 0x28: self.f = self.pop() | 0x20 # PLP,IMP,1,4,CZIDBVN
elif op == 0x40: self.f = self.pop() | 0x20; next_pc = self.pop16() # RTI,IMP,1,6,czidbvn
elif op == 0x60: next_pc = self.pop16() + 1 # RTS,IMP,1,6,czidbvn
elif op == 0xAA: self.x = self.nz(self.a) # TAX,IMP,1,2,cZidbvN
elif op == 0x8A: self.a = self.nz(self.x) # TXA,IMP,1,2,cZidbvN
elif op == 0xA8: self.y = self.nz(self.a) # TAY,IMP,1,2,cZidbvN
elif op == 0x98: self.a = self.nz(self.y) # TYA,IMP,1,2,cZidbvN
elif op == 0xBA: self.x = self.nz(self.s) # TSX,IMP,1,2,cZidbvN
elif op == 0x9A: self.s = self.x # TXS,IMP,1,2,czidbvn
elif op == 0xA9: self.a = self.nz(self.read(IMM)) # LDA,IMM,2,2,cZidbvN
elif op == 0xA5: self.a = self.nz(self.read(ZPG)) # LDA,ZP,2,3,cZidbvN
elif op == 0xB5: self.a = self.nz(self.read(ZPX)) # LDA,ZPX,2,4,cZidbvN
elif op == 0xAD: self.a = self.nz(self.read(ABS)) # LDA,ABS,3,4,cZidbvN
elif op == 0xBD: self.a = self.nz(self.read(ABX)) # LDA,AX,3,4,cZidbvN
elif op == 0xB9: self.a = self.nz(self.read(ABY)) # LDA,AY,3,4,cZidbvN
elif op == 0xA1: self.a = self.nz(self.read(IZX)) # LDA,ZIX,2,6,cZidbvN
elif op == 0xB1: self.a = self.nz(self.read(IZY)) # LDA,ZIY,2,5,cZidbvN
elif op == 0xA2: self.x = self.nz(self.read(IMM)) # LDX,IMM,2,2,cZidbvN
elif op == 0xA6: self.x = self.nz(self.read(ZPG)) # LDX,ZP,2,3,cZidbvN
elif op == 0xB6: self.x = self.nz(self.read(ZPY)) # LDX,ZPY,2,4,cZidbvN
elif op == 0xAE: self.x = self.nz(self.read(ABS)) # LDX,ABS,3,4,cZidbvN
elif op == 0xBE: self.x = self.nz(self.read(ABY)) # LDX,AY,3,4,cZidbvN
elif op == 0xA0: self.y = self.nz(self.read(IMM)) # LDY,IMM,2,2,cZidbvN
elif op == 0xA4: self.y = self.nz(self.read(ZPG)) # LDY,ZP,2,3,cZidbvN
elif op == 0xB4: self.y = self.nz(self.read(ZPX)) # LDY,ZPX,2,4,cZidbvN
elif op == 0xAC: self.y = self.nz(self.read(ABS)) # LDY,ABS,3,4,cZidbvN
elif op == 0xBC: self.y = self.nz(self.read(ABX)) # LDY,AX,3,4,cZidbvN
elif op == 0x85: self.write(ZPG, self.a) # STA,ZP,2,3,czidbvn
elif op == 0x95: self.write(ZPX, self.a) # STA,ZPX,2,4,czidbvn
elif op == 0x8D: self.write(ABS, self.a) # STA,ABS,3,4,czidbvn
elif op == 0x9D: self.write(ABX, self.a) # STA,AX,3,5,czidbvn
elif op == 0x99: self.write(ABY, self.a) # STA,AY,3,5,czidbvn
elif op == 0x81: self.write(IZX, self.a) # STA,ZIX,2,6,czidbvn
elif op == 0x91: self.write(IZY, self.a) # STA,ZIY,2,6,czidbvn
elif op == 0x86: self.write(ZPG, self.x) # STX,ZP,2,3,czidbvn
elif op == 0x96: self.write(ZPY, self.x) # STX,ZPY,2,4,czidbvn
elif op == 0x8E: self.write(ABS, self.x) # STX,ABS,3,4,czidbvn
elif op == 0x84: self.write(ZPG, self.y) # STY,ZP,2,3,czidbvn
elif op == 0x94: self.write(ZPX, self.y) # STY,ZPX,2,4,czidbvn
elif op == 0x8C: self.write(ABS, self.y) # STY,ABS,3,4,czidbvn
elif op == 0xEA: pass # NOP,IMP,1,2,czidbvn
else: print(op, self.pc); raise()
self.pc = u16(next_pc)
class AppleI:
def __init__(self):
self.keys = []
self.mem = bytearray(0x1000)
def __getitem__(self, i):
if i >= 0 and i < 0x1000: return self.mem[i] # 4K RAM
elif i >= 0xc100 and i <= 0xc1ff: return WOZACI[i - 0xc100]
elif i >= 0xff00 and i <= 0xffff: return WOZMON[i - 0xff00]
elif i >= 0xe000 and i <= 0xefff: return INTBASIC[i - 0xe000]
elif i == 0xd010: return self.keys.pop(0)|0x80 if len(self.keys) else 0x80
elif i == 0xd011: return 0x80 if len(self.keys) else 0
else: return 0
def __setitem__(self, i, n):
if i >= 0 and i < 0x0fff: self.mem[i] = n
elif i == 0xd012:
if n & 0x7f == 0x0d: print('')
elif n & 0x7f == 0x5f: print('\b', end = '', flush=True)
else: print(chr(n&0x7f), end='', flush=True)
def send_key(self, c):
self.keys.append(c)
import sys, termios, tty, select
a1 = AppleI()
cpu = MOS6502(a1)
attr = termios.tcgetattr(sys.stdin)
try:
tc = termios.tcgetattr(sys.stdin)
tc[3] = (tc[3] & ~termios.ICANON & ~termios.ECHO)
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, tc)
while True:
cpu.step()
if select.select([sys.stdin], [], [], 0.00001) == ([sys.stdin], [], []):
c = sys.stdin.read(1)
a1.send_key(13 if c == '\n' else ord(c))
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attr)
@AlexYeryomin
Copy link

I was looking for information on 6502 as I want to emulate RAM/ROM for the real chip using ESP32, and I found your post on emulating the whole processor. Very nice, detailed, easy to follow article, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment