diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..48be4b8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.14) +project(nes) + +set(CMAKE_CXX_STANDARD 14) + +find_package(SDL2 REQUIRED) + +add_executable(nes) + +target_sources( + nes + PRIVATE + Source/CPU/CPU.cpp + Source/CPU/AddressModes.cpp + Source/CPU/Instructions.cpp + Source/Main.cpp) + +target_compile_options( + nes + PRIVATE + -Wall) + +target_include_directories( + nes + PRIVATE + Source) + +target_link_libraries( + nes + PRIVATE + SDL2::SDL2) diff --git a/Source/CPU/AddressModes.cpp b/Source/CPU/AddressModes.cpp new file mode 100644 index 0000000..c2b23b7 --- /dev/null +++ b/Source/CPU/AddressModes.cpp @@ -0,0 +1,151 @@ +#include "CPU.hpp" + + +void CPU::Implicit() +{ + pc += 1; + + // Do nothing + fetchedByte = acc; +} + +void CPU::Immediate() +{ + pc += 2; + + fetchedAddress = pc - 1; + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::ZeroPage() +{ + pc += 2; + + fetchedAddress = ReadMemory(pc - 1); + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::ZeroPageX() +{ + pc += 2; + + uint8_t operand = ReadMemory(pc - 1); + fetchedAddress = (operand + x) & 0xFFu; + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::ZeroPageY() +{ + pc += 2; + + uint8_t operand = ReadMemory(pc - 1); + fetchedAddress = (operand + y) & 0xFFu; + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::Absolute() +{ + pc += 3; + + uint8_t addressLsb = ReadMemory(pc - 2); + uint8_t addressMsb = ReadMemory(pc - 1); + + fetchedAddress = ComposeAddress(addressMsb, addressLsb); + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::AbsoluteX() +{ + pc += 3; + + uint8_t addressLsb = ReadMemory(pc - 2); + uint8_t addressMsb = ReadMemory(pc - 1); + + uint16_t preAddress = ComposeAddress(addressMsb, addressLsb); + fetchedAddress = preAddress + x; + fetchedByte = ReadMemory(fetchedAddress); + + pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress); +} + +void CPU::AbsoluteY() +{ + pc += 3; + + uint8_t addressLsb = ReadMemory(pc - 2); + uint8_t addressMsb = ReadMemory(pc - 1); + + uint16_t preAddress = ComposeAddress(addressMsb, addressLsb); + fetchedAddress = preAddress + y; + fetchedByte = ReadMemory(fetchedAddress); + + pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress); +} + +void CPU::Indirect() +{ + pc += 3; + + uint8_t indirectLsb = ReadMemory(pc - 2); + uint8_t indirectMsb = ReadMemory(pc - 1); + + // NOTE: 6502 BUG - indirect address wraps + // $02FF + 1 = $0200 + + uint16_t addressIndirect = ComposeAddress(indirectMsb, indirectLsb); + uint8_t addressLsb = ReadMemory(addressIndirect); + + // Increment LSB to read second byte - it will wrap + ++indirectLsb; + + addressIndirect = ComposeAddress(indirectMsb, indirectLsb); + uint8_t addressMsb = ReadMemory(addressIndirect); + + fetchedAddress = ComposeAddress(addressMsb, addressLsb); + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::IndirectX() +{ + pc += 2; + + uint8_t addressIndirect = ReadMemory(pc - 1) + x; + + // Modulo to keep within zero-page + uint16_t addressLsb = ReadMemory((addressIndirect % 0x100)); + uint16_t addressMsb = ReadMemory((addressIndirect + 1) % 0x100); + + fetchedAddress = ComposeAddress(addressMsb, addressLsb); + fetchedByte = ReadMemory(fetchedAddress); +} + +void CPU::IndirectY() +{ + pc += 2; + + uint8_t addressIndirect = ReadMemory(pc - 1); + + // Modulo to keep within zero-page + uint8_t addressLsb = ReadMemory(addressIndirect % 0x100); + uint8_t addressMsb = ReadMemory((addressIndirect + 1) % 0x100); + + uint16_t preAddress = ComposeAddress(addressMsb, addressLsb); + fetchedAddress = preAddress + y; + fetchedByte = ReadMemory(fetchedAddress); + + pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress); +} + +void CPU::Relative() +{ + pc += 2; + + int8_t operand = ReadMemory(pc - 1); + + uint16_t oldPc = pc; + + fetchedAddress = pc + operand; + fetchedByte = ReadMemory(fetchedAddress); + + pageBoundaryCrossed = IsPageBoundaryCrossed(oldPc, fetchedAddress); +} diff --git a/Source/CPU/CPU.cpp b/Source/CPU/CPU.cpp new file mode 100644 index 0000000..3d86b86 --- /dev/null +++ b/Source/CPU/CPU.cpp @@ -0,0 +1,527 @@ +#include +#include "CPU.hpp" + + +const unsigned int STACK_START_ADDRESS = 0x100u; + +CPU::CPU() +{ + status.SetByte(0x24); + + instructions[0xA9] = Instruction{.cycles = 2, .execute = &CPU::LDA, .fetch = &CPU::Immediate}; + instructions[0xA5] = Instruction{.cycles = 3, .execute = &CPU::LDA, .fetch = &CPU::ZeroPage}; + instructions[0xB5] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::ZeroPageX}; + instructions[0xAD] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::Absolute}; + instructions[0xBD] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteX}; + instructions[0xB9] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteY}; + instructions[0xA1] = Instruction{.cycles = 6, .execute = &CPU::LDA, .fetch = &CPU::IndirectX}; + instructions[0xB1] = Instruction{.cycles = 5, .execute = &CPU::LDA, .fetch = &CPU::IndirectY}; + + instructions[0xA2] = Instruction{.cycles = 2, .execute = &CPU::LDX, .fetch = &CPU::Immediate}; + instructions[0xA6] = Instruction{.cycles = 3, .execute = &CPU::LDX, .fetch = &CPU::ZeroPage}; + instructions[0xB6] = Instruction{.cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::ZeroPageY}; + instructions[0xAE] = Instruction{.cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::Absolute}; + instructions[0xBE] = Instruction{.cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::AbsoluteY}; + + instructions[0xA0] = Instruction{.cycles = 2, .execute = &CPU::LDY, .fetch = &CPU::Immediate}; + instructions[0xA4] = Instruction{.cycles = 3, .execute = &CPU::LDY, .fetch = &CPU::ZeroPage}; + instructions[0xB4] = Instruction{.cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::ZeroPageX}; + instructions[0xAC] = Instruction{.cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::Absolute}; + instructions[0xBC] = Instruction{.cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::AbsoluteX}; + + instructions[0x85] = Instruction{.cycles = 3, .execute = &CPU::STA, .fetch = &CPU::ZeroPage}; + instructions[0x95] = Instruction{.cycles = 4, .execute = &CPU::STA, .fetch = &CPU::ZeroPageX}; + instructions[0x8D] = Instruction{.cycles = 4, .execute = &CPU::STA, .fetch = &CPU::Absolute}; + instructions[0x9D] = Instruction{.cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteX}; + instructions[0x99] = Instruction{.cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteY}; + instructions[0x81] = Instruction{.cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectX}; + instructions[0x91] = Instruction{.cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectY}; + + instructions[0x86] = Instruction{.cycles = 3, .execute = &CPU::STX, .fetch = &CPU::ZeroPage}; + instructions[0x96] = Instruction{.cycles = 4, .execute = &CPU::STX, .fetch = &CPU::ZeroPageY}; + instructions[0x8E] = Instruction{.cycles = 4, .execute = &CPU::STX, .fetch = &CPU::Absolute}; + + instructions[0x84] = Instruction{.cycles = 3, .execute = &CPU::STY, .fetch = &CPU::ZeroPage}; + instructions[0x94] = Instruction{.cycles = 4, .execute = &CPU::STY, .fetch = &CPU::ZeroPageX}; + instructions[0x8C] = Instruction{.cycles = 4, .execute = &CPU::STY, .fetch = &CPU::Absolute}; + + instructions[0xAA] = Instruction{.cycles = 2, .execute = &CPU::TAX, .fetch = &CPU::Implicit}; + instructions[0xA8] = Instruction{.cycles = 2, .execute = &CPU::TAY, .fetch = &CPU::Implicit}; + instructions[0x8A] = Instruction{.cycles = 2, .execute = &CPU::TXA, .fetch = &CPU::Implicit}; + instructions[0x98] = Instruction{.cycles = 2, .execute = &CPU::TYA, .fetch = &CPU::Implicit}; + instructions[0xBA] = Instruction{.cycles = 2, .execute = &CPU::TSX, .fetch = &CPU::Implicit}; + instructions[0x9A] = Instruction{.cycles = 2, .execute = &CPU::TXS, .fetch = &CPU::Implicit}; + + instructions[0x48] = Instruction{.cycles = 3, .execute = &CPU::PHA, .fetch = &CPU::Implicit}; + instructions[0x08] = Instruction{.cycles = 3, .execute = &CPU::PHP, .fetch = &CPU::Implicit}; + instructions[0x68] = Instruction{.cycles = 4, .execute = &CPU::PLA, .fetch = &CPU::Implicit}; + instructions[0x28] = Instruction{.cycles = 4, .execute = &CPU::PLP, .fetch = &CPU::Implicit}; + + instructions[0x29] = Instruction{.cycles = 2, .execute = &CPU::AND, .fetch = &CPU::Immediate}; + instructions[0x25] = Instruction{.cycles = 3, .execute = &CPU::AND, .fetch = &CPU::ZeroPage}; + instructions[0x35] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::ZeroPageX}; + instructions[0x2D] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::Absolute}; + instructions[0x3D] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteX}; + instructions[0x39] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteY}; + instructions[0x21] = Instruction{.cycles = 6, .execute = &CPU::AND, .fetch = &CPU::IndirectX}; + instructions[0x31] = Instruction{.cycles = 5, .execute = &CPU::AND, .fetch = &CPU::IndirectY}; + + instructions[0x49] = Instruction{.cycles = 2, .execute = &CPU::EOR, .fetch = &CPU::Immediate}; + instructions[0x45] = Instruction{.cycles = 3, .execute = &CPU::EOR, .fetch = &CPU::ZeroPage}; + instructions[0x55] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::ZeroPageX}; + instructions[0x4D] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::Absolute}; + instructions[0x5D] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteX}; + instructions[0x59] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteY}; + instructions[0x41] = Instruction{.cycles = 6, .execute = &CPU::EOR, .fetch = &CPU::IndirectX}; + instructions[0x51] = Instruction{.cycles = 5, .execute = &CPU::EOR, .fetch = &CPU::IndirectY}; + + instructions[0x09] = Instruction{.cycles = 2, .execute = &CPU::ORA, .fetch = &CPU::Immediate}; + instructions[0x05] = Instruction{.cycles = 3, .execute = &CPU::ORA, .fetch = &CPU::ZeroPage}; + instructions[0x15] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::ZeroPageX}; + instructions[0x0D] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::Absolute}; + instructions[0x1D] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteX}; + instructions[0x19] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteY}; + instructions[0x01] = Instruction{.cycles = 6, .execute = &CPU::ORA, .fetch = &CPU::IndirectX}; + instructions[0x11] = Instruction{.cycles = 5, .execute = &CPU::ORA, .fetch = &CPU::IndirectY}; + + instructions[0x24] = Instruction{.cycles = 3, .execute = &CPU::BIT, .fetch = &CPU::ZeroPage}; + instructions[0x2C] = Instruction{.cycles = 4, .execute = &CPU::BIT, .fetch = &CPU::Absolute}; + + instructions[0x69] = Instruction{.cycles = 2, .execute = &CPU::ADC, .fetch = &CPU::Immediate}; + instructions[0x65] = Instruction{.cycles = 3, .execute = &CPU::ADC, .fetch = &CPU::ZeroPage}; + instructions[0x75] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::ZeroPageX}; + instructions[0x6D] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::Absolute}; + instructions[0x7D] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteX}; + instructions[0x79] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteY}; + instructions[0x61] = Instruction{.cycles = 6, .execute = &CPU::ADC, .fetch = &CPU::IndirectX}; + instructions[0x71] = Instruction{.cycles = 5, .execute = &CPU::ADC, .fetch = &CPU::IndirectY}; + + instructions[0xE9] = Instruction{.cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate}; + instructions[0xE5] = Instruction{.cycles = 3, .execute = &CPU::SBC, .fetch = &CPU::ZeroPage}; + instructions[0xF5] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::ZeroPageX}; + instructions[0xED] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::Absolute}; + instructions[0xFD] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteX}; + instructions[0xF9] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteY}; + instructions[0xE1] = Instruction{.cycles = 6, .execute = &CPU::SBC, .fetch = &CPU::IndirectX}; + instructions[0xF1] = Instruction{.cycles = 5, .execute = &CPU::SBC, .fetch = &CPU::IndirectY}; + + instructions[0xC9] = Instruction{.cycles = 2, .execute = &CPU::CMP, .fetch = &CPU::Immediate}; + instructions[0xC5] = Instruction{.cycles = 3, .execute = &CPU::CMP, .fetch = &CPU::ZeroPage}; + instructions[0xD5] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::ZeroPageX}; + instructions[0xCD] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::Absolute}; + instructions[0xDD] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteX}; + instructions[0xD9] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteY}; + instructions[0xC1] = Instruction{.cycles = 6, .execute = &CPU::CMP, .fetch = &CPU::IndirectX}; + instructions[0xD1] = Instruction{.cycles = 5, .execute = &CPU::CMP, .fetch = &CPU::IndirectY}; + + instructions[0xE0] = Instruction{.cycles = 2, .execute = &CPU::CPX, .fetch = &CPU::Immediate}; + instructions[0xE4] = Instruction{.cycles = 3, .execute = &CPU::CPX, .fetch = &CPU::ZeroPage}; + instructions[0xEC] = Instruction{.cycles = 4, .execute = &CPU::CPX, .fetch = &CPU::Absolute}; + + instructions[0xC0] = Instruction{.cycles = 2, .execute = &CPU::CPY, .fetch = &CPU::Immediate}; + instructions[0xC4] = Instruction{.cycles = 3, .execute = &CPU::CPY, .fetch = &CPU::ZeroPage}; + instructions[0xCC] = Instruction{.cycles = 4, .execute = &CPU::CPY, .fetch = &CPU::Absolute}; + + instructions[0xE6] = Instruction{.cycles = 5, .execute = &CPU::INC, .fetch = &CPU::ZeroPage}; + instructions[0xF6] = Instruction{.cycles = 6, .execute = &CPU::INC, .fetch = &CPU::ZeroPageX}; + instructions[0xEE] = Instruction{.cycles = 6, .execute = &CPU::INC, .fetch = &CPU::Absolute}; + instructions[0xFE] = Instruction{.cycles = 7, .execute = &CPU::INC, .fetch = &CPU::AbsoluteX}; + + instructions[0xE8] = Instruction{.cycles = 2, .execute = &CPU::INX, .fetch = &CPU::Implicit}; + + instructions[0xC8] = Instruction{.cycles = 2, .execute = &CPU::INY, .fetch = &CPU::Implicit}; + + instructions[0xC6] = Instruction{.cycles = 5, .execute = &CPU::DEC, .fetch = &CPU::ZeroPage}; + instructions[0xD6] = Instruction{.cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::ZeroPageX}; + instructions[0xCE] = Instruction{.cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::Absolute}; + instructions[0xDE] = Instruction{.cycles = 7, .execute = &CPU::DEC, .fetch = &CPU::AbsoluteX}; + + instructions[0xCA] = Instruction{.cycles = 2, .execute = &CPU::DEX, .fetch = &CPU::Implicit}; + + instructions[0x88] = Instruction{.cycles = 2, .execute = &CPU::DEY, .fetch = &CPU::Implicit}; + + instructions[0x0A] = Instruction{.cycles = 2, .execute = &CPU::ASL, .fetch = &CPU::Implicit}; + instructions[0x06] = Instruction{.cycles = 5, .execute = &CPU::ASL, .fetch = &CPU::ZeroPage}; + instructions[0x16] = Instruction{.cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::ZeroPageX}; + instructions[0x0E] = Instruction{.cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::Absolute}; + instructions[0x1E] = Instruction{.cycles = 7, .execute = &CPU::ASL, .fetch = &CPU::AbsoluteX}; + + instructions[0x4A] = Instruction{.cycles = 2, .execute = &CPU::LSR, .fetch = &CPU::Implicit}; + instructions[0x46] = Instruction{.cycles = 5, .execute = &CPU::LSR, .fetch = &CPU::ZeroPage}; + instructions[0x56] = Instruction{.cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::ZeroPageX}; + instructions[0x4E] = Instruction{.cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::Absolute}; + instructions[0x5E] = Instruction{.cycles = 7, .execute = &CPU::LSR, .fetch = &CPU::AbsoluteX}; + + instructions[0x2A] = Instruction{.cycles = 2, .execute = &CPU::ROL, .fetch = &CPU::Implicit}; + instructions[0x26] = Instruction{.cycles = 5, .execute = &CPU::ROL, .fetch = &CPU::ZeroPage}; + instructions[0x36] = Instruction{.cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::ZeroPageX}; + instructions[0x2E] = Instruction{.cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::Absolute}; + instructions[0x3E] = Instruction{.cycles = 7, .execute = &CPU::ROL, .fetch = &CPU::AbsoluteX}; + + instructions[0x6A] = Instruction{.cycles = 2, .execute = &CPU::ROR, .fetch = &CPU::Implicit}; + instructions[0x66] = Instruction{.cycles = 5, .execute = &CPU::ROR, .fetch = &CPU::ZeroPage}; + instructions[0x76] = Instruction{.cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::ZeroPageX}; + instructions[0x6E] = Instruction{.cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::Absolute}; + instructions[0x7E] = Instruction{.cycles = 7, .execute = &CPU::ROR, .fetch = &CPU::AbsoluteX}; + + instructions[0x4C] = Instruction{.cycles = 3, .execute = &CPU::JMP, .fetch = &CPU::Absolute}; + instructions[0x6C] = Instruction{.cycles = 5, .execute = &CPU::JMP, .fetch = &CPU::Indirect}; + + instructions[0x20] = Instruction{.cycles = 6, .execute = &CPU::JSR, .fetch = &CPU::Absolute}; + + instructions[0x60] = Instruction{.cycles = 6, .execute = &CPU::RTS, .fetch = &CPU::Implicit}; + + instructions[0x90] = Instruction{.cycles = 2, .execute = &CPU::BCC, .fetch = &CPU::Relative}; + instructions[0xB0] = Instruction{.cycles = 2, .execute = &CPU::BCS, .fetch = &CPU::Relative}; + instructions[0xF0] = Instruction{.cycles = 2, .execute = &CPU::BEQ, .fetch = &CPU::Relative}; + instructions[0x30] = Instruction{.cycles = 2, .execute = &CPU::BMI, .fetch = &CPU::Relative}; + instructions[0xD0] = Instruction{.cycles = 2, .execute = &CPU::BNE, .fetch = &CPU::Relative}; + instructions[0x10] = Instruction{.cycles = 2, .execute = &CPU::BPL, .fetch = &CPU::Relative}; + instructions[0x50] = Instruction{.cycles = 2, .execute = &CPU::BVC, .fetch = &CPU::Relative}; + instructions[0x70] = Instruction{.cycles = 2, .execute = &CPU::BVS, .fetch = &CPU::Relative}; + + instructions[0x18] = Instruction{.cycles = 2, .execute = &CPU::CLC, .fetch = &CPU::Implicit}; + instructions[0xD8] = Instruction{.cycles = 2, .execute = &CPU::CLD, .fetch = &CPU::Implicit}; + instructions[0x58] = Instruction{.cycles = 2, .execute = &CPU::CLI, .fetch = &CPU::Implicit}; + instructions[0xB8] = Instruction{.cycles = 2, .execute = &CPU::CLV, .fetch = &CPU::Implicit}; + instructions[0x38] = Instruction{.cycles = 2, .execute = &CPU::SEC, .fetch = &CPU::Implicit}; + instructions[0xF8] = Instruction{.cycles = 2, .execute = &CPU::SED, .fetch = &CPU::Implicit}; + instructions[0x78] = Instruction{.cycles = 2, .execute = &CPU::SEI, .fetch = &CPU::Implicit}; + + instructions[0x00] = Instruction{.cycles = 7, .execute = &CPU::BRK, .fetch = &CPU::Implicit}; + instructions[0xEA] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x40] = Instruction{.cycles = 6, .execute = &CPU::RTI, .fetch = &CPU::Implicit}; + + instructions[0x1A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x3A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x5A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x7A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xDA] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xFA] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x80] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0x82] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0x89] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0xC2] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0xE2] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0x04] = Instruction{.cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage}; + instructions[0x44] = Instruction{.cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage}; + instructions[0x64] = Instruction{.cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage}; + instructions[0x14] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x34] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x54] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x74] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0xD4] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0xF4] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x0C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::Absolute}; + instructions[0x1C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0x3C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0x5C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0x7C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0xDC] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0xFC] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + + instructions[0x07] = Instruction{.cycles = 5, .execute = &CPU::SLO, .fetch = &CPU::ZeroPage}; + instructions[0x17] = Instruction{.cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::ZeroPageX}; + instructions[0x0F] = Instruction{.cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::Absolute}; + instructions[0x1F] = Instruction{.cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteX}; + instructions[0x1B] = Instruction{.cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteY}; + instructions[0x03] = Instruction{.cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectX}; + instructions[0x13] = Instruction{.cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectY}; + + instructions[0x27] = Instruction{.cycles = 5, .execute = &CPU::RLA, .fetch = &CPU::ZeroPage}; + instructions[0x37] = Instruction{.cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::ZeroPageX}; + instructions[0x2F] = Instruction{.cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::Absolute}; + instructions[0x3F] = Instruction{.cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteX}; + instructions[0x3B] = Instruction{.cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteY}; + instructions[0x23] = Instruction{.cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectX}; + instructions[0x33] = Instruction{.cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectY}; + + instructions[0x47] = Instruction{.cycles = 5, .execute = &CPU::SRE, .fetch = &CPU::ZeroPage}; + instructions[0x57] = Instruction{.cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::ZeroPageX}; + instructions[0x4F] = Instruction{.cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::Absolute}; + instructions[0x5F] = Instruction{.cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteX}; + instructions[0x5B] = Instruction{.cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteY}; + instructions[0x43] = Instruction{.cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectX}; + instructions[0x53] = Instruction{.cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectY}; + + instructions[0x67] = Instruction{.cycles = 5, .execute = &CPU::RRA, .fetch = &CPU::ZeroPage}; + instructions[0x77] = Instruction{.cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::ZeroPageX}; + instructions[0x6F] = Instruction{.cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::Absolute}; + instructions[0x7F] = Instruction{.cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteX}; + instructions[0x7B] = Instruction{.cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteY}; + instructions[0x63] = Instruction{.cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectX}; + instructions[0x73] = Instruction{.cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectY}; + + instructions[0x87] = Instruction{.cycles = 3, .execute = &CPU::SAX, .fetch = &CPU::ZeroPage}; + instructions[0x97] = Instruction{.cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::ZeroPageY}; + instructions[0x8F] = Instruction{.cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::Absolute}; + instructions[0x83] = Instruction{.cycles = 6, .execute = &CPU::SAX, .fetch = &CPU::IndirectX}; + + instructions[0xEB] = Instruction{.cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate}; + + instructions[0xAB] = Instruction{.cycles = 2, .execute = &CPU::LAX, .fetch = &CPU::Immediate}; + instructions[0xA7] = Instruction{.cycles = 3, .execute = &CPU::LAX, .fetch = &CPU::ZeroPage}; + instructions[0xB7] = Instruction{.cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::ZeroPageY}; + instructions[0xAF] = Instruction{.cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::Absolute}; + instructions[0xBF] = Instruction{.cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::AbsoluteY}; + instructions[0xA3] = Instruction{.cycles = 6, .execute = &CPU::LAX, .fetch = &CPU::IndirectX}; + instructions[0xB3] = Instruction{.cycles = 5, .execute = &CPU::LAX, .fetch = &CPU::IndirectY}; + + instructions[0xC7] = Instruction{.cycles = 5, .execute = &CPU::DCP, .fetch = &CPU::ZeroPage}; + instructions[0xD7] = Instruction{.cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::ZeroPageX}; + instructions[0xCF] = Instruction{.cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::Absolute}; + instructions[0xDF] = Instruction{.cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteX}; + instructions[0xDB] = Instruction{.cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteY}; + instructions[0xC3] = Instruction{.cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectX}; + instructions[0xD3] = Instruction{.cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectY}; + + instructions[0xE7] = Instruction{.cycles = 5, .execute = &CPU::ISC, .fetch = &CPU::ZeroPage}; + instructions[0xF7] = Instruction{.cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::ZeroPageX}; + instructions[0xEF] = Instruction{.cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::Absolute}; + instructions[0xFF] = Instruction{.cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteX}; + instructions[0xFB] = Instruction{.cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteY}; + instructions[0xE3] = Instruction{.cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectX}; + instructions[0xF3] = Instruction{.cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectY}; + + instructions[0x0B] = Instruction{.cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate}; + instructions[0x2B] = Instruction{.cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate}; + instructions[0x4B] = Instruction{.cycles = 0, .execute = &CPU::ALR, .fetch = &CPU::Immediate}; + instructions[0x6B] = Instruction{.cycles = 0, .execute = &CPU::ARR, .fetch = &CPU::Immediate}; + instructions[0xCB] = Instruction{.cycles = 0, .execute = &CPU::AXS, .fetch = &CPU::Immediate}; + + // Unused by NES + instructions[0x9C] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x9E] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x8B] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x93] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x9B] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x9F] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xBB] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x02] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x12] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x22] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x32] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x42] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x52] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x62] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x72] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x92] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xB2] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xD2] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xF2] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; +} + +void CPU::StackPush(uint8_t val) +{ + // Pointing to free space; get address before decrement + uint16_t address = STACK_START_ADDRESS | sp; + --sp; + + WriteMemory(address, val); +} + +uint8_t CPU::StackPop() +{ + // Pointing to free space; get address after increment + ++sp; + uint16_t address = STACK_START_ADDRESS | sp; + + return ReadMemory(address); +} + +void CPU::WriteMemory(uint16_t address, uint8_t value) +{ + // First five bits of address determines its mapping + switch ((address & 0xF800u) >> 11u) + { + // RAM and mirrors + case 0x00: + case 0x01: + case 0x02: + case 0x03: + { + uint16_t translatedAddress = address & 0x7FFu; + + ram[translatedAddress] = value; + break; + } + + // SRAM and mirrors + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + { + uint16_t translatedAddress = address & 0x1FFFu; + + sram[translatedAddress] = value; + break; + } + + // ROM (Low) and mirrors + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + uint16_t translatedAddress = address & 0x3FFFu; + + romLow[translatedAddress] = value; + break; + } + + // ROM (High) and mirrors + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + { + uint16_t translatedAddress = address & 0x3FFFu; + + romHigh[translatedAddress] = value; + break; + } + } +} + +uint8_t CPU::ReadMemory(uint16_t address) +{ + uint8_t byte{}; + + switch ((address & 0xF800u) >> 11u) + { + // RAM and mirrors + case 0x00: + case 0x01: + case 0x02: + case 0x03: + { + uint16_t translatedAddress = address & 0x7FFu; + + byte = ram[translatedAddress]; + break; + } + + // SRAM and mirrors + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + { + uint16_t translatedAddress = address & 0x1FFFu; + + byte = sram[translatedAddress]; + break; + } + + // ROM (Low) and mirrors + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + uint16_t translatedAddress = address & 0x3FFFu; + + byte = romLow[translatedAddress]; + break; + } + + // ROM (High) and mirrors + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + { + uint16_t translatedAddress = address & 0x3FFFu; + + byte = romHigh[translatedAddress]; + break; + } + } + + return byte; +} + +void CPU::LoadRom(const char* filename) +{ + std::ifstream file(filename, std::ios::binary | std::ios::ate); + + if (file.is_open()) + { + std::streampos size = file.tellg(); + char* buffer = new char[size]; + file.seekg(0x10, std::ios::beg); + file.read(buffer, size); + file.close(); + + for (long i = 0; i < 0x4000; ++i) + { + WriteMemory(0x8000 + i, buffer[i]); + WriteMemory(0xC000 + i, buffer[i]); + } + + delete[] buffer; + } +} + +void CPU::Cycle() +{ + opcode = ReadMemory(pc); + +#ifndef NDEBUG + Log(opcode, pc); +#endif + + pageBoundaryCrossed = false; + + ((*this).*(instructions[opcode].fetch))(); + + ((*this).*(instructions[opcode].execute))(); + + cycles += instructions[opcode].cycles; +} + +void CPU::Log(uint8_t instruction, uint16_t pc) +{ + printf("%04X ", pc); + + printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X PPU:000,000 CYC:%lu\n", acc, x, y, status.GetByte(), sp, cycles); +} + +bool CPU::TestBits(uint8_t value, uint8_t bits) +{ + return (value & bits) == bits; +} + +bool CPU::IsNegative(uint8_t value) +{ + // Bit 7 is set + return TestBits(value, 1u << 7u); +} + +bool CPU::IsPageBoundaryCrossed(uint16_t before, uint16_t after) +{ + // Any bits in upper byte indicates a page boundary crossing (pages are 0xFF) + return (before & 0xFF00u) != (after & 0xFF00u); +} + +uint16_t CPU::ComposeAddress(uint8_t msb, uint8_t lsb) +{ + return (msb << 8u) | lsb; +} diff --git a/Source/CPU/CPU.hpp b/Source/CPU/CPU.hpp new file mode 100644 index 0000000..23e1530 --- /dev/null +++ b/Source/CPU/CPU.hpp @@ -0,0 +1,243 @@ +#pragma once + +#include +#include + + +const unsigned int INSTRUCTION_COUNT = 256; +const unsigned int RAM_SIZE = 2048; +const unsigned int SRAM_SIZE = 8192; +const unsigned int ROM_SIZE = 16384; +const unsigned int IRQ_VECTOR_MSB = 0xFFFF; +const unsigned int IRQ_VECTOR_LSB = 0xFFFE; + + +class Status +{ +public: + void SetByte(uint8_t byte) + { + negative = (byte & 0x80u) >> 7u; + overflow = (byte & 0x40u) >> 6u; + b1 = (byte & 0x20u) >> 5u; + b0 = (byte & 0x10u) >> 4u; + decimal = (byte & 0x08u) >> 3u; + interruptDisable = (byte & 0x04u) >> 2u; + zero = (byte & 0x02u) >> 1u; + carry = (byte & 0x01u); + } + + uint8_t GetByte() + { + return negative << 7u | overflow << 6u | b1 << 5u | b0 << 4u + | decimal << 3u | interruptDisable << 2u | zero << 1u | carry; + } + + uint8_t carry; + uint8_t zero; + uint8_t interruptDisable; + uint8_t decimal; + uint8_t b0; + uint8_t b1; + uint8_t overflow; + uint8_t negative; +}; + + +class CPU; +class Instruction +{ +public: + uint8_t cycles; + + using CpuFunc = void (CPU::*)(); + CpuFunc fetch; + CpuFunc execute; +}; + + +class CPU +{ +public: + CPU(); + void LoadRom(const char* filename); + void Cycle(); + void Log(uint8_t instruction, uint16_t pc); + +private: + void StackPush(uint8_t val); + uint8_t StackPop(); + void WriteMemory(uint16_t address, uint8_t value); + uint8_t ReadMemory(uint16_t address); + + + // --------------- HELPERS --------------- // + static bool TestBits(uint8_t value, uint8_t bits); + static bool IsNegative(uint8_t value); + static bool IsPageBoundaryCrossed(uint16_t before, uint16_t after); + static uint16_t ComposeAddress(uint8_t msb, uint8_t lsb); + + + // --------------- ADDRESS MODES --------------- // + // Operand is implied by the instruction + void Implicit(); + + // Operand is the byte/STA + void Immediate(); + + // Operand contains a zero-page address (0x00 to 0xFF) + // Byte is retrieved from that address in memory + void ZeroPage(); + + // Operand is added to X to get effective address, with wrap around back to 0x00 + void ZeroPageX(); + + // Operand is added to X to get effective address, with wrap around back to 0x00 + void ZeroPageY(); + + // Operand is the memory location + void Absolute(); + + // Operand contains an absolute address which can be added to X to get a new address + // If the address after adding X crosses a page-boundary, an additional cycle occurs + void AbsoluteX(); + + // Operand contains an absolute address which can be added to Y to get a new address + // If the address after adding Y crosses a page-boundary, an additional cycle occurs + void AbsoluteY(); + + // Operand contains address of LSB of effective address - MSB is in address + 1 + void Indirect(); + + // Operand contains a zero-page address (0x00 to 0xFF) + // Value in X is added to operand to retrieve two bytes of address in memory (addition will wrap around back to 0x00) + // Effective address is used to retrieve the byte + void IndirectX(); + + // Operand contains a zero-page address (0x00 to 0xFF) + // Two bytes of address are retrieved from that address in memory + // Y is added to that address and the new address is used to retrieve the byte + // If the addition of Y causes a page-boundary crossing, an additional cycle occurs + void IndirectY(); + + // Operand is an offset from current instruction used for branching + void Relative(); + + + // --------------- LOGICAL --------------- // + void AND(); + void EOR(); + void ORA(); + void BIT(); + + // --------------- ARITHMETIC --------------- // + void ADC(); + void SBC(); + void CMP(); + void CPX(); + void CPY(); + + // --------------- LOAD/STORE --------------- // + void STA(); + void STX(); + void STY(); + void LDA(); + void LDX(); + void LDY(); + + // --------------- TRANSFERS --------------- // + void TXA(); + void TYA(); + void TXS(); + void TAX(); + void TAY(); + void TSX(); + + // --------------- STACK --------------- // + void PHP(); + void PLP(); + void PHA(); + void PLA(); + + // --------------- INCREMENTS & DECREMENTS --------------- // + void INC(); + void INX(); + void INY(); + void DEC(); + void DEX(); + void DEY(); + + // --------------- SHIFTS --------------- // + void ASL(); + void LSR(); + void ROL(); + void ROR(); + + // --------------- JUMPS & CALLS --------------- // + void JMP(); + void JSR(); + void RTS(); + + // --------------- STATUS FLAG CHANGES --------------- // + void CLC(); + void SEC(); + void CLI(); + void SEI(); + void CLV(); + void CLD(); + void SED(); + + // --------------- BRANCHES --------------- // + void BPL(); + void BMI(); + void BVC(); + void BVS(); + void BCC(); + void BCS(); + void BNE(); + void BEQ(); + + // --------------- SYSTEM --------------- // + void BRK(); + void NOP(); + void RTI(); + + // --------------- UNOFFICIAL --------------- // + void LAX(); + void SAX(); + void DCP(); + void ISC(); + void SLO(); + void RLA(); + void SRE(); + void RRA(); + void ANC(); + void ALR(); + void ARR(); + void AXS(); + + // Machine features + uint8_t ram[RAM_SIZE]{}; + uint8_t sram[SRAM_SIZE]{}; + uint8_t romLow[ROM_SIZE]{}; + uint8_t romHigh[ROM_SIZE]{}; + uint16_t pc = 0xC000; + uint8_t sp = 0xFD; + uint8_t acc{}; + uint8_t x{}; + uint8_t y{}; + Status status{}; + uint64_t cycles = 7; + bool irq{}; + bool nmi{}; + + // Emulator variables + bool pageBoundaryCrossed{}; + uint8_t opcode{}; + uint16_t fetchedAddress{}; + uint8_t fetchedByte{}; + + Instruction instructions[INSTRUCTION_COUNT]{}; +}; + + diff --git a/Source/CPU/Instructions.cpp b/Source/CPU/Instructions.cpp new file mode 100644 index 0000000..7abce46 --- /dev/null +++ b/Source/CPU/Instructions.cpp @@ -0,0 +1,698 @@ +#include "CPU/CPU.hpp" + + +// --------------- ARITHMETIC --------------- // +void CPU::ADC() +{ + uint16_t tempSum = acc + fetchedByte; + + tempSum += status.carry; + + status.carry = (tempSum & 0xFF00u) ? 1u : 0u; + + bool accSign = IsNegative(acc); + bool valSign = IsNegative(fetchedByte); + bool sumSign = IsNegative(tempSum); + + acc = tempSum; + + // If sign of operands does not match sign of result -> overflow + status.overflow = ((accSign == valSign) && (accSign != sumSign)) ? 1u : 0u; + + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; + + cycles += (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::SBC() +{ + uint16_t tempDiff = acc - fetchedByte; + + tempDiff -= (1u - status.carry); + + status.carry = (tempDiff & 0xFF00) ? 0u : 1u; + + bool accSign = IsNegative(acc); + bool valSign = IsNegative(fetchedByte); + bool sumSign = IsNegative(tempDiff); + + // Set and truncate most-significant byte of temp sum + acc = tempDiff; + + // If sign of operands does not match sign of result -> overflow + status.overflow = ((accSign != valSign) && (valSign == sumSign)) ? 1u : 0u; + + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; + + cycles += (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::CMP() +{ + status.carry = (acc >= fetchedByte) ? 1u : 0u; + status.zero = (acc == fetchedByte) ? 1u : 0u; + status.negative = (IsNegative(acc - fetchedByte)) ? 1u : 0u; + + cycles += (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::CPX() +{ + status.carry = (x >= fetchedByte) ? 1u : 0u; + status.zero = (x == fetchedByte) ? 1u : 0u; + status.negative = (IsNegative(x - fetchedByte)) ? 1u : 0u; +} + +void CPU::CPY() +{ + status.carry = (y >= fetchedByte) ? 1u : 0u; + status.zero = (y == fetchedByte) ? 1u : 0u; + status.negative = (IsNegative(y - fetchedByte)) ? 1u : 0u; +} + + +// --------------- BRANCHES --------------- // +void CPU::BPL() +{ + if (!status.negative) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BMI() +{ + if (status.negative) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BVC() +{ + if (!status.overflow) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BVS() +{ + if (status.overflow) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BCC() +{ + if (!status.carry) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BCS() +{ + if (status.carry) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BNE() +{ + if (!status.zero) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + +void CPU::BEQ() +{ + if (status.zero) + { + pc = fetchedAddress; + + cycles += (pageBoundaryCrossed) ? 2u : 1u; + } +} + + +// --------------- INCREMENT/DECREMENT --------------- // +void CPU::INC() +{ + ++fetchedByte; + + WriteMemory(fetchedAddress, fetchedByte); + + status.zero = fetchedByte ? 0u : 1u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; +} + +void CPU::INX() +{ + ++x; + + status.zero = x ? 0u : 1u; + status.negative = (IsNegative(x)) ? 1u : 0u; +} + +void CPU::INY() +{ + ++y; + + status.zero = y ? 0u : 1u; + status.negative = (IsNegative(y)) ? 1u : 0u; +} + +void CPU::DEC() +{ + --fetchedByte; + + WriteMemory(fetchedAddress, fetchedByte); + + status.zero = fetchedByte ? 0u : 1u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; +} + +void CPU::DEX() +{ + --x; + + status.zero = x ? 0u : 1u; + status.negative = (IsNegative(x)) ? 1u : 0u; +} + +void CPU::DEY() +{ + --y; + + status.zero = y ? 0u : 1u; + status.negative = (IsNegative(y)) ? 1u : 0u; +} + + +// --------------- JUMPS & CALLS --------------- // +void CPU::JMP() +{ + pc = fetchedAddress; +} + +void CPU::JSR() +{ + uint8_t pcMsb = ((pc - 1u) & 0xFF00u) >> 8u; + uint8_t pcLsb = (pc - 1u) & 0xFFu; + + StackPush(pcMsb); + StackPush(pcLsb); + + pc = fetchedAddress; +} + +void CPU::RTS() +{ + uint8_t pcLsb = StackPop(); + uint8_t pcMsb = StackPop(); + + uint16_t address = ComposeAddress(pcMsb, pcLsb) + 1u; + + pc = address; +} + + +// --------------- LOAD/STORE --------------- // +void CPU::LDA() +{ + cycles += (pageBoundaryCrossed) ? 1u : 0u; + + acc = fetchedByte; + + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + +void CPU::LDX() +{ + cycles += (pageBoundaryCrossed) ? 1u : 0u; + + x = fetchedByte; + + status.zero = x ? 0u : 1u; + status.negative = (IsNegative(x)) ? 1u : 0u; +} + +void CPU::LDY() +{ + cycles += (pageBoundaryCrossed) ? 1u : 0u; + + y = fetchedByte; + + status.zero = y ? 0u : 1u; + status.negative = (IsNegative(y)) ? 1u : 0u; +} + +void CPU::STA() +{ + WriteMemory(fetchedAddress, acc); +} + +void CPU::STX() +{ + WriteMemory(fetchedAddress, x); +} + +void CPU::STY() +{ + WriteMemory(fetchedAddress, y); +} + + +// --------------- LOGICAL --------------- // +void CPU::AND() +{ + acc &= fetchedByte; + + cycles += (pageBoundaryCrossed) ? 1u : 0u; + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + +void CPU::EOR() +{ + acc ^= fetchedByte; + + cycles += (pageBoundaryCrossed) ? 1u : 0u; + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + +void CPU::ORA() +{ + acc |= fetchedByte; + + cycles += (pageBoundaryCrossed) ? 1u : 0u; + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + +void CPU::BIT() +{ + status.zero = (acc & fetchedByte) ? 0u : 1u; + status.overflow = (TestBits(fetchedByte, (1u << 6u))) ? 1u : 0u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; +} + + +// --------------- SHIFTS --------------- // +void CPU::ASL() +{ + // Set carry to fetchedByte of MSB pre-shift + if (TestBits(fetchedByte, 0x80)) + { + status.carry = 1u; + } + else + { + status.carry = 0u; + } + + fetchedByte <<= 1u; + + status.zero = fetchedByte ? 0u : 1u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; + + if (instructions[opcode].fetch == &CPU::Implicit) + { + acc = fetchedByte; + } + else + { + WriteMemory(fetchedAddress, fetchedByte); + } +} + +void CPU::LSR() +{ + // Set carry to fetchedByte of LSB pre-shift + if (TestBits(fetchedByte, 0x01)) + { + status.carry = 1u; + } + else + { + status.carry = 0u; + } + + fetchedByte >>= 1u; + + status.zero = fetchedByte ? 0u : 1u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; + + if (instructions[opcode].fetch == &CPU::Implicit) + { + acc = fetchedByte; + } + else + { + WriteMemory(fetchedAddress, fetchedByte); + } +} + +void CPU::ROL() +{ + uint8_t oldCarry = status.carry; + + // Set carry to fetchedByte of MSB pre-shift + if (TestBits(fetchedByte, 0x80)) + { + status.carry = 1u; + } + else + { + status.carry = 0u; + } + + fetchedByte <<= 1u; + + // Put old carry at LSB + if (oldCarry) + { + fetchedByte |= 0x1u; + } + + status.zero = fetchedByte ? 0u : 1u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; + + if (instructions[opcode].fetch == &CPU::Implicit) + { + acc = fetchedByte; + } + else + { + WriteMemory(fetchedAddress, fetchedByte); + } +} + +void CPU::ROR() +{ + uint8_t oldCarry = status.carry; + + // Set carry to fetchedByte of LSB pre-shift + if (TestBits(fetchedByte, 0x01)) + { + status.carry = 1u; + } + else + { + status.carry = 0u; + } + + fetchedByte >>= 1u; + + // Put old carry at MSB + if (oldCarry) + { + fetchedByte |= 0x80u; + } + + status.zero = fetchedByte ? 0u : 1u; + status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; + + if (instructions[opcode].fetch == &CPU::Implicit) + { + acc = fetchedByte; + } + else + { + WriteMemory(fetchedAddress, fetchedByte); + } +} + + +// --------------- STACK --------------- // +void CPU::PHP() +{ + Status statusCopy = status; + statusCopy.b1 = 1u; + statusCopy.b0 = 1u; + + StackPush(statusCopy.GetByte()); +} + +void CPU::PLP() +{ + uint8_t stackCopy = StackPop(); + + status.SetByte(stackCopy); + status.b1 = 1u; + status.b0 = 0u; +} + +void CPU::PHA() +{ + StackPush(acc); +} + +void CPU::PLA() +{ + acc = StackPop(); + + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + + +// --------------- STATUS FLAG CHANGES --------------- // +void CPU::CLC() +{ + status.carry = 0u; +} + +void CPU::SEC() +{ + status.carry = 1u; +} + +void CPU::CLI() +{ + status.interruptDisable = 0u; +} + +void CPU::SEI() +{ + status.interruptDisable = 1u; +} + +void CPU::CLV() +{ + status.overflow = 0u; +} + +void CPU::CLD() +{ + status.decimal = 0u; +} + +void CPU::SED() +{ + status.decimal = 1u; +} + + +// --------------- SYSTEM --------------- // +void CPU::BRK() +{ + uint8_t pcMsb = (pc & 0xFF00u) >> 8u; + uint8_t pcLsb = pc & 0xFFu; + + StackPush(pcMsb); + StackPush(pcLsb); + + Status statusCopy = status; + statusCopy.b1 = 1u; + statusCopy.b0 = 0u; + + StackPush(statusCopy.GetByte()); + + pcMsb = ReadMemory(IRQ_VECTOR_MSB); + pcLsb = ReadMemory(IRQ_VECTOR_LSB); + + pc = ComposeAddress(pcMsb, pcLsb); +} + +void CPU::NOP() +{ + // Do nothing + cycles += (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::RTI() +{ + uint8_t statusCopy = StackPop(); + + status.SetByte(statusCopy); + status.b1 = 1u; + + uint8_t pcLsb = StackPop(); + uint8_t pcMsb = StackPop(); + + pc = ComposeAddress(pcMsb, pcLsb); +} + + +// --------------- TRANSFERS --------------- // +void CPU::TXA() +{ + acc = x; + + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + +void CPU::TYA() +{ + acc = y; + + status.zero = acc ? 0u : 1u; + status.negative = (IsNegative(acc)) ? 1u : 0u; +} + +void CPU::TXS() +{ + sp = x; +} + +void CPU::TAY() +{ + y = acc; + + status.zero = y ? 0u : 1u; + status.negative = (IsNegative(y)) ? 1u : 0u; +} + +void CPU::TAX() +{ + x = acc; + + status.zero = x ? 0u : 1u; + status.negative = (IsNegative(x)) ? 1u : 0u; +} + +void CPU::TSX() +{ + x = sp; + + status.zero = x ? 0u : 1u; + status.negative = (IsNegative(x)) ? 1u : 0u; +} + + +// --------------- UNOFFICIAL --------------- // +void CPU::LAX() +{ + LDA(); + TAX(); +} + +void CPU::SAX() +{ + uint8_t byte = acc & x; + + WriteMemory(fetchedAddress, byte); +} + +void CPU::DCP() +{ + DEC(); + CMP(); + + cycles -= (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::ISC() +{ + INC(); + SBC(); + + cycles -= (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::SLO() +{ + ASL(); + ORA(); + + cycles -= (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::RLA() +{ + ROL(); + AND(); + + cycles -= (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::SRE() +{ + LSR(); + EOR(); + + cycles -= (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::RRA() +{ + ROR(); + ADC(); + + cycles -= (pageBoundaryCrossed) ? 1u : 0u; +} + +void CPU::ANC() +{ + AND(); + + status.carry = (fetchedByte & 0x80u) >> 7u; +} + +void CPU::ALR() +{ + AND(); + LSR(); +} + +void CPU::ARR() +{ + AND(); + ROR(); + + status.carry = (fetchedByte & 0x40u) >> 6u; + status.overflow = ((fetchedByte & 0x40u) >> 6u) ^ ((fetchedByte & 0x20u) >> 5u); +} + +void CPU::AXS() +{ + x = (acc & x) - fetchedByte; + + status.negative = (IsNegative(x)) ? 1u : 0u; + status.zero = x ? 0u : 1u; + status.carry = (x & 0xFF00u) ? 1u : 0u; +} diff --git a/Source/Main.cpp b/Source/Main.cpp new file mode 100644 index 0000000..e9407ab --- /dev/null +++ b/Source/Main.cpp @@ -0,0 +1,21 @@ +#include +#include "CPU/CPU.hpp" + + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Usage: " << argv[0] << "\n"; + std::exit(EXIT_FAILURE); + } + + CPU cpu; + + cpu.LoadRom("nestest.nes"); + + while (true) + { + cpu.Cycle(); + } +}