Compare commits
No commits in common. "6a6833d2e3ef3ccbe2d34fc34d309bad0c5f08b3" and "f2810585e4af54da82eb282ffc8fb90c99ff6e69" have entirely different histories.
6a6833d2e3
...
f2810585e4
|
@ -10,14 +10,9 @@ add_executable(nes)
|
||||||
target_sources(
|
target_sources(
|
||||||
nes
|
nes
|
||||||
PRIVATE
|
PRIVATE
|
||||||
Source/Cartridge.cpp
|
|
||||||
Source/CPU/CPU.cpp
|
Source/CPU/CPU.cpp
|
||||||
Source/CPU/AddressModes.cpp
|
Source/CPU/AddressModes.cpp
|
||||||
Source/CPU/Instructions.cpp
|
Source/CPU/Instructions.cpp
|
||||||
Source/Input.cpp
|
|
||||||
Source/NES.cpp
|
|
||||||
Source/Platform.cpp
|
|
||||||
Source/PPU/PPU.cpp
|
|
||||||
Source/Main.cpp)
|
Source/Main.cpp)
|
||||||
|
|
||||||
target_compile_options(
|
target_compile_options(
|
||||||
|
|
|
@ -1,8 +1,2 @@
|
||||||
# NES Emulator
|
# NES Emulator
|
||||||
|
|
||||||
Unfinished NES emulator. Supports only the basic mapper, and doesn't render sprites.
|
|
||||||
|
|
||||||
[Here is a blog post about it.](https://austinmorlan.com/posts/nes_rendering_overview/)
|
|
||||||
|
|
||||||
![Demo](https://austinmorlan.com/files/nes-emulator/donkey_kong.gif)
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
#include "CPU.hpp"
|
#include "CPU.hpp"
|
||||||
#include "NES.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
void CPU::Implicit()
|
void CPU::Implicit()
|
||||||
{
|
{
|
||||||
pc += 1;
|
pc += 1;
|
||||||
|
|
||||||
|
// Do nothing
|
||||||
|
fetchedByte = acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::Immediate()
|
void CPU::Immediate()
|
||||||
|
@ -12,50 +14,56 @@ void CPU::Immediate()
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
fetchedAddress = pc - 1;
|
fetchedAddress = pc - 1;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ZeroPage()
|
void CPU::ZeroPage()
|
||||||
{
|
{
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
fetchedAddress = nes->Read(BusSource::CPU, pc - 1);
|
fetchedAddress = ReadMemory(pc - 1);
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ZeroPageX()
|
void CPU::ZeroPageX()
|
||||||
{
|
{
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
uint8_t operand = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t operand = ReadMemory(pc - 1);
|
||||||
fetchedAddress = (operand + x) & 0xFFu;
|
fetchedAddress = (operand + x) & 0xFFu;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ZeroPageY()
|
void CPU::ZeroPageY()
|
||||||
{
|
{
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
uint8_t operand = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t operand = ReadMemory(pc - 1);
|
||||||
fetchedAddress = (operand + y) & 0xFFu;
|
fetchedAddress = (operand + y) & 0xFFu;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::Absolute()
|
void CPU::Absolute()
|
||||||
{
|
{
|
||||||
pc += 3;
|
pc += 3;
|
||||||
|
|
||||||
uint8_t addressLsb = nes->Read(BusSource::CPU, pc - 2);
|
uint8_t addressLsb = ReadMemory(pc - 2);
|
||||||
uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t addressMsb = ReadMemory(pc - 1);
|
||||||
|
|
||||||
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::AbsoluteX()
|
void CPU::AbsoluteX()
|
||||||
{
|
{
|
||||||
pc += 3;
|
pc += 3;
|
||||||
|
|
||||||
uint8_t addressLsb = nes->Read(BusSource::CPU, pc - 2);
|
uint8_t addressLsb = ReadMemory(pc - 2);
|
||||||
uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t addressMsb = ReadMemory(pc - 1);
|
||||||
|
|
||||||
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
||||||
fetchedAddress = preAddress + x;
|
fetchedAddress = preAddress + x;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
|
|
||||||
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
||||||
}
|
}
|
||||||
|
@ -64,11 +72,12 @@ void CPU::AbsoluteY()
|
||||||
{
|
{
|
||||||
pc += 3;
|
pc += 3;
|
||||||
|
|
||||||
uint8_t addressLsb = nes->Read(BusSource::CPU, pc - 2);
|
uint8_t addressLsb = ReadMemory(pc - 2);
|
||||||
uint8_t addressMsb = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t addressMsb = ReadMemory(pc - 1);
|
||||||
|
|
||||||
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
||||||
fetchedAddress = preAddress + y;
|
fetchedAddress = preAddress + y;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
|
|
||||||
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
||||||
}
|
}
|
||||||
|
@ -77,49 +86,52 @@ void CPU::Indirect()
|
||||||
{
|
{
|
||||||
pc += 3;
|
pc += 3;
|
||||||
|
|
||||||
uint8_t indirectLsb = nes->Read(BusSource::CPU, pc - 2);
|
uint8_t indirectLsb = ReadMemory(pc - 2);
|
||||||
uint8_t indirectMsb = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t indirectMsb = ReadMemory(pc - 1);
|
||||||
|
|
||||||
// NOTE: 6502 BUG - indirect address wraps
|
// NOTE: 6502 BUG - indirect address wraps
|
||||||
// $02FF + 1 = $0200
|
// $02FF + 1 = $0200
|
||||||
|
|
||||||
uint16_t addressIndirect = ComposeAddress(indirectMsb, indirectLsb);
|
uint16_t addressIndirect = ComposeAddress(indirectMsb, indirectLsb);
|
||||||
uint8_t addressLsb = nes->Read(BusSource::CPU, addressIndirect);
|
uint8_t addressLsb = ReadMemory(addressIndirect);
|
||||||
|
|
||||||
// Increment LSB to read second byte - it will wrap
|
// Increment LSB to read second byte - it will wrap
|
||||||
++indirectLsb;
|
++indirectLsb;
|
||||||
|
|
||||||
addressIndirect = ComposeAddress(indirectMsb, indirectLsb);
|
addressIndirect = ComposeAddress(indirectMsb, indirectLsb);
|
||||||
uint8_t addressMsb = nes->Read(BusSource::CPU, addressIndirect);
|
uint8_t addressMsb = ReadMemory(addressIndirect);
|
||||||
|
|
||||||
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::IndirectX()
|
void CPU::IndirectX()
|
||||||
{
|
{
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
uint8_t addressIndirect = nes->Read(BusSource::CPU, pc - 1) + x;
|
uint8_t addressIndirect = ReadMemory(pc - 1) + x;
|
||||||
|
|
||||||
// Modulo to keep within zero-page
|
// Modulo to keep within zero-page
|
||||||
uint16_t addressLsb = nes->Read(BusSource::CPU, (addressIndirect % 0x100));
|
uint16_t addressLsb = ReadMemory((addressIndirect % 0x100));
|
||||||
uint16_t addressMsb = nes->Read(BusSource::CPU, (addressIndirect + 1) % 0x100);
|
uint16_t addressMsb = ReadMemory((addressIndirect + 1) % 0x100);
|
||||||
|
|
||||||
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
fetchedAddress = ComposeAddress(addressMsb, addressLsb);
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::IndirectY()
|
void CPU::IndirectY()
|
||||||
{
|
{
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
uint8_t addressIndirect = nes->Read(BusSource::CPU, pc - 1);
|
uint8_t addressIndirect = ReadMemory(pc - 1);
|
||||||
|
|
||||||
// Modulo to keep within zero-page
|
// Modulo to keep within zero-page
|
||||||
uint8_t addressLsb = nes->Read(BusSource::CPU, addressIndirect % 0x100);
|
uint8_t addressLsb = ReadMemory(addressIndirect % 0x100);
|
||||||
uint8_t addressMsb = nes->Read(BusSource::CPU, (addressIndirect + 1) % 0x100);
|
uint8_t addressMsb = ReadMemory((addressIndirect + 1) % 0x100);
|
||||||
|
|
||||||
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
uint16_t preAddress = ComposeAddress(addressMsb, addressLsb);
|
||||||
fetchedAddress = preAddress + y;
|
fetchedAddress = preAddress + y;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
|
|
||||||
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
pageBoundaryCrossed = IsPageBoundaryCrossed(preAddress, fetchedAddress);
|
||||||
}
|
}
|
||||||
|
@ -128,11 +140,12 @@ void CPU::Relative()
|
||||||
{
|
{
|
||||||
pc += 2;
|
pc += 2;
|
||||||
|
|
||||||
int8_t operand = nes->Read(BusSource::CPU, pc - 1);
|
int8_t operand = ReadMemory(pc - 1);
|
||||||
|
|
||||||
uint16_t oldPc = pc;
|
uint16_t oldPc = pc;
|
||||||
|
|
||||||
fetchedAddress = pc + operand;
|
fetchedAddress = pc + operand;
|
||||||
|
fetchedByte = ReadMemory(fetchedAddress);
|
||||||
|
|
||||||
pageBoundaryCrossed = IsPageBoundaryCrossed(oldPc, fetchedAddress);
|
pageBoundaryCrossed = IsPageBoundaryCrossed(oldPc, fetchedAddress);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,333 +1,314 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstring>
|
|
||||||
#include "CPU.hpp"
|
#include "CPU.hpp"
|
||||||
#include "NES.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
const unsigned int STACK_START_ADDRESS = 0x100u;
|
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()
|
CPU::CPU()
|
||||||
{
|
{
|
||||||
status.SetByte(0x24);
|
status.SetByte(0x24);
|
||||||
|
|
||||||
instructions[0xA9] = Instruction{.name = "LDA", .cycles = 2, .execute = &CPU::LDA, .fetch = &CPU::Immediate};
|
instructions[0xA9] = Instruction{.cycles = 2, .execute = &CPU::LDA, .fetch = &CPU::Immediate};
|
||||||
instructions[0xA5] = Instruction{.name = "LDA", .cycles = 3, .execute = &CPU::LDA, .fetch = &CPU::ZeroPage};
|
instructions[0xA5] = Instruction{.cycles = 3, .execute = &CPU::LDA, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xB5] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::ZeroPageX};
|
instructions[0xB5] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xAD] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::Absolute};
|
instructions[0xAD] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::Absolute};
|
||||||
instructions[0xBD] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteX};
|
instructions[0xBD] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xB9] = Instruction{.name = "LDA", .cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteY};
|
instructions[0xB9] = Instruction{.cycles = 4, .execute = &CPU::LDA, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0xA1] = Instruction{.name = "LDA", .cycles = 6, .execute = &CPU::LDA, .fetch = &CPU::IndirectX};
|
instructions[0xA1] = Instruction{.cycles = 6, .execute = &CPU::LDA, .fetch = &CPU::IndirectX};
|
||||||
instructions[0xB1] = Instruction{.name = "LDA", .cycles = 5, .execute = &CPU::LDA, .fetch = &CPU::IndirectY};
|
instructions[0xB1] = Instruction{.cycles = 5, .execute = &CPU::LDA, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0xA2] = Instruction{.name = "LDX", .cycles = 2, .execute = &CPU::LDX, .fetch = &CPU::Immediate};
|
instructions[0xA2] = Instruction{.cycles = 2, .execute = &CPU::LDX, .fetch = &CPU::Immediate};
|
||||||
instructions[0xA6] = Instruction{.name = "LDX", .cycles = 3, .execute = &CPU::LDX, .fetch = &CPU::ZeroPage};
|
instructions[0xA6] = Instruction{.cycles = 3, .execute = &CPU::LDX, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xB6] = Instruction{.name = "LDX", .cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::ZeroPageY};
|
instructions[0xB6] = Instruction{.cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::ZeroPageY};
|
||||||
instructions[0xAE] = Instruction{.name = "LDX", .cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::Absolute};
|
instructions[0xAE] = Instruction{.cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::Absolute};
|
||||||
instructions[0xBE] = Instruction{.name = "LDX", .cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::AbsoluteY};
|
instructions[0xBE] = Instruction{.cycles = 4, .execute = &CPU::LDX, .fetch = &CPU::AbsoluteY};
|
||||||
|
|
||||||
instructions[0xA0] = Instruction{.name = "LDY", .cycles = 2, .execute = &CPU::LDY, .fetch = &CPU::Immediate};
|
instructions[0xA0] = Instruction{.cycles = 2, .execute = &CPU::LDY, .fetch = &CPU::Immediate};
|
||||||
instructions[0xA4] = Instruction{.name = "LDY", .cycles = 3, .execute = &CPU::LDY, .fetch = &CPU::ZeroPage};
|
instructions[0xA4] = Instruction{.cycles = 3, .execute = &CPU::LDY, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xB4] = Instruction{.name = "LDY", .cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::ZeroPageX};
|
instructions[0xB4] = Instruction{.cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xAC] = Instruction{.name = "LDY", .cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::Absolute};
|
instructions[0xAC] = Instruction{.cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::Absolute};
|
||||||
instructions[0xBC] = Instruction{.name = "LDY", .cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::AbsoluteX};
|
instructions[0xBC] = Instruction{.cycles = 4, .execute = &CPU::LDY, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0x85] = Instruction{.name = "STA", .cycles = 3, .execute = &CPU::STA, .fetch = &CPU::ZeroPage};
|
instructions[0x85] = Instruction{.cycles = 3, .execute = &CPU::STA, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x95] = Instruction{.name = "STA", .cycles = 4, .execute = &CPU::STA, .fetch = &CPU::ZeroPageX};
|
instructions[0x95] = Instruction{.cycles = 4, .execute = &CPU::STA, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x8D] = Instruction{.name = "STA", .cycles = 4, .execute = &CPU::STA, .fetch = &CPU::Absolute};
|
instructions[0x8D] = Instruction{.cycles = 4, .execute = &CPU::STA, .fetch = &CPU::Absolute};
|
||||||
instructions[0x9D] = Instruction{.name = "STA", .cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteX};
|
instructions[0x9D] = Instruction{.cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x99] = Instruction{.name = "STA", .cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteY};
|
instructions[0x99] = Instruction{.cycles = 5, .execute = &CPU::STA, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x81] = Instruction{.name = "STA", .cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectX};
|
instructions[0x81] = Instruction{.cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x91] = Instruction{.name = "STA", .cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectY};
|
instructions[0x91] = Instruction{.cycles = 6, .execute = &CPU::STA, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x86] = Instruction{.name = "STX", .cycles = 3, .execute = &CPU::STX, .fetch = &CPU::ZeroPage};
|
instructions[0x86] = Instruction{.cycles = 3, .execute = &CPU::STX, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x96] = Instruction{.name = "STX", .cycles = 4, .execute = &CPU::STX, .fetch = &CPU::ZeroPageY};
|
instructions[0x96] = Instruction{.cycles = 4, .execute = &CPU::STX, .fetch = &CPU::ZeroPageY};
|
||||||
instructions[0x8E] = Instruction{.name = "STX", .cycles = 4, .execute = &CPU::STX, .fetch = &CPU::Absolute};
|
instructions[0x8E] = Instruction{.cycles = 4, .execute = &CPU::STX, .fetch = &CPU::Absolute};
|
||||||
|
|
||||||
instructions[0x84] = Instruction{.name = "STY", .cycles = 3, .execute = &CPU::STY, .fetch = &CPU::ZeroPage};
|
instructions[0x84] = Instruction{.cycles = 3, .execute = &CPU::STY, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x94] = Instruction{.name = "STY", .cycles = 4, .execute = &CPU::STY, .fetch = &CPU::ZeroPageX};
|
instructions[0x94] = Instruction{.cycles = 4, .execute = &CPU::STY, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x8C] = Instruction{.name = "STY", .cycles = 4, .execute = &CPU::STY, .fetch = &CPU::Absolute};
|
instructions[0x8C] = Instruction{.cycles = 4, .execute = &CPU::STY, .fetch = &CPU::Absolute};
|
||||||
|
|
||||||
instructions[0xAA] = Instruction{.name = "TAX", .cycles = 2, .execute = &CPU::TAX, .fetch = &CPU::Implicit};
|
instructions[0xAA] = Instruction{.cycles = 2, .execute = &CPU::TAX, .fetch = &CPU::Implicit};
|
||||||
instructions[0xA8] = Instruction{.name = "TAY", .cycles = 2, .execute = &CPU::TAY, .fetch = &CPU::Implicit};
|
instructions[0xA8] = Instruction{.cycles = 2, .execute = &CPU::TAY, .fetch = &CPU::Implicit};
|
||||||
instructions[0x8A] = Instruction{.name = "TXA", .cycles = 2, .execute = &CPU::TXA, .fetch = &CPU::Implicit};
|
instructions[0x8A] = Instruction{.cycles = 2, .execute = &CPU::TXA, .fetch = &CPU::Implicit};
|
||||||
instructions[0x98] = Instruction{.name = "TYA", .cycles = 2, .execute = &CPU::TYA, .fetch = &CPU::Implicit};
|
instructions[0x98] = Instruction{.cycles = 2, .execute = &CPU::TYA, .fetch = &CPU::Implicit};
|
||||||
instructions[0xBA] = Instruction{.name = "TSX", .cycles = 2, .execute = &CPU::TSX, .fetch = &CPU::Implicit};
|
instructions[0xBA] = Instruction{.cycles = 2, .execute = &CPU::TSX, .fetch = &CPU::Implicit};
|
||||||
instructions[0x9A] = Instruction{.name = "TXS", .cycles = 2, .execute = &CPU::TXS, .fetch = &CPU::Implicit};
|
instructions[0x9A] = Instruction{.cycles = 2, .execute = &CPU::TXS, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x48] = Instruction{.name = "PHA", .cycles = 3, .execute = &CPU::PHA, .fetch = &CPU::Implicit};
|
instructions[0x48] = Instruction{.cycles = 3, .execute = &CPU::PHA, .fetch = &CPU::Implicit};
|
||||||
instructions[0x08] = Instruction{.name = "PHP", .cycles = 3, .execute = &CPU::PHP, .fetch = &CPU::Implicit};
|
instructions[0x08] = Instruction{.cycles = 3, .execute = &CPU::PHP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x68] = Instruction{.name = "PLA", .cycles = 4, .execute = &CPU::PLA, .fetch = &CPU::Implicit};
|
instructions[0x68] = Instruction{.cycles = 4, .execute = &CPU::PLA, .fetch = &CPU::Implicit};
|
||||||
instructions[0x28] = Instruction{.name = "PLP", .cycles = 4, .execute = &CPU::PLP, .fetch = &CPU::Implicit};
|
instructions[0x28] = Instruction{.cycles = 4, .execute = &CPU::PLP, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x29] = Instruction{.name = "AND", .cycles = 2, .execute = &CPU::AND, .fetch = &CPU::Immediate};
|
instructions[0x29] = Instruction{.cycles = 2, .execute = &CPU::AND, .fetch = &CPU::Immediate};
|
||||||
instructions[0x25] = Instruction{.name = "AND", .cycles = 3, .execute = &CPU::AND, .fetch = &CPU::ZeroPage};
|
instructions[0x25] = Instruction{.cycles = 3, .execute = &CPU::AND, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x35] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::ZeroPageX};
|
instructions[0x35] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x2D] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::Absolute};
|
instructions[0x2D] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::Absolute};
|
||||||
instructions[0x3D] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteX};
|
instructions[0x3D] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x39] = Instruction{.name = "AND", .cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteY};
|
instructions[0x39] = Instruction{.cycles = 4, .execute = &CPU::AND, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x21] = Instruction{.name = "AND", .cycles = 6, .execute = &CPU::AND, .fetch = &CPU::IndirectX};
|
instructions[0x21] = Instruction{.cycles = 6, .execute = &CPU::AND, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x31] = Instruction{.name = "AND", .cycles = 5, .execute = &CPU::AND, .fetch = &CPU::IndirectY};
|
instructions[0x31] = Instruction{.cycles = 5, .execute = &CPU::AND, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x49] = Instruction{.name = "EOR", .cycles = 2, .execute = &CPU::EOR, .fetch = &CPU::Immediate};
|
instructions[0x49] = Instruction{.cycles = 2, .execute = &CPU::EOR, .fetch = &CPU::Immediate};
|
||||||
instructions[0x45] = Instruction{.name = "EOR", .cycles = 3, .execute = &CPU::EOR, .fetch = &CPU::ZeroPage};
|
instructions[0x45] = Instruction{.cycles = 3, .execute = &CPU::EOR, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x55] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::ZeroPageX};
|
instructions[0x55] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x4D] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::Absolute};
|
instructions[0x4D] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::Absolute};
|
||||||
instructions[0x5D] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteX};
|
instructions[0x5D] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x59] = Instruction{.name = "EOR", .cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteY};
|
instructions[0x59] = Instruction{.cycles = 4, .execute = &CPU::EOR, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x41] = Instruction{.name = "EOR", .cycles = 6, .execute = &CPU::EOR, .fetch = &CPU::IndirectX};
|
instructions[0x41] = Instruction{.cycles = 6, .execute = &CPU::EOR, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x51] = Instruction{.name = "EOR", .cycles = 5, .execute = &CPU::EOR, .fetch = &CPU::IndirectY};
|
instructions[0x51] = Instruction{.cycles = 5, .execute = &CPU::EOR, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x09] = Instruction{.name = "ORA", .cycles = 2, .execute = &CPU::ORA, .fetch = &CPU::Immediate};
|
instructions[0x09] = Instruction{.cycles = 2, .execute = &CPU::ORA, .fetch = &CPU::Immediate};
|
||||||
instructions[0x05] = Instruction{.name = "ORA", .cycles = 3, .execute = &CPU::ORA, .fetch = &CPU::ZeroPage};
|
instructions[0x05] = Instruction{.cycles = 3, .execute = &CPU::ORA, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x15] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::ZeroPageX};
|
instructions[0x15] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x0D] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::Absolute};
|
instructions[0x0D] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::Absolute};
|
||||||
instructions[0x1D] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteX};
|
instructions[0x1D] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x19] = Instruction{.name = "ORA", .cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteY};
|
instructions[0x19] = Instruction{.cycles = 4, .execute = &CPU::ORA, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x01] = Instruction{.name = "ORA", .cycles = 6, .execute = &CPU::ORA, .fetch = &CPU::IndirectX};
|
instructions[0x01] = Instruction{.cycles = 6, .execute = &CPU::ORA, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x11] = Instruction{.name = "ORA", .cycles = 5, .execute = &CPU::ORA, .fetch = &CPU::IndirectY};
|
instructions[0x11] = Instruction{.cycles = 5, .execute = &CPU::ORA, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x24] = Instruction{.name = "BIT", .cycles = 3, .execute = &CPU::BIT, .fetch = &CPU::ZeroPage};
|
instructions[0x24] = Instruction{.cycles = 3, .execute = &CPU::BIT, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x2C] = Instruction{.name = "BIT", .cycles = 4, .execute = &CPU::BIT, .fetch = &CPU::Absolute};
|
instructions[0x2C] = Instruction{.cycles = 4, .execute = &CPU::BIT, .fetch = &CPU::Absolute};
|
||||||
|
|
||||||
instructions[0x69] = Instruction{.name = "ADC", .cycles = 2, .execute = &CPU::ADC, .fetch = &CPU::Immediate};
|
instructions[0x69] = Instruction{.cycles = 2, .execute = &CPU::ADC, .fetch = &CPU::Immediate};
|
||||||
instructions[0x65] = Instruction{.name = "ADC", .cycles = 3, .execute = &CPU::ADC, .fetch = &CPU::ZeroPage};
|
instructions[0x65] = Instruction{.cycles = 3, .execute = &CPU::ADC, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x75] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::ZeroPageX};
|
instructions[0x75] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x6D] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::Absolute};
|
instructions[0x6D] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::Absolute};
|
||||||
instructions[0x7D] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteX};
|
instructions[0x7D] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x79] = Instruction{.name = "ADC", .cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteY};
|
instructions[0x79] = Instruction{.cycles = 4, .execute = &CPU::ADC, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x61] = Instruction{.name = "ADC", .cycles = 6, .execute = &CPU::ADC, .fetch = &CPU::IndirectX};
|
instructions[0x61] = Instruction{.cycles = 6, .execute = &CPU::ADC, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x71] = Instruction{.name = "ADC", .cycles = 5, .execute = &CPU::ADC, .fetch = &CPU::IndirectY};
|
instructions[0x71] = Instruction{.cycles = 5, .execute = &CPU::ADC, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0xE9] = Instruction{.name = "SBC", .cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate};
|
instructions[0xE9] = Instruction{.cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate};
|
||||||
instructions[0xE5] = Instruction{.name = "SBC", .cycles = 3, .execute = &CPU::SBC, .fetch = &CPU::ZeroPage};
|
instructions[0xE5] = Instruction{.cycles = 3, .execute = &CPU::SBC, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xF5] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::ZeroPageX};
|
instructions[0xF5] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xED] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::Absolute};
|
instructions[0xED] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::Absolute};
|
||||||
instructions[0xFD] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteX};
|
instructions[0xFD] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xF9] = Instruction{.name = "SBC", .cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteY};
|
instructions[0xF9] = Instruction{.cycles = 4, .execute = &CPU::SBC, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0xE1] = Instruction{.name = "SBC", .cycles = 6, .execute = &CPU::SBC, .fetch = &CPU::IndirectX};
|
instructions[0xE1] = Instruction{.cycles = 6, .execute = &CPU::SBC, .fetch = &CPU::IndirectX};
|
||||||
instructions[0xF1] = Instruction{.name = "SBC", .cycles = 5, .execute = &CPU::SBC, .fetch = &CPU::IndirectY};
|
instructions[0xF1] = Instruction{.cycles = 5, .execute = &CPU::SBC, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0xC9] = Instruction{.name = "CMP", .cycles = 2, .execute = &CPU::CMP, .fetch = &CPU::Immediate};
|
instructions[0xC9] = Instruction{.cycles = 2, .execute = &CPU::CMP, .fetch = &CPU::Immediate};
|
||||||
instructions[0xC5] = Instruction{.name = "CMP", .cycles = 3, .execute = &CPU::CMP, .fetch = &CPU::ZeroPage};
|
instructions[0xC5] = Instruction{.cycles = 3, .execute = &CPU::CMP, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xD5] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::ZeroPageX};
|
instructions[0xD5] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xCD] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::Absolute};
|
instructions[0xCD] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::Absolute};
|
||||||
instructions[0xDD] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteX};
|
instructions[0xDD] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xD9] = Instruction{.name = "CMP", .cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteY};
|
instructions[0xD9] = Instruction{.cycles = 4, .execute = &CPU::CMP, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0xC1] = Instruction{.name = "CMP", .cycles = 6, .execute = &CPU::CMP, .fetch = &CPU::IndirectX};
|
instructions[0xC1] = Instruction{.cycles = 6, .execute = &CPU::CMP, .fetch = &CPU::IndirectX};
|
||||||
instructions[0xD1] = Instruction{.name = "CMP", .cycles = 5, .execute = &CPU::CMP, .fetch = &CPU::IndirectY};
|
instructions[0xD1] = Instruction{.cycles = 5, .execute = &CPU::CMP, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0xE0] = Instruction{.name = "CPX", .cycles = 2, .execute = &CPU::CPX, .fetch = &CPU::Immediate};
|
instructions[0xE0] = Instruction{.cycles = 2, .execute = &CPU::CPX, .fetch = &CPU::Immediate};
|
||||||
instructions[0xE4] = Instruction{.name = "CPX", .cycles = 3, .execute = &CPU::CPX, .fetch = &CPU::ZeroPage};
|
instructions[0xE4] = Instruction{.cycles = 3, .execute = &CPU::CPX, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xEC] = Instruction{.name = "CPX", .cycles = 4, .execute = &CPU::CPX, .fetch = &CPU::Absolute};
|
instructions[0xEC] = Instruction{.cycles = 4, .execute = &CPU::CPX, .fetch = &CPU::Absolute};
|
||||||
|
|
||||||
instructions[0xC0] = Instruction{.name = "CPY", .cycles = 2, .execute = &CPU::CPY, .fetch = &CPU::Immediate};
|
instructions[0xC0] = Instruction{.cycles = 2, .execute = &CPU::CPY, .fetch = &CPU::Immediate};
|
||||||
instructions[0xC4] = Instruction{.name = "CPY", .cycles = 3, .execute = &CPU::CPY, .fetch = &CPU::ZeroPage};
|
instructions[0xC4] = Instruction{.cycles = 3, .execute = &CPU::CPY, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xCC] = Instruction{.name = "CPY", .cycles = 4, .execute = &CPU::CPY, .fetch = &CPU::Absolute};
|
instructions[0xCC] = Instruction{.cycles = 4, .execute = &CPU::CPY, .fetch = &CPU::Absolute};
|
||||||
|
|
||||||
instructions[0xE6] = Instruction{.name = "INC", .cycles = 5, .execute = &CPU::INC, .fetch = &CPU::ZeroPage};
|
instructions[0xE6] = Instruction{.cycles = 5, .execute = &CPU::INC, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xF6] = Instruction{.name = "INC", .cycles = 6, .execute = &CPU::INC, .fetch = &CPU::ZeroPageX};
|
instructions[0xF6] = Instruction{.cycles = 6, .execute = &CPU::INC, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xEE] = Instruction{.name = "INC", .cycles = 6, .execute = &CPU::INC, .fetch = &CPU::Absolute};
|
instructions[0xEE] = Instruction{.cycles = 6, .execute = &CPU::INC, .fetch = &CPU::Absolute};
|
||||||
instructions[0xFE] = Instruction{.name = "INC", .cycles = 7, .execute = &CPU::INC, .fetch = &CPU::AbsoluteX};
|
instructions[0xFE] = Instruction{.cycles = 7, .execute = &CPU::INC, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0xE8] = Instruction{.name = "INX", .cycles = 2, .execute = &CPU::INX, .fetch = &CPU::Implicit};
|
instructions[0xE8] = Instruction{.cycles = 2, .execute = &CPU::INX, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0xC8] = Instruction{.name = "INY", .cycles = 2, .execute = &CPU::INY, .fetch = &CPU::Implicit};
|
instructions[0xC8] = Instruction{.cycles = 2, .execute = &CPU::INY, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0xC6] = Instruction{.name = "DEC", .cycles = 5, .execute = &CPU::DEC, .fetch = &CPU::ZeroPage};
|
instructions[0xC6] = Instruction{.cycles = 5, .execute = &CPU::DEC, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xD6] = Instruction{.name = "DEC", .cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::ZeroPageX};
|
instructions[0xD6] = Instruction{.cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xCE] = Instruction{.name = "DEC", .cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::Absolute};
|
instructions[0xCE] = Instruction{.cycles = 6, .execute = &CPU::DEC, .fetch = &CPU::Absolute};
|
||||||
instructions[0xDE] = Instruction{.name = "DEC", .cycles = 7, .execute = &CPU::DEC, .fetch = &CPU::AbsoluteX};
|
instructions[0xDE] = Instruction{.cycles = 7, .execute = &CPU::DEC, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0xCA] = Instruction{.name = "DEX", .cycles = 2, .execute = &CPU::DEX, .fetch = &CPU::Implicit};
|
instructions[0xCA] = Instruction{.cycles = 2, .execute = &CPU::DEX, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x88] = Instruction{.name = "DEY", .cycles = 2, .execute = &CPU::DEY, .fetch = &CPU::Implicit};
|
instructions[0x88] = Instruction{.cycles = 2, .execute = &CPU::DEY, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x0A] = Instruction{.name = "ASL", .cycles = 2, .execute = &CPU::ASL, .fetch = &CPU::Implicit};
|
instructions[0x0A] = Instruction{.cycles = 2, .execute = &CPU::ASL, .fetch = &CPU::Implicit};
|
||||||
instructions[0x06] = Instruction{.name = "ASL", .cycles = 5, .execute = &CPU::ASL, .fetch = &CPU::ZeroPage};
|
instructions[0x06] = Instruction{.cycles = 5, .execute = &CPU::ASL, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x16] = Instruction{.name = "ASL", .cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::ZeroPageX};
|
instructions[0x16] = Instruction{.cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x0E] = Instruction{.name = "ASL", .cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::Absolute};
|
instructions[0x0E] = Instruction{.cycles = 6, .execute = &CPU::ASL, .fetch = &CPU::Absolute};
|
||||||
instructions[0x1E] = Instruction{.name = "ASL", .cycles = 7, .execute = &CPU::ASL, .fetch = &CPU::AbsoluteX};
|
instructions[0x1E] = Instruction{.cycles = 7, .execute = &CPU::ASL, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0x4A] = Instruction{.name = "LSR", .cycles = 2, .execute = &CPU::LSR, .fetch = &CPU::Implicit};
|
instructions[0x4A] = Instruction{.cycles = 2, .execute = &CPU::LSR, .fetch = &CPU::Implicit};
|
||||||
instructions[0x46] = Instruction{.name = "LSR", .cycles = 5, .execute = &CPU::LSR, .fetch = &CPU::ZeroPage};
|
instructions[0x46] = Instruction{.cycles = 5, .execute = &CPU::LSR, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x56] = Instruction{.name = "LSR", .cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::ZeroPageX};
|
instructions[0x56] = Instruction{.cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x4E] = Instruction{.name = "LSR", .cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::Absolute};
|
instructions[0x4E] = Instruction{.cycles = 6, .execute = &CPU::LSR, .fetch = &CPU::Absolute};
|
||||||
instructions[0x5E] = Instruction{.name = "LSR", .cycles = 7, .execute = &CPU::LSR, .fetch = &CPU::AbsoluteX};
|
instructions[0x5E] = Instruction{.cycles = 7, .execute = &CPU::LSR, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0x2A] = Instruction{.name = "ROL", .cycles = 2, .execute = &CPU::ROL, .fetch = &CPU::Implicit};
|
instructions[0x2A] = Instruction{.cycles = 2, .execute = &CPU::ROL, .fetch = &CPU::Implicit};
|
||||||
instructions[0x26] = Instruction{.name = "ROL", .cycles = 5, .execute = &CPU::ROL, .fetch = &CPU::ZeroPage};
|
instructions[0x26] = Instruction{.cycles = 5, .execute = &CPU::ROL, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x36] = Instruction{.name = "ROL", .cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::ZeroPageX};
|
instructions[0x36] = Instruction{.cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x2E] = Instruction{.name = "ROL", .cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::Absolute};
|
instructions[0x2E] = Instruction{.cycles = 6, .execute = &CPU::ROL, .fetch = &CPU::Absolute};
|
||||||
instructions[0x3E] = Instruction{.name = "ROL", .cycles = 7, .execute = &CPU::ROL, .fetch = &CPU::AbsoluteX};
|
instructions[0x3E] = Instruction{.cycles = 7, .execute = &CPU::ROL, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0x6A] = Instruction{.name = "ROR", .cycles = 2, .execute = &CPU::ROR, .fetch = &CPU::Implicit};
|
instructions[0x6A] = Instruction{.cycles = 2, .execute = &CPU::ROR, .fetch = &CPU::Implicit};
|
||||||
instructions[0x66] = Instruction{.name = "ROR", .cycles = 5, .execute = &CPU::ROR, .fetch = &CPU::ZeroPage};
|
instructions[0x66] = Instruction{.cycles = 5, .execute = &CPU::ROR, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x76] = Instruction{.name = "ROR", .cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::ZeroPageX};
|
instructions[0x76] = Instruction{.cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x6E] = Instruction{.name = "ROR", .cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::Absolute};
|
instructions[0x6E] = Instruction{.cycles = 6, .execute = &CPU::ROR, .fetch = &CPU::Absolute};
|
||||||
instructions[0x7E] = Instruction{.name = "ROR", .cycles = 7, .execute = &CPU::ROR, .fetch = &CPU::AbsoluteX};
|
instructions[0x7E] = Instruction{.cycles = 7, .execute = &CPU::ROR, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0x4C] = Instruction{.name = "JMP", .cycles = 3, .execute = &CPU::JMP, .fetch = &CPU::Absolute};
|
instructions[0x4C] = Instruction{.cycles = 3, .execute = &CPU::JMP, .fetch = &CPU::Absolute};
|
||||||
instructions[0x6C] = Instruction{.name = "JMP", .cycles = 5, .execute = &CPU::JMP, .fetch = &CPU::Indirect};
|
instructions[0x6C] = Instruction{.cycles = 5, .execute = &CPU::JMP, .fetch = &CPU::Indirect};
|
||||||
|
|
||||||
instructions[0x20] = Instruction{.name = "JSR", .cycles = 6, .execute = &CPU::JSR, .fetch = &CPU::Absolute};
|
instructions[0x20] = Instruction{.cycles = 6, .execute = &CPU::JSR, .fetch = &CPU::Absolute};
|
||||||
|
|
||||||
instructions[0x60] = Instruction{.name = "RTS", .cycles = 6, .execute = &CPU::RTS, .fetch = &CPU::Implicit};
|
instructions[0x60] = Instruction{.cycles = 6, .execute = &CPU::RTS, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x90] = Instruction{.name = "BCC", .cycles = 2, .execute = &CPU::BCC, .fetch = &CPU::Relative};
|
instructions[0x90] = Instruction{.cycles = 2, .execute = &CPU::BCC, .fetch = &CPU::Relative};
|
||||||
instructions[0xB0] = Instruction{.name = "BCS", .cycles = 2, .execute = &CPU::BCS, .fetch = &CPU::Relative};
|
instructions[0xB0] = Instruction{.cycles = 2, .execute = &CPU::BCS, .fetch = &CPU::Relative};
|
||||||
instructions[0xF0] = Instruction{.name = "BEQ", .cycles = 2, .execute = &CPU::BEQ, .fetch = &CPU::Relative};
|
instructions[0xF0] = Instruction{.cycles = 2, .execute = &CPU::BEQ, .fetch = &CPU::Relative};
|
||||||
instructions[0x30] = Instruction{.name = "BMI", .cycles = 2, .execute = &CPU::BMI, .fetch = &CPU::Relative};
|
instructions[0x30] = Instruction{.cycles = 2, .execute = &CPU::BMI, .fetch = &CPU::Relative};
|
||||||
instructions[0xD0] = Instruction{.name = "BNE", .cycles = 2, .execute = &CPU::BNE, .fetch = &CPU::Relative};
|
instructions[0xD0] = Instruction{.cycles = 2, .execute = &CPU::BNE, .fetch = &CPU::Relative};
|
||||||
instructions[0x10] = Instruction{.name = "BPL", .cycles = 2, .execute = &CPU::BPL, .fetch = &CPU::Relative};
|
instructions[0x10] = Instruction{.cycles = 2, .execute = &CPU::BPL, .fetch = &CPU::Relative};
|
||||||
instructions[0x50] = Instruction{.name = "BVC", .cycles = 2, .execute = &CPU::BVC, .fetch = &CPU::Relative};
|
instructions[0x50] = Instruction{.cycles = 2, .execute = &CPU::BVC, .fetch = &CPU::Relative};
|
||||||
instructions[0x70] = Instruction{.name = "BVS", .cycles = 2, .execute = &CPU::BVS, .fetch = &CPU::Relative};
|
instructions[0x70] = Instruction{.cycles = 2, .execute = &CPU::BVS, .fetch = &CPU::Relative};
|
||||||
|
|
||||||
instructions[0x18] = Instruction{.name = "CLC", .cycles = 2, .execute = &CPU::CLC, .fetch = &CPU::Implicit};
|
instructions[0x18] = Instruction{.cycles = 2, .execute = &CPU::CLC, .fetch = &CPU::Implicit};
|
||||||
instructions[0xD8] = Instruction{.name = "CLD", .cycles = 2, .execute = &CPU::CLD, .fetch = &CPU::Implicit};
|
instructions[0xD8] = Instruction{.cycles = 2, .execute = &CPU::CLD, .fetch = &CPU::Implicit};
|
||||||
instructions[0x58] = Instruction{.name = "CLI", .cycles = 2, .execute = &CPU::CLI, .fetch = &CPU::Implicit};
|
instructions[0x58] = Instruction{.cycles = 2, .execute = &CPU::CLI, .fetch = &CPU::Implicit};
|
||||||
instructions[0xB8] = Instruction{.name = "CLV", .cycles = 2, .execute = &CPU::CLV, .fetch = &CPU::Implicit};
|
instructions[0xB8] = Instruction{.cycles = 2, .execute = &CPU::CLV, .fetch = &CPU::Implicit};
|
||||||
instructions[0x38] = Instruction{.name = "SEC", .cycles = 2, .execute = &CPU::SEC, .fetch = &CPU::Implicit};
|
instructions[0x38] = Instruction{.cycles = 2, .execute = &CPU::SEC, .fetch = &CPU::Implicit};
|
||||||
instructions[0xF8] = Instruction{.name = "SED", .cycles = 2, .execute = &CPU::SED, .fetch = &CPU::Implicit};
|
instructions[0xF8] = Instruction{.cycles = 2, .execute = &CPU::SED, .fetch = &CPU::Implicit};
|
||||||
instructions[0x78] = Instruction{.name = "SEI", .cycles = 2, .execute = &CPU::SEI, .fetch = &CPU::Implicit};
|
instructions[0x78] = Instruction{.cycles = 2, .execute = &CPU::SEI, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x00] = Instruction{.name = "BRK", .cycles = 7, .execute = &CPU::BRK, .fetch = &CPU::Immediate};
|
instructions[0x00] = Instruction{.cycles = 7, .execute = &CPU::BRK, .fetch = &CPU::Implicit};
|
||||||
instructions[0xEA] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xEA] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x40] = Instruction{.name = "RTI", .cycles = 6, .execute = &CPU::RTI, .fetch = &CPU::Implicit};
|
instructions[0x40] = Instruction{.cycles = 6, .execute = &CPU::RTI, .fetch = &CPU::Implicit};
|
||||||
|
|
||||||
instructions[0x1A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x1A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x3A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x3A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x5A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x5A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x7A] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x7A] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0xDA] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xDA] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0xFA] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xFA] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x80] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
instructions[0x80] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
||||||
instructions[0x82] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
instructions[0x82] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
||||||
instructions[0x89] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
instructions[0x89] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
||||||
instructions[0xC2] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
instructions[0xC2] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
||||||
instructions[0xE2] = Instruction{.name = "NOP", .cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
instructions[0xE2] = Instruction{.cycles = 2, .execute = &CPU::NOP, .fetch = &CPU::Immediate};
|
||||||
instructions[0x04] = Instruction{.name = "NOP", .cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage};
|
instructions[0x04] = Instruction{.cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x44] = Instruction{.name = "NOP", .cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage};
|
instructions[0x44] = Instruction{.cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x64] = Instruction{.name = "NOP", .cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage};
|
instructions[0x64] = Instruction{.cycles = 3, .execute = &CPU::NOP, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x14] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
instructions[0x14] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x34] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
instructions[0x34] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x54] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
instructions[0x54] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x74] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
instructions[0x74] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xD4] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
instructions[0xD4] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xF4] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
instructions[0xF4] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x0C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::Absolute};
|
instructions[0x0C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::Absolute};
|
||||||
instructions[0x1C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
instructions[0x1C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x3C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
instructions[0x3C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x5C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
instructions[0x5C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x7C] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
instructions[0x7C] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xDC] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
instructions[0xDC] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xFC] = Instruction{.name = "NOP", .cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
instructions[0xFC] = Instruction{.cycles = 4, .execute = &CPU::NOP, .fetch = &CPU::AbsoluteX};
|
||||||
|
|
||||||
instructions[0x07] = Instruction{.name = "SLO", .cycles = 5, .execute = &CPU::SLO, .fetch = &CPU::ZeroPage};
|
instructions[0x07] = Instruction{.cycles = 5, .execute = &CPU::SLO, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x17] = Instruction{.name = "SLO", .cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::ZeroPageX};
|
instructions[0x17] = Instruction{.cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x0F] = Instruction{.name = "SLO", .cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::Absolute};
|
instructions[0x0F] = Instruction{.cycles = 6, .execute = &CPU::SLO, .fetch = &CPU::Absolute};
|
||||||
instructions[0x1F] = Instruction{.name = "SLO", .cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteX};
|
instructions[0x1F] = Instruction{.cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x1B] = Instruction{.name = "SLO", .cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteY};
|
instructions[0x1B] = Instruction{.cycles = 7, .execute = &CPU::SLO, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x03] = Instruction{.name = "SLO", .cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectX};
|
instructions[0x03] = Instruction{.cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x13] = Instruction{.name = "SLO", .cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectY};
|
instructions[0x13] = Instruction{.cycles = 8, .execute = &CPU::SLO, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x27] = Instruction{.name = "RLA", .cycles = 5, .execute = &CPU::RLA, .fetch = &CPU::ZeroPage};
|
instructions[0x27] = Instruction{.cycles = 5, .execute = &CPU::RLA, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x37] = Instruction{.name = "RLA", .cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::ZeroPageX};
|
instructions[0x37] = Instruction{.cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x2F] = Instruction{.name = "RLA", .cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::Absolute};
|
instructions[0x2F] = Instruction{.cycles = 6, .execute = &CPU::RLA, .fetch = &CPU::Absolute};
|
||||||
instructions[0x3F] = Instruction{.name = "RLA", .cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteX};
|
instructions[0x3F] = Instruction{.cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x3B] = Instruction{.name = "RLA", .cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteY};
|
instructions[0x3B] = Instruction{.cycles = 7, .execute = &CPU::RLA, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x23] = Instruction{.name = "RLA", .cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectX};
|
instructions[0x23] = Instruction{.cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x33] = Instruction{.name = "RLA", .cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectY};
|
instructions[0x33] = Instruction{.cycles = 8, .execute = &CPU::RLA, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x47] = Instruction{.name = "SRE", .cycles = 5, .execute = &CPU::SRE, .fetch = &CPU::ZeroPage};
|
instructions[0x47] = Instruction{.cycles = 5, .execute = &CPU::SRE, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x57] = Instruction{.name = "SRE", .cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::ZeroPageX};
|
instructions[0x57] = Instruction{.cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x4F] = Instruction{.name = "SRE", .cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::Absolute};
|
instructions[0x4F] = Instruction{.cycles = 6, .execute = &CPU::SRE, .fetch = &CPU::Absolute};
|
||||||
instructions[0x5F] = Instruction{.name = "SRE", .cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteX};
|
instructions[0x5F] = Instruction{.cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x5B] = Instruction{.name = "SRE", .cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteY};
|
instructions[0x5B] = Instruction{.cycles = 7, .execute = &CPU::SRE, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x43] = Instruction{.name = "SRE", .cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectX};
|
instructions[0x43] = Instruction{.cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x53] = Instruction{.name = "SRE", .cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectY};
|
instructions[0x53] = Instruction{.cycles = 8, .execute = &CPU::SRE, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x67] = Instruction{.name = "RRA", .cycles = 5, .execute = &CPU::RRA, .fetch = &CPU::ZeroPage};
|
instructions[0x67] = Instruction{.cycles = 5, .execute = &CPU::RRA, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x77] = Instruction{.name = "RRA", .cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::ZeroPageX};
|
instructions[0x77] = Instruction{.cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0x6F] = Instruction{.name = "RRA", .cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::Absolute};
|
instructions[0x6F] = Instruction{.cycles = 6, .execute = &CPU::RRA, .fetch = &CPU::Absolute};
|
||||||
instructions[0x7F] = Instruction{.name = "RRA", .cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteX};
|
instructions[0x7F] = Instruction{.cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0x7B] = Instruction{.name = "RRA", .cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteY};
|
instructions[0x7B] = Instruction{.cycles = 7, .execute = &CPU::RRA, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0x63] = Instruction{.name = "RRA", .cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectX};
|
instructions[0x63] = Instruction{.cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectX};
|
||||||
instructions[0x73] = Instruction{.name = "RRA", .cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectY};
|
instructions[0x73] = Instruction{.cycles = 8, .execute = &CPU::RRA, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x87] = Instruction{.name = "SAX", .cycles = 3, .execute = &CPU::SAX, .fetch = &CPU::ZeroPage};
|
instructions[0x87] = Instruction{.cycles = 3, .execute = &CPU::SAX, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0x97] = Instruction{.name = "SAX", .cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::ZeroPageY};
|
instructions[0x97] = Instruction{.cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::ZeroPageY};
|
||||||
instructions[0x8F] = Instruction{.name = "SAX", .cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::Absolute};
|
instructions[0x8F] = Instruction{.cycles = 4, .execute = &CPU::SAX, .fetch = &CPU::Absolute};
|
||||||
instructions[0x83] = Instruction{.name = "SAX", .cycles = 6, .execute = &CPU::SAX, .fetch = &CPU::IndirectX};
|
instructions[0x83] = Instruction{.cycles = 6, .execute = &CPU::SAX, .fetch = &CPU::IndirectX};
|
||||||
|
|
||||||
instructions[0xEB] = Instruction{.name = "SBC", .cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate};
|
instructions[0xEB] = Instruction{.cycles = 2, .execute = &CPU::SBC, .fetch = &CPU::Immediate};
|
||||||
|
|
||||||
instructions[0xAB] = Instruction{.name = "LAX", .cycles = 2, .execute = &CPU::LAX, .fetch = &CPU::Immediate};
|
instructions[0xAB] = Instruction{.cycles = 2, .execute = &CPU::LAX, .fetch = &CPU::Immediate};
|
||||||
instructions[0xA7] = Instruction{.name = "LAX", .cycles = 3, .execute = &CPU::LAX, .fetch = &CPU::ZeroPage};
|
instructions[0xA7] = Instruction{.cycles = 3, .execute = &CPU::LAX, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xB7] = Instruction{.name = "LAX", .cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::ZeroPageY};
|
instructions[0xB7] = Instruction{.cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::ZeroPageY};
|
||||||
instructions[0xAF] = Instruction{.name = "LAX", .cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::Absolute};
|
instructions[0xAF] = Instruction{.cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::Absolute};
|
||||||
instructions[0xBF] = Instruction{.name = "LAX", .cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::AbsoluteY};
|
instructions[0xBF] = Instruction{.cycles = 4, .execute = &CPU::LAX, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0xA3] = Instruction{.name = "LAX", .cycles = 6, .execute = &CPU::LAX, .fetch = &CPU::IndirectX};
|
instructions[0xA3] = Instruction{.cycles = 6, .execute = &CPU::LAX, .fetch = &CPU::IndirectX};
|
||||||
instructions[0xB3] = Instruction{.name = "LAX", .cycles = 5, .execute = &CPU::LAX, .fetch = &CPU::IndirectY};
|
instructions[0xB3] = Instruction{.cycles = 5, .execute = &CPU::LAX, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0xC7] = Instruction{.name = "DCP", .cycles = 5, .execute = &CPU::DCP, .fetch = &CPU::ZeroPage};
|
instructions[0xC7] = Instruction{.cycles = 5, .execute = &CPU::DCP, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xD7] = Instruction{.name = "DCP", .cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::ZeroPageX};
|
instructions[0xD7] = Instruction{.cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xCF] = Instruction{.name = "DCP", .cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::Absolute};
|
instructions[0xCF] = Instruction{.cycles = 6, .execute = &CPU::DCP, .fetch = &CPU::Absolute};
|
||||||
instructions[0xDF] = Instruction{.name = "DCP", .cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteX};
|
instructions[0xDF] = Instruction{.cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xDB] = Instruction{.name = "DCP", .cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteY};
|
instructions[0xDB] = Instruction{.cycles = 7, .execute = &CPU::DCP, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0xC3] = Instruction{.name = "DCP", .cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectX};
|
instructions[0xC3] = Instruction{.cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectX};
|
||||||
instructions[0xD3] = Instruction{.name = "DCP", .cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectY};
|
instructions[0xD3] = Instruction{.cycles = 8, .execute = &CPU::DCP, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0xE7] = Instruction{.name = "ISC", .cycles = 5, .execute = &CPU::ISC, .fetch = &CPU::ZeroPage};
|
instructions[0xE7] = Instruction{.cycles = 5, .execute = &CPU::ISC, .fetch = &CPU::ZeroPage};
|
||||||
instructions[0xF7] = Instruction{.name = "ISC", .cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::ZeroPageX};
|
instructions[0xF7] = Instruction{.cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::ZeroPageX};
|
||||||
instructions[0xEF] = Instruction{.name = "ISC", .cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::Absolute};
|
instructions[0xEF] = Instruction{.cycles = 6, .execute = &CPU::ISC, .fetch = &CPU::Absolute};
|
||||||
instructions[0xFF] = Instruction{.name = "ISC", .cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteX};
|
instructions[0xFF] = Instruction{.cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteX};
|
||||||
instructions[0xFB] = Instruction{.name = "ISC", .cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteY};
|
instructions[0xFB] = Instruction{.cycles = 7, .execute = &CPU::ISC, .fetch = &CPU::AbsoluteY};
|
||||||
instructions[0xE3] = Instruction{.name = "ISC", .cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectX};
|
instructions[0xE3] = Instruction{.cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectX};
|
||||||
instructions[0xF3] = Instruction{.name = "ISC", .cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectY};
|
instructions[0xF3] = Instruction{.cycles = 8, .execute = &CPU::ISC, .fetch = &CPU::IndirectY};
|
||||||
|
|
||||||
instructions[0x0B] = Instruction{.name = "ANC", .cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate};
|
instructions[0x0B] = Instruction{.cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate};
|
||||||
instructions[0x2B] = Instruction{.name = "ANC", .cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate};
|
instructions[0x2B] = Instruction{.cycles = 2, .execute = &CPU::ANC, .fetch = &CPU::Immediate};
|
||||||
instructions[0x4B] = Instruction{.name = "ALR", .cycles = 0, .execute = &CPU::ALR, .fetch = &CPU::Immediate};
|
instructions[0x4B] = Instruction{.cycles = 0, .execute = &CPU::ALR, .fetch = &CPU::Immediate};
|
||||||
instructions[0x6B] = Instruction{.name = "ARR", .cycles = 0, .execute = &CPU::ARR, .fetch = &CPU::Immediate};
|
instructions[0x6B] = Instruction{.cycles = 0, .execute = &CPU::ARR, .fetch = &CPU::Immediate};
|
||||||
instructions[0xCB] = Instruction{.name = "AXS", .cycles = 0, .execute = &CPU::AXS, .fetch = &CPU::Immediate};
|
instructions[0xCB] = Instruction{.cycles = 0, .execute = &CPU::AXS, .fetch = &CPU::Immediate};
|
||||||
|
|
||||||
// Unused by NES
|
// Unused by NES
|
||||||
instructions[0x9C] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x9C] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x9E] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x9E] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x8B] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x8B] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x93] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x93] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x9B] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x9B] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x9F] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x9F] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0xBB] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xBB] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x02] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x02] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x12] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x12] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x22] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x22] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x32] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x32] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x42] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x42] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x52] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x52] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x62] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x62] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x72] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x72] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0x92] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0x92] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0xB2] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xB2] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0xD2] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xD2] = Instruction{.cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
||||||
instructions[0xF2] = Instruction{.name = "ILL", .cycles = 0, .execute = &CPU::NOP, .fetch = &CPU::Implicit};
|
instructions[0xF2] = Instruction{.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)
|
void CPU::StackPush(uint8_t val)
|
||||||
|
@ -348,135 +329,179 @@ uint8_t CPU::StackPop()
|
||||||
return ReadMemory(address);
|
return ReadMemory(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t CPU::Cycle()
|
void CPU::WriteMemory(uint16_t address, uint8_t value)
|
||||||
{
|
{
|
||||||
if (nes->nmi)
|
// First five bits of address determines its mapping
|
||||||
|
switch ((address & 0xF800u) >> 11u)
|
||||||
{
|
{
|
||||||
NMI();
|
// RAM and mirrors
|
||||||
|
case 0x00:
|
||||||
|
case 0x01:
|
||||||
|
case 0x02:
|
||||||
|
case 0x03:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x7FFu;
|
||||||
|
|
||||||
|
ram[translatedAddress] = value;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SRAM and mirrors
|
||||||
|
case 0x0C:
|
||||||
|
case 0x0D:
|
||||||
|
case 0x0E:
|
||||||
|
case 0x0F:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x1FFFu;
|
||||||
|
|
||||||
|
sram[translatedAddress] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROM (Low) and mirrors
|
||||||
|
case 0x10:
|
||||||
|
case 0x11:
|
||||||
|
case 0x12:
|
||||||
|
case 0x13:
|
||||||
|
case 0x14:
|
||||||
|
case 0x15:
|
||||||
|
case 0x16:
|
||||||
|
case 0x17:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x3FFFu;
|
||||||
|
|
||||||
|
romLow[translatedAddress] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROM (High) and mirrors
|
||||||
|
case 0x18:
|
||||||
|
case 0x19:
|
||||||
|
case 0x1A:
|
||||||
|
case 0x1B:
|
||||||
|
case 0x1C:
|
||||||
|
case 0x1D:
|
||||||
|
case 0x1E:
|
||||||
|
case 0x1F:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x3FFFu;
|
||||||
|
|
||||||
|
romHigh[translatedAddress] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t CPU::ReadMemory(uint16_t address)
|
||||||
|
{
|
||||||
|
uint8_t byte{};
|
||||||
|
|
||||||
|
switch ((address & 0xF800u) >> 11u)
|
||||||
|
{
|
||||||
|
// RAM and mirrors
|
||||||
|
case 0x00:
|
||||||
|
case 0x01:
|
||||||
|
case 0x02:
|
||||||
|
case 0x03:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x7FFu;
|
||||||
|
|
||||||
|
byte = ram[translatedAddress];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRAM and mirrors
|
||||||
|
case 0x0C:
|
||||||
|
case 0x0D:
|
||||||
|
case 0x0E:
|
||||||
|
case 0x0F:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x1FFFu;
|
||||||
|
|
||||||
|
byte = sram[translatedAddress];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROM (Low) and mirrors
|
||||||
|
case 0x10:
|
||||||
|
case 0x11:
|
||||||
|
case 0x12:
|
||||||
|
case 0x13:
|
||||||
|
case 0x14:
|
||||||
|
case 0x15:
|
||||||
|
case 0x16:
|
||||||
|
case 0x17:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x3FFFu;
|
||||||
|
|
||||||
|
byte = romLow[translatedAddress];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROM (High) and mirrors
|
||||||
|
case 0x18:
|
||||||
|
case 0x19:
|
||||||
|
case 0x1A:
|
||||||
|
case 0x1B:
|
||||||
|
case 0x1C:
|
||||||
|
case 0x1D:
|
||||||
|
case 0x1E:
|
||||||
|
case 0x1F:
|
||||||
|
{
|
||||||
|
uint16_t translatedAddress = address & 0x3FFFu;
|
||||||
|
|
||||||
|
byte = romHigh[translatedAddress];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::LoadRom(const char* filename)
|
||||||
|
{
|
||||||
|
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||||
|
|
||||||
|
if (file.is_open())
|
||||||
|
{
|
||||||
|
std::streampos size = file.tellg();
|
||||||
|
char* buffer = new char[size];
|
||||||
|
file.seekg(0x10, std::ios::beg);
|
||||||
|
file.read(buffer, size);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
for (long i = 0; i < 0x4000; ++i)
|
||||||
|
{
|
||||||
|
WriteMemory(0x8000 + i, buffer[i]);
|
||||||
|
WriteMemory(0xC000 + i, buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::Cycle()
|
||||||
|
{
|
||||||
opcode = ReadMemory(pc);
|
opcode = ReadMemory(pc);
|
||||||
|
|
||||||
//Log();
|
#ifndef NDEBUG
|
||||||
|
Log(opcode, pc);
|
||||||
|
#endif
|
||||||
|
|
||||||
pageBoundaryCrossed = false;
|
pageBoundaryCrossed = false;
|
||||||
prevCycles = cycles;
|
|
||||||
|
|
||||||
((*this).*(instructions[opcode].fetch))();
|
((*this).*(instructions[opcode].fetch))();
|
||||||
|
|
||||||
((*this).*(instructions[opcode].execute))();
|
((*this).*(instructions[opcode].execute))();
|
||||||
|
|
||||||
cycles += instructions[opcode].cycles;
|
cycles += instructions[opcode].cycles;
|
||||||
|
|
||||||
return cycles - prevCycles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::NMI()
|
void CPU::Log(uint8_t instruction, uint16_t pc)
|
||||||
{
|
{
|
||||||
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("%04X ", pc);
|
||||||
|
|
||||||
if (instruction.fetch == &CPU::Implicit)
|
printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X PPU:000,000 CYC:%lu\n", acc, x, y, status.GetByte(), sp, cycles);
|
||||||
{
|
|
||||||
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)
|
bool CPU::TestBits(uint8_t value, uint8_t bits)
|
||||||
|
@ -500,13 +525,3 @@ uint16_t CPU::ComposeAddress(uint8_t msb, uint8_t lsb)
|
||||||
{
|
{
|
||||||
return (msb << 8u) | 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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,15 +7,13 @@
|
||||||
const unsigned int INSTRUCTION_COUNT = 256;
|
const unsigned int INSTRUCTION_COUNT = 256;
|
||||||
const unsigned int RAM_SIZE = 2048;
|
const unsigned int RAM_SIZE = 2048;
|
||||||
const unsigned int SRAM_SIZE = 8192;
|
const unsigned int SRAM_SIZE = 8192;
|
||||||
|
const unsigned int ROM_SIZE = 16384;
|
||||||
const unsigned int IRQ_VECTOR_MSB = 0xFFFF;
|
const unsigned int IRQ_VECTOR_MSB = 0xFFFF;
|
||||||
const unsigned int IRQ_VECTOR_LSB = 0xFFFE;
|
const unsigned int IRQ_VECTOR_LSB = 0xFFFE;
|
||||||
|
|
||||||
class NES;
|
|
||||||
|
|
||||||
class Status
|
class Status
|
||||||
{
|
{
|
||||||
friend class NES;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void SetByte(uint8_t byte)
|
void SetByte(uint8_t byte)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +48,6 @@ class CPU;
|
||||||
class Instruction
|
class Instruction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
char const* name;
|
|
||||||
uint8_t cycles;
|
uint8_t cycles;
|
||||||
|
|
||||||
using CpuFunc = void (CPU::*)();
|
using CpuFunc = void (CPU::*)();
|
||||||
|
@ -63,18 +60,16 @@ class CPU
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CPU();
|
CPU();
|
||||||
uint64_t Cycle();
|
void LoadRom(const char* filename);
|
||||||
void Log();
|
void Cycle();
|
||||||
void Reset();
|
void Log(uint8_t instruction, uint16_t pc);
|
||||||
void NMI();
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void StackPush(uint8_t val);
|
||||||
|
uint8_t StackPop();
|
||||||
void WriteMemory(uint16_t address, uint8_t value);
|
void WriteMemory(uint16_t address, uint8_t value);
|
||||||
uint8_t ReadMemory(uint16_t address);
|
uint8_t ReadMemory(uint16_t address);
|
||||||
|
|
||||||
friend class NES;
|
|
||||||
|
|
||||||
void StackPush(uint8_t val);
|
|
||||||
uint8_t StackPop();
|
|
||||||
|
|
||||||
// --------------- HELPERS --------------- //
|
// --------------- HELPERS --------------- //
|
||||||
static bool TestBits(uint8_t value, uint8_t bits);
|
static bool TestBits(uint8_t value, uint8_t bits);
|
||||||
|
@ -224,15 +219,17 @@ public:
|
||||||
// Machine features
|
// Machine features
|
||||||
uint8_t ram[RAM_SIZE]{};
|
uint8_t ram[RAM_SIZE]{};
|
||||||
uint8_t sram[SRAM_SIZE]{};
|
uint8_t sram[SRAM_SIZE]{};
|
||||||
uint16_t pc{};
|
uint8_t romLow[ROM_SIZE]{};
|
||||||
uint8_t sp{};
|
uint8_t romHigh[ROM_SIZE]{};
|
||||||
|
uint16_t pc = 0xC000;
|
||||||
|
uint8_t sp = 0xFD;
|
||||||
uint8_t acc{};
|
uint8_t acc{};
|
||||||
uint8_t x{};
|
uint8_t x{};
|
||||||
uint8_t y{};
|
uint8_t y{};
|
||||||
Status status{};
|
Status status{};
|
||||||
uint64_t prevCycles{};
|
uint64_t cycles = 7;
|
||||||
uint64_t cycles{};
|
|
||||||
bool irq{};
|
bool irq{};
|
||||||
|
bool nmi{};
|
||||||
|
|
||||||
// Emulator variables
|
// Emulator variables
|
||||||
bool pageBoundaryCrossed{};
|
bool pageBoundaryCrossed{};
|
||||||
|
@ -241,6 +238,6 @@ public:
|
||||||
uint8_t fetchedByte{};
|
uint8_t fetchedByte{};
|
||||||
|
|
||||||
Instruction instructions[INSTRUCTION_COUNT]{};
|
Instruction instructions[INSTRUCTION_COUNT]{};
|
||||||
|
|
||||||
NES* nes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include "CPU/CPU.hpp"
|
#include "CPU/CPU.hpp"
|
||||||
#include "NES.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
// --------------- ARITHMETIC --------------- //
|
// --------------- ARITHMETIC --------------- //
|
||||||
void CPU::ADC()
|
void CPU::ADC()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
uint16_t tempSum = acc + fetchedByte;
|
uint16_t tempSum = acc + fetchedByte;
|
||||||
|
|
||||||
tempSum += status.carry;
|
tempSum += status.carry;
|
||||||
|
@ -29,7 +27,6 @@ void CPU::ADC()
|
||||||
|
|
||||||
void CPU::SBC()
|
void CPU::SBC()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
uint16_t tempDiff = acc - fetchedByte;
|
uint16_t tempDiff = acc - fetchedByte;
|
||||||
|
|
||||||
tempDiff -= (1u - status.carry);
|
tempDiff -= (1u - status.carry);
|
||||||
|
@ -54,7 +51,6 @@ void CPU::SBC()
|
||||||
|
|
||||||
void CPU::CMP()
|
void CPU::CMP()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
status.carry = (acc >= fetchedByte) ? 1u : 0u;
|
status.carry = (acc >= fetchedByte) ? 1u : 0u;
|
||||||
status.zero = (acc == fetchedByte) ? 1u : 0u;
|
status.zero = (acc == fetchedByte) ? 1u : 0u;
|
||||||
status.negative = (IsNegative(acc - fetchedByte)) ? 1u : 0u;
|
status.negative = (IsNegative(acc - fetchedByte)) ? 1u : 0u;
|
||||||
|
@ -64,7 +60,6 @@ void CPU::CMP()
|
||||||
|
|
||||||
void CPU::CPX()
|
void CPU::CPX()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
status.carry = (x >= fetchedByte) ? 1u : 0u;
|
status.carry = (x >= fetchedByte) ? 1u : 0u;
|
||||||
status.zero = (x == fetchedByte) ? 1u : 0u;
|
status.zero = (x == fetchedByte) ? 1u : 0u;
|
||||||
status.negative = (IsNegative(x - fetchedByte)) ? 1u : 0u;
|
status.negative = (IsNegative(x - fetchedByte)) ? 1u : 0u;
|
||||||
|
@ -72,7 +67,6 @@ void CPU::CPX()
|
||||||
|
|
||||||
void CPU::CPY()
|
void CPU::CPY()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
status.carry = (y >= fetchedByte) ? 1u : 0u;
|
status.carry = (y >= fetchedByte) ? 1u : 0u;
|
||||||
status.zero = (y == fetchedByte) ? 1u : 0u;
|
status.zero = (y == fetchedByte) ? 1u : 0u;
|
||||||
status.negative = (IsNegative(y - fetchedByte)) ? 1u : 0u;
|
status.negative = (IsNegative(y - fetchedByte)) ? 1u : 0u;
|
||||||
|
@ -164,10 +158,9 @@ void CPU::BEQ()
|
||||||
// --------------- INCREMENT/DECREMENT --------------- //
|
// --------------- INCREMENT/DECREMENT --------------- //
|
||||||
void CPU::INC()
|
void CPU::INC()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
++fetchedByte;
|
++fetchedByte;
|
||||||
|
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
WriteMemory(fetchedAddress, fetchedByte);
|
||||||
|
|
||||||
status.zero = fetchedByte ? 0u : 1u;
|
status.zero = fetchedByte ? 0u : 1u;
|
||||||
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
||||||
|
@ -191,10 +184,9 @@ void CPU::INY()
|
||||||
|
|
||||||
void CPU::DEC()
|
void CPU::DEC()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
--fetchedByte;
|
--fetchedByte;
|
||||||
|
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
WriteMemory(fetchedAddress, fetchedByte);
|
||||||
|
|
||||||
status.zero = fetchedByte ? 0u : 1u;
|
status.zero = fetchedByte ? 0u : 1u;
|
||||||
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
||||||
|
@ -248,7 +240,6 @@ void CPU::RTS()
|
||||||
// --------------- LOAD/STORE --------------- //
|
// --------------- LOAD/STORE --------------- //
|
||||||
void CPU::LDA()
|
void CPU::LDA()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||||
|
|
||||||
acc = fetchedByte;
|
acc = fetchedByte;
|
||||||
|
@ -259,7 +250,6 @@ void CPU::LDA()
|
||||||
|
|
||||||
void CPU::LDX()
|
void CPU::LDX()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||||
|
|
||||||
x = fetchedByte;
|
x = fetchedByte;
|
||||||
|
@ -270,7 +260,6 @@ void CPU::LDX()
|
||||||
|
|
||||||
void CPU::LDY()
|
void CPU::LDY()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||||
|
|
||||||
y = fetchedByte;
|
y = fetchedByte;
|
||||||
|
@ -281,25 +270,23 @@ void CPU::LDY()
|
||||||
|
|
||||||
void CPU::STA()
|
void CPU::STA()
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, acc);
|
WriteMemory(fetchedAddress, acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::STX()
|
void CPU::STX()
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, x);
|
WriteMemory(fetchedAddress, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::STY()
|
void CPU::STY()
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, y);
|
WriteMemory(fetchedAddress, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --------------- LOGICAL --------------- //
|
// --------------- LOGICAL --------------- //
|
||||||
void CPU::AND()
|
void CPU::AND()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
|
|
||||||
acc &= fetchedByte;
|
acc &= fetchedByte;
|
||||||
|
|
||||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||||
|
@ -309,7 +296,6 @@ void CPU::AND()
|
||||||
|
|
||||||
void CPU::EOR()
|
void CPU::EOR()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
acc ^= fetchedByte;
|
acc ^= fetchedByte;
|
||||||
|
|
||||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||||
|
@ -319,7 +305,6 @@ void CPU::EOR()
|
||||||
|
|
||||||
void CPU::ORA()
|
void CPU::ORA()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
acc |= fetchedByte;
|
acc |= fetchedByte;
|
||||||
|
|
||||||
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
cycles += (pageBoundaryCrossed) ? 1u : 0u;
|
||||||
|
@ -329,7 +314,6 @@ void CPU::ORA()
|
||||||
|
|
||||||
void CPU::BIT()
|
void CPU::BIT()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
status.zero = (acc & fetchedByte) ? 0u : 1u;
|
status.zero = (acc & fetchedByte) ? 0u : 1u;
|
||||||
status.overflow = (TestBits(fetchedByte, (1u << 6u))) ? 1u : 0u;
|
status.overflow = (TestBits(fetchedByte, (1u << 6u))) ? 1u : 0u;
|
||||||
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
status.negative = (IsNegative(fetchedByte)) ? 1u : 0u;
|
||||||
|
@ -339,15 +323,6 @@ void CPU::BIT()
|
||||||
// --------------- SHIFTS --------------- //
|
// --------------- SHIFTS --------------- //
|
||||||
void CPU::ASL()
|
void CPU::ASL()
|
||||||
{
|
{
|
||||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
|
||||||
{
|
|
||||||
fetchedByte = acc;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set carry to fetchedByte of MSB pre-shift
|
// Set carry to fetchedByte of MSB pre-shift
|
||||||
if (TestBits(fetchedByte, 0x80))
|
if (TestBits(fetchedByte, 0x80))
|
||||||
{
|
{
|
||||||
|
@ -369,21 +344,12 @@ void CPU::ASL()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
WriteMemory(fetchedAddress, fetchedByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::LSR()
|
void CPU::LSR()
|
||||||
{
|
{
|
||||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
|
||||||
{
|
|
||||||
fetchedByte = acc;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set carry to fetchedByte of LSB pre-shift
|
// Set carry to fetchedByte of LSB pre-shift
|
||||||
if (TestBits(fetchedByte, 0x01))
|
if (TestBits(fetchedByte, 0x01))
|
||||||
{
|
{
|
||||||
|
@ -405,21 +371,12 @@ void CPU::LSR()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
WriteMemory(fetchedAddress, fetchedByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ROL()
|
void CPU::ROL()
|
||||||
{
|
{
|
||||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
|
||||||
{
|
|
||||||
fetchedByte = acc;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t oldCarry = status.carry;
|
uint8_t oldCarry = status.carry;
|
||||||
|
|
||||||
// Set carry to fetchedByte of MSB pre-shift
|
// Set carry to fetchedByte of MSB pre-shift
|
||||||
|
@ -449,21 +406,12 @@ void CPU::ROL()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
WriteMemory(fetchedAddress, fetchedByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ROR()
|
void CPU::ROR()
|
||||||
{
|
{
|
||||||
if (instructions[opcode].fetch == &CPU::Implicit)
|
|
||||||
{
|
|
||||||
fetchedByte = acc;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t oldCarry = status.carry;
|
uint8_t oldCarry = status.carry;
|
||||||
|
|
||||||
// Set carry to fetchedByte of LSB pre-shift
|
// Set carry to fetchedByte of LSB pre-shift
|
||||||
|
@ -493,7 +441,7 @@ void CPU::ROR()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, fetchedByte);
|
WriteMemory(fetchedAddress, fetchedByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,8 +531,8 @@ void CPU::BRK()
|
||||||
|
|
||||||
StackPush(statusCopy.GetByte());
|
StackPush(statusCopy.GetByte());
|
||||||
|
|
||||||
pcMsb = nes->Read(BusSource::CPU, IRQ_VECTOR_MSB);
|
pcMsb = ReadMemory(IRQ_VECTOR_MSB);
|
||||||
pcLsb = nes->Read(BusSource::CPU, IRQ_VECTOR_LSB);
|
pcLsb = ReadMemory(IRQ_VECTOR_LSB);
|
||||||
|
|
||||||
pc = ComposeAddress(pcMsb, pcLsb);
|
pc = ComposeAddress(pcMsb, pcLsb);
|
||||||
}
|
}
|
||||||
|
@ -667,7 +615,7 @@ void CPU::SAX()
|
||||||
{
|
{
|
||||||
uint8_t byte = acc & x;
|
uint8_t byte = acc & x;
|
||||||
|
|
||||||
nes->Write(BusSource::CPU, fetchedAddress, byte);
|
WriteMemory(fetchedAddress, byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::DCP()
|
void CPU::DCP()
|
||||||
|
@ -742,7 +690,6 @@ void CPU::ARR()
|
||||||
|
|
||||||
void CPU::AXS()
|
void CPU::AXS()
|
||||||
{
|
{
|
||||||
fetchedByte = nes->Read(BusSource::CPU, fetchedAddress);
|
|
||||||
x = (acc & x) - fetchedByte;
|
x = (acc & x) - fetchedByte;
|
||||||
|
|
||||||
status.negative = (IsNegative(x)) ? 1u : 0u;
|
status.negative = (IsNegative(x)) ? 1u : 0u;
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
#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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
#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;
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
#include "Input.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
void Input::Strobe(uint8_t* but)
|
|
||||||
{
|
|
||||||
if (strobe)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
|
||||||
buttons[i] = but[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Input::Poll()
|
|
||||||
{
|
|
||||||
uint8_t buttonState;
|
|
||||||
|
|
||||||
// Strobe set - return state of A button
|
|
||||||
if (strobe)
|
|
||||||
{
|
|
||||||
buttonState = buttons[Buttons::A];
|
|
||||||
}
|
|
||||||
|
|
||||||
// All 8 bits read - return 1u
|
|
||||||
else if (buttonIndex > Buttons::Right)
|
|
||||||
{
|
|
||||||
buttonState = 1u;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return next bit
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buttonState = buttons[buttonIndex];
|
|
||||||
|
|
||||||
++buttonIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttonState;
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
|
|
||||||
enum Buttons
|
|
||||||
{
|
|
||||||
A = 0,
|
|
||||||
B = 1,
|
|
||||||
Select = 2,
|
|
||||||
Start = 3,
|
|
||||||
Up = 4,
|
|
||||||
Down = 5,
|
|
||||||
Left = 6,
|
|
||||||
Right = 7
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Input
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void Strobe(uint8_t* but);
|
|
||||||
uint8_t Poll();
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class NES;
|
|
||||||
|
|
||||||
bool strobe{};
|
|
||||||
uint8_t buttonIndex{};
|
|
||||||
uint8_t buttons[8]{};
|
|
||||||
};
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "NES.hpp"
|
#include "CPU/CPU.hpp"
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
|
@ -10,9 +10,12 @@ int main(int argc, char** argv)
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
char const* filename = argv[1];
|
CPU cpu;
|
||||||
|
|
||||||
NES nes;
|
cpu.LoadRom("nestest.nes");
|
||||||
nes.InsertCartridge(filename);
|
|
||||||
nes.Run();
|
while (true)
|
||||||
|
{
|
||||||
|
cpu.Cycle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
374
Source/NES.cpp
374
Source/NES.cpp
|
@ -1,374 +0,0 @@
|
||||||
#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;
|
|
||||||
const unsigned int PPU_CYCLES_PER_CPU_CYCLE = 3u;
|
|
||||||
|
|
||||||
|
|
||||||
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)),
|
|
||||||
input(std::make_unique<Input>())
|
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
uint8_t buttons[8]{};
|
|
||||||
|
|
||||||
while (!platform->ProcessInput(buttons))
|
|
||||||
{
|
|
||||||
input->Strobe(buttons);
|
|
||||||
|
|
||||||
uint64_t cpuCycles = cpu->Cycle();
|
|
||||||
|
|
||||||
for (auto i = 0; i < cpuCycles * PPU_CYCLES_PER_CPU_CYCLE; ++i)
|
|
||||||
{
|
|
||||||
ppu->Tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ppu->currentScanline == 261)
|
|
||||||
{
|
|
||||||
//DrawDebugLines(ppu->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<PPU::Register>(address % 8u);
|
|
||||||
|
|
||||||
ppu->WriteRegister(index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// APU and IO
|
|
||||||
else if (address < 0x4020u)
|
|
||||||
{
|
|
||||||
uint16_t index = address & 0x1Fu;
|
|
||||||
|
|
||||||
// PPU OAM
|
|
||||||
if (index == 0x14u)
|
|
||||||
{
|
|
||||||
uint8_t addrMsb = value;
|
|
||||||
|
|
||||||
for (uint16_t addrLsb = 0x00; addrLsb <= 0xFF; ++addrLsb)
|
|
||||||
{
|
|
||||||
uint16_t addr = (addrMsb << 8u) | addrLsb;
|
|
||||||
|
|
||||||
ppu->oam[ppu->regOAMADDR + addrLsb] = Read(BusSource::CPU, addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controller
|
|
||||||
else if (index == 0x16u)
|
|
||||||
{
|
|
||||||
if (value & 0x1u)
|
|
||||||
{
|
|
||||||
input->strobe = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
input->strobe = false;
|
|
||||||
input->buttonIndex = 0u;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 & 0x1Cu) >> 2u;
|
|
||||||
uint8_t entry = address & 0x03u;
|
|
||||||
|
|
||||||
ppu->palette[index][entry] = 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<PPU::Register>(address % 8u);
|
|
||||||
|
|
||||||
byte = ppu->ReadRegister(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// APU and IO
|
|
||||||
else if (address < 0x4020u)
|
|
||||||
{
|
|
||||||
uint16_t index = address & 0x1Fu;
|
|
||||||
|
|
||||||
// Controller
|
|
||||||
if (index == 0x16u)
|
|
||||||
{
|
|
||||||
byte = input->Poll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 & 0x0Cu;
|
|
||||||
uint8_t entry = address & 0x03u;
|
|
||||||
|
|
||||||
byte = ppu->palette[index][entry];
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return byte;
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Cartridge.hpp"
|
|
||||||
#include "CPU/CPU.hpp"
|
|
||||||
#include "Input.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;
|
|
||||||
std::unique_ptr<Input> input;
|
|
||||||
|
|
||||||
bool nmi{};
|
|
||||||
Color palette[64];
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,703 +0,0 @@
|
||||||
#include "PPU.hpp"
|
|
||||||
#include "NES.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
const unsigned int BG_PALETTE_START_ADDRESS = 0x3F00u;
|
|
||||||
const unsigned int PATTERN_TABLE_0_START = 0x0000u;
|
|
||||||
const unsigned int PATTERN_TABLE_1_START = 0x1000u;
|
|
||||||
const unsigned int ATTRIBUTE_TABLE_START = 0x23C0u;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CheckBit(uint32_t value, uint32_t bit)
|
|
||||||
{
|
|
||||||
return (value & bit) == bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
PPU::PPU()
|
|
||||||
{
|
|
||||||
// Visible Frame
|
|
||||||
{
|
|
||||||
for (int scanline = 0; scanline <= 239; ++scanline)
|
|
||||||
{
|
|
||||||
// Data fetching
|
|
||||||
for (int cycle = 1; cycle <= 256; cycle += 8)
|
|
||||||
{
|
|
||||||
events[scanline][cycle + 0] |= Event::FETCH_NT;
|
|
||||||
events[scanline][cycle + 2] |= Event::FETCH_AT;
|
|
||||||
events[scanline][cycle + 4] |= Event::FETCH_PT_LOW;
|
|
||||||
events[scanline][cycle + 6] |= Event::FETCH_PT_HIGH;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incrementing Hori(V)
|
|
||||||
for (int cycle = 8; cycle <= 256; cycle += 8)
|
|
||||||
{
|
|
||||||
events[scanline][cycle] |= Event::INCREMENT_TILE_X;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw and shift
|
|
||||||
for (int cycle = 2; cycle <= 257; ++cycle)
|
|
||||||
{
|
|
||||||
events[scanline][cycle] |= Event::DRAW | Event::SHIFT_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload shift registers
|
|
||||||
for (int cycle = 9; cycle <= 257; cycle += 8)
|
|
||||||
{
|
|
||||||
events[scanline][cycle] |= Event::LOAD_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inc Vert(v)
|
|
||||||
events[scanline][256] |= Event::INCREMENT_TILE_Y;
|
|
||||||
|
|
||||||
// Hori(v) = Hori(T)
|
|
||||||
events[scanline][257] |= Event::SET_TILE_X;
|
|
||||||
|
|
||||||
// Data fetching
|
|
||||||
for (int cycle = 321; cycle <= 336; cycle += 8)
|
|
||||||
{
|
|
||||||
events[scanline][cycle + 0] |= Event::FETCH_NT;
|
|
||||||
events[scanline][cycle + 2] |= Event::FETCH_AT;
|
|
||||||
events[scanline][cycle + 4] |= Event::FETCH_PT_LOW;
|
|
||||||
events[scanline][cycle + 6] |= Event::FETCH_PT_HIGH;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift
|
|
||||||
for (int cycle = 322; cycle <= 337; ++cycle)
|
|
||||||
{
|
|
||||||
events[scanline][cycle] |= Event::SHIFT_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload shift registers
|
|
||||||
for (int cycle = 329; cycle <= 337; cycle += 8)
|
|
||||||
{
|
|
||||||
events[scanline][cycle] |= Event::LOAD_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
events[scanline][328] |= Event::INCREMENT_TILE_X;
|
|
||||||
events[scanline][336] |= Event::INCREMENT_TILE_X;
|
|
||||||
|
|
||||||
// Unused Fetches
|
|
||||||
events[scanline][337] |= Event::FETCH_NT;
|
|
||||||
events[scanline][339] |= Event::FETCH_NT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VBlank
|
|
||||||
{
|
|
||||||
events[241][1] |= Event::SET_VBLANK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-render
|
|
||||||
{
|
|
||||||
// Clear VBlank, Sprite0 Hit, and Sprite Overflow
|
|
||||||
events[261][1] |= Event::CLEAR_FLAGS;
|
|
||||||
|
|
||||||
// Data fetching
|
|
||||||
for (int cycle = 1; cycle <= 256; cycle += 8)
|
|
||||||
{
|
|
||||||
events[261][cycle + 0] |= Event::FETCH_NT;
|
|
||||||
events[261][cycle + 2] |= Event::FETCH_AT;
|
|
||||||
events[261][cycle + 4] |= Event::FETCH_PT_LOW;
|
|
||||||
events[261][cycle + 6] |= Event::FETCH_PT_HIGH;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incrementing Hori(V)
|
|
||||||
for (int cycle = 8; cycle <= 256; cycle += 8)
|
|
||||||
{
|
|
||||||
events[261][cycle] |= Event::INCREMENT_TILE_X;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift
|
|
||||||
for (int cycle = 2; cycle <= 257; ++cycle)
|
|
||||||
{
|
|
||||||
events[261][cycle] |= Event::SHIFT_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload shift registers
|
|
||||||
for (int cycle = 9; cycle <= 257; cycle += 8)
|
|
||||||
{
|
|
||||||
events[261][cycle] |= Event::LOAD_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inc Vert(v)
|
|
||||||
events[261][256] |= Event::INCREMENT_TILE_Y;
|
|
||||||
|
|
||||||
// Hori(v) = Hori(t)
|
|
||||||
events[261][257] |= Event::SET_TILE_X;
|
|
||||||
|
|
||||||
// Vert(v) = Vert(t)
|
|
||||||
for (int cycle = 280; cycle <= 304; ++cycle)
|
|
||||||
{
|
|
||||||
events[261][cycle] |= Event::SET_TILE_Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data fetching
|
|
||||||
for (int cycle = 321; cycle <= 336; cycle += 8)
|
|
||||||
{
|
|
||||||
events[261][cycle + 0] |= Event::FETCH_NT;
|
|
||||||
events[261][cycle + 2] |= Event::FETCH_AT;
|
|
||||||
events[261][cycle + 4] |= Event::FETCH_PT_LOW;
|
|
||||||
events[261][cycle + 6] |= Event::FETCH_PT_HIGH;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift
|
|
||||||
for (int cycle = 322; cycle <= 337; ++cycle)
|
|
||||||
{
|
|
||||||
events[261][cycle] |= Event::SHIFT_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload shift registers
|
|
||||||
for (int cycle = 329; cycle <= 337; cycle += 8)
|
|
||||||
{
|
|
||||||
events[261][cycle] |= Event::LOAD_BACKGROUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
events[261][328] |= Event::INCREMENT_TILE_X;
|
|
||||||
events[261][336] |= Event::INCREMENT_TILE_X;
|
|
||||||
|
|
||||||
// Unused Fetches
|
|
||||||
events[261][337] |= Event::FETCH_NT;
|
|
||||||
events[261][339] |= Event::FETCH_NT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::Tick()
|
|
||||||
{
|
|
||||||
int event = events[currentScanline][currentCycle];
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::DRAW))
|
|
||||||
{
|
|
||||||
Draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::SHIFT_BACKGROUND))
|
|
||||||
{
|
|
||||||
ShiftBG();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::FETCH_NT))
|
|
||||||
{
|
|
||||||
FetchNT();
|
|
||||||
}
|
|
||||||
else if (CheckBit(event, Event::FETCH_AT))
|
|
||||||
{
|
|
||||||
FetchAT();
|
|
||||||
}
|
|
||||||
else if (CheckBit(event, Event::FETCH_PT_LOW))
|
|
||||||
{
|
|
||||||
FetchPTLow();
|
|
||||||
}
|
|
||||||
else if (CheckBit(event, Event::FETCH_PT_HIGH))
|
|
||||||
{
|
|
||||||
FetchPTHigh();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::LOAD_BACKGROUND))
|
|
||||||
{
|
|
||||||
LoadBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::SET_TILE_X))
|
|
||||||
{
|
|
||||||
SetTileX();
|
|
||||||
}
|
|
||||||
else if (CheckBit(event, Event::SET_TILE_Y))
|
|
||||||
{
|
|
||||||
SetTileY();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::INCREMENT_TILE_Y))
|
|
||||||
{
|
|
||||||
IncrementTileY();
|
|
||||||
}
|
|
||||||
else if (CheckBit(event, Event::INCREMENT_TILE_X))
|
|
||||||
{
|
|
||||||
IncrementTileX();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckBit(event, Event::SET_VBLANK))
|
|
||||||
{
|
|
||||||
SetVBlank();
|
|
||||||
}
|
|
||||||
else if (CheckBit(event, Event::CLEAR_FLAGS))
|
|
||||||
{
|
|
||||||
ClearFlags();
|
|
||||||
}
|
|
||||||
|
|
||||||
++currentCycle;
|
|
||||||
|
|
||||||
// Odd frame, skip
|
|
||||||
if (currentScanline == 261 && currentCycle == 340 && (frameNumber % 2))
|
|
||||||
{
|
|
||||||
currentScanline = 0;
|
|
||||||
currentCycle = 0;
|
|
||||||
++frameNumber;
|
|
||||||
}
|
|
||||||
else if (currentCycle == 341)
|
|
||||||
{
|
|
||||||
currentCycle = 0;
|
|
||||||
++currentScanline;
|
|
||||||
|
|
||||||
if (currentScanline == 262)
|
|
||||||
{
|
|
||||||
currentScanline = 0;
|
|
||||||
++frameNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::WriteRegister(Register reg, uint8_t value)
|
|
||||||
{
|
|
||||||
switch (reg)
|
|
||||||
{
|
|
||||||
case Register::PPUCTRL:
|
|
||||||
{
|
|
||||||
regPPUCTRL.SetByte(value);
|
|
||||||
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// t: --- BA-- ---- ---- = d: ---- --BA
|
|
||||||
tempAddress.nametableY = (value & 0x2u) >> 1u;
|
|
||||||
tempAddress.nametableX = (value & 0x1u) >> 1u;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Register::PPUMASK:
|
|
||||||
{
|
|
||||||
regPPUMASK.SetByte(value);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Register::OAMADDR:
|
|
||||||
{
|
|
||||||
regOAMADDR = value;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Register::OAMDATA:
|
|
||||||
{
|
|
||||||
oam[regOAMADDR] = value;
|
|
||||||
|
|
||||||
++regOAMADDR;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Register::PPUSCROLL:
|
|
||||||
{
|
|
||||||
if (firstWrite)
|
|
||||||
{
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// t: --- ---- ---H GFED = d: HGFE D---
|
|
||||||
// x: CBA = d: ---- -CBA
|
|
||||||
// w: = 1
|
|
||||||
tempAddress.scrollCoarseX = (value & 0xF8u) >> 3u;
|
|
||||||
scrollFineX = value & 0x7u;
|
|
||||||
|
|
||||||
firstWrite = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// t: CBA --HG FED- ---- = d: HGFE DCBA
|
|
||||||
// w: = 0
|
|
||||||
tempAddress.scrollCoarseY = (value & 0xF8u) >> 3u;
|
|
||||||
tempAddress.scrollFineY = value & 0x7u;
|
|
||||||
|
|
||||||
firstWrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Register::PPUADDR:
|
|
||||||
{
|
|
||||||
// MSB
|
|
||||||
if (firstWrite)
|
|
||||||
{
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// t: -FE DCBA ---- ---- = d: --FE DCBA
|
|
||||||
// t: X-- ---- ---- ---- = 0
|
|
||||||
// w: = 1
|
|
||||||
tempAddress.scrollCoarseY &= ~(0x18u);
|
|
||||||
tempAddress.scrollCoarseY |= (value & 0x3u) << 3u;
|
|
||||||
|
|
||||||
tempAddress.nametableY = (value & 0x8u) >> 3u;
|
|
||||||
tempAddress.nametableX = (value & 0x4u) >> 2u;
|
|
||||||
|
|
||||||
tempAddress.scrollFineY = (value & 0x30u) >> 4u;
|
|
||||||
tempAddress.scrollFineY &= ~(0x4u);
|
|
||||||
|
|
||||||
firstWrite = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LSB
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// t: ... .... HGFE DCBA = d: HGFE DCBA
|
|
||||||
// v = t
|
|
||||||
// w: = 0
|
|
||||||
tempAddress.scrollCoarseY &= ~(0x7u);
|
|
||||||
tempAddress.scrollCoarseY |= (value & 0xE0u) >> 5u;
|
|
||||||
|
|
||||||
tempAddress.scrollCoarseX = value & 0x1Fu;
|
|
||||||
|
|
||||||
currentAddress = tempAddress;
|
|
||||||
|
|
||||||
firstWrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Register::PPUDATA:
|
|
||||||
{
|
|
||||||
WriteMemory(currentAddress.GetValue(), value);
|
|
||||||
|
|
||||||
uint16_t temp = currentAddress.GetValue();
|
|
||||||
temp += (regPPUCTRL.vramAddrIncrement) ? 32u : 1u;
|
|
||||||
currentAddress.SetValue(temp);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t PPU::ReadRegister(Register reg)
|
|
||||||
{
|
|
||||||
uint8_t byte{};
|
|
||||||
|
|
||||||
switch (reg)
|
|
||||||
{
|
|
||||||
case Register::PPUSTATUS:
|
|
||||||
{
|
|
||||||
byte = regPPUSTATUS.GetByte();
|
|
||||||
|
|
||||||
regPPUSTATUS.vblankStarted = 0u;
|
|
||||||
firstWrite = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Register::OAMDATA:
|
|
||||||
{
|
|
||||||
byte = oam[regOAMADDR];
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Register::PPUDATA:
|
|
||||||
{
|
|
||||||
// Returned byte is buffered from last read of PPUDATA
|
|
||||||
byte = oldByte;
|
|
||||||
oldByte = ReadMemory(currentAddress.GetValue());
|
|
||||||
|
|
||||||
// If reading palette data, return actual byte instead of buffered byte
|
|
||||||
if (currentAddress.GetValue() >= BG_PALETTE_START_ADDRESS)
|
|
||||||
{
|
|
||||||
byte = oldByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t temp = currentAddress.GetValue();
|
|
||||||
temp += (regPPUCTRL.vramAddrIncrement) ? 32u : 1u;
|
|
||||||
currentAddress.SetValue(temp);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::FetchNT()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
uint16_t address = 0x2000u | (currentAddress.scrollCoarseY << 5u) | currentAddress.scrollCoarseX;
|
|
||||||
|
|
||||||
nametableLatch = ReadMemory(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::FetchAT()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
// Bits 4,3,2 of Coarse Y Scroll are Bits 5,4,3 of attribute table address
|
|
||||||
// Bits 4,3,2 of Coarse X Scroll are Bits 2,1,0 of attribute table address
|
|
||||||
uint16_t address = ATTRIBUTE_TABLE_START
|
|
||||||
| ((currentAddress.scrollCoarseY & 0x1Cu) << 1u)
|
|
||||||
| ((currentAddress.scrollCoarseX & 0x1Cu) >> 2u);
|
|
||||||
|
|
||||||
// Bit 1 of Coarse Y Scroll is Block Row (0 or 1)
|
|
||||||
// Bit 1 of Coarse X Scroll is Block Col (0 or 1)
|
|
||||||
|
|
||||||
// Y determines if top or bottom
|
|
||||||
// X determines if left or right
|
|
||||||
|
|
||||||
uint8_t at = ReadMemory(address);
|
|
||||||
|
|
||||||
uint8_t location = ((currentAddress.scrollCoarseY & 0x2u))
|
|
||||||
| ((currentAddress.scrollCoarseX & 0x2u) >> 1u);
|
|
||||||
|
|
||||||
switch (location)
|
|
||||||
{
|
|
||||||
// Top Left
|
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
attributeTableLatch = at & 0x03u;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top Right
|
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
attributeTableLatch = (at & 0x0Cu) >> 2u;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom Left
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
attributeTableLatch = (at & 0x30u) >> 4u;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom Right
|
|
||||||
case 3:
|
|
||||||
{
|
|
||||||
attributeTableLatch = (at & 0xC0u) >> 6u;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::FetchPTLow()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
uint16_t address = regPPUCTRL.bgPatternTableAddr ? PATTERN_TABLE_1_START : PATTERN_TABLE_0_START;
|
|
||||||
address += (nametableLatch * 16u) + currentAddress.scrollFineY;
|
|
||||||
|
|
||||||
patternTableLowLatch = ReadMemory(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::FetchPTHigh()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
uint16_t address = regPPUCTRL.bgPatternTableAddr ? PATTERN_TABLE_1_START : PATTERN_TABLE_0_START;
|
|
||||||
address += (nametableLatch * 16u) + 8u + currentAddress.scrollFineY;
|
|
||||||
|
|
||||||
patternTableHighLatch = ReadMemory(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::SetVBlank()
|
|
||||||
{
|
|
||||||
regPPUSTATUS.vblankStarted = 1u;
|
|
||||||
|
|
||||||
if (regPPUCTRL.nmiEnable)
|
|
||||||
{
|
|
||||||
nes->nmi = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::SetTileX()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// v: --- -F-- ---E DCBA = t: --- -F-- ---E DCBA
|
|
||||||
currentAddress.nametableX = tempAddress.nametableX;
|
|
||||||
currentAddress.scrollCoarseX = tempAddress.scrollCoarseX;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::SetTileY()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
// v: IHG F-ED CBA- ---- = t: IHG F-ED CBA- ----
|
|
||||||
currentAddress.scrollCoarseY = tempAddress.scrollCoarseY;
|
|
||||||
currentAddress.nametableY = tempAddress.nametableY;
|
|
||||||
currentAddress.scrollFineY = tempAddress.scrollFineY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::Draw()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
// Draw occurs on cycle 2
|
|
||||||
uint8_t x = currentCycle - 2;
|
|
||||||
uint8_t h = (patternTableHighShift & 0x8000u) >> (15u - scrollFineX);
|
|
||||||
uint8_t l = (patternTableLowShift & 0x8000u) >> (15u - scrollFineX);
|
|
||||||
|
|
||||||
uint8_t bgColor = (h << 1u) | l;
|
|
||||||
|
|
||||||
uint8_t paletteIndexHigh = (atShiftHigh & 0x80u) >> (7u - scrollFineX);
|
|
||||||
uint8_t paletteIndexLow = (atShiftLow & 0x80u) >> (7u - scrollFineX);
|
|
||||||
|
|
||||||
uint8_t pIndex = (paletteIndexHigh << 1u) | paletteIndexLow;
|
|
||||||
|
|
||||||
uint8_t paletteIndex = palette[pIndex][bgColor];
|
|
||||||
|
|
||||||
uint32_t color = nes->palette[paletteIndex].GetValue();
|
|
||||||
|
|
||||||
video[currentScanline * VIDEO_WIDTH + x] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::LoadBackground()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
patternTableHighShift &= ~(0xFFu);
|
|
||||||
patternTableHighShift |= (patternTableHighLatch);
|
|
||||||
|
|
||||||
patternTableLowShift &= ~(0xFFu);
|
|
||||||
patternTableLowShift |= (patternTableLowLatch);
|
|
||||||
|
|
||||||
attribute = attributeTableLatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::IncrementTileX()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
// Increment coarse X scroll and wrap if needed
|
|
||||||
if (currentAddress.scrollCoarseX == 31)
|
|
||||||
{
|
|
||||||
currentAddress.scrollCoarseX = 0;
|
|
||||||
currentAddress.nametableX = currentAddress.nametableX ? 0u : 1u;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++currentAddress.scrollCoarseX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::IncrementTileY()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
// Increment Y scroll and wrap if needed
|
|
||||||
if (currentAddress.scrollFineY < 7)
|
|
||||||
{
|
|
||||||
++currentAddress.scrollFineY;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentAddress.scrollFineY = 0;
|
|
||||||
|
|
||||||
if (currentAddress.scrollCoarseY == 29)
|
|
||||||
{
|
|
||||||
currentAddress.scrollCoarseY = 0;
|
|
||||||
currentAddress.nametableY = currentAddress.nametableY ? 0u : 1u;
|
|
||||||
}
|
|
||||||
else if (currentAddress.scrollCoarseY == 31)
|
|
||||||
{
|
|
||||||
currentAddress.scrollCoarseY = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++currentAddress.scrollCoarseY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::ClearFlags()
|
|
||||||
{
|
|
||||||
regPPUSTATUS.vblankStarted = 0u;
|
|
||||||
regPPUSTATUS.spriteOverflow = 0u;
|
|
||||||
regPPUSTATUS.sprite0Hit = 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::ShiftBG()
|
|
||||||
{
|
|
||||||
if (!regPPUMASK.showBackground)
|
|
||||||
{return;}
|
|
||||||
|
|
||||||
patternTableHighShift <<= 1u;
|
|
||||||
patternTableLowShift <<= 1u;
|
|
||||||
atShiftLow <<= 1u;
|
|
||||||
atShiftHigh <<= 1u;
|
|
||||||
|
|
||||||
atShiftLow |= attribute & 0x1u;
|
|
||||||
atShiftHigh |= (attribute & 0x2u) >> 1u;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t PPU::PpuStatus::GetByte()
|
|
||||||
{
|
|
||||||
uint8_t byte = vblankStarted << 7u | sprite0Hit << 6u | spriteOverflow << 5u | previousLsb;
|
|
||||||
|
|
||||||
return byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::PpuMask::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::PpuCtrl::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));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PPU::Address::SetValue(uint16_t value)
|
|
||||||
{
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
scrollFineY = (value & 0x7000u) >> 12u;
|
|
||||||
nametableX = (value & 0x0800u) >> 11u;
|
|
||||||
nametableY = (value & 0x0400u) >> 10u;
|
|
||||||
scrollCoarseY = (value & 0x03E0u) >> 5u;
|
|
||||||
scrollCoarseX = value & 0x0001Fu;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t PPU::Address::GetValue()
|
|
||||||
{
|
|
||||||
// yyy NNYY YYYX XXXX
|
|
||||||
return (scrollFineY << 12u) | (nametableY << 11u) | (nametableX << 10u) | (scrollCoarseY << 5u) | scrollCoarseX;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
|
|
||||||
class NES;
|
|
||||||
|
|
||||||
|
|
||||||
const unsigned int VRAM_SIZE = 2048u;
|
|
||||||
const unsigned int PALETTE_SIZE = 8u;
|
|
||||||
const unsigned int OAM_SIZE = 256u;
|
|
||||||
const unsigned int VIDEO_WIDTH = 256u;
|
|
||||||
const unsigned int VIDEO_HEIGHT = 240u;
|
|
||||||
const unsigned int TOTAL_SCANLINES = 262u;
|
|
||||||
const unsigned int TOTAL_DOTS = 341u;
|
|
||||||
|
|
||||||
|
|
||||||
class PPU
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PPU();
|
|
||||||
enum Register
|
|
||||||
{
|
|
||||||
PPUCTRL = 0x00,
|
|
||||||
PPUMASK = 0x01,
|
|
||||||
PPUSTATUS = 0x02,
|
|
||||||
OAMADDR = 0x03,
|
|
||||||
OAMDATA = 0x04,
|
|
||||||
PPUSCROLL = 0x05,
|
|
||||||
PPUADDR = 0x06,
|
|
||||||
PPUDATA = 0x07,
|
|
||||||
};
|
|
||||||
|
|
||||||
void Tick();
|
|
||||||
void WriteRegister(Register reg, uint8_t value);
|
|
||||||
uint8_t ReadRegister(Register reg);
|
|
||||||
|
|
||||||
void WriteMemory(uint16_t address, uint8_t value);
|
|
||||||
uint8_t ReadMemory(uint16_t address);
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class NES;
|
|
||||||
|
|
||||||
void FetchNT();
|
|
||||||
void FetchAT();
|
|
||||||
void FetchPTLow();
|
|
||||||
void FetchPTHigh();
|
|
||||||
void SetVBlank();
|
|
||||||
void SetTileX();
|
|
||||||
void SetTileY();
|
|
||||||
void LoadBackground();
|
|
||||||
void Draw();
|
|
||||||
void IncrementTileX();
|
|
||||||
void IncrementTileY();
|
|
||||||
void ClearFlags();
|
|
||||||
|
|
||||||
enum Event
|
|
||||||
{
|
|
||||||
FETCH_NT = 0x1,
|
|
||||||
FETCH_AT = 0x2,
|
|
||||||
FETCH_PT_LOW = 0x4,
|
|
||||||
FETCH_PT_HIGH = 0x8,
|
|
||||||
LOAD_BACKGROUND = 0x10,
|
|
||||||
SHIFT_BACKGROUND = 0x20,
|
|
||||||
SET_VBLANK = 0x40,
|
|
||||||
SET_TILE_X = 0x80,
|
|
||||||
SET_TILE_Y = 0x100,
|
|
||||||
DRAW = 0x200,
|
|
||||||
INCREMENT_TILE_X = 0x400,
|
|
||||||
INCREMENT_TILE_Y = 0x800,
|
|
||||||
CLEAR_FLAGS = 0x1000
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PpuCtrl
|
|
||||||
{
|
|
||||||
void SetByte(uint8_t byte);
|
|
||||||
|
|
||||||
uint16_t nametableBaseAddr;
|
|
||||||
uint8_t vramAddrIncrement;
|
|
||||||
uint8_t spritePatternTableAddr;
|
|
||||||
uint8_t bgPatternTableAddr;
|
|
||||||
uint8_t spriteSize;
|
|
||||||
uint8_t masterSlaveSelect;
|
|
||||||
uint8_t nmiEnable;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PpuMask
|
|
||||||
{
|
|
||||||
void SetByte(uint8_t byte);
|
|
||||||
|
|
||||||
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 PpuStatus
|
|
||||||
{
|
|
||||||
uint8_t GetByte();
|
|
||||||
|
|
||||||
uint8_t vblankStarted;
|
|
||||||
uint8_t sprite0Hit;
|
|
||||||
uint8_t spriteOverflow;
|
|
||||||
uint8_t previousLsb;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Address
|
|
||||||
{
|
|
||||||
uint16_t GetValue();
|
|
||||||
void SetValue(uint16_t value);
|
|
||||||
|
|
||||||
uint8_t scrollFineY;
|
|
||||||
uint8_t nametableX;
|
|
||||||
uint8_t nametableY;
|
|
||||||
uint8_t scrollCoarseX;
|
|
||||||
uint8_t scrollCoarseY;
|
|
||||||
};
|
|
||||||
|
|
||||||
// External Registersgg
|
|
||||||
PpuCtrl regPPUCTRL{}; // $2000, W
|
|
||||||
PpuMask regPPUMASK{}; // $2001, W
|
|
||||||
PpuStatus regPPUSTATUS{}; // $2002, R
|
|
||||||
uint8_t regOAMADDR{}; // $2003, W
|
|
||||||
uint8_t oldByte{};
|
|
||||||
|
|
||||||
// Internal Registers
|
|
||||||
Address currentAddress{};
|
|
||||||
Address tempAddress{};
|
|
||||||
uint8_t scrollFineX{};
|
|
||||||
bool firstWrite{true};
|
|
||||||
|
|
||||||
// Timing tracking
|
|
||||||
uint32_t events[TOTAL_SCANLINES][TOTAL_DOTS]{};
|
|
||||||
uint16_t currentCycle{};
|
|
||||||
uint16_t currentScanline{};
|
|
||||||
|
|
||||||
// Memory
|
|
||||||
uint8_t ram[VRAM_SIZE]{};
|
|
||||||
uint8_t palette[PALETTE_SIZE][4]{};
|
|
||||||
uint8_t oam[OAM_SIZE]{};
|
|
||||||
uint32_t video[VIDEO_WIDTH * VIDEO_HEIGHT]{};
|
|
||||||
|
|
||||||
uint64_t frameNumber{};
|
|
||||||
|
|
||||||
// Shift registers and latches
|
|
||||||
uint16_t patternTableHighShift{};
|
|
||||||
uint16_t patternTableLowShift{};
|
|
||||||
uint8_t atShiftLow{};
|
|
||||||
uint8_t atShiftHigh{};
|
|
||||||
uint8_t nametableLatch{};
|
|
||||||
uint8_t attributeTableLatch{};
|
|
||||||
uint8_t attribute;
|
|
||||||
uint8_t patternTableLowLatch{};
|
|
||||||
uint8_t patternTableHighLatch{};
|
|
||||||
|
|
||||||
|
|
||||||
NES* nes;
|
|
||||||
void ShiftBG();
|
|
||||||
};
|
|
|
@ -1,157 +0,0 @@
|
||||||
#include "Platform.hpp"
|
|
||||||
#include "Input.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(uint8_t* buttons)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case SDLK_UP:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Up] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_DOWN:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Down] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_LEFT:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Left] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_RIGHT:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Right] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_LSHIFT:
|
|
||||||
{
|
|
||||||
buttons[Buttons::Select] = 1u;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_RETURN:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Start] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_z:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::A] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_x:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::B] = 1u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SDL_KEYUP:
|
|
||||||
{
|
|
||||||
switch (event.key.keysym.sym)
|
|
||||||
{
|
|
||||||
case SDLK_UP:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Up] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_DOWN:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Down] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_LEFT:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Left] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_RIGHT:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Right] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_LSHIFT:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Select] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_RETURN:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::Start] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_z:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::A] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_x:
|
|
||||||
{
|
|
||||||
{buttons[Buttons::B] = 0u;}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return quit;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include "Input.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
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(uint8_t* buttons);
|
|
||||||
|
|
||||||
SDL_Window* window{};
|
|
||||||
SDL_Renderer* renderer{};
|
|
||||||
SDL_Texture* texture{};
|
|
||||||
};
|
|
Loading…
Reference in New Issue