first tests now pass

This commit is contained in:
2024-04-02 17:38:43 -07:00
parent 0f95c0da86
commit 5c573789c9
12 changed files with 557 additions and 230 deletions

View File

@@ -42,6 +42,6 @@ void AddressSpace::loadGame(const std::string& filename) {
std::istream_iterator<Byte>(rom), std::istream_iterator<Byte>(rom),
std::istream_iterator<Byte>()); std::istream_iterator<Byte>());
memcpy(memoryLayout.romBank0, game.data(), ROM_BANK_SIZE); memoryLayout.romBank0 = game.data();
memcpy(memoryLayout.romBankSwitch, game.data() + ROM_BANK_SIZE, ROM_BANK_SIZE); memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
} }

View File

@@ -12,34 +12,76 @@
class AddressSpace { class AddressSpace {
bool bootromLoaded = true; bool bootromLoaded = true;
Byte bootrom[BOOTROM_SIZE] = {0}; Byte bootrom[BOOTROM_SIZE] = {0};
public:
std::vector<Byte> game; std::vector<Byte> game;
public:
AddressSpace() { AddressSpace() {
// Initialize the memory to zero // Initialize the memory to zero
memoryLayout = {}; memoryLayout = {};
std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory));
} }
// Nested union for the memory layout Byte* cartridgeRam = nullptr;
union MemoryLayout {
Byte memory[0x10000];
struct { struct {
Byte romBank0[ROM_BANK_SIZE]; // Mapped to 0x0000 Byte* romBank0; //[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 oam[0xA0]; //Mapped to 0xFE00
Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00 Byte notUsable[0x60]; //Mapped to 0xFEA0
Byte notUsable[0x60]; // Mapped to 0xFEA0 //General purpose hardware registers
Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag Byte JOYP;
Byte specialRam[0x7F]; // Mapped to 0xFF80 Byte SB;
Byte interuptEnableReg; // Mapped to 0xFFFF Byte SC;
}; Byte DIV;
//Timer registers
Byte TIMA;
Byte TMA;
Byte TAC;
//interrupt flag and enable
Byte IF;
//Sound registers
Byte NR10;
Byte NR11;
Byte NR12;
Byte NR13;
Byte NR14;
Byte NR20; //not used
Byte NR21;
Byte NR22;
Byte NR23;
Byte NR24;
Byte NR30;
Byte NR31;
Byte NR32;
Byte NR33;
Byte NR34;
Byte NR40; //unused
Byte NR41;
Byte NR42;
Byte NR43;
Byte NR44;
Byte NR50;
Byte NR51;
Byte NR52;
Byte waveRam[0x10];
//PPU registers
Byte LCDC;
Byte STAT;
Byte SCY;
Byte SCX;
Byte LY;
Byte LYC;
Byte DMA;
Byte BGP;
Byte OBP0;
Byte OBP1;
Byte WY;
Byte WX;
Byte specialRam[0x7F]; //Mapped to 0xFF80
Byte IE; // Mapped to 0xFFFF
} memoryLayout{}; } memoryLayout{};
void unmapBootrom(); void unmapBootrom();
@@ -49,35 +91,281 @@ public:
void loadGame(const std::string& filename); void loadGame(const std::string& filename);
void determineMBCInfo(); void determineMBCInfo();
static bool testMBCWrite(Word address);
Byte* MBCRead(Word address);
//prevents seg faults when programs with no MBC try to write to ROM
Byte dummyVal = 0;
void MBCUpdate();
void loadRomBank();
void createRamBank();
void loadRamBank();
MBCType MBC = {}; MBCType MBC = {};
uint32_t romSize = 0; uint32_t romSize = 0;
uint32_t romBanks = 0; uint32_t romBanks = 0;
uint32_t externalRamSize = 0; uint32_t externalRamSize = 0;
uint32_t externalRamBanks = 0; uint32_t externalRamBanks = 0;
Byte romBankRegister = 0x01;
Byte ramBankRegister = 0x00; //Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
Byte selectedRomBank = 0;
Byte romBankRegister = 0x00;
//2 bit register acts as secondary rom bank register or ram bank number
Byte twoBitBankRegister = 0x0;
Byte selectedExternalRamBank = 0;
Byte romRamSelect = 0x00; Byte romRamSelect = 0x00;
Byte ramEnable = 0x00;
//MBC3 //MBC3
Byte latchClockData = 0x00; Byte latchClockData = 0x00;
Byte ramBankRTCRegister = 0x00; Byte ramBankRTCRegister = 0x00;
//overload [] for echo ram and bootrom support //read
Byte operator[](const uint32_t address) const { Byte operator[](const Word address) const {
if (address >= 0xE000 && address < 0xFE00)
return memoryLayout.echoRam[address - 0x2000];
if (address < 0x0100 && bootromLoaded) if (address < 0x0100 && bootromLoaded)
return bootrom[address]; return bootrom[address];
if (address < 0x4000)
return memoryLayout.romBank0[address];
if (address < 0x8000)
return memoryLayout.romBankSwitch[address - 0x4000];
if (address < 0xA000)
return memoryLayout.vram[address - 0x8000];
if (address < 0xC000) {
if (externalRamSize == 0)
return 0xFF;
return memoryLayout.externalRam[address - 0xA000];
}
if (address < 0xD000)
return memoryLayout.memoryBank1[address - 0xC000];
if (address < 0xE000)
return memoryLayout.memoryBank2[address - 0xD000];
if (address < 0xFE00)
return memoryLayout.memoryBank1[address - 0xE000];
if (address < 0xFEA0)
return memoryLayout.oam[address - 0xFE00];
if (address < 0xFF00) {
if ((memoryLayout.STAT & 0x03) == 2 || (memoryLayout.STAT & 0x03) == 3)
return 0xFF;
return 0x00;
}
if (address < 0xFF80)
switch (address) {
case 0xFF00:
return memoryLayout.JOYP;
case 0xFF01:
return memoryLayout.SB;
case 0xFF02:
return memoryLayout.SC;
// Timer registers
case 0xFF04:
return memoryLayout.DIV;
case 0xFF05:
return memoryLayout.TIMA;
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
// Interrupt flag
case 0xFF0F:
return memoryLayout.IF;
// Sound registers
case 0xFF10:
return memoryLayout.NR10;
case 0xFF11:
return memoryLayout.NR11;
case 0xFF12:
return memoryLayout.NR12;
case 0xFF13:
return memoryLayout.NR13;
case 0xFF14:
return memoryLayout.NR14;
case 0xFF16:
return memoryLayout.NR21;
case 0xFF17:
return memoryLayout.NR22;
case 0xFF18:
return memoryLayout.NR23;
case 0xFF19:
return memoryLayout.NR24;
case 0xFF1A:
return memoryLayout.NR30;
case 0xFF1B:
return memoryLayout.NR31;
case 0xFF1C:
return memoryLayout.NR32;
case 0xFF1D:
return memoryLayout.NR33;
case 0xFF1E:
return memoryLayout.NR34;
case 0xFF20:
return memoryLayout.NR41;
case 0xFF21:
return memoryLayout.NR42;
case 0xFF22:
return memoryLayout.NR43;
case 0xFF23:
return memoryLayout.NR44;
case 0xFF24:
return memoryLayout.NR50;
case 0xFF25:
return memoryLayout.NR51;
case 0xFF26:
return memoryLayout.NR52;
// PPU registers
case 0xFF40:
return memoryLayout.LCDC;
case 0xFF41:
return memoryLayout.STAT;
case 0xFF42:
return memoryLayout.SCY;
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
return memoryLayout.LY;
case 0xFF45:
return memoryLayout.LYC;
case 0xFF46:
return memoryLayout.DMA;
case 0xFF47:
return memoryLayout.BGP;
case 0xFF48:
return memoryLayout.OBP0;
case 0xFF49:
return memoryLayout.OBP1;
case 0xFF4A:
return memoryLayout.WY;
case 0xFF4B:
return memoryLayout.WX;
return memoryLayout.memory[address]; default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
}
return 0xFF;
}
if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80];
//0xFFFF
return memoryLayout.IE;
} }
Byte& operator[](const uint32_t address) { //write
if (address >= 0xE000 && address < 0xFE00) Byte& operator[](const Word address) {
return memoryLayout.echoRam[address - 0x2000];
if (address < 0x0100 && bootromLoaded) if (address < 0x0100 && bootromLoaded)
return bootrom[address]; return bootrom[address];
if (address < 0x8000)
return (*MBCRead(address));
if (address < 0xA000)
return memoryLayout.vram[address - 0x8000];
if (address < 0xC000)
return memoryLayout.externalRam[address - 0xA000];
if (address < 0xD000)
return memoryLayout.memoryBank1[address - 0xC000];
if (address < 0xE000)
return memoryLayout.memoryBank2[address - 0xD000];
if (address < 0xFE00)
return memoryLayout.memoryBank1[address - 0xE000];
if (address < 0xFEA0)
return memoryLayout.oam[address - 0xFE00];
if (address < 0xFF00)
return memoryLayout.notUsable[address - 0xFEA0];
if (address < 0xFF80)
switch (address) {
case 0xFF00:
return memoryLayout.JOYP;
case 0xFF01:
return memoryLayout.SB;
case 0xFF02:
return memoryLayout.SC;
case 0xFF04:
return memoryLayout.DIV;
// Timer registers
case 0xFF05:
return memoryLayout.TIMA;
// The address 0xFF15 is mentioned as unused, possibly a mistake? Original has TMA = 0xFF06 typically.
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
// Interrupt flag
case 0xFF0F:
return memoryLayout.IF;
// Sound registers
case 0xFF10:
return memoryLayout.NR10;
case 0xFF11:
return memoryLayout.NR11;
case 0xFF12:
return memoryLayout.NR12;
case 0xFF13:
return memoryLayout.NR13;
case 0xFF14:
return memoryLayout.NR14;
case 0xFF16:
return memoryLayout.NR21;
case 0xFF17:
return memoryLayout.NR22;
case 0xFF18:
return memoryLayout.NR23;
case 0xFF19:
return memoryLayout.NR24;
case 0xFF1A:
return memoryLayout.NR30;
case 0xFF1B:
return memoryLayout.NR31;
case 0xFF1C:
return memoryLayout.NR32;
case 0xFF1D:
return memoryLayout.NR33;
case 0xFF1E:
return memoryLayout.NR34;
case 0xFF20:
return memoryLayout.NR41;
case 0xFF21:
return memoryLayout.NR42;
case 0xFF22:
return memoryLayout.NR43;
case 0xFF23:
return memoryLayout.NR44;
case 0xFF24:
return memoryLayout.NR50;
case 0xFF25:
return memoryLayout.NR51;
case 0xFF26:
return memoryLayout.NR52;
// PPU registers
case 0xFF40:
return memoryLayout.LCDC;
case 0xFF41:
return memoryLayout.STAT;
case 0xFF42:
return memoryLayout.SCY;
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
return memoryLayout.LY;
case 0xFF45:
return memoryLayout.LYC;
case 0xFF46:
return memoryLayout.DMA;
case 0xFF47:
return memoryLayout.BGP;
case 0xFF48:
return memoryLayout.OBP0;
case 0xFF49:
return memoryLayout.OBP1;
case 0xFF4A:
return memoryLayout.WY;
case 0xFF4B:
return memoryLayout.WX;
return memoryLayout.memory[address]; default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
}
return dummyVal;
}
if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80];
//0xFFFF
return memoryLayout.IE;
} }
}; };

View File

@@ -32,6 +32,7 @@
#define SCREEN_BPP 3 #define SCREEN_BPP 3
#define ROM_BANK_SIZE 0x4000 #define ROM_BANK_SIZE 0x4000
#define RAM_BANK_SIZE 0x2000
#define SCANLINES_PER_FRAME 154 #define SCANLINES_PER_FRAME 154
#define SCANLINE_DURATION 456 #define SCANLINE_DURATION 456

View File

@@ -3,7 +3,7 @@
void GameBoy::extendedOpcodeResolver() { void GameBoy::extendedOpcodeResolver() {
PC += 1; PC += 1;
switch (addressSpace[PC]) { switch (readOnlyAddressSpace[PC]) {
case 0x00: case 0x00:
rlc(BC.hi); rlc(BC.hi);
PC += 1; PC += 1;
@@ -425,7 +425,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x46: case 0x46:
bit(0, addressSpace[HL.reg]); bit(0, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -473,7 +473,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x4E: case 0x4E:
bit(1, addressSpace[HL.reg]); bit(1, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -521,7 +521,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x56: case 0x56:
bit(2, addressSpace[HL.reg]); bit(2, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -569,7 +569,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x5E: case 0x5E:
bit(3, addressSpace[HL.reg]); bit(3, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -617,7 +617,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x66: case 0x66:
bit(4, addressSpace[HL.reg]); bit(4, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -665,7 +665,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x6E: case 0x6E:
bit(5, addressSpace[HL.reg]); bit(5, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -713,7 +713,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x76: case 0x76:
bit(6, addressSpace[HL.reg]); bit(6, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -761,7 +761,7 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
case 0x7E: case 0x7E:
bit(7, addressSpace[HL.reg]); bit(7, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;

View File

@@ -13,10 +13,11 @@ void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.loadBootrom(bootrom); addressSpace.loadBootrom(bootrom);
addressSpace.loadGame(game); addressSpace.loadGame(game);
addressSpace.determineMBCInfo(); addressSpace.determineMBCInfo();
addressSpace.createRamBank();
//init some registers that won't otherwise by set //init some registers that won't otherwise by set
(*JOYP) = 0xCF; addressSpace.memoryLayout.JOYP = 0xCF;
(*SC) = 0x7E; addressSpace.memoryLayout.SC = 0x7E;
bool quit = false; bool quit = false;
@@ -28,29 +29,34 @@ void GameBoy::start(std::string bootrom, std::string game) {
if (event.type == SDL_QUIT) { if (event.type == SDL_QUIT) {
quit = true; quit = true;
} }
if (event.type == SDL_KEYUP) {
display = true;
}
} }
while (!rendered) { while (!rendered) {
if (PC > 0xFF && addressSpace.getBootromState()) { if (PC > 0xFF && addressSpace.getBootromState()) {
addressSpace.unmapBootrom(); addressSpace.unmapBootrom();
} }
ppuEnabled = (*LCDC) & 0x80; ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
if (PC >= 0xe0) // if (PC == 0x100)
display = true; // display = true;
if (display) { // if (display) {
printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, addressSpace[PC], // printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
cyclesSinceLastScanline(), currentMode); // 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("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg); // printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.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("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); // printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); // printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
printf("Cart type: 0x%.2x\n", addressSpace.game[0x147]); // printf("\n");
printf("\n"); // }
} // if (PC >= 0xf000)
// exit(1);
opcodeResolver(); opcodeResolver();
addressSpace.MBCUpdate();
interruptHandler(); interruptHandler();
timingHandler(); timingHandler();
if (ppuEnabled) { if (ppuEnabled) {
@@ -60,8 +66,8 @@ void GameBoy::start(std::string bootrom, std::string game) {
ppuCycles = 2; ppuCycles = 2;
lastScanline = 0; lastScanline = 0;
lastRefresh = 0; lastRefresh = 0;
(*LY) = 0x00; addressSpace.memoryLayout.LY = 0x00;
(*STAT) &= 0xfc; addressSpace.memoryLayout.STAT &= 0xfc;
} }
} }
rendered = false; rendered = false;

View File

@@ -46,61 +46,7 @@ class GameBoy {
Word PC = 0x0000; //program counter Word PC = 0x0000; //program counter
AddressSpace addressSpace; AddressSpace addressSpace;
const AddressSpace& readOnlyAddressSpace = addressSpace;
//General purpose hardware registers
Byte* const JOYP = &addressSpace[0xFF00];
Byte* const SB = &addressSpace[0xFF01];
Byte* const SC = &addressSpace[0xFF02];
Byte* const DIV = &addressSpace[0xFF04];
//Timer registers
Byte* const TIMA = &addressSpace[0xFF05];
Byte* const TMA = &addressSpace[0xFF15]; //unused
Byte* const TAC = &addressSpace[0xFF16];
//interrupt flag and enable
Byte* const IF = &addressSpace[0xFF0F];
Byte* const IE = &addressSpace[0xFFFF];
//Sound registers
Byte* const NR10 = &addressSpace[0xFF10];
Byte* const NR11 = &addressSpace[0xFF11];
Byte* const NR12 = &addressSpace[0xFF12];
Byte* const NR13 = &addressSpace[0xFF13];
Byte* const NR14 = &addressSpace[0xFF14];
Byte* const NR20 = &addressSpace[0xFF15]; //unused
Byte* const NR21 = &addressSpace[0xFF16];
Byte* const NR22 = &addressSpace[0xFF17];
Byte* const NR23 = &addressSpace[0xFF18];
Byte* const NR24 = &addressSpace[0xFF19];
Byte* const NR30 = &addressSpace[0xFF1A];
Byte* const NR31 = &addressSpace[0xFF1B];
Byte* const NR32 = &addressSpace[0xFF1C];
Byte* const NR33 = &addressSpace[0xFF1D];
Byte* const NR34 = &addressSpace[0xFF1E];
Byte* const NR40 = &addressSpace[0xFF1F]; //unused
Byte* const NR41 = &addressSpace[0xFF20];
Byte* const NR42 = &addressSpace[0xFF21];
Byte* const NR43 = &addressSpace[0xFF22];
Byte* const NR44 = &addressSpace[0xFF23];
Byte* const NR50 = &addressSpace[0xFF24];
Byte* const NR51 = &addressSpace[0xFF25];
Byte* const NR52 = &addressSpace[0xFF26];
Byte* const waveRam = &addressSpace[0xFF30]; //WaveRam[0x10]
//PPU registers
Byte* const LCDC = &addressSpace[0xFF40];
Byte* const STAT = &addressSpace[0xFF41];
Byte* const SCY = &addressSpace[0xFF42];
Byte* const SCX = &addressSpace[0xFF43];
Byte* const LY = &addressSpace[0xFF44];
Byte* const LYC = &addressSpace[0xFF45];
Byte* const DMA = &addressSpace[0xFF46];
Byte* const BGP = &addressSpace[0xFF47];
Byte* const OBP0 = &addressSpace[0xFF48];
Byte* const OBP1 = &addressSpace[0xFF49];
Byte* const WY = &addressSpace[0xFF4A];
Byte* const WX = &addressSpace[0xFF4B];
PPUMode currentMode = PPUMode::mode0; PPUMode currentMode = PPUMode::mode0;
@@ -114,8 +60,6 @@ class GameBoy {
uint32_t frameTime = 0; uint32_t frameTime = 0;
const int frameDelay = 1000 / V_SYNC; const int frameDelay = 1000 / V_SYNC;
bool testMBCWrite(const Byte& address);
void opcodeResolver(); void opcodeResolver();
void incLY(); void incLY();
void ppuUpdate(); void ppuUpdate();
@@ -131,7 +75,7 @@ class GameBoy {
void interruptHandler(); void interruptHandler();
bool testInterruptEnabled(Byte interrupt) const; bool testInterruptEnabled(Byte interrupt) const;
void resetInterrupt(Byte interrupt) const; void resetInterrupt(Byte interrupt);
void VBlankHandle(); void VBlankHandle();
void LCDStatHandle(); void LCDStatHandle();
@@ -162,8 +106,8 @@ class GameBoy {
void xorBitwise(T& dest, T src); void xorBitwise(T& dest, T src);
void bit(Byte testBit, Byte reg); void bit(Byte testBit, Byte reg);
void extendedOpcodeResolver(); void extendedOpcodeResolver();
static void set(const uint8_t testBit, uint8_t& reg); static void set(uint8_t testBit, uint8_t& reg);
static void res(const uint8_t testBit, uint8_t& reg); static void res(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>

View File

@@ -2,27 +2,32 @@
#include "gameboy.hpp" #include "gameboy.hpp"
bool GameBoy::testInterruptEnabled(const Byte interrupt) const { bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
return (*IE) & static_cast<Byte>(1 << interrupt); return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt);
} }
void GameBoy::resetInterrupt(const Byte interrupt) const { void GameBoy::resetInterrupt(const Byte interrupt) {
*IF &= ~(1 << interrupt); addressSpace.memoryLayout.IF &= ~(1 << interrupt);
*IF |= 0xE0; addressSpace.memoryLayout.IF |= 0xE0;
} }
void GameBoy::interruptHandler() { void GameBoy::interruptHandler() {
if (!IME) if (!IME)
return; return;
if (*IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT)) if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
VBLANK_INTERRUPT))
VBlankHandle(); VBlankHandle();
if (*IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT)) if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
LCD_STAT_INTERRUPT))
LCDStatHandle(); LCDStatHandle();
if (*IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT)) if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
TIMER_INTERRUPT))
timerHandle(); timerHandle();
if (*IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT)) if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
SERIAL_INTERRUPT))
serialHandle(); serialHandle();
if (*IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT)) if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
JOYPAD_INTERRUPT))
joypadHandle(); joypadHandle();
} }

View File

@@ -4,7 +4,7 @@
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("../dmg_boot.bin", "../roms/cpu_instrs.gb"); gb->start("../dmg_boot.bin", "../roms/03-op_sp,hl.gb");
gb->SDL2destroy(); gb->SDL2destroy();
delete gb; delete gb;

View File

@@ -1,10 +1,9 @@
#include "addressSpace.hpp" #include "addressSpace.hpp"
#include "gameboy.hpp"
void AddressSpace::determineMBCInfo() { void AddressSpace::determineMBCInfo() {
MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]); MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]);
romSize = 32768 * (1 << memoryLayout.romBank0[0x147]); romSize = 32768 * (1 << memoryLayout.romBank0[0x147]);
romBanks = (1 << (memoryLayout.romBank0[0x147] + 1)); romBanks = 1 << (memoryLayout.romBank0[0x147] + 1);
switch (memoryLayout.romBank0[0x0149]) { switch (memoryLayout.romBank0[0x0149]) {
case 0x02: case 0x02:
@@ -29,16 +28,90 @@ void AddressSpace::determineMBCInfo() {
break; break;
} }
if (MBC == MBC2 || MBC2Battery) { if (MBC == MBC2 || MBC == MBC2Battery) {
//only the lower 4 bits are usable //only the lower 4 bits are usable
externalRamSize = 512; externalRamSize = 512;
} }
} }
bool GameBoy::testMBCWrite(const Byte& address) { bool AddressSpace::testMBCWrite(const Word address) {
const Byte* ptr = &address; if (address <= 0x7FFF)
if (ptr >= &addressSpace[0x0] && ptr <= &addressSpace[0x7FFF])
return true; return true;
return false; return false;
} }
Byte* AddressSpace::MBCRead(const Word address) {
if (MBC == MBC1) {
if (address <= 0x1FFF)
return &ramEnable;
if (address <= 0x3FFF)
return &romBankRegister;
if (address <= 0x5FFF)
return &twoBitBankRegister;
if (address <= 0x7FFF)
return &romRamSelect;
}
if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
if (address <= 0x1FFF)
return &ramEnable;
//bits 0-4
if (address <= 0x3FFF)
return &romBankRegister;
if (address <= 0x5FFF) {
return &twoBitBankRegister;
}
if (address <= 0x7FFF)
return &romRamSelect;
}
return &dummyVal;
}
void AddressSpace::MBCUpdate() {
if (MBC == MBC1) {
//TODO: multicart roms need to be able to switch the first rom bank as well
//see: https://gbdev.io/pandocs/MBC1.html
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
romBankRegister &= 0x1F;
twoBitBankRegister &= 0x3;
//512 KiB can only have 8KiB of ram
if (romSize > 524288) {
if (romBankRegister == 0)
selectedRomBank = (twoBitBankRegister << 5) + 1;
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
selectedExternalRamBank = 0;
}
else {
if (romBankRegister == 0)
selectedRomBank = 1;
else
selectedRomBank = romBankRegister;
selectedExternalRamBank = twoBitBankRegister;
}
loadRomBank();
loadRamBank();
}
if (MBC == MBC1Ram || MBC == MBC1RamBattery) {}
}
void AddressSpace::loadRomBank() {
Byte* old = memoryLayout.romBankSwitch;
memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank);
if (old != memoryLayout.romBankSwitch)
printf("\n");
}
void AddressSpace::createRamBank() {
if (externalRamSize)
cartridgeRam = new Byte[externalRamSize];
}
void AddressSpace::loadRamBank() {
Byte* old = memoryLayout.externalRam;
if (cartridgeRam != nullptr)
memoryLayout.externalRam = cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank);
if (old != memoryLayout.externalRam)
printf("\n");
}

View File

@@ -16,28 +16,28 @@ Word GameBoy::getWordPC() {
RegisterPair word = {0}; RegisterPair word = {0};
//remember little endianness //remember little endianness
word.lo = addressSpace[PC + 1]; word.lo = readOnlyAddressSpace[PC + 1];
word.hi = addressSpace[PC + 2]; word.hi = readOnlyAddressSpace[PC + 2];
return word.reg; return word.reg;
} }
Byte GameBoy::getBytePC() { Byte GameBoy::getBytePC() {
return addressSpace[PC + 1]; return readOnlyAddressSpace[PC + 1];
} }
Word GameBoy::getWordSP() { Word GameBoy::getWordSP() {
RegisterPair word = {0}; RegisterPair word = {0};
//remember little endianness //remember little endianness
word.lo = addressSpace[SP++]; word.lo = readOnlyAddressSpace[SP++];
word.hi = addressSpace[SP++]; word.hi = readOnlyAddressSpace[SP++];
return word.reg; return word.reg;
} }
Byte GameBoy::getByteSP() { Byte GameBoy::getByteSP() {
return addressSpace[SP++]; return readOnlyAddressSpace[SP++];
} }
void GameBoy::ret() { void GameBoy::ret() {
@@ -47,8 +47,8 @@ void GameBoy::ret() {
template <typename T> template <typename T>
void GameBoy::ld(T& dest, T src) { void GameBoy::ld(T& dest, T src) {
if constexpr (std::is_same_v<T, Byte>) { if constexpr (std::is_same_v<T, Byte>) {
if (&dest == DIV) { if (&dest == &addressSpace.memoryLayout.DIV) {
*DIV = 0x00; addressSpace.memoryLayout.DIV = 0x00;
lastDivUpdate = cycles; lastDivUpdate = cycles;
} }
else { else {
@@ -90,7 +90,7 @@ void GameBoy::add(T& reg, T value) {
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 ((static_cast<unsigned>(value) + static_cast<unsigned>(reg)) & 0x10000)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
@@ -98,10 +98,12 @@ void GameBoy::add(T& reg, T value) {
reg += value; reg += value;
if (reg == 0) if (sizeof(reg) == sizeof(Byte)) {
setFlag(ZERO_FLAG); if (reg == 0)
else setFlag(ZERO_FLAG);
resetFlag(ZERO_FLAG); else
resetFlag(ZERO_FLAG);
}
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
} }
@@ -225,6 +227,8 @@ void GameBoy::orBitwise(T& dest, T src) {
if (dest == 0) if (dest == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
@@ -237,6 +241,8 @@ void GameBoy::andBitwise(T& dest, T src) {
if (dest == 0) if (dest == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
@@ -249,6 +255,8 @@ void GameBoy::xorBitwise(T& dest, T src) {
if (dest == 0) if (dest == 0)
setFlag(ZERO_FLAG); setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
@@ -473,7 +481,7 @@ void GameBoy::rr(Byte& reg) {
reg >>= 1; reg >>= 1;
if (getFlag(CARRY_FLAG)) if (getFlag(CARRY_FLAG))
AF.hi |= 0x80; reg |= 0x80;
if (lsb) if (lsb)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
@@ -628,6 +636,7 @@ void GameBoy::srl(Byte& reg) {
template <typename T> template <typename T>
void GameBoy::pop(T& reg) { void GameBoy::pop(T& reg) {
reg = getWordSP(); reg = getWordSP();
AF.reg &= 0xFFF0;
} }
template <typename T> template <typename T>
@@ -636,9 +645,9 @@ void GameBoy::push(T reg) {
RegisterPair temp = {0}; RegisterPair temp = {0};
temp.lo = reg & 0xFF; temp.lo = reg & 0xFF;
temp.hi = reg >> 8; temp.hi = reg >> 8;
SP--; SP -= 1;
addressSpace[SP] = temp.hi; addressSpace[SP] = temp.hi;
SP--; SP -= 1;
addressSpace[SP] = temp.lo; addressSpace[SP] = temp.lo;
} }
@@ -678,9 +687,9 @@ void GameBoy::ccf() {
void GameBoy::stop() {} void GameBoy::stop() {}
void GameBoy::opcodeResolver() { void GameBoy::opcodeResolver() {
if (addressSpace[PC] != 0xCB) { if (readOnlyAddressSpace[PC] != 0xCB) {
bool jumped; bool jumped;
switch (addressSpace[PC]) { switch (readOnlyAddressSpace[PC]) {
case 0x00: case 0x00:
//NOP //NOP
PC += 1; PC += 1;
@@ -742,7 +751,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x0A: case 0x0A:
ld(AF.hi, addressSpace[BC.reg]); ld(AF.hi, readOnlyAddressSpace[BC.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -790,13 +799,13 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x12: case 0x12:
ld(addressSpace[BC.reg], AF.hi); ld(addressSpace[DE.reg], AF.hi);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
case 0x13: case 0x13:
DE.reg++; //no flags change no just inc it manually DE.reg += 1; //no flags change no just inc it manually
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -837,7 +846,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x1A: case 0x1A:
ld(AF.hi, addressSpace[DE.reg]); ld(AF.hi, readOnlyAddressSpace[DE.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -891,7 +900,7 @@ void GameBoy::opcodeResolver() {
case 0x22: case 0x22:
ld(addressSpace[HL.reg], AF.hi); ld(addressSpace[HL.reg], AF.hi);
HL.reg++; HL.reg += 1;
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -944,7 +953,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x2A: case 0x2A:
ld(AF.hi, addressSpace[HL.reg]); ld(AF.hi, readOnlyAddressSpace[HL.reg]);
HL.reg += 1; HL.reg += 1;
PC += 1; PC += 1;
addCycles(8); addCycles(8);
@@ -963,7 +972,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x2D: case 0x2D:
dec(HL.hi); dec(HL.lo);
PC += 1; PC += 1;
addCycles(4); addCycles(4);
break; break;
@@ -999,7 +1008,7 @@ void GameBoy::opcodeResolver() {
case 0x32: case 0x32:
ld(addressSpace[HL.reg], AF.hi); ld(addressSpace[HL.reg], AF.hi);
HL.reg--; HL.reg -= 1;
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1052,14 +1061,14 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x3A: case 0x3A:
ld(AF.hi, addressSpace[HL.reg]); ld(AF.hi, readOnlyAddressSpace[HL.reg]);
HL.reg -= 1; HL.reg -= 1;
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
case 0x3B: case 0x3B:
SP--; SP -= 1;
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1125,7 +1134,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x46: case 0x46:
ld(BC.hi, addressSpace[HL.reg]); ld(BC.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1173,7 +1182,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x4E: case 0x4E:
ld(BC.lo, addressSpace[HL.reg]); ld(BC.lo, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1221,7 +1230,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x56: case 0x56:
ld(DE.hi, addressSpace[HL.reg]); ld(DE.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1269,7 +1278,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x5E: case 0x5E:
ld(DE.lo, addressSpace[HL.reg]); ld(DE.lo, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1317,7 +1326,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x66: case 0x66:
ld(HL.hi, addressSpace[HL.reg]); ld(HL.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1365,7 +1374,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x6E: case 0x6E:
ld(HL.lo, addressSpace[HL.reg]); ld(HL.lo, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1461,7 +1470,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x7E: case 0x7E:
ld(AF.hi, addressSpace[HL.reg]); ld(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1509,7 +1518,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x86: case 0x86:
add(AF.hi, addressSpace[HL.reg]); add(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1557,7 +1566,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x8E: case 0x8E:
adc(AF.hi, addressSpace[HL.reg]); adc(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1605,7 +1614,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x96: case 0x96:
sub(addressSpace[HL.reg]); sub(readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1653,7 +1662,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x9E: case 0x9E:
sbc(addressSpace[HL.reg]); sbc(readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1701,7 +1710,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xA6: case 0xA6:
andBitwise(AF.hi, addressSpace[HL.reg]); andBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1749,7 +1758,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xAE: case 0xAE:
xorBitwise(AF.hi, addressSpace[HL.reg]); xorBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1797,7 +1806,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xB6: case 0xB6:
orBitwise(AF.hi, addressSpace[HL.reg]); orBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -1845,7 +1854,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xBE: case 0xBE:
cp(addressSpace[HL.reg]); cp(readOnlyAddressSpace[HL.reg]);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -2091,7 +2100,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xE2: case 0xE2:
ld(addressSpace[BC.lo + 0xFF00], AF.hi); ld(addressSpace[0xFF00 + BC.lo], AF.hi);
PC += 1; PC += 1;
addCycles(8); addCycles(8);
break; break;
@@ -2115,20 +2124,19 @@ void GameBoy::opcodeResolver() {
case 0xE8: case 0xE8:
{ {
const auto immediate = static_cast<int8_t>(getBytePC()); const int16_t immediate = static_cast<int8_t>(getBytePC());
const Word result = SP + static_cast<int16_t>(immediate);
if (((SP ^ immediate ^ result) & 0x10) != 0) if ((SP & 0xF) + (immediate & 0xF) > 0xF)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
if (((SP ^ immediate ^ result) & 0x100) != 0) if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
SP = result; SP += immediate;
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
@@ -2161,7 +2169,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xF0: case 0xF0:
ld(AF.hi, addressSpace[0xFF00 + getBytePC()]); ld(AF.hi, readOnlyAddressSpace[0xFF00 + getBytePC()]);
PC += 2; PC += 2;
addCycles(12); addCycles(12);
break; break;
@@ -2173,7 +2181,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xF2: case 0xF2:
ld(AF.hi, addressSpace[0xFF00 + BC.lo]); ld(AF.hi, readOnlyAddressSpace[0xFF00 + BC.lo]);
PC += 1; PC += 1;
addCycles(12); addCycles(12);
break; break;
@@ -2203,20 +2211,20 @@ void GameBoy::opcodeResolver() {
case 0xF8: case 0xF8:
{ {
const auto n = static_cast<int8_t>(getBytePC()); const int16_t immediate = static_cast<int8_t>(getBytePC());
const Word result = SP + n; HL.reg = SP + immediate;
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ if ((SP & 0xF) + (immediate & 0xF) > 0xF)
if ((((result & 0xF) + (HL.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/
if ((((result & 0xFF) + (HL.reg & 0xFF)) & 0x100) == 0x100)
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
HL.reg = result; // Load the result into HL
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
@@ -2233,7 +2241,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0xFA: case 0xFA:
ld(AF.hi, addressSpace[getWordPC()]); ld(AF.hi, readOnlyAddressSpace[getWordPC()]);
PC += 3; PC += 3;
addCycles(16); addCycles(16);
break; break;

View File

@@ -8,25 +8,28 @@ void GameBoy::ppuUpdate() {
//test for HBlank //test for HBlank
checkPPUMode(); checkPPUMode();
if ((*LY) == (*LYC) || (*STAT) & (1 << 6)) { if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC || readOnlyAddressSpace.
memoryLayout.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
// Road Rage and Zerd no Denetsu rely on this // Road Rage and Zerd no Denetsu rely on this
(*STAT) |= (1 << 2); addressSpace.memoryLayout.STAT |= (1 << 2);
} }
else { else {
(*STAT) &= ~(1 << 2); addressSpace.memoryLayout.STAT &= ~(1 << 2);
} }
// Check for STAT interrupts and request if needed (e.g., when entering specific modes) // Check for STAT interrupts and request if needed (e.g., when entering specific modes)
bool hBlankInterruptEnabled = (*STAT) & (1 << 3); bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
bool vBlankInterruptEnabled = (*STAT) & (1 << 4); /* Determine if VBlank interrupt is enabled */ bool vBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
bool oamInterruptEnabled = (*STAT) & (1 << 5); /* Determine if OAM Search interrupt is enabled */ /* Determine if VBlank interrupt is enabled */
bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
/* Determine if OAM Search interrupt is enabled */
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled || if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
currentMode == PPUMode::mode1 && vBlankInterruptEnabled || currentMode == PPUMode::mode1 && vBlankInterruptEnabled ||
currentMode == PPUMode::mode2 && oamInterruptEnabled) { currentMode == PPUMode::mode2 && oamInterruptEnabled) {
*IF |= 0x2; addressSpace.memoryLayout.IF |= 0x2;
} }
} }
@@ -58,16 +61,16 @@ void GameBoy::checkPPUMode() {
} }
void GameBoy::incLY() { void GameBoy::incLY() {
(*LY)++; addressSpace.memoryLayout.LY += 1;
setPPUMode(PPUMode::mode2); setPPUMode(PPUMode::mode2);
if ((*LY) > SCANLINES_PER_FRAME - 1) { if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
(*LY) = 0; addressSpace.memoryLayout.LY = 0;
} }
else if ((*LY) == 144) { else if (addressSpace.memoryLayout.LY == 144) {
// VBlank Period // VBlank Period
SDL2present(); SDL2present();
setPPUMode(PPUMode::mode1); setPPUMode(PPUMode::mode1);
*IF |= 0x1; addressSpace.memoryLayout.IF |= 0x1;
} }
} }
@@ -84,31 +87,31 @@ uint64_t GameBoy::cyclesSinceLastRefresh() const {
void GameBoy::setPPUMode(const PPUMode mode) { void GameBoy::setPPUMode(const PPUMode mode) {
switch (mode) { switch (mode) {
case PPUMode::mode0: case PPUMode::mode0:
(*STAT) &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
currentMode = PPUMode::mode0; currentMode = PPUMode::mode0;
break; break;
case PPUMode::mode1: case PPUMode::mode1:
(*STAT) &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
(*STAT) |= 0x01; addressSpace.memoryLayout.STAT |= 0x01;
currentMode = PPUMode::mode1; currentMode = PPUMode::mode1;
break; break;
case PPUMode::mode2: case PPUMode::mode2:
(*STAT) &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
(*STAT) |= 0x02; addressSpace.memoryLayout.STAT |= 0x02;
currentMode = PPUMode::mode2; currentMode = PPUMode::mode2;
break; break;
case PPUMode::mode3: case PPUMode::mode3:
(*STAT) &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
(*STAT) |= 0x03; addressSpace.memoryLayout.STAT |= 0x03;
currentMode = PPUMode::mode3; currentMode = PPUMode::mode3;
break; break;
} }
//7th bit is unused but always set //7th bit is unused but always set
(*STAT) |= 0x80; addressSpace.memoryLayout.STAT |= 0x80;
} }
void GameBoy::drawLine() { void GameBoy::drawLine() {
const uint8_t line = (*LY); const uint8_t line = readOnlyAddressSpace.memoryLayout.LY;
// Calculate the starting index of the current scanline in the framebuffer // Calculate the starting index of the current scanline in the framebuffer
const uint32_t lineStartIndex = line * RESOLUTION_X; const uint32_t lineStartIndex = line * RESOLUTION_X;
@@ -116,25 +119,27 @@ void GameBoy::drawLine() {
// 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;
if (!(*LCDC & 0x1)) { if (!(readOnlyAddressSpace.memoryLayout.LCDC & 0x1)) {
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off. std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off.
return; return;
} }
const uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800; const uint16_t backgroundMapAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x08) ? 0x9C00 : 0x9800;
const uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800; const uint16_t tileDataTableAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x10) ? 0x8000 : 0x8800;
const bool signedIndex = !(*LCDC & 0x10); const bool signedIndex = !(readOnlyAddressSpace.memoryLayout.LCDC & 0x10);
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
const uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width const uint8_t xPos = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width
const uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height const uint8_t yPos = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256; // 256 pixels in total BG height
const uint16_t tileRow = (yPos / 8) * 32; const uint16_t tileRow = (yPos / 8) * 32;
const uint16_t tileCol = xPos / 8; const uint16_t tileCol = xPos / 8;
const uint16_t tileIndex = tileRow + tileCol; const uint16_t tileIndex = tileRow + tileCol;
const uint16_t tileAddr = backgroundMapAddr + tileIndex; const uint16_t tileAddr = backgroundMapAddr + tileIndex;
const int8_t tileID = signedIndex ? static_cast<int8_t>(addressSpace[tileAddr]) : addressSpace[tileAddr]; const int8_t tileID = signedIndex
? static_cast<int8_t>(readOnlyAddressSpace[tileAddr])
: addressSpace[tileAddr];
uint16_t tileDataAddr; uint16_t tileDataAddr;
if (signedIndex) { if (signedIndex) {
@@ -145,14 +150,14 @@ void GameBoy::drawLine() {
} }
const uint8_t lineOffset = yPos % 8; const uint8_t lineOffset = yPos % 8;
const uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)]; const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1]; const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
const uint8_t colourBit = 7 - (xPos % 8); const uint8_t colourBit = 7 - (xPos % 8);
const 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 = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
if (xPos == 0) if (xPos == 0)
palette = 0x3; palette = 0x3;
switch (palette) { switch (palette) {
@@ -198,7 +203,6 @@ void GameBoy::SDL2present() {
frameTime = SDL_GetTicks() - frameStart; frameTime = SDL_GetTicks() - frameStart;
std::cout << SDL_GetTicks() << " " << frameTime << std::endl;
if (frameDelay > frameTime) { if (frameDelay > frameTime) {
SDL_Delay(frameDelay - frameTime); SDL_Delay(frameDelay - frameTime);

View File

@@ -4,9 +4,7 @@
void GameBoy::timingHandler() { void GameBoy::timingHandler() {
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) { if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ; const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
addressSpace.memoryLayout.DIV += increments;
(*DIV) += increments;
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ; lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
} }
} }