Initial commit
This commit is contained in:
commit
2e6ebe201a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
cmake-build-debug/
|
||||||
|
.idea/
|
||||||
6
CMakeLists.txt
Normal file
6
CMakeLists.txt
Normal 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
26
main.cpp
Normal 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
24
src/Bus.cpp
Normal 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
29
src/Bus.h
Normal 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
180
src/Cpu.cpp
Normal 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
85
src/Cpu.h
Normal 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
40
src/Nes.cpp
Normal 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
34
src/Nes.h
Normal 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
31
src/Rom.cpp
Normal 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
40
src/Rom.h
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user