initial commit

This commit is contained in:
Lauchmelder 2022-02-28 16:04:25 +01:00
commit ff8389a76b
No known key found for this signature in database
GPG key ID: C2403C69D78F011D
46 changed files with 9118 additions and 0 deletions

109
src/Application.cpp Normal file
View file

@ -0,0 +1,109 @@
#include "Application.hpp"
#include <stdexcept>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <imgui/backends/imgui_impl_glfw.h>
#include <imgui/backends/imgui_impl_opengl3.h>
#include <imgui/imgui.h>
#include "Log.hpp"
#include "Bus.hpp"
#include "Debugger.hpp"
#include "gfx/Window.hpp"
void Application::Launch()
{
glfwInit();
Application* app = nullptr;
try
{
app = new Application;
}
catch (const std::runtime_error& err)
{
LOG_CORE_FATAL(err.what());
delete app;
glfwTerminate();
return;
}
if (app == nullptr)
{
LOG_CORE_ERROR("Application object is nullptr");
glfwTerminate();
return;
}
while (app->Update());
delete app;
glfwTerminate();
}
Application::Application() :
bus(nullptr), window(nullptr)
{
LOG_CORE_INFO("Creating window");
try
{
window = new Window(1280, 720, "NES Emulator");
}
catch (const std::runtime_error& err)
{
LOG_CORE_ERROR("Window creation failed");
throw err;
}
LOG_CORE_INFO("Loading OpenGL API");
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
throw std::runtime_error("Failed to set up OpenGL loader");
LOG_CORE_INFO("Setting up ImGui");
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
ImGui_ImplGlfw_InitForOpenGL(window->GetNativeWindow(), true);
ImGui_ImplOpenGL3_Init("#version 460 core");
ImGui::StyleColorsDark();
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
bus = new Bus;
debugger = new Debugger(bus);
}
Application::~Application()
{
delete debugger;
if(bus)
delete bus;
delete window;
}
bool Application::Update()
{
glfwPollEvents();
if (!debugger->Update())
return false;
window->Begin();
debugger->Render();
window->End();
return !window->ShouldClose();
}

22
src/Application.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
class Bus;
class Window;
class Debugger;
class Application
{
public:
static void Launch();
private:
Application();
~Application();
bool Update();
private:
Window* window;
Bus* bus;
Debugger* debugger;
};

142
src/Bus.cpp Normal file
View file

@ -0,0 +1,142 @@
#include "Bus.hpp"
#include "Log.hpp"
#include <stdexcept>
Bus::Bus() :
cpu(this), ppu(this), cartridge(this)
{
LOG_CORE_INFO("Allocating RAM");
RAM = std::vector<Byte>(0x800);
LOG_CORE_INFO("Allocating VRAM");
VRAM = std::vector<Byte>(0x1000);
LOG_CORE_INFO("Inserting cartridge");
cartridge.Load("roms/nestest.nes");
LOG_CORE_INFO("Powering up CPU");
cpu.Powerup();
LOG_CORE_INFO("Powering up PPU");
ppu.Powerup();
}
void Bus::Reboot()
{
cpu.Powerup();
ppu.Powerup();
}
void Bus::Reset()
{
cpu.Reset();
ppu.Reset();
}
uint8_t Bus::Tick()
{
uint8_t result = cpu.Tick();
// 3 ppu ticks per cpu tick
ppu.Tick();
ppu.Tick();
ppu.Tick();
return result;
}
bool Bus::Instruction()
{
try
{
while (Tick());
}
catch (const std::runtime_error& err)
{
LOG_CORE_FATAL("Fatal Bus error: {0}", err.what());
cpu.Halt();
return true;
}
return true;
}
bool Bus::Frame()
{
try
{
while (!ppu.IsFrameDone())
Tick();
}
catch (const std::runtime_error& err)
{
LOG_CORE_FATAL("Fatal Bus error: {0}", err.what());
cpu.Halt();
return true;
}
return true;
}
Byte Bus::ReadCPU(Word addr)
{
if (0x0000 <= addr && addr < 0x2000)
{
return RAM[addr & 0x7FF];
}
else if (0x2000 <= addr && addr < 0x4000)
{
return ppu.ReadRegister(addr & 0x7);
}
else if (0x8000 <= addr && addr <= 0xFFFF)
{
return cartridge.ReadCPU(addr);
}
}
Byte Bus::ReadPPU(Word addr)
{
addr &= 0x3FFF;
if (0x0000 <= addr && addr < 0x2000)
{
return cartridge.ReadPPU(addr);
}
else if(0x2000 <= addr && addr < 0x4000)
{
return VRAM[addr & 0xFFF];
}
return 0x00;
}
void Bus::WriteCPU(Word addr, Byte val)
{
if (0x0000 <= addr && addr < 0x2000)
{
RAM[addr & 0x7FF] = val;
}
else if (0x2000 <= addr && addr < 0x4000)
{
ppu.WriteRegister(addr & 0x7, val);
}
else if (0x8000 <= addr && addr <= 0xFFFF)
{
cartridge.WriteCPU(addr, val);
}
}
void Bus::WritePPU(Word addr, Byte val)
{
addr &= 0x3FFF;
if (0x0000 <= addr && addr < 0x2000)
{
cartridge.WritePPU(addr, val);
}
else if (0x2000 <= addr && addr < 0x4000)
{
VRAM[addr & 0xFFF] = val;
}
}

38
src/Bus.hpp Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include "Types.hpp"
#include "CPU.hpp"
#include "PPU.hpp"
#include "Cartridge.hpp"
class Bus
{
friend class Debugger;
friend class MemoryViewer;
friend class NametableViewer;
public:
Bus();
void Reboot();
void Reset();
uint8_t Tick();
bool Instruction();
bool Frame();
Byte ReadCPU(Word addr);
Byte ReadPPU(Word addr);
void WriteCPU(Word addr, Byte val);
void WritePPU(Word addr, Byte val);
inline void NMI() { cpu.NMI(); }
private:
std::vector<Byte> RAM, VRAM;
CPU cpu;
PPU ppu;
Cartridge cartridge;
};

33
src/CMakeLists.txt Normal file
View file

@ -0,0 +1,33 @@
add_executable(nesemu
"main.cpp"
"Application.cpp"
"Bus.cpp"
"CPU.cpp"
"Cartridge.cpp"
"mappers/Mapper000.cpp"
"Log.cpp"
"PPU.cpp"
"gfx/Window.cpp"
"debugger/CPUWatcher.cpp"
"debugger/Debugger.cpp" "debugger/PPUWatcher.cpp" "debugger/Disassembler.cpp" "debugger/MemoryViewer.cpp" "debugger/NametableViewer.cpp")
target_include_directories(nesemu PRIVATE
mappers
gfx
debugger
${IMGUI_INCLUDE}
)
target_sources(nesemu PRIVATE
${IMGUI_SOURCES}
)
target_link_libraries(nesemu
spdlog
glfw
glad
)
add_custom_command(TARGET nesemu POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/roms $<TARGET_FILE_DIR:nesemu>/roms
)

1239
src/CPU.cpp Normal file

File diff suppressed because it is too large Load diff

191
src/CPU.hpp Normal file
View file

@ -0,0 +1,191 @@
#pragma once
#include <array>
#include <functional>
#include <sstream>
#include <deque>
#include "Types.hpp"
class Bus;
using Operation = std::function<void(void)>;
using AddressingMode = std::function<void(void)>;
enum class Addressing
{
ABS,
ABX,
ABY,
ACC,
IDX,
IDY,
IMM,
IMP,
IND,
REL,
ZPG,
ZPX,
ZPY
};
union StatusFlag
{
struct
{
Byte Carry : 1;
Byte Zero : 1;
Byte InterruptDisable : 1;
Byte Decimal : 1;
Byte Break : 1;
Byte NoEffect : 1;
Byte Overflow : 1;
Byte Negative : 1;
} Flag;
Word Raw;
};
struct Instruction
{
Operation Operation = nullptr;
AddressingMode Mode = nullptr;
Addressing AddrType = Addressing::IMP;
uint8_t Size = 0;
uint8_t Cycles = 0;
char Mnemonic[5] = " XXX";
};
class CPU
{
friend class Debugger;
friend class CPUWatcher;
friend class Disassembler;
public:
CPU(Bus* bus);
uint8_t Tick();
void Powerup();
void Reset();
inline void Halt() { halted = true; }
void IRQ();
void NMI();
private:
void CreateInstructionTable();
inline void Push(Byte val) { Write(0x0100 | (sp--), val); }
inline Byte Pop() { return Read(0x0100 | (++sp)); }
Byte Read(Word addr);
void Write(Word addr, Byte val);
private:
Address rawAddress;
Address absoluteAddress;
Byte relativeAddress;
Byte fetchedVal;
bool accumulatorAddressing = false;
void ABS();
void ABX();
void ABY();
void ACC();
void IDX();
void IDY();
void IMM();
void IMP();
void IND();
void REL();
void ZPG();
void ZPX();
void ZPY();
private:
void ADC();
void AND();
void ASL();
void BCC();
void BCS();
void BEQ();
void BIT();
void BMI();
void BNE();
void BPL();
void BRK();
void BVC();
void BVS();
void CLC();
void CLD();
void CLI();
void CLV();
void CMP();
void CPX();
void CPY();
void DCP();
void DEC();
void DEX();
void DEY();
void EOR();
void INC();
void INX();
void INY();
void ISC();
void JMP();
void JSR();
void LAX();
void LDA();
void LDX();
void LDY();
void LSR();
void NOP();
void ORA();
void PHA();
void PHP();
void PLA();
void PLP();
void RLA();
void ROL();
void ROR();
void RRA();
void RTI();
void RTS();
void SAX();
void SBC();
void SEC();
void SED();
void SEI();
void SLO();
void SRE();
void STA();
void STX();
void STY();
void TAX();
void TAY();
void TSX();
void TXA();
void TXS();
void TYA();
private:
Byte acc;
Byte idx, idy;
Address pc;
Word sp;
StatusFlag status;
Instruction* currentInstruction = nullptr;
uint8_t remainingCycles = 0;
uint8_t additionalCycles = 0;
uint64_t totalCycles = 0;
std::deque<Word> pastPCs;
bool halted = false;
#ifndef NDEBUG
std::stringstream debugString;
#endif
private:
std::array<Instruction, 256> InstructionTable;
Bus* bus;
};

59
src/Cartridge.cpp Normal file
View file

@ -0,0 +1,59 @@
#include "Cartridge.hpp"
#include "Bus.hpp"
#include "Log.hpp"
#include <fstream>
#include "Mapper.hpp"
#include "mappers/Mapper000.hpp"
Cartridge::Cartridge(Bus* bus) :
bus(bus)
{
}
Cartridge::~Cartridge()
{
delete mapper;
}
Byte Cartridge::ReadCPU(Word addr)
{
return mapper->ReadCPU(addr);
}
Byte Cartridge::ReadPPU(Word addr)
{
return mapper->ReadPPU(addr);
}
void Cartridge::WriteCPU(Word addr, Byte val)
{
mapper->WriteCPU(addr, val);
}
void Cartridge::WritePPU(Word addr, Byte val)
{
mapper->WritePPU(addr, val);
}
void Cartridge::Load(std::string path)
{
std::ifstream file(path, std::ios::binary);
if (!file)
throw std::runtime_error("Failed to open file " + path);
LOG_CORE_INFO("Extracting header");
Header header;
file.read((char*)&header, sizeof(Header));
uint8_t mapperNumber = (header.MapperHi & 0xF0) | (header.MapperLo >> 4);
LOG_CORE_INFO("Cartridge requires Mapper {0:d}", mapperNumber);
switch (mapperNumber)
{
case 0: mapper = new Mapper000(header, file); break;
default:
throw std::runtime_error("Unsupported mapper ID " + std::to_string(mapperNumber));
}
}

42
src/Cartridge.hpp Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <memory>
#include "Types.hpp"
#include "Mapper.hpp"
class Bus;
struct Header
{
Byte Signature[4];
Byte PrgROM;
Byte ChrROM;
Byte MapperLo;
Byte MapperHi;
Byte PrgRAM;
Byte TV1;
Byte TV2;
Byte Padding[5];
};
class Cartridge
{
public:
Cartridge(Bus* bus);
~Cartridge();
Byte ReadCPU(Word addr);
Byte ReadPPU(Word addr);
void WriteCPU(Word addr, Byte val);
void WritePPU(Word addr, Byte val);
void Load(std::string path);
inline Mapper* GetMapper() { return mapper; }
private:
Mapper* mapper;
Bus* bus;
};

18
src/Log.cpp Normal file
View file

@ -0,0 +1,18 @@
#include "Log.hpp"
#include "spdlog/sinks/stdout_color_sinks.h"
std::shared_ptr<spdlog::logger> Log::coreLogger;
std::shared_ptr<spdlog::logger> Log::debugLogger;
void Log::Init()
{
debugLogger = spdlog::stdout_color_mt("DEBUG");
debugLogger->set_pattern("%^[%T] %n: %v%$");
debugLogger->set_level(spdlog::level::trace);
coreLogger = spdlog::stdout_color_mt("CORE");
coreLogger->set_pattern("%^[%T] %n: %v%$");
coreLogger->set_level(spdlog::level::info);
LOG_CORE_INFO("Logger initialized");
}

30
src/Log.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/fmt/ostr.h>
#define FORCE_NO_DEBUG_LOG
class Log
{
public:
static void Init();
inline static std::shared_ptr<spdlog::logger> GetCoreLogger() { return coreLogger; }
inline static std::shared_ptr<spdlog::logger> GetDebugLogger() { return debugLogger; }
private:
static std::shared_ptr<spdlog::logger> coreLogger;
static std::shared_ptr<spdlog::logger> debugLogger;
};
#define LOG_CORE_TRACE(...) ::Log::GetCoreLogger()->trace(__VA_ARGS__)
#define LOG_CORE_INFO(...) ::Log::GetCoreLogger()->info(__VA_ARGS__)
#define LOG_CORE_WARN(...) ::Log::GetCoreLogger()->warn(__VA_ARGS__)
#define LOG_CORE_ERROR(...) ::Log::GetCoreLogger()->error(__VA_ARGS__)
#define LOG_CORE_FATAL(...) ::Log::GetCoreLogger()->critical(__VA_ARGS__)
#define LOG_DEBUG_TRACE(...) ::Log::GetDebugLogger()->trace(__VA_ARGS__)
#define LOG_DEBUG_INFO(...) ::Log::GetDebugLogger()->info(__VA_ARGS__)
#define LOG_DEBUG_WARN(...) ::Log::GetDebugLogger()->warn(__VA_ARGS__)
#define LOG_DEBUG_ERROR(...) ::Log::GetDebugLogger()->error(__VA_ARGS__)
#define LOG_DEBUG_FATAL(...) ::Log::GetDebugLogger()->critical(__VA_ARGS__)

22
src/Mapper.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include "Types.hpp"
class Mapper
{
friend class Disassembler;
public:
virtual Byte ReadCPU(Word addr) = 0;
virtual Byte ReadPPU(Word addr) = 0;
virtual void WriteCPU(Word addr, Byte val) = 0;
virtual void WritePPU(Word addr, Byte val) = 0;
protected:
Mapper() = default;
protected:
std::vector<Byte> PRG_ROM;
std::vector<Byte> CHR_ROM;
};

156
src/PPU.cpp Normal file
View file

@ -0,0 +1,156 @@
#include "PPU.hpp"
#include "Log.hpp"
#include "Bus.hpp"
PPU::PPU(Bus* bus) :
bus(bus), ppuctrl{0}, ppustatus{0}
{
}
void PPU::Powerup()
{
ppuctrl.Raw = 0b00000000;
ppumask.Raw = 0b00000000;
ppustatus.Raw = 0b10100000;
ppuscroll.x = 0x00;
ppuscroll.y = 0x00;
ppuaddr.Raw = 0x0000;
x = 0;
y = 0;
addressLatch = 0;
}
void PPU::Reset()
{
ppuctrl.Raw = 0b00000000;
ppumask.Raw = 0b00000000;
ppuscroll.x = 0x00;
ppuscroll.y = 0x00;
x = 0;
y = 0;
addressLatch = 1;
}
void PPU::Tick()
{
if (y == 241 && x == 1)
{
ppustatus.Flag.VBlankStarted = 1;
if (ppuctrl.Flag.VBlankNMI)
bus->NMI();
isFrameDone = true;
}
if (y == 261 && x == 1)
ppustatus.Flag.VBlankStarted = 0;
x++;
if (x > 340)
{
x = 0;
y++;
if (y > 261)
y = 0;
}
}
Byte PPU::ReadRegister(Byte id)
{
Byte returnVal = 0x00;
switch (id)
{
case 0:
returnVal = ppuctrl.Raw;
break;
case 1:
returnVal = ppumask.Raw;
break;
case 2:
returnVal = ppustatus.Raw;
ppustatus.Flag.VBlankStarted = 0;
addressLatch = 0;
break;
case 5:
returnVal = 0x00;
break;
case 6:
returnVal = 0x00;
break;
case 7:
returnVal = bus->ReadPPU(ppuaddr.Raw);
ppuaddr.Raw += (ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
break;
default:
LOG_CORE_WARN("Tried to read unimplemented PPU register $20{0:02X}", (Word)id);
break;
}
return returnVal;
}
void PPU::WriteRegister(Byte id, Byte val)
{
switch (id)
{
case 0:
ppuctrl.Raw = val;
break;
case 1:
ppumask.Raw = val;
break;
case 2:
ppustatus.Raw = val;
break;
case 5:
if (addressLatch == 0)
ppuscroll.x = val;
else
ppuscroll.y = val;
addressLatch = 1 - addressLatch;
break;
case 6:
if (addressLatch == 0)
ppuaddr.Bytes.hi = val;
else
ppuaddr.Bytes.lo = val;
addressLatch = 1 - addressLatch;
break;
case 7:
bus->WritePPU(ppuaddr.Raw, val);
ppuaddr.Raw += (ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
break;
default:
LOG_CORE_WARN("Tried to write unimplemented PPU register $20{0:02X}", (Word)id);
return;
}
ppustatus.Flag.Unused = val & 0x1F;
}
Byte PPU::Read(Word addr)
{
return bus->ReadPPU(addr);
}
void PPU::Write(Word addr, Byte val)
{
bus->WritePPU(addr, val);
}

90
src/PPU.hpp Normal file
View file

@ -0,0 +1,90 @@
#pragma once
#include "Types.hpp"
class Bus;
class PPU
{
friend class PPUWatcher;
public:
PPU(Bus* bus);
void Powerup();
void Reset();
void Tick();
Byte ReadRegister(Byte id);
void WriteRegister(Byte id, Byte val);
inline bool IsFrameDone() { bool returnVal = isFrameDone; isFrameDone = false; return returnVal; }
private:
Byte Read(Word addr);
void Write(Word addr, Byte val);
private: // Registers
union
{
struct
{
Byte BaseNametableAddr : 2;
Byte VRAMAddrIncrement : 1;
Byte SpritePatternTableAddr : 1;
Byte BackgrPatternTableAddr : 1;
Byte SpriteSize : 1;
Byte MasterSlaveSelect : 1;
Byte VBlankNMI : 1;
} Flag;
Byte Raw;
} ppuctrl;
union
{
struct
{
Byte Greyscale : 1;
Byte BackgroundOnLeft : 1;
Byte SpriteOnLeft : 1;
Byte ShowBackground : 1;
Byte ShowSprites : 1;
Byte EmphasizeRed : 1;
Byte EmphasizeGreen : 1;
Byte EmphasizeBlue : 1;
} Flag;
Byte Raw;
} ppumask;
union
{
struct
{
Byte Unused : 5;
Byte SpriteOverflow : 1;
Byte SpriteZeroHit : 1;
Byte VBlankStarted : 1;
} Flag;
Byte Raw;
} ppustatus;
struct
{
Byte x;
Byte y;
} ppuscroll;
Address ppuaddr;
uint16_t x, y;
Byte addressLatch = 0;
private:
bool isFrameDone = false;
Bus* bus;
};

17
src/Types.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
using Byte = uint8_t;
using Word = uint16_t;
union Address
{
struct
{
Byte lo;
Byte hi;
} Bytes;
Word Raw;
};

101
src/debugger/CPUWatcher.cpp Normal file
View file

@ -0,0 +1,101 @@
#include "CPUWatcher.hpp"
#include <iomanip>
#include <imgui/imgui.h>
#include "../CPU.hpp"
CPUWatcher::CPUWatcher(CPU* cpu) :
DebugWindow("CPU Watch"), cpu(cpu)
{
}
void CPUWatcher::OnRender()
{
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(title.c_str(), &isOpen))
{
ImGui::End();
return;
}
ImGui::Text("Registers");
if (ImGui::BeginTable("Registers", 6))
{
ImGui::TableNextColumn();
ImGui::Text("A");
ImGui::TableNextColumn();
ImGui::Text("X");
ImGui::TableNextColumn();
ImGui::Text("Y");
ImGui::TableNextColumn();
ImGui::Text("PC");
ImGui::TableNextColumn();
ImGui::Text("SP");
ImGui::TableNextColumn();
ImGui::Text("P");
ImGui::TableNextColumn();
ImGui::Text("%02X", cpu->acc);
ImGui::TableNextColumn();
ImGui::Text("%02X", cpu->idx);
ImGui::TableNextColumn();
ImGui::Text("%02X", cpu->idy);
ImGui::TableNextColumn();
ImGui::Text("%04X", cpu->pc);
ImGui::TableNextColumn();
ImGui::Text("%02X", cpu->sp);
ImGui::TableNextColumn();
ImGui::Text("%02X", cpu->status.Raw);
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("Status Flag Breakdown");
if (ImGui::BeginTable("Status", 8))
{
ImGui::TableNextColumn();
ImGui::Text("N");
ImGui::TableNextColumn();
ImGui::Text("V");
ImGui::TableNextColumn();
ImGui::Text("U");
ImGui::TableNextColumn();
ImGui::Text("B");
ImGui::TableNextColumn();
ImGui::Text("D");
ImGui::TableNextColumn();
ImGui::Text("I");
ImGui::TableNextColumn();
ImGui::Text("Z");
ImGui::TableNextColumn();
ImGui::Text("C");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.Negative ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.Overflow ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.NoEffect ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.Break ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.Decimal ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.InterruptDisable ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.Zero ? "1" : "-");
ImGui::TableNextColumn();
ImGui::Text(cpu->status.Flag.Carry ? "1" : "-");
ImGui::TableNextColumn();
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("Halted: %s", cpu->halted ? "Yes" : "No");
ImGui::End();
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "DebugWindow.hpp"
class CPU;
class CPUWatcher :
public DebugWindow
{
public:
CPUWatcher(CPU* cpu);
virtual void OnRender() override;
private:
CPU* cpu;
};

View file

@ -0,0 +1,19 @@
#pragma once
#include <string>
class DebugWindow
{
public:
DebugWindow(const std::string& title) :
title(title)
{
}
virtual ~DebugWindow() = default;
virtual void OnRender() = 0;
public:
const std::string title;
bool isOpen = false;
};

127
src/debugger/Debugger.cpp Normal file
View file

@ -0,0 +1,127 @@
#include "Debugger.hpp"
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "../Bus.hpp"
#include "CPUWatcher.hpp"
#include "PPUWatcher.hpp"
#include "Disassembler.hpp"
#include "MemoryViewer.hpp"
#include "NametableViewer.hpp"
Debugger::Debugger(Bus* bus) :
bus(bus)
{
windows.push_back(new CPUWatcher(&bus->cpu));
windows.push_back(new PPUWatcher(&bus->ppu));
windows.push_back(new Disassembler(&bus->cpu));
windows.push_back(new MemoryViewer(bus));
windows.push_back(new NametableViewer(bus));
}
Debugger::~Debugger()
{
for (DebugWindow* window : windows)
delete window;
}
bool Debugger::Update()
{
if (running)
return bus->Frame();
return true;
}
void Debugger::Render()
{
ImGui::SetNextWindowSize(ImVec2(400, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Debugger", (bool*)0, ImGuiWindowFlags_MenuBar))
{
ImGui::End();
return;
}
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Tools"))
{
for (DebugWindow* window : windows)
{
ImGui::MenuItem(window->title.c_str(), NULL, &window->isOpen);
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::Separator();
if (ImGui::CollapsingHeader("Controls", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, running);
if (running)
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.7f, 0.7f, 0.7f, 0.7f });
if (ImGui::Button("Single Step"))
bus->Instruction();
ImGui::SameLine();
if (ImGui::Button("Single Frame"))
bus->Frame();
if (running)
ImGui::PopStyleColor();
ImGui::PopItemFlag();
ImGui::SameLine();
if (ImGui::Button(running ? "Pause" : "Run"))
running = !running;
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, running);
if (running)
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.7f, 0.7f, 0.7f, 0.7f });
if (ImGui::Button("Reset"))
{
bus->Reset();
if (overrideResetVector)
bus->cpu.pc.Raw = resetVector;
}
ImGui::SameLine();
if (ImGui::Button("Reboot"))
{
bus->Reboot();
if (overrideResetVector)
bus->cpu.pc.Raw = resetVector;
}
if (running)
ImGui::PopStyleColor();
ImGui::PopItemFlag();
ImGui::Separator();
ImGui::Text("Override Reset Vector: ");
ImGui::SameLine();
if (ImGui::Button(overrideResetVector ? "Yes" : "No"))
overrideResetVector = !overrideResetVector;
ImGui::InputScalar("Reset Vector", ImGuiDataType_U16, &resetVector, (const void*)0, (const void*)0, "%04X", ImGuiInputTextFlags_CharsHexadecimal);
}
for (DebugWindow* window : windows)
{
if (window->isOpen) window->OnRender();
}
ImGui::End();
}

27
src/debugger/Debugger.hpp Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include "DebugWindow.hpp"
class Bus;
class Debugger
{
public:
Debugger(Bus* bus);
~Debugger();
bool Update();
void Render();
public:
bool isOpen = true;
private:
Bus* bus;
bool running = false;
bool overrideResetVector = false;
uint16_t resetVector = 0x0000;
std::vector<DebugWindow*> windows;
};

View file

@ -0,0 +1,177 @@
#include "Disassembler.hpp"
#include <iomanip>
#include <imgui/imgui.h>
#include "../Mapper.hpp"
#include "../CPU.hpp"
#define FORMAT std::setfill('0') << std::setw(4) << std::hex << std::uppercase
Disassembler::Disassembler(CPU* cpu) :
DebugWindow("Disassembler"), cpu(cpu)
{
}
void Disassembler::OnRender()
{
static bool scrollCenter = true;
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(title.c_str(), &isOpen))
{
ImGui::End();
return;
}
std::string disassembly;
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.8f, 0.8f, 0.8f, 1.0f });
if (cpu->pastPCs.size() < 50)
{
for (int i = 0; i < 50 - cpu->pastPCs.size(); i++)
{
ImGui::Text("-");
}
}
for (Word pc : cpu->pastPCs)
{
Disassemble(disassembly, pc);
ImGui::Text("- %s", disassembly.c_str());
}
ImGui::PopStyleColor();
ImGui::Separator();
uint16_t pc = cpu->pc.Raw;
Disassemble(disassembly, pc);
ImGui::Text("> %s", disassembly.c_str());
if (scrollCenter)
{
ImGui::SetScrollHereY(0.5f);
scrollCenter = false;
}
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.8f, 0.8f, 0.8f, 1.0f });
for (int i = 0; i < 50; i++)
{
Disassemble(disassembly, pc);
ImGui::Text("+ %s", disassembly.c_str());
}
ImGui::PopStyleColor();
ImGui::End();
}
void Disassembler::Disassemble(std::string& target, uint16_t& pc)
{
std::stringstream ss;
ss << FORMAT << pc << ": ";
Instruction* currentInstr = &cpu->InstructionTable[cpu->Read(pc)];
for (int i = 0; i < currentInstr->Size; i++)
{
ss << FORMAT << std::setw(2) << (Word)cpu->Read(pc + i) << " ";
}
ss << std::string(15 - ss.str().size(), ' ') << currentInstr->Mnemonic << " ";
Address absoluteAddress;
switch (currentInstr->AddrType)
{
case Addressing::ACC:
ss << "A";
break;
case Addressing::ABS:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
absoluteAddress.Bytes.hi = cpu->Read(pc + 2);
ss << "$" << FORMAT << absoluteAddress.Raw;
} break;
case Addressing::ABX:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
absoluteAddress.Bytes.hi = cpu->Read(pc + 2);
ss << "$" << FORMAT << absoluteAddress.Raw << ",X";
} break;
case Addressing::ABY:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
absoluteAddress.Bytes.hi = cpu->Read(pc + 2);
ss << "$" << FORMAT << absoluteAddress.Raw << ",Y";
} break;
case Addressing::IMM:
{
Word value = cpu->Read(pc + 1);
ss << "#$" << FORMAT << std::setw(2) << value;
} break;
case Addressing::IMP:
break;
case Addressing::IND:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
absoluteAddress.Bytes.hi = cpu->Read(pc + 2);
ss << "($" << FORMAT << absoluteAddress.Raw << ")";
} break;
case Addressing::IDX:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
ss << "($" << FORMAT << std::setw(2) << (Word)absoluteAddress.Bytes.lo << ",X)";
} break;
case Addressing::IDY:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
ss << "($" << FORMAT << std::setw(2) << (Word)absoluteAddress.Bytes.lo << "),Y";
} break;
case Addressing::REL:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
ss << "$" << FORMAT << std::setw(2) << (Word)absoluteAddress.Bytes.lo;
} break;
case Addressing::ZPG:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
ss << "$" << FORMAT << std::setw(2) << (Word)absoluteAddress.Bytes.lo;
} break;
case Addressing::ZPX:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
ss << "$" << FORMAT << std::setw(2) << (Word)absoluteAddress.Bytes.lo << ",X";
} break;
case Addressing::ZPY:
{
absoluteAddress.Bytes.lo = cpu->Read(pc + 1);
ss << "$" << FORMAT << std::setw(2) << (Word)absoluteAddress.Bytes.lo << ",Y";
} break;
}
pc += currentInstr->Size;
target = ss.str();
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "DebugWindow.hpp"
class CPU;
class Disassembler :
public DebugWindow
{
public:
Disassembler(CPU* cpu);
virtual void OnRender() override;
private:
void Disassemble(std::string& target, uint16_t& pc);
private:
CPU* cpu;
};

View file

@ -0,0 +1,78 @@
#include "MemoryViewer.hpp"
#include <imgui/imgui.h>
#include "../Bus.hpp"
MemoryViewer::MemoryViewer(Bus* bus) :
DebugWindow("Memory Viewer"), bus(bus)
{
}
void MemoryViewer::OnRender()
{
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(title.c_str(), &isOpen))
{
ImGui::End();
return;
}
ImGui::BeginTabBar("Pages");
for (Byte page = 0; page < 8; page++)
{
char title[7];
std::sprintf(title, "Page %d", page);
if (ImGui::BeginTabItem(title))
{
DrawPage(page);
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
ImGui::End();
}
void MemoryViewer::DrawPage(Byte page)
{
Word baseAddr = ((Word)page << 8);
if (ImGui::BeginTable("memorymap", 17))
{
ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
for (Byte header = 0x0; header < 0x10; header++)
{
ImGui::TableNextColumn();
ImGui::Text("%X", header);
}
ImGui::PopStyleColor();
for (Byte hi = 0x0; hi <= 0xF; hi++)
{
Byte hiOffset = hi << 4;
ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
ImGui::Text("%04X", baseAddr | hiOffset);
ImGui::PopStyleColor();
for (Byte lo = 0x0; lo <= 0xF; lo++)
{
ImGui::TableNextColumn();
Byte entry = bus->RAM[baseAddr | hiOffset | lo];
if (entry == 0x00)
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
ImGui::Text("%02X", entry);
if (entry == 0x00)
ImGui::PopStyleColor();
}
}
ImGui::EndTable();
}
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "DebugWindow.hpp"
#include "../Types.hpp"
class Bus;
class MemoryViewer :
public DebugWindow
{
public:
MemoryViewer(Bus* bus);
virtual void OnRender() override;
private:
void DrawPage(Byte page);
private:
Bus* bus;
};

View file

@ -0,0 +1,78 @@
#include "NametableViewer.hpp"
#include <imgui/imgui.h>
#include "../Bus.hpp"
NametableViewer::NametableViewer(Bus* bus) :
DebugWindow("Nametable Viewer"), bus(bus)
{
}
void NametableViewer::OnRender()
{
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(title.c_str(), &isOpen))
{
ImGui::End();
return;
}
ImGui::BeginTabBar("Nametables");
for (uint8_t index = 0; index < 4; index++)
{
char baseAddress[6];
std::sprintf(baseAddress, "$2%X00", index * 4);
if (ImGui::BeginTabItem(baseAddress))
{
DisplayNametable(index);
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
ImGui::End();
}
void NametableViewer::DisplayNametable(uint8_t index)
{
Word baseAddr = 0x400 * index;
Word displayBaseAddr = 0x2000 + baseAddr;
if (ImGui::BeginTable("memorymap", 17))
{
ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
for (Byte header = 0x0; header < 0x10; header++)
{
ImGui::TableNextColumn();
ImGui::Text("%X", header);
}
ImGui::PopStyleColor();
for (Word hi = 0x0; hi <= 0x3F; hi++)
{
Byte hiOffset = hi << 4;
ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
ImGui::Text("%04X", displayBaseAddr | hiOffset);
ImGui::PopStyleColor();
for (Byte lo = 0x0; lo <= 0xF; lo++)
{
ImGui::TableNextColumn();
Byte entry = bus->VRAM[baseAddr | hiOffset | lo];
if (entry == 0x00)
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
ImGui::Text("%02X", entry);
if (entry == 0x00)
ImGui::PopStyleColor();
}
}
ImGui::EndTable();
}
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "DebugWindow.hpp"
class Bus;
class NametableViewer :
public DebugWindow
{
public:
NametableViewer(Bus* bus);
virtual void OnRender() override;
private:
void DisplayNametable(uint8_t index);
private:
Bus* bus;
};

152
src/debugger/PPUWatcher.cpp Normal file
View file

@ -0,0 +1,152 @@
#include "PPUWatcher.hpp"
#include <imgui/imgui.h>
#include "../PPU.hpp"
PPUWatcher::PPUWatcher(PPU* ppu) :
DebugWindow("PPU Watch"), ppu(ppu)
{
}
void PPUWatcher::OnRender()
{
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(title.c_str(), &isOpen))
{
ImGui::End();
return;
}
ImGui::Text("On Pixel (%d, %d)", ppu->x, ppu->y);
ImGui::Separator();
if (ImGui::CollapsingHeader("PPUCTRL"))
{
if (ImGui::BeginTable("ppuctrl", 2))
{
ImGui::TableNextColumn();
ImGui::Text("Base Nametable Addr");
ImGui::TableNextColumn();
ImGui::Text("$%04X", 0x2000 + 0x400 * ppu->ppuctrl.Flag.BaseNametableAddr);
ImGui::TableNextColumn();
ImGui::Text("VRAM Addr Increment");
ImGui::TableNextColumn();
ImGui::Text("%d", ppu->ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
ImGui::TableNextColumn();
ImGui::Text("Sprite Pattern Table Addr");
ImGui::TableNextColumn();
ImGui::Text("$%04X", ppu->ppuctrl.Flag.SpritePatternTableAddr ? 0x1000 : 0x0000);
ImGui::TableNextColumn();
ImGui::Text("Backgr Pattern Table Addr");
ImGui::TableNextColumn();
ImGui::Text("$%04X", ppu->ppuctrl.Flag.BackgrPatternTableAddr ? 0x1000 : 0x0000);
ImGui::TableNextColumn();
ImGui::Text("Master/Slave");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppuctrl.Flag.MasterSlaveSelect ? "Output to EXT" : "Read from EXT");
ImGui::TableNextColumn();
ImGui::Text("VBlank NMI Generation");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppuctrl.Flag.VBlankNMI ? "On" : "Off");
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("PPUMASK"))
{
if (ImGui::BeginTable("ppumask", 2))
{
ImGui::TableNextColumn();
ImGui::Text("Greyscale");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppumask.Flag.Greyscale ? "On" : "Off");
ImGui::TableNextColumn();
ImGui::Text("Left Col Background");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppumask.Flag.BackgroundOnLeft ? "Show" : "Hide");
ImGui::TableNextColumn();
ImGui::Text("Left Col Sprites");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppumask.Flag.SpriteOnLeft ? "Show" : "Hide");
ImGui::TableNextColumn();
ImGui::Text("Show Background");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppumask.Flag.ShowBackground ? "On" : "Off");
ImGui::TableNextColumn();
ImGui::Text("Show Sprites");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppumask.Flag.ShowSprites ? "On" : "Off");
ImGui::TableNextColumn();
ImGui::Text("Emphasized Colors");
ImGui::TableNextColumn();
ImGui::Text("%s%s%s", ppu->ppumask.Flag.EmphasizeRed ? "R" : "-", ppu->ppumask.Flag.EmphasizeGreen ? "G" : "-", ppu->ppumask.Flag.EmphasizeBlue ? "B" : "-");
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("PPUSTATUS"))
{
if (ImGui::BeginTable("ppustatus", 2))
{
ImGui::TableNextColumn();
ImGui::Text("Lower 5 Bits");
ImGui::TableNextColumn();
ImGui::Text("%02X", ppu->ppustatus.Flag.Unused);
ImGui::TableNextColumn();
ImGui::Text("Sprite Overflow");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppustatus.Flag.SpriteOverflow ? "Yes" : "No");
ImGui::TableNextColumn();
ImGui::Text("Sprite 0 Hit");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppustatus.Flag.SpriteZeroHit ? "Yes" : "No");
ImGui::TableNextColumn();
ImGui::Text("VBlank Started");
ImGui::TableNextColumn();
ImGui::Text(ppu->ppustatus.Flag.VBlankStarted ? "Yes" : "No");
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("PPUSCROLL & PPUADDR"))
{
if (ImGui::BeginTable("ppuscrolladdr", 2))
{
ImGui::TableNextColumn();
ImGui::Text("Scroll X");
ImGui::TableNextColumn();
ImGui::Text("%d", ppu->ppuscroll.x);
ImGui::TableNextColumn();
ImGui::Text("Scroll Y");
ImGui::TableNextColumn();
ImGui::Text("%d", ppu->ppuscroll.x);
ImGui::TableNextColumn();
ImGui::Text("Address");
ImGui::TableNextColumn();
ImGui::Text("$%04X", ppu->ppuaddr.Raw);
ImGui::EndTable();
}
}
ImGui::End();
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "DebugWindow.hpp"
class PPU;
class PPUWatcher :
public DebugWindow
{
public:
PPUWatcher(PPU* ppu);
virtual void OnRender() override;
private:
PPU* ppu;
};

52
src/gfx/Window.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "Window.hpp"
#include <stdexcept>
#include <imgui/backends/imgui_impl_glfw.h>
#include <imgui/backends/imgui_impl_opengl3.h>
#include <imgui/imgui.h>
Window::Window(uint16_t width, uint16_t height, const std::string& title) :
handle(nullptr)
{
handle = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
if (handle == nullptr)
{
const char* err;
int code = glfwGetError(&err);
throw std::runtime_error(std::string(err) + " (" + std::to_string(code) + ")");
}
glfwMakeContextCurrent(handle);
}
Window::~Window()
{
if(handle)
glfwDestroyWindow(handle);
}
void Window::Begin()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
void Window::End()
{
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
GLFWwindow* backup_current_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
}
glfwSwapBuffers(handle);
}

23
src/gfx/Window.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
#include <string>
#include <glad/glad.h>
#include <glfw/glfw3.h>
class Window
{
public:
Window(uint16_t width, uint16_t height, const std::string& title);
~Window();
inline bool ShouldClose() { return glfwWindowShouldClose(handle); }
inline GLFWwindow* GetNativeWindow() { return handle; }
void Begin();
void End();
private:
GLFWwindow* handle;
};

10
src/main.cpp Normal file
View file

@ -0,0 +1,10 @@
#include "Application.hpp"
#include "Log.hpp"
int main(int argc, char** argv)
{
Log::Init();
Application::Launch();
return 0;
}

40
src/mappers/Mapper000.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "Mapper000.hpp"
#include "../Log.hpp"
#include <fstream>
#include "../Cartridge.hpp"
Mapper000::Mapper000(Header& header, std::ifstream& ifs)
{
LOG_CORE_INFO("Allocating PRG ROM");
PRG_ROM = std::vector<Byte>(0x4000);
ifs.read((char*)PRG_ROM.data(), 0x4000);
LOG_CORE_INFO("Allocating CHR ROM");
CHR_ROM = std::vector<Byte>(0x2000);
ifs.read((char*)CHR_ROM.data(), 0x2000);
}
Byte Mapper000::ReadCPU(Word addr)
{
if (0x8000 <= addr && addr <= 0xFFFF)
{
return PRG_ROM[addr & 0x03FFF];
}
}
Byte Mapper000::ReadPPU(Word addr)
{
if (0x0000 <= addr && addr <= 0x1FFF)
{
return CHR_ROM[addr];
}
}
void Mapper000::WriteCPU(Word addr, Byte val)
{
}
void Mapper000::WritePPU(Word addr, Byte val)
{
}

19
src/mappers/Mapper000.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include <fstream>
#include "../Mapper.hpp"
struct Header;
class Mapper000 :
public Mapper
{
public:
Mapper000(Header& header, std::ifstream& ifs);
virtual Byte ReadCPU(Word addr) override;
virtual Byte ReadPPU(Word addr) override;
virtual void WriteCPU(Word addr, Byte val) override;
virtual void WritePPU(Word addr, Byte val) override;
};