#include #include #include "apu.hpp" #include "cartridge.hpp" #include "joypad.hpp" #include "ppu.hpp" #include "cpu.hpp" namespace CPU { /* CPU state */ u8 ram[0x800]; u8 A, X, Y, S; u16 PC; Flags P; bool nmi, irq; // Remaining clocks to end frame: const int totalCycles = 29781; int remainingCycles; inline int elapsed() { return totalCycles - remainingCycles; } /* Cycle emulation */ #define T tick() inline void tick() { PPU::step(); PPU::step(); PPU::step(); remainingCycles--; } /* Flags updating */ inline void upd_cv(u8 x, u8 y, s16 r) { P[C] = (r>0xFF); P[V] = ~(x^y) & (x^r) & 0x80; } inline void upd_nz(u8 x) { P[N] = x & 0x80; P[Z] = (x == 0); } // Does adding I to A cross a page? inline bool cross(u16 a, u8 i) { return ((a+i) & 0xFF00) != ((a & 0xFF00)); } /* Memory access */ void dma_oam(u8 bank); template inline u8 access(u16 addr, u8 v = 0) { u8* r; switch (addr) { case 0x0000 ... 0x1FFF: r = &ram[addr % 0x800]; if (wr) *r = v; return *r; // RAM. case 0x2000 ... 0x3FFF: return PPU::access(addr % 8, v); // PPU. // APU: case 0x4000 ... 0x4013: case 0x4015: return APU::access(elapsed(), addr, v); case 0x4017: if (wr) return APU::access(elapsed(), addr, v); else return Joypad::read_state(1); // Joypad 1. case 0x4014: if (wr) dma_oam(v); break; // OAM DMA. case 0x4016: if (wr) { Joypad::write_strobe(v & 1); break; } // Joypad strobe. else return Joypad::read_state(0); // Joypad 0. case 0x4018 ... 0xFFFF: return Cartridge::access(addr, v); // Cartridge. } return 0; } inline u8 wr(u16 a, u8 v) { T; return access<1>(a, v); } inline u8 rd(u16 a) { T; return access<0>(a); } inline u16 rd16_d(u16 a, u16 b) { return rd(a) | (rd(b) << 8); } // Read from A and B and merge. inline u16 rd16(u16 a) { return rd16_d(a, a+1); } inline u8 push(u8 v) { return wr(0x100 + (S--), v); } inline u8 pop() { return rd(0x100 + (++S)); } void dma_oam(u8 bank) { for (int i = 0; i < 256; i++) wr(0x2014, rd(bank*0x100 + i)); } /* Addressing modes */ inline u16 imm() { return PC++; } inline u16 imm16() { PC += 2; return PC - 2; } inline u16 abs() { return rd16(imm16()); } inline u16 _abx() { T; return abs() + X; } // Exception. inline u16 abx() { u16 a = abs(); if (cross(a, X)) T; return a + X; } inline u16 aby() { u16 a = abs(); if (cross(a, Y)) T; return a + Y; } inline u16 zp() { return rd(imm()); } inline u16 zpx() { T; return (zp() + X) % 0x100; } inline u16 zpy() { T; return (zp() + Y) % 0x100; } inline u16 izx() { u8 i = zpx(); return rd16_d(i, (i+1) % 0x100); } inline u16 _izy() { u8 i = zp(); return rd16_d(i, (i+1) % 0x100) + Y; } // Exception. inline u16 izy() { u16 a = _izy(); if (cross(a-Y, Y)) T; return a; } /* STx */ template void st() { wr( m() , r); } template<> void st() { T; wr(_izy() , A); } // Exceptions. template<> void st() { T; wr( abs() + X, A); } // ... template<> void st() { T; wr( abs() + Y, A); } // ... #define G u16 a = m(); u8 p = rd(a) /* Fetch parameter */ template void ld() { G; upd_nz(r = p); } // LDx template void cmp() { G; upd_nz(r - p); P[C] = (r >= p); } // CMP, CPx /* Arithmetic and bitwise */ template void ADC() { G ; s16 r = A + p + P[C]; upd_cv(A, p, r); upd_nz(A = r); } template void SBC() { G ^ 0xFF; s16 r = A + p + P[C]; upd_cv(A, p, r); upd_nz(A = r); } template void BIT() { G; P[Z] = !(A & p); P[N] = p & 0x80; P[V] = p & 0x40; } template void AND() { G; upd_nz(A &= p); } template void EOR() { G; upd_nz(A ^= p); } template void ORA() { G; upd_nz(A |= p); } /* Read-Modify-Write */ template void ASL() { G; P[C] = p & 0x80; T; upd_nz(wr(a, p << 1)); } template void LSR() { G; P[C] = p & 0x01; T; upd_nz(wr(a, p >> 1)); } template void ROL() { G; u8 c = P[C] ; P[C] = p & 0x80; T; upd_nz(wr(a, (p << 1) | c) ); } template void ROR() { G; u8 c = P[C] << 7; P[C] = p & 0x01; T; upd_nz(wr(a, c | (p >> 1)) ); } template void DEC() { G; T; upd_nz(wr(a, --p)); } template void INC() { G; T; upd_nz(wr(a, ++p)); } #undef G /* DEx, INx */ template void dec() { upd_nz(--r); T; } template void inc() { upd_nz(++r); T; } /* Bit shifting on the accumulator */ void ASL_A() { P[C] = A & 0x80; upd_nz(A <<= 1); T; } void LSR_A() { P[C] = A & 0x01; upd_nz(A >>= 1); T; } void ROL_A() { u8 c = P[C] ; P[C] = A & 0x80; upd_nz(A = ((A << 1) | c) ); T; } void ROR_A() { u8 c = P[C] << 7; P[C] = A & 0x01; upd_nz(A = (c | (A >> 1)) ); T; } /* Txx (move values between registers) */ template void tr() { upd_nz(d = s); T; } template<> void tr() { S = X; T; } // TSX, exception. /* Stack operations */ void PLP() { T; T; P.set(pop()); } void PHP() { T; push(P.get() | (1 << 4)); } // B flag set. void PLA() { T; T; A = pop(); upd_nz(A); } void PHA() { T; push(A); } /* Flow control (branches, jumps) */ template void br() { s8 j = rd(imm()); if (P[f] == v) { T; PC += j; } } void JMP_IND() { u16 i = rd16(imm16()); PC = rd16_d(i, (i&0xFF00) | ((i+1) % 0x100)); } void JMP() { PC = rd16(imm16()); } void JSR() { u16 t = PC+1; T; push(t >> 8); push(t); PC = rd16(imm16()); } /* Return instructions */ void RTS() { T; T; PC = (pop() | (pop() << 8)) + 1; T; } void RTI() { PLP(); PC = pop() | (pop() << 8); } template void flag() { P[f] = v; T; } // Clear and set flags. template void INT() { T; if (t != BRK) T; // BRK already performed the fetch. if (t != RESET) // Writes on stack are inhibited on RESET. { push(PC >> 8); push(PC & 0xFF); push(P.get() | ((t == BRK) << 4)); // Set B if BRK. } else { S -= 3; T; T; T; } P[I] = true; /* NMI Reset IRQ BRK */ constexpr u16 vect[] = { 0xFFFA, 0xFFFC, 0xFFFE, 0xFFFE }; PC = rd16(vect[t]); if (t == NMI) nmi = false; } void NOP() { T; } /* Execute a CPU instruction */ void exec() { switch (rd(PC++)) // Fetch the opcode. { // Select the right function to emulate the instruction: case 0x00: return INT() ; case 0x01: return ORA() ; case 0x05: return ORA() ; case 0x06: return ASL() ; case 0x08: return PHP() ; case 0x09: return ORA() ; case 0x0A: return ASL_A() ; case 0x0D: return ORA() ; case 0x0E: return ASL() ; case 0x10: return br() ; case 0x11: return ORA() ; case 0x15: return ORA() ; case 0x16: return ASL() ; case 0x18: return flag() ; case 0x19: return ORA() ; case 0x1D: return ORA() ; case 0x1E: return ASL<_abx>() ; case 0x20: return JSR() ; case 0x21: return AND() ; case 0x24: return BIT() ; case 0x25: return AND() ; case 0x26: return ROL() ; case 0x28: return PLP() ; case 0x29: return AND() ; case 0x2A: return ROL_A() ; case 0x2C: return BIT() ; case 0x2D: return AND() ; case 0x2E: return ROL() ; case 0x30: return br() ; case 0x31: return AND() ; case 0x35: return AND() ; case 0x36: return ROL() ; case 0x38: return flag() ; case 0x39: return AND() ; case 0x3D: return AND() ; case 0x3E: return ROL<_abx>() ; case 0x40: return RTI() ; case 0x41: return EOR() ; case 0x45: return EOR() ; case 0x46: return LSR() ; case 0x48: return PHA() ; case 0x49: return EOR() ; case 0x4A: return LSR_A() ; case 0x4C: return JMP() ; case 0x4D: return EOR() ; case 0x4E: return LSR() ; case 0x50: return br() ; case 0x51: return EOR() ; case 0x55: return EOR() ; case 0x56: return LSR() ; case 0x58: return flag() ; case 0x59: return EOR() ; case 0x5D: return EOR() ; case 0x5E: return LSR<_abx>() ; case 0x60: return RTS() ; case 0x61: return ADC() ; case 0x65: return ADC() ; case 0x66: return ROR() ; case 0x68: return PLA() ; case 0x69: return ADC() ; case 0x6A: return ROR_A() ; case 0x6C: return JMP_IND() ; case 0x6D: return ADC() ; case 0x6E: return ROR() ; case 0x70: return br() ; case 0x71: return ADC() ; case 0x75: return ADC() ; case 0x76: return ROR() ; case 0x78: return flag() ; case 0x79: return ADC() ; case 0x7D: return ADC() ; case 0x7E: return ROR<_abx>() ; case 0x81: return st() ; case 0x84: return st() ; case 0x85: return st() ; case 0x86: return st() ; case 0x88: return dec() ; case 0x8A: return tr() ; case 0x8C: return st() ; case 0x8D: return st() ; case 0x8E: return st() ; case 0x90: return br() ; case 0x91: return st() ; case 0x94: return st() ; case 0x95: return st() ; case 0x96: return st() ; case 0x98: return tr() ; case 0x99: return st() ; case 0x9A: return tr() ; case 0x9D: return st() ; case 0xA0: return ld() ; case 0xA1: return ld() ; case 0xA2: return ld() ; case 0xA4: return ld() ; case 0xA5: return ld() ; case 0xA6: return ld() ; case 0xA8: return tr() ; case 0xA9: return ld() ; case 0xAA: return tr() ; case 0xAC: return ld() ; case 0xAD: return ld() ; case 0xAE: return ld() ; case 0xB0: return br() ; case 0xB1: return ld() ; case 0xB4: return ld() ; case 0xB5: return ld() ; case 0xB6: return ld() ; case 0xB8: return flag() ; case 0xB9: return ld() ; case 0xBA: return tr() ; case 0xBC: return ld() ; case 0xBD: return ld() ; case 0xBE: return ld() ; case 0xC0: return cmp(); case 0xC1: return cmp(); case 0xC4: return cmp() ; case 0xC5: return cmp() ; case 0xC6: return DEC() ; case 0xC8: return inc() ; case 0xC9: return cmp(); case 0xCA: return dec() ; case 0xCC: return cmp(); case 0xCD: return cmp(); case 0xCE: return DEC() ; case 0xD0: return br() ; case 0xD1: return cmp(); case 0xD5: return cmp(); case 0xD6: return DEC() ; case 0xD8: return flag() ; case 0xD9: return cmp(); case 0xDD: return cmp(); case 0xDE: return DEC<_abx>() ; case 0xE0: return cmp(); case 0xE1: return SBC() ; case 0xE4: return cmp() ; case 0xE5: return SBC() ; case 0xE6: return INC() ; case 0xE8: return inc() ; case 0xE9: return SBC() ; case 0xEA: return NOP() ; case 0xEC: return cmp(); case 0xED: return SBC() ; case 0xEE: return INC() ; case 0xF0: return br() ; case 0xF1: return SBC() ; case 0xF5: return SBC() ; case 0xF6: return INC() ; case 0xF8: return flag() ; case 0xF9: return SBC() ; case 0xFD: return SBC() ; case 0xFE: return INC<_abx>() ; default: return exit(1) ; } } void set_nmi(bool v) { nmi = v; } void set_irq(bool v) { irq = v; } int dmc_read(void*, cpu_addr_t addr) { return access<0>(addr); } /* Turn on the CPU */ void power() { remainingCycles = 0; P.set(0x04); A = X = Y = S = 0x00; memset(ram, 0xFF, sizeof(ram)); nmi = irq = false; INT(); } /* Run the CPU for roughly a frame */ void run_frame() { remainingCycles += totalCycles; while (remainingCycles > 0) { if (nmi) INT(); else if (irq and !P[I]) INT(); exec(); } APU::run_frame(elapsed()); } }