From fa9ff2c753e439cefca56759cf22001855f2208b Mon Sep 17 00:00:00 2001 From: slinky55 Date: Tue, 3 Mar 2026 23:50:39 -0600 Subject: [PATCH] initial commit --- .gitignore | 3 ++ .gitmodules | 6 +++ Bus.h | 15 ++++++ CLI11 | 1 + CMakeLists.txt | 19 ++++++++ Cartridge.cpp | 5 ++ Cartridge.h | 18 +++++++ common.h | 10 ++++ cpu.cpp | 129 +++++++++++++++++++++++++++++++++++++++++++++++++ cpu.h | 78 ++++++++++++++++++++++++++++++ main.cpp | 75 ++++++++++++++++++++++++++++ memory.cpp | 44 +++++++++++++++++ memory.h | 37 ++++++++++++++ spdlog | 1 + 14 files changed, 441 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Bus.h create mode 160000 CLI11 create mode 100644 CMakeLists.txt create mode 100644 Cartridge.cpp create mode 100644 Cartridge.h create mode 100644 common.h create mode 100644 cpu.cpp create mode 100644 cpu.h create mode 100644 main.cpp create mode 100644 memory.cpp create mode 100644 memory.h create mode 160000 spdlog diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f5f220 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*build* +.idea +.vscode \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..17200aa --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "spdlog"] + path = spdlog + url = https://github.com/gabime/spdlog.git +[submodule "CLI11"] + path = CLI11 + url = https://github.com/CLIUtils/CLI11.git diff --git a/Bus.h b/Bus.h new file mode 100644 index 0000000..cb16d8f --- /dev/null +++ b/Bus.h @@ -0,0 +1,15 @@ +// More of a console context, contains pointers to other console systems who add themselves to the bus as they are started up + +#ifndef BEAR_BUS_H +#define BEAR_BUS_H + +#include "Cartridge.h" +#include "cpu.h" + +struct Bus +{ + CPU* cpu; + Cartridge* cartridge; +}; + +#endif //BEAR_BUS_H \ No newline at end of file diff --git a/CLI11 b/CLI11 new file mode 160000 index 0000000..fe3772d --- /dev/null +++ b/CLI11 @@ -0,0 +1 @@ +Subproject commit fe3772d3c2969330ed0e4f32351ad066e8d375c5 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..08a6b37 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.1) +project(Bear) + +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(spdlog) +add_subdirectory(CLI11) + +add_executable(${PROJECT_NAME} main.cpp + cpu.cpp + cpu.h + memory.h + common.h + memory.cpp + Bus.h + Cartridge.h + Cartridge.cpp) + +target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog CLI11) \ No newline at end of file diff --git a/Cartridge.cpp b/Cartridge.cpp new file mode 100644 index 0000000..ba1bef2 --- /dev/null +++ b/Cartridge.cpp @@ -0,0 +1,5 @@ +// +// Created by lbmas on 2/25/2026. +// + +#include "Cartridge.h" diff --git a/Cartridge.h b/Cartridge.h new file mode 100644 index 0000000..f0b57a7 --- /dev/null +++ b/Cartridge.h @@ -0,0 +1,18 @@ +#ifndef BEAR_CARTRIDGE_H +#define BEAR_CARTRIDGE_H + +#include + +class Cartridge +{ +public: + Cartridge() = default; + explicit Cartridge(std::byte* romData) : _romData(romData) {} + + [[nodiscard]] const std::byte* rom() const { return _romData; } +private: + std::byte* _romData; +}; + + +#endif //BEAR_CARTRIDGE_H \ No newline at end of file diff --git a/common.h b/common.h new file mode 100644 index 0000000..25cdc4c --- /dev/null +++ b/common.h @@ -0,0 +1,10 @@ +#ifndef BEAR_COMMON_H +#define BEAR_COMMON_H +#include + +typedef uint8_t reg8_val_t; +typedef uint16_t reg16_val_t; + +constexpr uint16_t STACK_ADDR_TOP = 0x0200; + +#endif //BEAR_COMMON_H \ No newline at end of file diff --git a/cpu.cpp b/cpu.cpp new file mode 100644 index 0000000..f8f451f --- /dev/null +++ b/cpu.cpp @@ -0,0 +1,129 @@ +#include "cpu.h" + +CPU::CPU(IMemoryAccessor* memory) : a(0), x(0), y(0), pc(0), sp(0), stat(0), cycles(0) +{ + _memory = memory; +} + +void CPU::start() +{ + a = 0; + x = 0; + y = 0; + sp = 0xFF; + pc = 0xFFFC; +} + +void CPU::reset() +{ + pc = 0xFFFC; + sp -= 3; +} + +void CPU::loop() +{ + bool stop = false; + while (!stop) + { + const std::uint8_t op = _memory->read8(pc++); + switch (static_cast(op)) + { + case 0x69: + case 0x65: + case 0x75: + case 0x6D: + adc(op); + break; + default: + stop = true; + break; + } + } +} + +std::uint8_t CPU::readImm() const +{ + return _memory->read8(pc); +} + +std::uint8_t CPU::readZp() const +{ + const std::uint8_t zpOffset = _memory->read8(pc); + return _memory->read8(0x0000 + zpOffset); +} + +std::uint8_t CPU::readZpX() const +{ + const std::uint8_t zpOffset = _memory->read8(pc); + const uint16_t eff = 0x0000 + zpOffset + x & 0x00FF; + return _memory->read8(eff); +} + +std::uint8_t CPU::readAbs() +{ + const std::uint8_t low = _memory->read8(pc++); + const std::uint8_t high = _memory->read8(pc); + const uint16_t eff = static_cast(high) << 8 | low; + return _memory->read8(eff); +} + +std::uint8_t CPU::readAbsX() +{ + const std::uint8_t low = _memory->read8(pc++); + const std::uint8_t high = _memory->read8(pc); + const uint16_t eff = (static_cast(high) << 8 | low) + x; + return _memory->read8(eff); +} + +std::uint8_t CPU::readAbsY() +{ + const std::uint8_t low = _memory->read8(pc++); + const std::uint8_t high = _memory->read8(pc); + const uint16_t eff = (static_cast(high) << 8 | low) + y; + return _memory->read8(eff); +} + +std::uint8_t CPU::readIdxInd() +{ + +} + +std::uint8_t CPU::readIndIdx() +{ + +} + +void CPU::adc(std::uint8_t opcode) +{ + uint8_t val; + switch (static_cast(opcode)) + { + case 0x69: + val = readImm(); + break; + case 0x65: + val = readZp(); + break; + case 0x75: + val = readZpX(); + break; + case 0x6D: + val = readAbs(); + break; + default: + stop = true; + return; + } + + const uint16_t result = a + val + static_cast(c()); + a = result & 0xFF; + setFlagsFromResult(result); +} + +void CPU::setFlagsFromResult(std::uint16_t result) +{ + if (result > 0xFF) // unsigned overflow + { + c(true); + } +} diff --git a/cpu.h b/cpu.h new file mode 100644 index 0000000..dff7eb2 --- /dev/null +++ b/cpu.h @@ -0,0 +1,78 @@ +#ifndef BEAR_CPU_H +#define BEAR_CPU_H + +#include "common.h" +#include "memory.h" + +class CPU +{ +public: + explicit CPU(IMemoryAccessor* memory); + + void start(); + void reset(); +private: + enum STATUS_FLAGS + { + C = 1 << 0, + Z = 1 << 1, + I = 1 << 2, + D = 1 << 3, + B = 1 << 4, + O = 1 << 6, + N = 1 << 7, + }; + + uint8_t a; + uint8_t x, y; + + uint16_t pc; + + uint8_t sp; + + IMemoryAccessor* _memory; + + // Stat flags + uint8_t stat; + + int cycles; + + bool stop; + + void c(const bool _v) { _v ? (stat |= C) : stat &= ~C; } + [[nodiscard]] bool c() const { return (stat & C) > 0; } + + void z(const bool _v) { _v ? (stat |= Z) : stat &= ~Z; } + [[nodiscard]] bool z() const { return (stat & Z) > 0; } + + void i(const bool _v) { _v ? (stat |= I) : stat &= ~I; } + [[nodiscard]] bool i() const { return (stat & I) > 0; } + + void d(const bool _v) { _v ? (stat |= D) : stat &= ~D; } + [[nodiscard]] bool d() const { return (stat & D) > 0; } + + void b(const bool _v) { _v ? (stat |= B) : stat &= ~B; } + [[nodiscard]] bool b() const { return (stat & B) > 0; } + + void o(const bool _v) { _v ? (stat |= O) : stat &= ~O; } + [[nodiscard]] bool o() const { return (stat & O) > 0; } + // END Stat flags + + void loop(); + + [[nodiscard]] std::uint8_t readImm() const; + [[nodiscard]] std::uint8_t readZp() const; + [[nodiscard]] std::uint8_t readZpX() const; + [[nodiscard]] std::uint8_t readAbs(); + [[nodiscard]] std::uint8_t readAbsX(); + [[nodiscard]] std::uint8_t readAbsY(); + [[nodiscard]] std::uint8_t readIdxInd(); + [[nodiscard]] std::uint8_t readIndIdx(); + + void adc(std::uint8_t val); + + void setFlagsFromResult(std::uint16_t result); +}; + + +#endif //BEAR_CPU_H \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..d07a071 --- /dev/null +++ b/main.cpp @@ -0,0 +1,75 @@ +#include "Bus.h" +#include "cpu.h" + +#include "spdlog/spdlog.h" +#include "spdlog/sinks/rotating_file_sink.h" + +#include "CLI11/include/CLI/CLI.hpp" + +Bus* g_bus; + +IMemoryAccessor* g_cpuMemory; +CPU* g_cpu; + +Cartridge* g_cartridge; + +std::shared_ptr gLogger; +spdlog::logger* Logger() { return gLogger.get(); } + +void initLogger() +{ + constexpr static std::size_t MAX_LOG_SIZE = 1048576 * 5; + constexpr static std::size_t MAX_LOG_FILES = 10; + + gLogger = spdlog::rotating_logger_mt("bear", "logs/bear.log", MAX_LOG_SIZE, MAX_LOG_FILES); +} + +int main(int argc, char** argv) +{ + initLogger(); + Logger()->info("Bear started..."); + + CLI::App args { "A NES Emulator" }; + argv = args.ensure_utf8(argv); + + std::string filename; + args.add_option("-f, --file", filename, ".nes ROM file to load"); + + CLI11_PARSE(args, argc, argv); + + if (filename.empty()) + { + Logger()->error("ROM file not specified"); + exit(-1); + } + + std::ifstream romFile(filename.c_str(), std::ios::binary); + if (!romFile.is_open()) + { + Logger()->error("Could not open ROM file"); + exit(-1); + } + + romFile.seekg(0, std::ios::end); + const std::streamsize romFileSize = romFile.tellg(); + romFile.seekg(0, std::ios::beg); + + auto* rom = new std::byte[romFileSize]; + + if (!romFile.read(reinterpret_cast(rom), romFileSize)) + { + Logger()->error("Could not read ROM file"); + exit(-1); + } + + g_cartridge = new Cartridge(rom); + g_bus->cartridge = g_cartridge; + + g_cpuMemory = new CPUMemoryAccessor(g_bus); + g_cpu = new CPU(g_cpuMemory); + g_bus->cpu = g_cpu; + + g_cpu->start(); + + return 0; +} \ No newline at end of file diff --git a/memory.cpp b/memory.cpp new file mode 100644 index 0000000..1223a16 --- /dev/null +++ b/memory.cpp @@ -0,0 +1,44 @@ +#include "memory.h" + +#include + +#include "Bus.h" + +CPUMemoryAccessor::CPUMemoryAccessor(Bus* bus) +{ + ram = static_cast(malloc(0x0200)); + this->bus = bus; +} + +void CPUMemoryAccessor::write(std::uint16_t _addr, std::uint8_t value) +{ + if (isRamAddress(_addr)) + { + writeToRam(_addr, value); + } +} + +std::uint8_t CPUMemoryAccessor::read8(const std::uint16_t _addr) +{ + if (isRamAddress(_addr)) + { + return readFromRam(_addr); + } + + return 0x00; +} + +std::uint8_t CPUMemoryAccessor::readFromRam(const std::uint16_t _addr) const +{ + return ram[_addr]; +} + +void CPUMemoryAccessor::writeToRam(const std::uint16_t _addr, const std::uint8_t value) const +{ + ram[_addr] = value; +} + +bool CPUMemoryAccessor::isRamAddress(const std::uint16_t _addr) +{ + return _addr <= 0x0200; +} diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..ff606d6 --- /dev/null +++ b/memory.h @@ -0,0 +1,37 @@ +#ifndef BEAR_MEMORY_H +#define BEAR_MEMORY_H + +#include +#include + +#include "Bus.h" + +class IMemoryAccessor +{ +public: + virtual ~IMemoryAccessor() = default; + + virtual void write(std::uint16_t _addr, std::uint8_t value) = 0; + virtual std::uint8_t read8(std::uint16_t _addr) = 0; +}; + +class CPUMemoryAccessor : public IMemoryAccessor +{ +public: + explicit CPUMemoryAccessor(Bus* bus); + ~CPUMemoryAccessor() override = default; + + void write(std::uint16_t _addr, std::uint8_t value) override; + std::uint8_t read8(std::uint16_t _addr) override; + +private: + std::uint8_t* ram = nullptr; + Bus* bus = nullptr; + + static bool isRamAddress(std::uint16_t _addr); + + [[nodiscard]] std::uint8_t readFromRam(std::uint16_t _addr) const; + void writeToRam(std::uint16_t _addr, std::uint8_t value) const; +}; + +#endif //BEAR_MEMORY_H \ No newline at end of file diff --git a/spdlog b/spdlog new file mode 160000 index 0000000..0f7562a --- /dev/null +++ b/spdlog @@ -0,0 +1 @@ +Subproject commit 0f7562a0f9273cfc71fddc6ae52ebff7a490fa04