diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 332f3bc..3e6d7b5 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -14,6 +14,12 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive + + - name: install_dependencies + run: | + sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" + sudo apt-get update -y -qq + sudo apt-get install libsdl2-dev - name: build run: | diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8da94ae..7784932 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES cpu.cpp gameBoy.cpp mmap.cpp + graphics.cpp # ------- # Header Files @@ -11,6 +12,7 @@ set(SOURCES gameBoy.h mmap.h types.h + graphics.h ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/src/cpu.cpp b/src/cpu.cpp index cb483b7..d2a6872 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -58,9 +58,6 @@ CPU::CPU() // Set isHalted to false isHalted = false; - // The debug logging file - outfile = fopen("logfile.txt", "w"); - // TODO: check the initial state of IME IMEFlag = -1; @@ -3971,20 +3968,6 @@ int CPU::executeInstruction(Byte opcode) int CPU::executeNextInstruction() { - // Check if boot execution is complete - // If yes, we can do logging in debug log outfile - if (mMap->readMemory(0xFF50) == 0x01) - { - dumpState(); - } - - // Turn off logging - // If reached infinite loop - if (reg_PC.dat == 0xCC62) - { - fclose(outfile); - } - // Get the opcode Byte opcode = (*mMap)[reg_PC.dat]; return (this->*method_pointer[opcode])(); @@ -7789,11 +7772,6 @@ int CPU::SET_7_A() return 4; } -void CPU::dumpState() -{ - //fprintf(outfile, "A:%02X F:%02X B:%02X C:%02X D:%02X E:%02X H:%02X L:%02X SP:%04X PC:%04X PCMEM:%02X,%02X,%02X,%02X\n", reg_AF.hi, reg_AF.lo, reg_BC.hi, reg_BC.lo, reg_DE.hi, reg_DE.lo, reg_HL.hi, reg_HL.lo, reg_SP.dat, reg_PC.dat, (*mMap)[reg_PC.dat], (*mMap)[reg_PC.dat + 1], (*mMap)[reg_PC.dat + 2], (*mMap)[reg_PC.dat + 3]); -} - // Checks for interrupts and services them if needed // Behaviour source: https://gbdev.io/pandocs/Interrupts.html int CPU::performInterrupt() diff --git a/src/cpu.h b/src/cpu.h index 7538054..b7f3e48 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -1,6 +1,7 @@ #pragma once #include "types.h" #include "mmap.h" +#include "graphics.h" // CPU Register // Pulled from https://gbdev.io/pandocs/CPU_Registers_and_Flags.html @@ -104,6 +105,8 @@ class CPU // Memory Map MemoryMap* mMap; + PPU* ppu; + // ISA // Pulled from https://izik1.github.io/gbops/index.html typedef int (CPU::*method_function)(); @@ -1129,12 +1132,6 @@ class CPU int SET_7_HLp(); int SET_7_A(); - // Dump CPU state in logfile - // Useful for debugging - void dumpState(); - - FILE* outfile; - public: const int clockSpeed = 4194304; // 4.194304 MHz CPU const int clockSpeedPerFrame = 70224; // 4194304 / 59.73fps @@ -1144,6 +1141,9 @@ class CPU // set the memory map void setMemory(MemoryMap* memory) { mMap = memory; } + // set the PPU + void setPPU(PPU* ppu_arg) { ppu = ppu_arg; } + // set the Accumulator void set_reg_A(Byte value) { reg_AF.hi = value; } diff --git a/src/gameBoy.cpp b/src/gameBoy.cpp index b3e52c7..3041768 100644 --- a/src/gameBoy.cpp +++ b/src/gameBoy.cpp @@ -12,38 +12,39 @@ GBE::GBE() // Initialize the MemoryMap gbe_mMap = new MemoryMap(); + // Initialize the Graphics + gbe_graphics = new PPU(); + // Unify the CPU and MemoryMap gbe_cpu->setMemory(gbe_mMap); + // Unify the CPU and PPU + gbe_cpu->setPPU(gbe_graphics); + + // Unify the PPU and MmeoryMap + gbe_graphics->setMemoryMap(gbe_mMap); + + gbe_graphics->init(); + // Open the Boot ROM if ((bootROM = fopen("../src/dmg_boot.gb", "rb")) == NULL) printf("boot rom file not opened"); // Open the Game ROM - if ((gameROM = fopen("../tests/instr_timing.gb", "rb")) == NULL) + if ((gameROM = fopen("../tests/pkmnred.gb", "rb")) == NULL) printf("game rom file not opened"); - // Load the Boot ROM - // Into the first 0x100 bytes - fread(gbe_mMap->getRomBank0(), 1, 256, bootROM); + // Set the Boot ROM + gbe_mMap->setBootRomFile(bootROM); - // Load Game ROM in Bank 0 - // After offsetting for Boot ROM first - fseek(gameROM, 0x100, SEEK_SET); + // Set the Game ROM + gbe_mMap->setRomFile(gameROM); - fread(gbe_mMap->getRomBank0() + 0x100, 1, 16128, gameROM); - fread(gbe_mMap->getRomBank1(), 1, 16384, gameROM); + // Map to ROMs to mMap + gbe_mMap->mapRom(); s_Cycles = 0; - // STUB: Fooling the emulator - // Into thinking the frame is ready - // Helps us get out of the loop at - // 0x0064 in the boot ROM - // Needs to be removed once Timers - // and PPU is implemented - gbe_mMap->writeMemory(0xff44, 0x90); - // Adding the Nintendo Logo to ROM // to pass the Boot check @@ -96,449 +97,6 @@ GBE::GBE() gbe_mMap->debugWriteMemory(0x132, 0x33); gbe_mMap->debugWriteMemory(0x133, 0x3E); - // Using the Tetris header to pass the checksum test - // Unnecessary if loading a valid rom - - /*gbe_mMap->debugWriteMemory(0x134, 0x54); - gbe_mMap->debugWriteMemory(0x135, 0x45); - gbe_mMap->debugWriteMemory(0x136, 0x54); - gbe_mMap->debugWriteMemory(0x137, 0x52); - gbe_mMap->debugWriteMemory(0x138, 0x49); - gbe_mMap->debugWriteMemory(0x139, 0x53); - gbe_mMap->debugWriteMemory(0x13A, 0x00); - gbe_mMap->debugWriteMemory(0x13B, 0x00); - gbe_mMap->debugWriteMemory(0x13C, 0x00); - gbe_mMap->debugWriteMemory(0x13D, 0x00); - gbe_mMap->debugWriteMemory(0x13E, 0x00); - gbe_mMap->debugWriteMemory(0x13F, 0x00); - gbe_mMap->debugWriteMemory(0x140, 0x00); - gbe_mMap->debugWriteMemory(0x141, 0x00); - gbe_mMap->debugWriteMemory(0x142, 0x00); - gbe_mMap->debugWriteMemory(0x143, 0x00); - gbe_mMap->debugWriteMemory(0x144, 0x00); - gbe_mMap->debugWriteMemory(0x145, 0x00); - gbe_mMap->debugWriteMemory(0x146, 0x00); - gbe_mMap->debugWriteMemory(0x147, 0x00); - gbe_mMap->debugWriteMemory(0x148, 0x00); - gbe_mMap->debugWriteMemory(0x149, 0x00); - gbe_mMap->debugWriteMemory(0x14A, 0x00); - gbe_mMap->debugWriteMemory(0x14B, 0x01); - gbe_mMap->debugWriteMemory(0x14C, 0x01); - gbe_mMap->debugWriteMemory(0x14D, 0x0A);*/ - - // I have commented my tests for now as the Nintendo logo now sits there. - // We can test using blargg's test rom then. - - //// Current State: AF = 0x01B0, BC = 0x0013, DE = 0x00D8, HL = 0x014D, SP = 0xFFFE, PC = 0x0100 - - //// NOP - //// Does nothing - //// Final State: No change - // gbe_mMap->debugWriteMemory(0x0100, 0x00); - - //// LD BC, u16 - //// Loads a 16 bit immediate into the register BC - //// Final State: BC = 0XE001 - // gbe_mMap->debugWriteMemory(0x0101, 0x01); - // gbe_mMap->debugWriteMemory(0x0102, 0x01); - // gbe_mMap->debugWriteMemory(0x0103, 0xE0); - - //// LD (BC), A - //// Loads the value of the accumulator into the memory address pointed to by BC - //// Final State: Value 0x01 at address 0xE001 (2nd byte of Work RAM) - // gbe_mMap->debugWriteMemory(0x0104, 0x02); - - //// INC BC - //// Increments the value of BC by 1 - //// Final State: BC = 0xE002 - // gbe_mMap->debugWriteMemory(0x0105, 0x03); - - //// INC B - //// Increments the value of B by 1 - //// Final State: BC = 0xE102, Flag_N = 0, Flag_H = 0, Flag_Z = 0, AF = 0110 - // gbe_mMap->debugWriteMemory(0x0106, 0x04); - - //// Set BC = 0XFFFF to test INC B for Z flag - // gbe_mMap->debugWriteMemory(0x0107, 0x01); - // gbe_mMap->debugWriteMemory(0x0108, 0xFF); - // gbe_mMap->debugWriteMemory(0x0109, 0xFF); - - //// INC B - //// Increments the value of B by 1 - //// Final State: BC = 0x00FF, Flag_N = 0, Flag_H = 1, Flag_Z = 1, AF = 01B0 - // gbe_mMap->debugWriteMemory(0x010A, 0x04); - - //// Set BC = 0X0FFF to test INC B for H flag - // gbe_mMap->debugWriteMemory(0x010B, 0x01); - // gbe_mMap->debugWriteMemory(0x010C, 0xFF); - // gbe_mMap->debugWriteMemory(0x010D, 0x0F); - - //// INC B - //// Increments the value of B by 1 - //// Final State: BC = 0x10FF, Flag_N = 0, Flag_H = 1, Flag_Z = 0, AF = 0130 - // gbe_mMap->debugWriteMemory(0x010E, 0x04); - - //// DEC B - //// Decrements the value of B by 1 - //// Final State: BC = 0x0FFF, Flag_N = 1, Flag_H = 1, Flag_Z = 0, AF = 0170 - // gbe_mMap->debugWriteMemory(0x010F, 0x05); - - //// Set BC = 0X01FF to test DEC B for Z flag - // gbe_mMap->debugWriteMemory(0x0110, 0x01); - // gbe_mMap->debugWriteMemory(0x0111, 0xFF); - // gbe_mMap->debugWriteMemory(0x0112, 0x01); - - //// DEC B - //// Decrements the value of B by 1 - //// Final State: BC = 0x00FF, Flag_N = 1, Flag_H = 0, Flag_Z = 1, AF = 01D0 - // gbe_mMap->debugWriteMemory(0x0113, 0x05); - - //// Set BC = 0X020F to test DEC B - // gbe_mMap->debugWriteMemory(0x0114, 0x01); - // gbe_mMap->debugWriteMemory(0x0115, 0x0F); - // gbe_mMap->debugWriteMemory(0x0116, 0x02); - - //// DEC B - //// Decrements the value of B by 1 - //// Final State: BC = 0x010F, Flag_N = 1, Flag_H = 0, Flag_Z = 0, AF = 0150 - // gbe_mMap->debugWriteMemory(0x0117, 0x05); - - //// LD B, u8 - //// Loads an 8 bit immediate into the register B - //// Final State: BC = 0x690F - // gbe_mMap->debugWriteMemory(0x0118, 0x06); - // gbe_mMap->debugWriteMemory(0x0119, 0x69); - - //// RLCA - //// Rotates the value of the accumulator to the left - //// Final State: AF = 0x0200, Flag_C = 0 - // gbe_mMap->debugWriteMemory(0x011A, 0x07); - - //// Set A = 0x80 to test RLCA for C Flag - - //// RLCA - //// TODO: Will write later when LD A instructions have been tested - //// as that is needed to test this instruction more for flags - - //// LD (u16), SP - //// Loads the value of the stack pointer into the memory address pointed to by the immediate - //// Final State: Value 0xFFFE at address 0XE002 - // gbe_mMap->debugWriteMemory(0x011B, 0x08); - // gbe_mMap->debugWriteMemory(0x011C, 0x02); - // gbe_mMap->debugWriteMemory(0x011D, 0xE0); - - //// ADD HL, BC - //// Adds the value of BC to HL - //// Final State: HL = 0x6BFC - // gbe_mMap->debugWriteMemory(0x011E, 0x09); - - //// LD BC u16 - //// Loading address 0xE003 in BC to test next instruction - // gbe_mMap->debugWriteMemory(0x011F, 0x01); - // gbe_mMap->debugWriteMemory(0x0120, 0x03); - // gbe_mMap->debugWriteMemory(0x0121, 0xE0); - - //// LD A, (BC) - //// Loads the value of the memory address pointed to by BC into the accumulator - //// Final State: A = 0xFE, AF = 0xFE00 - // gbe_mMap->debugWriteMemory(0x0122, 0x0A); - - //// DEC BC - //// Decrements the value of BC by 1 - //// Final State: BC = 0xE002 - // gbe_mMap->debugWriteMemory(0x0123, 0x0B); - - //// INC C - //// Increments the value of C by 1 - //// Final State: BC = 0xE003, Flag_N = 0, Flag_H = 0, Flag_Z = 0, AF = 0xFE00 - // gbe_mMap->debugWriteMemory(0x0124, 0x0C); - - //// LD BC u16 - //// Loading address 0xE00F in BC to test H flag of INC C - // gbe_mMap->debugWriteMemory(0x0125, 0x01); - // gbe_mMap->debugWriteMemory(0x0126, 0x0F); - // gbe_mMap->debugWriteMemory(0x0127, 0xE0); - - //// INC C - //// Increments the value of C by 1 - //// Final State: BC = 0xE010, Flag_N = 0, Flag_H = 1, Flag_Z = 0, AF = 0xFE20 - // gbe_mMap->debugWriteMemory(0x0128, 0x0C); - - //// LD BC u16 - //// Loading value 0xE0FF in BC to test Z flag of INC C - // gbe_mMap->debugWriteMemory(0x0129, 0x01); - // gbe_mMap->debugWriteMemory(0x012A, 0xFF); - // gbe_mMap->debugWriteMemory(0x012B, 0xE0); - - //// INC C - //// Increments the value of C by 1 - //// Final State: BC = 0xE010, Flag_N = 0, Flag_H = 1, Flag_Z = 0, AF = 0xFE20 - // gbe_mMap->debugWriteMemory(0x012C, 0x0C); - - //// DEC C - //// Decrements the value of C by 1 - //// Final State: BC = 0xE0FF, Flag_N = 1, Flag_H = 1, Flag_Z = 0, AF = 0xFE60 - // gbe_mMap->debugWriteMemory(0x012D, 0x0D); - - //// LD BC u16 - //// Loading value 0xE001 in BC to test H flag of DEC C - // gbe_mMap->debugWriteMemory(0x012E, 0x01); - // gbe_mMap->debugWriteMemory(0x012F, 0x01); - // gbe_mMap->debugWriteMemory(0x0130, 0xE0); - - //// DEC C - //// Decrements the value of C by 1 - //// Final State: BC = 0xE000, Flag_N = 1, Flag_H = 0, Flag_Z = 1, AF = 0xFEB0 - // gbe_mMap->debugWriteMemory(0x0131, 0x0D); - - //// LD C, u8 - //// Loads an 8 bit immediate into the register C - //// Final State: BC = 0xE069 - // gbe_mMap->debugWriteMemory(0x0132, 0x0E); - // gbe_mMap->debugWriteMemory(0x0133, 0x69); - - //// RRCA - //// Rotates the value of the accumulator to the right - //// Final State: AF = 0x7F00, Flag_C = 0 - // gbe_mMap->debugWriteMemory(0x0134, 0x0F); - - //// STOP - //// Stops the CPU until an interrupt occurs - //// Did a NOP for now, as this stops execution of code. - //// TODO: Find a way to resume execution - // gbe_mMap->debugWriteMemory(0x0135, 0x00); - - //// STOP does an unnecessary byte read - // gbe_mMap->debugWriteMemory(0x0136, 0x00); - - //// LD DE, u16 - //// Loads an 16 bit immediate into the register DE - //// Final State: DE = 0xFFEC - // gbe_mMap->debugWriteMemory(0x0137, 0x11); - // gbe_mMap->debugWriteMemory(0x0138, 0xEC); - // gbe_mMap->debugWriteMemory(0x0139, 0xFF); - - //// LD (DE), A - //// Loads the value of the accumulator into the memory address pointed to by DE - //// Final State: Value 0x7F at address 0x69E0 - // gbe_mMap->debugWriteMemory(0x013A, 0x12); - - //// INC DE - //// Increments the value of DE by 1 - //// Final State: DE = 0x69E1 - // gbe_mMap->debugWriteMemory(0x013B, 0x13); - - //// INC D - //// Increments the value of D by 1 - //// Final State: DE = 0x6AE1, Flag_N = 0, Flag_H = 0, Flag_Z = 0, AF = 0x7F00 - // gbe_mMap->debugWriteMemory(0x013C, 0x14); - - //// DEC D - //// Decrements the value of D by 1 - //// Final State: DE = 0x69E1, Flag_N = 1, Flag_H = 0, Flag_Z = 0, AF = 0x7F40 - // gbe_mMap->debugWriteMemory(0x013D, 0x15); - - //// LD D, u8 - //// Loads an 8 bit immediate into the register D - //// Final State: DE = 0xE0E1 - // gbe_mMap->debugWriteMemory(0x013E, 0x16); - // gbe_mMap->debugWriteMemory(0x013F, 0xE0); - - //// RLA - //// Rotates the value of the accumulator to the left - //// Final State: AF = 0xFE00, Flag_C = 0 - // gbe_mMap->debugWriteMemory(0x0140, 0x17); - - //// RLA - //// Rotates the value of the accumulator to the left - //// Final State: AF = 0xFC10, Flag_C = 1 - // gbe_mMap->debugWriteMemory(0x0141, 0x17); - - //// JR i8 - //// Jumps to the address at PC + i8 + 2 - //// Final State: Next instruction is selected - //// i8 is a signed 8 bit value - // gbe_mMap->debugWriteMemory(0x0142, 0x18); - // gbe_mMap->debugWriteMemory(0x0143, 0x00); - - //// JR i8 - //// This one must go in an infinite loop - // gbe_mMap->debugWriteMemory(0x0144, 0x18); - // gbe_mMap->debugWriteMemory(0x0145, 0xFE); - - //// TODO: ADD HL, DE test after implementing and testing LD HL, u16 - - //// Loading 0x0100 into DE to test LD (DE), A - //// Final State: DE = 0x0100 - // gbe_mMap->debugWriteMemory(0x0146, 0x11); - // gbe_mMap->debugWriteMemory(0x0147, 0x00); - // gbe_mMap->debugWriteMemory(0x0148, 0x01); - - //// LD A, (DE) - //// Loads the value of the memory address pointed to by DE into the accumulator - //// Final State: AF = 0x0010 - // gbe_mMap->debugWriteMemory(0x0149, 0x1A); - - //// DEC DE - //// Decrements the value of DE by 1 - //// Final State: DE = 0x00FF - // gbe_mMap->debugWriteMemory(0x014A, 0x1B); - - //// INC E - //// Increments the value of E by 1 - //// Final State: DE = 0x0100, Flag_N = 0, Flag_H = 1, Flag_Z = 1, AF = 0x00A0 - // gbe_mMap->debugWriteMemory(0x014B, 0x1C); - - //// DEC E - //// Decrements the value of E by 1 - //// Final State: DE = 0x00FF, Flag_N = 1, Flag_H = 1, Flag_Z = 0, AF = 0x0060 - // gbe_mMap->debugWriteMemory(0x014C, 0x1D); - - //// LD E, u8 - //// Loads an 8 bit immediate into the register E - //// Final State: DE = 0x00E0 - // gbe_mMap->debugWriteMemory(0x014D, 0x1E); - // gbe_mMap->debugWriteMemory(0x014E, 0xE0); - - //// Loading a value into the accumulator to test RRA - //// Final State: DE = 0x014E, AF = 0xE000 - // gbe_mMap->debugWriteMemory(0x014F, 0x11); - // gbe_mMap->debugWriteMemory(0x0150, 0x4E); - // gbe_mMap->debugWriteMemory(0x0151, 0x01); - // gbe_mMap->debugWriteMemory(0x0152, 0x1A); - - //// RRA - //// Rotates the value of the accumulator to the right - //// Final State: AF = 0x7000, Flag_C = 0 - // gbe_mMap->debugWriteMemory(0x0153, 0x1F); - - //// Loading a value into the accumulator to test RRA - //// Final State: DE = 0x0151, AF = 0x0100 - // gbe_mMap->debugWriteMemory(0x0154, 0x11); - // gbe_mMap->debugWriteMemory(0x0155, 0x51); - // gbe_mMap->debugWriteMemory(0x0156, 0x01); - // gbe_mMap->debugWriteMemory(0x0157, 0x1A); - - //// RRA - //// Rotates the value of the accumulator to the right - //// Final State: AF = 0x0010, Flag_C = 1 - // gbe_mMap->debugWriteMemory(0x0158, 0x1F); - - //// JR NZ, i8 - //// Jumps to the address at PC + i8 + 2 if the zero flag is not set - //// Final State: Next instruction is not selected as the zero flag is not set - //// PC = 0x015C - // gbe_mMap->debugWriteMemory(0x0159, 0x20); - // gbe_mMap->debugWriteMemory(0x015A, 0x01); - - //// NOP - //// This must be skipped - // gbe_mMap->debugWriteMemory(0x015B, 0x00); - - //// Setting the zero flag to test JR NZ, i8 - //// Load 0xFF in B and INC B - //// Final State: BC = 0x0069, AF = 0x00B0, Flag_Z = 1 - // gbe_mMap->debugWriteMemory(0x015C, 0x06); - // gbe_mMap->debugWriteMemory(0x015D, 0xFF); - // gbe_mMap->debugWriteMemory(0x015E, 0x04); - - //// JR NZ, i8 - //// Jumps to the address at PC + i8 + 2 if the zero flag is not set - //// Final State: Next instruction is selected as the zero flag is set - //// PC = 0x0161 - // gbe_mMap->debugWriteMemory(0x015F, 0x20); - // gbe_mMap->debugWriteMemory(0x0160, 0x01); - - //// NOP - //// This must be executed - // gbe_mMap->debugWriteMemory(0x0161, 0x00); - - //// LD HL, u16 - //// Loads a 16 bit immediate into the register HL - //// Final State: HL = 0xC010 - // gbe_mMap->debugWriteMemory(0x0162, 0x21); - // gbe_mMap->debugWriteMemory(0x0163, 0x10); - // gbe_mMap->debugWriteMemory(0x0164, 0xC0); - - //// LD (HL+), A - //// Loads the value of the accumulator into the memory address pointed to by HL - //// and then increments HL - //// Final State: HL = 0xC011, AF = 0x0010, 0X00 at 0XC010 - // gbe_mMap->debugWriteMemory(0x0165, 0x22); - - //// INC HL - //// Increments the value of HL by 1 - //// Final State: HL = 0xC012 - // gbe_mMap->debugWriteMemory(0x0166, 0x23); - - //// INC H - //// Increments the value of H by 1 - //// Final State: HL = 0xC113, Flag_N = 0, Flag_H = 0, Flag_Z = 0, AF = 0x0010 - // gbe_mMap->debugWriteMemory(0x0167, 0x24); - - //// DEC H - //// Decrements the value of H by 1 - //// Final State: HL = 0xC112, Flag_N = 1, Flag_H = 0, Flag_Z = 0, AF = 0x0050 - // gbe_mMap->debugWriteMemory(0x0168, 0x25); - - //// LD H, u8 - //// Loads an 8 bit immediate into the register H - //// Final State: HL = 0xA012 - // gbe_mMap->debugWriteMemory(0x0169, 0x26); - // gbe_mMap->debugWriteMemory(0x016A, 0xA0); - - //// DAA - //// Adjusts the value of the accumulator to be a valid BCD value - //// Final State: AF = 0x0010 - //// TODO: Test this. High risk opcode - // gbe_mMap->debugWriteMemory(0x016B, 0x27); - - //// JR Z, i8 - //// Jumps to the address at PC + i8 + 2 if the zero flag is set - //// Final State: Next instruction is selected as the zero flag is not set - //// PC = 0x016E - // gbe_mMap->debugWriteMemory(0x016C, 0x28); - // gbe_mMap->debugWriteMemory(0x016D, 0x01); - - //// NOP - //// This must be not skipped - // gbe_mMap->debugWriteMemory(0x016E, 0x00); - - //// Setting the zero flag to test JR Z, i8 - //// Load 0xFF in B and INC B - // gbe_mMap->debugWriteMemory(0x016F, 0x06); - // gbe_mMap->debugWriteMemory(0x0170, 0xFF); - // gbe_mMap->debugWriteMemory(0x0171, 0x04); - - //// JR Z, i8 - //// Jumps to the address at PC + i8 + 2 if the zero flag is set - //// Final State: Next instruction is not selected as the zero flag is set - //// PC = 0x0175 - // gbe_mMap->debugWriteMemory(0x0172, 0x28); - // gbe_mMap->debugWriteMemory(0x0173, 0x01); - - //// NOP - //// This must be skipped - // gbe_mMap->debugWriteMemory(0x0174, 0x00); - - //// ADD HL, HL - //// Adds the value of HL to HL - //// Final State: HL = 0x4024, Flag_N = 0, Flag_H = 0, Flag_C = 0, AF = 0x0010 - // gbe_mMap->debugWriteMemory(0x0175, 0x29); - - //// Loading value in HL to test next opcode - // gbe_mMap->debugWriteMemory(0x0162, 0x21); - // gbe_mMap->debugWriteMemory(0x0163, 0x10); - // gbe_mMap->debugWriteMemory(0x0164, 0xC0); - - //// LD A, (HL+) - //// Loads the value of the memory address pointed to by HL into the accumulator - //// and then increments HL - //// Final State: HL = 0xC013, AF = 0x0010, 0X00 at 0XC011 - - //// Seg fault to end using UNKOWN - // gbe_mMap->debugWriteMemory(0x0146, 0xEB); - executeBootROM(); update(); @@ -553,19 +111,14 @@ void GBE::update() { // Execute the next instruction s_Cycles += gbe_cpu->executeNextInstruction(); - if ((*gbe_mMap)[0xFF02] == 0x81) - { - printf("%c", (*gbe_mMap)[0xFF01]); - gbe_mMap->writeMemory(0xFF02, 0x00); - } // update the DIV and TIMA timers gbe_cpu->updateTimers(s_Cycles); - // updateGraphics() + gbe_graphics->executePPU(s_Cycles); s_Cycles = 0; s_Cycles += gbe_cpu->performInterrupt(); + gbe_graphics->pollEvents(); } - // renderGraphics() } void GBE::executeBootROM() @@ -573,9 +126,11 @@ void GBE::executeBootROM() while (gbe_mMap->readMemory(0xFF50) == 0x00) { s_Cycles += gbe_cpu->executeNextInstruction(); + gbe_cpu->updateTimers(s_Cycles); + gbe_graphics->executePPU(s_Cycles); + s_Cycles = 0; + s_Cycles += gbe_cpu->performInterrupt(); } - // Overwrite the boot ROM with first 256 bytes of game ROM - fseek(gameROM, 0x00, SEEK_SET); - fread(gbe_mMap->getRomBank0(), 1, 256, gameROM); + gbe_mMap->unloadBootRom(); } \ No newline at end of file diff --git a/src/gameBoy.h b/src/gameBoy.h index d785049..35c22f8 100644 --- a/src/gameBoy.h +++ b/src/gameBoy.h @@ -2,6 +2,7 @@ #include "types.h" #include "cpu.h" #include "mmap.h" +#include "graphics.h" // GBE stands for GameBoyEmulator @@ -24,6 +25,9 @@ class GBE // Pointer to the MemoryMap MemoryMap* gbe_mMap; + // Pointer to the Graphics + PPU* gbe_graphics; + // File pointer for Boot ROM FILE* bootROM; diff --git a/src/graphics.cpp b/src/graphics.cpp new file mode 100644 index 0000000..e1030bb --- /dev/null +++ b/src/graphics.cpp @@ -0,0 +1,485 @@ +#include "types.h" +#include "graphics.h" + +PPU::PPU() +{ + // Initialize members + window = nullptr; + renderer = nullptr; + texture = nullptr; + isEnabled = false; + showBGWin = false; + showWindow = false; + //renderWindow = false; + mMap = nullptr; + bgTileDataAddr = 0x0000; + bgTileMapAddr = 0x0000; + winTileMapAddr = 0x0000; + bgPalette = 0x00; + objPalette0 = 0x00; + objPalette1 = 0x00; + currentLine = 0x00; + hiddenWindowLineCounter = 0x00; + ppuMode = 0x02; + event = new SDL_Event(); + + ppuMode = 0; + currentClock = modeClocks[ppuMode]; + scanlineRendered = false; + frameRendered = false; + + // Fill renderArray initially with white (lightest color in palette) + std::fill(renderArray, renderArray + (160 * 144), bg_colors[0]); +} + +bool PPU::init() +{ + // Initialize SDL + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + // Set hint to use hardware acceleration + if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) + { + printf("Hardware Acceleration not enabled! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + // Set hint for VSync + if (!SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1")) + { + printf("VSync not enabled! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + // Create window and renderer + if (!(window = SDL_CreateWindow("GameBoy Emulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2, SDL_WINDOW_SHOWN))) + { + printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + if (!(renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC))) + { + printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + // Evaluate LCDC register + Byte LCDC = mMap->getRegLCDC(); + + isEnabled = (LCDC & 0x80); + bgTileMapAddr = (LCDC & 0x04) ? 0x9C00 : 0x9800; + bgTileDataAddr = (LCDC & 0x08) ? 0x8000 : 0x8800; + winTileMapAddr = (LCDC & 0x40) ? 0x9C00 : 0x9800; + + // Evaluate Background Palette register + bgPalette = mMap->getRegBGP(); + + // Evaluate Sprite Palette 0 register + objPalette0 = mMap->getRegOBP0(); + + // Evaluate Sprite Palette 1 register + objPalette1 = mMap->getRegOBP1(); + + // Create a placeholder texture + // 512x512 to have 4 copies of tilemap + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, 160, 144); + + // Render the texture + SDL_UpdateTexture(texture, NULL, renderArray, 160 * 4); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + return true; +} + +// Poll Events to check for inputs +// And process them +bool PPU::pollEvents() +{ + while (SDL_PollEvent(event)) + { + if (event->key.type == SDL_KEYDOWN) + { + switch (event->key.keysym.sym) + { + case SDLK_LEFT: + *(mMap->joyPadState) &= 0xFD; + break; + case SDLK_RIGHT: + *(mMap->joyPadState) &= 0xFE; + break; + case SDLK_UP: + *(mMap->joyPadState) &= 0xFB; + break; + case SDLK_DOWN: + *(mMap->joyPadState) &= 0xF7; + break; + case SDLK_a: + *(mMap->joyPadState) &= 0xEF; + break; + case SDLK_s: + *(mMap->joyPadState) &= 0xDF; + break; + case SDLK_LSHIFT: + *(mMap->joyPadState) &= 0xBF; + break; + case SDLK_SPACE: + *(mMap->joyPadState) &= 0x7F; + break; + case SDLK_ESCAPE: + exit(0); + default: + break; + } + } + else if (event->key.type == SDL_KEYUP) + { + switch (event->key.keysym.sym) + { + case SDLK_LEFT: + *(mMap->joyPadState) |= 0x02; + break; + case SDLK_RIGHT: + *(mMap->joyPadState) |= 0x01; + break; + case SDLK_UP: + *(mMap->joyPadState) |= 0x04; + break; + case SDLK_DOWN: + *(mMap->joyPadState) |= 0x08; + break; + case SDLK_a: + *(mMap->joyPadState) |= 0x10; + break; + case SDLK_s: + *(mMap->joyPadState) |= 0x20; + break; + case SDLK_LSHIFT: + *(mMap->joyPadState) |= 0x40; + break; + case SDLK_SPACE: + *(mMap->joyPadState) |= 0x80; + break; + default: + break; + } + } + } + return false; +} + +void PPU::renderScanline(Byte line) +{ + // Evaluate LCDC register + Byte LCDC = mMap->getRegLCDC(); + + isEnabled = (LCDC & 0x80); + showBGWin = (LCDC & 0x1); + showWindow = (LCDC & 0x20); + showSprites = (LCDC & 0x2); + + if (!isEnabled) + return; + + bgTileMapAddr = (LCDC & 0x08) ? 0x9C00 : 0x9800; + bgTileDataAddr = (LCDC & 0x10) ? 0x8000 : 0x8800; + winTileMapAddr = (LCDC & 0x40) ? 0x9C00 : 0x9800; + + // Read palette registers + bgPalette = mMap->getRegBGP(); + objPalette0 = mMap->getRegOBP0(); + objPalette1 = mMap->getRegOBP1(); + + Byte win_y = mMap->getRegWY(); + Byte win_x = mMap->getRegWX() - 7; + Byte win_pixel_y = hiddenWindowLineCounter; + Byte bg_pixel_y = line + mMap->getRegSCY(); + Byte scroll_x = mMap->getRegSCX(); + Byte bg_pixel_x, bg_pixel_col, win_pixel_x, win_pixel_col, sprite_y, sprite_pixel_col; + Byte bg_tilenum, win_tilenum, sprite_palette; + Byte sprite_height = (LCDC & 0x4) ? 16 : 8; + + // Filling pixel array + // Going over each pixel on screen + // And filling it with the correct color + // If LCDC.4 is set, then the background tile map uses $8000 method and unsigned addressing + // If LCDC.4 is not set, then the background tile map uses $8800 method and signed addressing + // Each tile has 8x8 pixels, and each pixel has a color ID of 0 to 3 + // Each tile occupies 16 bytes, where each line is represented by 2 bytes + // For each line, the 1st byte specifies the LSB of the color ID of each pixel, and the 2nd byte specifies the MSB. + // The color numbers are translated into gray shades depending on the current palette + + // First we calculate the tile number of the tile that the pixel is in + // To do that, we divide the pixel's x and y coordinates by 8 (floor division) + // Then we multiply the resultant y by 32 (the number of tiles in a row) (256 pixels / 8 pixels per tile = 32 tiles) + // Then we add the resultant x which gives us the tile number we must check for data + // Here i is y and j is x + + // Now, using the tile number, we can calculate the address of the tile data by multiplying the tile number by 16 and adding it to the tile data address + // The tile data address is either 0x8000 or 0x8800 depending on LCDC.4 for Background + // If the tile data address is 0x8000, then the tile number is unsigned, else signed at 0x8800 + // Now, depending on the row of pixel, we find which 2 bytes of data to use out of the 16 in pixel data, by taking remainder from 8 and choosing the pair of bytes + // Then, we find the color ID of the pixel by taking the bit at the position of the pixel in the row (7 - (j % 8)) and shifting it to the LSB for both the bytes in the pair + // 7-(j % 8) will give us the 7th bit for j = 0, 6th bit for j = 1, and so on, and the last bit for j = 8. Exactly the bit we need from both bytes in pair + // Adding these LSBs will give us the pixel color we want + + // Source: https://gbdev.io/pandocs/Tile_Data.html + + for (Byte j = 0; j < 160; j++) + { + // Background rendering + bg_pixel_x = scroll_x + j; + bg_tilenum = (*mMap)[bgTileMapAddr + ((bg_pixel_y / 8) * 32) + (bg_pixel_x / 8)]; + + if (bgTileDataAddr == 0x8800) + { + bg_pixel_col = ((*mMap)[bgTileDataAddr + 0x800 + ((SByte)bg_tilenum * 0x10) + (bg_pixel_y % 8 * 2)] >> (7 - (bg_pixel_x % 8)) & 0x1) + ((*mMap)[bgTileDataAddr + 0x800 + ((SByte)bg_tilenum * 0x10) + (bg_pixel_y % 8 * 2) + 1] >> (7 - (bg_pixel_x % 8)) & 0x1) * 2; + } + else + { + bg_pixel_col = ((*mMap)[bgTileDataAddr + (bg_tilenum * 0x10) + (bg_pixel_y % 8 * 2)] >> (7 - (bg_pixel_x % 8)) & 0x1) + (((*mMap)[bgTileDataAddr + (bg_tilenum * 0x10) + (bg_pixel_y % 8 * 2) + 1] >> (7 - (bg_pixel_x % 8)) & 0x1) * 2); + } + + if (showBGWin) + renderArray[(line * 160) + j] = bg_colors[(bgPalette >> (bg_pixel_col * 2)) & 0x3]; + else + renderArray[(line * 160) + j] = bg_colors[0]; + + // Window rendering + if (showBGWin && showWindow && ((win_y <= line) && (win_y < 144)) && (win_x < 160) && (hiddenWindowLineCounter < 144) && (j >= win_x)) + { + win_pixel_x = j - win_x; + win_tilenum = (*mMap)[winTileMapAddr + ((win_pixel_y / 8) * 32) + (win_pixel_x / 8)]; + + if (bgTileDataAddr == 0x8800) + { + win_pixel_col = ((*mMap)[bgTileDataAddr + 0x800 + ((SByte)win_tilenum * 0x10) + (win_pixel_y % 8 * 2)] >> (7 - (win_pixel_x % 8)) & 0x1) + ((*mMap)[bgTileDataAddr + 0x800 + ((SByte)win_tilenum * 0x10) + (win_pixel_y % 8 * 2) + 1] >> (7 - (win_pixel_x % 8)) & 0x1) * 2; + } + else + { + win_pixel_col = ((*mMap)[bgTileDataAddr + (win_tilenum * 0x10) + (win_pixel_y % 8 * 2)] >> (7 - (win_pixel_x % 8)) & 0x1) + (((*mMap)[bgTileDataAddr + (win_tilenum * 0x10) + (win_pixel_y % 8 * 2) + 1] >> (7 - (win_pixel_x % 8)) & 0x1) * 2); + } + + if ((win_pixel_col != 0) || (win_x)) + renderArray[(line * 160) + j] = bg_colors[(bgPalette >> (win_pixel_col * 2)) & 0x3]; + } + } + + if (showBGWin && showWindow && ((win_y <= line) && (win_y < 144)) && (win_x < 160) && (hiddenWindowLineCounter < 144)) + hiddenWindowLineCounter++; + + // Sprite rendering + if (showSprites) + { + sprites.clear(); + for (Word i = 0xFE00; i < 0xFEA0; i += 4) + { + if (sprites.size() >= 10) + break; + sprite_y = (*mMap)[i]; + if ((line < (sprite_y - 16) || line > (sprite_y - 16 + sprite_height - 1))) + continue; + + Sprite* sprite = new Sprite(); + sprite->address = i; + sprite->y = sprite_y; + sprite->x = (*mMap)[i + 1]; + sprite->tile = (*mMap)[i + 2]; + sprite->flags = (*mMap)[i + 3]; + sprites.push_back(*sprite); + } + + if (sprites.size()) + std::sort(sprites.begin(), sprites.end(), [](Sprite& a, Sprite& b) { return (((a.x == b.x) && (a.address > b.address)) || (a.x > b.x)); }); + + for (auto it = sprites.begin(); it != sprites.end(); ++it) + { + sprite_palette = (it->flags & 0x10) ? objPalette1 : objPalette0; + for (int i = 0; i < 8; i++) + { + if (sprite_height == 16) + it->tile &= 0xFE; + switch (it->flags & 0x60) + { + case 0x00: // Normal + sprite_pixel_col = ((*mMap)[0x8000 + (it->tile * 0x10) + ((line - (it->y - 16)) * 2)] >> (7 - i) & 0x1) + (((*mMap)[0x8000 + (it->tile * 0x10) + ((line - (it->y - 16)) * 2) + 1] >> (7 - i) & 0x1) * 2); + break; + case 0x20: // Flip X + sprite_pixel_col = ((*mMap)[0x8000 + (it->tile * 0x10) + ((line - (it->y - 16)) * 2)] >> i & 0x1) + (((*mMap)[0x8000 + (it->tile * 0x10) + ((line - (it->y - 16)) * 2) + 1] >> i & 0x1) * 2); + break; + case 0x40: // Flip Y + sprite_pixel_col = ((*mMap)[0x8000 + (it->tile * 0x10) + ((sprite_height - (line - (it->y - 16)) - 1) * 2)] >> (7 - i) & 0x1) + (((*mMap)[0x8000 + (it->tile * 0x10) + ((sprite_height - (line - (it->y - 16)) - 1) * 2) + 1] >> (7 - i) & 0x1) * 2); + break; + case 0x60: // Flip X and Y + sprite_pixel_col = ((*mMap)[0x8000 + (it->tile * 0x10) + ((sprite_height - (line - (it->y - 16)) - 1) * 2)] >> i & 0x1) + (((*mMap)[0x8000 + (it->tile * 0x10) + ((sprite_height - (line - (it->y - 16)) - 1) * 2) + 1] >> i & 0x1) * 2); + break; + default: + break; + } + + if (sprite_pixel_col != 0) + { + if (((it->x + i - 8) < 160) && (!(it->flags & 0x80) || (renderArray[(line * 160) + (it->x + i - 8)] == bg_colors[0]))) + renderArray[(line * 160) + (it->x + i - 8)] = bg_colors[(sprite_palette >> (sprite_pixel_col * 2)) & 0x3]; + } + } + } + } +} + +void PPU::executePPU(int cycles) +{ + currentClock -= cycles; + switch (ppuMode) + { + case HBLANK: + { + if (!scanlineRendered) + { + renderScanline(mMap->getRegLY()); + scanlineRendered = true; + } + + if (currentClock < 0) + { + Byte LY = mMap->getRegLY(); + Byte LYC = mMap->getRegLYC(); + Byte STAT = mMap->getRegSTAT(); + mMap->setRegLY(LY + 1); + + if (LY + 1 == LYC) + { + mMap->setRegSTAT(STAT | 0x4); + if (STAT & 0x40) + mMap->setRegIF(mMap->getRegIF() | 0x2); + } + else + { + mMap->setRegSTAT(STAT & 0xFB); + } + + if (LY == 0x8F) + { + mMap->setRegIF(mMap->getRegIF() | 0x1); + mMap->setRegSTAT((STAT & 0xFC) | 0x1); + if (STAT & 0x10) + mMap->setRegIF(mMap->getRegIF() | 0x2); + ppuMode = 1; + hiddenWindowLineCounter = 0; + } + else + { + mMap->setRegSTAT((STAT & 0xFC) | 0x2); + if (STAT & 0x20) + mMap->setRegIF(mMap->getRegIF() | 0x2); + ppuMode = 2; + } + currentClock += modeClocks[ppuMode]; + } + } + break; + case VBLANK: + { + if (!frameRendered) + { + SDL_UpdateTexture(texture, NULL, renderArray, 160 * 4); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + frameRendered = true; + } + if (currentClock < 0) + { + Byte LY = mMap->getRegLY(); + Byte LYC = mMap->getRegLYC(); + Byte STAT = mMap->getRegSTAT(); + mMap->setRegLY(LY + 1); + + if (LYC == LY + 1) + { + mMap->setRegSTAT(STAT & 0x4); + if (STAT & 0x40) + mMap->setRegIF(mMap->getRegIF() | 0x2); + } + else + { + mMap->setRegSTAT(STAT & 0xFB); + } + + if (LY == 0x99) + { + mMap->setRegSTAT((STAT & 0xFC) | 0x2); + if (STAT & 0x20) + mMap->setRegIF(mMap->getRegIF() | 0x2); + ppuMode = 2; + scanlineRendered = false; + mMap->setRegLY(0); + if (LYC == 0) + { + mMap->setRegSTAT(STAT & 0x4); + if (STAT & 0x40) + mMap->setRegIF(mMap->getRegIF() | 0x2); + } + else + { + mMap->setRegSTAT(STAT & 0xFB); + } + } + currentClock += modeClocks[ppuMode]; + } + } + break; + case OAM: + { + frameRendered = false; + if (currentClock < 0) + { + // TODO: Implement OAM memory restriction + Byte STAT = mMap->getRegSTAT(); + mMap->setRegSTAT((STAT & 0xFC) | 0x3); + ppuMode = 3; + currentClock += modeClocks[ppuMode]; + } + } + break; + case TRANSFER: + { + scanlineRendered = false; + + if (currentClock < 0) + { + // TODO: Implement All memory restriction + Byte STAT = mMap->getRegSTAT(); + mMap->setRegSTAT(STAT & 0xFC); + if (STAT & 0x8) + mMap->setRegIF(mMap->getRegIF() | 0x2); + ppuMode = 0; + currentClock += modeClocks[ppuMode]; + } + } + break; + default: + printf("Unknown PPU Mode %d\n", ppuMode); + break; + } +} + +void PPU::close() +{ + // Destroy texture + SDL_DestroyTexture(texture); + + // Destroy renderer + SDL_DestroyRenderer(renderer); + + // Destroy window + SDL_DestroyWindow(window); + + // Quit SDL subsystems + SDL_Quit(); +} \ No newline at end of file diff --git a/src/graphics.h b/src/graphics.h new file mode 100644 index 0000000..706f696 --- /dev/null +++ b/src/graphics.h @@ -0,0 +1,120 @@ +#pragma once +#include "types.h" +#include "mmap.h" +#include +#include +#include +#include + +struct Sprite +{ + Word address; + Byte y; + Byte x; + Byte tile; + Byte flags; +}; + +class PPU +{ +private: + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* texture; + SDL_Event* event; + + // renderArray to be converted to texture + // stores 4 copies of texture for wrapping of screen + color renderArray[160 * 144]; + + MemoryMap* mMap; + + // LCDC 7th bit is the LCD enable flag + bool isEnabled; + + // LCDC 0th bit is the BG and Window Display Enable flag + bool showBGWin; + + // LCDC 5th bit is the Window Display Enable flag + bool showWindow; + + // LCDC 1st bit is the OBJ (Sprite) Display Enable flag + bool showSprites; + + // LCDC 3rd bit is the BG and Window Tile Data Select flag + Word bgTileDataAddr; + + // LCDC 4th bit is the BG Tile Map Display Select flag + Word bgTileMapAddr; + + // LCDC 6th bit is the Window Tile Map Display Select flag + Word winTileMapAddr; + + // BGP register is the BG Palette Data + Byte bgPalette; + + // OBP0 register is the OBJ Palette 0 Data + Byte objPalette0; + + // OBP1 register is the OBJ Palette 1 Data + Byte objPalette1; + + // Internal window line counter + Byte hiddenWindowLineCounter; + + // The GameBoy screen + // 160x144 screen resolution withing a 256x224 border + // The original GameBoy supported 4 colors + // Pulled from https://gbdev.io/pandocs/Specifications.html + const int SCREEN_WIDTH = 160; + const int SCREEN_HEIGHT = 144; + + // Color Mapping for background + color bg_colors[4] = { 0x9BBC0FFF, 0x8BAC0FFF, 0x306230FF, 0x0F380FFF }; + + // Color Mapping for objects + // NOTE: 0 is transparent + // indices 1, 2, 3 are the actual colors and will be populated later + color obj_colors[4] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + + // Current line being rendered + int currentLine; + + // PPU Mode + Byte ppuMode; + + // PPU Mode Clocks + // Mode 0: 204 cycles + // Mode 1: 456 cycles + // Mode 2: 80 cycles + // Mode 3: 172 cycles + int modeClocks[4] = { 204, 456, 80, 172 }; + + // Current PPU Mode Clock + int currentClock; + + // Scanline Rendered Flag + bool scanlineRendered; + + bool frameRendered; + + enum PPU_MODES + { + HBLANK, + VBLANK, + OAM, + TRANSFER + }; + + std::vector sprites; + +public: + PPU(); + bool init(); + bool pollEvents(); + void renderScanline(Byte line); + void close(); + void setMemoryMap(MemoryMap* m) { mMap = m; } + void executePPU(int cycles); + Byte getPPUMode() { return ppuMode; } +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0fdf031..e29a31a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,7 @@ -#include #include "gameBoy.h" -int main() +int main(int argv, char** argc) { - std::cout << "Hello World!"; - GBE* gbe = new GBE(); return 0; diff --git a/src/mmap.cpp b/src/mmap.cpp index d0f4731..874b9b1 100644 --- a/src/mmap.cpp +++ b/src/mmap.cpp @@ -64,16 +64,104 @@ MemoryMap::MemoryMap() // IF at 0xFF0F reg_IF = ioPorts + 0x0F; + + // LCDC at 0xFF40 + reg_LCDC = ioPorts + 0x40; + + // SCX at 0xFF43 + reg_SCX = ioPorts + 0x43; + + // SCY at 0xFF42 + reg_SCY = ioPorts + 0x42; + + // BGP at 0xFF47 + reg_BGP = ioPorts + 0x47; + + // OBP0 at 0xFF48 + reg_OBP0 = ioPorts + 0x48; + + // OBP1 at 0xFF49 + reg_OBP1 = ioPorts + 0x49; + + // LY at 0xFF44 + reg_LY = ioPorts + 0x44; + + // LYC at 0xFF45 + reg_LYC = ioPorts + 0x45; + + // STAT at 0xFF41 + reg_STAT = ioPorts + 0x41; + + // WY at 0xFF4A + reg_WY = ioPorts + 0x4A; + + // WX at 0xFF4B + reg_WX = ioPorts + 0x4B; + + joyPadState = new Byte; + *joyPadState = 0xFF; + + bootRomFile = nullptr; + romFile = nullptr; + + mbcMode = 0x0; + + romSize = 0x0; + + ramSize = 0x0; + + ramEnable = 0; + + romBankNumber = 0; + + ramBankNumber = 0; + + bankingModeSelect = 0; + + ramExistenceMask = 0; + + romBankNumberMask = 0; + + ramBankNumberMaskForRom = 0; + + ramBankNumberMaskForRam = 0; } // Write to memory // TODO: Make emulation memory secure bool MemoryMap::writeMemory(Word address, Byte value) { - if (address < 0x8000) + if (address < 0x2000) { - printf("Writing to ROM is not allowed"); - return false; + // If values is 0x0A then external RAM is enabled, else it is disabled + if (value == 0x0A) + { + ramEnable = true; + } + else + { + ramEnable = false; + } + } + else if (address < 0x4000) + { + // Decide ROM Bank Number + romBankNumber = (value & 0b11111); + bankRom(); + } + else if (address < 0x6000) + { + // Decide RAM Bank Number + ramBankNumber = (value & 0b11); + bankRom(); + bankRam(); + } + else if (address < 0x8000) + { + // Decide Banking Mode Select + bankingModeSelect = (value & 0b1); + bankRom(); + bankRam(); } else if (address < 0xA000) { @@ -82,8 +170,11 @@ bool MemoryMap::writeMemory(Word address, Byte value) } else if (address < 0xC000) { - // Write to External RAM - externalRam[address - 0xA000] = value; + // Write to External RAM if external RAM has been enabled + if (ramEnable) + { + externalRam[address - 0xA000] = value; + } } else if (address < 0xE000) { @@ -113,6 +204,26 @@ bool MemoryMap::writeMemory(Word address, Byte value) // else write to I/O ports if (address == 0xFF04) *reg_DIV = 0x00; + // Check for DMA transfer + // Writing a loop instead of std::copy + // as memoury is not a single unit + // in our architecture + else if (address == 0xFF46) + { + Word val = value; + val = val << 8; + for (Word i = 0; i < 0xA0; i++) + oamTable[i] = readMemory(val + i); + ioPorts[address - 0xFF00] = value; + } + else if (address == 0xFF44) + *reg_LY = 0x00; + else if (address == 0xFF00) + { + readInput(value); + } + //if (value != 0xFF) + //printf("0x%02x\n", ioPorts[0]);} else ioPorts[address - 0xFF00] = value; } @@ -208,4 +319,160 @@ Byte MemoryMap::readMemory(Word address) Byte MemoryMap::operator[](Word address) { return MemoryMap::readMemory(address); +} + +void MemoryMap::readInput(Byte value) +{ + ioPorts[0] = (ioPorts[0] & 0xCF) | (value & 0x30); + + Byte current = ioPorts[0] & 0xF0; + + switch (current & 0x30) + { + case 0x10: + current = 0xD0; + current |= (((*joyPadState) >> 4) & 0x0F); + break; + case 0x20: + current = 0xE0; + current |= ((*joyPadState) & 0x0F); + break; + case 0x30: + current = 0xF0; + current |= 0x0F; + break; + } + + if ((ioPorts[0] & (~current) & 0x0F) != 0) + (*reg_IF) |= 0x10; + + ioPorts[0] = current; +} + +void MemoryMap::mapRom() +{ + // Load the Boot ROM + // Into the first 0x100 bytes + fread(romBank0, 1, 256, bootRomFile); + + // Load Game ROM in Bank 0 + // After offsetting for Boot ROM first + fseek(romFile, 0x100, SEEK_SET); + + fread(romBank0 + 0x100, 1, 16128, romFile); + fread(romBank1, 1, 16384, romFile); + + // MBC + + // Check 0x147 for MBC mode + mbcMode = romBank0[0x147]; + + // Check 0x148 for ROM size + romSize = romBank0[0x148]; + + // Check 0x149 for RAM size + ramSize = romBank0[0x149]; + + // Seek to begining of ROM + fseek(romFile, 0x00, SEEK_SET); + + // Get the ROM banks + romBankList = new Byte[2 << romSize][0x4000]; + for (int i = 0; i < (2 << romSize); ++i) + { + memset(romBankList[i], 0x00, 0x4000); + } + for (int i = 0; i < (2 << romSize); ++i) + { + fread(romBankList[i], 1, 16384, romFile); + } + + // Set RAM banks + int ramBanks = 0; + switch (ramSize) + { + case 0: + ramBanks = 0; + break; + case 2: + ramBanks = 1; + break; + case 3: + ramBanks = 4; + break; + case 4: + ramBanks = 16; + break; + case 5: + ramBanks = 8; + break; + } + ramBankList = new Byte[ramBanks][0x2000]; + for (int i = 0; i < ramBanks; ++i) + { + memset(ramBankList[i], 0x00, 0x2000); + } + if (ramBanks > 0) + { + externalRam = ramBankList[0]; + } + + // Set the RAM Existence Mask + // Tells if External RAM is available in the Cartridge + if ((mbcMode == 2 or mbcMode == 3) and ramBanks > 0) + { + ramExistenceMask = 1; + } + + // Set the ROM Bank Number Mask + // Tells the useful bits in the ROM Bank number depending on ROM size + romBankNumberMask = ((2 << romSize) < 0b100000) ? (2 << romSize) : 0b100000; + romBankNumberMask -= 1; + + // Set the RAM Bank Number Mask for ROM + // Tells the useful bits in the RAM Bank number for ROM depending on ROM size + if (romSize > 4) + { + ramBankNumberMaskForRom = ((1 << (romSize - 4)) < 0b100) ? (1 << (romSize - 4)) : 0b100; + ramBankNumberMaskForRom -= 1; + } + + // Set the RAM Bank Number Mask for RAM + // Tells the useful bits in the RAM Bank number for RAM depending on RAM size + if (ramBanks > 0) + { + ramBankNumberMaskForRam = (ramBanks < 0b100) ? ramBanks : 0b100; + ramBankNumberMaskForRam -= 1; + } +} + +void MemoryMap::unloadBootRom() +{ + fseek(romFile, 0x00, SEEK_SET); + fread(romBank0, 1, 256, romFile); +} + +void MemoryMap::bankRom() +{ + + Byte completeBankNumber = romBankNumber; + + if (completeBankNumber == 0) + { + completeBankNumber += 1; + } + + completeBankNumber &= romBankNumberMask; + completeBankNumber |= ((ramBankNumber & ramBankNumberMaskForRom) << 5); + + romBank0 = romBankList[((ramBankNumber & ramBankNumberMaskForRom) << 5) & (bankingModeSelect * 0b1100000)]; + romBank1 = romBankList[completeBankNumber]; +} + +void MemoryMap::bankRam() +{ + if (ramExistenceMask) + { + externalRam = ramBankList[(ramBankNumber & ramBankNumberMaskForRam) & (bankingModeSelect * 0b11)]; + } } \ No newline at end of file diff --git a/src/mmap.h b/src/mmap.h index c04790e..5b0a0aa 100644 --- a/src/mmap.h +++ b/src/mmap.h @@ -8,6 +8,11 @@ class MemoryMap { private: + Byte mbcMode; + + FILE* bootRomFile; + FILE* romFile; + // First ROM Bank // 16 KB 0x0000 - 0x3FFF // Contains the first 16 KB of the ROM @@ -88,13 +93,115 @@ class MemoryMap // Signals which interrupt must take place Byte* reg_IF; + // The LCD Control Register + // Stays in the I/O Ports at 0xFF40 + Byte* reg_LCDC; + + // The SCX Register + // Stays in the I/O Ports at 0xFF43 + Byte* reg_SCX; + + // The SCY Register + // Stays in the I/O Ports at 0xFF42 + Byte* reg_SCY; + + // The BGP Register + // Stays in the I/O Ports at 0xFF47 + Byte* reg_BGP; + + // The OBP0 Register + // Stays in the I/O Ports at 0xFF48 + Byte* reg_OBP0; + + // The OBP1 Register + // Stays in the I/O Ports at 0xFF49 + Byte* reg_OBP1; + + // The LY Register + // Stays in the I/O Ports at 0xFF44 + Byte* reg_LY; + + // The LYC Register + // Stays in the I/O Ports at 0xFF45 + Byte* reg_LYC; + + // The STAT Register + // Stays in the I/O Ports at 0xFF41 + Byte* reg_STAT; + + // The WY Register + // Stays in the I/O Ports at 0xFF4A + Byte* reg_WY; + + // The WX Register + // Stays in the I/O Ports at 0xFF4B + Byte* reg_WX; + + // Number indicating number of ROM Banks available + // Number of ROM Banks = ( 2 << romSize ) + Byte romSize; + + // Number of RAM Banks available + // Number of ramSize RAM Banks + // 0 0 + // 2 1 + // 3 4 + // 4 16 + // 5 8 + Byte ramSize; + + // All ROM Banks + // 16KiB each + Byte (*romBankList)[0x4000]; + + // All RAM Banks + // 8KiB each + Byte (*ramBankList)[0x2000]; + + // 1 bit MBC register + // Tells whether External RAM is enabled for read and write + bool ramEnable; + + // 5 bit MBC register + // Tells which ROM Bank to use + Byte romBankNumber; + + // 2 bit MBC register + // Tells which RAM Bank to use + // Might also tell which ROM Bank to use in case of ROM > 512KiB + Byte ramBankNumber; + + // 1 bit MBC register + // Selects the banking mode + bool bankingModeSelect; + + // 1 bit mask + // Tells if External RAM is available in the Cartridge + bool ramExistenceMask; + + // 5 bit mask + // Tells the number of bits of ROM Bank Number that are useful + Byte romBankNumberMask; + + // 2 bit mask + // Tells the number of bits of RAM Bank Number that are useful for ROM + Byte ramBankNumberMaskForRom; + + // 2 bit mask + // Tells the number of bits of RAM Bank Number that are useful for RAM + Byte ramBankNumberMaskForRam; + public: + Byte* joyPadState; + // Constructor MemoryMap(); // Destructor ~MemoryMap(); + void readInput(Byte value); + // Returns the ROM Bank 0 Byte* getRomBank0() const { return romBank0; } @@ -138,6 +245,12 @@ class MemoryMap // increments the divider register void updateDividerRegister() { (*reg_DIV)++; } + // Map the boot and game to memory4 + void mapRom(); + + // Unload boot ROM after boot execution + void unloadBootRom(); + // gets the reg_TAC Byte getRegTAC() { return *reg_TAC; } @@ -153,9 +266,63 @@ class MemoryMap // gets the reg_IE Byte getRegIE() { return *interruptEnableRegister; } + // gets the reg_LCDC + Byte getRegLCDC() { return *reg_LCDC; } + + // gets the reg_SCX + Byte getRegSCX() { return *reg_SCX; } + + // gets the reg_SCY + Byte getRegSCY() { return *reg_SCY; } + + // gets the reg_BGP + Byte getRegBGP() { return *reg_BGP; } + + // gets the reg_OBP0 + Byte getRegOBP0() { return *reg_OBP0; } + + // gets the reg_OBP1 + Byte getRegOBP1() { return *reg_OBP1; } + + // gets the reg_LY + Byte getRegLY() { return *reg_LY; } + + // gets the reg_LYC + Byte getRegLYC() { return *reg_LYC; } + + // gets the reg_STAT + Byte getRegSTAT() { return *reg_STAT; } + + // gets the reg_WY + Byte getRegWY() { return *reg_WY; } + + // gets the reg_WX + Byte getRegWX() { return *reg_WX; } + // sets the reg_TIMA void setRegTIMA(Byte value) { *reg_TIMA = value; } // sets the reg_IF to request an interrupt void setRegIF(Byte value) { *reg_IF |= value; } + + // sets the reg_LY + void setRegLY(Byte value) { *reg_LY = value; } + + // sets the reg_STAT + void setRegSTAT(Byte value) { *reg_STAT = value; } + + // sets the reg_WY + void setRegWY(Byte value) { *reg_WY = value; } + + // sets the boot ROM + void setBootRomFile(FILE* file) { bootRomFile = file; } + + // sets the ROM file + void setRomFile(FILE* file) { romFile = file; } + + // Change ROM Banking according to the set registers + void bankRom(); + + // Change RAM Banking according to the set registers + void bankRam(); }; \ No newline at end of file diff --git a/src/types.h b/src/types.h index 097a7ac..edd42d2 100644 --- a/src/types.h +++ b/src/types.h @@ -7,3 +7,4 @@ typedef unsigned char Byte; typedef char SByte; typedef unsigned short Word; typedef signed short SWord; +typedef unsigned int color; \ No newline at end of file