static inline Word MOS6502_ORA(struct MOS6502* self, Word wopcode) { Word opcode = wopcode&0xFF; switch(opcode) { case 0x0F: case 0x1F: case 0x1B: case 0x17: case 0x03: case 0x13: case 0x07: /* invalid instruction (ASO / SLO), but emulate it. */ if(WIDECODE_P(wopcode)) MOS6502_ASL32(self, opcode); else MOS6502_ASL8(self, opcode); /* fallthrough */ } SET_A(A | MOS6502_loadValue(self, opcode)); return(NEW_PC); } static inline Word MOS6502_AND(struct MOS6502* self, Word wopcode) { Word opcode = wopcode&0xFF; if(opcode != 0x2B && opcode != 0x0B && (opcode&3) == 3) { /* invalid opcode RLA, but emulate anyway... */ if(WIDECODE_P(wopcode)) MOS6502_ROL32(self, opcode); else MOS6502_ROL8(self, opcode); /* fallthrough */ } SET_A(A&MOS6502_loadValue(self, opcode)); return(NEW_PC); } static inline Word MOS6502_ANC(struct MOS6502* self, Word wopcode) { Word newPC = MOS6502_AND(self, wopcode); if(P&F_Negative8) SET_P(P | F_Carry8); else SET_P(P&~F_Carry8); if(P&F_Negative32) SET_P(P | F_Carry32); else SET_P(P&~F_Carry32); return newPC; } static inline Word MOS6502_EOR(struct MOS6502* self, Word wopcode) { Word opcode = wopcode&0xFF; switch(opcode) { case 0x4F: case 0x5F: case 0x5B: case 0x47: case 0x57: case 0x43: case 0x53: /* invalid operation LSE / SRE, but emulate anyway... */ if(WIDECODE_P(wopcode)) MOS6502_LSR32(self, opcode); else MOS6502_LSR8(self, opcode); /* fallthrough */ } SET_A(A ^ MOS6502_loadValue(self, opcode)); return(NEW_PC); } static inline Word MOS6502_branchw(struct MOS6502* self, Word wopcode, Word previousInstruction) { unsigned char opcode = wopcode&0xFF; switch(wopcode) { case 0x00: return MOS6502_BRK(self, opcode); case 0x10: return MOS6502_BPL8(self, opcode); case 0x20: /* JSR abs */ return MOS6502_JSR(self, opcode); case 0x30: return MOS6502_BMI8(self, opcode); case 0x40: return MOS6502_RTI(self, opcode); case 0x50: return MOS6502_BVC8(self, opcode); case 0x60: return MOS6502_RTS(self, opcode); case 0x70: return MOS6502_BVS8(self, opcode); case 0x90: return MOS6502_BCC8(self, opcode); case 0xC0: return MOS6502_CPY8(self, opcode); case 0xD0: /* FIXME check for timer loop (DEX, BNE). Make sure to store the previous instruction in order to be able to find out. The jump would have to go exactly there. */ return MOS6502_BNE8(self, opcode); case 0xE0: return MOS6502_CPX8(self, opcode); case 0xF0: return MOS6502_BEQ8(self, opcode); case WIDECODE | 0x00: return MOS6502_BRK(self, opcode); case WIDECODE | 0x10: return MOS6502_BPL32(self, opcode); case WIDECODE | 0x20: /* JSR abs */ return MOS6502_JSR(self, opcode); case WIDECODE | 0x30: return MOS6502_BMI32(self, opcode); case WIDECODE | 0x40: return MOS6502_RTI(self, opcode); case WIDECODE | 0x50: return MOS6502_BVC32(self, opcode); case WIDECODE | 0x60: return MOS6502_RTS(self, opcode); case WIDECODE | 0x70: return MOS6502_BVS32(self, opcode); case WIDECODE | 0x90: return MOS6502_BCC32(self, opcode); case WIDECODE | 0xC0: return MOS6502_CPY32(self, opcode); case WIDECODE | 0xD0: return MOS6502_BNE32(self, opcode); case WIDECODE | 0xE0: return MOS6502_CPX32(self, opcode); case WIDECODE | 0xF0: return MOS6502_BEQ32(self, opcode); default: abort(); return 0; } } static inline Word MOS6502_loadstore(struct MOS6502* self, Word wopcode) { /* load/store */ unsigned char opcode = wopcode&0xFF; /* TODO other branches */ if(wopcode&32) { /* load */ if(opcode == 0xBA) return MOS6502_T2X(self, opcode, SP); else if(opcode == 0xA8) /* TAY */ return MOS6502_T2Y(self, opcode, A); else if(opcode == 0xAA) /* TAX */ return MOS6502_T2X(self, opcode, A); else if(opcode == 0xB8) /* CLV */ return MOS6502_CLV(self, opcode); Word result = MOS6502_loadValue(self, opcode); /* TODO unlikely */ if(opcode == 0xB0) return MOS6502_BCS8(self, opcode); else if(wopcode == (WIDECODE | 0xB0)) return MOS6502_BCS32(self, opcode); else if((opcode&0xF) == 2 && opcode != 0xA2) fprintf(stderr, "note: invalid opcode ignored\n"); switch(opcode) { case 0xA2: SET_X(result); break; default: switch(opcode&3) { case 0: SET_Y(result); break; case 1: SET_A(result); break; case 2: SET_X(result); break; case 3: /* inofficial */ SET_A(result); SET_X(result); break; } } } else { /* store */ Word value; Word off; off = opcode&3; switch(off) { case 0: value = Y; break; case 1: value = A; break; case 2: value = X; break; case 3: value = X&A; /* FIXME is that correct? */ break; default: abort(); } switch(wopcode) { case 0x8A: /* TXA */ case WIDECODE | 0x8A: return MOS6502_T2A(self, wopcode, X/*or value*/); case 0x98: /* TYA */ case WIDECODE | 0x98: return MOS6502_T2A(self, wopcode, Y/*or value*/); case 0xA8: /* TAY */ case WIDECODE | 0xA8: return MOS6502_T2Y(self, wopcode, A/*or value*/); case 0x9A: /* TXS */ case WIDECODE | 0x9A: return MOS6502_T2S(self, wopcode, X/*or value*/); case 0xBA: /* TSX */ case WIDECODE | 0xBA: return MOS6502_T2X(self, wopcode, SP/*or value*/); case 0xAA: /* TAX */ case WIDECODE | 0xAA: return MOS6502_T2X(self, wopcode, A/*or value*/); case 0x88: /* DEY */ return MOS6502_DEY8(self, wopcode); /* we also could do that with far more cunning (and less transparency), by (--value) */ case 0x90: /* BCC */ return MOS6502_BCC8(self, wopcode); case WIDECODE | 0x88: /* DEY */ return MOS6502_DEY32(self, wopcode); /* we also could do that with far more cunning (and less transparency), by (--value) */ case WIDECODE | 0x90: /* BCC */ return MOS6502_BCC32(self, wopcode); case 0x80: /* NOP */ case WIDECODE | 0x80: /* NOP */ case 0x82: /* NOP */ case WIDECODE | 0x82: /* NOP */ case 0x89: /* NOP */ case WIDECODE | 0x89: /* NOP */ return MOS6502_NOP(self, opcode); case 0x9C: case 0x9E: return MOS6502_SinvalidA8(self, opcode, value); case WIDECODE | 0x9C: case WIDECODE | 0x9E: return MOS6502_SinvalidA32(self, opcode, value); default: MOS6502_storeValue(self, opcode, value/*or value*/); } } return(NEW_PC); } static inline void MOS6502_checkInterrupt(struct MOS6502* self) { /* NMI is checked directly when changing flags */ if((P&F_InterruptDisabled) == 0) { if(self->interruptLines[Interrupt_IRQ]) { fprintf(stderr, "IRQ still set (%X), causing interrupt immediately\n", self->interruptLines[Interrupt_IRQ]); SET_PC(MOS6502_handleInterrupt(self, 0xFFFE/*IRQ*/, 0)); } } } static inline Word MOS6502_step1(struct MOS6502* self) { MOS6502_checkInterrupt(self); Word wopcode = MOS6502_readMemory(self, PC); Word previousInstruction = self->previousInstruction; self->previousInstruction = wopcode; unsigned char opcode = wopcode&0xFF; Word lopcode = wopcode&0xF; if(opcode >= 0x80 && opcode < 0xC0) { /* load/store */ return(MOS6502_loadstore(self, opcode)); } else if (wopcode&1) { /* ALU */ if(opcode == 0x2B || opcode == 0x0B) { /* invalid opcode ANC */ return MOS6502_ANC(self, wopcode); } else if(opcode == 0xCB) { if(!WIDECODE_P(wopcode)) return MOS6502_SAX8(self, wopcode); else return MOS6502_SAX32(self, wopcode); } if(!WIDECODE_P(wopcode)) switch((enum OpOddSmallerx80)opcode>>5) { case ORA: return(MOS6502_ORA(self, wopcode)); case AND: return(MOS6502_AND(self, wopcode)); case EOR: if(opcode == 0x4B) return MOS6502_ALR8(self, wopcode); else return(MOS6502_EOR(self, wopcode)); case ADC: if(opcode == 0x6B) return MOS6502_ARR8(self, wopcode); else return(MOS6502_ADC8(self, wopcode)); case CMP: return(MOS6502_CMP8(self, wopcode)); case SBC: return(MOS6502_SBC8(self, wopcode)); default: abort(); } else { switch((enum OpOddSmallerx80)opcode>>5) { case ORA: return(MOS6502_ORA(self, wopcode)); case AND: return(MOS6502_AND(self, wopcode)); case EOR: if(opcode == 0x4B) return MOS6502_ALR32(self, wopcode); else return(MOS6502_EOR(self, wopcode)); case ADC: if(opcode == 0x6B) return MOS6502_ARR32(self, wopcode); else return(MOS6502_ADC32(self, wopcode)); case CMP: return(MOS6502_CMP32(self, wopcode)); case SBC: return(MOS6502_SBC32(self, wopcode)); default: abort(); } } } else if(lopcode == 0) { /* branches */ return(MOS6502_branchw(self, wopcode, previousInstruction)); } else { /* misc */ switch(opcode) { case 0x24: case 0x2C: return MOS6502_BIT(self, opcode); case 0xC4: case 0xCC: if(WIDECODE_P(wopcode)) return MOS6502_CPY32(self, opcode); else return MOS6502_CPY8(self, opcode); case 0xE4: case 0xEC: if(WIDECODE_P(wopcode)) return MOS6502_CPX32(self, opcode); else return MOS6502_CPX8(self, opcode); case 0x06: case 0x0E: case 0x0A: case 0x16: case 0x1E: if(WIDECODE_P(wopcode)) return MOS6502_ASL32(self, opcode); else return MOS6502_ASL8(self, opcode); case 0x26: case 0x2E: case 0x2A: case 0x36: case 0x3E: if(WIDECODE_P(wopcode)) return MOS6502_ROL32(self, opcode); else return MOS6502_ROL8(self, opcode); case 0x46: case 0x4E: case 0x4A: case 0x56: case 0x5E: if(WIDECODE_P(wopcode)) return MOS6502_LSR32(self, opcode); else return MOS6502_LSR8(self, opcode); case 0x66: case 0x6E: case 0x6A: case 0x76: case 0x7E: if(WIDECODE_P(wopcode)) return MOS6502_ROR32(self, opcode); else return MOS6502_ROR8(self, opcode); case 0xC6: case 0xCE: case 0xD6: case 0xDE: if(WIDECODE_P(wopcode)) return MOS6502_DEC32(self, opcode); else return MOS6502_DEC8(self, opcode); case 0xCA: if(WIDECODE_P(wopcode)) return MOS6502_DEX32(self, opcode); else return MOS6502_DEX8(self, opcode); case 0xE6: case 0xEE: case 0xF6: case 0xFE: if(WIDECODE_P(wopcode)) return MOS6502_INC32(self, opcode); else return MOS6502_INC8(self, opcode); case 0x08: if(WIDECODE_P(wopcode)) return MOS6502_PHP32(self, opcode); else return MOS6502_PHP8(self, opcode); case 0x18: return MOS6502_CLC(self, opcode); case 0x28: return MOS6502_PLP(self, opcode); case 0x38: return MOS6502_SEC(self, opcode); case 0x48: return MOS6502_PHA(self, opcode); case 0x58: return MOS6502_CLI(self, opcode); case 0x68: return MOS6502_PLA(self, opcode); case 0x78: return MOS6502_SEI(self, opcode); case 0xC8: if(WIDECODE_P(wopcode)) return MOS6502_INY32(self, opcode); else return MOS6502_INY8(self, opcode); case 0xD8: return MOS6502_CLD(self, opcode); case 0xE8: if(WIDECODE_P(wopcode)) return MOS6502_INX32(self, opcode); else return MOS6502_INX8(self, opcode); case 0xF8: return MOS6502_SED(self, opcode); case 0x4C: case 0x6C: /* JMP indirect */ return MOS6502_JMP(self, opcode); default: if((opcode&0xF) == 4 || (opcode&0xF) == 0xA) return MOS6502_NOP(self, opcode); else { /* these aren't exactly common */ if(opcode == 0xC2 || opcode == 0xE2 || opcode == 0x0C || opcode == 0x1C || opcode == 0x3C || opcode == 0x5C || opcode == 0x7C || opcode == 0xDC || opcode == 0xFC) return MOS6502_NOP(self, opcode); else { fprintf(stderr, "error: unsupported opcode $%02X\n", opcode); abort(); } } } } } struct MOS6502* MOS6502_new(struct MMU* MMU, Word(*handleBRK)(void* userdata, struct MOS6502* CPU, Word aPC, Word aP, Word aA, Word aX, Word aY, Word aSP), void* handleBRKUserdata) { struct MOS6502* self; self = xcalloc(1, sizeof(struct MOS6502)); MOS6502_init(self, MMU, handleBRK, handleBRKUserdata); return(self); } size_t MOS6502_getAllocationSize(void) { return sizeof(struct MOS6502); } void MOS6502_printCallstack(struct MOS6502* self) { int i; const char* name; fprintf(stderr, "Traceback:\n"); for(i = 0; i < self->callstackCount; ++i) { Word addr = self->callstack[i]; name = self->symboltable ? Symboltable_getSymbolnameByAddr(self->symboltable, addr) : NULL; if(name) fprintf(stderr, "* %s\n", name); else fprintf(stderr, "* $%04X\n", addr); } } static void MOS6502_disasm1(struct MOS6502* self) { /* TODO test C2, E2, 0C, 1C, 3C, 5C, 7C, DC, FC NOP */ int sz, i; Word wopcode; const char* name = NULL; enum AMode mode; Word addr = 0; sz = 1 + operandSize(MOS6502_readMemory(self, PC)); wopcode = MOS6502_readMemory(self, PC); unsigned char opcode = wopcode&0xFF; mode = opcodeAMode(opcode); if(sz > 1) { addr = 0; for(i = sz - 1; i >= 1; --i) { Word v = MOS6502_readMemory(self, PC + i); addr = (addr << 8) | v; } if(mode == Rel) { char offset = (Word)addr; // MOS6502_readMemory(self, PC + 1); addr = PC + 2 + offset; } if(self->symboltable && mode != Imm) { /* Imm16 is a jump */ if(self->bTrackJSR && opcode == 0x20/*JSR*/ && self->inISR <= 0) self->disasmrJSRLevel += 1; name = Symboltable_getSymbolnameByAddr(self->symboltable, addr); if(name) { if(self->bTrackJSR && opcode == 0x20/*JSR*/ && !self->bDisasmrJSRMask && self->inISR <= 0) { self->bDisasmrJSRMask = 1; self->disasmrJSRMaskBeginning = self->disasmrJSRLevel; } } } } else { if(self->bTrackJSR && opcode == 0x60/*RTS*/ && self->inISR <= 0) { if(self->disasmrJSRLevel > 0) { self->disasmrJSRLevel -= 1; if(self->bDisasmrJSRMask && self->disasmrJSRLevel < self->disasmrJSRMaskBeginning) self->bDisasmrJSRMask = 0; } } } if(!self->bDisasmrJSRMask || self->disasmrJSRLevel < self->disasmrJSRMaskBeginning || self->inISR > 0) { } else return; fprintf(stderr, "%04X: (ISR%d) %02X ", PC, self->inISR, opcode); for(i = 1; i < sz; ++i) fprintf(stderr, "%02X ", MOS6502_readMemory(self, PC + i)); fprintf(stderr, "\t%s%s ", opcodeSymname(opcode), mode == Imp ? "" : mode == Zp ? "Z" : mode == Zpx ? "ZX" : mode == Zpy ? "ZY" : mode == Izx ? "IX" : mode == Izy ? "IY" : mode == Ab ? "a" : mode == Abx ? "aX" : mode == Aby ? "aY" : mode == Rel ? "r" : mode == Imm ? "v" : mode == Imm16 ? "V" : mode == Ab16 ? "6" : "?"); if(sz > 1) { fprintf(stderr, "$%04X", addr); if(name) { fprintf(stderr, " ; %s", name); } } fprintf(stderr, "\n"); if(self->inISR == 0 && (opcode == 0x20/*JSR*/ || opcode == 0x60/*RTS*/)) { MOS6502_printCallstack(self); } } void MOS6502_step(struct MOS6502* self) { if(self->bDisasm) MOS6502_disasm1(self); SET_PC(MOS6502_step1(self)); } gpointer MOS6502_thread(struct MOS6502* self) { while(1) { MOS6502_step(self); } return(NULL); } void MOS6502_run(struct MOS6502* self) { MOS6502_initPC(self); g_thread_new("MOS6502", (GThreadFunc) MOS6502_thread, self); }