bootrom mostly works, refactoring and all opcodes added
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,4 +3,4 @@ build/
|
|||||||
.idea
|
.idea
|
||||||
|
|
||||||
roms/
|
roms/
|
||||||
bootrom.bin
|
dmg_boot.bin
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ set(CMAKE_CXX_STANDARD 23)
|
|||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
include_directories(${SDL2_INCLUDE_DIRS})
|
include_directories(${SDL2_INCLUDE_DIRS})
|
||||||
|
|
||||||
add_executable(GBpp src/main.cpp src/gameboy.cpp src/opcode.cpp
|
add_executable(GBpp src/main.cpp
|
||||||
src/interupts.cpp src/ppu.cpp)
|
src/gameboy.cpp
|
||||||
|
src/opcodeResolver.cpp
|
||||||
|
src/interupts.cpp
|
||||||
|
src/ppu.cpp
|
||||||
|
src/timing.cpp
|
||||||
|
src/extendedOpcodeResolver.cpp
|
||||||
|
)
|
||||||
target_link_libraries(GBpp ${SDL2_LIBRARIES})
|
target_link_libraries(GBpp ${SDL2_LIBRARIES})
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
#define T_CLOCK_FREQ 4194304 //2^22
|
#define T_CLOCK_FREQ 4194304 //2^22
|
||||||
|
|
||||||
#define DIVIDER_REGISTER_FREQ 16384
|
#define DIVIDER_REGISTER_FREQ (4194304/16384)
|
||||||
|
|
||||||
#define BOOTROM_SIZE 0x100
|
#define BOOTROM_SIZE 0x100
|
||||||
|
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
#define FRAME_DURATION 70224
|
#define FRAME_DURATION 70224
|
||||||
#define MODE2_DURATION 80
|
#define MODE2_DURATION 80
|
||||||
#define MODE3_MIN_DURATION 172
|
#define MODE3_MIN_DURATION 172
|
||||||
#define MODE0_3_DURATION 376 //mode3 is 172 to 289, mode0 87 to 204
|
|
||||||
|
|
||||||
#define H_SYNC 9198
|
#define H_SYNC 9198
|
||||||
#define V_SYNC 59.73
|
#define V_SYNC 59.73
|
||||||
|
|||||||
1547
src/extendedOpcodeResolver.cpp
Normal file
1547
src/extendedOpcodeResolver.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
bool AddressSpace::getBootromState() {
|
bool AddressSpace::getBootromState() const {
|
||||||
return bootromLoaded;
|
return bootromLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,10 +13,9 @@ void AddressSpace::mapBootrom() {
|
|||||||
bootromLoaded = true;
|
bootromLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::loadBootrom(std::string filename) {
|
void AddressSpace::loadBootrom(const std::string& filename) {
|
||||||
std::ifstream file;
|
std::ifstream file;
|
||||||
int size = std::filesystem::file_size(filename);
|
if (const uintmax_t size = std::filesystem::file_size(filename); size != 256) {
|
||||||
if (size != 256) {
|
|
||||||
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
|
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -24,7 +23,7 @@ void AddressSpace::loadBootrom(std::string filename) {
|
|||||||
file.read(reinterpret_cast<char*>(bootrom), BOOTROM_SIZE);
|
file.read(reinterpret_cast<char*>(bootrom), BOOTROM_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::loadGame(std::string filename) {
|
void AddressSpace::loadGame(const std::string& filename) {
|
||||||
game.open(filename, std::ios::binary);
|
game.open(filename, std::ios::binary);
|
||||||
|
|
||||||
if (!game.is_open()) {
|
if (!game.is_open()) {
|
||||||
@@ -34,8 +33,11 @@ void AddressSpace::loadGame(std::string filename) {
|
|||||||
game.read(reinterpret_cast<char*>(memoryLayout.romBank1), ROM_BANK_SIZE * 2);
|
game.read(reinterpret_cast<char*>(memoryLayout.romBank1), ROM_BANK_SIZE * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::addCycles(uint8_t ticks) {
|
void GameBoy::addCycles(const uint8_t ticks) {
|
||||||
cycles = (cycles + ticks) % T_CLOCK_FREQ;
|
cycles += ticks;
|
||||||
|
if (ppuEnabled) {
|
||||||
|
ppuCycles += ticks;
|
||||||
|
}
|
||||||
lastOpTicks = ticks;
|
lastOpTicks = ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,28 +45,52 @@ void GameBoy::start(std::string bootrom, std::string game) {
|
|||||||
addressSpace.loadBootrom(bootrom);
|
addressSpace.loadBootrom(bootrom);
|
||||||
addressSpace.loadGame(game);
|
addressSpace.loadGame(game);
|
||||||
|
|
||||||
|
//init some registers that won't otherwise by set
|
||||||
|
(*JOYP) = 0xCF;
|
||||||
|
(*SC) = 0x7E;
|
||||||
|
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
uint32_t cyclesSince = 0;
|
|
||||||
|
bool display = false;
|
||||||
|
|
||||||
while (!quit) {
|
while (!quit) {
|
||||||
// Event loop: Check and handle SDL events
|
// Event loop: Check and handle SDL events
|
||||||
if (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
if (event.type == SDL_QUIT) {
|
if (event.type == SDL_QUIT) {
|
||||||
quit = true; // Set the quit flag when the close button is hit
|
quit = true; // Set the quit flag when the close button is hit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opcodeHandler();
|
|
||||||
interruptHandler();
|
|
||||||
//timing();
|
|
||||||
ppuUpdate();
|
|
||||||
if (PC > 0xFF && addressSpace.getBootromState()) {
|
if (PC > 0xFF && addressSpace.getBootromState()) {
|
||||||
addressSpace.unmapBootrom();
|
addressSpace.unmapBootrom();
|
||||||
}
|
}
|
||||||
cyclesSince = cyclesSinceLastRefresh();
|
ppuEnabled = (*LCDC) & 0x80;
|
||||||
if (cyclesSince > FRAME_DURATION) {
|
|
||||||
lastRefresh = cycles;
|
// if (PC == 0x100)
|
||||||
SDL2present();
|
// display = true;
|
||||||
|
// if (display) {
|
||||||
|
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, addressSpace[PC],
|
||||||
|
// cyclesSinceLastScanline(), currentMode);
|
||||||
|
// printf("PC:0x%.2x, SP:0x%.2x\n", PC, SP);
|
||||||
|
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
||||||
|
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
|
||||||
|
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
|
||||||
|
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
|
||||||
|
// printf("\n");
|
||||||
|
// }
|
||||||
|
|
||||||
|
opcodeResolver();
|
||||||
|
interruptHandler();
|
||||||
|
timingHandler();
|
||||||
|
if (ppuEnabled) {
|
||||||
|
ppuUpdate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ppuCycles = 2;
|
||||||
|
lastScanline = 0;
|
||||||
|
lastRefresh = 0;
|
||||||
|
(*LY) = 0x00;
|
||||||
|
(*STAT) &= 0xfc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
200
src/gameboy.hpp
200
src/gameboy.hpp
@@ -35,9 +35,8 @@ union RegisterPair {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class AddressSpace {
|
class AddressSpace {
|
||||||
private:
|
|
||||||
bool bootromLoaded = true;
|
bool bootromLoaded = true;
|
||||||
Byte bootrom[BOOTROM_SIZE];
|
Byte bootrom[BOOTROM_SIZE] = {0};
|
||||||
std::ifstream game;
|
std::ifstream game;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -65,50 +64,54 @@ public:
|
|||||||
Byte specialRam[0x7F]; // Mapped to 0xFF80
|
Byte specialRam[0x7F]; // Mapped to 0xFF80
|
||||||
Byte interuptEnableReg; // Mapped to 0xFFFF
|
Byte interuptEnableReg; // Mapped to 0xFFFF
|
||||||
};
|
};
|
||||||
} memoryLayout;
|
} memoryLayout{};
|
||||||
|
|
||||||
bool getBootromState();
|
|
||||||
void unmapBootrom();
|
void unmapBootrom();
|
||||||
void mapBootrom();
|
void mapBootrom();
|
||||||
void loadBootrom(std::string filename);
|
bool getBootromState() const;
|
||||||
void loadGame(std::string filename);
|
void loadBootrom(const std::string& filename);
|
||||||
|
void loadGame(const std::string& filename);
|
||||||
|
|
||||||
//overload [] for echo ram and bootrom support
|
//overload [] for echo ram and bootrom support
|
||||||
Byte operator[](uint32_t address) const {
|
Byte operator[](const uint32_t address) const {
|
||||||
if (address >= 0xE000 && address < 0xFE00)
|
if (address >= 0xE000 && address < 0xFE00)
|
||||||
return memoryLayout.echoRam[address - 0xE000];
|
return memoryLayout.echoRam[address - 0x2000];
|
||||||
if (address < 0x0100 && bootromLoaded)
|
if (address < 0x0100 && bootromLoaded)
|
||||||
return bootrom[address];
|
return bootrom[address];
|
||||||
else
|
|
||||||
return memoryLayout.memory[address];
|
return memoryLayout.memory[address];
|
||||||
}
|
}
|
||||||
|
|
||||||
Byte& operator[](uint32_t address) {
|
Byte& operator[](const uint32_t address) {
|
||||||
if (address >= 0xE000 && address < 0xFE00)
|
if (address >= 0xE000 && address < 0xFE00)
|
||||||
return memoryLayout.echoRam[address - 0xE000];
|
return memoryLayout.echoRam[address - 0x2000];
|
||||||
if (address < 0x0100 && bootromLoaded)
|
if (address < 0x0100 && bootromLoaded)
|
||||||
return bootrom[address];
|
return bootrom[address];
|
||||||
else
|
|
||||||
return memoryLayout.memory[address];
|
return memoryLayout.memory[address];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameBoy {
|
class GameBoy {
|
||||||
private:
|
//T-cycles not M-cycles (4 T-cycles = 1 M-cycle)
|
||||||
uint32_t cycles = 0;
|
uint64_t cycles = 0;
|
||||||
uint32_t lastOpTicks = 0;
|
//Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
|
||||||
uint32_t lastRefresh = 0;
|
uint64_t ppuCycles = 2;
|
||||||
uint32_t lastScanline = 0;
|
bool ppuEnabled = false;
|
||||||
uint32_t cyclesToStayInHblank = -1;
|
uint64_t lastOpTicks = 0;
|
||||||
|
uint64_t lastRefresh = 0;
|
||||||
|
uint64_t lastScanline = 0;
|
||||||
|
uint64_t cyclesToStayInHblank = -1;
|
||||||
|
uint64_t lastDivUpdate = 0;
|
||||||
|
|
||||||
uint8_t IME = 0; //enables interupts
|
uint8_t IME = 0; //enables interupts
|
||||||
|
|
||||||
//Accumulator and flags
|
//Accumulator and flags
|
||||||
RegisterPair AF;
|
RegisterPair AF = {0};
|
||||||
//General purpose CPU registers
|
//General purpose CPU registers
|
||||||
RegisterPair BC;
|
RegisterPair BC = {0};
|
||||||
RegisterPair DE;
|
RegisterPair DE = {0};
|
||||||
RegisterPair HL;
|
RegisterPair HL = {0};
|
||||||
|
|
||||||
Word SP = 0xFFFE; //stack pointer
|
Word SP = 0xFFFE; //stack pointer
|
||||||
Word PC = 0x0000; //program counter
|
Word PC = 0x0000; //program counter
|
||||||
@@ -116,70 +119,74 @@ private:
|
|||||||
AddressSpace addressSpace;
|
AddressSpace addressSpace;
|
||||||
|
|
||||||
//General purpose hardware registers
|
//General purpose hardware registers
|
||||||
Byte* JOYP = &addressSpace[0xFF00];
|
Byte* const JOYP = &addressSpace[0xFF00];
|
||||||
Byte* SB = &addressSpace[0xFF01];
|
|
||||||
Byte* SC = &addressSpace[0xFF02];
|
Byte* const SB = &addressSpace[0xFF01];
|
||||||
Byte* DIV = &addressSpace[0xFF04];
|
Byte* const SC = &addressSpace[0xFF02];
|
||||||
|
Byte* const DIV = &addressSpace[0xFF04];
|
||||||
|
|
||||||
//Timer registers
|
//Timer registers
|
||||||
Byte* TIMA = &addressSpace[0xFF05];
|
Byte* const TIMA = &addressSpace[0xFF05];
|
||||||
Byte* TMA = &addressSpace[0xFF15]; //unused
|
Byte* const TMA = &addressSpace[0xFF15]; //unused
|
||||||
Byte* TAC = &addressSpace[0xFF16];
|
Byte* const TAC = &addressSpace[0xFF16];
|
||||||
|
|
||||||
//interrupt flag and enable
|
//interrupt flag and enable
|
||||||
Byte* IF = &addressSpace[0xFF0F];
|
Byte* const IF = &addressSpace[0xFF0F];
|
||||||
Byte* IE = &addressSpace[0xFFFF];
|
Byte* const IE = &addressSpace[0xFFFF];
|
||||||
|
|
||||||
//Sound registers
|
//Sound registers
|
||||||
Byte* NR10 = &addressSpace[0xFF10];
|
Byte* const NR10 = &addressSpace[0xFF10];
|
||||||
Byte* NR11 = &addressSpace[0xFF11];
|
Byte* const NR11 = &addressSpace[0xFF11];
|
||||||
Byte* NR12 = &addressSpace[0xFF12];
|
Byte* const NR12 = &addressSpace[0xFF12];
|
||||||
Byte* NR13 = &addressSpace[0xFF13];
|
Byte* const NR13 = &addressSpace[0xFF13];
|
||||||
Byte* NR14 = &addressSpace[0xFF14];
|
Byte* const NR14 = &addressSpace[0xFF14];
|
||||||
Byte* NR20 = &addressSpace[0xFF15]; //unused
|
Byte* const NR20 = &addressSpace[0xFF15]; //unused
|
||||||
Byte* NR21 = &addressSpace[0xFF16];
|
Byte* const NR21 = &addressSpace[0xFF16];
|
||||||
Byte* NR22 = &addressSpace[0xFF17];
|
Byte* const NR22 = &addressSpace[0xFF17];
|
||||||
Byte* NR23 = &addressSpace[0xFF18];
|
Byte* const NR23 = &addressSpace[0xFF18];
|
||||||
Byte* NR24 = &addressSpace[0xFF19];
|
Byte* const NR24 = &addressSpace[0xFF19];
|
||||||
Byte* NR30 = &addressSpace[0xFF1A];
|
Byte* const NR30 = &addressSpace[0xFF1A];
|
||||||
Byte* NR31 = &addressSpace[0xFF1B];
|
Byte* const NR31 = &addressSpace[0xFF1B];
|
||||||
Byte* NR32 = &addressSpace[0xFF1C];
|
Byte* const NR32 = &addressSpace[0xFF1C];
|
||||||
Byte* NR33 = &addressSpace[0xFF1D];
|
Byte* const NR33 = &addressSpace[0xFF1D];
|
||||||
Byte* NR34 = &addressSpace[0xFF1E];
|
Byte* const NR34 = &addressSpace[0xFF1E];
|
||||||
Byte* NR40 = &addressSpace[0xFF1F]; //unused
|
Byte* const NR40 = &addressSpace[0xFF1F]; //unused
|
||||||
Byte* NR41 = &addressSpace[0xFF20];
|
Byte* const NR41 = &addressSpace[0xFF20];
|
||||||
Byte* NR42 = &addressSpace[0xFF21];
|
Byte* const NR42 = &addressSpace[0xFF21];
|
||||||
Byte* NR43 = &addressSpace[0xFF22];
|
Byte* const NR43 = &addressSpace[0xFF22];
|
||||||
Byte* NR44 = &addressSpace[0xFF23];
|
Byte* const NR44 = &addressSpace[0xFF23];
|
||||||
Byte* NR50 = &addressSpace[0xFF24];
|
Byte* const NR50 = &addressSpace[0xFF24];
|
||||||
Byte* NR51 = &addressSpace[0xFF25];
|
Byte* const NR51 = &addressSpace[0xFF25];
|
||||||
Byte* NR52 = &addressSpace[0xFF26];
|
Byte* const NR52 = &addressSpace[0xFF26];
|
||||||
Byte* waveRam = &addressSpace[0xFF30]; //WaveRam[0x10]
|
Byte* const waveRam = &addressSpace[0xFF30]; //WaveRam[0x10]
|
||||||
|
|
||||||
//PPU registers
|
//PPU registers
|
||||||
Byte* LCDC = &addressSpace[0xFF40];
|
Byte* const LCDC = &addressSpace[0xFF40];
|
||||||
Byte* STAT = &addressSpace[0xFF41];
|
Byte* const STAT = &addressSpace[0xFF41];
|
||||||
Byte* SCY = &addressSpace[0xFF42];
|
Byte* const SCY = &addressSpace[0xFF42];
|
||||||
Byte* SCX = &addressSpace[0xFF43];
|
Byte* const SCX = &addressSpace[0xFF43];
|
||||||
Byte* LY = &addressSpace[0xFF44];
|
Byte* const LY = &addressSpace[0xFF44];
|
||||||
Byte* LYC = &addressSpace[0xFF45];
|
Byte* const LYC = &addressSpace[0xFF45];
|
||||||
Byte* DMA = &addressSpace[0xFF46];
|
Byte* const DMA = &addressSpace[0xFF46];
|
||||||
Byte* BGP = &addressSpace[0xFF47];
|
Byte* const BGP = &addressSpace[0xFF47];
|
||||||
Byte* OBP0 = &addressSpace[0xFF48];
|
Byte* const OBP0 = &addressSpace[0xFF48];
|
||||||
Byte* OBP1 = &addressSpace[0xFF49];
|
Byte* const OBP1 = &addressSpace[0xFF49];
|
||||||
Byte* WY = &addressSpace[0xFF4A];
|
Byte* const WY = &addressSpace[0xFF4A];
|
||||||
Byte* WX = &addressSpace[0xFF4B];
|
Byte* const WX = &addressSpace[0xFF4B];
|
||||||
|
|
||||||
PPUMode currentMode;
|
PPUMode currentMode = PPUMode::mode0;
|
||||||
|
|
||||||
//3 colour channels
|
//3 colour channels
|
||||||
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
||||||
SDL_Window* screen;
|
SDL_Window* screen = nullptr;
|
||||||
SDL_Renderer* renderer;
|
SDL_Renderer* renderer = nullptr;
|
||||||
SDL_Texture* texture;
|
SDL_Texture* texture = nullptr;
|
||||||
SDL_Event event;
|
SDL_Event event = {0};
|
||||||
|
uint32_t frameStart = 0;
|
||||||
|
uint32_t frameTime = 0;
|
||||||
|
const int frameDelay = 1000 / V_SYNC;
|
||||||
|
|
||||||
void opcodeHandler();
|
void opcodeResolver();
|
||||||
void incLY();
|
void incLY();
|
||||||
void ppuUpdate();
|
void ppuUpdate();
|
||||||
void drawLine();
|
void drawLine();
|
||||||
@@ -187,12 +194,14 @@ private:
|
|||||||
|
|
||||||
void checkPPUMode();
|
void checkPPUMode();
|
||||||
void setPPUMode(PPUMode mode);
|
void setPPUMode(PPUMode mode);
|
||||||
uint32_t cyclesSinceLastScanline();
|
uint64_t cyclesSinceLastScanline() const;
|
||||||
uint32_t cyclesSinceLastRefresh();
|
uint64_t cyclesSinceLastRefresh() const;
|
||||||
|
|
||||||
|
void timingHandler();
|
||||||
|
|
||||||
void interruptHandler();
|
void interruptHandler();
|
||||||
bool testInterruptEnabled(Byte interrupt);
|
bool testInterruptEnabled(Byte interrupt) const;
|
||||||
void resetInterrupt(Byte interrupt);
|
void resetInterrupt(Byte interrupt) const;
|
||||||
|
|
||||||
void VBlankHandle();
|
void VBlankHandle();
|
||||||
void LCDStatHandle();
|
void LCDStatHandle();
|
||||||
@@ -220,17 +229,24 @@ private:
|
|||||||
void andBitwise(T& dest, T src);
|
void andBitwise(T& dest, T src);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void xorBitwise(T& dest, T src);
|
void xorBitwise(T& dest, T src);
|
||||||
template <typename T>
|
void bit(Byte testBit, Byte reg);
|
||||||
void bit(T testBit, T reg);
|
void extendedOpcodeResolver();
|
||||||
|
static void set(const uint8_t testBit, uint8_t& reg);
|
||||||
|
static void res(const uint8_t testBit, uint8_t& reg);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void jp(T address);
|
void jp(T address);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool jrNZ(T offset);
|
bool jrNZ(T offset);
|
||||||
|
template <class T>
|
||||||
|
bool jrNC(T offset);
|
||||||
|
template <class T>
|
||||||
|
bool jrC(T offset);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void inc(T& reg);
|
void inc(T& reg);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void call(T address);
|
void call(T address);
|
||||||
void halt();
|
void halt();
|
||||||
|
void daa();
|
||||||
void stop();
|
void stop();
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void ldW(T dest, T src);
|
void ldW(T dest, T src);
|
||||||
@@ -242,29 +258,41 @@ private:
|
|||||||
bool jrZ(T offset);
|
bool jrZ(T offset);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void sub(T value);
|
void sub(T value);
|
||||||
|
template <class T>
|
||||||
|
void sbc(T value);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void jr(T OFFSET);
|
void jr(T OFFSET);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void push(T reg);
|
void push(T reg);
|
||||||
template <typename T>
|
void rl(Byte& reg);
|
||||||
void rl(T& reg);
|
void sla(Byte& reg);
|
||||||
|
void sra(uint8_t& reg);
|
||||||
|
void srl(uint8_t& reg);
|
||||||
|
void rrc(Byte& reg);
|
||||||
|
void rrca();
|
||||||
|
void rra();
|
||||||
|
void rr(Byte& reg);
|
||||||
|
void rlc(Byte& reg);
|
||||||
|
void rlca();
|
||||||
|
void rla();
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void pop(T& reg);
|
void pop(T& reg);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void rla(T& reg);
|
|
||||||
template <typename T>
|
|
||||||
void rst(T address);
|
void rst(T address);
|
||||||
void ret();
|
void ret();
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void add(T& reg, T value);
|
void add(T& reg, T value);
|
||||||
|
template <class T>
|
||||||
|
void adc(T& reg, T value);
|
||||||
void cpl();
|
void cpl();
|
||||||
|
void scf();
|
||||||
void ccf();
|
void ccf();
|
||||||
void swap(Byte& value);
|
void swap(Byte& value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void start(std::string bootrom, std::string game);
|
void start(std::string bootrom, std::string game);
|
||||||
void SDL2setup();
|
void SDL2setup();
|
||||||
void SDL2destroy();
|
void SDL2destroy() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //GBPP_SRC_GAMEBOY_HPP_
|
#endif //GBPP_SRC_GAMEBOY_HPP_
|
||||||
|
|||||||
@@ -1,32 +1,33 @@
|
|||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
bool GameBoy::testInterruptEnabled(Byte interrupt) {
|
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
||||||
return (*IE) & (Byte)(1 << interrupt);
|
return (*IE) & static_cast<Byte>(1 << interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::resetInterrupt(Byte interrupt) {
|
void GameBoy::resetInterrupt(const Byte interrupt) const {
|
||||||
*IF &= ~(1 << interrupt);
|
*IF &= ~(1 << interrupt);
|
||||||
|
*IF |= 0xE0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::interruptHandler() {
|
void GameBoy::interruptHandler() {
|
||||||
if (!IME)
|
if (!IME)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (*IF & (Byte)(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT))
|
if (*IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT))
|
||||||
VBlankHandle();
|
VBlankHandle();
|
||||||
if (*IF & (Byte)(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT))
|
if (*IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT))
|
||||||
LCDStatHandle();
|
LCDStatHandle();
|
||||||
if (*IF & (Byte)(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT))
|
if (*IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT))
|
||||||
timerHandle();
|
timerHandle();
|
||||||
if (*IF & (Byte)(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT))
|
if (*IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT))
|
||||||
serialHandle();
|
serialHandle();
|
||||||
if (*IF & (Byte)(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT))
|
if (*IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT))
|
||||||
joypadHandle();
|
joypadHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::VBlankHandle() {
|
void GameBoy::VBlankHandle() {
|
||||||
printf("VBlank interrupt");
|
//printf("VBlank interrupt\n");
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
PC = 0x40;
|
PC = 0x40;
|
||||||
@@ -34,7 +35,7 @@ void GameBoy::VBlankHandle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::LCDStatHandle() {
|
void GameBoy::LCDStatHandle() {
|
||||||
printf("LCD stat interrupt");
|
//printf("LCD stat interrupt\n");
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
@@ -43,7 +44,7 @@ void GameBoy::LCDStatHandle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::timerHandle() {
|
void GameBoy::timerHandle() {
|
||||||
printf("timer interrupt");
|
//printf("timer interrupt\n");
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
@@ -52,7 +53,7 @@ void GameBoy::timerHandle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::serialHandle() {
|
void GameBoy::serialHandle() {
|
||||||
printf("serial interrupt");
|
//printf("serial interrupt\n");
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
@@ -61,7 +62,7 @@ void GameBoy::serialHandle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::joypadHandle() {
|
void GameBoy::joypadHandle() {
|
||||||
printf("joypad interrupt");
|
printf("joypad interrupt\n");
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
auto* gb = new GameBoy();
|
auto* gb = new GameBoy();
|
||||||
gb->SDL2setup();
|
gb->SDL2setup();
|
||||||
gb->start("../bootrom.bin", "../roms/DrMario.gb");
|
gb->start("../dmg_boot.bin", "../roms/cpu_instrs.gb");
|
||||||
gb->SDL2destroy();
|
gb->SDL2destroy();
|
||||||
delete gb;
|
delete gb;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
2271
src/opcodeResolver.cpp
Normal file
2271
src/opcodeResolver.cpp
Normal file
File diff suppressed because it is too large
Load Diff
170
src/ppu.cpp
170
src/ppu.cpp
@@ -2,52 +2,12 @@
|
|||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
void GameBoy::ppuUpdate() {
|
void GameBoy::ppuUpdate() {
|
||||||
//test for HBlank
|
//test for HBlank
|
||||||
checkPPUMode();
|
checkPPUMode();
|
||||||
|
|
||||||
if (cyclesToStayInHblank != -1) {
|
|
||||||
if (cyclesToStayInHblank < cyclesSinceLastScanline()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (cyclesToStayInHblank >= cyclesSinceLastScanline()) {
|
|
||||||
lastScanline = cycles;
|
|
||||||
cyclesToStayInHblank = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
|
|
||||||
Byte mode = (*STAT) & 0x03;
|
|
||||||
switch (mode) {
|
|
||||||
case 0:
|
|
||||||
if (cyclesSinceLastScanline() > MODE2_DURATION + MODE3_MIN_DURATION) {
|
|
||||||
drawLine();
|
|
||||||
cyclesToStayInHblank = SCANLINE_DURATION - cyclesSinceLastScanline();
|
|
||||||
lastScanline = cycles;
|
|
||||||
incLY();
|
|
||||||
}
|
|
||||||
currentMode = PPUMode::mode0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
//vblank
|
|
||||||
case 1:
|
|
||||||
if (currentMode != PPUMode::mode1) {
|
|
||||||
setPPUMode(PPUMode::mode1);
|
|
||||||
*IF |= 0x1;
|
|
||||||
}
|
|
||||||
if (cyclesSinceLastScanline() > SCANLINE_DURATION) {
|
|
||||||
lastScanline = cycles;
|
|
||||||
incLY();
|
|
||||||
}
|
|
||||||
currentMode = PPUMode::mode1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
currentMode = PPUMode::mode2;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
currentMode = PPUMode::mode3;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((*LY) == (*LYC) || (*STAT) & (1 << 6)) {
|
if ((*LY) == (*LYC) || (*STAT) & (1 << 6)) {
|
||||||
// Request STAT interrupt if LY matches LYC
|
// Request STAT interrupt if LY matches LYC
|
||||||
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
||||||
@@ -70,56 +30,77 @@ void GameBoy::ppuUpdate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameBoy::checkPPUMode() {
|
||||||
|
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
|
||||||
|
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
|
||||||
|
|
||||||
|
switch (currentMode) {
|
||||||
|
//hblank and vblank
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
if (cyclesSinceScanline > SCANLINE_DURATION) {
|
||||||
|
lastScanline = ppuCycles - (cyclesSinceScanline - SCANLINE_DURATION);
|
||||||
|
incLY();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (cyclesSinceScanline > MODE2_DURATION) {
|
||||||
|
setPPUMode(PPUMode::mode3);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (cyclesSinceScanline > MODE2_DURATION + MODE3_MIN_DURATION) {
|
||||||
|
drawLine();
|
||||||
|
setPPUMode(PPUMode::mode0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameBoy::incLY() {
|
void GameBoy::incLY() {
|
||||||
(*LY)++;
|
(*LY)++;
|
||||||
if ((*LY) > 153)
|
setPPUMode(PPUMode::mode2);
|
||||||
|
if ((*LY) > SCANLINES_PER_FRAME - 1) {
|
||||||
(*LY) = 0;
|
(*LY) = 0;
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GameBoy::cyclesSinceLastScanline() {
|
|
||||||
const uint32_t difference = cycles - lastScanline;
|
|
||||||
return difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GameBoy::cyclesSinceLastRefresh() {
|
|
||||||
const uint32_t difference = cycles - lastRefresh;
|
|
||||||
return difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoy::checkPPUMode() {
|
|
||||||
uint32_t oamFetchTime = 0;
|
|
||||||
if ((*LY) < 144) {
|
|
||||||
const uint32_t currentDuration = cyclesSinceLastScanline();
|
|
||||||
// Active Display Period (HBlank, OAM Search, and Pixel Transfer)
|
|
||||||
if (currentDuration < MODE2_DURATION)
|
|
||||||
setPPUMode(PPUMode::mode2);
|
|
||||||
else if (currentDuration < MODE2_DURATION + MODE3_MIN_DURATION + oamFetchTime)
|
|
||||||
setPPUMode(PPUMode::mode3);
|
|
||||||
else
|
|
||||||
setPPUMode(PPUMode::mode0);
|
|
||||||
}
|
}
|
||||||
else {
|
else if ((*LY) == 144) {
|
||||||
// VBlank Period
|
// VBlank Period
|
||||||
|
SDL2present();
|
||||||
setPPUMode(PPUMode::mode1);
|
setPPUMode(PPUMode::mode1);
|
||||||
|
*IF |= 0x1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::setPPUMode(PPUMode mode) {
|
uint64_t GameBoy::cyclesSinceLastScanline() const {
|
||||||
|
const uint64_t difference = ppuCycles - lastScanline;
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GameBoy::cyclesSinceLastRefresh() const {
|
||||||
|
const uint64_t difference = ppuCycles - lastRefresh;
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::setPPUMode(const PPUMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case PPUMode::mode0:
|
case PPUMode::mode0:
|
||||||
(*STAT) &= ~0x03;
|
(*STAT) &= ~0x03;
|
||||||
|
currentMode = PPUMode::mode0;
|
||||||
break;
|
break;
|
||||||
case PPUMode::mode1:
|
case PPUMode::mode1:
|
||||||
(*STAT) &= ~0x03;
|
(*STAT) &= ~0x03;
|
||||||
(*STAT) |= 0x01;
|
(*STAT) |= 0x01;
|
||||||
|
currentMode = PPUMode::mode1;
|
||||||
break;
|
break;
|
||||||
case PPUMode::mode2:
|
case PPUMode::mode2:
|
||||||
(*STAT) &= ~0x03;
|
(*STAT) &= ~0x03;
|
||||||
(*STAT) |= 0x02;
|
(*STAT) |= 0x02;
|
||||||
|
currentMode = PPUMode::mode2;
|
||||||
break;
|
break;
|
||||||
case PPUMode::mode3:
|
case PPUMode::mode3:
|
||||||
(*STAT) &= ~0x03;
|
(*STAT) &= ~0x03;
|
||||||
(*STAT) |= 0x03;
|
(*STAT) |= 0x03;
|
||||||
|
currentMode = PPUMode::mode3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//7th bit is unused but always set
|
//7th bit is unused but always set
|
||||||
@@ -127,10 +108,10 @@ void GameBoy::setPPUMode(PPUMode mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::drawLine() {
|
void GameBoy::drawLine() {
|
||||||
uint8_t line = (*LY);
|
const uint8_t line = (*LY);
|
||||||
|
|
||||||
// Calculate the starting index of the current scanline in the framebuffer
|
// Calculate the starting index of the current scanline in the framebuffer
|
||||||
uint32_t lineStartIndex = line * RESOLUTION_X;
|
const uint32_t lineStartIndex = line * RESOLUTION_X;
|
||||||
|
|
||||||
// Pointer to the current line's pixel data in the framebuffer
|
// Pointer to the current line's pixel data in the framebuffer
|
||||||
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
|
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
|
||||||
@@ -140,20 +121,20 @@ void GameBoy::drawLine() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800;
|
const uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800;
|
||||||
uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800;
|
const uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800;
|
||||||
bool signedIndex = !(*LCDC & 0x10);
|
const bool signedIndex = !(*LCDC & 0x10);
|
||||||
|
|
||||||
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
||||||
uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width
|
const uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width
|
||||||
uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height
|
const uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height
|
||||||
|
|
||||||
uint16_t tileRow = (yPos / 8) * 32;
|
const uint16_t tileRow = (yPos / 8) * 32;
|
||||||
uint16_t tileCol = xPos / 8;
|
const uint16_t tileCol = xPos / 8;
|
||||||
uint16_t tileIndex = tileRow + tileCol;
|
const uint16_t tileIndex = tileRow + tileCol;
|
||||||
|
|
||||||
uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
||||||
int8_t tileID = signedIndex ? static_cast<int8_t>(addressSpace[tileAddr]) : addressSpace[tileAddr];
|
const int8_t tileID = signedIndex ? static_cast<int8_t>(addressSpace[tileAddr]) : addressSpace[tileAddr];
|
||||||
|
|
||||||
uint16_t tileDataAddr;
|
uint16_t tileDataAddr;
|
||||||
if (signedIndex) {
|
if (signedIndex) {
|
||||||
@@ -163,12 +144,12 @@ void GameBoy::drawLine() {
|
|||||||
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t lineOffset = yPos % 8;
|
const uint8_t lineOffset = yPos % 8;
|
||||||
uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)];
|
const uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)];
|
||||||
uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
const uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||||
|
|
||||||
uint8_t colourBit = 7 - (xPos % 8);
|
const uint8_t colourBit = 7 - (xPos % 8);
|
||||||
uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
// Apply the BGP register for palette mapping
|
||||||
uint8_t palette = (*BGP >> (colourNum * 2)) & 0x3;
|
uint8_t palette = (*BGP >> (colourNum * 2)) & 0x3;
|
||||||
@@ -196,14 +177,14 @@ void GameBoy::SDL2setup() {
|
|||||||
SDL_WINDOW_OPENGL);
|
SDL_WINDOW_OPENGL);
|
||||||
|
|
||||||
// Create an SDL renderer to draw on the window
|
// Create an SDL renderer to draw on the window
|
||||||
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
|
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
// Create an SDL texture to hold the framebuffer data
|
// Create an SDL texture to hold the framebuffer data
|
||||||
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||||
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y);
|
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2destroy() {
|
void GameBoy::SDL2destroy() const {
|
||||||
SDL_DestroyTexture(texture);
|
SDL_DestroyTexture(texture);
|
||||||
SDL_DestroyRenderer(renderer);
|
SDL_DestroyRenderer(renderer);
|
||||||
SDL_DestroyWindow(screen);
|
SDL_DestroyWindow(screen);
|
||||||
@@ -211,13 +192,18 @@ void GameBoy::SDL2destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2present() {
|
void GameBoy::SDL2present() {
|
||||||
// Update the SDL texture with the framebuffer data
|
SDL_UpdateTexture(texture, nullptr, framebuffer, RESOLUTION_X * sizeof(uint32_t));
|
||||||
SDL_UpdateTexture(texture, NULL, framebuffer, RESOLUTION_X * sizeof(uint32_t));
|
|
||||||
|
|
||||||
// Clear the renderer and render the texture
|
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||||
|
|
||||||
|
|
||||||
|
frameTime = SDL_GetTicks() - frameStart;
|
||||||
|
std::cout << SDL_GetTicks() << " " << frameTime << std::endl;
|
||||||
|
|
||||||
|
if (frameDelay > frameTime) {
|
||||||
|
SDL_Delay(frameDelay - frameTime);
|
||||||
|
}
|
||||||
|
frameStart = SDL_GetTicks();
|
||||||
|
|
||||||
// Present the renderer on the screen
|
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/timing.cpp
Normal file
12
src/timing.cpp
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
|
//handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
|
||||||
|
void GameBoy::timingHandler() {
|
||||||
|
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
||||||
|
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
||||||
|
|
||||||
|
(*DIV) += increments;
|
||||||
|
|
||||||
|
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user