Initial commit

This commit is contained in:
Selim Mustafaev 2023-08-18 22:54:42 +03:00
commit 2e6ebe201a
11 changed files with 497 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
cmake-build-debug/
.idea/

6
CMakeLists.txt Normal file
View File

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

26
main.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "src/Nes.h"
#include <iostream>
#include <sstream>
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;
}

24
src/Bus.cpp Normal file
View File

@ -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<uint8_t[]>(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;
}
}

29
src/Bus.h Normal file
View File

@ -0,0 +1,29 @@
//
// Created by Selim Mustafaev on 12.08.2023.
//
#ifndef NES_BUS_H
#define NES_BUS_H
#include "Rom.h"
#include <cstdint>
#include <memory>
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<uint8_t[]> _ram;
Rom* _rom;
};
}
#endif //NES_BUS_H

180
src/Cpu.cpp Normal file
View File

@ -0,0 +1,180 @@
//
// Created by Selim Mustafaev on 11.08.2023.
//
#include "Cpu.h"
#include <iostream>
namespace nes {
Cpu::Cpu(Bus* bus): _ticks{}, _bus{bus}, A{}, X{}, Y{}, PC{}, SP{}, flags{} {
_instructions = std::vector<Instruction>(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};
}
}

85
src/Cpu.h Normal file
View File

@ -0,0 +1,85 @@
//
// Created by Selim Mustafaev on 11.08.2023.
//
#ifndef NES_CPU_H
#define NES_CPU_H
#include "Bus.h"
#include <cstdint>
#include <vector>
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<Instruction> _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

40
src/Nes.cpp Normal file
View File

@ -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<Bus>();
_cpu = std::make_unique<Cpu>(_bus.get());
}
void Nes::runRom(const fs::path &path) {
_rom = std::make_unique<Rom>(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--;
}
}
}

34
src/Nes.h Normal file
View File

@ -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 <filesystem>
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> _bus;
std::unique_ptr<Cpu> _cpu;
std::unique_ptr<Rom> _rom;
};
}
#endif //NES_NES_H

31
src/Rom.cpp Normal file
View File

@ -0,0 +1,31 @@
//
// 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);
}
}

40
src/Rom.h Normal file
View File

@ -0,0 +1,40 @@
//
// Created by Selim Mustafaev on 09.08.2023.
//
#ifndef NES_ROM_H
#define NES_ROM_H
#include <filesystem>
#include <memory>
#include <span>
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<std::byte[]> _romData;
RomHeader* _header;
std::span<std::byte> _prgRom;
std::span<std::byte> _chrRom;
};
}
#endif //NES_ROM_H