initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*build*
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal 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
15
Bus.h
Normal 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
1
CLI11
Submodule
Submodule CLI11 added at fe3772d3c2
19
CMakeLists.txt
Normal file
19
CMakeLists.txt
Normal 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
5
Cartridge.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Created by lbmas on 2/25/2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Cartridge.h"
|
||||||
18
Cartridge.h
Normal file
18
Cartridge.h
Normal 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
10
common.h
Normal 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
129
cpu.cpp
Normal 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
78
cpu.h
Normal 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
75
main.cpp
Normal 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
44
memory.cpp
Normal 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
37
memory.h
Normal 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
1
spdlog
Submodule
Submodule spdlog added at 0f7562a0f9
Reference in New Issue
Block a user