From b1265da7d3e1cf19bd341f596767bc9d4821d013 Mon Sep 17 00:00:00 2001 From: Austin Morlan Date: Fri, 6 Sep 2019 16:58:57 -0700 Subject: [PATCH] PPU (inaccurate) --- CMakeLists.txt | 5 + Source/CPU/AddressModes.cpp | 63 +-- Source/CPU/CPU.cpp | 805 ++++++++++++++++++------------------ Source/CPU/CPU.hpp | 31 +- Source/CPU/Instructions.cpp | 25 +- Source/Cartridge.cpp | 58 +++ Source/Cartridge.hpp | 18 + Source/Main.cpp | 13 +- Source/NES.cpp | 329 +++++++++++++++ Source/NES.hpp | 52 +++ Source/PPU/PPU.cpp | 326 +++++++++++++++ Source/PPU/PPU.hpp | 54 +++ Source/PPU/Registers.hpp | 98 +++++ Source/Platform.cpp | 66 +++ Source/Platform.hpp | 22 + 15 files changed, 1490 insertions(+), 475 deletions(-) create mode 100644 Source/Cartridge.cpp create mode 100644 Source/Cartridge.hpp create mode 100644 Source/NES.cpp create mode 100644 Source/NES.hpp create mode 100644 Source/PPU/PPU.cpp create mode 100644 Source/PPU/PPU.hpp create mode 100644 Source/PPU/Registers.hpp create mode 100644 Source/Platform.cpp create mode 100644 Source/Platform.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 48be4b8..ab432ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,14 @@ add_executable(nes) target_sources( nes PRIVATE + Source/Cartridge.cpp Source/CPU/CPU.cpp Source/CPU/AddressModes.cpp Source/CPU/Instructions.cpp + Source/NES.cpp + Source/Platform.cpp + Source/PPU/PPU.cpp + Source/PPU/Registers.hpp Source/Main.cpp) target_compile_options( diff --git a/Source/CPU/AddressModes.cpp b/Source/CPU/AddressModes.cpp index c2b23b7..1925c95 100644 --- a/Source/CPU/AddressModes.cpp +++ b/Source/CPU/AddressModes.cpp @@ -1,4 +1,5 @@ #include "CPU.hpp" +#include "NES.hpp" void CPU::Implicit() @@ -14,56 +15,56 @@ void CPU::Immediate() pc += 2; fetchedAddress = pc - 1; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::ZeroPage() { pc += 2; - fetchedAddress = ReadMemory(pc - 1); - fetchedByte = ReadMemory(fetchedAddress); + fetchedAddress = nes->Read(BusSource::CPU, pc - 1); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::ZeroPageX() { pc += 2; - uint8_t operand = ReadMemory(pc - 1); + uint8_t operand = nes->Read(BusSource::CPU, pc - 1); fetchedAddress = (operand + x) & 0xFFu; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::ZeroPageY() { pc += 2; - uint8_t operand = ReadMemory(pc - 1); + uint8_t operand = nes->Read(BusSource::CPU, pc - 1); fetchedAddress = (operand + y) & 0xFFu; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::Absolute() { pc += 3; - uint8_t addressLsb = ReadMemory(pc - 2); - uint8_t addressMsb = ReadMemory(pc - 1); + uint8_t addressLsb = nes->Read(BusSource::CPU, pc - 2); + uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1); fetchedAddress = ComposeAddress(addressMsb, addressLsb); - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::AbsoluteX() { pc += 3; - uint8_t addressLsb = ReadMemory(pc - 2); - uint8_t addressMsb = ReadMemory(pc - 1); + uint8_t addressLsb = nes->Read(BusSource::CPU, pc - 2); + uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1); uint16_t preAddress = ComposeAddress(addressMsb, addressLsb); fetchedAddress = preAddress + x; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress); } @@ -72,12 +73,12 @@ void CPU::AbsoluteY() { pc += 3; - uint8_t addressLsb = ReadMemory(pc - 2); - uint8_t addressMsb = ReadMemory(pc - 1); + uint8_t addressLsb = nes->Read(BusSource::CPU, pc - 2); + uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1); uint16_t preAddress = ComposeAddress(addressMsb, addressLsb); fetchedAddress = preAddress + y; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress); } @@ -86,52 +87,52 @@ void CPU::Indirect() { pc += 3; - uint8_t indirectLsb = ReadMemory(pc - 2); - uint8_t indirectMsb = ReadMemory(pc - 1); + uint8_t indirectLsb = nes->Read(BusSource::CPU, pc - 2); + uint8_t indirectMsb = nes->Read(BusSource::CPU, pc - 1); // NOTE: 6502 BUG - indirect address wraps // $02FF + 1 = $0200 uint16_t addressIndirect = ComposeAddress(indirectMsb, indirectLsb); - uint8_t addressLsb = ReadMemory(addressIndirect); + uint8_t addressLsb = nes->Read(BusSource::CPU, addressIndirect); // Increment LSB to read second byte - it will wrap ++indirectLsb; addressIndirect = ComposeAddress(indirectMsb, indirectLsb); - uint8_t addressMsb = ReadMemory(addressIndirect); + uint8_t addressMsb = nes->Read(BusSource::CPU, addressIndirect); fetchedAddress = ComposeAddress(addressMsb, addressLsb); - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::IndirectX() { pc += 2; - uint8_t addressIndirect = ReadMemory(pc - 1) + x; + uint8_t addressIndirect = nes->Read(BusSource::CPU, pc - 1) + x; // Modulo to keep within zero-page - uint16_t addressLsb = ReadMemory((addressIndirect % 0x100)); - uint16_t addressMsb = ReadMemory((addressIndirect + 1) % 0x100); + uint16_t addressLsb = nes->Read(BusSource::CPU, (addressIndirect % 0x100)); + uint16_t addressMsb = nes->Read(BusSource::CPU, (addressIndirect + 1) % 0x100); fetchedAddress = ComposeAddress(addressMsb, addressLsb); - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); } void CPU::IndirectY() { pc += 2; - uint8_t addressIndirect = ReadMemory(pc - 1); + uint8_t addressIndirect = nes->Read(BusSource::CPU, pc - 1); // Modulo to keep within zero-page - uint8_t addressLsb = ReadMemory(addressIndirect % 0x100); - uint8_t addressMsb = ReadMemory((addressIndirect + 1) % 0x100); + uint8_t addressLsb = nes->Read(BusSource::CPU, addressIndirect % 0x100); + uint8_t addressMsb = nes->Read(BusSource::CPU, (addressIndirect + 1) % 0x100); uint16_t preAddress = ComposeAddress(addressMsb, addressLsb); fetchedAddress = preAddress + y; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress); } @@ -140,12 +141,12 @@ void CPU::Relative() { pc += 2; - int8_t operand = ReadMemory(pc - 1); + int8_t operand = nes->Read(BusSource::CPU, pc - 1); uint16_t oldPc = pc; fetchedAddress = pc + operand; - fetchedByte = ReadMemory(fetchedAddress); + fetchedByte = nes->Read(BusSource::CPU, fetchedAddress); pageBoundaryCrossed = IsPageBoundaryCrossed(oldPc, fetchedAddress); } diff --git a/Source/CPU/CPU.cpp b/Source/CPU/CPU.cpp index 3d86b86..fb93ce6 100644 --- a/Source/CPU/CPU.cpp +++ b/Source/CPU/CPU.cpp @@ -1,314 +1,333 @@ #include +#include #include "CPU.hpp" +#include "NES.hpp" const unsigned int STACK_START_ADDRESS = 0x100u; +const unsigned int NMI_VECTOR_LSB = 0xFFFAu; +const unsigned int NMI_VECTOR_MSB = 0xFFFBu; +const unsigned int RESET_VECTOR_LSB = 0xFFFCu; +const unsigned int RESET_VECTOR_MSB = 0xFFFDu; 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[0xA9] = Instruction{.name = "LDA", .cycles = 2, .execute = &CPU::LDA, .fetch = &CPU::Immediate}; + instructions[0xA5] = Instruction{.name = "LDA", .cycles = 3, .execute = &CPU::LDA, .fetch = &CPU::ZeroPage}; + instructions[0xB5] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::ZeroPageX}; + instructions[0xAD] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::Absolute}; + instructions[0xBD] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteX}; + instructions[0xB9] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteY}; + instructions[0xA1] = Instruction{.name = "LDA", .cycles = 6, .execute = &CPU::LDA, .fetch = &CPU::IndirectX}; + instructions[0xB1] = Instruction{.name = "LDA", .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[0xA2] = Instruction{.name = "LDX", .cycles = 2, .execute = &CPU::LDX, .fetch = &CPU::Immediate}; + instructions[0xA6] = Instruction{.name = "LDX", .cycles = 3, .execute = &CPU::LDX, .fetch = &CPU::ZeroPage}; + instructions[0xB6] = Instruction{.name = "LDX", .cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::ZeroPageY}; + instructions[0xAE] = Instruction{.name = "LDX", .cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::Absolute}; + instructions[0xBE] = Instruction{.name = "LDX", .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[0xA0] = Instruction{.name = "LDY", .cycles = 2, .execute = &CPU::LDY, .fetch = &CPU::Immediate}; + instructions[0xA4] = Instruction{.name = "LDY", .cycles = 3, .execute = &CPU::LDY, .fetch = &CPU::ZeroPage}; + instructions[0xB4] = Instruction{.name = "LDY", .cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::ZeroPageX}; + instructions[0xAC] = Instruction{.name = "LDY", .cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::Absolute}; + instructions[0xBC] = Instruction{.name = "LDY", .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[0x85] = Instruction{.name = "STA", .cycles = 3, .execute = &CPU::STA, .fetch = &CPU::ZeroPage}; + instructions[0x95] = Instruction{.name = "STA", .cycles = 4, .execute = &CPU::STA, .fetch = &CPU::ZeroPageX}; + instructions[0x8D] = Instruction{.name = "STA", .cycles = 4, .execute = &CPU::STA, .fetch = &CPU::Absolute}; + instructions[0x9D] = Instruction{.name = "STA", .cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteX}; + instructions[0x99] = Instruction{.name = "STA", .cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteY}; + instructions[0x81] = Instruction{.name = "STA", .cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectX}; + instructions[0x91] = Instruction{.name = "STA", .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[0x86] = Instruction{.name = "STX", .cycles = 3, .execute = &CPU::STX, .fetch = &CPU::ZeroPage}; + instructions[0x96] = Instruction{.name = "STX", .cycles = 4, .execute = &CPU::STX, .fetch = &CPU::ZeroPageY}; + instructions[0x8E] = Instruction{.name = "STX", .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[0x84] = Instruction{.name = "STY", .cycles = 3, .execute = &CPU::STY, .fetch = &CPU::ZeroPage}; + instructions[0x94] = Instruction{.name = "STY", .cycles = 4, .execute = &CPU::STY, .fetch = &CPU::ZeroPageX}; + instructions[0x8C] = Instruction{.name = "STY", .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[0xAA] = Instruction{.name = "TAX", .cycles = 2, .execute = &CPU::TAX, .fetch = &CPU::Implicit}; + instructions[0xA8] = Instruction{.name = "TAY", .cycles = 2, .execute = &CPU::TAY, .fetch = &CPU::Implicit}; + instructions[0x8A] = Instruction{.name = "TXA", .cycles = 2, .execute = &CPU::TXA, .fetch = &CPU::Implicit}; + instructions[0x98] = Instruction{.name = "TYA", .cycles = 2, .execute = &CPU::TYA, .fetch = &CPU::Implicit}; + instructions[0xBA] = Instruction{.name = "TSX", .cycles = 2, .execute = &CPU::TSX, .fetch = &CPU::Implicit}; + instructions[0x9A] = Instruction{.name = "TXS", .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[0x48] = Instruction{.name = "PHA", .cycles = 3, .execute = &CPU::PHA, .fetch = &CPU::Implicit}; + instructions[0x08] = Instruction{.name = "PHP", .cycles = 3, .execute = &CPU::PHP, .fetch = &CPU::Implicit}; + instructions[0x68] = Instruction{.name = "PLA", .cycles = 4, .execute = &CPU::PLA, .fetch = &CPU::Implicit}; + instructions[0x28] = Instruction{.name = "PLP", .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[0x29] = Instruction{.name = "AND", .cycles = 2, .execute = &CPU::AND, .fetch = &CPU::Immediate}; + instructions[0x25] = Instruction{.name = "AND", .cycles = 3, .execute = &CPU::AND, .fetch = &CPU::ZeroPage}; + instructions[0x35] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::ZeroPageX}; + instructions[0x2D] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::Absolute}; + instructions[0x3D] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteX}; + instructions[0x39] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteY}; + instructions[0x21] = Instruction{.name = "AND", .cycles = 6, .execute = &CPU::AND, .fetch = &CPU::IndirectX}; + instructions[0x31] = Instruction{.name = "AND", .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[0x49] = Instruction{.name = "EOR", .cycles = 2, .execute = &CPU::EOR, .fetch = &CPU::Immediate}; + instructions[0x45] = Instruction{.name = "EOR", .cycles = 3, .execute = &CPU::EOR, .fetch = &CPU::ZeroPage}; + instructions[0x55] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::ZeroPageX}; + instructions[0x4D] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::Absolute}; + instructions[0x5D] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteX}; + instructions[0x59] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteY}; + instructions[0x41] = Instruction{.name = "EOR", .cycles = 6, .execute = &CPU::EOR, .fetch = &CPU::IndirectX}; + instructions[0x51] = Instruction{.name = "EOR", .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[0x09] = Instruction{.name = "ORA", .cycles = 2, .execute = &CPU::ORA, .fetch = &CPU::Immediate}; + instructions[0x05] = Instruction{.name = "ORA", .cycles = 3, .execute = &CPU::ORA, .fetch = &CPU::ZeroPage}; + instructions[0x15] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::ZeroPageX}; + instructions[0x0D] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::Absolute}; + instructions[0x1D] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteX}; + instructions[0x19] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteY}; + instructions[0x01] = Instruction{.name = "ORA", .cycles = 6, .execute = &CPU::ORA, .fetch = &CPU::IndirectX}; + instructions[0x11] = Instruction{.name = "ORA", .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[0x24] = Instruction{.name = "BIT", .cycles = 3, .execute = &CPU::BIT, .fetch = &CPU::ZeroPage}; + instructions[0x2C] = Instruction{.name = "BIT", .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[0x69] = Instruction{.name = "ADC", .cycles = 2, .execute = &CPU::ADC, .fetch = &CPU::Immediate}; + instructions[0x65] = Instruction{.name = "ADC", .cycles = 3, .execute = &CPU::ADC, .fetch = &CPU::ZeroPage}; + instructions[0x75] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::ZeroPageX}; + instructions[0x6D] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::Absolute}; + instructions[0x7D] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteX}; + instructions[0x79] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteY}; + instructions[0x61] = Instruction{.name = "ADC", .cycles = 6, .execute = &CPU::ADC, .fetch = &CPU::IndirectX}; + instructions[0x71] = Instruction{.name = "ADC", .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[0xE9] = Instruction{.name = "SBC", .cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate}; + instructions[0xE5] = Instruction{.name = "SBC", .cycles = 3, .execute = &CPU::SBC, .fetch = &CPU::ZeroPage}; + instructions[0xF5] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::ZeroPageX}; + instructions[0xED] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::Absolute}; + instructions[0xFD] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteX}; + instructions[0xF9] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteY}; + instructions[0xE1] = Instruction{.name = "SBC", .cycles = 6, .execute = &CPU::SBC, .fetch = &CPU::IndirectX}; + instructions[0xF1] = Instruction{.name = "SBC", .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[0xC9] = Instruction{.name = "CMP", .cycles = 2, .execute = &CPU::CMP, .fetch = &CPU::Immediate}; + instructions[0xC5] = Instruction{.name = "CMP", .cycles = 3, .execute = &CPU::CMP, .fetch = &CPU::ZeroPage}; + instructions[0xD5] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::ZeroPageX}; + instructions[0xCD] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::Absolute}; + instructions[0xDD] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteX}; + instructions[0xD9] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteY}; + instructions[0xC1] = Instruction{.name = "CMP", .cycles = 6, .execute = &CPU::CMP, .fetch = &CPU::IndirectX}; + instructions[0xD1] = Instruction{.name = "CMP", .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[0xE0] = Instruction{.name = "CPX", .cycles = 2, .execute = &CPU::CPX, .fetch = &CPU::Immediate}; + instructions[0xE4] = Instruction{.name = "CPX", .cycles = 3, .execute = &CPU::CPX, .fetch = &CPU::ZeroPage}; + instructions[0xEC] = Instruction{.name = "CPX", .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[0xC0] = Instruction{.name = "CPY", .cycles = 2, .execute = &CPU::CPY, .fetch = &CPU::Immediate}; + instructions[0xC4] = Instruction{.name = "CPY", .cycles = 3, .execute = &CPU::CPY, .fetch = &CPU::ZeroPage}; + instructions[0xCC] = Instruction{.name = "CPY", .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[0xE6] = Instruction{.name = "INC", .cycles = 5, .execute = &CPU::INC, .fetch = &CPU::ZeroPage}; + instructions[0xF6] = Instruction{.name = "INC", .cycles = 6, .execute = &CPU::INC, .fetch = &CPU::ZeroPageX}; + instructions[0xEE] = Instruction{.name = "INC", .cycles = 6, .execute = &CPU::INC, .fetch = &CPU::Absolute}; + instructions[0xFE] = Instruction{.name = "INC", .cycles = 7, .execute = &CPU::INC, .fetch = &CPU::AbsoluteX}; - instructions[0xE8] = Instruction{.cycles = 2, .execute = &CPU::INX, .fetch = &CPU::Implicit}; + instructions[0xE8] = Instruction{.name = "INX", .cycles = 2, .execute = &CPU::INX, .fetch = &CPU::Implicit}; - instructions[0xC8] = Instruction{.cycles = 2, .execute = &CPU::INY, .fetch = &CPU::Implicit}; + instructions[0xC8] = Instruction{.name = "INY", .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[0xC6] = Instruction{.name = "DEC", .cycles = 5, .execute = &CPU::DEC, .fetch = &CPU::ZeroPage}; + instructions[0xD6] = Instruction{.name = "DEC", .cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::ZeroPageX}; + instructions[0xCE] = Instruction{.name = "DEC", .cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::Absolute}; + instructions[0xDE] = Instruction{.name = "DEC", .cycles = 7, .execute = &CPU::DEC, .fetch = &CPU::AbsoluteX}; - instructions[0xCA] = Instruction{.cycles = 2, .execute = &CPU::DEX, .fetch = &CPU::Implicit}; + instructions[0xCA] = Instruction{.name = "DEX", .cycles = 2, .execute = &CPU::DEX, .fetch = &CPU::Implicit}; - instructions[0x88] = Instruction{.cycles = 2, .execute = &CPU::DEY, .fetch = &CPU::Implicit}; + instructions[0x88] = Instruction{.name = "DEY", .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[0x0A] = Instruction{.name = "ASL", .cycles = 2, .execute = &CPU::ASL, .fetch = &CPU::Implicit}; + instructions[0x06] = Instruction{.name = "ASL", .cycles = 5, .execute = &CPU::ASL, .fetch = &CPU::ZeroPage}; + instructions[0x16] = Instruction{.name = "ASL", .cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::ZeroPageX}; + instructions[0x0E] = Instruction{.name = "ASL", .cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::Absolute}; + instructions[0x1E] = Instruction{.name = "ASL", .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[0x4A] = Instruction{.name = "LSR", .cycles = 2, .execute = &CPU::LSR, .fetch = &CPU::Implicit}; + instructions[0x46] = Instruction{.name = "LSR", .cycles = 5, .execute = &CPU::LSR, .fetch = &CPU::ZeroPage}; + instructions[0x56] = Instruction{.name = "LSR", .cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::ZeroPageX}; + instructions[0x4E] = Instruction{.name = "LSR", .cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::Absolute}; + instructions[0x5E] = Instruction{.name = "LSR", .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[0x2A] = Instruction{.name = "ROL", .cycles = 2, .execute = &CPU::ROL, .fetch = &CPU::Implicit}; + instructions[0x26] = Instruction{.name = "ROL", .cycles = 5, .execute = &CPU::ROL, .fetch = &CPU::ZeroPage}; + instructions[0x36] = Instruction{.name = "ROL", .cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::ZeroPageX}; + instructions[0x2E] = Instruction{.name = "ROL", .cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::Absolute}; + instructions[0x3E] = Instruction{.name = "ROL", .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[0x6A] = Instruction{.name = "ROR", .cycles = 2, .execute = &CPU::ROR, .fetch = &CPU::Implicit}; + instructions[0x66] = Instruction{.name = "ROR", .cycles = 5, .execute = &CPU::ROR, .fetch = &CPU::ZeroPage}; + instructions[0x76] = Instruction{.name = "ROR", .cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::ZeroPageX}; + instructions[0x6E] = Instruction{.name = "ROR", .cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::Absolute}; + instructions[0x7E] = Instruction{.name = "ROR", .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[0x4C] = Instruction{.name = "JMP", .cycles = 3, .execute = &CPU::JMP, .fetch = &CPU::Absolute}; + instructions[0x6C] = Instruction{.name = "JMP", .cycles = 5, .execute = &CPU::JMP, .fetch = &CPU::Indirect}; - instructions[0x20] = Instruction{.cycles = 6, .execute = &CPU::JSR, .fetch = &CPU::Absolute}; + instructions[0x20] = Instruction{.name = "JSR", .cycles = 6, .execute = &CPU::JSR, .fetch = &CPU::Absolute}; - instructions[0x60] = Instruction{.cycles = 6, .execute = &CPU::RTS, .fetch = &CPU::Implicit}; + instructions[0x60] = Instruction{.name = "RTS", .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[0x90] = Instruction{.name = "BCC", .cycles = 2, .execute = &CPU::BCC, .fetch = &CPU::Relative}; + instructions[0xB0] = Instruction{.name = "BCS", .cycles = 2, .execute = &CPU::BCS, .fetch = &CPU::Relative}; + instructions[0xF0] = Instruction{.name = "BEQ", .cycles = 2, .execute = &CPU::BEQ, .fetch = &CPU::Relative}; + instructions[0x30] = Instruction{.name = "BMI", .cycles = 2, .execute = &CPU::BMI, .fetch = &CPU::Relative}; + instructions[0xD0] = Instruction{.name = "BNE", .cycles = 2, .execute = &CPU::BNE, .fetch = &CPU::Relative}; + instructions[0x10] = Instruction{.name = "BPL", .cycles = 2, .execute = &CPU::BPL, .fetch = &CPU::Relative}; + instructions[0x50] = Instruction{.name = "BVC", .cycles = 2, .execute = &CPU::BVC, .fetch = &CPU::Relative}; + instructions[0x70] = Instruction{.name = "BVS", .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[0x18] = Instruction{.name = "CLC", .cycles = 2, .execute = &CPU::CLC, .fetch = &CPU::Implicit}; + instructions[0xD8] = Instruction{.name = "CLD", .cycles = 2, .execute = &CPU::CLD, .fetch = &CPU::Implicit}; + instructions[0x58] = Instruction{.name = "CLI", .cycles = 2, .execute = &CPU::CLI, .fetch = &CPU::Implicit}; + instructions[0xB8] = Instruction{.name = "CLV", .cycles = 2, .execute = &CPU::CLV, .fetch = &CPU::Implicit}; + instructions[0x38] = Instruction{.name = "SEC", .cycles = 2, .execute = &CPU::SEC, .fetch = &CPU::Implicit}; + instructions[0xF8] = Instruction{.name = "SED", .cycles = 2, .execute = &CPU::SED, .fetch = &CPU::Implicit}; + instructions[0x78] = Instruction{.name = "SEI", .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[0x00] = Instruction{.name = "BRK", .cycles = 7, .execute = &CPU::BRK, .fetch = &CPU::Immediate}; + instructions[0xEA] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x40] = Instruction{.name = "RTI", .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[0x1A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x3A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x5A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x7A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xDA] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xFA] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x80] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0x82] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0x89] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0xC2] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0xE2] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate}; + instructions[0x04] = Instruction{.name = "NOP", .cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage}; + instructions[0x44] = Instruction{.name = "NOP", .cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage}; + instructions[0x64] = Instruction{.name = "NOP", .cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage}; + instructions[0x14] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x34] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x54] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x74] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0xD4] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0xF4] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX}; + instructions[0x0C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::Absolute}; + instructions[0x1C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0x3C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0x5C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0x7C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0xDC] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX}; + instructions[0xFC] = Instruction{.name = "NOP", .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[0x07] = Instruction{.name = "SLO", .cycles = 5, .execute = &CPU::SLO, .fetch = &CPU::ZeroPage}; + instructions[0x17] = Instruction{.name = "SLO", .cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::ZeroPageX}; + instructions[0x0F] = Instruction{.name = "SLO", .cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::Absolute}; + instructions[0x1F] = Instruction{.name = "SLO", .cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteX}; + instructions[0x1B] = Instruction{.name = "SLO", .cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteY}; + instructions[0x03] = Instruction{.name = "SLO", .cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectX}; + instructions[0x13] = Instruction{.name = "SLO", .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[0x27] = Instruction{.name = "RLA", .cycles = 5, .execute = &CPU::RLA, .fetch = &CPU::ZeroPage}; + instructions[0x37] = Instruction{.name = "RLA", .cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::ZeroPageX}; + instructions[0x2F] = Instruction{.name = "RLA", .cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::Absolute}; + instructions[0x3F] = Instruction{.name = "RLA", .cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteX}; + instructions[0x3B] = Instruction{.name = "RLA", .cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteY}; + instructions[0x23] = Instruction{.name = "RLA", .cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectX}; + instructions[0x33] = Instruction{.name = "RLA", .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[0x47] = Instruction{.name = "SRE", .cycles = 5, .execute = &CPU::SRE, .fetch = &CPU::ZeroPage}; + instructions[0x57] = Instruction{.name = "SRE", .cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::ZeroPageX}; + instructions[0x4F] = Instruction{.name = "SRE", .cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::Absolute}; + instructions[0x5F] = Instruction{.name = "SRE", .cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteX}; + instructions[0x5B] = Instruction{.name = "SRE", .cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteY}; + instructions[0x43] = Instruction{.name = "SRE", .cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectX}; + instructions[0x53] = Instruction{.name = "SRE", .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[0x67] = Instruction{.name = "RRA", .cycles = 5, .execute = &CPU::RRA, .fetch = &CPU::ZeroPage}; + instructions[0x77] = Instruction{.name = "RRA", .cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::ZeroPageX}; + instructions[0x6F] = Instruction{.name = "RRA", .cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::Absolute}; + instructions[0x7F] = Instruction{.name = "RRA", .cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteX}; + instructions[0x7B] = Instruction{.name = "RRA", .cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteY}; + instructions[0x63] = Instruction{.name = "RRA", .cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectX}; + instructions[0x73] = Instruction{.name = "RRA", .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[0x87] = Instruction{.name = "SAX", .cycles = 3, .execute = &CPU::SAX, .fetch = &CPU::ZeroPage}; + instructions[0x97] = Instruction{.name = "SAX", .cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::ZeroPageY}; + instructions[0x8F] = Instruction{.name = "SAX", .cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::Absolute}; + instructions[0x83] = Instruction{.name = "SAX", .cycles = 6, .execute = &CPU::SAX, .fetch = &CPU::IndirectX}; - instructions[0xEB] = Instruction{.cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate}; + instructions[0xEB] = Instruction{.name = "SBC", .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[0xAB] = Instruction{.name = "LAX", .cycles = 2, .execute = &CPU::LAX, .fetch = &CPU::Immediate}; + instructions[0xA7] = Instruction{.name = "LAX", .cycles = 3, .execute = &CPU::LAX, .fetch = &CPU::ZeroPage}; + instructions[0xB7] = Instruction{.name = "LAX", .cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::ZeroPageY}; + instructions[0xAF] = Instruction{.name = "LAX", .cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::Absolute}; + instructions[0xBF] = Instruction{.name = "LAX", .cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::AbsoluteY}; + instructions[0xA3] = Instruction{.name = "LAX", .cycles = 6, .execute = &CPU::LAX, .fetch = &CPU::IndirectX}; + instructions[0xB3] = Instruction{.name = "LAX", .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[0xC7] = Instruction{.name = "DCP", .cycles = 5, .execute = &CPU::DCP, .fetch = &CPU::ZeroPage}; + instructions[0xD7] = Instruction{.name = "DCP", .cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::ZeroPageX}; + instructions[0xCF] = Instruction{.name = "DCP", .cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::Absolute}; + instructions[0xDF] = Instruction{.name = "DCP", .cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteX}; + instructions[0xDB] = Instruction{.name = "DCP", .cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteY}; + instructions[0xC3] = Instruction{.name = "DCP", .cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectX}; + instructions[0xD3] = Instruction{.name = "DCP", .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[0xE7] = Instruction{.name = "ISC", .cycles = 5, .execute = &CPU::ISC, .fetch = &CPU::ZeroPage}; + instructions[0xF7] = Instruction{.name = "ISC", .cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::ZeroPageX}; + instructions[0xEF] = Instruction{.name = "ISC", .cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::Absolute}; + instructions[0xFF] = Instruction{.name = "ISC", .cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteX}; + instructions[0xFB] = Instruction{.name = "ISC", .cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteY}; + instructions[0xE3] = Instruction{.name = "ISC", .cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectX}; + instructions[0xF3] = Instruction{.name = "ISC", .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}; + instructions[0x0B] = Instruction{.name = "ANC", .cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate}; + instructions[0x2B] = Instruction{.name = "ANC", .cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate}; + instructions[0x4B] = Instruction{.name = "ALR", .cycles = 0, .execute = &CPU::ALR, .fetch = &CPU::Immediate}; + instructions[0x6B] = Instruction{.name = "ARR", .cycles = 0, .execute = &CPU::ARR, .fetch = &CPU::Immediate}; + instructions[0xCB] = Instruction{.name = "AXS", .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}; + instructions[0x9C] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x9E] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x8B] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x93] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x9B] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x9F] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xBB] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x02] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x12] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x22] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x32] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x42] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x52] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x62] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x72] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0x92] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xB2] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xD2] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; + instructions[0xF2] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit}; +} + +void CPU::Reset() +{ + uint8_t startAddressLsb = ReadMemory(RESET_VECTOR_LSB); + uint8_t startAddressMsb = ReadMemory(RESET_VECTOR_MSB); + uint16_t startAddress = ComposeAddress(startAddressMsb, startAddressLsb); + + pc = startAddress; + cycles = 7u; + status.SetByte(0x34u); + sp = 0xFDu; + memset(ram, 0, RAM_SIZE); } void CPU::StackPush(uint8_t val) @@ -329,166 +348,21 @@ uint8_t CPU::StackPop() 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() { + if (nes->nmi) + { + NMI(); + } + opcode = ReadMemory(pc); #ifndef NDEBUG - Log(opcode, pc); + //Log(); #endif pageBoundaryCrossed = false; + prevCycles = cycles; ((*this).*(instructions[opcode].fetch))(); @@ -497,11 +371,112 @@ void CPU::Cycle() cycles += instructions[opcode].cycles; } -void CPU::Log(uint8_t instruction, uint16_t pc) +void CPU::NMI() { + nes->nmi = false; + + 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(NMI_VECTOR_MSB); + pcLsb = ReadMemory(NMI_VECTOR_LSB); + + pc = ComposeAddress(pcMsb, pcLsb); +} + +void CPU::Log() +{ + Instruction& instruction = instructions[opcode]; + 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); + if (instruction.fetch == &CPU::Implicit) + { + printf("%02X %s ", ReadMemory(pc), instruction.name); + } + else if (instruction.fetch == &CPU::Immediate) + { + uint8_t op = ReadMemory(pc); + uint8_t operand = ReadMemory(pc + 1); + + printf("%02X %02X %s #$%02X ", op, operand, instruction.name, operand); + } + else if (instruction.fetch == &CPU::ZeroPage) + { + uint8_t op = ReadMemory(pc); + uint8_t operand = ReadMemory(pc + 1); + printf("%02X %02X %s $%02X ", op, operand, instruction.name, operand); + } + else if (instruction.fetch == &CPU::ZeroPageX) + { + uint8_t op = ReadMemory(pc); + uint8_t operand = ReadMemory(pc + 1); + printf("%02X %02X %s $%02X,X ", op, operand, instruction.name, operand); + } + else if (instruction.fetch == &CPU::ZeroPageY) + { + uint8_t op = ReadMemory(pc); + uint8_t operand = ReadMemory(pc + 1); + printf("%02X %02X %s $%02X,Y ", op, operand, instruction.name, operand); + } + else if (instruction.fetch == &CPU::IndirectX) + { + uint8_t op = ReadMemory(pc); + uint8_t operand = ReadMemory(pc + 1); + printf("%02X %02X %s ($%02X,X) ", op, operand, instruction.name, operand); + } + else if (instruction.fetch == &CPU::Relative) + { + uint8_t op = ReadMemory(pc); + int8_t operand = ReadMemory(pc + 1); + uint16_t address = pc + 2 + operand; + printf("%02X %02X %s $%04X ", op, (uint8_t)operand, instruction.name, address); + } + else if (instruction.fetch == &CPU::IndirectY) + { + uint8_t op = ReadMemory(pc); + uint8_t operand = ReadMemory(pc + 1); + printf("%02X %02X %s ($%02X),Y ", op, operand, instruction.name, operand); + } + else if (instruction.fetch == &CPU::Absolute) + { + uint8_t op = ReadMemory(pc); + uint8_t operand1 = ReadMemory(pc + 1); + uint8_t operand2 = ReadMemory(pc + 2); + printf("%02X %02X %02X %s $%02X%02X ", op, operand1, operand2, instruction.name, operand2, operand1); + } + else if (instruction.fetch == &CPU::Indirect) + { + uint8_t op = ReadMemory(pc); + uint8_t operand1 = ReadMemory(pc + 1); + uint8_t operand2 = ReadMemory(pc + 2); + printf("%02X %02X %02X %s ($%02X%02X) ", op, operand1, operand2, instruction.name, operand2, operand1); + } + else if (instruction.fetch == &CPU::AbsoluteX) + { + uint8_t op = ReadMemory(pc); + uint8_t operand1 = ReadMemory(pc + 1); + uint8_t operand2 = ReadMemory(pc + 2); + printf("%02X %02X %02X %s $%02X%02X,X ", op, operand1, operand2, instruction.name, operand2, operand1); + } + else if (instruction.fetch == &CPU::AbsoluteY) + { + uint8_t op = ReadMemory(pc); + uint8_t operand1 = ReadMemory(pc + 1); + uint8_t operand2 = ReadMemory(pc + 2); + printf("%02X %02X %02X %s $%02X%02X,Y ", op, operand1, operand2, instruction.name, operand2, operand1); + } + + printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X, CYC:%lu\n", acc, x, y, status.GetByte(), sp, cycles); } bool CPU::TestBits(uint8_t value, uint8_t bits) @@ -525,3 +500,13 @@ uint16_t CPU::ComposeAddress(uint8_t msb, uint8_t lsb) { return (msb << 8u) | lsb; } + +void CPU::WriteMemory(uint16_t address, uint8_t value) +{ + nes->Write(BusSource::CPU, address, value); +} + +uint8_t CPU::ReadMemory(uint16_t address) +{ + return nes->Read(BusSource::CPU, address); +} diff --git a/Source/CPU/CPU.hpp b/Source/CPU/CPU.hpp index 23e1530..3d24c89 100644 --- a/Source/CPU/CPU.hpp +++ b/Source/CPU/CPU.hpp @@ -7,13 +7,15 @@ 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 NES; class Status { + friend class NES; + public: void SetByte(uint8_t byte) { @@ -48,6 +50,7 @@ class CPU; class Instruction { public: + char const* name; uint8_t cycles; using CpuFunc = void (CPU::*)(); @@ -60,16 +63,18 @@ class CPU { public: CPU(); - void LoadRom(const char* filename); void Cycle(); - void Log(uint8_t instruction, uint16_t pc); + void Log(); + void Reset(); + void NMI(); -private: - void StackPush(uint8_t val); - uint8_t StackPop(); void WriteMemory(uint16_t address, uint8_t value); uint8_t ReadMemory(uint16_t address); + friend class NES; + + void StackPush(uint8_t val); + uint8_t StackPop(); // --------------- HELPERS --------------- // static bool TestBits(uint8_t value, uint8_t bits); @@ -219,17 +224,15 @@ private: // 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; + uint16_t pc{}; + uint8_t sp{}; uint8_t acc{}; uint8_t x{}; uint8_t y{}; Status status{}; - uint64_t cycles = 7; + uint8_t prevCycles{}; + uint64_t cycles{}; bool irq{}; - bool nmi{}; // Emulator variables bool pageBoundaryCrossed{}; @@ -238,6 +241,6 @@ private: uint8_t fetchedByte{}; Instruction instructions[INSTRUCTION_COUNT]{}; + + NES* nes; }; - - diff --git a/Source/CPU/Instructions.cpp b/Source/CPU/Instructions.cpp index 7abce46..de5d0ae 100644 --- a/Source/CPU/Instructions.cpp +++ b/Source/CPU/Instructions.cpp @@ -1,4 +1,5 @@ #include "CPU/CPU.hpp" +#include "NES.hpp" // --------------- ARITHMETIC --------------- // @@ -160,7 +161,7 @@ void CPU::INC() { ++fetchedByte; - WriteMemory(fetchedAddress, fetchedByte); + nes->Write(BusSource::CPU, fetchedAddress, fetchedByte); status.zero = fetchedByte ? 0u : 1u; status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; @@ -186,7 +187,7 @@ void CPU::DEC() { --fetchedByte; - WriteMemory(fetchedAddress, fetchedByte); + nes->Write(BusSource::CPU, fetchedAddress, fetchedByte); status.zero = fetchedByte ? 0u : 1u; status.negative = (IsNegative(fetchedByte)) ? 1u : 0u; @@ -270,17 +271,17 @@ void CPU::LDY() void CPU::STA() { - WriteMemory(fetchedAddress, acc); + nes->Write(BusSource::CPU, fetchedAddress, acc); } void CPU::STX() { - WriteMemory(fetchedAddress, x); + nes->Write(BusSource::CPU, fetchedAddress, x); } void CPU::STY() { - WriteMemory(fetchedAddress, y); + nes->Write(BusSource::CPU, fetchedAddress, y); } @@ -344,7 +345,7 @@ void CPU::ASL() } else { - WriteMemory(fetchedAddress, fetchedByte); + nes->Write(BusSource::CPU, fetchedAddress, fetchedByte); } } @@ -371,7 +372,7 @@ void CPU::LSR() } else { - WriteMemory(fetchedAddress, fetchedByte); + nes->Write(BusSource::CPU, fetchedAddress, fetchedByte); } } @@ -406,7 +407,7 @@ void CPU::ROL() } else { - WriteMemory(fetchedAddress, fetchedByte); + nes->Write(BusSource::CPU, fetchedAddress, fetchedByte); } } @@ -441,7 +442,7 @@ void CPU::ROR() } else { - WriteMemory(fetchedAddress, fetchedByte); + nes->Write(BusSource::CPU, fetchedAddress, fetchedByte); } } @@ -531,8 +532,8 @@ void CPU::BRK() StackPush(statusCopy.GetByte()); - pcMsb = ReadMemory(IRQ_VECTOR_MSB); - pcLsb = ReadMemory(IRQ_VECTOR_LSB); + pcMsb = nes->Read(BusSource::CPU, IRQ_VECTOR_MSB); + pcLsb = nes->Read(BusSource::CPU, IRQ_VECTOR_LSB); pc = ComposeAddress(pcMsb, pcLsb); } @@ -615,7 +616,7 @@ void CPU::SAX() { uint8_t byte = acc & x; - WriteMemory(fetchedAddress, byte); + nes->Write(BusSource::CPU, fetchedAddress, byte); } void CPU::DCP() diff --git a/Source/Cartridge.cpp b/Source/Cartridge.cpp new file mode 100644 index 0000000..ff8f08a --- /dev/null +++ b/Source/Cartridge.cpp @@ -0,0 +1,58 @@ +#include "Cartridge.hpp" +#include +#include +#include + + +const unsigned int HEADER_SIZE = 16u; + + +#pragma pack(push, 1) +struct Header +{ + uint8_t string[4]; + uint8_t prgSize; + uint8_t chrSize; + uint8_t flags6; + uint8_t flags7; + uint8_t flags8; + uint8_t flags9; + uint8_t flags10; + uint8_t padding[5]; +}; +#pragma pack(pop) + + +void Cartridge::Load(char const* filename) +{ + std::ifstream file(filename, std::ios::binary); + + if (file.is_open()) + { + char buf[HEADER_SIZE]; + file.read(buf, HEADER_SIZE); + + auto header = reinterpret_cast(buf); + + // Trainer + if (header->flags6 & 0x4) + { + char trainerBuf[512]; + file.read(trainerBuf, 512); + } + + uint32_t prgSize = header->prgSize * 16384; + prgROM.resize(prgSize); + file.read(reinterpret_cast(prgROM.data()), prgSize); + + uint32_t chrSize = header->chrSize * 8192; + + if (chrSize > 0) + { + chrROM.resize(chrSize); + file.read(reinterpret_cast(chrROM.data()), chrSize); + } + + file.close(); + } +} diff --git a/Source/Cartridge.hpp b/Source/Cartridge.hpp new file mode 100644 index 0000000..49f662d --- /dev/null +++ b/Source/Cartridge.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + + +class Cartridge +{ +public: + void Load(char const* filename); + +private: + friend class NES; + + std::vector prgROM; + std::vector chrROM; +}; + diff --git a/Source/Main.cpp b/Source/Main.cpp index e9407ab..4ee06a1 100644 --- a/Source/Main.cpp +++ b/Source/Main.cpp @@ -1,5 +1,5 @@ #include -#include "CPU/CPU.hpp" +#include "NES.hpp" int main(int argc, char** argv) @@ -10,12 +10,9 @@ int main(int argc, char** argv) std::exit(EXIT_FAILURE); } - CPU cpu; + char const* filename = argv[1]; - cpu.LoadRom("nestest.nes"); - - while (true) - { - cpu.Cycle(); - } + NES nes; + nes.InsertCartridge(filename); + nes.Run(); } diff --git a/Source/NES.cpp b/Source/NES.cpp new file mode 100644 index 0000000..155eeb5 --- /dev/null +++ b/Source/NES.cpp @@ -0,0 +1,329 @@ +#include "NES.hpp" +#include + + +const unsigned int VIDEO_SCALE = 4u; +const unsigned int WINDOW_WIDTH = VIDEO_WIDTH * VIDEO_SCALE; +const unsigned int WINDOW_HEIGHT = VIDEO_HEIGHT * VIDEO_SCALE; +const unsigned int VIDEO_PITCH = VIDEO_WIDTH * 4u; + + +void DrawDebugLines(uint32_t* buffer) +{ + for (int y = 0; y < VIDEO_HEIGHT; ++y) + { + for (int x = 0; x < VIDEO_WIDTH; ++x) + { + if (x == 0 || y == 0) + { + continue; + } + + if (x % 8 == 0u) + { + buffer[y * VIDEO_WIDTH + x] = 0xFF00FFFFu; + } + if (x % 32 == 0u) + { + buffer[y * VIDEO_WIDTH + x] = 0xFF0000FFu; + } + + if (y % 8 == 0u) + { + buffer[y * VIDEO_WIDTH + x] = 0xFF00FFFFu; + } + if (y % 32 == 0u) + { + buffer[y * VIDEO_WIDTH + x] = 0xFF0000FFu; + } + } + } +} + +NES::NES() + : cpu(std::make_unique()), + ppu(std::make_unique()), + cartridge(std::make_unique()), + platform(std::make_unique("NES", WINDOW_WIDTH, WINDOW_HEIGHT, VIDEO_WIDTH, VIDEO_HEIGHT)) +{ + ppu->nes = this; + + palette[0] = Color(84, 84, 84); + palette[1] = Color(0, 30, 116); + palette[2] = Color(8, 16, 144); + palette[3] = Color(48, 0, 136); + palette[4] = Color(68, 0, 100); + palette[5] = Color(92, 0, 48); + palette[6] = Color(84, 4, 0); + palette[7] = Color(60, 24, 0); + palette[8] = Color(32, 42, 0); + palette[9] = Color(8, 58, 0); + palette[10] = Color(0, 64, 0); + palette[11] = Color(0, 60, 0); + palette[12] = Color(0, 50, 60); + palette[13] = Color(0, 0, 0); + palette[14] = Color(0, 0, 0); + palette[15] = Color(0, 0, 0); + palette[16] = Color(152, 150, 152); + palette[17] = Color(8, 76, 196); + palette[18] = Color(48, 50, 236); + palette[19] = Color(92, 30, 228); + palette[20] = Color(136, 20, 176); + palette[21] = Color(160, 20, 100); + palette[22] = Color(152, 34, 32); + palette[23] = Color(120, 60, 0); + palette[24] = Color(84, 90, 0); + palette[25] = Color(40, 114, 0); + palette[26] = Color(8, 124, 0); + palette[27] = Color(0, 118, 40); + palette[28] = Color(0, 102, 120); + palette[29] = Color(0, 0, 0); + palette[30] = Color(0, 0, 0); + palette[31] = Color(0, 0, 0); + palette[32] = Color(236, 238, 236); + palette[33] = Color(76, 154, 236); + palette[34] = Color(120, 124, 236); + palette[35] = Color(176, 98, 236); + palette[36] = Color(228, 84, 236); + palette[37] = Color(236, 88, 180); + palette[38] = Color(236, 106, 100); + palette[39] = Color(212, 136, 32); + palette[40] = Color(160, 170, 0); + palette[41] = Color(116, 196, 0); + palette[42] = Color(76, 208, 32); + palette[43] = Color(56, 204, 108); + palette[44] = Color(56, 180, 204); + palette[45] = Color(60, 60, 60); + palette[46] = Color(0, 0, 0); + palette[47] = Color(0, 0, 0); + palette[48] = Color(236, 238, 236); + palette[49] = Color(168, 204, 236); + palette[50] = Color(188, 188, 236); + palette[51] = Color(212, 178, 236); + palette[52] = Color(236, 174, 236); + palette[53] = Color(236, 174, 212); + palette[54] = Color(236, 180, 176); + palette[55] = Color(228, 196, 144); + palette[56] = Color(204, 210, 120); + palette[57] = Color(180, 222, 120); + palette[58] = Color(168, 226, 144); + palette[59] = Color(152, 226, 180); + palette[60] = Color(160, 214, 228); + palette[61] = Color(160, 162, 160); + palette[62] = Color(0, 0, 0); + palette[63] = Color(0, 0, 0); +} + +void NES::InsertCartridge(char const* filename) +{ + cartridge->Load(filename); + cpu->nes = this; + cpu->Reset(); +} + +void NES::Run() +{ + while (!platform->ProcessInput()) + { + cpu->Cycle(); + + uint8_t cpuCyclesTaken = (cpu->cycles - cpu->prevCycles) * 3u; + + ppu->Cycle(cpuCyclesTaken); + + if (ppu->scanline == 260) + { + //DrawDebugLines(video); + platform->Update(ppu->video, VIDEO_PITCH); + } + } +} + +void NES::Write(BusSource source, uint16_t address, uint8_t value) +{ + switch (source) + { + case BusSource::CPU: + { + // CPU RAM and Mirrors + if (address < 0x2000u) + { + uint16_t index = address & 0x7FFu; + + cpu->ram[index] = value; + } + + // PPU Registers and Mirrors + else if (address < 0x4000u) + { + auto index = static_cast(address % 8u); + + ppu->WriteRegister(index, value); + } + + // APU and IO + else if (address < 0x4020u) + { + uint16_t index = address & 0x1Fu; + + if (index == 0x14u) + { + uint8_t addrMsb = value; + + for (uint16_t addrLsb = 0x00; addrLsb <= 0xFF; ++addrLsb) + { + uint16_t addr = (addrMsb << 8u) | addrLsb; + + ppu->oam[ppu->oamAddr + addrLsb] = Read(BusSource::CPU, addr); + } + } + } + + // Expansion I/O + else if (address < 0x6000u) + { + } + + // SRAM + else if (address < 0x8000u) + { + uint16_t index = address & 0x1FFFu; + + cpu->sram[index] = value; + } + + break; + } + + case BusSource::PPU: + { + // Name Tables and Attribute Tables + if (address > 0x2000u && address < 0x3000u) + { + uint16_t index = address & 0x3FFu; + + ppu->ram[index] = value; + } + + // Mirrors of $2000 - $2EFF + else if (address < 0x3F00u) + { + uint16_t index = address & 0xEFFu; + + ppu->ram[index] = value; + } + + // Palette RAM Indexes and Mirrors + else if (address < 0x3F20u) + { + uint8_t index = address & 0x1Fu; + + ppu->paletteIndexes[index] = value; + } + + break; + } + } +} + +uint8_t NES::Read(BusSource source, uint16_t address) +{ + uint8_t byte{}; + + switch (source) + { + case BusSource::CPU: + { + // CPU RAM and Mirrors + if (address < 0x2000u) + { + uint16_t index = address & 0x7FFu; + + byte = cpu->ram[index]; + } + + // PPU Registers and Mirrors + else if (address < 0x4000u) + { + auto index = static_cast(address % 8u); + + byte = ppu->ReadRegister(index); + } + + // APU and IO + else if (address < 0x4020u) + { + } + + // Expansion I/O + else if (address < 0x6000u) + { + } + + // SRAM + else if (address < 0x8000u) + { + uint16_t index = address & 0x1FFFu; + + byte = cpu->sram[index]; + } + + // CHR-ROM + else if (address < 0xC000u) + { + uint16_t index = address & 0x3FFFu; + + byte = cartridge->chrROM[index]; + } + + // PRG-ROM + else if (address < 0xFFFFu) + { + uint16_t index = address & 0x3FFFu; + + byte = cartridge->prgROM[index]; + } + + break; + } + + case BusSource::PPU: + { + // Cartridge ROM + if (address < 0x2000u) + { + uint16_t index = address & 0x1FFFu; + + byte = cartridge->chrROM[index]; + } + + // Name Tables and Attribute Tables + else if (address < 0x3000u) + { + uint16_t index = address & 0x3FFu; + + byte = ppu->ram[index]; + } + + // Mirrors of $2000 - $2EFF + else if (address < 0x3F00u) + { + uint16_t index = address & 0xEFFu; + + byte = ppu->ram[index]; + } + + // Palette RAM Indexes and Mirrors + else if (address < 0x3F20u) + { + uint8_t index = address & 0x1Fu; + + byte = ppu->paletteIndexes[index]; + } + + break; + } + } + + return byte; +} diff --git a/Source/NES.hpp b/Source/NES.hpp new file mode 100644 index 0000000..3924826 --- /dev/null +++ b/Source/NES.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "Cartridge.hpp" +#include "CPU/CPU.hpp" +#include "Platform.hpp" +#include "PPU/PPU.hpp" +#include + + +struct Color +{ + Color() + : r(0), g(0), b(0), a(0) + { + } + + Color(uint8_t r, uint8_t g, uint8_t b) + : r(r), g(g), b(b), a(255) + {} + + uint8_t r, g, b, a; + + uint32_t GetValue() + { + return (r << 24u) | (g << 16u) | (b << 8u) | a; + } +}; + +enum class BusSource +{ + CPU, + PPU +}; + +class NES +{ +public: + NES(); + void InsertCartridge(char const* filename); + void Run(); + void Write(BusSource source, uint16_t address, uint8_t value); + uint8_t Read(BusSource source, uint16_t address); + + std::unique_ptr cpu; + std::unique_ptr ppu; + std::unique_ptr cartridge; + std::unique_ptr platform; + + bool nmi{}; + Color palette[64]; +}; + diff --git a/Source/PPU/PPU.cpp b/Source/PPU/PPU.cpp new file mode 100644 index 0000000..1ed00a9 --- /dev/null +++ b/Source/PPU/PPU.cpp @@ -0,0 +1,326 @@ +#include "PPU.hpp" +#include "NES.hpp" + + +const unsigned int tilesPerWidth = 32u; +const unsigned int tilesPerHeight = 30u; + +unsigned char reverse(uint8_t b) +{ + b = (b & 0xF0u) >> 4u | (b & 0x0Fu) << 4u; + b = (b & 0xCCu) >> 2u | (b & 0x33u) << 2u; + b = (b & 0xAAu) >> 1u | (b & 0x55u) << 1u; + return b; +} + +void PPU::Cycle(uint8_t cpuCyclesElapsed) +{ + for (int cpuCycle = 0; cpuCycle < cpuCyclesElapsed; ++cpuCycle) + { + if (scanline < 240u && cycles < 256u) + { + // Draw BG + uint8_t bgTilePositionX = cycles / 8u; + uint8_t bgTilePositionY = scanline / 8u; + uint8_t bgBlockX = bgTilePositionX / 4u; + uint8_t bgBlockY = bgTilePositionY / 4u; + uint16_t blockAddress = (bgBlockY * 8 + bgBlockX); + uint8_t attribute = ReadMemory(0x23C0 + blockAddress); + + uint8_t paletteIndex[4] = { + static_cast(attribute & 0x03u), // Upper Left + static_cast((attribute & 0x0Cu) >> 2u), // Upper Right + static_cast((attribute & 0x30u) >> 4u), // Bottom Left + static_cast((attribute & 0xC0u) >> 6u), // Bottom Right + }; + + // Which quadrant of block are we in? + uint8_t blockX = (bgTilePositionX % 4u) / 2; + uint8_t blockY = (bgTilePositionY % 4u) / 2; + + uint8_t pIndex = paletteIndex[(blockY << 1u) | blockX]; + + uint8_t bgTileIndex = ram[bgTilePositionY * tilesPerWidth + bgTilePositionX]; + uint8_t bgPixelWithinTileX = cycles % 8u; + uint8_t bgPixelWithinTileY = scanline % 8u; + uint16_t bgTileAddress = (0x1000u | (bgTileIndex << 4u)) + bgPixelWithinTileY; + + int screenPixelX = (bgTilePositionX * 8) + bgPixelWithinTileX; + int screenPixelY = (bgTilePositionY * 8) + bgPixelWithinTileY; + + uint8_t bgPixelColorLsb = + (ReadMemory(bgTileAddress) & (1u << (7u - bgPixelWithinTileX))) >> (7u - bgPixelWithinTileX); + uint8_t bgPixelColorMsb = + (ReadMemory(bgTileAddress + 8) & (1u << (7u - bgPixelWithinTileX))) >> (7u - bgPixelWithinTileX); + + uint8_t bgPixelColor = bgPixelColorMsb << 1u | bgPixelColorLsb; + + uint8_t systemPaletteIndex = paletteIndexes[(4 * pIndex) + bgPixelColor]; + + uint32_t color = nes->palette[systemPaletteIndex].GetValue(); + + video[screenPixelY * VIDEO_WIDTH + screenPixelX] = color; + + // Draw sprites - lower index = higher priority + for (int sprite = 63; sprite >= 0; --sprite) + { + uint8_t spritePositionY = oam[4 * sprite + 0]; + uint8_t spriteTileIndex = oam[4 * sprite + 1]; + uint8_t spriteAttributes = oam[4 * sprite + 2]; + uint8_t spritePositionX = oam[4 * sprite + 3]; + + uint8_t spritePalette = spriteAttributes & 0x3u; + + bool spriteWithinScanline = (scanline >= spritePositionY) && (scanline < (spritePositionY + 8)); + bool spriteWithinCycle = (cycles >= spritePositionX) && (cycles < (spritePositionX + 8)); + + // Sprite on scanline + if (spriteWithinScanline && spriteWithinCycle) + { + uint8_t spritePixelWithinTileY = scanline - spritePositionY; + uint8_t spritePixelWithinTileX = cycles - spritePositionX; + uint16_t spriteTileAddress = (spriteTileIndex << 4u) + spritePixelWithinTileY; + + uint8_t pixelX = ((spritePositionX) + spritePixelWithinTileX) % 256; + uint8_t pixelY = ((spritePositionY) + spritePixelWithinTileY) % 240; + + uint8_t spriteLsb = ReadMemory(spriteTileAddress); + uint8_t spriteMsb = ReadMemory(spriteTileAddress + 8); + uint8_t spriteChr = spriteMsb | spriteLsb; + + // Flip horizontally + if (spriteAttributes & 0x40u) + { + spriteLsb = reverse(spriteLsb); + spriteMsb = reverse(spriteMsb); + } + + uint8_t spritePixelColorLsb = + (spriteLsb & (1u << (7u - spritePixelWithinTileX))) >> (7u - spritePixelWithinTileX); + uint8_t spritePixelColorMsb = + (spriteMsb & (1u << (7u - spritePixelWithinTileX))) >> (7u - spritePixelWithinTileX); + + uint8_t spritePixelColor = spritePixelColorMsb << 1u | spritePixelColorLsb; + + uint8_t spriteSystemPaletteIndex = paletteIndexes[0x10 + (4 * spritePalette) + spritePixelColor]; + + uint32_t spriteColor = nes->palette[spriteSystemPaletteIndex].GetValue(); + + // Do not draw empty pixels of sprite + if (spriteChr != 0u) + { + if (sprite == 0) + { + if (bgPixelColor != 0u) + { + ppuStatus.sprite0Hit = 1u; + } + } + + video[pixelY * VIDEO_WIDTH + pixelX] = spriteColor; + } + } + } + } + else if (scanline == 241u && cycles == 0u) + { + ppuStatus.vblankStarted = 1u; + + if (ppuCtrl.nmiEnable) + { + nes->nmi = true; + } + } + else if (scanline == 261u) + { + ppuStatus.sprite0Hit = 0u; + ppuStatus.vblankStarted = 0; + } + + ++cycles; + + if (cycles == 341u) + { + cycles = 0; + + ++scanline; + + if (scanline == 262u) + { + scanline = 0; + } + } + } +} + +void PPU::WriteRegister(PpuRegister reg, uint8_t value) +{ + switch (reg) + { + case PpuRegister::PPUCTRL: + { + ppuCtrl.SetByte(value); + regT |= (value & 0x3u) << 10u; + + break; + } + case PpuRegister::PPUMASK: + { + ppuMask.SetByte(value); + + break; + } + case PpuRegister::PPUSTATUS: + { + ppuStatus.SetByte(value); + regW = 0u; + + break; + } + case PpuRegister::OAMADDR: + { + oamAddr = value; + + break; + } + case PpuRegister::OAMDATA: + { + oamData = value; + + break; + } + case PpuRegister::PPUSCROLL: + { + ppuScroll = value; + + if (regW == 0u) + { + regT |= (value & 0xF8u) >> 3u; + regX = value & 0x07u; + + regW = 1u; + } + else if (regW == 1u) + { + regT |= (value & 0x07u) << 12u; + regT |= (value & 0x38u) << 2u; + regT |= (value & 0xC0u) << 2u; + + regW = 0u; + } + + break; + } + case PpuRegister::PPUADDR: + { + // MSB + if (!ppuAddrW) + { + ppuAddr = value << 8u; + + ppuAddrW = true; + } + + // LSB + else + { + ppuAddr |= value; + + ppuAddrW = false; + } + + if (regW == 0u) + { + regT |= (value & 0x3Fu) << 8u; + regT &= 0x7Fu; + regW = 1u; + } + else if (regW == 1u) + { + regT |= value; + regV = regT; + regW = 0u; + } + + break; + } + case PpuRegister::PPUDATA: + { + WriteMemory(ppuAddr, value); + + ppuAddr += (ppuCtrl.vramAddrIncrement) ? 32u : 1u; + + break; + } + } +} + +uint8_t PPU::ReadRegister(PpuRegister reg) +{ + uint8_t byte{}; + + switch (reg) + { + case PpuRegister::PPUCTRL: + { + byte = ppuCtrl.GetByte(); + + break; + } + case PpuRegister::PPUMASK: + { + byte = ppuMask.GetByte(); + + break; + } + case PpuRegister::PPUSTATUS: + { + byte = ppuStatus.GetByte(); + ppuStatus.vblankStarted = 0u; + + break; + } + case PpuRegister::OAMADDR: + { + byte = oamAddr; + + break; + } + case PpuRegister::OAMDATA: + { + byte = oamData; + + break; + } + case PpuRegister::PPUSCROLL: + { + byte = ppuScroll; + + break; + } + case PpuRegister::PPUADDR: + { + byte = ppuAddr; + + break; + } + case PpuRegister::PPUDATA: + { + byte = ppuData; + + break; + } + } + + return byte; +} + +void PPU::WriteMemory(uint16_t address, uint8_t value) +{ + nes->Write(BusSource::PPU, address, value); +} + +uint8_t PPU::ReadMemory(uint16_t address) +{ + return nes->Read(BusSource::PPU, address); +} diff --git a/Source/PPU/PPU.hpp b/Source/PPU/PPU.hpp new file mode 100644 index 0000000..fa3d262 --- /dev/null +++ b/Source/PPU/PPU.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "Registers.hpp" +#include + + +class NES; + + +const unsigned int VRAM_SIZE = 2048u; +const unsigned int VIDEO_WIDTH = 256u; +const unsigned int VIDEO_HEIGHT = 240u; + + +class PPU +{ +public: + void Cycle(uint8_t cpuCyclesElapsed); + void WriteRegister(PpuRegister reg, uint8_t value); + uint8_t ReadRegister(PpuRegister reg); + + void WriteMemory(uint16_t address, uint8_t value); + uint8_t ReadMemory(uint16_t address); + +private: + friend class NES; + + PpuRegisterCTRL ppuCtrl{}; + PpuRegisterMask ppuMask{}; + PpuRegisterStatus ppuStatus{}; + uint8_t oamAddr{}; + uint8_t oamData{}; + uint8_t oam[256]{}; + uint8_t ppuScroll{}; + uint16_t ppuAddr{}; + uint8_t ppuData{}; + + bool ppuAddrW{}; + + uint8_t regV{}; + uint8_t regT{}; + uint8_t regX{}; + uint8_t regW{}; + + uint16_t cycles{}; + uint16_t scanline{}; + + uint8_t ram[VRAM_SIZE]{}; + uint8_t paletteIndexes[32]{}; + + uint32_t video[VIDEO_WIDTH * VIDEO_HEIGHT]{}; + + NES* nes; +}; diff --git a/Source/PPU/Registers.hpp b/Source/PPU/Registers.hpp new file mode 100644 index 0000000..662af19 --- /dev/null +++ b/Source/PPU/Registers.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include + + +enum PpuRegister +{ + PPUCTRL = 0x00, + PPUMASK = 0x01, + PPUSTATUS = 0x02, + OAMADDR = 0x03, + OAMDATA = 0x04, + PPUSCROLL = 0x05, + PPUADDR = 0x06, + PPUDATA = 0x07, +}; + +struct PpuRegisterCTRL +{ + void SetByte(uint8_t byte) + { + nmiEnable = (byte & 0x80u) >> 7u; + masterSlaveSelect = (byte & 0x40u) >> 6u; + spriteSize = (byte & 0x20u) >> 5u; + bgPatternTableAddr = (byte & 0x10u) >> 4u; + spritePatternTableAddr = (byte & 0x08u) >> 3u; + vramAddrIncrement = (byte & 0x04u) >> 2u; + nametableBaseAddr = ((byte & 0x02u) >> 1u | (byte & 0x01u)); + } + + uint8_t GetByte() + { + return nmiEnable << 7u | masterSlaveSelect << 6u | spriteSize << 5u | bgPatternTableAddr << 4u + | spritePatternTableAddr << 3u | vramAddrIncrement << 2u | (nametableBaseAddr & 0xFF00u) << 1u + | (nametableBaseAddr & 0xFFu); + } + + uint16_t nametableBaseAddr; + uint8_t vramAddrIncrement; + uint8_t spritePatternTableAddr; + uint8_t bgPatternTableAddr; + uint8_t spriteSize; + uint8_t masterSlaveSelect; + uint8_t nmiEnable; +}; + +struct PpuRegisterMask +{ + void SetByte(uint8_t byte) + { + blueEmphasis = (byte & 0x80u) >> 7u; + greenEmphasis = (byte & 0x40u) >> 6u; + redEmphasis = (byte & 0x20u) >> 5u; + showSprites = (byte & 0x10u) >> 4u; + showBackground = (byte & 0x08u) >> 3u; + showSpritesLeft = (byte & 0x04u) >> 2u; + showBackgroundLeft = (byte & 0x02u) >> 1u; + grayscale = byte & 0x01u; + } + + uint8_t GetByte() + { + return blueEmphasis << 7u | greenEmphasis << 6u | redEmphasis << 5u | showSprites << 4u + | showBackground << 3u | showSpritesLeft << 2u | showBackgroundLeft << 1u | grayscale; + } + + uint8_t grayscale; + uint8_t showBackgroundLeft; + uint8_t showSpritesLeft; + uint8_t showBackground; + uint8_t showSprites; + uint8_t redEmphasis; + uint8_t greenEmphasis; + uint8_t blueEmphasis; +}; + +struct PpuRegisterStatus +{ + void SetByte(uint8_t byte) + { + vblankStarted = (byte & 0x80u) >> 7u; + sprite0Hit = (byte & 0x40u) >> 6u; + spriteOverflow = (byte & 0x20u) >> 5u; + previousLsb = byte & 0x1Fu; + } + + uint8_t GetByte() + { + uint8_t byte = vblankStarted << 7u | sprite0Hit << 6u | spriteOverflow << 5u | previousLsb; + vblankStarted = 0u; + return byte; + } + + uint8_t vblankStarted; + uint8_t sprite0Hit; + uint8_t spriteOverflow; + uint8_t previousLsb; +}; diff --git a/Source/Platform.cpp b/Source/Platform.cpp new file mode 100644 index 0000000..2ca9506 --- /dev/null +++ b/Source/Platform.cpp @@ -0,0 +1,66 @@ +#include "Platform.hpp" +#include + + +Platform::Platform(char const* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight) +{ + SDL_Init(SDL_INIT_VIDEO); + + window = SDL_CreateWindow(title, 0, 0, windowWidth, windowHeight, SDL_WINDOW_SHOWN); + + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + + texture = SDL_CreateTexture( + renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, textureWidth, textureHeight); +} + +Platform::~Platform() +{ + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); +} + +void Platform::Update(void const* buffer, int pitch) +{ + SDL_UpdateTexture(texture, nullptr, buffer, pitch); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, nullptr, nullptr); + SDL_RenderPresent(renderer); +} + +bool Platform::ProcessInput() +{ + bool quit = false; + + SDL_Event event{}; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + { + quit = true; + break; + } + + case SDL_KEYDOWN: + { + switch (event.key.keysym.sym) + { + case SDLK_ESCAPE: + { + quit = true; + break; + } + } + + break; + } + } + } + + return quit; +} diff --git a/Source/Platform.hpp b/Source/Platform.hpp new file mode 100644 index 0000000..768a2d7 --- /dev/null +++ b/Source/Platform.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + + +class SDL_Window; +class SDL_Renderer; +class SDL_Texture; + + +class Platform +{ +public: + Platform(char const* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight); + ~Platform(); + void Update(void const* buffer, int pitch); + bool ProcessInput(); + + SDL_Window* window{}; + SDL_Renderer* renderer{}; + SDL_Texture* texture{}; +};