From 2e6ebe201ab5e41172a85cad565c433e94713b6e Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Fri, 18 Aug 2023 22:54:42 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + CMakeLists.txt | 6 ++ main.cpp | 26 +++++++ src/Bus.cpp | 24 +++++++ src/Bus.h | 29 ++++++++ src/Cpu.cpp | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Cpu.h | 85 +++++++++++++++++++++++ src/Nes.cpp | 40 +++++++++++ src/Nes.h | 34 ++++++++++ src/Rom.cpp | 31 +++++++++ src/Rom.h | 40 +++++++++++ 11 files changed, 497 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 main.cpp create mode 100644 src/Bus.cpp create mode 100644 src/Bus.h create mode 100644 src/Cpu.cpp create mode 100644 src/Cpu.h create mode 100644 src/Nes.cpp create mode 100644 src/Nes.h create mode 100644 src/Rom.cpp create mode 100644 src/Rom.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c828c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cmake-build-debug/ +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..43d5c98 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.25) +project(nes) + +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) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..604ad60 --- /dev/null +++ b/main.cpp @@ -0,0 +1,26 @@ +#include "src/Nes.h" +#include +#include + +int main() { + + nes::Nes device; + + 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"; + uint16_t nOffset = 0x8000; + while (!ss.eof()) + { + std::string b; + ss >> b; + device.write(nOffset++, (uint8_t)std::stoul(b, nullptr, 16)); + } + + // Set Reset Vector + device.write(0xFFFC, 0x00); + device.write(0xFFFD, 0x80); + + device.reset(); + + return 0; +} diff --git a/src/Bus.cpp b/src/Bus.cpp new file mode 100644 index 0000000..84913c7 --- /dev/null +++ b/src/Bus.cpp @@ -0,0 +1,24 @@ +// +// Created by Selim Mustafaev on 12.08.2023. +// + +#include "Bus.h" + +namespace nes { + + Bus::Bus(): _rom{nullptr} { + _ram = std::make_unique(64*1024); + } + + uint8_t Bus::read(uint16_t address) { + return _ram[address]; + } + + void Bus::write(uint16_t address, uint8_t value) { + _ram[address] = value; + } + + void Bus::connect(Rom *rom) { + _rom = rom; + } +} \ No newline at end of file diff --git a/src/Bus.h b/src/Bus.h new file mode 100644 index 0000000..7163608 --- /dev/null +++ b/src/Bus.h @@ -0,0 +1,29 @@ +// +// Created by Selim Mustafaev on 12.08.2023. +// + +#ifndef NES_BUS_H +#define NES_BUS_H + +#include "Rom.h" + +#include +#include + +namespace nes { + + class Bus { + public: + Bus(); + uint8_t read(uint16_t address); + void write(uint16_t address, uint8_t value); + void connect(Rom* rom); + + private: + std::unique_ptr _ram; + Rom* _rom; + }; + +} + +#endif //NES_BUS_H diff --git a/src/Cpu.cpp b/src/Cpu.cpp new file mode 100644 index 0000000..72383d4 --- /dev/null +++ b/src/Cpu.cpp @@ -0,0 +1,180 @@ +// +// Created by Selim Mustafaev on 11.08.2023. +// + +#include "Cpu.h" +#include + +namespace nes { + + Cpu::Cpu(Bus* bus): _ticks{}, _bus{bus}, A{}, X{}, Y{}, PC{}, SP{}, flags{} { + _instructions = std::vector(256); + _instructions[0xA2] = {"LDX", &Cpu::LDX, &Cpu::IMM, 2, false}; + _instructions[0x8E] = {"STX", &Cpu::STX, &Cpu::ABS, 4, false}; + _instructions[0xAC] = {"LDY", &Cpu::LDY, &Cpu::ABS, 4, false}; + _instructions[0xA9] = {"LDA", &Cpu::LDA, &Cpu::IMM, 2, false}; + _instructions[0x18] = {"CLC", &Cpu::CLC, &Cpu::IMP, 2, false}; + _instructions[0x6D] = {"ADC", &Cpu::ADC, &Cpu::ABS, 4, false}; + _instructions[0x88] = {"DEY", &Cpu::DEY, &Cpu::IMP, 2, false}; + _instructions[0xD0] = {"BNE", &Cpu::BNE, &Cpu::REL, 2, false}; + _instructions[0x8D] = {"STA", &Cpu::STA, &Cpu::ABS, 4, false}; + _instructions[0xEA] = {"NOP", &Cpu::NOP, &Cpu::IMP, 2, false}; + } + + void Cpu::reset() { + A = 0; + X = 0; + Y = 0; + SP = 0xFF; + flags = 0; + + uint16_t lo = _bus->read(0xFFFC); + uint16_t hi = _bus->read(0xFFFD); + + PC = (hi << 8) | lo; + } + + void Cpu::tick() { + if(_ticks == 0) { + uint8_t opcode = _bus->read(PC++); + auto instruction = _instructions[opcode]; + + if(instruction.getAddress == nullptr) { + return; + } + + auto args = (this->*instruction.getAddress)(); + (this->*instruction.process)(args); + + std::cout << instruction.name << ", A: " << (int)A << ", X: " << (int)X << ", Y: " << (int)Y << std::endl; + + _ticks = instruction.cycles; + if(instruction.variableCycles) { + _ticks += args.cycles; + } + } + + _ticks--; + } + + void Cpu::setFlag(CpuFlags flag, bool value) { + if(value) { + flags |= flag; + } else { + flags &= ~flag; + } + } + + bool Cpu::getFlag(CpuFlags flag) { + return flags & flag; + } + + // CPU instructions + + void Cpu::LDA(Cpu::InstructionArgs args) { + A = _bus->read(args.address); + setFlag(Negative, A & 0x80); + setFlag(Zero, A == 0); + } + + void Cpu::LDX(InstructionArgs args) { + X = _bus->read(args.address); + setFlag(Negative, X & 0x80); + setFlag(Zero, X == 0); + } + + void Cpu::LDY(Cpu::InstructionArgs args) { + Y = _bus->read(args.address); + setFlag(Negative, Y & 0x80); + setFlag(Zero, Y == 0); + } + + void Cpu::STA(Cpu::InstructionArgs args) { + _bus->write(args.address, A); + } + + void Cpu::STX(InstructionArgs args) { + _bus->write(args.address, X); + } + + void Cpu::CLC(Cpu::InstructionArgs args) { + setFlag(Carry, false); + } + + void Cpu::ADC(Cpu::InstructionArgs args) { + uint16_t value = _bus->read(args.address); + uint16_t result = A + value + getFlag(Carry); + + setFlag(Carry, result > 255); + setFlag(Zero, (result & 0xFF) == 0); + setFlag(Negative, result & 0x80); + setFlag(Overflow, (~(A^value) & (A^result)) & 0x80); + + A = result & 0xFF; + } + + void Cpu::SBC(Cpu::InstructionArgs args) { + uint16_t value = _bus->read(args.address) ^ 0xFF; + uint16_t result = A + value + getFlag(Carry); + + setFlag(Carry, result > 255); + setFlag(Zero, (result & 0xFF) == 0); + setFlag(Negative, result & 0x80); + setFlag(Overflow, (~(A^value) & (A^result)) & 0x80); + + A = result & 0xFF; + } + + void Cpu::DEY(Cpu::InstructionArgs args) { + Y -= 1; + setFlag(Negative, Y & 0x80); + setFlag(Zero, Y == 0); + } + + void Cpu::BNE(Cpu::InstructionArgs args) { + if(!getFlag(Zero)) { + _ticks++; + + 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) { + } + + // 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}; + } + +} diff --git a/src/Cpu.h b/src/Cpu.h new file mode 100644 index 0000000..fa5aa4f --- /dev/null +++ b/src/Cpu.h @@ -0,0 +1,85 @@ +// +// Created by Selim Mustafaev on 11.08.2023. +// + +#ifndef NES_CPU_H +#define NES_CPU_H + +#include "Bus.h" +#include +#include + +namespace nes { + + enum CpuFlags: uint8_t { + Carry = (1 << 0), + Zero = (1 << 1), + InterruptDisable = (1 << 2), + DecimalMode = (1 << 3), + Break = (1 << 4), + Unused = (1 << 5), + Overflow = (1 << 6), + Negative = (1 << 7) + }; + + class Cpu { + public: + struct InstructionArgs { + uint16_t address; + uint8_t cycles; + }; + + struct Instruction { + char name[4]; + void(Cpu::*process)(InstructionArgs); + InstructionArgs(Cpu::*getAddress)(); + uint8_t cycles; + bool variableCycles; + }; + + public: + explicit Cpu(Bus* bus); + void reset(); + void tick(); + void setFlag(CpuFlags flag, bool value); + bool getFlag(CpuFlags flag); + + private: + size_t _ticks; + Bus* _bus; + std::vector _instructions; + + private: + // Registers + uint8_t A; // Accumulator + uint8_t X; // - Index registers + uint8_t Y; // / + + uint16_t PC; // Program Counter + uint8_t SP; // Stack Pointer + uint8_t flags; + + private: + void LDA(InstructionArgs args); + void LDX(InstructionArgs args); + void LDY(InstructionArgs args); + void STA(InstructionArgs args); + void STX(InstructionArgs args); + void CLC(InstructionArgs args); + void ADC(InstructionArgs args); + void SBC(InstructionArgs args); + void DEY(InstructionArgs args); + void BNE(InstructionArgs args); + void NOP(InstructionArgs args); + + private: + InstructionArgs IMM(); + InstructionArgs ABS(); + InstructionArgs IMP(); + InstructionArgs REL(); + }; + +} + + +#endif //NES_CPU_H diff --git a/src/Nes.cpp b/src/Nes.cpp new file mode 100644 index 0000000..5987b76 --- /dev/null +++ b/src/Nes.cpp @@ -0,0 +1,40 @@ +// +// Created by Selim Mustafaev on 11.08.2023. +// + +#include "Nes.h" +#include "Rom.h" +#include "Cpu.h" + +namespace nes { + + Nes::Nes() { + _bus = std::make_unique(); + _cpu = std::make_unique(_bus.get()); + } + + void Nes::runRom(const fs::path &path) { + _rom = std::make_unique(path); + _bus->connect(_rom.get()); + reset(); + } + + uint8_t Nes::read(uint16_t address) { + return _bus->read(address); + } + + void Nes::write(uint16_t address, uint8_t value) { + _bus->write(address, value); + } + + void Nes::reset() { + _cpu->reset(); + + size_t ticks = 5000; + while (ticks > 0) { + _cpu->tick(); + ticks--; + } + } + +} diff --git a/src/Nes.h b/src/Nes.h new file mode 100644 index 0000000..310a36f --- /dev/null +++ b/src/Nes.h @@ -0,0 +1,34 @@ +// +// Created by Selim Mustafaev on 11.08.2023. +// + +#ifndef NES_NES_H +#define NES_NES_H + +#include "Cpu.h" +#include "Rom.h" +#include + +namespace nes { + + namespace fs = std::filesystem; + + class Nes { + public: + Nes(); + void runRom(const fs::path& path); + void reset(); + + public: // For debug + uint8_t read(uint16_t address); + void write(uint16_t address, uint8_t value); + + private: + std::unique_ptr _bus; + std::unique_ptr _cpu; + std::unique_ptr _rom; + }; + +} + +#endif //NES_NES_H diff --git a/src/Rom.cpp b/src/Rom.cpp new file mode 100644 index 0000000..4ad4e42 --- /dev/null +++ b/src/Rom.cpp @@ -0,0 +1,31 @@ +// +// Created by Selim Mustafaev on 09.08.2023. +// + +#include "Rom.h" +#include +#include +#include + +namespace nes { + + Rom::Rom(const fs::path &path) { + auto romSize = fs::file_size(path); + _romData = std::make_unique(romSize); + + std::ifstream rom(path); + rom.read(reinterpret_cast(_romData.get()), static_cast(romSize)); + + // UB here + // Should be std::start_lifetime_as(_romData); + // when it become available + _header = reinterpret_cast(_romData.get()); + + const size_t prgSize = _header->prgChunks*PRG_CHUNK_SIZE; + const size_t chrSize = _header->chrChunks*CHR_CHUNK_SIZE; + + _prgRom = std::span(_romData.get() + sizeof(RomHeader), prgSize); + _chrRom = std::span(_romData.get() + sizeof(RomHeader) + prgSize, chrSize); + } + +} diff --git a/src/Rom.h b/src/Rom.h new file mode 100644 index 0000000..555b9b8 --- /dev/null +++ b/src/Rom.h @@ -0,0 +1,40 @@ +// +// Created by Selim Mustafaev on 09.08.2023. +// + +#ifndef NES_ROM_H +#define NES_ROM_H + +#include +#include +#include + +namespace nes { + + namespace fs = std::filesystem; + + constexpr uint32_t ROM_MAGIC = 0x4E45531A; + constexpr size_t PRG_CHUNK_SIZE = 16*1024; + constexpr size_t CHR_CHUNK_SIZE = 8*1024; + + struct RomHeader { + uint32_t magic; + uint8_t prgChunks; + uint8_t chrChunks; + uint8_t reserved[10]; + }; + + class Rom { + public: + explicit Rom(const fs::path& path); + + private: + std::unique_ptr _romData; + RomHeader* _header; + std::span _prgRom; + std::span _chrRom; + }; + +} + +#endif //NES_ROM_H