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