struct Registers { Word rA; Word rX; Word rY; Word rSP; Word rP; Word rPC; }; #define CALLSTACK_SIZE 200 struct MOS6502 { struct MMU* MMU; struct Registers registers; Word previousInstruction; int bDisasm; struct Symboltable* symboltable; Word interruptLines[2]; /* 0=normal, 1=NMI */ int inISR; int bTrackJSR; int bDisasmrJSRMask; int disasmrJSRMaskBeginning; int disasmrJSRLevel; Word callstack[CALLSTACK_SIZE]; /* debugging */ int callstackCount; Word (*handleBRK)(void* userdata, struct MOS6502* CPU, Word aPC, Word aP, Word aA, Word aX, Word aY, Word aSP); void* handleBRKUserdata; }; #define SET_PC(value) (self->registers.rPC = value) #define PC self->registers.rPC #define A self->registers.rA #define X self->registers.rX #define S self->registers.rS #define Y self->registers.rY #define P self->registers.rP #define SP self->registers.rSP #define SET_A(v) MOS6502_updateFlags(self, A = (v)),MOS6502_printReg(self, "A", A) #define SET_P(v) (P = (v)),MOS6502_printReg(self, "P", P) #define SET_X(v) MOS6502_updateFlags(self, X = (v)),MOS6502_printReg(self, "X", X) #define SET_Y(v) MOS6502_updateFlags(self, Y = (v)),MOS6502_printReg(self, "Y", Y) #define SET_SP(v) (SP = (v),MOS6502_printReg(self, "SP", SP)) #define NEW_PC (PC + 1 + operandSize(opcode)) void MOS6502_init(struct MOS6502* self, struct MMU* MMU, Word(*handleBRK)(void* userdata, struct MOS6502* CPU, Word aPC, Word aP, Word aA, Word aX, Word aY, Word aSP), void* handleBRKUserdata) { self->previousInstruction = 0; self->handleBRK = handleBRK; self->handleBRKUserdata = handleBRKUserdata; self->bTrackJSR = 0; self->bDisasmrJSRMask = 0; self->disasmrJSRMaskBeginning = INT_MAX; self->disasmrJSRLevel = 0; self->MMU = MMU; self->bDisasm = 0; /* FIXME init registers */ self->interruptLines[0] = 0; self->interruptLines[1] = 0; self->inISR = 0; self->registers.rP = F_InterruptDisabled | F_Zero8; // FIXME initial state. self->registers.rA = 0; self->registers.rX = 0; self->registers.rY = 0; self->registers.rSP = 0xFD; /* not sure whether the actual CPU does that... */ self->symboltable = Symboltable_new(); } /* opcodes: From 0x80 to 0xC0 are load/store operations. Everything else is subdivided into ALU commands (odd numbers) which are always the same base operations, every ALU column. Left thus are the even: .0 are branches .2 are invalid except for A2 LDX. .4 are bit tests, comparisons with X, Y. .6 and .E (and .F) are shifts. .8 are flag manipulations and stack, DEY, T, INY, INX. -- .0 adr mostly branches, returns, traps, NOP .1 iz* mostly ALU with A and iz* .2 imm mostly invalid imm .3 iz* invalid .4 zp* do something with X, Y. Or bit test. Or invalid. .5 zp* mostly ALU with A .6 zp* shift, inc, dec .7 zp* invalid .8 imp mostly flags .9 iay mostly ALU with immediate .A imp mostly shifts with A. Transfer A X Y. .B iay completely invalid .C ab* mostly ALU with abs addressing. .E ab* mostly ALU with abs addressing. .F ab* completely invalid. 80 NOP imm 2 (invalid) A0 LDY imm 2 C0 CPY imm 2 E0 CPX imm 2 82 NOP imm 2 (invalid) A2 LDX imm 2 (!!) C2 NOP imm 2 (invalid) E2 NOP imm 2 (invalid) 24 BIT zp 3 smaller than $80: ORA (0|1). AND (2|3). EOR (4|5). ADC (6|7). SBC (E|F). bigger than $80 smaller than 0xC0: ST (8|9). */ enum OpOddSmallerx80 { /* nr. is row//2 */ ORA = 0, /* always odd column */ AND = 1, /* always odd column */ EOR = 2, /* always odd column */ ADC = 3, /* always odd column */ _r0 = 4, _r1 = 5, CMP = 6, /* C. */ SBC = 7, }; enum AModeSuper { /* (opcode&F)>>2 */ ImmIz, ZpzpxZpzpx, ImpImmaby, AbsabxAbsabx, }; enum AMode { Imp, Imm, Zp, Zpx, Zpy, //Iz, Izx, Izy, Ab, Abx, Aby, Rel, Imm16, Ab16, }; static const char* opsymsZeroes[] = { "BRK", "BPL", "JSR", "BMI", "RTI", "BVC", "RTS", "BVS", "NOP", "BCC", "LDY", "BCS", "CPY", "BNE", "CPX", "BEQ" }; static const char* opsymsFlags[] = { "PHP", "CLC", "PLP", "SEC", "PHA", "CLI", "PLA", "SEI" }; static const char* opsymsLogic[] = { "ORA", "AND", "EOR", "ADC" }; static const char* opsymsLogic2[] = { "ASL", "ROL", "LSR", "ROR" }; static const char* opsymsStores[] = { "STY", "STA", "STX", "STX", }; static const char* opsymsLoads[] = { "LDY", "LDA", "LDX", "LDX", }; static const char* opsymsSteppers[] = { "DEC", "DEC", "INC", "INC" }; static const char* opsymsSteppers2[] = { "INY", "CLD", "INX", "SED" }; const char* opcodeSymname(Word opcode) { /* almost only even columns can be NOP. Columns 6,8,E have no NOPs. */ Word lo = opcode&0xF; if(lo == 0) { /* branches, NOP, LDY, CPY, CPX */ return opsymsZeroes[opcode >> 4]; } else if(lo == 2 || lo == 3 || lo == 7 || lo == 0xB || lo == 0xF) { /* invalid, but just make stuff up in order to match docs */ if(opcode == 0xA2) return "LDX"; else if(opcode == 0x82 || opcode == 0xC2 || opcode == 0xE2) return "NOP"; else if(opcode == 0xEB) return "SBC"; else return "invalid"; } if(opcode < 0x80) { if(opcode == 0x4C) return "JMP"; /* imm 3 */ else if(opcode == 0x6C) return "JMP"; /* abs 5 */ if(lo == 8) return opsymsFlags[opcode>>4]; Word sub1 = opcode&3; if(sub1 == 0) { /* various */ if(opcode == 0x24) return "BIT"; /* zp 3 */ else if(opcode == 0x2C) return "BIT"; /* abs 4 */ return "NOP"; } else if(sub1 == 1) return opsymsLogic[opcode>>5]; else if(sub1 == 2) { if(opcode == 0x1A || opcode == 0x3A || opcode == 0x5A || opcode == 0x7A) return "NOP"; /* 2, instable */ return opsymsLogic2[opcode>>5]; } } else if(opcode >= 0x80 && opcode < 0xC0) { /* load/store */ if(opcode == 0x89) return "NOP"; /* NOP imm 2, invalid */ else if(opcode == 0x88) return "DEY"; /* 2 */ else if(opcode >= 0xA0) { /* load */ if(opcode == 0xA8) /* special names */ return "TAY"; else if(opcode == 0xAA) /* special names */ return "TAX"; else if(opcode == 0xBA) /* special names */ return "TSX"; else if(opcode == 0xC8) return "INY"; /* 2 */ else if(opcode == 0xB8) return "CLV"; /* 2 */ return opsymsLoads[opcode&3]; } else { /* store */ if(opcode == 0x8A) /* special names */ return "TXA"; else if(opcode == 0x98) /* special names */ return "TYA"; else if(opcode == 0x9A) /* special names */ return "TXS"; else if(opcode == 0x9C || opcode == 0x9E) return "invalid"; return opsymsStores[opcode&3]; } } else if(opcode >= 0xC0) { /* compare, subtract */ if(opcode == 0xC2 || opcode == 0xE2) return "NOP"; /* imm 2 */ else if(opcode == 0xCA) return "DEX"; /* 2 */ else if(opcode == 0xD4 || opcode == 0xDA || opcode == 0xDC || opcode == 0xF4 || opcode == 0xFA || opcode == 0xFC) return "NOP"; /* invalid */ else if(opcode == 0xEB) return "SBC"; /* invalid */ else if(lo == 6 || lo == 0xE) { /* inc, dec, technically also for 7 and 0xF, but it's broken in the real hardware. */ return opsymsSteppers[(opcode>>4) - 0xC]; } else if(lo == 8) { /* INY, more flags, INX */ return opsymsSteppers2[(opcode >>4) - 0xC]; } Word sub1 = opcode&3; if(opcode >= 0xE0 && sub1 == 1) return "SBC"; else { if(opcode >= 0xE0) { if(opcode == 0xEA) return "NOP"; /* 2 */ return "CPX"; } else if(opcode == 0xC0 || opcode == 0xC4 || opcode == 0xCC) return "CPY"; else return "CMP"; } } return "???"; } enum AMode opcodeAMode(Word opcode) { Word lo = opcode&0xF; switch(lo) { case 0: if(((opcode>>4)&1) == 1) return Rel; if(opcode == 0x20) return Imm16; /* JSR */ else if(opcode == 0x80 || opcode == 0xA0 || opcode == 0xC0 || opcode == 0xE0) return Imm; else return Imp; break; case 2: return Imm; case 1: case 3: /* indirect */ return ((opcode>>4)&1) != 0 ? Izy : Izx; case 4: case 5: case 6: case 7: /* zero page */ if(opcode >= 0x80 && opcode < 0xC0) if(lo == 6 || lo == 7) return ((opcode>>4)&1) != 0 ? Zpy : Zp; return ((opcode>>4)&1) != 0 ? Zpx : Zp; case 8: case 0xA: /* flags */ return Imp; case 9: case 0xB: /* flags */ return ((opcode>>4)&1) != 0 ? Aby : Imm; default: /* big */ if(opcode == 0x4C) /* JMP## */ return Imm16; else if(opcode == 0x6C) /* JMPaa */ return Ab16; else if(opcode >= 0x80 && opcode < 0xC0) if(lo >= 0xE) return ((opcode>>4)&1) != 0 ? Aby : Ab; return ((opcode>>4)&1) != 0 ? Abx : Ab; } return Imm; } static inline int operandSize(Word opcode) { enum AMode mode; mode = opcodeAMode(opcode); switch(mode) { case Imp: return 0; case Imm16: case Ab: case Abx: case Aby: case Ab16: return 2; default: return 1; } } static inline Word MOS6502_readMemory(struct MOS6502* self, unsigned addr) { return MMU_read(self->MMU, addr); } static inline void MOS6502_writeMemory(struct MOS6502* self, unsigned addr, Word value) { MMU_write(self->MMU, addr, value); } static inline Word MOS6502_readZeropageMemory(struct MOS6502* self, Word addr) { return MMU_read(self->MMU, addr); } static inline void MOS6502_writeZeropageMemory(struct MOS6502* self, Word addr, Word value) { assert(addr < 0x100); MMU_write(self->MMU, addr, value); }