1
0
Fork 0

PPU (inaccurate)

This commit is contained in:
Austin Morlan 2019-09-06 16:58:57 -07:00
parent f2810585e4
commit b1265da7d3
Signed by: austin
GPG Key ID: FD6B27654AF5E348
15 changed files with 1490 additions and 475 deletions

View File

@ -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(

View File

@ -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);
}

View File

@ -1,314 +1,333 @@
#include <iostream>
#include <cstring>
#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);
}

View File

@ -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;
};

View File

@ -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()

58
Source/Cartridge.cpp Normal file
View File

@ -0,0 +1,58 @@
#include "Cartridge.hpp"
#include <fstream>
#include <iostream>
#include <string>
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<Header*>(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<char*>(prgROM.data()), prgSize);
uint32_t chrSize = header->chrSize * 8192;
if (chrSize > 0)
{
chrROM.resize(chrSize);
file.read(reinterpret_cast<char*>(chrROM.data()), chrSize);
}
file.close();
}
}

18
Source/Cartridge.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <vector>
class Cartridge
{
public:
void Load(char const* filename);
private:
friend class NES;
std::vector<uint8_t> prgROM;
std::vector<uint8_t> chrROM;
};

View File

@ -1,5 +1,5 @@
#include <iostream>
#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();
}

329
Source/NES.cpp Normal file
View File

@ -0,0 +1,329 @@
#include "NES.hpp"
#include <chrono>
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<CPU>()),
ppu(std::make_unique<PPU>()),
cartridge(std::make_unique<Cartridge>()),
platform(std::make_unique<Platform>("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<PpuRegister>(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<PpuRegister>(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;
}

52
Source/NES.hpp Normal file
View File

@ -0,0 +1,52 @@
#pragma once
#include "Cartridge.hpp"
#include "CPU/CPU.hpp"
#include "Platform.hpp"
#include "PPU/PPU.hpp"
#include <memory>
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> cpu;
std::unique_ptr<PPU> ppu;
std::unique_ptr<Cartridge> cartridge;
std::unique_ptr<Platform> platform;
bool nmi{};
Color palette[64];
};

326
Source/PPU/PPU.cpp Normal file
View File

@ -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<uint8_t>(attribute & 0x03u), // Upper Left
static_cast<uint8_t>((attribute & 0x0Cu) >> 2u), // Upper Right
static_cast<uint8_t>((attribute & 0x30u) >> 4u), // Bottom Left
static_cast<uint8_t>((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);
}

54
Source/PPU/PPU.hpp Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include "Registers.hpp"
#include <cstdint>
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;
};

98
Source/PPU/Registers.hpp Normal file
View File

@ -0,0 +1,98 @@
#pragma once
#include <cstdint>
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;
};

66
Source/Platform.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "Platform.hpp"
#include <SDL2/SDL.h>
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;
}

22
Source/Platform.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
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{};
};