From a5f1a23de85af2ea55aff04b44b8aa580d745b7f Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 19 Aug 2023 02:08:17 -0400 Subject: [PATCH] Add CPU and Memory class for SNES emulator --- src/app/CMakeLists.txt | 1 + src/app/emu/cpu.cc | 1038 ++++++++++++++++++++++++++++++++++++++++ src/app/emu/cpu.h | 334 +++++++++++++ src/app/emu/mem.h | 45 ++ test/CMakeLists.txt | 2 + test/cpu_test.cc | 253 ++++++++++ 6 files changed, 1673 insertions(+) create mode 100644 src/app/emu/cpu.cc create mode 100644 src/app/emu/cpu.h create mode 100644 src/app/emu/mem.h create mode 100644 test/cpu_test.cc diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 31b6069b..04c0118b 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable( yaze app/yaze.cc app/rom.cc + app/emu/cpu.cc ${YAZE_APP_CORE_SRC} ${YAZE_APP_EDITOR_SRC} ${YAZE_APP_GFX_SRC} diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc new file mode 100644 index 00000000..9db8b4ed --- /dev/null +++ b/src/app/emu/cpu.cc @@ -0,0 +1,1038 @@ +#include "cpu.h" + +#include +#include +#include + +namespace yaze { +namespace app { +namespace emu { + +uint8_t CPU::ReadByte(uint16_t addr) const { + return memory.at(addr); // Read a byte from memory at the specified address +} + +uint16_t CPU::ReadWord(uint16_t address) const { + uint8_t low = ReadByte(address); + uint8_t high = ReadByte(address + 1); + return (high << 8) | low; +} + +uint32_t CPU::ReadWordLong(uint16_t address) const { + uint8_t low = ReadByte(address); + uint8_t mid = ReadByte(address + 1); + uint8_t high = ReadByte(address + 2); + return (high << 16) | (mid << 8) | low; +} + +uint8_t CPU::FetchByte() { + uint8_t byte = memory.ReadWord(PC); // Read a byte from memory at PC + PC++; // Increment the Program Counter + return byte; +} + +uint16_t CPU::FetchWord() { + uint16_t value = memory.ReadWord(PC); + PC += 2; + return value; +} + +uint32_t CPU::FetchLong() { + uint32_t value = memory.ReadWordLong(PC); + PC += 3; + return value; +} + +int8_t CPU::FetchSignedByte() { return static_cast(FetchByte()); } + +int16_t CPU::FetchSignedWord() { + auto offset = static_cast(FetchWord()); + return offset; +} + +uint8_t CPU::FetchByteDirectPage(uint8_t operand) { + // Calculate the effective address in the Direct Page + uint16_t effectiveAddress = D + operand; + + // Fetch the byte from memory + uint8_t fetchedByte = memory.ReadByte(effectiveAddress); + + PC++; // Increment the Program Counter + + return fetchedByte; +} + +uint16_t CPU::DirectPageIndexedIndirectX() { + uint8_t dp = FetchByte(); + return ReadWord((dp + X) & 0xFF); +} + +uint16_t CPU::StackRelative() { + uint8_t sr = FetchByte(); + return SP + sr; +} + +uint16_t CPU::DirectPage() { return FetchByte(); } + +uint16_t CPU::DirectPageIndirectLong() { + uint8_t dp = FetchByte(); + return ReadWordLong(dp); +} + +uint16_t CPU::Immediate() { return PC++; } + +uint16_t CPU::Absolute() { return FetchWord(); } + +uint16_t CPU::AbsoluteLong() { return FetchLong(); } + +uint16_t CPU::DirectPageIndirectIndexedY() { + uint8_t dp = FetchByte(); + return ReadWord(dp) + Y; +} + +uint16_t CPU::DirectPageIndirect() { + uint8_t dp = FetchByte(); + return ReadWord(dp); +} + +uint16_t CPU::StackRelativeIndirectIndexedY() { + uint8_t sr = FetchByte(); + return ReadWord(SP + sr) + Y; +} + +uint16_t CPU::DirectPageIndexedX() { + uint8_t dp = FetchByte(); + return (dp + X) & 0xFF; +} + +uint16_t CPU::DirectPageIndirectLongIndexedY() { + uint8_t dp = FetchByte(); + return ReadWordLong(dp) + Y; +} + +uint16_t CPU::AbsoluteIndexedY() { return FetchWord() + Y; } + +uint16_t CPU::AbsoluteIndexedX() { return FetchWord() + X; } + +uint16_t CPU::AbsoluteLongIndexedX() { return FetchLong() + X; } + +void CPU::ExecuteInstruction(uint8_t opcode) { + // uint8_t opcode = FetchByte(); + uint8_t operand = -1; + switch (opcode) { + case 0x61: // ADC DP Indexed Indirect, X + operand = memory.ReadByte(DirectPageIndexedIndirectX()); + ADC(operand); + break; + case 0x63: // ADC Stack Relative + operand = memory.ReadByte(StackRelative()); + ADC(operand); + break; + case 0x65: // ADC Direct Page + operand = FetchByteDirectPage(PC); + ADC(operand); + break; + case 0x67: // ADC DP Indirect Long + operand = memory.ReadByte(DirectPageIndirectLong()); + ADC(operand); + break; + case 0x69: // ADC Immediate + operand = memory.ReadByte(Immediate()); + ADC(operand); + break; + case 0x6D: // ADC Absolute + operand = memory.ReadByte(Absolute()); + ADC(operand); + break; + case 0x6F: // ADC Absolute Long + operand = memory.ReadByte(AbsoluteLong()); + ADC(operand); + break; + case 0x71: // ADC DP Indirect Indexed, Y + operand = memory.ReadByte(DirectPageIndirectIndexedY()); + ADC(operand); + break; + case 0x72: // ADC DP Indirect + operand = memory.ReadByte(DirectPageIndirect()); + ADC(operand); + break; + case 0x73: // ADC SR Indirect Indexed, Y + operand = memory.ReadByte(StackRelativeIndirectIndexedY()); + ADC(operand); + break; + case 0x75: // ADC DP Indexed, X + operand = memory.ReadByte(DirectPageIndexedX()); + ADC(operand); + break; + case 0x77: // ADC DP Indirect Long Indexed, Y + operand = memory.ReadByte(DirectPageIndirectLongIndexedY()); + ADC(operand); + break; + case 0x79: // ADC Absolute Indexed, Y + operand = memory.ReadByte(AbsoluteIndexedY()); + ADC(operand); + break; + case 0x7D: // ADC Absolute Indexed, X + operand = memory.ReadByte(AbsoluteIndexedX()); + ADC(operand); + break; + case 0x7F: // ADC Absolute Long Indexed, X + operand = memory.ReadByte(AbsoluteLongIndexedX()); + ADC(operand); + break; + + case 0x21: // AND DP Indexed Indirect, X + // AND(); + break; + case 0x23: // AND Stack Relative + // AND(); + break; + case 0x25: // AND Direct Page + // AND(); + break; + case 0x27: // AND DP Indirect Long + // AND(); + break; + case 0x29: // AND Immediate + // AND(); + break; + case 0x2D: // AND Absolute + // AND(); + break; + case 0x2F: // AND Absolute Long + // AND(); + break; + case 0x31: // AND DP Indirect Indexed, Y + // AND(); + break; + case 0x32: // AND DP Indirect + // AND(); + break; + case 0x33: // AND SR Indirect Indexed, Y + // AND(); + break; + case 0x35: // AND DP Indexed, X + // AND(); + break; + case 0x37: // AND DP Indirect Long Indexed, Y + // AND(); + break; + case 0x39: // AND Absolute Indexed, Y + // AND(); + break; + case 0x3D: // AND Absolute Indexed, X + // AND(); + break; + case 0x3F: // AND Absolute Long Indexed, X + // AND(); + break; + + case 0x06: // ASL Direct Page + // ASL(); + break; + case 0x0A: // ASL Accumulator + // ASL(); + break; + case 0x0E: // ASL Absolute + // ASL(); + break; + case 0x16: // ASL DP Indexed, X + // ASL(); + break; + case 0x1E: // ASL Absolute Indexed, X + // ASL(); + break; + + case 0x90: // BCC Branch if carry clear + operand = memory.ReadByte(PC); + BCC(operand); + break; + + case 0xB0: // BCS Branch if carry set + // BCS(); + break; + + case 0xF0: // BEQ Branch if equal (zero set) + operand = memory.ReadByte(PC); + BEQ(operand); + break; + + case 0x24: // BIT Direct Page + // BIT(); + break; + case 0x2C: // BIT Absolute + // BIT(); + break; + case 0x34: // BIT DP Indexed, X + // BIT(); + break; + case 0x3C: // BIT Absolute Indexed, X + // BIT(); + break; + case 0x89: // BIT Immediate + // BIT(); + break; + + case 0x30: // BMI Branch if minus (negative set) + // BMI(); + break; + + case 0xD0: // BNE Branch if not equal (zero clear) + // BNE(); + break; + + case 0x10: // BPL Branch if plus (negative clear) + // BPL(); + break; + + case 0x80: // BRA Branch always + // BRA(); + break; + + case 0x00: // BRK Break + // BRK(); + break; + + case 0x82: // BRL Branch always long + PC += FetchSignedWord(); + break; + + case 0x50: // BVC Branch if overflow clear + // BVC(); + break; + + case 0x70: // BVS Branch if overflow set + // BVS(); + break; + + case 0x18: // CLC Clear carry + CLC(); + break; + + case 0xD8: // CLD Clear decimal + CLD(); + break; + + case 0x58: // CLI Clear interrupt disable + CLI(); + break; + + case 0xB8: // CLV Clear overflow + CLV(); + break; + + case 0xC1: // CMP DP Indexed Indirect, X + // CMP(); + break; + case 0xC3: // CMP Stack Relative + // CMP(); + break; + case 0xC5: // CMP Direct Page + // CMP(); + break; + case 0xC7: // CMP DP Indirect Long + // CMP(); + break; + case 0xC9: // CMP Immediate + // CMP(); + break; + case 0xCD: // CMP Absolute + // CMP(); + break; + case 0xCF: // CMP Absolute Long + // CMP(); + break; + case 0xD1: // CMP DP Indirect Indexed, Y + // CMP(); + break; + case 0xD2: // CMP DP Indirect + // CMP(); + break; + case 0xD3: // CMP SR Indirect Indexed, Y + // CMP(); + break; + case 0xD5: // CMP DP Indexed, X + // CMP(); + break; + case 0xD7: // CMP DP Indirect Long Indexed, Y + // CMP(); + break; + case 0xD9: // CMP Absolute Indexed, Y + // CMP(); + break; + case 0xDD: // CMP Absolute Indexed, X + // CMP(); + break; + case 0xDF: // CMP Absolute Long Indexed, X + // CMP(); + break; + + case 0x02: // COP Coprocessor + // COP(); + break; + + case 0xE0: // CPX Immediate + // CPX(); + break; + case 0xE4: // CPX Direct Page + // CPX(); + break; + case 0xEC: // CPX Absolute + // CPX(); + break; + + case 0xC0: // CPY Immediate + // CPY(); + break; + case 0xC4: // CPY Direct Page + // CPY(); + break; + case 0xCC: // CPY Absolute + // CPY(); + break; + + case 0x3A: // DEC Accumulator + // DEC(); + break; + case 0xC6: // DEC Direct Page + // DEC(); + break; + case 0xCE: // DEC Absolute + // DEC(); + break; + case 0xD6: // DEC DP Indexed, X + // DEC(); + break; + case 0xDE: // DEC Absolute Indexed, X + // DEC(); + break; + + case 0xCA: // DEX Decrement X register + // DEX(); + break; + + case 0x88: // DEY Decrement Y register + // DEY(); + break; + + case 0x41: // EOR DP Indexed Indirect, X + // EOR(); + break; + case 0x43: // EOR Stack Relative + // EOR(); + break; + case 0x45: // EOR Direct Page + // EOR(); + break; + case 0x47: // EOR DP Indirect Long + // EOR(); + break; + case 0x49: // EOR Immediate + // EOR(); + break; + case 0x4D: // EOR Absolute + // EOR(); + break; + case 0x4F: // EOR Absolute Long + // EOR(); + break; + case 0x51: // EOR DP Indirect Indexed, Y + // EOR(); + break; + case 0x52: // EOR DP Indirect + // EOR(); + break; + case 0x53: // EOR SR Indirect Indexed, Y + // EOR(); + break; + case 0x55: // EOR DP Indexed, X + // EOR(); + break; + case 0x57: // EOR DP Indirect Long Indexed, Y + // EOR(); + break; + case 0x59: // EOR Absolute Indexed, Y + // EOR(); + break; + case 0x5D: // EOR Absolute Indexed, X + // EOR(); + break; + case 0x5F: // EOR Absolute Long Indexed, X + // EOR(); + break; + + case 0x1A: // INC Accumulator + // INC(); + break; + case 0xE6: // INC Direct Page + // INC(); + break; + case 0xEE: // INC Absolute + // INC(); + break; + case 0xF6: // INC DP Indexed, X + // INC(); + break; + case 0xFE: // INC Absolute Indexed, X + // INC(); + break; + + case 0xE8: // INX Increment X register + INX(); + break; + + case 0xC8: // INY Increment Y register + INY(); + break; + + case 0x4C: // JMP Absolute + // JMP(); + break; + case 0x5C: // JMP Absolute Long + // JMP(); + break; + case 0x6C: // JMP Absolute Indirect + // JMP(); + break; + case 0x7C: // JMP Absolute Indexed Indirect, X + // JMP(); + break; + case 0xDC: // JMP Absolute Indirect Long + // JMP(); + break; + + case 0x20: // JSR Absolute + // JSR(); + break; + + case 0x22: // JSL Absolute Long + // JSL(); + break; + + case 0xFC: // JSR Absolute Indexed Indirect, X + // JSR(); + break; + + case 0xA1: // LDA DP Indexed Indirect, X + // LDA(); + break; + case 0xA3: // LDA Stack Relative + // LDA(); + break; + case 0xA5: // LDA Direct Page + // LDA(); + break; + case 0xA7: // LDA DP Indirect Long + // LDA(); + break; + case 0xA9: // LDA Immediate + LDA(); + break; + case 0xAD: // LDA Absolute + // LDA(); + break; + case 0xAF: // LDA Absolute Long + // LDA(); + break; + case 0xB1: // LDA DP Indirect Indexed, Y + // LDA(); + break; + case 0xB2: // LDA DP Indirect + // LDA(); + break; + case 0xB3: // LDA SR Indirect Indexed, Y + // LDA(); + break; + case 0xB5: // LDA DP Indexed, X + // LDA(); + break; + case 0xB7: // LDA DP Indirect Long Indexed, Y + // LDA(); + break; + case 0xB9: // LDA Absolute Indexed, Y + // LDA(); + break; + case 0xBD: // LDA Absolute Indexed, X + // LDA(); + break; + case 0xBF: // LDA Absolute Long Indexed, X + // LDA(); + break; + + case 0xA2: // LDX Immediate + // LDX(); + break; + case 0xA6: // LDX Direct Page + // LDX(); + break; + case 0xAE: // LDX Absolute + // LDX(); + break; + case 0xB6: // LDX DP Indexed, Y + // LDX(); + break; + case 0xBE: // LDX Absolute Indexed, Y + // LDX(); + break; + + case 0xA0: // LDY Immediate + // LDY(); + break; + case 0xA4: // LDY Direct Page + // LDY(); + break; + case 0xAC: // LDY Absolute + // LDY(); + break; + case 0xB4: // LDY DP Indexed, X + // LDY(); + break; + case 0xBC: // LDY Absolute Indexed, X + // LDY(); + break; + + case 0x46: // LSR Direct Page + // LSR(); + break; + case 0x4A: // LSR Accumulator + // LSR(); + break; + case 0x4E: // LSR Absolute + // LSR(); + break; + case 0x56: // LSR DP Indexed, X + // LSR(); + break; + case 0x5E: // LSR Absolute Indexed, X + // LSR(); + break; + + case 0x54: // MVN Block Move Next + // MVN(); + break; + + case 0xEA: // NOP No Operation + // NOP(); + break; + + case 0x01: // ORA DP Indexed Indirect, X + // ORA(); + break; + case 0x03: // ORA Stack Relative + // ORA(); + break; + case 0x05: // ORA Direct Page + // ORA(); + break; + case 0x07: // ORA DP Indirect Long + // ORA(); + break; + case 0x09: // ORA Immediate + // ORA(); + break; + case 0x0D: // ORA Absolute + // ORA(); + break; + case 0x0F: // ORA Absolute Long + // ORA(); + break; + case 0x11: // ORA DP Indirect Indexed, Y + // ORA(); + break; + case 0x12: // ORA DP Indirect + // ORA(); + break; + case 0x13: // ORA SR Indirect Indexed, Y + // ORA(); + break; + case 0x15: // ORA DP Indexed, X + // ORA(); + break; + case 0x17: // ORA DP Indirect Long Indexed, Y + // ORA(); + break; + case 0x19: // ORA Absolute Indexed, Y + // ORA(); + break; + case 0x1D: // ORA Absolute Indexed, X + // ORA(); + break; + case 0x1F: // ORA Absolute Long Indexed, X + // ORA(); + break; + + case 0xF4: // PEA Push Effective Absolute address + // PEA(); + break; + + case 0xD4: // PEI Push Effective Indirect address + // PEI(); + break; + + case 0x62: // PER Push Effective PC Relative Indirect address + // PER(); + break; + + case 0x48: // PHA Push Accumulator + // PHA(); + break; + + case 0x8B: // PHB Push Data Bank Register + // PHB(); + break; + + case 0x0B: // PHD Push Direct Page Register + // PHD(); + break; + + case 0x4B: // PHK Push Program Bank Register + // PHK(); + break; + + case 0x08: // PHP Push Processor Status Register + // PHP(); + break; + + case 0xDA: // PHX Push X register + // PHX(); + break; + + case 0x5A: // PHY Push Y register + // PHY(); + break; + + case 0x68: // PLA Pull Accumulator + // PLA(); + break; + + case 0xAB: // PLB Pull Data Bank Register + // PLB(); + break; + + case 0x2B: // PLD Pull Direct Page Register + // PLD(); + break; + + case 0x28: // PLP Pull Processor Status Register + // PLP(); + break; + + case 0xFA: // PLX Pull X register + // PLX(); + break; + + case 0x7A: // PLY Pull Y register + // PLY(); + break; + + case 0xC2: // REP Reset status bits + REP(); + break; + + case 0x26: // ROL Direct Page + // ROL(); + break; + + case 0x2A: // ROL Accumulator + // ROL(); + break; + case 0x2E: // ROL Absolute + // ROL(); + break; + case 0x36: // ROL DP Indexed, X + // ROL(); + break; + case 0x3E: // ROL Absolute Indexed, X + // ROL(); + break; + + case 0x66: // ROR Direct Page + // ROR(); + break; + case 0x6A: // ROR Accumulator + // ROR(); + break; + case 0x6E: // ROR Absolute + // ROR(); + break; + case 0x76: // ROR DP Indexed, X + // ROR(); + break; + case 0x7E: // ROR Absolute Indexed, X + // ROR(); + break; + + case 0x40: // RTI Return from interrupt + // RTI(); + break; + + case 0x6B: // RTL Return from subroutine long + // RTL(); + break; + + case 0x60: // RTS Return from subroutine + // RTS(); + break; + + case 0xE1: // SBC DP Indexed Indirect, X + // SBC(); + break; + case 0xE3: // SBC Stack Relative + // SBC(); + break; + case 0xE5: // SBC Direct Page + // SBC(); + break; + case 0xE7: // SBC DP Indirect Long + // SBC(); + break; + case 0xE9: // SBC Immediate + // SBC(); + break; + case 0xED: // SBC Absolute + // SBC(); + break; + case 0xEF: // SBC Absolute Long + // SBC(); + break; + case 0xF1: // SBC DP Indirect Indexed, Y + // SBC(); + break; + case 0xF2: // SBC DP Indirect + // SBC(); + break; + case 0xF3: // SBC SR Indirect Indexed, Y + // SBC(); + break; + case 0xF5: // SBC DP Indexed, X + // SBC(); + break; + case 0xF7: // SBC DP Indirect Long Indexed, Y + // SBC(); + break; + case 0xF9: // SBC Absolute Indexed, Y + // SBC(); + break; + case 0xFD: // SBC Absolute Indexed, X + // SBC(); + break; + case 0xFF: // SBC Absolute Long Indexed, X + // SBC(); + break; + + case 0x38: // SEC Set carry + SEC(); + break; + + case 0xF8: // SED Set decimal + SED(); + break; + + case 0x78: // SEI Set interrupt disable + SEI(); + break; + + case 0xE2: // SEP Set status bits + SEP(); + break; + + case 0x81: // STA DP Indexed Indirect, X + // STA(); + break; + case 0x83: // STA Stack Relative + // STA(); + break; + case 0x85: // STA Direct Page + // STA(); + break; + case 0x87: // STA DP Indirect Long + // STA(); + break; + case 0x8D: // STA Absolute + // STA(); + break; + case 0x8F: // STA Absolute Long + // STA(); + break; + case 0x91: // STA DP Indirect Indexed, Y + // STA(); + break; + case 0x92: // STA DP Indirect + // STA(); + break; + case 0x93: // STA SR Indirect Indexed, Y + // STA(); + break; + case 0x95: // STA DP Indexed, X + // STA(); + break; + case 0x97: // STA DP Indirect Long Indexed, Y + // STA(); + break; + case 0x99: // STA Absolute Indexed, Y + // STA(); + break; + case 0x9D: // STA Absolute Indexed, X + // STA(); + break; + case 0x9F: // STA Absolute Long Indexed, X + // STA(); + break; + + case 0xDB: // STP Stop the clock + // STP(); + break; + + case 0x86: // STX Direct Page + // STX(); + break; + case 0x8E: // STX Absolute + // STX(); + break; + case 0x96: // STX DP Indexed, Y + // STX(); + break; + + case 0x84: // STY Direct Page + // STY(); + break; + case 0x8C: // STY Absolute + // STY(); + break; + case 0x94: // STY DP Indexed, X + // STY(); + break; + + case 0x64: // STZ Direct Page + // STZ(); + break; + case 0x74: // STZ DP Indexed, X + // STZ(); + break; + case 0x9C: // STZ Absolute + // STZ(); + break; + case 0x9E: // STZ Absolute Indexed, X + // STZ(); + break; + + case 0xAA: // TAX + TAX(); + break; + + case 0xA8: // TAY + TAY(); + break; + + case 0x5B: // TCD + TCD(); + break; + + case 0x1B: // TCS + TCS(); + break; + + case 0x7B: // TDC + TDC(); + break; + + case 0x14: // TRB Direct Page + // TRB(); + break; + + case 0x1C: // TRB Absolute + // TRB(); + break; + + case 0x04: // TSB Direct Page + // TSB(); + break; + + case 0x0C: // TSB Absolute + // TSB(); + break; + + case 0x3B: // TSC + TSC(); + break; + + case 0xBA: // TSX + TSX(); + break; + + case 0x8A: // TXA + TXA(); + break; + + case 0x9A: // TXS + TXS(); + break; + + case 0x9B: // TXY + TXY(); + break; + + case 0x98: // TYA + TYA(); + break; + + case 0xBB: // TYX + TYX(); + break; + + case 0xCB: // WAI + // WAI(); + break; + + case 0xEB: // XBA + // XBA(); + break; + + case 0xFB: // XCE + // XCE(); + break; + default: + std::cerr << "Unknown instruction: " << std::hex + << static_cast(opcode) << std::endl; + break; + } +} + +void CPU::ADC(uint8_t operand) { + auto C = (bool)GetCarryFlag(); + if (GetAccumulatorSize()) { // 8-bit mode + uint16_t result = (A & 0xFF) + (operand & 0xFF); // + (C ? 1 : 0); + SetCarryFlag(!(result > 0xFF)); // Update the carry flag + + // Update the overflow flag + bool overflow = (~(A ^ operand) & (A ^ result) & 0x80) != 0; + if (overflow) { + status |= 0x40; // Set overflow flag + } else { + status &= ~0x40; // Clear overflow flag + } + + // Update the accumulator + A = (A & 0xFF00) | (result & 0xFF); + + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + } else { + uint32_t result = A + operand; // + (C ? 1 : 0); + SetCarryFlag(!(result > 0xFFFF)); // Update the carry flag + + // Update the overflow flag + bool overflow = (~(A ^ operand) & (A ^ result) & 0x8000) != 0; + SetOverflowFlag(overflow); + + // Update the accumulator + A = result & 0xFFFF; + + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x8000); + } +} + +} // namespace emu +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h new file mode 100644 index 00000000..75f69d95 --- /dev/null +++ b/src/app/emu/cpu.h @@ -0,0 +1,334 @@ +#include +#include +#include + +#include "mem.h" + +namespace yaze { +namespace app { +namespace emu { + +// ADC: Add with carry +// AND: Logical AND +// ASL: Arithmetic shift left +// BCC: Branch if carry clear +// BCS: Branch if carry set +// BEQ: Branch if equal (zero set) +// BIT: Bit test +// BMI: Branch if minus (negative set) +// BNE: Branch if not equal (zero clear) +// BPL: Branch if plus (negative clear) +// BRA: Branch always +// BRK: Break +// BRL: Branch always long +// BVC: Branch if overflow clear +// BVS: Branch if overflow set +// CLC: Clear carry +// CLD: Clear decimal +// CLI: Clear interrupt disable +// CLV: Clear overflow +// CMP: Compare +// COP: Coprocessor +// CPX: Compare X register +// CPY: Compare Y register +// DEC: Decrement +// DEX: Decrement X register +// DEY: Decrement Y register +// EOR: Exclusive OR +// INC: Increment +// INX: Increment X register +// INY: Increment Y register +// JMP: Jump +// JML: Jump long +// JSR: Jump to subroutine +// JSL: Jump to subroutine long +// LDA: Load accumulator +// LDX: Load X register +// LDY: Load Y register +// LSR: Logical shift right +// MVN: Move negative +// MVP: Move positive +// NOP: No operation +// ORA: Logical OR +// PEA: Push effective address +// PEI: Push effective indirect address +// PER: Push effective PC-relative address +// PHA: Push accumulator +// PHB: Push data bank register +// PHD: Push direct page register +// PHK: Push program bank register +// PHP: Push processor status register +// PHX: Push X register +// PHY: Push Y register +// PLA: Pull accumulator +// PLB: Pull data bank register +// PLD: Pull direct page register +// PLP: Pull processor status register +// PLX: Pull X register +// PLY: Pull Y register +// ROL: Rotate left +// ROR: Rotate right +// RTI: Return from interrupt +// RTL: Return from subroutine long +// RTS: Return from subroutine +// SBC: Subtract with carry +// STA: Store accumulator +// STP: Stop the clock +// STX: Store X register +// STY: Store Y register +// STZ: Store zero +// TDC: Transfer direct page register to accumulator +// TRB: Test and reset bits +// TSB: Test and set bits +// WAI: Wait for interrupt +// XBA: Exchange B and A accumulator +// XCE: Exchange carry and emulation + +class CPU : public Memory { + private: + Memory& memory; + + public: + explicit CPU(Memory& mem) : memory(mem) {} + + uint8_t ReadByte(uint16_t address) const override; + uint16_t ReadWord(uint16_t address) const override; + uint32_t ReadWordLong(uint16_t address) const override; + void SetMemory(const std::vector& data) override { + memory.SetMemory(data); + } + + uint8_t FetchByte(); + uint16_t FetchWord(); + uint32_t FetchLong(); + int8_t FetchSignedByte(); + int16_t FetchSignedWord(); + + uint8_t FetchByteDirectPage(uint8_t operand); + + uint16_t DirectPageIndexedIndirectX(); + uint16_t StackRelative(); + uint16_t DirectPage(); + uint16_t DirectPageIndirectLong(); + uint16_t Immediate(); + uint16_t Absolute(); + uint16_t AbsoluteLong(); + uint16_t DirectPageIndirectIndexedY(); + uint16_t DirectPageIndirect(); + uint16_t StackRelativeIndirectIndexedY(); + uint16_t DirectPageIndexedX(); + uint16_t DirectPageIndirectLongIndexedY(); + uint16_t AbsoluteIndexedY(); + uint16_t AbsoluteIndexedX(); + uint16_t AbsoluteLongIndexedX(); + + void ExecuteInstruction(uint8_t opcode); + + void loadROM(const std::vector& rom) { + // if (rom.size() > memory.size()) { + // std::cerr << "ROM too large" << std::endl; + // return; + // } + // std::copy(rom.begin(), rom.end(), memory.begin()); + } + + // Registers + uint8_t A = 0; // Accumulator + uint8_t X = 0; // X index register + uint8_t Y = 0; // Y index register + uint8_t SP = 0; // Stack Pointer + uint16_t DB = 0; // Data Bank register + uint16_t D = 0; // Direct Page register + uint16_t PB = 0; // Program Bank register + uint16_t PC = 0; // Program Counter + uint8_t status; // Processor Status (P) + + // Mnemonic Value Binary Description + // N #$80 10000000 Negative + // V #$40 01000000 Overflow + // M #$20 00100000 Accumulator size (0 = 16-bit, 1 = 8-bit) + // X #$10 00010000 Index size (0 = 16-bit, 1 = 8-bit) + // D #$08 00001000 Decimal + // I #$04 00000100 IRQ disable + // Z #$02 00000010 Zero + // C #$01 00000001 Carry + // E 6502 emulation mode + // B #$10 00010000 Break (emulation mode only) + + // Setting flags in the status register + void SetZeroFlag(bool condition) { + if (condition) { + status |= 0x02; + } else { + status &= ~0x02; + } + } + + void SetNegativeFlag(bool condition) { + if (condition) { + status |= 0x80; + } else { + status &= ~0x80; + } + } + + void SetOverflowFlag(bool condition) { + if (condition) { + status |= 0x40; + } else { + status &= ~0x40; + } + } + + void SetCarryFlag(bool condition) { + if (condition) { + status |= 0x01; + } else { + status &= ~0x01; + } + } + + int GetCarryFlag() { return status & 0x01; } + int GetZeroFlag() { return status & 0x02; } + + int GetAccumulatorSize() { return status & 0x20; } + int GetIndexSize() { return status & 0x10; } + + int GetEmulationMode() { return status & 0x04; } + + // Instructions + void ADC(uint8_t operand); + + void BEQ(int8_t offset) { + if (GetZeroFlag()) { // If the zero flag is set + PC += offset; // Add the offset to the program counter + } + } + + void BCC(int8_t offset) { + if (!GetCarryFlag()) { // If the carry flag is clear + PC += offset; // Add the offset to the program counter + } + } + + void BRL(int16_t offset) { + PC += offset; // Add the offset to the program counter + } + + void LDA() { + A = memory[PC]; + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + PC++; + } + + void SEC() { status |= 0x01; } + + void CLC() { status &= ~0x01; } + + void CLD() { status &= ~0x08; } + + void CLI() { status &= ~0x04; } + + void CLV() { status &= ~0x40; } + + void SEI() { status |= 0x04; } + + void SED() { status |= 0x08; } + + void SEP() { + PC++; + auto byte = FetchByte(); + status |= byte; + } + + void REP() { + PC++; + auto byte = FetchByte(); + status &= ~byte; + } + + void TCD() { + D = A; + SetZeroFlag(D == 0); + SetNegativeFlag(D & 0x80); + } + + void TDC() { + A = D; + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + } + + void TCS() { SP = A; } + + void TAX() { + X = A; + SetZeroFlag(X == 0); + SetNegativeFlag(X & 0x80); + } + + void TAY() { + Y = A; + SetZeroFlag(Y == 0); + SetNegativeFlag(Y & 0x80); + } + + void TYA() { + A = Y; + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + } + + void TXA() { + A = X; + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + } + + void TXY() { + X = Y; + SetZeroFlag(X == 0); + SetNegativeFlag(X & 0x80); + } + + void TYX() { + Y = X; + SetZeroFlag(Y == 0); + SetNegativeFlag(Y & 0x80); + } + + void TSX() { + X = SP; + SetZeroFlag(X == 0); + SetNegativeFlag(X & 0x80); + } + + void TXS() { SP = X; } + + void TSC() { + A = SP; + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + } + + void INX() { + X++; + SetZeroFlag(X == 0); + SetNegativeFlag(X & 0x80); + } + + void INY() { + Y++; + SetZeroFlag(Y == 0); + SetNegativeFlag(Y & 0x80); + } + + // Appease the C++ Gods... + uint8_t operator[](int i) const override { return 0; } + uint8_t at(int i) const override { return 0; } +}; + +} // namespace emu +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/mem.h b/src/app/emu/mem.h new file mode 100644 index 00000000..1dc7280a --- /dev/null +++ b/src/app/emu/mem.h @@ -0,0 +1,45 @@ +#ifndef MEM_H +#define MEM_H + +// memory.h +class Memory { + public: + virtual ~Memory() = default; + virtual uint8_t ReadByte(uint16_t address) const = 0; + virtual uint16_t ReadWord(uint16_t address) const = 0; + virtual uint32_t ReadWordLong(uint16_t address) const = 0; + virtual void SetMemory(const std::vector& data) = 0; + + virtual uint8_t operator[](int i) const = 0; + virtual uint8_t at(int i) const = 0; +}; + +class MemoryImpl : public Memory { + public: + uint8_t ReadByte(uint16_t address) const override { + return memory_.at(address); + } + uint16_t ReadWord(uint16_t address) const override { + return static_cast(memory_.at(address)) | + (static_cast(memory_.at(address + 1)) << 8); + } + void SetMemory(const std::vector& data) override { memory_ = data; } + + uint8_t at(int i) const override { return memory_[i]; } + auto size() const { return memory_.size(); } + auto begin() const { return memory_.begin(); } + auto end() const { return memory_.end(); } + + uint8_t operator[](int i) const override { + if (i > memory_.size()) { + std::cout << i << " out of bounds \n"; + return memory_[0]; + } + return memory_[i]; + } + + // Memory (64KB) + std::vector memory_; +}; + +#endif // MEM_H \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7cca856a..1ec108be 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,11 +14,13 @@ add_executable( yaze_test yaze_test.cc z3ed_test.cc + cpu_test.cc ../src/cli/patch.cc ../src/cli/command_handler.cc compression_test.cc snes_palette_test.cc ../src/app/rom.cc + ../src/app/emu/cpu.cc ../src/app/gfx/bitmap.cc ../src/app/gfx/snes_tile.cc ../src/app/gfx/snes_palette.cc diff --git a/test/cpu_test.cc b/test/cpu_test.cc new file mode 100644 index 00000000..e424890a --- /dev/null +++ b/test/cpu_test.cc @@ -0,0 +1,253 @@ +#include "app/emu/cpu.h" + +#include +#include + +#include "app/emu/mem.h" + +namespace yaze { +namespace app { +namespace emu { + +class MockMemory : public Memory { + public: + MOCK_CONST_METHOD1(ReadByte, uint8_t(uint16_t address)); + MOCK_CONST_METHOD1(ReadWord, uint16_t(uint16_t address)); + MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint16_t address)); + MOCK_CONST_METHOD1(at, uint8_t(int i)); + uint8_t operator[](int i) const override { return at(i); } + + MOCK_METHOD1(SetMemory, void(const std::vector& data)); + + void SetMemoryContents(const std::vector& data) { + memory_ = data; + ON_CALL(*this, ReadByte(::testing::_)) + .WillByDefault( + [this](uint16_t address) { return memory_.at(address); }); + ON_CALL(*this, ReadWord(::testing::_)) + .WillByDefault([this](uint16_t address) { + return static_cast(memory_.at(address)) | + (static_cast(memory_.at(address + 1)) << 8); + }); + } + + private: + std::vector memory_; +}; + +using ::testing::_; +using ::testing::Return; + +TEST(CPUTest, CheckMemoryContents) { + MockMemory memory; + std::vector data = {0x00, 0x01, 0x02, 0x03, 0x04}; + memory.SetMemoryContents(data); + EXPECT_EQ(memory.ReadByte(0), 0x00); + EXPECT_EQ(memory.ReadByte(1), 0x01); + EXPECT_EQ(memory.ReadByte(2), 0x02); + EXPECT_EQ(memory.ReadByte(3), 0x03); + EXPECT_EQ(memory.ReadByte(4), 0x04); +} + +TEST(CPUTest, AddTwoPositiveNumbers) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0x01; + std::vector data = {0x69, 0x01}; + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x01)); + + cpu.ExecuteInstruction(0x69); // ADC Immediate + EXPECT_EQ(cpu.A, 0x02); +} + +TEST(CPUTest, AddPositiveAndNegativeNumbers) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 10; + std::vector data = {0x69, static_cast(-20)}; + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(-20)); + + cpu.ExecuteInstruction(0x69); // ADC Immediate + EXPECT_EQ(cpu.A, static_cast(-10)); +} + +TEST(CPUTest, CheckCarryFlag) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0xFF; + cpu.status = 0; + std::vector data = {0x15, 0x01}; // Operand at address 0x15 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(1)); + + cpu.ExecuteInstruction(0x69); // ADC Immediate + + EXPECT_EQ(cpu.A, 0x00); + EXPECT_TRUE(cpu.GetCarryFlag()); +} + +TEST(CPUTest, BCCWhenCarryFlagClear) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.SetCarryFlag(false); + cpu.PC = 0x1000; + std::vector data(0x1001, 2); // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(2)); + + cpu.ExecuteInstruction(0x90); // BCC + EXPECT_EQ(cpu.PC, 0x1002); +} + +TEST(CPUTest, BCCWhenCarryFlagSet) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.SetCarryFlag(true); + cpu.PC = 0x1000; + std::vector data(0x1001, 2); // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(2)); + + cpu.ExecuteInstruction(0x90); // BCC + cpu.BCC(2); + EXPECT_EQ(cpu.PC, 0x1000); +} + +TEST(CPUTest, BranchLongAlways) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.PC = 0x1000; + std::vector data(0x1001, 2); // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadWord(_)).WillOnce(Return(2)); + + cpu.ExecuteInstruction(0x82); // BRL + EXPECT_EQ(cpu.PC, 0x1004); +} + +TEST(CPUTest, REP) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.status = 0xFF; // All flags set + std::vector data = {0xC2, 0x30, 0x00}; // REP #0x30 (clear N & Z flags) + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xC2); // REP + EXPECT_EQ(cpu.status, 0xCF); // 11001111 +} + +TEST(CPUTest, SEP) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.status = 0x00; // All flags cleared + std::vector data = {0xE2, 0x30, 0x00}; // SEP #0x30 (set N & Z flags) + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xE2); // SEP + EXPECT_EQ(cpu.status, 0x30); // 00110000 +} + +TEST(CPUTest, TXA) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.X = 0xAB; // X register + std::vector data = {0x8A}; // TXA + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x8A); // TXA + EXPECT_EQ(cpu.A, 0xAB); // A register should now be equal to X +} + +TEST(CPUTest, TAX) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0xBC; // A register + std::vector data = {0xAA}; // TAX + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xAA); // TAX + EXPECT_EQ(cpu.X, 0xBC); // X register should now be equal to A +} + +TEST(CPUTest, TYA) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.Y = 0xCD; // Y register + std::vector data = {0x98}; // TYA + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x98); // TYA + EXPECT_EQ(cpu.A, 0xCD); // A register should now be equal to Y +} + +TEST(CPUTest, TAY) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0xDE; // A register + std::vector data = {0xA8}; // TAY + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xA8); // TAY + EXPECT_EQ(cpu.Y, 0xDE); // Y register should now be equal to A +} + +TEST(CPUTest, ADCDirectPage) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0x01; + cpu.D = 0x0000; + std::vector data = {0x65, 0x01, 0x05}; + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x01)); + + cpu.ExecuteInstruction(0x65); // ADC Direct Page + EXPECT_EQ(cpu.A, 0x06); +} + +TEST(CPUTest, ADCAbsolute) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0x01; + std::vector data = {0x6D, 0x10, 0x00, 0x05}; + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x6D); // ADC Absolute + EXPECT_EQ(cpu.A, 0x06); +} + +TEST(CPUTest, ADCIndirectX) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0x01; + cpu.X = 0x02; + std::vector data = {0x72, 0x10, 0x00, 0x00, 0x20, 0x05}; + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x72); // ADC Indirect Indexed with X + EXPECT_EQ(cpu.A, 0x06); +} + +TEST(CPUTest, ADCIndexedIndirect) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0x01; + cpu.X = 0x02; + std::vector data = {0x61, 0x10, 0x18, 0x20, 0x05}; + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x61); // ADC Indexed Indirect + EXPECT_EQ(cpu.A, 0x06); +} + +} // namespace emu +} // namespace app +} // namespace yaze