166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
# m6502.py - minimal 6502 opcode table, instruction decoder and recursive-descent code tracer.
|
|
from dataclasses import dataclass
|
|
|
|
# addressing modes
|
|
IMP, ACC, IMM, ZP, ZPX, ZPY, ABS, ABX, ABY, IND, IZX, IZY, REL = range(13)
|
|
MODE_LEN = {IMP:1, ACC:1, IMM:2, ZP:2, ZPX:2, ZPY:2, ABS:3, ABX:3, ABY:3, IND:3, IZX:2, IZY:2, REL:2}
|
|
|
|
OPCODES = {}
|
|
def _def(mnemonic, table):
|
|
for opcode, mode in table.items():
|
|
OPCODES[opcode] = (mnemonic, mode)
|
|
|
|
_def("adc", {0x69:IMM,0x65:ZP,0x75:ZPX,0x6D:ABS,0x7D:ABX,0x79:ABY,0x61:IZX,0x71:IZY})
|
|
_def("and", {0x29:IMM,0x25:ZP,0x35:ZPX,0x2D:ABS,0x3D:ABX,0x39:ABY,0x21:IZX,0x31:IZY})
|
|
_def("asl", {0x0A:ACC,0x06:ZP,0x16:ZPX,0x0E:ABS,0x1E:ABX})
|
|
_def("bcc", {0x90:REL}); _def("bcs", {0xB0:REL}); _def("beq", {0xF0:REL}); _def("bmi", {0x30:REL})
|
|
_def("bne", {0xD0:REL}); _def("bpl", {0x10:REL}); _def("bvc", {0x50:REL}); _def("bvs", {0x70:REL})
|
|
_def("bit", {0x24:ZP,0x2C:ABS})
|
|
_def("brk", {0x00:IMP})
|
|
_def("clc", {0x18:IMP}); _def("cld", {0xD8:IMP}); _def("cli", {0x58:IMP}); _def("clv", {0xB8:IMP})
|
|
_def("cmp", {0xC9:IMM,0xC5:ZP,0xD5:ZPX,0xCD:ABS,0xDD:ABX,0xD9:ABY,0xC1:IZX,0xD1:IZY})
|
|
_def("cpx", {0xE0:IMM,0xE4:ZP,0xEC:ABS}); _def("cpy", {0xC0:IMM,0xC4:ZP,0xCC:ABS})
|
|
_def("dec", {0xC6:ZP,0xD6:ZPX,0xCE:ABS,0xDE:ABX}); _def("dex", {0xCA:IMP}); _def("dey", {0x88:IMP})
|
|
_def("eor", {0x49:IMM,0x45:ZP,0x55:ZPX,0x4D:ABS,0x5D:ABX,0x59:ABY,0x41:IZX,0x51:IZY})
|
|
_def("inc", {0xE6:ZP,0xF6:ZPX,0xEE:ABS,0xFE:ABX}); _def("inx", {0xE8:IMP}); _def("iny", {0xC8:IMP})
|
|
_def("jmp", {0x4C:ABS,0x6C:IND}); _def("jsr", {0x20:ABS})
|
|
_def("lda", {0xA9:IMM,0xA5:ZP,0xB5:ZPX,0xAD:ABS,0xBD:ABX,0xB9:ABY,0xA1:IZX,0xB1:IZY})
|
|
_def("ldx", {0xA2:IMM,0xA6:ZP,0xB6:ZPY,0xAE:ABS,0xBE:ABY})
|
|
_def("ldy", {0xA0:IMM,0xA4:ZP,0xB4:ZPX,0xAC:ABS,0xBC:ABX})
|
|
_def("lsr", {0x4A:ACC,0x46:ZP,0x56:ZPX,0x4E:ABS,0x5E:ABX})
|
|
_def("nop", {0xEA:IMP})
|
|
_def("ora", {0x09:IMM,0x05:ZP,0x15:ZPX,0x0D:ABS,0x1D:ABX,0x19:ABY,0x01:IZX,0x11:IZY})
|
|
_def("pha", {0x48:IMP}); _def("php", {0x08:IMP}); _def("pla", {0x68:IMP}); _def("plp", {0x28:IMP})
|
|
_def("rol", {0x2A:ACC,0x26:ZP,0x36:ZPX,0x2E:ABS,0x3E:ABX})
|
|
_def("ror", {0x6A:ACC,0x66:ZP,0x76:ZPX,0x6E:ABS,0x7E:ABX})
|
|
_def("rti", {0x40:IMP}); _def("rts", {0x60:IMP})
|
|
_def("sbc", {0xE9:IMM,0xE5:ZP,0xF5:ZPX,0xED:ABS,0xFD:ABX,0xF9:ABY,0xE1:IZX,0xF1:IZY})
|
|
_def("sec", {0x38:IMP}); _def("sed", {0xF8:IMP}); _def("sei", {0x78:IMP})
|
|
_def("sta", {0x85:ZP,0x95:ZPX,0x8D:ABS,0x9D:ABX,0x99:ABY,0x81:IZX,0x91:IZY})
|
|
_def("stx", {0x86:ZP,0x96:ZPY,0x8E:ABS}); _def("sty", {0x84:ZP,0x94:ZPX,0x8C:ABS})
|
|
_def("tax", {0xAA:IMP}); _def("tay", {0xA8:IMP}); _def("tsx", {0xBA:IMP})
|
|
_def("txa", {0x8A:IMP}); _def("txs", {0x9A:IMP}); _def("tya", {0x98:IMP})
|
|
|
|
BRANCHES = {"bcc","bcs","beq","bmi","bne","bpl","bvc","bvs"}
|
|
FLOW_END = {"rts","rti","brk"}
|
|
|
|
|
|
@dataclass
|
|
class InsnT:
|
|
addr: int
|
|
opcode: int
|
|
mnemonic: str
|
|
mode: int
|
|
length: int
|
|
operand: int # raw operand value (or 0)
|
|
target: int # effective address for ABS/ZP/REL/etc (None for IMP/ACC/IMM)
|
|
|
|
def isLegal(self):
|
|
return self.mnemonic != "???"
|
|
|
|
|
|
def decode(mem, addr):
|
|
opcode = mem[addr]
|
|
if opcode not in OPCODES:
|
|
return InsnT(addr, opcode, "???", IMP, 1, 0, None)
|
|
mnemonic, mode = OPCODES[opcode]
|
|
length = MODE_LEN[mode]
|
|
operand = 0
|
|
target = None
|
|
if length == 2:
|
|
operand = mem[(addr+1) & 0xFFFF]
|
|
elif length == 3:
|
|
operand = mem[(addr+1) & 0xFFFF] | (mem[(addr+2) & 0xFFFF] << 8)
|
|
if mode == REL:
|
|
target = (addr + 2 + ((operand ^ 0x80) - 0x80)) & 0xFFFF
|
|
elif mode in (ZP, ZPX, ZPY, ABS, ABX, ABY, IND, IZX, IZY):
|
|
target = operand
|
|
return InsnT(addr, opcode, mnemonic, mode, length, operand, target)
|
|
|
|
|
|
def formatOperand(insn, labelFor=None):
|
|
m = insn.mode
|
|
def lab(a, width):
|
|
if labelFor:
|
|
l = labelFor(a)
|
|
if l:
|
|
return l
|
|
return f"${a:0{width}X}"
|
|
if m in (IMP,):
|
|
return ""
|
|
if m == ACC:
|
|
return "a"
|
|
if m == IMM:
|
|
return f"#${insn.operand:02X}"
|
|
if m == ZP:
|
|
return lab(insn.operand, 2)
|
|
if m == ZPX:
|
|
return lab(insn.operand, 2) + ",x"
|
|
if m == ZPY:
|
|
return lab(insn.operand, 2) + ",y"
|
|
if m == ABS:
|
|
return lab(insn.operand, 4)
|
|
if m == ABX:
|
|
return lab(insn.operand, 4) + ",x"
|
|
if m == ABY:
|
|
return lab(insn.operand, 4) + ",y"
|
|
if m == IND:
|
|
return "(" + lab(insn.operand, 4) + ")"
|
|
if m == IZX:
|
|
return "(" + lab(insn.operand, 2) + ",x)"
|
|
if m == IZY:
|
|
return "(" + lab(insn.operand, 2) + "),y"
|
|
if m == REL:
|
|
return lab(insn.target, 4)
|
|
return "?"
|
|
|
|
|
|
def trace(mem, entries, isValid=lambda a: True, noReturn=frozenset(), inlineArgs=None, known=frozenset()):
|
|
"""Recursive-descent: returns (codeSet, callTargets, jumpTargets, indirectJumps, illegalHits).
|
|
inlineArgs: dict subroutineAddr -> number of inline bytes that follow a JSR to it."""
|
|
code = set()
|
|
calls = {}
|
|
jumps = {}
|
|
indirects = []
|
|
illegal = []
|
|
work = list(entries)
|
|
seen = set(entries)
|
|
while work:
|
|
pc = work.pop()
|
|
while True:
|
|
if pc in code or pc in known or not isValid(pc):
|
|
break
|
|
insn = decode(mem, pc)
|
|
if not insn.isLegal():
|
|
illegal.append(pc)
|
|
break
|
|
for i in range(insn.length):
|
|
code.add((pc + i) & 0xFFFF)
|
|
nxt = (pc + insn.length) & 0xFFFF
|
|
if insn.mnemonic in BRANCHES:
|
|
if insn.target not in seen:
|
|
seen.add(insn.target); work.append(insn.target)
|
|
pc = nxt
|
|
elif insn.mnemonic == "jsr":
|
|
calls.setdefault(insn.target, []).append(pc)
|
|
if insn.target not in seen:
|
|
seen.add(insn.target); work.append(insn.target)
|
|
if inlineArgs and insn.target in inlineArgs:
|
|
nxt = (nxt + inlineArgs[insn.target]) & 0xFFFF
|
|
if insn.target in noReturn:
|
|
break
|
|
pc = nxt
|
|
elif insn.mnemonic == "jmp":
|
|
if insn.mode == IND:
|
|
indirects.append(pc)
|
|
break
|
|
jumps.setdefault(insn.target, []).append(pc)
|
|
if insn.target not in seen:
|
|
seen.add(insn.target); work.append(insn.target)
|
|
break
|
|
elif insn.mnemonic in FLOW_END:
|
|
break
|
|
else:
|
|
pc = nxt
|
|
return code, calls, jumps, indirects, illegal
|