commit fa9ff2c753e439cefca56759cf22001855f2208b Author: slinky55 Date: Tue Mar 3 23:50:39 2026 -0600 initial commit 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