Adding mapper0 and new CPU instructions

This commit is contained in:
Selim Mustafaev 2023-08-20 11:36:03 +03:00
parent 2e6ebe201a
commit 40e2f45177
15 changed files with 583 additions and 113 deletions

View File

@ -3,4 +3,4 @@ project(nes)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
add_executable(nes main.cpp src/Rom.cpp src/Rom.h src/Nes.cpp src/Nes.h src/Cpu.cpp src/Cpu.h src/Bus.cpp src/Bus.h) add_executable(nes main.cpp src/Cartridge.cpp src/Cartridge.h src/Nes.cpp src/Nes.h src/Cpu.cpp src/Cpu.h src/Bus.cpp src/Bus.h src/Mapper/Mapper.cpp src/Mapper/Mapper.h src/Mapper/Mapper0.cpp src/Mapper/Mapper0.h)

View File

@ -6,21 +6,23 @@ int main() {
nes::Nes device; nes::Nes device;
std::stringstream ss; // std::stringstream ss;
ss << "A2 0A 8E 00 00 A2 03 8E 01 00 AC 00 00 A9 00 18 6D 01 00 88 D0 FA 8D 02 00 EA EA EA"; // ss << "A2 0A 8E 00 00 A2 03 8E 01 00 AC 00 00 A9 00 18 6D 01 00 88 D0 FA 8D 02 00 EA EA EA";
uint16_t nOffset = 0x8000; // uint16_t nOffset = 0x8000;
while (!ss.eof()) // while (!ss.eof())
{ // {
std::string b; // std::string b;
ss >> b; // ss >> b;
device.write(nOffset++, (uint8_t)std::stoul(b, nullptr, 16)); // device.write(nOffset++, (uint8_t)std::stoul(b, nullptr, 16));
} // }
//
// // Set Reset Vector
// device.write(0xFFFC, 0x00);
// device.write(0xFFFD, 0x80);
//
// device.reset();
// Set Reset Vector device.runRom("/home/selim/Downloads/nestest.nes", 0xC000);
device.write(0xFFFC, 0x00);
device.write(0xFFFD, 0x80);
device.reset();
return 0; return 0;
} }

View File

@ -3,22 +3,42 @@
// //
#include "Bus.h" #include "Bus.h"
#include <iostream>
namespace nes { namespace nes {
Bus::Bus(): _rom{nullptr} { Bus::Bus(): _cartridge{nullptr} {
_ram = std::make_unique<uint8_t[]>(64*1024); _ram = std::make_unique<uint8_t[]>(2*1024);
} }
uint8_t Bus::read(uint16_t address) { uint8_t Bus::read(uint16_t address) {
return _ram[address]; if(address < 0x2000) {
return _ram[address & 0x07FF];
}
else if(address >= 0x2000 && address < 0x4000) {
std::cout << "PPU read at address: " << address << std::endl;
return 0;
}
else if(address >= 0x8000) {
return _cartridge->readPrg(address);
}
return 0;
} }
void Bus::write(uint16_t address, uint8_t value) { void Bus::write(uint16_t address, uint8_t value) {
_ram[address] = value; if(address < 0x2000) {
_ram[address & 0x07FF] = value;
}
else if(address >= 0x2000 && address < 0x4000) {
std::cout << "PPU write at address: " << address << std::endl;
}
else if(address >= 0x8000) {
std::cout << "Cartridge write at address: " << address << std::endl;
}
} }
void Bus::connect(Rom *rom) { void Bus::connect(Cartridge *cartridge) {
_rom = rom; _cartridge = cartridge;
} }
} }

View File

@ -5,7 +5,7 @@
#ifndef NES_BUS_H #ifndef NES_BUS_H
#define NES_BUS_H #define NES_BUS_H
#include "Rom.h" #include "Cartridge.h"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@ -17,11 +17,11 @@ namespace nes {
Bus(); Bus();
uint8_t read(uint16_t address); uint8_t read(uint16_t address);
void write(uint16_t address, uint8_t value); void write(uint16_t address, uint8_t value);
void connect(Rom* rom); void connect(Cartridge* cartridge);
private: private:
std::unique_ptr<uint8_t[]> _ram; std::unique_ptr<uint8_t[]> _ram;
Rom* _rom; Cartridge* _cartridge;
}; };
} }

43
src/Cartridge.cpp Normal file
View File

@ -0,0 +1,43 @@
//
// Created by Selim Mustafaev on 09.08.2023.
//
#include "Cartridge.h"
#include <fstream>
#include <iostream>
#include <memory>
namespace nes {
Cartridge::Cartridge(const fs::path &path) {
auto romSize = fs::file_size(path);
_romData = std::make_unique<uint8_t[]>(romSize);
std::ifstream rom(path);
rom.read(reinterpret_cast<char*>(_romData.get()), static_cast<std::streamsize>(romSize));
// UB here
// Should be std::start_lifetime_as<RomHeader>(_romData);
// when it become available
_header = reinterpret_cast<RomHeader*>(_romData.get());
const size_t prgSize = _header->prgChunks*PRG_CHUNK_SIZE;
const size_t chrSize = _header->chrChunks*CHR_CHUNK_SIZE;
_prgRom = std::span<uint8_t>(_romData.get() + sizeof(RomHeader), prgSize);
_chrRom = std::span<uint8_t>(_romData.get() + sizeof(RomHeader) + prgSize, chrSize);
_mapper = std::make_unique<Mapper0>(_header->prgChunks, _header->chrChunks);
}
uint8_t Cartridge::readPrg(uint16_t address) {
uint32_t mappedAddress = _mapper->mapPrg(address);
return _prgRom[mappedAddress];
}
uint8_t Cartridge::readChr(uint16_t address) {
uint32_t mappedAddress = _mapper->mapChr(address);
return _chrRom[mappedAddress];
}
}

View File

@ -2,8 +2,10 @@
// Created by Selim Mustafaev on 09.08.2023. // Created by Selim Mustafaev on 09.08.2023.
// //
#ifndef NES_ROM_H #ifndef NES_CARTRIDGE_H
#define NES_ROM_H #define NES_CARTRIDGE_H
#include "Mapper/Mapper0.h"
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
@ -24,17 +26,22 @@ namespace nes {
uint8_t reserved[10]; uint8_t reserved[10];
}; };
class Rom { class Cartridge {
public: public:
explicit Rom(const fs::path& path); explicit Cartridge(const fs::path& path);
public:
uint8_t readPrg(uint16_t address);
uint8_t readChr(uint16_t address);
private: private:
std::unique_ptr<std::byte[]> _romData; std::unique_ptr<uint8_t[]> _romData;
std::unique_ptr<Mapper> _mapper;
RomHeader* _header; RomHeader* _header;
std::span<std::byte> _prgRom; std::span<uint8_t> _prgRom;
std::span<std::byte> _chrRom; std::span<uint8_t> _chrRom;
}; };
} }
#endif //NES_ROM_H #endif //NES_CARTRIDGE_H

View File

@ -9,16 +9,61 @@ namespace nes {
Cpu::Cpu(Bus* bus): _ticks{}, _bus{bus}, A{}, X{}, Y{}, PC{}, SP{}, flags{} { Cpu::Cpu(Bus* bus): _ticks{}, _bus{bus}, A{}, X{}, Y{}, PC{}, SP{}, flags{} {
_instructions = std::vector<Instruction>(256); _instructions = std::vector<Instruction>(256);
_instructions[0x00] = {"BRK", &Cpu::BRK, &Cpu::IMP, 7, false};
_instructions[0xA2] = {"LDX", &Cpu::LDX, &Cpu::IMM, 2, false}; _instructions[0xA2] = {"LDX", &Cpu::LDX, &Cpu::IMM, 2, false};
_instructions[0xAE] = {"LDX", &Cpu::LDX, &Cpu::ABS, 4, false};
_instructions[0x86] = {"STX", &Cpu::STX, &Cpu::ZP0, 3, false};
_instructions[0x8E] = {"STX", &Cpu::STX, &Cpu::ABS, 4, false}; _instructions[0x8E] = {"STX", &Cpu::STX, &Cpu::ABS, 4, false};
_instructions[0xAC] = {"LDY", &Cpu::LDY, &Cpu::ABS, 4, false}; _instructions[0xAC] = {"LDY", &Cpu::LDY, &Cpu::ABS, 4, false};
_instructions[0xA0] = {"LDY", &Cpu::LDY, &Cpu::IMM, 2, false};
_instructions[0xA9] = {"LDA", &Cpu::LDA, &Cpu::IMM, 2, false}; _instructions[0xA9] = {"LDA", &Cpu::LDA, &Cpu::IMM, 2, false};
_instructions[0xAD] = {"LDA", &Cpu::LDA, &Cpu::ABS, 4, false};
_instructions[0x18] = {"CLC", &Cpu::CLC, &Cpu::IMP, 2, false}; _instructions[0x18] = {"CLC", &Cpu::CLC, &Cpu::IMP, 2, false};
_instructions[0x6D] = {"ADC", &Cpu::ADC, &Cpu::ABS, 4, false}; _instructions[0x6D] = {"ADC", &Cpu::ADC, &Cpu::ABS, 4, false};
_instructions[0x69] = {"ADC", &Cpu::ADC, &Cpu::IMM, 2, false};
_instructions[0x88] = {"DEY", &Cpu::DEY, &Cpu::IMP, 2, false}; _instructions[0x88] = {"DEY", &Cpu::DEY, &Cpu::IMP, 2, false};
_instructions[0xD0] = {"BNE", &Cpu::BNE, &Cpu::REL, 2, false}; _instructions[0xD0] = {"BNE", &Cpu::BNE, &Cpu::REL, 2, false};
_instructions[0x8D] = {"STA", &Cpu::STA, &Cpu::ABS, 4, false}; _instructions[0x8D] = {"STA", &Cpu::STA, &Cpu::ABS, 4, false};
_instructions[0xEA] = {"NOP", &Cpu::NOP, &Cpu::IMP, 2, false}; _instructions[0xEA] = {"NOP", &Cpu::NOP, &Cpu::IMP, 2, false};
_instructions[0x78] = {"SEI", &Cpu::SEI, &Cpu::IMP, 2, false};
_instructions[0xD8] = {"CLD", &Cpu::CLD, &Cpu::IMP, 2, false};
_instructions[0x9A] = {"TXS", &Cpu::TXS, &Cpu::IMP, 2, false};
_instructions[0x10] = {"BPL", &Cpu::BPL, &Cpu::REL, 2, false};
_instructions[0x4C] = {"JMP", &Cpu::JMP, &Cpu::ABS, 3, false};
_instructions[0x20] = {"JSR", &Cpu::JSR, &Cpu::ABS, 6, false};
_instructions[0x38] = {"SEC", &Cpu::SEC, &Cpu::IMP, 2, false};
_instructions[0xB0] = {"BCS", &Cpu::BCS, &Cpu::REL, 2, false};
_instructions[0x90] = {"BCC", &Cpu::BCC, &Cpu::REL, 2, false};
_instructions[0xF0] = {"BEQ", &Cpu::BEQ, &Cpu::REL, 2, false};
_instructions[0x85] = {"STA", &Cpu::STA, &Cpu::ZP0, 3, false};
_instructions[0x24] = {"BIT", &Cpu::BIT, &Cpu::ZP0, 3, false};
_instructions[0x70] = {"BVS", &Cpu::BVS, &Cpu::REL, 2, false};
_instructions[0x50] = {"BVC", &Cpu::BVC, &Cpu::REL, 2, false};
_instructions[0x60] = {"RTS", &Cpu::RTS, &Cpu::IMP, 6, false};
_instructions[0xF8] = {"SED", &Cpu::SED, &Cpu::IMP, 2, false};
_instructions[0x08] = {"PHP", &Cpu::PHP, &Cpu::IMP, 3, false};
_instructions[0x68] = {"PLA", &Cpu::PLA, &Cpu::IMP, 4, false};
_instructions[0x29] = {"AND", &Cpu::AND, &Cpu::IMM, 2, false};
_instructions[0xC9] = {"CMP", &Cpu::CMP, &Cpu::IMM, 2, false};
_instructions[0x30] = {"BMI", &Cpu::BMI, &Cpu::REL, 2, false};
_instructions[0x48] = {"PHA", &Cpu::PHA, &Cpu::IMP, 3, false};
_instructions[0x28] = {"PLP", &Cpu::PLP, &Cpu::IMP, 4, false};
_instructions[0x09] = {"ORA", &Cpu::ORA, &Cpu::IMM, 2, false};
_instructions[0xB8] = {"CLV", &Cpu::CLV, &Cpu::IMP, 2, false};
_instructions[0x49] = {"EOR", &Cpu::EOR, &Cpu::IMM, 2, false};
_instructions[0xC0] = {"CPY", &Cpu::CPY, &Cpu::IMM, 2, false};
_instructions[0xE0] = {"CPX", &Cpu::CPX, &Cpu::IMM, 2, false};
_instructions[0xE9] = {"SBC", &Cpu::SBC, &Cpu::IMM, 2, false};
_instructions[0x84] = {"STY", &Cpu::STY, &Cpu::ZP0, 3, false};
_instructions[0xC8] = {"INY", &Cpu::INY, &Cpu::IMP, 2, false};
_instructions[0xE8] = {"INX", &Cpu::INX, &Cpu::IMP, 2, false};
_instructions[0xCA] = {"DEX", &Cpu::DEX, &Cpu::IMP, 2, false};
_instructions[0xA8] = {"TAY", &Cpu::TAY, &Cpu::IMP, 2, false};
_instructions[0xAA] = {"TAX", &Cpu::TAX, &Cpu::IMP, 2, false};
_instructions[0x98] = {"TYA", &Cpu::TYA, &Cpu::IMP, 2, false};
_instructions[0x8A] = {"TXA", &Cpu::TXA, &Cpu::IMP, 2, false};
_instructions[0xBA] = {"TSX", &Cpu::TSX, &Cpu::IMP, 2, false};
_instructions[0x40] = {"RTI", &Cpu::RTI, &Cpu::IMP, 6, false};
} }
void Cpu::reset() { void Cpu::reset() {
@ -40,13 +85,14 @@ namespace nes {
auto instruction = _instructions[opcode]; auto instruction = _instructions[opcode];
if(instruction.getAddress == nullptr) { if(instruction.getAddress == nullptr) {
std::cout << "Unknown instruction: " << (int)opcode << std::endl;
return; return;
} }
auto args = (this->*instruction.getAddress)(); auto args = (this->*instruction.getAddress)();
(this->*instruction.process)(args); (this->*instruction.process)(args);
std::cout << instruction.name << ", A: " << (int)A << ", X: " << (int)X << ", Y: " << (int)Y << std::endl; std::cout << instruction.name << std::hex << ", PC: " << (int)PC << ", A: " << (int)A << ", X: " << (int)X << ", Y: " << (int)Y << std::endl;
_ticks = instruction.cycles; _ticks = instruction.cycles;
if(instruction.variableCycles) { if(instruction.variableCycles) {
@ -69,6 +115,56 @@ namespace nes {
return flags & flag; return flags & flag;
} }
void Cpu::setStartAddress(uint16_t address) {
PC = address;
}
void Cpu::branch(Cpu::InstructionArgs args) {
_ticks++;
uint16_t address = PC + args.address;
uint16_t srcPage = PC & 0xFF00;
uint16_t dstPage = address & 0xFF00;
if(srcPage != dstPage) {
_ticks++;
}
PC = address;
}
// Addressing modes
Cpu::InstructionArgs Cpu::IMM() {
return {++PC, 0};
}
Cpu::InstructionArgs Cpu::ABS() {
uint8_t lo = _bus->read(PC++);
uint8_t hi = _bus->read(PC++);
uint16_t address = (hi << 8) | lo;
return {address, 0};
}
Cpu::InstructionArgs Cpu::IMP() {
return {0, 0};
}
Cpu::InstructionArgs Cpu::REL() {
uint16_t address = _bus->read(PC++);
if (address & 0x80) {
address |= 0xFF00;
}
return {address, 0};
}
Cpu::InstructionArgs Cpu::ZP0() {
uint16_t address = _bus->read(PC++);
return {address, 0};
}
// CPU instructions // CPU instructions
void Cpu::LDA(Cpu::InstructionArgs args) { void Cpu::LDA(Cpu::InstructionArgs args) {
@ -126,55 +222,254 @@ namespace nes {
} }
void Cpu::DEY(Cpu::InstructionArgs args) { void Cpu::DEY(Cpu::InstructionArgs args) {
Y -= 1; Y--;
setFlag(Negative, Y & 0x80); setFlag(Negative, Y & 0x80);
setFlag(Zero, Y == 0); setFlag(Zero, Y == 0);
} }
void Cpu::BNE(Cpu::InstructionArgs args) { void Cpu::BNE(Cpu::InstructionArgs args) {
if(!getFlag(Zero)) { if(!getFlag(Zero)) {
_ticks++; branch(args);
uint16_t address = PC + args.address;
uint16_t srcPage = PC & 0xFF00;
uint16_t dstPage = address & 0xFF00;
if(srcPage != dstPage) {
_ticks++;
}
PC = address;
} }
} }
void Cpu::NOP(Cpu::InstructionArgs args) { void Cpu::NOP(Cpu::InstructionArgs args) {
} }
// Addressing modes void Cpu::SEI(Cpu::InstructionArgs args) {
setFlag(InterruptDisable, true);
Cpu::InstructionArgs Cpu::IMM() {
return {PC++, 0};
} }
Cpu::InstructionArgs Cpu::ABS() { void Cpu::CLD(Cpu::InstructionArgs args) {
uint8_t lo = _bus->read(PC++); setFlag(DecimalMode, false);
uint8_t hi = _bus->read(PC++);
uint16_t address = (hi << 8) | lo;
return {address, 0};
} }
Cpu::InstructionArgs Cpu::IMP() { void Cpu::TXS(Cpu::InstructionArgs args) {
return {0, 0}; SP = X;
} }
Cpu::InstructionArgs Cpu::REL() { void Cpu::BPL(Cpu::InstructionArgs args) {
uint16_t address = _bus->read(PC++); if(!getFlag(Negative)) {
branch(args);
if (address & 0x80) { }
address |= 0xFF00;
} }
return {address, 0}; void Cpu::JMP(Cpu::InstructionArgs args) {
PC = args.address;
}
void Cpu::JSR(Cpu::InstructionArgs args) {
std::cout << "+++ >>> Jumping from: " << PC << std::endl;
PC--;
_bus->write(STACK_BASE + SP--, PC >> 8);
_bus->write(STACK_BASE + SP--, PC & 0x00FF);
PC = args.address;
}
void Cpu::SEC(Cpu::InstructionArgs args) {
setFlag(Carry, true);
}
void Cpu::BCS(Cpu::InstructionArgs args) {
if(getFlag(Carry)) {
branch(args);
}
}
void Cpu::BCC(Cpu::InstructionArgs args) {
if(!getFlag(Carry)) {
branch(args);
}
}
void Cpu::BEQ(Cpu::InstructionArgs args) {
if(getFlag(Zero)) {
branch(args);
}
}
void Cpu::BIT(Cpu::InstructionArgs args) {
uint8_t value = _bus->read(args.address);
setFlag(Zero, (A & value) == 0);
setFlag(Negative, value & (1 << 7));
setFlag(Overflow, value & (1 << 6));
}
void Cpu::BVS(Cpu::InstructionArgs args) {
if(getFlag(Overflow)) {
branch(args);
}
}
void Cpu::BVC(Cpu::InstructionArgs args) {
if(!getFlag(Overflow)) {
branch(args);
}
}
void Cpu::RTS(Cpu::InstructionArgs args) {
uint16_t lo = _bus->read(STACK_BASE + ++SP);
uint16_t hi = _bus->read(STACK_BASE + ++SP);
PC = (hi << 8) | lo;
PC++;
std::cout << "+++ <<< return to: " << PC << std::endl;
}
void Cpu::SED(Cpu::InstructionArgs args) {
setFlag(DecimalMode, true);
}
void Cpu::PHP(Cpu::InstructionArgs args) {
_bus->write(STACK_BASE + SP--, flags | Break | Unused);
setFlag(Break, false);
setFlag(Unused, false);
}
void Cpu::PLA(Cpu::InstructionArgs args) {
A = _bus->read(STACK_BASE + ++SP);
setFlag(Zero, A == 0);
setFlag(Negative, A & 0x80);
}
void Cpu::AND(Cpu::InstructionArgs args) {
A &= _bus->read(args.address);;
setFlag(Zero, A == 0);
setFlag(Negative, A & 0x80);
}
void Cpu::CMP(Cpu::InstructionArgs args) {
uint16_t value = _bus->read(args.address);
uint16_t diff = A - value;
setFlag(Carry, A > value);
setFlag(Zero, (diff & 0x00FF) == 0);
setFlag(Negative, diff & 0x0080);
}
void Cpu::BMI(Cpu::InstructionArgs args) {
if(getFlag(Negative)) {
branch(args);
}
}
void Cpu::PHA(Cpu::InstructionArgs args) {
_bus->write(STACK_BASE + SP--, A);
}
void Cpu::PLP(Cpu::InstructionArgs args) {
flags = _bus->read(STACK_BASE + ++SP);
setFlag(Unused, true);
}
void Cpu::ORA(Cpu::InstructionArgs args) {
A |= _bus->read(args.address);
setFlag(Zero, A == 0);
setFlag(Negative, A & 0x80);
}
void Cpu::CLV(Cpu::InstructionArgs args) {
setFlag(Overflow, false);
}
void Cpu::EOR(Cpu::InstructionArgs args) {
A ^= _bus->read(args.address);
setFlag(Zero, A == 0);
setFlag(Negative, A & 0x80);
}
void Cpu::CPY(Cpu::InstructionArgs args) {
uint16_t value = _bus->read(args.address);
uint16_t diff = Y - value;
setFlag(Carry, Y > value);
setFlag(Zero, (diff & 0x00FF) == 0);
setFlag(Negative, diff & 0x0080);
}
void Cpu::CPX(Cpu::InstructionArgs args) {
uint16_t value = _bus->read(args.address);
uint16_t diff = X - value;
setFlag(Carry, X > value);
setFlag(Zero, (diff & 0x00FF) == 0);
setFlag(Negative, diff & 0x0080);
}
void Cpu::STY(Cpu::InstructionArgs args) {
_bus->write(args.address, Y);
}
void Cpu::INY(Cpu::InstructionArgs args) {
Y++;
setFlag(Zero, Y == 0);
setFlag(Negative, Y & 0x80);
}
void Cpu::INX(Cpu::InstructionArgs args) {
X++;
setFlag(Zero, X == 0);
setFlag(Negative, X & 0x80);
}
void Cpu::DEX(Cpu::InstructionArgs args) {
X--;
setFlag(Zero, X == 0);
setFlag(Negative, X & 0x80);
}
void Cpu::TAY(Cpu::InstructionArgs args) {
Y = A;
setFlag(Zero, Y == 0);
setFlag(Negative, Y & 0x80);
}
void Cpu::TAX(Cpu::InstructionArgs args) {
X = A;
setFlag(Zero, X == 0);
setFlag(Negative, X & 0x80);
}
void Cpu::TYA(Cpu::InstructionArgs args) {
A = Y;
setFlag(Zero, A == 0);
setFlag(Negative, A & 0x80);
}
void Cpu::TXA(Cpu::InstructionArgs args) {
A = X;
setFlag(Zero, A == 0);
setFlag(Negative, A & 0x80);
}
void Cpu::TSX(Cpu::InstructionArgs args) {
X = SP;
setFlag(Zero, X == 0);
setFlag(Negative, X & 0x80);
}
void Cpu::BRK(Cpu::InstructionArgs args) {
PC++;
setFlag(InterruptDisable, true);
_bus->write(STACK_BASE + SP--, PC >> 8);
_bus->write(STACK_BASE + SP--, PC & 0x00FF);
setFlag(Break, true);
_bus->write(STACK_BASE + SP--, flags);
setFlag(Break, false);
uint16_t lo = _bus->read(0xFFFE);
uint16_t hi = _bus->read(0xFFFF);
PC = (hi << 8) | lo;
}
void Cpu::RTI(Cpu::InstructionArgs args) {
flags = _bus->read(STACK_BASE + ++SP);
setFlag(Break, false);
setFlag(Unused, false);
uint16_t lo = _bus->read(STACK_BASE + ++SP);
uint16_t hi = _bus->read(STACK_BASE + ++SP);
PC = (hi << 8) | lo;
} }
} }

View File

@ -37,12 +37,15 @@ namespace nes {
bool variableCycles; bool variableCycles;
}; };
static constexpr uint16_t STACK_BASE = 0x0100;
public: public:
explicit Cpu(Bus* bus); explicit Cpu(Bus* bus);
void reset(); void reset();
void tick(); void tick();
void setFlag(CpuFlags flag, bool value); void setFlag(CpuFlags flag, bool value);
bool getFlag(CpuFlags flag); bool getFlag(CpuFlags flag);
void setStartAddress(uint16_t address);
private: private:
size_t _ticks; size_t _ticks;
@ -59,6 +62,16 @@ namespace nes {
uint8_t SP; // Stack Pointer uint8_t SP; // Stack Pointer
uint8_t flags; uint8_t flags;
private:
void branch(InstructionArgs args);
private:
InstructionArgs IMM();
InstructionArgs ABS();
InstructionArgs IMP();
InstructionArgs REL();
InstructionArgs ZP0();
private: private:
void LDA(InstructionArgs args); void LDA(InstructionArgs args);
void LDX(InstructionArgs args); void LDX(InstructionArgs args);
@ -71,12 +84,44 @@ namespace nes {
void DEY(InstructionArgs args); void DEY(InstructionArgs args);
void BNE(InstructionArgs args); void BNE(InstructionArgs args);
void NOP(InstructionArgs args); void NOP(InstructionArgs args);
void SEI(InstructionArgs args);
private: void CLD(InstructionArgs args);
InstructionArgs IMM(); void TXS(InstructionArgs args);
InstructionArgs ABS(); void BPL(InstructionArgs args);
InstructionArgs IMP(); void JMP(InstructionArgs args);
InstructionArgs REL(); void JSR(InstructionArgs args);
void SEC(InstructionArgs args);
void BCS(InstructionArgs args);
void BCC(InstructionArgs args);
void BEQ(InstructionArgs args);
void BIT(InstructionArgs args);
void BVS(InstructionArgs args);
void BVC(InstructionArgs args);
void RTS(InstructionArgs args);
void SED(InstructionArgs args);
void PHP(InstructionArgs args);
void PLA(InstructionArgs args);
void AND(InstructionArgs args);
void CMP(InstructionArgs args);
void BMI(InstructionArgs args);
void PHA(InstructionArgs args);
void PLP(InstructionArgs args);
void ORA(InstructionArgs args);
void CLV(InstructionArgs args);
void EOR(InstructionArgs args);
void CPY(InstructionArgs args);
void CPX(InstructionArgs args);
void STY(InstructionArgs args);
void INY(InstructionArgs args);
void INX(InstructionArgs args);
void DEX(InstructionArgs args);
void TAY(InstructionArgs args);
void TAX(InstructionArgs args);
void TYA(InstructionArgs args);
void TXA(InstructionArgs args);
void TSX(InstructionArgs args);
void BRK(InstructionArgs args);
void RTI(InstructionArgs args);
}; };
} }

12
src/Mapper/Mapper.cpp Normal file
View File

@ -0,0 +1,12 @@
//
// Created by selim on 8/19/23.
//
#include "Mapper.h"
namespace nes {
Mapper::Mapper(uint8_t prgBanks, uint8_t chrBanks): _prgBanks(prgBanks), _chrBanks(chrBanks) {
}
}

28
src/Mapper/Mapper.h Normal file
View File

@ -0,0 +1,28 @@
//
// Created by selim on 8/19/23.
//
#ifndef NES_MAPPER_H
#define NES_MAPPER_H
#include <cstdint>
namespace nes {
class Mapper {
public:
Mapper(uint8_t prgBanks, uint8_t chrBanks);
virtual ~Mapper() = default;
public:
virtual uint32_t mapPrg(uint16_t address) = 0;
virtual uint32_t mapChr(uint16_t address) = 0;
protected:
uint8_t _prgBanks;
uint8_t _chrBanks;
};
}
#endif //NES_MAPPER_H

16
src/Mapper/Mapper0.cpp Normal file
View File

@ -0,0 +1,16 @@
//
// Created by selim on 8/19/23.
//
#include "Mapper0.h"
namespace nes {
uint32_t Mapper0::mapPrg(uint16_t address) {
return address & (_prgBanks > 1 ? 0x7FFF : 0x3FFF);
}
uint32_t Mapper0::mapChr(uint16_t address) {
return address;
}
}

24
src/Mapper/Mapper0.h Normal file
View File

@ -0,0 +1,24 @@
//
// Created by selim on 8/19/23.
//
#ifndef NES_MAPPER0_H
#define NES_MAPPER0_H
#include "Mapper.h"
namespace nes {
class Mapper0: public Mapper {
public:
using Mapper::Mapper;
~Mapper0() override = default;
public:
uint32_t mapPrg(uint16_t address) override;
uint32_t mapChr(uint16_t address) override;
};
}
#endif //NES_MAPPER0_H

View File

@ -3,9 +3,11 @@
// //
#include "Nes.h" #include "Nes.h"
#include "Rom.h" #include "Cartridge.h"
#include "Cpu.h" #include "Cpu.h"
#include <iostream>
namespace nes { namespace nes {
Nes::Nes() { Nes::Nes() {
@ -13,10 +15,10 @@ namespace nes {
_cpu = std::make_unique<Cpu>(_bus.get()); _cpu = std::make_unique<Cpu>(_bus.get());
} }
void Nes::runRom(const fs::path &path) { void Nes::runRom(const fs::path &path, std::optional<uint16_t> address) {
_rom = std::make_unique<Rom>(path); _cartridge = std::make_unique<Cartridge>(path);
_bus->connect(_rom.get()); _bus->connect(_cartridge.get());
reset(); reset(address);
} }
uint8_t Nes::read(uint16_t address) { uint8_t Nes::read(uint16_t address) {
@ -27,14 +29,20 @@ namespace nes {
_bus->write(address, value); _bus->write(address, value);
} }
void Nes::reset() { void Nes::reset(std::optional<uint16_t> address) {
_cpu->reset(); _cpu->reset();
size_t ticks = 5000; if(address) {
while (ticks > 0) { _cpu->setStartAddress(address.value());
_cpu->tick();
ticks--;
} }
size_t ticks = 0;
while (ticks <= 2000) {
_cpu->tick();
ticks++;
}
std::cout << "The end" << std::endl;
} }
} }

View File

@ -6,8 +6,9 @@
#define NES_NES_H #define NES_NES_H
#include "Cpu.h" #include "Cpu.h"
#include "Rom.h" #include "Cartridge.h"
#include <filesystem> #include <filesystem>
#include <optional>
namespace nes { namespace nes {
@ -16,8 +17,8 @@ namespace nes {
class Nes { class Nes {
public: public:
Nes(); Nes();
void runRom(const fs::path& path); void runRom(const fs::path& path, std::optional<uint16_t> address = std::nullopt);
void reset(); void reset(std::optional<uint16_t> address = std::nullopt);
public: // For debug public: // For debug
uint8_t read(uint16_t address); uint8_t read(uint16_t address);
@ -26,7 +27,7 @@ namespace nes {
private: private:
std::unique_ptr<Bus> _bus; std::unique_ptr<Bus> _bus;
std::unique_ptr<Cpu> _cpu; std::unique_ptr<Cpu> _cpu;
std::unique_ptr<Rom> _rom; std::unique_ptr<Cartridge> _cartridge;
}; };
} }

View File

@ -1,31 +0,0 @@
//
// Created by Selim Mustafaev on 09.08.2023.
//
#include "Rom.h"
#include <fstream>
#include <iostream>
#include <memory>
namespace nes {
Rom::Rom(const fs::path &path) {
auto romSize = fs::file_size(path);
_romData = std::make_unique<std::byte[]>(romSize);
std::ifstream rom(path);
rom.read(reinterpret_cast<char*>(_romData.get()), static_cast<std::streamsize>(romSize));
// UB here
// Should be std::start_lifetime_as<RomHeader>(_romData);
// when it become available
_header = reinterpret_cast<RomHeader*>(_romData.get());
const size_t prgSize = _header->prgChunks*PRG_CHUNK_SIZE;
const size_t chrSize = _header->chrChunks*CHR_CHUNK_SIZE;
_prgRom = std::span<std::byte>(_romData.get() + sizeof(RomHeader), prgSize);
_chrRom = std::span<std::byte>(_romData.get() + sizeof(RomHeader) + prgSize, chrSize);
}
}