diff --git a/.gitignore b/.gitignore index aa6cf58..77724da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ build/ .idea roms/ -bootrom.bin +dmg_boot.bin diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d32882..ab855ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,12 @@ set(CMAKE_CXX_STANDARD 23) find_package(SDL2 REQUIRED) include_directories(${SDL2_INCLUDE_DIRS}) -add_executable(GBpp src/main.cpp src/gameboy.cpp src/opcode.cpp - src/interupts.cpp src/ppu.cpp) +add_executable(GBpp src/main.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}) \ No newline at end of file diff --git a/src/defines.hpp b/src/defines.hpp index 30e63c2..85b0934 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -23,7 +23,7 @@ #define T_CLOCK_FREQ 4194304 //2^22 -#define DIVIDER_REGISTER_FREQ 16384 +#define DIVIDER_REGISTER_FREQ (4194304/16384) #define BOOTROM_SIZE 0x100 @@ -38,7 +38,6 @@ #define FRAME_DURATION 70224 #define MODE2_DURATION 80 #define MODE3_MIN_DURATION 172 -#define MODE0_3_DURATION 376 //mode3 is 172 to 289, mode0 87 to 204 #define H_SYNC 9198 #define V_SYNC 59.73 diff --git a/src/extendedOpcodeResolver.cpp b/src/extendedOpcodeResolver.cpp new file mode 100644 index 0000000..1c35bf6 --- /dev/null +++ b/src/extendedOpcodeResolver.cpp @@ -0,0 +1,1547 @@ +#include "gameboy.hpp" + +void GameBoy::extendedOpcodeResolver() { + PC += 1; + + switch (addressSpace[PC]) { + case 0x00: + rlc(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x01: + rlc(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x02: + rlc(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x03: + rlc(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x04: + rlc(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x05: + rlc(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x06: + rlc(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x07: + rlc(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x08: + rrc(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x09: + rrc(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x0A: + rrc(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x0B: + rrc(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x0C: + rrc(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x0D: + rrc(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x0E: + rrc(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x0F: + rrc(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x10: + rl(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x11: + rl(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x12: + rl(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x13: + rl(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x14: + rl(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x15: + rl(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x16: + rl(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x17: + rl(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x18: + rr(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x19: + rr(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x1A: + rr(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x1B: + rr(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x1C: + rr(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x1D: + rr(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x1E: + rr(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x1F: + rr(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x20: + sla(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x21: + sla(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x22: + sla(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x23: + sla(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x24: + sla(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x25: + sla(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x26: + sla(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x27: + sla(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x28: + sra(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x29: + sra(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x2A: + sra(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x2B: + sra(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x2C: + sra(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x2D: + sra(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x2E: + sra(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x2F: + sra(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x30: + swap(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x31: + swap(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x32: + swap(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x33: + swap(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x34: + swap(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x35: + swap(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x36: + swap(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x37: + swap(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x38: + srl(BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x39: + srl(BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x3A: + srl(DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x3B: + srl(DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x3C: + srl(HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x3D: + srl(HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x3E: + srl(addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x3F: + srl(AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x40: + bit(0, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x41: + bit(0, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x42: + bit(0, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x43: + bit(0, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x44: + bit(0, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x45: + bit(0, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x46: + bit(0, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x47: + bit(0, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x48: + bit(1, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x49: + bit(1, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x4A: + bit(1, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x4B: + bit(1, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x4C: + bit(1, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x4D: + bit(1, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x4E: + bit(1, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x4F: + bit(1, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x50: + bit(2, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x51: + bit(2, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x52: + bit(2, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x53: + bit(2, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x54: + bit(2, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x55: + bit(2, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x56: + bit(2, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x57: + bit(2, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x58: + bit(3, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x59: + bit(3, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x5A: + bit(3, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x5B: + bit(3, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x5C: + bit(3, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x5D: + bit(3, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x5E: + bit(3, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x5F: + bit(3, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x60: + bit(4, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x61: + bit(4, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x62: + bit(4, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x63: + bit(4, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x64: + bit(4, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x65: + bit(4, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x66: + bit(4, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x67: + bit(4, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x68: + bit(5, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x69: + bit(5, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x6A: + bit(5, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x6B: + bit(5, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x6C: + bit(5, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x6D: + bit(5, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x6E: + bit(5, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x6F: + bit(5, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x70: + bit(6, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x71: + bit(6, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x72: + bit(6, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x73: + bit(6, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x74: + bit(6, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x75: + bit(6, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x76: + bit(6, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x77: + bit(6, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x78: + bit(7, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x79: + bit(7, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x7A: + bit(7, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x7B: + bit(7, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x7C: + bit(7, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x7D: + bit(7, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x7E: + bit(7, addressSpace[HL.reg]); + PC += 1; + addCycles(12); + break; + + case 0x7F: + bit(3, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x80: + res(0, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x81: + res(0, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x82: + res(0, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x83: + res(0, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x84: + res(0, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x85: + res(0, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x86: + res(0, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x87: + res(0, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x88: + res(1, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x89: + res(1, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x8A: + res(1, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x8B: + res(1, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x8C: + res(1, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x8D: + res(1, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x8E: + res(1, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x8F: + res(1, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x90: + res(2, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x91: + res(2, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x92: + res(2, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x93: + res(2, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x94: + res(2, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x95: + res(2, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x96: + res(2, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x97: + res(2, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0x98: + res(3, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0x99: + res(3, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0x9A: + res(3, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0x9B: + res(3, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0x9C: + res(3, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0x9D: + res(3, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0x9E: + res(3, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0x9F: + res(3, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xA0: + res(4, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xA1: + res(4, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xA2: + res(4, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xA3: + res(4, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xA4: + res(4, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xA5: + res(4, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xA6: + res(4, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xA7: + res(4, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xA8: + res(5, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xA9: + res(5, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xAA: + res(5, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xAB: + res(5, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xAC: + res(5, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xAD: + res(5, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xAE: + res(5, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xAF: + res(5, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xB0: + res(6, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xB1: + res(6, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xB2: + res(6, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xB3: + res(6, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xB4: + res(6, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xB5: + res(6, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xB6: + res(6, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xB7: + res(6, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xB8: + res(7, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xB9: + res(7, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xBA: + res(7, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xBB: + res(7, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xBC: + res(7, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xBD: + res(7, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xBE: + res(7, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xBF: + res(7, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xC0: + set(0, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xC1: + set(0, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xC2: + set(0, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xC3: + set(0, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xC4: + set(0, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xC5: + set(0, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xC6: + set(0, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xC7: + set(0, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xC8: + set(1, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xC9: + set(1, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xCA: + set(1, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xCB: + set(1, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xCC: + set(1, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xCD: + set(1, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xCE: + set(1, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xCF: + set(1, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xD0: + set(2, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xD1: + set(2, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xD2: + set(2, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xD3: + set(2, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xD4: + set(2, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xD5: + set(2, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xD6: + set(2, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xD7: + set(2, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xD8: + set(3, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xD9: + set(3, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xDA: + set(3, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xDB: + set(3, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xDC: + set(3, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xDD: + set(3, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xDE: + set(3, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xDF: + set(3, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xE0: + set(4, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xE1: + set(4, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xE2: + set(4, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xE3: + set(4, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xE4: + set(4, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xE5: + set(4, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xE6: + set(4, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xE7: + set(4, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xE8: + set(5, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xE9: + set(5, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xEA: + set(5, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xEB: + set(5, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xEC: + set(5, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xED: + set(5, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xEE: + set(5, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xEF: + set(5, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xF0: + set(6, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xF1: + set(6, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xF2: + set(6, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xF3: + set(6, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xF4: + set(6, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xF5: + set(6, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xF6: + set(6, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xF7: + set(6, AF.hi); + PC += 1; + addCycles(8); + break; + + case 0xF8: + set(7, BC.hi); + PC += 1; + addCycles(8); + break; + + case 0xF9: + set(7, BC.lo); + PC += 1; + addCycles(8); + break; + + case 0xFA: + set(7, DE.hi); + PC += 1; + addCycles(8); + break; + + case 0xFB: + set(7, DE.lo); + PC += 1; + addCycles(8); + break; + + case 0xFC: + set(7, HL.hi); + PC += 1; + addCycles(8); + break; + + case 0xFD: + set(7, HL.lo); + PC += 1; + addCycles(8); + break; + + case 0xFE: + set(7, addressSpace[HL.reg]); + PC += 1; + addCycles(16); + break; + + case 0xFF: + set(7, AF.hi); + PC += 1; + addCycles(8); + break; + + default: + printf("Unsupported extended opcode found: PC:0x%.2x, Opcode:0xcb%.2x\n", PC, addressSpace[PC]); + exit(1); + } +} diff --git a/src/gameboy.cpp b/src/gameboy.cpp index 9c92479..a835277 100644 --- a/src/gameboy.cpp +++ b/src/gameboy.cpp @@ -1,7 +1,7 @@ #include #include "gameboy.hpp" -bool AddressSpace::getBootromState() { +bool AddressSpace::getBootromState() const { return bootromLoaded; } @@ -13,10 +13,9 @@ void AddressSpace::mapBootrom() { bootromLoaded = true; } -void AddressSpace::loadBootrom(std::string filename) { +void AddressSpace::loadBootrom(const std::string& filename) { std::ifstream file; - int size = std::filesystem::file_size(filename); - if (size != 256) { + if (const uintmax_t size = std::filesystem::file_size(filename); size != 256) { std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl; exit(1); } @@ -24,7 +23,7 @@ void AddressSpace::loadBootrom(std::string filename) { file.read(reinterpret_cast(bootrom), BOOTROM_SIZE); } -void AddressSpace::loadGame(std::string filename) { +void AddressSpace::loadGame(const std::string& filename) { game.open(filename, std::ios::binary); if (!game.is_open()) { @@ -34,8 +33,11 @@ void AddressSpace::loadGame(std::string filename) { game.read(reinterpret_cast(memoryLayout.romBank1), ROM_BANK_SIZE * 2); } -void GameBoy::addCycles(uint8_t ticks) { - cycles = (cycles + ticks) % T_CLOCK_FREQ; +void GameBoy::addCycles(const uint8_t ticks) { + cycles += ticks; + if (ppuEnabled) { + ppuCycles += ticks; + } lastOpTicks = ticks; } @@ -43,28 +45,52 @@ void GameBoy::start(std::string bootrom, std::string game) { addressSpace.loadBootrom(bootrom); addressSpace.loadGame(game); + //init some registers that won't otherwise by set + (*JOYP) = 0xCF; + (*SC) = 0x7E; + bool quit = false; - uint32_t cyclesSince = 0; + + bool display = false; while (!quit) { // Event loop: Check and handle SDL events - if (SDL_PollEvent(&event)) { + while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { quit = true; // Set the quit flag when the close button is hit } } - opcodeHandler(); - interruptHandler(); - //timing(); - ppuUpdate(); if (PC > 0xFF && addressSpace.getBootromState()) { addressSpace.unmapBootrom(); } - cyclesSince = cyclesSinceLastRefresh(); - if (cyclesSince > FRAME_DURATION) { - lastRefresh = cycles; - SDL2present(); + ppuEnabled = (*LCDC) & 0x80; + + // if (PC == 0x100) + // 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; } } } diff --git a/src/gameboy.hpp b/src/gameboy.hpp index 2526394..e00d9d0 100644 --- a/src/gameboy.hpp +++ b/src/gameboy.hpp @@ -35,9 +35,8 @@ union RegisterPair { }; class AddressSpace { -private: bool bootromLoaded = true; - Byte bootrom[BOOTROM_SIZE]; + Byte bootrom[BOOTROM_SIZE] = {0}; std::ifstream game; public: @@ -65,50 +64,54 @@ public: Byte specialRam[0x7F]; // Mapped to 0xFF80 Byte interuptEnableReg; // Mapped to 0xFFFF }; - } memoryLayout; + } memoryLayout{}; - bool getBootromState(); void unmapBootrom(); void mapBootrom(); - void loadBootrom(std::string filename); - void loadGame(std::string filename); + bool getBootromState() const; + void loadBootrom(const std::string& filename); + void loadGame(const std::string& filename); //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) - return memoryLayout.echoRam[address - 0xE000]; + return memoryLayout.echoRam[address - 0x2000]; if (address < 0x0100 && bootromLoaded) 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) - return memoryLayout.echoRam[address - 0xE000]; + return memoryLayout.echoRam[address - 0x2000]; if (address < 0x0100 && bootromLoaded) return bootrom[address]; - else - return memoryLayout.memory[address]; + + return memoryLayout.memory[address]; } }; class GameBoy { -private: - uint32_t cycles = 0; - uint32_t lastOpTicks = 0; - uint32_t lastRefresh = 0; - uint32_t lastScanline = 0; - uint32_t cyclesToStayInHblank = -1; + //T-cycles not M-cycles (4 T-cycles = 1 M-cycle) + uint64_t cycles = 0; + //Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s + uint64_t ppuCycles = 2; + bool ppuEnabled = false; + 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 //Accumulator and flags - RegisterPair AF; + RegisterPair AF = {0}; //General purpose CPU registers - RegisterPair BC; - RegisterPair DE; - RegisterPair HL; + RegisterPair BC = {0}; + RegisterPair DE = {0}; + RegisterPair HL = {0}; Word SP = 0xFFFE; //stack pointer Word PC = 0x0000; //program counter @@ -116,70 +119,74 @@ private: AddressSpace addressSpace; //General purpose hardware registers - Byte* JOYP = &addressSpace[0xFF00]; - Byte* SB = &addressSpace[0xFF01]; - Byte* SC = &addressSpace[0xFF02]; - Byte* DIV = &addressSpace[0xFF04]; + Byte* const JOYP = &addressSpace[0xFF00]; + + Byte* const SB = &addressSpace[0xFF01]; + Byte* const SC = &addressSpace[0xFF02]; + Byte* const DIV = &addressSpace[0xFF04]; //Timer registers - Byte* TIMA = &addressSpace[0xFF05]; - Byte* TMA = &addressSpace[0xFF15]; //unused - Byte* TAC = &addressSpace[0xFF16]; + Byte* const TIMA = &addressSpace[0xFF05]; + Byte* const TMA = &addressSpace[0xFF15]; //unused + Byte* const TAC = &addressSpace[0xFF16]; //interrupt flag and enable - Byte* IF = &addressSpace[0xFF0F]; - Byte* IE = &addressSpace[0xFFFF]; + Byte* const IF = &addressSpace[0xFF0F]; + Byte* const IE = &addressSpace[0xFFFF]; //Sound registers - Byte* NR10 = &addressSpace[0xFF10]; - Byte* NR11 = &addressSpace[0xFF11]; - Byte* NR12 = &addressSpace[0xFF12]; - Byte* NR13 = &addressSpace[0xFF13]; - Byte* NR14 = &addressSpace[0xFF14]; - Byte* NR20 = &addressSpace[0xFF15]; //unused - Byte* NR21 = &addressSpace[0xFF16]; - Byte* NR22 = &addressSpace[0xFF17]; - Byte* NR23 = &addressSpace[0xFF18]; - Byte* NR24 = &addressSpace[0xFF19]; - Byte* NR30 = &addressSpace[0xFF1A]; - Byte* NR31 = &addressSpace[0xFF1B]; - Byte* NR32 = &addressSpace[0xFF1C]; - Byte* NR33 = &addressSpace[0xFF1D]; - Byte* NR34 = &addressSpace[0xFF1E]; - Byte* NR40 = &addressSpace[0xFF1F]; //unused - Byte* NR41 = &addressSpace[0xFF20]; - Byte* NR42 = &addressSpace[0xFF21]; - Byte* NR43 = &addressSpace[0xFF22]; - Byte* NR44 = &addressSpace[0xFF23]; - Byte* NR50 = &addressSpace[0xFF24]; - Byte* NR51 = &addressSpace[0xFF25]; - Byte* NR52 = &addressSpace[0xFF26]; - Byte* waveRam = &addressSpace[0xFF30]; //WaveRam[0x10] + 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* LCDC = &addressSpace[0xFF40]; - Byte* STAT = &addressSpace[0xFF41]; - Byte* SCY = &addressSpace[0xFF42]; - Byte* SCX = &addressSpace[0xFF43]; - Byte* LY = &addressSpace[0xFF44]; - Byte* LYC = &addressSpace[0xFF45]; - Byte* DMA = &addressSpace[0xFF46]; - Byte* BGP = &addressSpace[0xFF47]; - Byte* OBP0 = &addressSpace[0xFF48]; - Byte* OBP1 = &addressSpace[0xFF49]; - Byte* WY = &addressSpace[0xFF4A]; - Byte* WX = &addressSpace[0xFF4B]; + 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 currentMode = PPUMode::mode0; //3 colour channels uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP]; - SDL_Window* screen; - SDL_Renderer* renderer; - SDL_Texture* texture; - SDL_Event event; + SDL_Window* screen = nullptr; + SDL_Renderer* renderer = nullptr; + SDL_Texture* texture = nullptr; + 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 ppuUpdate(); void drawLine(); @@ -187,12 +194,14 @@ private: void checkPPUMode(); void setPPUMode(PPUMode mode); - uint32_t cyclesSinceLastScanline(); - uint32_t cyclesSinceLastRefresh(); + uint64_t cyclesSinceLastScanline() const; + uint64_t cyclesSinceLastRefresh() const; + + void timingHandler(); void interruptHandler(); - bool testInterruptEnabled(Byte interrupt); - void resetInterrupt(Byte interrupt); + bool testInterruptEnabled(Byte interrupt) const; + void resetInterrupt(Byte interrupt) const; void VBlankHandle(); void LCDStatHandle(); @@ -220,17 +229,24 @@ private: void andBitwise(T& dest, T src); template void xorBitwise(T& dest, T src); - template - void bit(T testBit, T reg); + void bit(Byte testBit, Byte reg); + void extendedOpcodeResolver(); + static void set(const uint8_t testBit, uint8_t& reg); + static void res(const uint8_t testBit, uint8_t& reg); template void jp(T address); template bool jrNZ(T offset); + template + bool jrNC(T offset); + template + bool jrC(T offset); template void inc(T& reg); template void call(T address); void halt(); + void daa(); void stop(); template void ldW(T dest, T src); @@ -242,29 +258,41 @@ private: bool jrZ(T offset); template void sub(T value); + template + void sbc(T value); template void jr(T OFFSET); template void push(T reg); - template - void rl(T& reg); + void rl(Byte& 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 void pop(T& reg); template - void rla(T& reg); - template void rst(T address); void ret(); template void add(T& reg, T value); + template + void adc(T& reg, T value); void cpl(); + void scf(); void ccf(); void swap(Byte& value); public: void start(std::string bootrom, std::string game); void SDL2setup(); - void SDL2destroy(); + void SDL2destroy() const; }; #endif //GBPP_SRC_GAMEBOY_HPP_ diff --git a/src/interupts.cpp b/src/interupts.cpp index 2b1a2f1..5b1b33f 100644 --- a/src/interupts.cpp +++ b/src/interupts.cpp @@ -1,32 +1,33 @@ #include "defines.hpp" #include "gameboy.hpp" -bool GameBoy::testInterruptEnabled(Byte interrupt) { - return (*IE) & (Byte)(1 << interrupt); +bool GameBoy::testInterruptEnabled(const Byte interrupt) const { + return (*IE) & static_cast(1 << interrupt); } -void GameBoy::resetInterrupt(Byte interrupt) { +void GameBoy::resetInterrupt(const Byte interrupt) const { *IF &= ~(1 << interrupt); + *IF |= 0xE0; } void GameBoy::interruptHandler() { if (!IME) return; - if (*IF & (Byte)(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT)) + if (*IF & static_cast(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT)) VBlankHandle(); - if (*IF & (Byte)(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT)) + if (*IF & static_cast(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT)) LCDStatHandle(); - if (*IF & (Byte)(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT)) + if (*IF & static_cast(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT)) timerHandle(); - if (*IF & (Byte)(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT)) + if (*IF & static_cast(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT)) serialHandle(); - if (*IF & (Byte)(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT)) + if (*IF & static_cast(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT)) joypadHandle(); } void GameBoy::VBlankHandle() { - printf("VBlank interrupt"); + //printf("VBlank interrupt\n"); IME = 0; push(PC); PC = 0x40; @@ -34,7 +35,7 @@ void GameBoy::VBlankHandle() { } void GameBoy::LCDStatHandle() { - printf("LCD stat interrupt"); + //printf("LCD stat interrupt\n"); IME = 0; push(PC); addCycles(16); @@ -43,7 +44,7 @@ void GameBoy::LCDStatHandle() { } void GameBoy::timerHandle() { - printf("timer interrupt"); + //printf("timer interrupt\n"); IME = 0; push(PC); addCycles(16); @@ -52,7 +53,7 @@ void GameBoy::timerHandle() { } void GameBoy::serialHandle() { - printf("serial interrupt"); + //printf("serial interrupt\n"); IME = 0; push(PC); addCycles(16); @@ -61,7 +62,7 @@ void GameBoy::serialHandle() { } void GameBoy::joypadHandle() { - printf("joypad interrupt"); + printf("joypad interrupt\n"); IME = 0; push(PC); addCycles(16); diff --git a/src/main.cpp b/src/main.cpp index b1a90de..a1919b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,8 +4,9 @@ int main(int argc, char** argv) { auto* gb = new GameBoy(); gb->SDL2setup(); - gb->start("../bootrom.bin", "../roms/DrMario.gb"); + gb->start("../dmg_boot.bin", "../roms/cpu_instrs.gb"); gb->SDL2destroy(); delete gb; + return 0; } diff --git a/src/opcode.cpp b/src/opcodeResolver.cpp similarity index 69% rename from src/opcode.cpp rename to src/opcodeResolver.cpp index eeaa683..7f131c8 100644 --- a/src/opcode.cpp +++ b/src/opcodeResolver.cpp @@ -1,19 +1,19 @@ #include "gameboy.hpp" -void GameBoy::setFlag(Byte bit) { +void GameBoy::setFlag(const Byte bit) { AF.lo |= (1 << bit); } -void GameBoy::resetFlag(Byte bit) { +void GameBoy::resetFlag(const Byte bit) { AF.lo &= ~(1 << bit); } -bool GameBoy::getFlag(Byte bit) const { +bool GameBoy::getFlag(const Byte bit) const { return (AF.lo >> bit) & 1; } Word GameBoy::getWordPC() { - RegisterPair word; + RegisterPair word = {0}; //remember little endianness word.lo = addressSpace[PC + 1]; @@ -27,7 +27,7 @@ Byte GameBoy::getBytePC() { } Word GameBoy::getWordSP() { - RegisterPair word; + RegisterPair word = {0}; //remember little endianness word.lo = addressSpace[SP++]; @@ -46,46 +46,25 @@ void GameBoy::ret() { template void GameBoy::ld(T& dest, T src) { - dest = src; + if constexpr (std::is_same_v) { + if (&dest == DIV) + *DIV = 0x00; + lastDivUpdate = cycles; + } + else { + dest = src; + } } + template void GameBoy::ldW(T dest, T src) { if (sizeof(src) == sizeof(Word)) { - addressSpace[dest] = (Byte)(src & 0xFF00) >> 8; - addressSpace[dest + 1] = (Byte)(src & 0xFF); + addressSpace[dest] = static_cast(src & 0xFF00) >> 8; + addressSpace[dest + 1] = static_cast(src & 0xFF); } } -template -void GameBoy::rla(T& reg) { - //printf("0x%.2x\n", REG); - //printf("%d\n", GET_FLAG(CARRY_FLAG)); - bool carry; - - //printf("\n0x%x\n", REG); - //printf("0x%x\n\n", REG & ((T)1 << 7)); - - if (reg & (1 << 7)) - carry = true; - else - carry = false; - - reg <<= 1; - - if (getFlag(CARRY_FLAG)) - reg += 1; - - if (carry) - setFlag(CARRY_FLAG); - else - resetFlag(CARRY_FLAG); - - resetFlag(ZERO_FLAG); - resetFlag(SUBTRACT_FLAG); - resetFlag(HALFCARRY_FLAG); -} - template void GameBoy::add(T& reg, T value) { if (sizeof(reg) == sizeof(Byte)) { @@ -124,6 +103,125 @@ void GameBoy::add(T& reg, T value) { resetFlag(SUBTRACT_FLAG); } +template +void GameBoy::adc(T& reg, T value) { + T carry = getFlag(CARRY_FLAG) ? 1 : 0; + + if (sizeof(reg) == sizeof(Byte)) { + //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10) + setFlag(HALFCARRY_FLAG); + else + resetFlag(HALFCARRY_FLAG); + //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((((value & 0xFF) + (reg & 0xFF)) & 0x100) == 0x100) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + } + + if (sizeof(reg) == sizeof(Word)) { + //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((((value & 0xFFF) + (reg & 0xFFF)) & 0x1000) == 0x1000) + setFlag(HALFCARRY_FLAG); + else + resetFlag(HALFCARRY_FLAG); + //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((((value & 0xFFFF) + (reg & 0xFFFF)) & 0x10000) == 0x10000) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + } + + reg += value + carry; + + if (reg == 0) + setFlag(ZERO_FLAG); + else + resetFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); +} + +template +void GameBoy::sub(T value) { + if (AF.hi < value) { + setFlag(CARRY_FLAG); + resetFlag(ZERO_FLAG); + } + else if (AF.hi == value) { + setFlag(ZERO_FLAG); + resetFlag(CARRY_FLAG); + } + + AF.hi -= value; + + setFlag(SUBTRACT_FLAG); + //halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/ + if (0 > (((AF.hi) & 0xf) - (value & 0xf))) + setFlag(HALFCARRY_FLAG); + else + resetFlag(HALFCARRY_FLAG); +} + +template +void GameBoy::sbc(T value) { + T carry = getFlag(CARRY_FLAG) ? 1 : 0; + T result = AF.hi - value - carry; + + if (AF.hi < value + carry) { + setFlag(CARRY_FLAG); + } + else { + resetFlag(CARRY_FLAG); + } + + if (result == 0) { + setFlag(ZERO_FLAG); + } + else { + resetFlag(ZERO_FLAG); + } + + setFlag(SUBTRACT_FLAG); + + if ((AF.hi & 0xF) < (value & 0xF) + carry) { + setFlag(HALFCARRY_FLAG); + } + else { + resetFlag(HALFCARRY_FLAG); + } + + AF.hi = result; +} + +//https://gbdev.gg8.se/wiki/articles/DAA +void GameBoy::daa() { + if (getFlag(SUBTRACT_FLAG)) { + if (getFlag(CARRY_FLAG)) { + AF.hi -= 0x60; + } + if (getFlag(HALFCARRY_FLAG)) { + AF.hi -= 0x06; + } + } + else { + if (getFlag(CARRY_FLAG) || (AF.hi & 0xFF) > 0x99) { + AF.hi += 0x60; + setFlag(CARRY_FLAG); + } + if (getFlag(HALFCARRY_FLAG) || (AF.hi & 0x0F) > 0x09) { + AF.hi += 0x06; + } + } + + if (AF.hi == 0) + setFlag(ZERO_FLAG); + else + resetFlag(ZERO_FLAG); + resetFlag(HALFCARRY_FLAG); +} + template void GameBoy::orBitwise(T& dest, T src) { dest |= src; @@ -160,11 +258,8 @@ void GameBoy::xorBitwise(T& dest, T src) { resetFlag(HALFCARRY_FLAG); } -template -void GameBoy::bit(T testBit, T reg) { - Byte result = reg & (T)(1 << testBit); - - if (result == 0) +void GameBoy::bit(Byte testBit, Byte reg) { + if (const Byte result = reg & (1 << testBit); result == 0) setFlag(ZERO_FLAG); else resetFlag(ZERO_FLAG); @@ -173,12 +268,62 @@ void GameBoy::bit(T testBit, T reg) { setFlag(HALFCARRY_FLAG); } +void GameBoy::set(const Byte testBit, Byte& reg) { + reg |= (1 << testBit); +} + +void GameBoy::res(const Byte testBit, Byte& reg) { + reg &= ~(1 << testBit); +} + + +template +void GameBoy::jr(T offset) { + PC += static_cast(offset) + 2; //PC moves 2 from original instruction +} + template bool GameBoy::jrNZ(T offset) { bool jumped = false; if (!getFlag(ZERO_FLAG)) //if not set { - PC += (int8_t)offset + 2; //PC moves 2 from the original instruction + PC += static_cast(offset) + 2; //PC moves 2 from the original instruction + jumped = true; + } + + return jumped; +} + +template +bool GameBoy::jrZ(T offset) { + bool jumped = false; + if (getFlag(ZERO_FLAG)) //if not set + { + PC += static_cast(offset) + 2; //PC moves 2 from the original instruction + jumped = true; + } + + return jumped; +} + +template +bool GameBoy::jrNC(T offset) { + bool jumped = false; + if (!getFlag(CARRY_FLAG)) //if not set + { + PC += static_cast(offset) + 2; //PC moves 2 from the original instruction + jumped = true; + } + + return jumped; +} + +template +bool GameBoy::jrC(T offset) { + bool jumped = false; + if (getFlag(CARRY_FLAG)) //if not set + { + PC += static_cast(offset) + 2; //PC moves 2 from the original instruction jumped = true; } @@ -187,7 +332,7 @@ bool GameBoy::jrNZ(T offset) { template void GameBoy::inc(T& reg) { - reg++; + reg += 1; if (sizeof(reg) == sizeof(Byte)) { if (reg == 0) @@ -198,7 +343,7 @@ void GameBoy::inc(T& reg) { resetFlag(SUBTRACT_FLAG); //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ - if (((((reg - 1) & 0xf) + (reg & 0xf)) & 0x10) == 0x10) + if ((reg & 0x0F) == 0) setFlag(HALFCARRY_FLAG); else resetFlag(HALFCARRY_FLAG); @@ -252,22 +397,10 @@ void GameBoy::dec(T& reg) { } } -template -bool GameBoy::jrZ(T offset) { - bool jumped = false; - if (getFlag(ZERO_FLAG)) //if not set - { - PC += (int8_t)offset + 2; //PC moves 2 from the original instruction - jumped = true; - } - - return jumped; -} - void GameBoy::swap(Byte& value) { // Extract the lower and upper nibbles of the register - Byte lowerNibble = value & 0x0F; - Byte upperNibble = (value >> 4) & 0x0F; + const Byte lowerNibble = value & 0x0F; + const Byte upperNibble = (value >> 4) & 0x0F; // Swap the lower and upper nibbles value = (lowerNibble << 4) | upperNibble; @@ -283,53 +416,213 @@ void GameBoy::swap(Byte& value) { void GameBoy::halt() {} -template -void GameBoy::sub(T value) { - if (AF.hi < value) { +void GameBoy::rrc(Byte& reg) { + const Byte lsb = reg & 0x01; + reg >>= 1; + + if (lsb) + reg |= 0x80; + + if (lsb) setFlag(CARRY_FLAG); - resetFlag(ZERO_FLAG); - } - else if (AF.hi == value) { - setFlag(ZERO_FLAG); + else resetFlag(CARRY_FLAG); - } - AF.hi -= value; - - setFlag(SUBTRACT_FLAG); - //halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/ - if (0 > (((AF.hi) & 0xf) - (value & 0xf))) - setFlag(HALFCARRY_FLAG); + if (reg) + resetFlag(ZERO_FLAG); else - resetFlag(HALFCARRY_FLAG); + setFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); } -template -void GameBoy::jr(T offset) { - PC += (int8_t)offset + 2; //PC moves 2 from original instruction +void GameBoy::rrca() { + const Byte lsb = AF.hi & 0x01; + AF.hi >>= 1; + + if (lsb) + AF.hi |= 0x80; + + if (lsb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + resetFlag(ZERO_FLAG); + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); } -template -void GameBoy::rl(T& reg) { - bool carry; +void GameBoy::rra() { + const Byte lsb = AF.hi & 0x01; + AF.hi >>= 1; - if (reg & (1 << 7)) - carry = true; + if (getFlag(CARRY_FLAG)) + AF.hi |= 0x80; + + if (lsb) + setFlag(CARRY_FLAG); else - carry = false; + resetFlag(CARRY_FLAG); + + resetFlag(ZERO_FLAG); + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::rr(Byte& reg) { + const Byte lsb = reg & 0x01; + + reg >>= 1; + + if (getFlag(CARRY_FLAG)) + AF.hi |= 0x80; + + if (lsb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + if (reg) + resetFlag(ZERO_FLAG); + else + setFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::rlc(Byte& reg) { + const Byte msb = (reg & 0x80) >> 7; + reg <<= 1; + + reg |= msb; + + if (msb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + if (reg) + resetFlag(ZERO_FLAG); + else + setFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::rlca() { + const Byte msb = (AF.hi & 0x80) >> 7; + AF.hi <<= 1; + + AF.hi |= msb; + + if (msb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + resetFlag(ZERO_FLAG); + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::rla() { + const Byte msb = (AF.hi & 0x80) >> 7; + AF.hi <<= 1; + + if (getFlag(CARRY_FLAG)) + AF.hi |= 0x01; + + if (msb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + resetFlag(ZERO_FLAG); + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::rl(Byte& reg) { + const Byte msb = (reg & 0x80) >> 7; reg <<= 1; if (getFlag(CARRY_FLAG)) - reg += 1; + reg |= 1; - if (carry) + if (msb) setFlag(CARRY_FLAG); - else resetFlag(CARRY_FLAG); - if (reg == 0) + if (reg) + resetFlag(ZERO_FLAG); + else + setFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::sla(Byte& reg) { + const Byte msb = (reg & 0x80) >> 7; + + reg <<= 1; + + if (msb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + if (reg) + resetFlag(ZERO_FLAG); + else + setFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::sra(Byte& reg) { + const Byte msb = (reg & 0x80) >> 7; + const Byte lsb = reg & 0x1; + + reg >>= 1; + + if (msb) + reg |= 0x80; + + if (lsb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + if (reg) + resetFlag(ZERO_FLAG); + else + setFlag(ZERO_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); +} + +void GameBoy::srl(Byte& reg) { + const Byte lsb = reg & 0x1; + + reg >>= 1; + + if (lsb) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + if (reg) + resetFlag(ZERO_FLAG); + else setFlag(ZERO_FLAG); resetFlag(SUBTRACT_FLAG); @@ -344,7 +637,7 @@ void GameBoy::pop(T& reg) { template void GameBoy::push(T reg) { //little endian - RegisterPair temp; + RegisterPair temp = {0}; temp.lo = reg & 0xFF; temp.hi = reg >> 8; SP--; @@ -370,30 +663,27 @@ void GameBoy::cpl() { setFlag(HALFCARRY_FLAG); } -void GameBoy::ccf() { +void GameBoy::scf() { resetFlag(SUBTRACT_FLAG); resetFlag(HALFCARRY_FLAG); + setFlag(CARRY_FLAG); +} + +void GameBoy::ccf() { if (getFlag(CARRY_FLAG)) resetFlag(CARRY_FLAG); else setFlag(CARRY_FLAG); + + resetFlag(SUBTRACT_FLAG); + resetFlag(HALFCARRY_FLAG); } void GameBoy::stop() {} -void GameBoy::opcodeHandler() { - bool jumped; - +void GameBoy::opcodeResolver() { if (addressSpace[PC] != 0xCB) { - //printf("PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); - if (PC == 0x100) { - printf("LY:0x%.2x\n", (*LY)); - exit(1); - // 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)); - + bool jumped; switch (addressSpace[PC]) { case 0x00: //NOP @@ -437,6 +727,12 @@ void GameBoy::opcodeHandler() { addCycles(8); break; + case 0x07: + rlca(); + PC += 1; + addCycles(4); + break; + case 0x08: ldW(getWordPC(), SP); PC += 3; @@ -479,6 +775,12 @@ void GameBoy::opcodeHandler() { addCycles(8); break; + case 0x0F: + rrca(); + PC += 1; + addCycles(4); + break; + case 0x10: stop(); PC += 2; @@ -522,15 +824,14 @@ void GameBoy::opcodeHandler() { break; case 0x17: - rla(AF.hi); + rla(); PC += 1; addCycles(4); break; case 0x18: jr(getBytePC()); - PC += 1; - addCycles(4); + addCycles(12); break; case 0x19: @@ -569,6 +870,12 @@ void GameBoy::opcodeHandler() { addCycles(8); break; + case 0x1F: + rra(); + PC += 1; + addCycles(4); + break; + case 0x20: jumped = jrNZ(getBytePC()); if (jumped) { @@ -611,6 +918,18 @@ void GameBoy::opcodeHandler() { addCycles(4); break; + case 0x26: + ld(HL.hi, getBytePC()); + PC += 2; + addCycles(8); + break; + + case 0x27: + daa(); + PC += 1; + addCycles(4); + break; + case 0x28: jumped = jrZ(getBytePC()); if (jumped) { @@ -622,6 +941,12 @@ void GameBoy::opcodeHandler() { } break; + case 0x29: + add(HL.reg, HL.reg); + PC += 1; + addCycles(8); + break; + case 0x2A: ld(AF.hi, addressSpace[HL.reg]); HL.reg += 1; @@ -629,6 +954,24 @@ void GameBoy::opcodeHandler() { addCycles(8); break; + case 0x2B: + dec(HL.reg); + PC += 1; + addCycles(8); + break; + + case 0x2C: + inc(HL.lo); + PC += 1; + addCycles(4); + break; + + case 0x2D: + dec(HL.hi); + PC += 1; + addCycles(4); + break; + case 0x2E: ld(HL.lo, getBytePC()); PC += 2; @@ -641,6 +984,17 @@ void GameBoy::opcodeHandler() { addCycles(4); break; + case 0x30: + jumped = jrNC(getBytePC()); + if (jumped) { + addCycles(12); + } + else { + PC += 2; + addCycles(8); + } + break; + case 0x31: ld(SP, getWordPC()); PC += 3; @@ -678,6 +1032,23 @@ void GameBoy::opcodeHandler() { addCycles(12); break; + case 0x37: + scf(); + PC += 1; + addCycles(4); + break; + + case 0x38: + jumped = jrC(getBytePC()); + if (jumped) { + addCycles(12); + } + else { + PC += 2; + addCycles(8); + } + break; + case 0x39: add(HL.reg, SP); PC += 1; @@ -1153,6 +1524,54 @@ void GameBoy::opcodeHandler() { addCycles(4); break; + case 0x88: + adc(AF.hi, BC.hi); + PC += 1; + addCycles(4); + break; + + case 0x89: + adc(AF.hi, BC.lo); + PC += 1; + addCycles(4); + break; + + case 0x8A: + adc(AF.hi, DE.hi); + PC += 1; + addCycles(4); + break; + + case 0x8B: + adc(AF.hi, DE.lo); + PC += 1; + addCycles(4); + break; + + case 0x8C: + adc(AF.hi, HL.hi); + PC += 1; + addCycles(4); + break; + + case 0x8D: + adc(AF.hi, HL.lo); + PC += 1; + addCycles(4); + break; + + case 0x8E: + adc(AF.hi, addressSpace[HL.reg]); + PC += 1; + addCycles(8); + break; + + case 0x8F: + adc(AF.hi, AF.hi); + PC += 1; + addCycles(4); + break; + case 0x90: sub(BC.hi); PC += 1; @@ -1201,6 +1620,54 @@ void GameBoy::opcodeHandler() { addCycles(4); break; + case 0x98: + sbc(BC.hi); + PC += 1; + addCycles(4); + break; + + case 0x99: + sbc(BC.lo); + PC += 1; + addCycles(4); + break; + + case 0x9A: + sbc(DE.hi); + PC += 1; + addCycles(4); + break; + + case 0x9B: + sbc(DE.lo); + PC += 1; + addCycles(4); + break; + + case 0x9C: + sbc(HL.hi); + PC += 1; + addCycles(4); + break; + + case 0x9D: + sbc(HL.lo); + PC += 1; + addCycles(4); + break; + + case 0x9E: + sbc(addressSpace[HL.reg]); + PC += 1; + addCycles(8); + break; + + case 0x9F: + sbc(AF.hi); + PC += 1; + addCycles(4); + break; + case 0xA0: andBitwise(AF.hi, BC.hi); PC += 1; @@ -1443,6 +1910,17 @@ void GameBoy::opcodeHandler() { addCycles(16); break; + case 0xC6: + add(AF.hi, getBytePC()); + PC += 2; + addCycles(8); + break; + + case 0xC7: + rst(0x0000); + addCycles(16); + break; + case 0xC8: if (getFlag(ZERO_FLAG)) { ret(); @@ -1483,6 +1961,12 @@ void GameBoy::opcodeHandler() { case 0xCD: call(getWordPC()); + addCycles(24); + break; + + case 0xCE: + adc(AF.hi, getBytePC()); + PC += 2; addCycles(8); break; @@ -1536,6 +2020,17 @@ void GameBoy::opcodeHandler() { addCycles(16); break; + case 0xD6: + sub(getBytePC()); + PC += 2; + addCycles(8); + break; + + case 0xD7: + rst(0x0010); + addCycles(16); + break; + case 0xD8: if (getFlag(CARRY_FLAG)) { ret(); @@ -1576,6 +2071,12 @@ void GameBoy::opcodeHandler() { } break; + case 0xDE: + sbc(getBytePC()); + PC += 2; + addCycles(8); + break; + case 0xDF: rst(0x18); addCycles(16); @@ -1584,7 +2085,7 @@ void GameBoy::opcodeHandler() { case 0xE0: ld(addressSpace[0xFF00 + getBytePC()], AF.hi); PC += 2; - addCycles(8); + addCycles(12); break; case 0xE1: @@ -1611,11 +2112,35 @@ void GameBoy::opcodeHandler() { addCycles(8); break; - // case 0xE8: - // SP += (int8_t)getByte(); - // PC += 2; - // addCycles(16); - // break; + case 0xE7: + rst(0x0020); + addCycles(16); + break; + + case 0xE8: + { + const auto immediate = static_cast(getBytePC()); + const Word result = SP + static_cast(immediate); + + if (((SP ^ immediate ^ result) & 0x10) != 0) + setFlag(HALFCARRY_FLAG); + else + resetFlag(HALFCARRY_FLAG); + + if (((SP ^ immediate ^ result) & 0x100) != 0) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + + SP = result; + + resetFlag(ZERO_FLAG); + resetFlag(SUBTRACT_FLAG); + + PC += 2; + addCycles(16); + } + break; case 0xE9: jp(HL.reg); @@ -1628,6 +2153,12 @@ void GameBoy::opcodeHandler() { addCycles(16); break; + case 0xEE: + xorBitwise(AF.hi, getBytePC()); + PC += 2; + addCycles(8); + break; + case 0xEF: rst(0x28); addCycles(16); @@ -1645,6 +2176,12 @@ void GameBoy::opcodeHandler() { addCycles(12); break; + case 0xF2: + ld(AF.hi, addressSpace[0xFF00 + BC.lo]); + PC += 1; + addCycles(12); + break; + case 0xF3: IME = 0; PC += 1; @@ -1657,6 +2194,48 @@ void GameBoy::opcodeHandler() { addCycles(16); break; + case 0xF6: + orBitwise(AF.hi, getBytePC()); + PC += 2; + addCycles(8); + break; + + case 0xF7: + rst(0x0030); + addCycles(16); + break; + + case 0xF8: + { + const auto n = static_cast(getBytePC()); + const Word result = SP + n; + + //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((((result & 0xF) + (HL.reg & 0xF)) & 0x10) == 0x10) + setFlag(HALFCARRY_FLAG); + else + resetFlag(HALFCARRY_FLAG); + //halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((((result & 0xFF) + (HL.reg & 0xFF)) & 0x100) == 0x100) + setFlag(CARRY_FLAG); + else + resetFlag(CARRY_FLAG); + HL.reg = result; // Load the result into HL + + resetFlag(ZERO_FLAG); + resetFlag(SUBTRACT_FLAG); + + PC += 2; + addCycles(12); + } + break; + + case 0xF9: + ld(SP, HL.reg); + PC += 1; + addCycles(8); + break; + case 0xFA: ldW(AF.hi, addressSpace[getWordPC()]); PC += 3; @@ -1664,7 +2243,7 @@ void GameBoy::opcodeHandler() { break; //should not actually enable until the next opcode - //EI 0xFB then DI 0xF3 does not allow interrupts to happen + //EI (0xFB) then DI (0xF3) does not allow interrupts to happen case 0xFB: IME = 1; PC += 1; @@ -1683,81 +2262,10 @@ void GameBoy::opcodeHandler() { break; default: - printf("Unimplemented opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); - exit(1); - } - } - else //extension - { - //printf("PC:0x%.2x, Opcode:0x%x%.2x\n", PC, addressSpace[PC], addressSpace[PC + 1]); - PC += 1; - addCycles(4); - - //extension handler - switch (addressSpace[PC]) { - case 0x11: - rl(BC.lo); - PC += 1; - addCycles(8); - break; - - case 0x30: - swap(BC.hi); - PC += 1; - addCycles(8); - break; - - case 0x31: - swap(BC.lo); - PC += 1; - addCycles(8); - break; - - case 0x32: - swap(DE.hi); - PC += 1; - addCycles(8); - break; - - case 0x33: - swap(DE.lo); - PC += 1; - addCycles(8); - break; - - case 0x34: - swap(HL.hi); - PC += 1; - addCycles(8); - break; - - case 0x35: - swap(HL.lo); - PC += 1; - addCycles(8); - break; - - case 0x36: - swap(addressSpace[HL.reg]); - PC += 1; - addCycles(16); - break; - - case 0x37: - swap(AF.hi); - PC += 1; - addCycles(8); - break; - - case 0x7C: - bit((Byte)7, HL.hi); - PC += 1; - addCycles(8); - break; - - default: - printf("Unimplemented extended opcode found: PC:0x%.2x, Opcode:0xcb%.2x\n", PC, addressSpace[PC]); + printf("Unsupported opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); exit(1); } } + else + extendedOpcodeResolver(); } diff --git a/src/ppu.cpp b/src/ppu.cpp index 1622ddc..eafbcf8 100644 --- a/src/ppu.cpp +++ b/src/ppu.cpp @@ -2,52 +2,12 @@ #include "defines.hpp" #include #include +#include void GameBoy::ppuUpdate() { //test for HBlank 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)) { // Request STAT interrupt if LY matches LYC // 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() { (*LY)++; - if ((*LY) > 153) + setPPUMode(PPUMode::mode2); + if ((*LY) > SCANLINES_PER_FRAME - 1) { (*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 + SDL2present(); 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) { case PPUMode::mode0: (*STAT) &= ~0x03; + currentMode = PPUMode::mode0; break; case PPUMode::mode1: (*STAT) &= ~0x03; (*STAT) |= 0x01; + currentMode = PPUMode::mode1; break; case PPUMode::mode2: (*STAT) &= ~0x03; (*STAT) |= 0x02; + currentMode = PPUMode::mode2; break; case PPUMode::mode3: (*STAT) &= ~0x03; (*STAT) |= 0x03; + currentMode = PPUMode::mode3; break; } //7th bit is unused but always set @@ -127,10 +108,10 @@ void GameBoy::setPPUMode(PPUMode mode) { } void GameBoy::drawLine() { - uint8_t line = (*LY); + const uint8_t line = (*LY); // 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 uint32_t* currentLinePixels = framebuffer + lineStartIndex; @@ -140,20 +121,20 @@ void GameBoy::drawLine() { return; } - uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800; - uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800; - bool signedIndex = !(*LCDC & 0x10); + const uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800; + const uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800; + const bool signedIndex = !(*LCDC & 0x10); for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { - 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 xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width + const uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height - uint16_t tileRow = (yPos / 8) * 32; - uint16_t tileCol = xPos / 8; - uint16_t tileIndex = tileRow + tileCol; + const uint16_t tileRow = (yPos / 8) * 32; + const uint16_t tileCol = xPos / 8; + const uint16_t tileIndex = tileRow + tileCol; - uint16_t tileAddr = backgroundMapAddr + tileIndex; - int8_t tileID = signedIndex ? static_cast(addressSpace[tileAddr]) : addressSpace[tileAddr]; + const uint16_t tileAddr = backgroundMapAddr + tileIndex; + const int8_t tileID = signedIndex ? static_cast(addressSpace[tileAddr]) : addressSpace[tileAddr]; uint16_t tileDataAddr; if (signedIndex) { @@ -163,12 +144,12 @@ void GameBoy::drawLine() { tileDataAddr = tileDataTableAddr + (tileID * 16); } - uint8_t lineOffset = yPos % 8; - uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)]; - uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1]; + const uint8_t lineOffset = yPos % 8; + const uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)]; + const uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1]; - uint8_t colourBit = 7 - (xPos % 8); - uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1); + const uint8_t colourBit = 7 - (xPos % 8); + const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1); // Apply the BGP register for palette mapping uint8_t palette = (*BGP >> (colourNum * 2)) & 0x3; @@ -196,14 +177,14 @@ void GameBoy::SDL2setup() { SDL_WINDOW_OPENGL); // 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 texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y); } -void GameBoy::SDL2destroy() { +void GameBoy::SDL2destroy() const { SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(screen); @@ -211,13 +192,18 @@ void GameBoy::SDL2destroy() { } void GameBoy::SDL2present() { - // Update the SDL texture with the framebuffer data - SDL_UpdateTexture(texture, NULL, framebuffer, RESOLUTION_X * sizeof(uint32_t)); - - // Clear the renderer and render the texture + SDL_UpdateTexture(texture, nullptr, framebuffer, RESOLUTION_X * sizeof(uint32_t)); 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); } diff --git a/src/timing.cpp b/src/timing.cpp new file mode 100644 index 0000000..5b8d5d4 --- /dev/null +++ b/src/timing.cpp @@ -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; + } +}