reformatting

This commit is contained in:
2024-02-05 22:09:34 -08:00
parent 73a40230da
commit 2809dbfca8
7 changed files with 238 additions and 356 deletions

View File

@@ -9,7 +9,7 @@
// 6 n - - Add/Sub-Flag (BCD) // 6 n - - Add/Sub-Flag (BCD)
// 5 h - - Half Carry Flag (BCD) // 5 h - - Half Carry Flag (BCD)
// 4 cy C NC Carry Flag // 4 cy C NC Carry Flag
// 3-0 - - - Not used (always zero) // 3-0 - - - Not used
#define CARRY_FLAG 4 //'C' #define CARRY_FLAG 4 //'C'
#define HALFCARRY_FLAG 5 //'H' #define HALFCARRY_FLAG 5 //'H'
#define SUBTRACT_FLAG 6 //'N' #define SUBTRACT_FLAG 6 //'N'
@@ -21,7 +21,7 @@
#define SERIAL_INTERRUPT 3 #define SERIAL_INTERRUPT 3
#define JOYPAD_INTERRUPT 4 #define JOYPAD_INTERRUPT 4
#define T_CLOCK_FREQ 4194304 #define T_CLOCK_FREQ 4194304 //2^22
#define DIVIDER_REGISTER_FREQ 16384 #define DIVIDER_REGISTER_FREQ 16384
@@ -39,13 +39,13 @@
#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 MODE0_3_DURATION 376 //mode3 is 172 to 289, mode0 87 to 204
#define MODE1_DURATION 4560
#define H_SYNC 9198 #define H_SYNC 9198
#define V_SYNC 59.73 #define V_SYNC 59.73
#define HBlank_DURATION 204 //GPU_MODE 0 #define HBLANK_DURATION 204 //PPU_MODE 0
#define SCANLINE_OAM_FREQ 80 //GPU_MODE 2 #define VBLANK_DURATION 4560
#define SCANLINE_VRAM_FREQ 80 //GPU_MODE 3 #define SCANLINE_OAM_FREQ 80 //PPU_MODE 2
#define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3
#endif #endif

View File

@@ -1,60 +1,50 @@
#include <iostream> #include <iostream>
#include "gameboy.hpp" #include "gameboy.hpp"
bool AddressSpace::getBootromState() bool AddressSpace::getBootromState() {
{
return bootromLoaded; return bootromLoaded;
} }
void AddressSpace::unmapBootrom() void AddressSpace::unmapBootrom() {
{
bootromLoaded = false; bootromLoaded = false;
} }
void AddressSpace::mapBootrom() void AddressSpace::mapBootrom() {
{
bootromLoaded = true; bootromLoaded = true;
} }
void AddressSpace::loadBootrom(std::string filename) void AddressSpace::loadBootrom(std::string filename) {
{
std::ifstream file; std::ifstream file;
int size = std::filesystem::file_size(filename); int size = std::filesystem::file_size(filename);
if(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);
} }
file.open(filename, std::ios::binary); file.open(filename, std::ios::binary);
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(std::string filename) {
{
game.open(filename, std::ios::binary); game.open(filename, std::ios::binary);
if(!game.is_open()) if (!game.is_open()) {
{
std::cerr << "Game was not found!\nQuitting!\n" << std::endl; std::cerr << "Game was not found!\nQuitting!\n" << std::endl;
exit(1); exit(1);
} }
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(uint8_t ticks) {
{ cycles = (cycles + ticks) % T_CLOCK_FREQ;
cycles = (cycles+ticks) % T_CLOCK_FREQ;
lastOpTicks = ticks; lastOpTicks = ticks;
} }
void GameBoy::start(std::string bootrom, std::string game) void GameBoy::start(std::string bootrom, std::string game) {
{
addressSpace.loadBootrom(bootrom); addressSpace.loadBootrom(bootrom);
addressSpace.loadGame(game); addressSpace.loadGame(game);
bool quit = false; bool quit = false;
while(!quit) while (!quit) {
{
// Event loop: Check and handle SDL events // Event loop: Check and handle SDL events
// if(SDL_PollEvent(&event)) // if(SDL_PollEvent(&event))
// { // {
@@ -68,16 +58,13 @@ void GameBoy::start(std::string bootrom, std::string game)
interruptHandler(); interruptHandler();
//timing(); //timing();
ppuUpdate(); ppuUpdate();
if(PC > 0xFF && addressSpace.getBootromState()) if (PC > 0xFF && addressSpace.getBootromState()) {
{
addressSpace.unmapBootrom(); addressSpace.unmapBootrom();
} }
int cyclesSince = cyclesSinceLastRefresh(); int cyclesSince = cyclesSinceLastRefresh();
if(cyclesSince > FRAME_DURATION) if (cyclesSince > FRAME_DURATION) {
{
lastRefresh = cycles; lastRefresh = cycles;
SDL2present(); SDL2present();
} }
} }
} }

View File

@@ -11,66 +11,59 @@
#include "defines.hpp" #include "defines.hpp"
//two bits per colour //two bits per colour
enum Colour enum Colour {
{
black = 0b11, black = 0b11,
darkGray = 0b10, darkGray = 0b10,
lightGray = 0b01, lightGray = 0b01,
white = 0b00 white = 0b00
}; };
enum PPUMode enum PPUMode {
{
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period. mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period.
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period. mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period.
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation. mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation.
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen. mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen.
}; };
union RegisterPair union RegisterPair {
{
Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo) Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo)
struct struct {
{
Byte lo; Byte lo;
Byte hi; Byte hi;
}; };
}; };
class AddressSpace class AddressSpace {
{ private:
private:
bool bootromLoaded = true; bool bootromLoaded = true;
Byte bootrom[BOOTROM_SIZE]; Byte bootrom[BOOTROM_SIZE];
std::ifstream game; std::ifstream game;
public: public:
AddressSpace() AddressSpace() {
{
// Initialize the memory to zero // Initialize the memory to zero
memoryLayout = {}; memoryLayout = {};
std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory)); std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory));
} }
// Nested union for the memory layout // Nested union for the memory layout
union MemoryLayout union MemoryLayout {
{
Byte memory[0x10000]; Byte memory[0x10000];
struct
{ struct {
Byte romBank1[ROM_BANK_SIZE]; // Mapped to 0x0000 Byte romBank1[ROM_BANK_SIZE]; // Mapped to 0x0000
Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000 Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000
Byte vram[0x2000]; // Mapped to 0x8000 Byte vram[0x2000]; // Mapped to 0x8000
Byte externalRam[0x2000]; // Mapped to 0xA000 Byte externalRam[0x2000]; // Mapped to 0xA000
Byte memoryBank1[0x1000]; // Mapped to 0xC000 Byte memoryBank1[0x1000]; // Mapped to 0xC000
Byte memoryBank2[0x1000]; // Mapped to 0xD000 Byte memoryBank2[0x1000]; // Mapped to 0xD000
Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF) Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF)
Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00 Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00
Byte notUsable[0x60]; // Mapped to 0xFEA0 Byte notUsable[0x60]; // Mapped to 0xFEA0
Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag
Byte specialRam[0x7F]; // Mapped to 0xFF80 Byte specialRam[0x7F]; // Mapped to 0xFF80
Byte interuptEnableReg; // Mapped to 0xFFFF Byte interuptEnableReg; // Mapped to 0xFFFF
}; };
} memoryLayout; } memoryLayout;
@@ -81,20 +74,19 @@ class AddressSpace
void loadGame(std::string filename); void loadGame(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[](uint32_t address) const {
{ if (address >= 0xE000 && address < 0xFE00)
if(address >= 0xE000 && address < 0xFE00)
return memoryLayout.echoRam[address - 0xE000]; return memoryLayout.echoRam[address - 0xE000];
if(address < 0x0100 && bootromLoaded) if (address < 0x0100 && bootromLoaded)
return bootrom[address]; return bootrom[address];
else else
return memoryLayout.memory[address]; return memoryLayout.memory[address];
} }
Byte& operator[](uint32_t address)
{ Byte& operator[](uint32_t address) {
if(address >= 0xE000 && address < 0xFE00) if (address >= 0xE000 && address < 0xFE00)
return memoryLayout.echoRam[address - 0xE000]; return memoryLayout.echoRam[address - 0xE000];
if(address < 0x0100 && bootromLoaded) if (address < 0x0100 && bootromLoaded)
return bootrom[address]; return bootrom[address];
else else
return memoryLayout.memory[address]; return memoryLayout.memory[address];
@@ -102,7 +94,7 @@ class AddressSpace
}; };
class GameBoy { class GameBoy {
private: private:
uint32_t cycles = 0; uint32_t cycles = 0;
uint32_t lastOpTicks = 0; uint32_t lastOpTicks = 0;
uint32_t lastRefresh = 0; uint32_t lastRefresh = 0;
@@ -181,10 +173,10 @@ class GameBoy {
PPUMode currentMode; PPUMode currentMode;
//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;
SDL_Renderer *renderer; SDL_Renderer* renderer;
SDL_Texture *texture; SDL_Texture* texture;
SDL_Event event; SDL_Event event;
void opcodeHandler(); void opcodeHandler();
@@ -220,56 +212,56 @@ class GameBoy {
void addCycles(Byte ticks); void addCycles(Byte ticks);
//OPCODE FUNCTIONS //OPCODE FUNCTIONS
template<typename T> template <typename T>
void ld(T& dest, T src); void ld(T& dest, T src);
template<typename T> template <typename T>
void orBitwise(T &dest, T src); void orBitwise(T& dest, T src);
template<typename T> template <typename T>
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> template <typename T>
void bit(T testBit, T reg); void bit(T testBit, 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<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 stop(); void stop();
template<typename T> template <typename T>
void ldW(T dest, T src); void ldW(T dest, T src);
template<typename T> template <typename T>
void cp(T value); void cp(T value);
template<typename T> template <typename T>
void dec(T& reg); void dec(T& reg);
template<typename T> template <typename T>
bool jrZ(T offset); bool jrZ(T offset);
template<typename T> template <typename T>
void sub(T value); void sub(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> template <typename T>
void rl(T& reg); void rl(T& reg);
template<typename T> template <typename T>
void pop(T& reg); void pop(T& reg);
template<typename T> template <typename T>
void rla(T& reg); void rla(T& reg);
template<typename T> 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);
void cpl(); void cpl();
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();

View File

@@ -1,35 +1,31 @@
#include "defines.hpp" #include "defines.hpp"
#include "gameboy.hpp" #include "gameboy.hpp"
bool GameBoy::testInterruptEnabled(Byte interrupt) bool GameBoy::testInterruptEnabled(Byte interrupt) {
{ return (*IE) & (Byte)(1 << interrupt);
return (*IE) & (Byte) (1 << interrupt);
} }
void GameBoy::resetInterrupt(Byte interrupt) void GameBoy::resetInterrupt(Byte interrupt) {
{
*IF &= ~(1 << interrupt); *IF &= ~(1 << interrupt);
} }
void GameBoy::interruptHandler() void GameBoy::interruptHandler() {
{ if (!IME)
if(!IME)
return; return;
if(*IF & (Byte) (1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT)) if (*IF & (Byte)(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT))
VBlankHandle(); VBlankHandle();
if(*IF & (Byte) (1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT)) if (*IF & (Byte)(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT))
LCDStatHandle(); LCDStatHandle();
if(*IF & (Byte) (1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT)) if (*IF & (Byte)(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT))
timerHandle(); timerHandle();
if(*IF & (Byte) (1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT)) if (*IF & (Byte)(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT))
serialHandle(); serialHandle();
if(*IF & (Byte) (1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT)) if (*IF & (Byte)(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT))
joypadHandle(); joypadHandle();
} }
void GameBoy::VBlankHandle() void GameBoy::VBlankHandle() {
{
printf("VBlank interrupt"); printf("VBlank interrupt");
IME = 0; IME = 0;
push(PC); push(PC);
@@ -37,8 +33,7 @@ void GameBoy::VBlankHandle()
resetInterrupt(VBLANK_INTERRUPT); resetInterrupt(VBLANK_INTERRUPT);
} }
void GameBoy::LCDStatHandle() void GameBoy::LCDStatHandle() {
{
printf("LCD stat interrupt"); printf("LCD stat interrupt");
IME = 0; IME = 0;
push(PC); push(PC);
@@ -47,8 +42,7 @@ void GameBoy::LCDStatHandle()
resetInterrupt(LCD_STAT_INTERRUPT); resetInterrupt(LCD_STAT_INTERRUPT);
} }
void GameBoy::timerHandle() void GameBoy::timerHandle() {
{
printf("timer interrupt"); printf("timer interrupt");
IME = 0; IME = 0;
push(PC); push(PC);
@@ -57,8 +51,7 @@ void GameBoy::timerHandle()
resetInterrupt(TIMER_INTERRUPT); resetInterrupt(TIMER_INTERRUPT);
} }
void GameBoy::serialHandle() void GameBoy::serialHandle() {
{
printf("serial interrupt"); printf("serial interrupt");
IME = 0; IME = 0;
push(PC); push(PC);
@@ -67,8 +60,7 @@ void GameBoy::serialHandle()
resetInterrupt(SERIAL_INTERRUPT); resetInterrupt(SERIAL_INTERRUPT);
} }
void GameBoy::joypadHandle() void GameBoy::joypadHandle() {
{
printf("joypad interrupt"); printf("joypad interrupt");
IME = 0; IME = 0;
push(PC); push(PC);

View File

@@ -1,8 +1,7 @@
#include <string> #include <string>
#include "gameboy.hpp" #include "gameboy.hpp"
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("../bootrom.bin", "../roms/DrMario.gb");

View File

@@ -1,22 +1,18 @@
#include "gameboy.hpp" #include "gameboy.hpp"
void GameBoy::setFlag(Byte bit) void GameBoy::setFlag(Byte bit) {
{
AF.lo |= (1 << bit); AF.lo |= (1 << bit);
} }
void GameBoy::resetFlag(Byte bit) void GameBoy::resetFlag(Byte bit) {
{
AF.lo &= ~(1 << bit); AF.lo &= ~(1 << bit);
} }
bool GameBoy::getFlag(Byte bit) const bool GameBoy::getFlag(Byte bit) const {
{
return (AF.lo >> bit) & 1; return (AF.lo >> bit) & 1;
} }
Word GameBoy::getWordPC() Word GameBoy::getWordPC() {
{
RegisterPair word; RegisterPair word;
//remember little endianness //remember little endianness
@@ -26,13 +22,11 @@ Word GameBoy::getWordPC()
return word.reg; return word.reg;
} }
Byte GameBoy::getBytePC() Byte GameBoy::getBytePC() {
{
return addressSpace[PC + 1]; return addressSpace[PC + 1];
} }
Word GameBoy::getWordSP() Word GameBoy::getWordSP() {
{
RegisterPair word; RegisterPair word;
//remember little endianness //remember little endianness
@@ -42,35 +36,29 @@ Word GameBoy::getWordSP()
return word.reg; return word.reg;
} }
Byte GameBoy::getByteSP() Byte GameBoy::getByteSP() {
{
return addressSpace[SP++]; return addressSpace[SP++];
} }
void GameBoy::ret() void GameBoy::ret() {
{
pop(PC); pop(PC);
} }
template<typename T> template <typename T>
void GameBoy::ld(T &dest, T src) void GameBoy::ld(T& dest, T src) {
{
dest = src; dest = src;
} }
template<typename T> template <typename T>
void GameBoy::ldW(T dest, T src) void GameBoy::ldW(T dest, T src) {
{ if (sizeof(src) == sizeof(Word)) {
if(sizeof(src) == sizeof(Word)) addressSpace[dest] = (Byte)(src & 0xFF00) >> 8;
{ addressSpace[dest + 1] = (Byte)(src & 0xFF);
addressSpace[dest] = (Byte) (src & 0xFF00) >> 8;
addressSpace[dest + 1] = (Byte) (src & 0xFF);
} }
} }
template<typename T> template <typename T>
void GameBoy::rla(T &reg) void GameBoy::rla(T& reg) {
{
//printf("0x%.2x\n", REG); //printf("0x%.2x\n", REG);
//printf("%d\n", GET_FLAG(CARRY_FLAG)); //printf("%d\n", GET_FLAG(CARRY_FLAG));
bool carry; bool carry;
@@ -78,17 +66,17 @@ void GameBoy::rla(T &reg)
//printf("\n0x%x\n", REG); //printf("\n0x%x\n", REG);
//printf("0x%x\n\n", REG & ((T)1 << 7)); //printf("0x%x\n\n", REG & ((T)1 << 7));
if(reg & (1 << 7)) if (reg & (1 << 7))
carry = true; carry = true;
else else
carry = false; carry = false;
reg <<= 1; reg <<= 1;
if(getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG))
reg += 1; reg += 1;
if(carry) if (carry)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
@@ -98,32 +86,29 @@ void GameBoy::rla(T &reg)
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::add(T &reg, T value) void GameBoy::add(T& reg, T value) {
{ if (sizeof(reg) == sizeof(Byte)) {
if(sizeof(reg) == sizeof(Byte))
{
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10) if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if((((value & 0xFF) + (reg & 0xFF)) & 0x100) == 0x100) if ((((value & 0xFF) + (reg & 0xFF)) & 0x100) == 0x100)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
if(sizeof(reg) == sizeof(Word)) if (sizeof(reg) == sizeof(Word)) {
{
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if((((value & 0xFFF) + (reg & 0xFFF)) & 0x1000) == 0x1000) if ((((value & 0xFFF) + (reg & 0xFFF)) & 0x1000) == 0x1000)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if((((value & 0xFFFF) + (reg & 0xFFFF)) & 0x10000) == 0x10000) if ((((value & 0xFFFF) + (reg & 0xFFFF)) & 0x10000) == 0x10000)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
@@ -131,21 +116,19 @@ void GameBoy::add(T &reg, T value)
reg += value; reg += value;
if(reg == 0) if (reg == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else else
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::orBitwise(T &dest, T src) void GameBoy::orBitwise(T& dest, T src) {
{
dest |= src; dest |= src;
if(dest == 0) if (dest == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
@@ -153,12 +136,11 @@ void GameBoy::orBitwise(T &dest, T src)
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::andBitwise(T &dest, T src) void GameBoy::andBitwise(T& dest, T src) {
{
dest &= src; dest &= src;
if(dest == 0) if (dest == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
@@ -166,12 +148,11 @@ void GameBoy::andBitwise(T &dest, T src)
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::xorBitwise(T &dest, T src) void GameBoy::xorBitwise(T& dest, T src) {
{
dest ^= src; dest ^= src;
if(dest == 0) if (dest == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
@@ -179,12 +160,11 @@ void GameBoy::xorBitwise(T &dest, T src)
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::bit(T testBit, T reg) void GameBoy::bit(T testBit, T reg) {
{ Byte result = reg & (T)(1 << testBit);
Byte result = reg & (T) (1 << testBit);
if(result == 0) if (result == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else else
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
@@ -193,27 +173,24 @@ void GameBoy::bit(T testBit, T reg)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
} }
template<typename T> template <typename T>
bool GameBoy::jrNZ(T offset) bool GameBoy::jrNZ(T offset) {
{
bool jumped = false; bool jumped = false;
if(!getFlag(ZERO_FLAG)) //if not set if (!getFlag(ZERO_FLAG)) //if not set
{ {
PC += (int8_t) offset + 2; //PC moves 2 from the original instruction PC += (int8_t)offset + 2; //PC moves 2 from the original instruction
jumped = true; jumped = true;
} }
return jumped; return jumped;
} }
template<typename T> template <typename T>
void GameBoy::inc(T &reg) void GameBoy::inc(T& reg) {
{
reg++; reg++;
if(sizeof(reg) == sizeof(Byte)) if (sizeof(reg) == sizeof(Byte)) {
{ if (reg == 0)
if(reg == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else else
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
@@ -221,31 +198,27 @@ void GameBoy::inc(T &reg)
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if(((((reg - 1) & 0xf) + (reg & 0xf)) & 0x10) == 0x10) if (((((reg - 1) & 0xf) + (reg & 0xf)) & 0x10) == 0x10)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
} }
template<typename T> template <typename T>
void GameBoy::call(T address) void GameBoy::call(T address) {
{
push(PC + 3); push(PC + 3);
PC = address; PC = address;
} }
template<typename T> template <typename T>
void GameBoy::cp(T value) //compare void GameBoy::cp(T value) //compare
{ {
if(AF.hi < value) if (AF.hi < value) {
{
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
} }
else if(AF.hi == value) else if (AF.hi == value) {
{
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
@@ -253,21 +226,18 @@ void GameBoy::cp(T value) //compare
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/ //halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
if(0 > (((AF.hi) & 0xf) - (value & 0xf))) if (0 > (((AF.hi) & 0xf) - (value & 0xf)))
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::dec(T &reg) void GameBoy::dec(T& reg) {
{
reg -= 1; reg -= 1;
if(sizeof(reg) == sizeof(Byte)) if (sizeof(reg) == sizeof(Byte)) {
{ if (reg == 0)
if(reg == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else else
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
@@ -275,37 +245,33 @@ void GameBoy::dec(T &reg)
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/ //halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
if(0 > (((reg + 1) & 0xf) - (reg & 0xf))) if (0 > (((reg + 1) & 0xf) - (reg & 0xf)))
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
} }
template<typename T> template <typename T>
bool GameBoy::jrZ(T offset) bool GameBoy::jrZ(T offset) {
{
bool jumped = false; bool jumped = false;
if(getFlag(ZERO_FLAG)) //if not set if (getFlag(ZERO_FLAG)) //if not set
{ {
PC += (int8_t) offset + 2; //PC moves 2 from the original instruction PC += (int8_t)offset + 2; //PC moves 2 from the original instruction
jumped = true; jumped = true;
} }
return jumped; return jumped;
} }
void GameBoy::swap(Byte &value) void GameBoy::swap(Byte& value) {
{
// Extract the lower and upper nibbles of the register // Extract the lower and upper nibbles of the register
Byte lowerNibble = value & 0x0F; Byte lowerNibble = value & 0x0F;
Byte upperNibble = (value >> 4) & 0x0F; Byte upperNibble = (value >> 4) & 0x0F;
// Swap the lower and upper nibbles // Swap the lower and upper nibbles
value = (lowerNibble << 4) | upperNibble; value = (lowerNibble << 4) | upperNibble;
if(value == 0) if (value == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else else
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
@@ -315,21 +281,15 @@ void GameBoy::swap(Byte &value)
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
void GameBoy::halt() void GameBoy::halt() {}
{
} template <typename T>
void GameBoy::sub(T value) {
template<typename T> if (AF.hi < value) {
void GameBoy::sub(T value)
{
if(AF.hi < value)
{
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
} }
else if(AF.hi == value) else if (AF.hi == value) {
{
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
@@ -338,61 +298,51 @@ void GameBoy::sub(T value)
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/ //halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
if(0 > (((AF.hi) & 0xf) - (value & 0xf))) if (0 > (((AF.hi) & 0xf) - (value & 0xf)))
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::jr(T offset) void GameBoy::jr(T offset) {
{ PC += (int8_t)offset + 2; //PC moves 2 from original instruction
PC += (int8_t) offset + 2; //PC moves 2 from original instruction
} }
template<typename T> template <typename T>
void GameBoy::rl(T &reg) void GameBoy::rl(T& reg) {
{
//printf("0x%.2x\n", REG);
//printf("%d\n", GET_FLAG(CARRY_FLAG));
bool carry; bool carry;
//printf("\n0x%x\n", REG); if (reg & (1 << 7))
//printf("0x%x\n\n", REG & ((T)1 << 7));
if(reg & (1 << 7))
carry = true; carry = true;
else else
carry = false; carry = false;
reg <<= 1; reg <<= 1;
if(getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG))
reg += 1; reg += 1;
if(carry) if (carry)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
if(reg == 0) if (reg == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template<typename T> template <typename T>
void GameBoy::pop(T &reg) void GameBoy::pop(T& reg) {
{
reg = getWordSP(); reg = getWordSP();
} }
template<typename T> template <typename T>
void GameBoy::push(T reg) void GameBoy::push(T reg) {
{
//little endian //little endian
RegisterPair temp; RegisterPair temp;
temp.lo = reg & 0xFF; temp.lo = reg & 0xFF;
@@ -403,57 +353,47 @@ void GameBoy::push(T reg)
addressSpace[SP] = temp.lo; addressSpace[SP] = temp.lo;
} }
template<typename T> template <typename T>
void GameBoy::jp(T address) void GameBoy::jp(T address) {
{
PC = address; PC = address;
} }
template<typename T> template <typename T>
void GameBoy::rst(T address) void GameBoy::rst(T address) {
{
push(PC); push(PC);
PC = address; PC = address;
} }
void GameBoy::cpl() void GameBoy::cpl() {
{
AF.hi = ~AF.hi; AF.hi = ~AF.hi;
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
} }
void GameBoy::ccf() void GameBoy::ccf() {
{
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
if(getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG))
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
else else
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
} }
void GameBoy::stop() void GameBoy::stop() {}
{
}
void GameBoy::opcodeHandler() void GameBoy::opcodeHandler() {
{
bool jumped; bool jumped;
if(addressSpace[PC] != 0xCB) if (addressSpace[PC] != 0xCB) {
{
//printf("PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); //printf("PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]);
if(PC == 0x100) if (PC == 0x100) {
{
printf("LY:0x%.2x\n", (*LY)); printf("LY:0x%.2x\n", (*LY));
// printf("PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); // printf("PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]);
// printf("IME:%b IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); // printf("IME:%b IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
} }
//printf("IME:%b IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); //printf("IME:%b IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
switch(addressSpace[PC]) switch (addressSpace[PC]) {
{
case 0x00: case 0x00:
//NOP //NOP
PC += 1; PC += 1;
@@ -630,12 +570,10 @@ void GameBoy::opcodeHandler()
case 0x20: case 0x20:
jumped = jrNZ(getBytePC()); jumped = jrNZ(getBytePC());
if(jumped) if (jumped) {
{
addCycles(12); addCycles(12);
} }
else else {
{
PC += 2; PC += 2;
addCycles(8); addCycles(8);
} }
@@ -674,12 +612,10 @@ void GameBoy::opcodeHandler()
case 0x28: case 0x28:
jumped = jrZ(getBytePC()); jumped = jrZ(getBytePC());
if(jumped) if (jumped) {
{
addCycles(12); addCycles(12);
} }
else else {
{
PC += 2; PC += 2;
addCycles(8); addCycles(8);
} }
@@ -1457,13 +1393,11 @@ void GameBoy::opcodeHandler()
break; break;
case 0xC0: //RET NZ case 0xC0: //RET NZ
if(!getFlag(ZERO_FLAG)) if (!getFlag(ZERO_FLAG)) {
{
ret(); ret();
addCycles(20); addCycles(20);
} }
else else {
{
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
@@ -1476,13 +1410,11 @@ void GameBoy::opcodeHandler()
break; break;
case 0xC2: case 0xC2:
if(!getFlag(ZERO_FLAG)) if (!getFlag(ZERO_FLAG)) {
{
jp(getWordPC()); jp(getWordPC());
addCycles(16); addCycles(16);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1494,13 +1426,11 @@ void GameBoy::opcodeHandler()
break; break;
case 0xC4: case 0xC4:
if(!getFlag(ZERO_FLAG)) if (!getFlag(ZERO_FLAG)) {
{
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1513,17 +1443,15 @@ void GameBoy::opcodeHandler()
break; break;
case 0xC8: case 0xC8:
if(getFlag(ZERO_FLAG)) if (getFlag(ZERO_FLAG)) {
{
ret(); ret();
addCycles(20); addCycles(20);
} }
else else {
{
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
break; break;
case 0xC9: case 0xC9:
ret(); ret();
@@ -1531,26 +1459,22 @@ void GameBoy::opcodeHandler()
break; break;
case 0xCA: case 0xCA:
if(getFlag(ZERO_FLAG)) if (getFlag(ZERO_FLAG)) {
{
jp(getWordPC()); jp(getWordPC());
addCycles(16); addCycles(16);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
break; break;
case 0xCC: case 0xCC:
if(getFlag(ZERO_FLAG)) if (getFlag(ZERO_FLAG)) {
{
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1567,13 +1491,11 @@ void GameBoy::opcodeHandler()
break; break;
case 0xD0: //RET NC case 0xD0: //RET NC
if(!getFlag(CARRY_FLAG)) if (!getFlag(CARRY_FLAG)) {
{
ret(); ret();
addCycles(20); addCycles(20);
} }
else else {
{
addCycles(8); addCycles(8);
PC += 3; PC += 3;
} }
@@ -1586,26 +1508,22 @@ void GameBoy::opcodeHandler()
break; break;
case 0xD2: case 0xD2:
if(!getFlag(CARRY_FLAG)) if (!getFlag(CARRY_FLAG)) {
{
jp(getWordPC()); jp(getWordPC());
addCycles(24); addCycles(24);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
break; break;
case 0xD4: case 0xD4:
if(!getFlag(CARRY_FLAG)) if (!getFlag(CARRY_FLAG)) {
{
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1618,13 +1536,11 @@ void GameBoy::opcodeHandler()
break; break;
case 0xD8: case 0xD8:
if(getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG)) {
{
ret(); ret();
addCycles(20); addCycles(20);
} }
else else {
{
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
@@ -1638,26 +1554,22 @@ void GameBoy::opcodeHandler()
break; break;
case 0xDA: case 0xDA:
if(getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG)) {
{
jp(getWordPC()); jp(getWordPC());
addCycles(16); addCycles(16);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
break; break;
case 0xDC: case 0xDC:
if(getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG)) {
{
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} }
else else {
{
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1698,11 +1610,11 @@ void GameBoy::opcodeHandler()
addCycles(8); addCycles(8);
break; break;
// case 0xE8: // case 0xE8:
// SP += (int8_t)getByte(); // SP += (int8_t)getByte();
// PC += 2; // PC += 2;
// addCycles(16); // addCycles(16);
// break; // break;
case 0xE9: case 0xE9:
jp(HL.reg); jp(HL.reg);
@@ -1773,7 +1685,6 @@ void GameBoy::opcodeHandler()
printf("Unimplemented opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); printf("Unimplemented opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]);
exit(1); exit(1);
} }
} }
else //extension else //extension
{ {
@@ -1782,8 +1693,7 @@ void GameBoy::opcodeHandler()
addCycles(4); addCycles(4);
//extension handler //extension handler
switch(addressSpace[PC]) switch (addressSpace[PC]) {
{
case 0x11: case 0x11:
rl(BC.lo); rl(BC.lo);
PC += 1; PC += 1;
@@ -1839,7 +1749,7 @@ void GameBoy::opcodeHandler()
break; break;
case 0x7C: case 0x7C:
bit((Byte) 7, HL.hi); bit((Byte)7, HL.hi);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;

View File

@@ -4,7 +4,7 @@
#include <cstdint> #include <cstdint>
void GameBoy::ppuUpdate() { void GameBoy::ppuUpdate() {
//test HBlank //test for HBlank
checkPPUMode(); checkPPUMode();
if (cyclesToStayInHblank != -1) { if (cyclesToStayInHblank != -1) {
@@ -53,7 +53,8 @@ void GameBoy::ppuUpdate() {
// 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
// Road Rage and Zerd no Denetsu rely on this // Road Rage and Zerd no Denetsu rely on this
(*STAT) |= (1 << 2); (*STAT) |= (1 << 2);
} else { }
else {
(*STAT) &= ~(1 << 2); (*STAT) &= ~(1 << 2);
} }
@@ -96,7 +97,8 @@ void GameBoy::checkPPUMode() {
setPPUMode(PPUMode::mode3); setPPUMode(PPUMode::mode3);
else else
setPPUMode(PPUMode::mode0); setPPUMode(PPUMode::mode0);
} else { }
else {
// VBlank Period // VBlank Period
setPPUMode(PPUMode::mode1); setPPUMode(PPUMode::mode1);
} }