initial commit

This commit is contained in:
2026-03-03 23:50:39 -06:00
commit fa9ff2c753
14 changed files with 441 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*build*
.idea
.vscode

6
.gitmodules vendored Normal file
View File

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

15
Bus.h Normal file
View File

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

1
CLI11 Submodule

Submodule CLI11 added at fe3772d3c2

19
CMakeLists.txt Normal file
View File

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

5
Cartridge.cpp Normal file
View File

@@ -0,0 +1,5 @@
//
// Created by lbmas on 2/25/2026.
//
#include "Cartridge.h"

18
Cartridge.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef BEAR_CARTRIDGE_H
#define BEAR_CARTRIDGE_H
#include <cstddef>
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

10
common.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef BEAR_COMMON_H
#define BEAR_COMMON_H
#include <cstdint>
typedef uint8_t reg8_val_t;
typedef uint16_t reg16_val_t;
constexpr uint16_t STACK_ADDR_TOP = 0x0200;
#endif //BEAR_COMMON_H

129
cpu.cpp Normal file
View File

@@ -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<int>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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<int>(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<std::uint8_t>(c());
a = result & 0xFF;
setFlagsFromResult(result);
}
void CPU::setFlagsFromResult(std::uint16_t result)
{
if (result > 0xFF) // unsigned overflow
{
c(true);
}
}

78
cpu.h Normal file
View File

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

75
main.cpp Normal file
View File

@@ -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<spdlog::logger> 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<char*>(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;
}

44
memory.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include "memory.h"
#include <cstdlib>
#include "Bus.h"
CPUMemoryAccessor::CPUMemoryAccessor(Bus* bus)
{
ram = static_cast<std::uint8_t*>(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;
}

37
memory.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef BEAR_MEMORY_H
#define BEAR_MEMORY_H
#include <cstddef>
#include <cstdint>
#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

1
spdlog Submodule

Submodule spdlog added at 0f7562a0f9