formatting
This commit is contained in:
@@ -2,17 +2,11 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
bool AddressSpace::getBootromState() const {
|
bool AddressSpace::getBootromState() const { return bootromLoaded; }
|
||||||
return bootromLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddressSpace::unmapBootrom() {
|
void AddressSpace::unmapBootrom() { bootromLoaded = false; }
|
||||||
bootromLoaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddressSpace::mapBootrom() {
|
void AddressSpace::mapBootrom() { bootromLoaded = true; }
|
||||||
bootromLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddressSpace::loadBootrom(const std::string &filename) {
|
void AddressSpace::loadBootrom(const std::string &filename) {
|
||||||
std::ifstream file;
|
std::ifstream file;
|
||||||
@@ -45,8 +39,7 @@ void AddressSpace::loadGame(const std::string& filename) {
|
|||||||
rom.seekg(0, std::ios::beg);
|
rom.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
game.reserve(rom_size);
|
game.reserve(rom_size);
|
||||||
game.insert(game.begin(),
|
game.insert(game.begin(), std::istream_iterator<Byte>(rom),
|
||||||
std::istream_iterator<Byte>(rom),
|
|
||||||
std::istream_iterator<Byte>());
|
std::istream_iterator<Byte>());
|
||||||
|
|
||||||
memoryLayout.romBank0 = game.data();
|
memoryLayout.romBank0 = game.data();
|
||||||
@@ -57,11 +50,10 @@ void AddressSpace::dmaTransfer() {
|
|||||||
dmaTransferRequested = false;
|
dmaTransferRequested = false;
|
||||||
const Word addr = memoryLayout.DMA << 8;
|
const Word addr = memoryLayout.DMA << 8;
|
||||||
for (int i = 0; i < 0xA0; i++) {
|
for (int i = 0; i < 0xA0; i++) {
|
||||||
const Byte data = (*this)[addr + i];;
|
const Byte data = (*this)[addr + i];
|
||||||
|
;
|
||||||
(*this)[0xFE00 + i] = data;
|
(*this)[0xFE00 + i] = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::setTesting(const bool state) {
|
void AddressSpace::setTesting(const bool state) { testing = state; }
|
||||||
testing = state;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#ifndef ADDRESSSPACE_HPP
|
#ifndef ADDRESSSPACE_HPP
|
||||||
#define ADDRESSSPACE_HPP
|
#define ADDRESSSPACE_HPP
|
||||||
#include <fstream>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -42,7 +42,8 @@ public:
|
|||||||
Byte TMA;
|
Byte TMA;
|
||||||
Byte TAC = 0xF8;
|
Byte TAC = 0xF8;
|
||||||
// interrupt flag and enable
|
// interrupt flag and enable
|
||||||
Byte IF = 0xE1;;
|
Byte IF = 0xE1;
|
||||||
|
;
|
||||||
// Sound registers
|
// Sound registers
|
||||||
Byte NR10;
|
Byte NR10;
|
||||||
Byte NR11;
|
Byte NR11;
|
||||||
@@ -168,7 +169,8 @@ public:
|
|||||||
case 0xFF06:
|
case 0xFF06:
|
||||||
return memoryLayout.TMA;
|
return memoryLayout.TMA;
|
||||||
case 0xFF07:
|
case 0xFF07:
|
||||||
return memoryLayout.TAC | 0xF8;;
|
return memoryLayout.TAC | 0xF8;
|
||||||
|
;
|
||||||
case 0xFF0F:
|
case 0xFF0F:
|
||||||
return memoryLayout.IF | 0xE0;
|
return memoryLayout.IF | 0xE0;
|
||||||
case 0xFF10:
|
case 0xFF10:
|
||||||
@@ -379,5 +381,4 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif // ADDRESSSPACE_HPP
|
#endif // ADDRESSSPACE_HPP
|
||||||
|
|||||||
@@ -47,7 +47,6 @@
|
|||||||
#define X_FLIP 5
|
#define X_FLIP 5
|
||||||
#define OBJ_PALETTE 4
|
#define OBJ_PALETTE 4
|
||||||
|
|
||||||
|
|
||||||
#define ROM_BANK_SIZE 0x4000
|
#define ROM_BANK_SIZE 0x4000
|
||||||
#define RAM_BANK_SIZE 0x2000
|
#define RAM_BANK_SIZE 0x2000
|
||||||
|
|
||||||
@@ -95,7 +94,8 @@ enum MBCType {
|
|||||||
MBC3TimerBattery = 0x0F,
|
MBC3TimerBattery = 0x0F,
|
||||||
MBC3TimerRamBattery = 0x10,
|
MBC3TimerRamBattery = 0x10,
|
||||||
MBC3 = 0x11,
|
MBC3 = 0x11,
|
||||||
MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in Pocket Monsters: Crystal Version.
|
MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in
|
||||||
|
// Pocket Monsters: Crystal Version.
|
||||||
MBC3RamBattery = 0x13,
|
MBC3RamBattery = 0x13,
|
||||||
MBC5 = 0x19,
|
MBC5 = 0x19,
|
||||||
MBC5Ram = 0x1A,
|
MBC5Ram = 0x1A,
|
||||||
@@ -112,10 +112,14 @@ enum MBCType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum PPUMode {
|
enum PPUMode {
|
||||||
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period.
|
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during
|
||||||
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period.
|
// horizontal blanking period.
|
||||||
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation.
|
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during
|
||||||
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen.
|
// vertical blanking period.
|
||||||
|
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only,
|
||||||
|
// sprite evaluation.
|
||||||
|
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual
|
||||||
|
// pixel transfer to the screen.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1541,7 +1541,8 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printf("Unsupported extended opcode found: PC:0x%.2x, Opcode:0xcb%.2x\n", PC, addressSpace[PC]);
|
printf("Unsupported extended opcode found: PC:0x%.2x, Opcode:0xcb%.2x\n",
|
||||||
|
PC, addressSpace[PC]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include <iostream>
|
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
void GameBoy::addCycles(const uint8_t ticks) {
|
void GameBoy::addCycles(const uint8_t ticks) {
|
||||||
cycles += ticks;
|
cycles += ticks;
|
||||||
@@ -34,17 +34,10 @@ GameboyTestState GameBoy::runTest(GameboyTestState initial) {
|
|||||||
for (const auto &[addr, val] : initial.RAM) {
|
for (const auto &[addr, val] : initial.RAM) {
|
||||||
returnRAM.emplace_back(addr, addressSpace[addr]);
|
returnRAM.emplace_back(addr, addressSpace[addr]);
|
||||||
}
|
}
|
||||||
return {
|
return {PC, SP, AF.hi, AF.lo, BC.hi, BC.lo,
|
||||||
PC, SP,
|
DE.hi, DE.lo, HL.hi, HL.lo, returnRAM};
|
||||||
AF.hi, AF.lo,
|
|
||||||
BC.hi, BC.lo,
|
|
||||||
DE.hi, DE.lo,
|
|
||||||
HL.hi, HL.lo,
|
|
||||||
returnRAM
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GameBoy::start(const std::string &bootrom, const std::string &game) {
|
void GameBoy::start(const std::string &bootrom, const std::string &game) {
|
||||||
addressSpace.loadBootrom(bootrom);
|
addressSpace.loadBootrom(bootrom);
|
||||||
addressSpace.loadGame(game);
|
addressSpace.loadGame(game);
|
||||||
@@ -146,35 +139,34 @@ void GameBoy::start(const std::string& bootrom, const std::string& game) {
|
|||||||
prevTMA = addressSpace.memoryLayout.TMA;
|
prevTMA = addressSpace.memoryLayout.TMA;
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
printf(
|
printf("A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: "
|
||||||
"A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: %.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
|
"%.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
|
||||||
AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC],
|
AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC,
|
||||||
readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
|
readOnlyAddressSpace[PC], readOnlyAddressSpace[PC + 1],
|
||||||
|
readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
|
||||||
|
|
||||||
|
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n",
|
||||||
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
|
// cycles, readOnlyAddressSpace[PC],
|
||||||
// cyclesSinceLastScanline(), currentMode);
|
// cyclesSinceLastScanline(), currentMode);
|
||||||
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
||||||
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
|
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
|
||||||
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
|
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
|
||||||
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
|
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
|
||||||
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
|
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT),
|
||||||
// printf("\n");
|
// (*LY), (*LYC)); printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!halted) {
|
if (!halted) {
|
||||||
opcodeResolver();
|
opcodeResolver();
|
||||||
addressSpace.MBCUpdate();
|
addressSpace.MBCUpdate();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(4);
|
addCycles(4);
|
||||||
}
|
}
|
||||||
timingHandler();
|
timingHandler();
|
||||||
interruptHandler();
|
interruptHandler();
|
||||||
if (ppuEnabled) {
|
if (ppuEnabled) {
|
||||||
ppuUpdate();
|
ppuUpdate();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
ppuCycles = 2;
|
ppuCycles = 2;
|
||||||
lastScanline = 0;
|
lastScanline = 0;
|
||||||
lastRefresh = 0;
|
lastRefresh = 0;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#ifndef GBPP_SRC_GAMEBOY_HPP_
|
#ifndef GBPP_SRC_GAMEBOY_HPP_
|
||||||
#define GBPP_SRC_GAMEBOY_HPP_
|
#define GBPP_SRC_GAMEBOY_HPP_
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <SDL.h>
|
|
||||||
#include "defines.hpp"
|
|
||||||
#include "addressSpace.hpp"
|
#include "addressSpace.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
#include "testing.hpp"
|
#include "testing.hpp"
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
union RegisterPair {
|
union RegisterPair {
|
||||||
Word reg; // register.reg == (hi << 8) + lo. (hi is more significant than lo)
|
Word reg; // register.reg == (hi << 8) + lo. (hi is more significant than lo)
|
||||||
@@ -21,7 +21,8 @@ union RegisterPair {
|
|||||||
class GameBoy {
|
class GameBoy {
|
||||||
// T-cycles not M-cycles (4 T-cycles = 1 M-cycle)
|
// T-cycles not M-cycles (4 T-cycles = 1 M-cycle)
|
||||||
uint64_t cycles = 0;
|
uint64_t cycles = 0;
|
||||||
//Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
|
// Start at 2 T-cycles
|
||||||
|
// https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
|
||||||
uint64_t ppuCycles = 2;
|
uint64_t ppuCycles = 2;
|
||||||
bool ppuEnabled = false;
|
bool ppuEnabled = false;
|
||||||
uint16_t lastOpTicks = 0;
|
uint16_t lastOpTicks = 0;
|
||||||
@@ -60,7 +61,8 @@ class GameBoy {
|
|||||||
bool stopped = false;
|
bool stopped = false;
|
||||||
|
|
||||||
// 3 colour channels
|
// 3 colour channels
|
||||||
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
uint32_t *framebuffer =
|
||||||
|
new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
||||||
SDL_Window *screen = nullptr;
|
SDL_Window *screen = nullptr;
|
||||||
SDL_Renderer *renderer = nullptr;
|
SDL_Renderer *renderer = nullptr;
|
||||||
SDL_Texture *texture = nullptr;
|
SDL_Texture *texture = nullptr;
|
||||||
@@ -113,43 +115,30 @@ class GameBoy {
|
|||||||
void addCycles(Byte ticks);
|
void addCycles(Byte ticks);
|
||||||
|
|
||||||
// OPCODE FUNCTIONS
|
// OPCODE FUNCTIONS
|
||||||
template <typename T>
|
template <typename T> void ld(T &dest, T src);
|
||||||
void ld(T& dest, T src);
|
|
||||||
void ldW(Word destAddr, Word src);
|
void ldW(Word destAddr, Word src);
|
||||||
template <typename T>
|
template <typename T> void orBitwise(T &dest, T src);
|
||||||
void orBitwise(T& dest, T src);
|
template <typename T> void andBitwise(T &dest, T src);
|
||||||
template <typename T>
|
template <typename T> void xorBitwise(T &dest, T src);
|
||||||
void andBitwise(T& dest, T src);
|
|
||||||
template <typename T>
|
|
||||||
void xorBitwise(T& dest, T src);
|
|
||||||
void bit(Byte testBit, Byte reg);
|
void bit(Byte testBit, Byte reg);
|
||||||
void extendedOpcodeResolver();
|
void extendedOpcodeResolver();
|
||||||
static void set(uint8_t testBit, uint8_t ®);
|
static void set(uint8_t testBit, uint8_t ®);
|
||||||
static void res(uint8_t testBit, uint8_t ®);
|
static void res(uint8_t testBit, uint8_t ®);
|
||||||
template <typename T>
|
template <typename T> void jp(T address);
|
||||||
void jp(T address);
|
template <typename T> bool jrNZ(T offset);
|
||||||
template <typename T>
|
template <class T> bool jrNC(T offset);
|
||||||
bool jrNZ(T offset);
|
template <class T> bool jrC(T offset);
|
||||||
template <class T>
|
template <typename T> void inc(T ®);
|
||||||
bool jrNC(T offset);
|
template <typename T> void call(T address);
|
||||||
template <class T>
|
|
||||||
bool jrC(T offset);
|
|
||||||
template <typename T>
|
|
||||||
void inc(T& reg);
|
|
||||||
template <typename T>
|
|
||||||
void call(T address);
|
|
||||||
void halt();
|
void halt();
|
||||||
void daa();
|
void daa();
|
||||||
void stop();
|
void stop();
|
||||||
void cp(Byte value);
|
void cp(Byte value);
|
||||||
template <typename T>
|
template <typename T> void dec(T ®);
|
||||||
void dec(T& reg);
|
template <typename T> bool jrZ(T offset);
|
||||||
template <typename T>
|
|
||||||
bool jrZ(T offset);
|
|
||||||
void sub(Byte value);
|
void sub(Byte value);
|
||||||
void sbc(Byte value);
|
void sbc(Byte value);
|
||||||
template <typename T>
|
template <typename T> void jr(T OFFSET);
|
||||||
void jr(T OFFSET);
|
|
||||||
void push(Word reg);
|
void push(Word reg);
|
||||||
void rl(Byte ®);
|
void rl(Byte ®);
|
||||||
void sla(Byte ®);
|
void sla(Byte ®);
|
||||||
@@ -162,13 +151,10 @@ class GameBoy {
|
|||||||
void rlc(Byte ®);
|
void rlc(Byte ®);
|
||||||
void rlca();
|
void rlca();
|
||||||
void rla();
|
void rla();
|
||||||
template <typename T>
|
template <typename T> void pop(T ®);
|
||||||
void pop(T& reg);
|
template <typename T> void rst(T address);
|
||||||
template <typename T>
|
|
||||||
void rst(T address);
|
|
||||||
void ret();
|
void ret();
|
||||||
template <typename T>
|
template <typename T> void add(T ®, T value);
|
||||||
void add(T& reg, T value);
|
|
||||||
void adc(Byte value);
|
void adc(Byte value);
|
||||||
void cpl();
|
void cpl();
|
||||||
void scf();
|
void scf();
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
||||||
return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt);
|
return readOnlyAddressSpace.memoryLayout.IE &
|
||||||
|
static_cast<Byte>(1 << interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::setInterrupt(const Byte interrupt) {
|
void GameBoy::setInterrupt(const Byte interrupt) {
|
||||||
@@ -16,32 +17,37 @@ void GameBoy::resetInterrupt(const Byte interrupt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::interruptHandler() {
|
void GameBoy::interruptHandler() {
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
VBLANK_INTERRUPT)) {
|
static_cast<Byte>(1 << VBLANK_INTERRUPT) &&
|
||||||
|
testInterruptEnabled(VBLANK_INTERRUPT)) {
|
||||||
if (IME)
|
if (IME)
|
||||||
VBlankHandle();
|
VBlankHandle();
|
||||||
halted = false;
|
halted = false;
|
||||||
}
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
LCD_STAT_INTERRUPT)) {
|
static_cast<Byte>(1 << LCD_STAT_INTERRUPT) &&
|
||||||
|
testInterruptEnabled(LCD_STAT_INTERRUPT)) {
|
||||||
if (IME)
|
if (IME)
|
||||||
LCDStatHandle();
|
LCDStatHandle();
|
||||||
halted = false;
|
halted = false;
|
||||||
}
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
TIMER_INTERRUPT)) {
|
static_cast<Byte>(1 << TIMER_INTERRUPT) &&
|
||||||
|
testInterruptEnabled(TIMER_INTERRUPT)) {
|
||||||
if (IME)
|
if (IME)
|
||||||
timerHandle();
|
timerHandle();
|
||||||
halted = false;
|
halted = false;
|
||||||
}
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
SERIAL_INTERRUPT)) {
|
static_cast<Byte>(1 << SERIAL_INTERRUPT) &&
|
||||||
|
testInterruptEnabled(SERIAL_INTERRUPT)) {
|
||||||
if (IME)
|
if (IME)
|
||||||
serialHandle();
|
serialHandle();
|
||||||
halted = false;
|
halted = false;
|
||||||
}
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
JOYPAD_INTERRUPT)) {
|
static_cast<Byte>(1 << JOYPAD_INTERRUPT) &&
|
||||||
|
testInterruptEnabled(JOYPAD_INTERRUPT)) {
|
||||||
if (IME)
|
if (IME)
|
||||||
joypadHandle();
|
joypadHandle();
|
||||||
halted = false;
|
halted = false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "gameboy.hpp"
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
void GameBoy::joypadHandler() {
|
void GameBoy::joypadHandler() {
|
||||||
const Byte joyP = addressSpace.memoryLayout.JOYP;
|
const Byte joyP = addressSpace.memoryLayout.JOYP;
|
||||||
|
|||||||
25
src/main.cpp
25
src/main.cpp
@@ -1,8 +1,8 @@
|
|||||||
#include <string>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <vector>
|
|
||||||
#include "3rdParty/json.hpp"
|
#include "3rdParty/json.hpp"
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -32,7 +32,6 @@ void runJSONTests(GameBoy* gb) {
|
|||||||
for (const auto &entry : fs::directory_iterator(path))
|
for (const auto &entry : fs::directory_iterator(path))
|
||||||
testFiles.emplace_back(entry.path());
|
testFiles.emplace_back(entry.path());
|
||||||
|
|
||||||
|
|
||||||
for (const auto &testFile : testFiles) {
|
for (const auto &testFile : testFiles) {
|
||||||
std::ifstream file(testFile);
|
std::ifstream file(testFile);
|
||||||
std::cout << "Running test: " << testFile << std::endl;
|
std::cout << "Running test: " << testFile << std::endl;
|
||||||
@@ -42,10 +41,10 @@ void runJSONTests(GameBoy* gb) {
|
|||||||
// create state
|
// create state
|
||||||
std::vector<std::tuple<Word, Byte>> initialRAM;
|
std::vector<std::tuple<Word, Byte>> initialRAM;
|
||||||
for (int i = 0; i < test["initial"]["ram"].size(); i++)
|
for (int i = 0; i < test["initial"]["ram"].size(); i++)
|
||||||
initialRAM.emplace_back(test["initial"]["ram"][i][0], test["initial"]["ram"][i][1]);
|
initialRAM.emplace_back(test["initial"]["ram"][i][0],
|
||||||
|
test["initial"]["ram"][i][1]);
|
||||||
|
|
||||||
GameboyTestState initialState = {
|
GameboyTestState initialState = {test["initial"]["pc"],
|
||||||
test["initial"]["pc"],
|
|
||||||
test["initial"]["sp"],
|
test["initial"]["sp"],
|
||||||
test["initial"]["a"],
|
test["initial"]["a"],
|
||||||
test["initial"]["f"],
|
test["initial"]["f"],
|
||||||
@@ -55,8 +54,7 @@ void runJSONTests(GameBoy* gb) {
|
|||||||
test["initial"]["e"],
|
test["initial"]["e"],
|
||||||
test["initial"]["h"],
|
test["initial"]["h"],
|
||||||
test["initial"]["l"],
|
test["initial"]["l"],
|
||||||
initialRAM
|
initialRAM};
|
||||||
};
|
|
||||||
|
|
||||||
// run
|
// run
|
||||||
GameboyTestState result = gb->runTest(initialState);
|
GameboyTestState result = gb->runTest(initialState);
|
||||||
@@ -64,10 +62,10 @@ void runJSONTests(GameBoy* gb) {
|
|||||||
// compare new state to expected
|
// compare new state to expected
|
||||||
std::vector<std::tuple<Word, Byte>> finalRAM;
|
std::vector<std::tuple<Word, Byte>> finalRAM;
|
||||||
for (int i = 0; i < test["final"]["ram"].size(); i++)
|
for (int i = 0; i < test["final"]["ram"].size(); i++)
|
||||||
finalRAM.emplace_back(test["final"]["ram"][i][0], test["final"]["ram"][i][1]);
|
finalRAM.emplace_back(test["final"]["ram"][i][0],
|
||||||
|
test["final"]["ram"][i][1]);
|
||||||
|
|
||||||
GameboyTestState finalState = {
|
GameboyTestState finalState = {test["final"]["pc"],
|
||||||
test["final"]["pc"],
|
|
||||||
test["final"]["sp"],
|
test["final"]["sp"],
|
||||||
test["final"]["a"],
|
test["final"]["a"],
|
||||||
test["final"]["f"],
|
test["final"]["f"],
|
||||||
@@ -77,8 +75,7 @@ void runJSONTests(GameBoy* gb) {
|
|||||||
test["final"]["e"],
|
test["final"]["e"],
|
||||||
test["final"]["h"],
|
test["final"]["h"],
|
||||||
test["final"]["l"],
|
test["final"]["l"],
|
||||||
finalRAM
|
finalRAM};
|
||||||
};
|
|
||||||
|
|
||||||
if (finalState != result) {
|
if (finalState != result) {
|
||||||
std::cout << "Test " << testFile << " failed!" << std::endl;
|
std::cout << "Test " << testFile << " failed!" << std::endl;
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
void GameBoy::setFlag(const Byte bit) {
|
void GameBoy::setFlag(const Byte bit) { AF.lo |= (1 << bit); }
|
||||||
AF.lo |= (1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoy::resetFlag(const Byte bit) {
|
void GameBoy::resetFlag(const Byte bit) { AF.lo &= ~(1 << bit); }
|
||||||
AF.lo &= ~(1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GameBoy::getFlag(const Byte bit) const {
|
bool GameBoy::getFlag(const Byte bit) const { return (AF.lo >> bit) & 1; }
|
||||||
return (AF.lo >> bit) & 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Word GameBoy::getWordPC() {
|
Word GameBoy::getWordPC() {
|
||||||
RegisterPair word = {0};
|
RegisterPair word = {0};
|
||||||
@@ -22,9 +16,7 @@ Word GameBoy::getWordPC() {
|
|||||||
return word.reg;
|
return word.reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Byte GameBoy::getBytePC() {
|
Byte GameBoy::getBytePC() { return readOnlyAddressSpace[PC + 1]; }
|
||||||
return readOnlyAddressSpace[PC + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
Word GameBoy::getWordSP() {
|
Word GameBoy::getWordSP() {
|
||||||
RegisterPair word = {0};
|
RegisterPair word = {0};
|
||||||
@@ -36,27 +28,22 @@ Word GameBoy::getWordSP() {
|
|||||||
return word.reg;
|
return word.reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Byte GameBoy::getByteSP() {
|
Byte GameBoy::getByteSP() { return readOnlyAddressSpace[SP++]; }
|
||||||
return readOnlyAddressSpace[SP++];
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoy::ret() {
|
void GameBoy::ret() {
|
||||||
PC = readOnlyAddressSpace[SP++];
|
PC = readOnlyAddressSpace[SP++];
|
||||||
PC |= readOnlyAddressSpace[SP++] << 8;
|
PC |= readOnlyAddressSpace[SP++] << 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::ld(T &dest, T src) {
|
||||||
void GameBoy::ld(T& dest, T src) {
|
|
||||||
if constexpr (std::is_same_v<T, Byte>) {
|
if constexpr (std::is_same_v<T, Byte>) {
|
||||||
if (&dest == &addressSpace.memoryLayout.DIV) {
|
if (&dest == &addressSpace.memoryLayout.DIV) {
|
||||||
addressSpace.memoryLayout.DIV = 0x00;
|
addressSpace.memoryLayout.DIV = 0x00;
|
||||||
lastDivUpdate = cycles;
|
lastDivUpdate = cycles;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
dest = src;
|
dest = src;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// 16-bit register pair write
|
// 16-bit register pair write
|
||||||
dest = src;
|
dest = src;
|
||||||
}
|
}
|
||||||
@@ -67,8 +54,7 @@ void GameBoy::ldW(const Word destAddr, const Word src) {
|
|||||||
addressSpace[destAddr + 1] = static_cast<Byte>((src & 0xFF00) >> 8);
|
addressSpace[destAddr + 1] = static_cast<Byte>((src & 0xFF00) >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::add(T ®, T value) {
|
||||||
void GameBoy::add(T& reg, T value) {
|
|
||||||
if (sizeof(reg) == sizeof(Byte)) {
|
if (sizeof(reg) == sizeof(Byte)) {
|
||||||
if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10)
|
if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10)
|
||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
@@ -115,7 +101,6 @@ void GameBoy::adc(const Byte value) {
|
|||||||
else
|
else
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
|
|
||||||
|
|
||||||
AF.hi += value + carry;
|
AF.hi += value + carry;
|
||||||
|
|
||||||
if (AF.hi == 0)
|
if (AF.hi == 0)
|
||||||
@@ -149,7 +134,8 @@ void GameBoy::sbc(const Byte value) {
|
|||||||
const Byte carry = getFlag(CARRY_FLAG) ? 1 : 0;
|
const Byte carry = getFlag(CARRY_FLAG) ? 1 : 0;
|
||||||
const Byte result = AF.hi - value - carry;
|
const Byte result = AF.hi - value - carry;
|
||||||
|
|
||||||
if ((static_cast<unsigned>(AF.hi) - static_cast<unsigned>(value) - carry) > 0xFF)
|
if ((static_cast<unsigned>(AF.hi) - static_cast<unsigned>(value) - carry) >
|
||||||
|
0xFF)
|
||||||
setFlag(CARRY_FLAG);
|
setFlag(CARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
@@ -178,8 +164,7 @@ void GameBoy::daa() {
|
|||||||
if (getFlag(HALFCARRY_FLAG)) {
|
if (getFlag(HALFCARRY_FLAG)) {
|
||||||
AF.hi -= 0x06;
|
AF.hi -= 0x06;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (getFlag(CARRY_FLAG) || (AF.hi & 0xFF) > 0x99) {
|
if (getFlag(CARRY_FLAG) || (AF.hi & 0xFF) > 0x99) {
|
||||||
AF.hi += 0x60;
|
AF.hi += 0x60;
|
||||||
setFlag(CARRY_FLAG);
|
setFlag(CARRY_FLAG);
|
||||||
@@ -196,8 +181,7 @@ void GameBoy::daa() {
|
|||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::orBitwise(T &dest, T src) {
|
||||||
void GameBoy::orBitwise(T& dest, T src) {
|
|
||||||
dest |= src;
|
dest |= src;
|
||||||
|
|
||||||
if (dest == 0)
|
if (dest == 0)
|
||||||
@@ -210,8 +194,7 @@ void GameBoy::orBitwise(T& dest, T src) {
|
|||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::andBitwise(T &dest, T src) {
|
||||||
void GameBoy::andBitwise(T& dest, T src) {
|
|
||||||
dest &= src;
|
dest &= src;
|
||||||
|
|
||||||
if (dest == 0)
|
if (dest == 0)
|
||||||
@@ -224,8 +207,7 @@ void GameBoy::andBitwise(T& dest, T src) {
|
|||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::xorBitwise(T &dest, T src) {
|
||||||
void GameBoy::xorBitwise(T& dest, T src) {
|
|
||||||
dest ^= src;
|
dest ^= src;
|
||||||
|
|
||||||
if (dest == 0)
|
if (dest == 0)
|
||||||
@@ -248,70 +230,63 @@ void GameBoy::bit(Byte testBit, Byte reg) {
|
|||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::set(const Byte testBit, Byte& reg) {
|
void GameBoy::set(const Byte testBit, Byte ®) { reg |= (1 << testBit); }
|
||||||
reg |= (1 << testBit);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoy::res(const Byte testBit, Byte& reg) {
|
void GameBoy::res(const Byte testBit, Byte ®) { reg &= ~(1 << testBit); }
|
||||||
reg &= ~(1 << testBit);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
template <typename T> void GameBoy::jr(T offset) {
|
||||||
template <typename T>
|
|
||||||
void GameBoy::jr(T offset) {
|
|
||||||
PC += static_cast<int8_t>(offset) + 2; // PC moves 2 from original instruction
|
PC += static_cast<int8_t>(offset) + 2; // PC moves 2 from original instruction
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> bool GameBoy::jrNZ(T offset) {
|
||||||
bool GameBoy::jrNZ(T offset) {
|
|
||||||
bool jumped = false;
|
bool jumped = false;
|
||||||
if (!getFlag(ZERO_FLAG)) // if not set
|
if (!getFlag(ZERO_FLAG)) // if not set
|
||||||
{
|
{
|
||||||
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction
|
PC += static_cast<int8_t>(offset) +
|
||||||
|
2; // PC moves 2 from the original instruction
|
||||||
jumped = true;
|
jumped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jumped;
|
return jumped;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> bool GameBoy::jrZ(T offset) {
|
||||||
bool GameBoy::jrZ(T offset) {
|
|
||||||
bool jumped = false;
|
bool jumped = false;
|
||||||
if (getFlag(ZERO_FLAG)) // if not set
|
if (getFlag(ZERO_FLAG)) // if not set
|
||||||
{
|
{
|
||||||
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction
|
PC += static_cast<int8_t>(offset) +
|
||||||
|
2; // PC moves 2 from the original instruction
|
||||||
jumped = true;
|
jumped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jumped;
|
return jumped;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> bool GameBoy::jrNC(T offset) {
|
||||||
bool GameBoy::jrNC(T offset) {
|
|
||||||
bool jumped = false;
|
bool jumped = false;
|
||||||
if (!getFlag(CARRY_FLAG)) // if not set
|
if (!getFlag(CARRY_FLAG)) // if not set
|
||||||
{
|
{
|
||||||
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction
|
PC += static_cast<int8_t>(offset) +
|
||||||
|
2; // PC moves 2 from the original instruction
|
||||||
jumped = true;
|
jumped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jumped;
|
return jumped;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> bool GameBoy::jrC(T offset) {
|
||||||
bool GameBoy::jrC(T offset) {
|
|
||||||
bool jumped = false;
|
bool jumped = false;
|
||||||
if (getFlag(CARRY_FLAG)) // if not set
|
if (getFlag(CARRY_FLAG)) // if not set
|
||||||
{
|
{
|
||||||
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction
|
PC += static_cast<int8_t>(offset) +
|
||||||
|
2; // PC moves 2 from the original instruction
|
||||||
jumped = true;
|
jumped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jumped;
|
return jumped;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::inc(T ®) {
|
||||||
void GameBoy::inc(T& reg) {
|
|
||||||
reg += 1;
|
reg += 1;
|
||||||
|
|
||||||
if (sizeof(reg) == sizeof(Byte)) {
|
if (sizeof(reg) == sizeof(Byte)) {
|
||||||
@@ -322,7 +297,8 @@ void GameBoy::inc(T& reg) {
|
|||||||
|
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
|
|
||||||
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
// halfcarry test
|
||||||
|
// https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
||||||
if ((reg & 0x0F) == 0)
|
if ((reg & 0x0F) == 0)
|
||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
else
|
else
|
||||||
@@ -330,8 +306,7 @@ void GameBoy::inc(T& reg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::call(T address) {
|
||||||
void GameBoy::call(T address) {
|
|
||||||
push(PC + 3);
|
push(PC + 3);
|
||||||
PC = address;
|
PC = address;
|
||||||
}
|
}
|
||||||
@@ -355,8 +330,7 @@ void GameBoy::cp(const Byte value) //compare
|
|||||||
setFlag(SUBTRACT_FLAG);
|
setFlag(SUBTRACT_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::dec(T ®) {
|
||||||
void GameBoy::dec(T& reg) {
|
|
||||||
reg -= 1;
|
reg -= 1;
|
||||||
|
|
||||||
if (sizeof(reg) == sizeof(Byte)) {
|
if (sizeof(reg) == sizeof(Byte)) {
|
||||||
@@ -367,7 +341,8 @@ void GameBoy::dec(T& reg) {
|
|||||||
|
|
||||||
setFlag(SUBTRACT_FLAG);
|
setFlag(SUBTRACT_FLAG);
|
||||||
|
|
||||||
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
|
// halfcarry test
|
||||||
|
// https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
|
||||||
if (0 > (((reg + 1) & 0xf) - (reg & 0xf)))
|
if (0 > (((reg + 1) & 0xf) - (reg & 0xf)))
|
||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
else
|
else
|
||||||
@@ -610,8 +585,7 @@ void GameBoy::srl(Byte& reg) {
|
|||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::pop(T ®) {
|
||||||
void GameBoy::pop(T& reg) {
|
|
||||||
reg = getWordSP();
|
reg = getWordSP();
|
||||||
AF.reg &= 0xFFF0;
|
AF.reg &= 0xFFF0;
|
||||||
}
|
}
|
||||||
@@ -622,13 +596,9 @@ void GameBoy::push(const Word reg) {
|
|||||||
addressSpace[--SP] = reg & 0xFF;
|
addressSpace[--SP] = reg & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::jp(T address) { PC = address; }
|
||||||
void GameBoy::jp(T address) {
|
|
||||||
PC = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> void GameBoy::rst(T address) {
|
||||||
void GameBoy::rst(T address) {
|
|
||||||
PC += 1;
|
PC += 1;
|
||||||
push(PC);
|
push(PC);
|
||||||
PC = address;
|
PC = address;
|
||||||
@@ -656,9 +626,7 @@ void GameBoy::ccf() {
|
|||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::stop() {
|
void GameBoy::stop() { stopped = true; }
|
||||||
stopped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoy::opcodeResolver() {
|
void GameBoy::opcodeResolver() {
|
||||||
if (readOnlyAddressSpace[PC] != 0xCB) {
|
if (readOnlyAddressSpace[PC] != 0xCB) {
|
||||||
@@ -859,8 +827,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
jumped = jrNZ(getBytePC());
|
jumped = jrNZ(getBytePC());
|
||||||
if (jumped) {
|
if (jumped) {
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
}
|
}
|
||||||
@@ -913,8 +880,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
jumped = jrZ(getBytePC());
|
jumped = jrZ(getBytePC());
|
||||||
if (jumped) {
|
if (jumped) {
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
}
|
}
|
||||||
@@ -967,8 +933,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
jumped = jrNC(getBytePC());
|
jumped = jrNC(getBytePC());
|
||||||
if (jumped) {
|
if (jumped) {
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
}
|
}
|
||||||
@@ -1021,8 +986,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
jumped = jrC(getBytePC());
|
jumped = jrC(getBytePC());
|
||||||
if (jumped) {
|
if (jumped) {
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
}
|
}
|
||||||
@@ -1843,8 +1807,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (!getFlag(ZERO_FLAG)) {
|
if (!getFlag(ZERO_FLAG)) {
|
||||||
ret();
|
ret();
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
}
|
}
|
||||||
@@ -1860,8 +1823,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (!getFlag(ZERO_FLAG)) {
|
if (!getFlag(ZERO_FLAG)) {
|
||||||
jp(getWordPC());
|
jp(getWordPC());
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -1876,8 +1838,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (!getFlag(ZERO_FLAG)) {
|
if (!getFlag(ZERO_FLAG)) {
|
||||||
call(getWordPC());
|
call(getWordPC());
|
||||||
addCycles(24);
|
addCycles(24);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -1904,8 +1865,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (getFlag(ZERO_FLAG)) {
|
if (getFlag(ZERO_FLAG)) {
|
||||||
ret();
|
ret();
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
}
|
}
|
||||||
@@ -1920,8 +1880,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (getFlag(ZERO_FLAG)) {
|
if (getFlag(ZERO_FLAG)) {
|
||||||
jp(getWordPC());
|
jp(getWordPC());
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -1931,8 +1890,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (getFlag(ZERO_FLAG)) {
|
if (getFlag(ZERO_FLAG)) {
|
||||||
call(getWordPC());
|
call(getWordPC());
|
||||||
addCycles(24);
|
addCycles(24);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -1958,8 +1916,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (!getFlag(CARRY_FLAG)) {
|
if (!getFlag(CARRY_FLAG)) {
|
||||||
ret();
|
ret();
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
}
|
}
|
||||||
@@ -1975,8 +1932,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (!getFlag(CARRY_FLAG)) {
|
if (!getFlag(CARRY_FLAG)) {
|
||||||
jp(getWordPC());
|
jp(getWordPC());
|
||||||
addCycles(24);
|
addCycles(24);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -1986,8 +1942,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (!getFlag(CARRY_FLAG)) {
|
if (!getFlag(CARRY_FLAG)) {
|
||||||
call(getWordPC());
|
call(getWordPC());
|
||||||
addCycles(24);
|
addCycles(24);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -2014,8 +1969,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (getFlag(CARRY_FLAG)) {
|
if (getFlag(CARRY_FLAG)) {
|
||||||
ret();
|
ret();
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
}
|
}
|
||||||
@@ -2032,8 +1986,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (getFlag(CARRY_FLAG)) {
|
if (getFlag(CARRY_FLAG)) {
|
||||||
jp(getWordPC());
|
jp(getWordPC());
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -2043,8 +1996,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
if (getFlag(CARRY_FLAG)) {
|
if (getFlag(CARRY_FLAG)) {
|
||||||
call(getWordPC());
|
call(getWordPC());
|
||||||
addCycles(24);
|
addCycles(24);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
}
|
}
|
||||||
@@ -2096,8 +2048,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
addCycles(16);
|
addCycles(16);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xE8:
|
case 0xE8: {
|
||||||
{
|
|
||||||
const int16_t immediate = static_cast<int8_t>(getBytePC());
|
const int16_t immediate = static_cast<int8_t>(getBytePC());
|
||||||
|
|
||||||
if ((SP & 0xF) + (immediate & 0xF) > 0xF)
|
if ((SP & 0xF) + (immediate & 0xF) > 0xF)
|
||||||
@@ -2117,8 +2068,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
|
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case 0xE9:
|
case 0xE9:
|
||||||
jp(HL.reg);
|
jp(HL.reg);
|
||||||
@@ -2183,8 +2133,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
addCycles(16);
|
addCycles(16);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xF8:
|
case 0xF8: {
|
||||||
{
|
|
||||||
const int16_t immediate = static_cast<int8_t>(getBytePC());
|
const int16_t immediate = static_cast<int8_t>(getBytePC());
|
||||||
HL.reg = SP + immediate;
|
HL.reg = SP + immediate;
|
||||||
|
|
||||||
@@ -2193,20 +2142,17 @@ void GameBoy::opcodeResolver() {
|
|||||||
else
|
else
|
||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
|
|
||||||
|
|
||||||
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
|
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
|
||||||
setFlag(CARRY_FLAG);
|
setFlag(CARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
|
|
||||||
|
|
||||||
resetFlag(ZERO_FLAG);
|
resetFlag(ZERO_FLAG);
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
|
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case 0xF9:
|
case 0xF9:
|
||||||
ld(SP, HL.reg);
|
ld(SP, HL.reg);
|
||||||
@@ -2240,10 +2186,10 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printf("Unsupported opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]);
|
printf("Unsupported opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC,
|
||||||
|
addressSpace[PC]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
extendedOpcodeResolver();
|
extendedOpcodeResolver();
|
||||||
}
|
}
|
||||||
|
|||||||
154
src/ppu.cpp
154
src/ppu.cpp
@@ -1,5 +1,5 @@
|
|||||||
#include "gameboy.hpp"
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
#include "gameboy.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -33,32 +33,36 @@ void GameBoy::ppuUpdate() {
|
|||||||
checkPPUMode();
|
checkPPUMode();
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
// bug on DMG models triggers a STAT interrupt anytime the STAT register is
|
||||||
// Road Rage and Zerd no Denetsu rely on this
|
// written Road Rage and Zerd no Denetsu rely on this
|
||||||
|
|
||||||
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
|
// Check for STAT interrupts and request if needed (e.g., when entering
|
||||||
const bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
|
// specific modes)
|
||||||
const bool drawingInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
|
const bool hBlankInterruptEnabled =
|
||||||
const bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
|
||||||
|
const bool drawingInterruptEnabled =
|
||||||
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
|
||||||
|
const bool oamInterruptEnabled =
|
||||||
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
|
||||||
const bool previousInterruptLine = statInteruptLine;
|
const bool previousInterruptLine = statInteruptLine;
|
||||||
|
|
||||||
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
|
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
|
||||||
currentMode == PPUMode::mode3 && drawingInterruptEnabled ||
|
currentMode == PPUMode::mode3 && drawingInterruptEnabled ||
|
||||||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
|
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
|
||||||
statInteruptLine = true;
|
statInteruptLine = true;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
statInteruptLine = false;
|
statInteruptLine = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool lyLycInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 6);
|
const bool lyLycInterruptEnabled =
|
||||||
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 6);
|
||||||
|
|
||||||
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC) {
|
if (readOnlyAddressSpace.memoryLayout.LY ==
|
||||||
|
readOnlyAddressSpace.memoryLayout.LYC) {
|
||||||
addressSpace.memoryLayout.STAT |= (1 << 2);
|
addressSpace.memoryLayout.STAT |= (1 << 2);
|
||||||
if (lyLycInterruptEnabled)
|
if (lyLycInterruptEnabled)
|
||||||
statInteruptLine = true;
|
statInteruptLine = true;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
||||||
}
|
}
|
||||||
if (statInteruptLine && !previousInterruptLine)
|
if (statInteruptLine && !previousInterruptLine)
|
||||||
@@ -98,8 +102,7 @@ void GameBoy::incLY() {
|
|||||||
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
||||||
addressSpace.memoryLayout.LY = 0;
|
addressSpace.memoryLayout.LY = 0;
|
||||||
windowLineCounter = 0;
|
windowLineCounter = 0;
|
||||||
}
|
} else if (addressSpace.memoryLayout.LY == 144) {
|
||||||
else if (addressSpace.memoryLayout.LY == 144) {
|
|
||||||
// VBlank Period
|
// VBlank Period
|
||||||
SDL2present();
|
SDL2present();
|
||||||
setPPUMode(PPUMode::mode1);
|
setPPUMode(PPUMode::mode1);
|
||||||
@@ -153,18 +156,22 @@ void GameBoy::drawLine() {
|
|||||||
uint32_t *currentLinePixels = framebuffer + lineStartIndex;
|
uint32_t *currentLinePixels = framebuffer + lineStartIndex;
|
||||||
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
|
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
|
||||||
|
|
||||||
|
const uint16_t backgroundMapAddr =
|
||||||
const uint16_t backgroundMapAddr = LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
||||||
const uint16_t windowMapAddr = LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
const uint16_t windowMapAddr =
|
||||||
const uint16_t tileDataTableAddr = LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800;
|
LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
||||||
|
const uint16_t tileDataTableAddr =
|
||||||
|
LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800;
|
||||||
const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
|
const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
|
||||||
|
|
||||||
// BG
|
// BG
|
||||||
if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
|
if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
|
||||||
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
||||||
const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
|
const uint16_t xIndex =
|
||||||
|
(pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
|
||||||
// 256 pixels in total BG width
|
// 256 pixels in total BG width
|
||||||
const uint16_t yIndex = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
|
const uint16_t yIndex =
|
||||||
|
(line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
|
||||||
// 256 pixels in total BG height
|
// 256 pixels in total BG height
|
||||||
|
|
||||||
const uint16_t tileUpper = (yIndex / 8) << 5;
|
const uint16_t tileUpper = (yIndex / 8) << 5;
|
||||||
@@ -172,39 +179,46 @@ void GameBoy::drawLine() {
|
|||||||
const uint16_t tileIndex = tileUpper + tileLower;
|
const uint16_t tileIndex = tileUpper + tileLower;
|
||||||
|
|
||||||
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
||||||
const int16_t tileID = signedIndex
|
const int16_t tileID =
|
||||||
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
||||||
: readOnlyAddressSpace[tileAddr];
|
: readOnlyAddressSpace[tileAddr];
|
||||||
|
|
||||||
uint16_t tileDataAddr;
|
uint16_t tileDataAddr;
|
||||||
if (signedIndex)
|
if (signedIndex)
|
||||||
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around
|
tileDataAddr = tileDataTableAddr +
|
||||||
|
(((tileID + 128) % 256) << 4); // For signed, wrap around
|
||||||
else
|
else
|
||||||
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
|
|
||||||
|
|
||||||
const uint8_t lineOffset = yIndex % 8;
|
const uint8_t lineOffset = yIndex % 8;
|
||||||
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
const uint8_t tileRowData1 =
|
||||||
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
||||||
|
const uint8_t tileRowData2 =
|
||||||
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||||
|
|
||||||
// get pixel data (2 bits)
|
// get pixel data (2 bits)
|
||||||
const uint8_t colourBit = 7 - (xIndex % 8);
|
const uint8_t colourBit = 7 - (xIndex % 8);
|
||||||
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
|
||||||
|
((tileRowData1 >> colourBit) & 0x1);
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
// Apply the BGP register for palette mapping
|
||||||
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
const uint8_t palette =
|
||||||
|
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
||||||
|
|
||||||
currentLinePixels[pixel] = getColourFromPalette(palette);
|
currentLinePixels[pixel] = getColourFromPalette(palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the window to be displayed on a scanline, the following conditions must be met:
|
// For the window to be displayed on a scanline, the following conditions
|
||||||
// WY condition was triggered: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only)
|
// must be met: WY condition was triggered: i.e. at some point in this frame
|
||||||
// WX condition was triggered: i.e. the current X coordinate being rendered + 7 was equal to WX
|
// the value of WY was equal to LY (checked at the start of Mode 2 only) WX
|
||||||
// Window enable bit in LCDC is set
|
// condition was triggered: i.e. the current X coordinate being rendered + 7
|
||||||
|
// was equal to WX Window enable bit in LCDC is set
|
||||||
// Window
|
// Window
|
||||||
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
|
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
|
||||||
const int16_t windowX = static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
|
const int16_t windowX =
|
||||||
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && windowX < RESOLUTION_X && line >= windowY) {
|
static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
|
||||||
|
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 &&
|
||||||
|
windowX < RESOLUTION_X && line >= windowY) {
|
||||||
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
|
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
|
||||||
const uint16_t yIndex = windowLineCounter;
|
const uint16_t yIndex = windowLineCounter;
|
||||||
const uint16_t windowTileUpper = (yIndex / 8) << 5;
|
const uint16_t windowTileUpper = (yIndex / 8) << 5;
|
||||||
@@ -215,28 +229,31 @@ void GameBoy::drawLine() {
|
|||||||
const uint16_t tileIndex = windowTileUpper + windowTileLower;
|
const uint16_t tileIndex = windowTileUpper + windowTileLower;
|
||||||
const uint16_t tileAddr = windowMapAddr + tileIndex;
|
const uint16_t tileAddr = windowMapAddr + tileIndex;
|
||||||
|
|
||||||
const int16_t tileID = signedIndex
|
const int16_t tileID =
|
||||||
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
||||||
: readOnlyAddressSpace[tileAddr];
|
: readOnlyAddressSpace[tileAddr];
|
||||||
|
|
||||||
uint16_t tileDataAddr;
|
uint16_t tileDataAddr;
|
||||||
if (signedIndex)
|
if (signedIndex)
|
||||||
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
|
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) *
|
||||||
|
16); // For signed, wrap around
|
||||||
else
|
else
|
||||||
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
|
|
||||||
|
|
||||||
const uint8_t lineOffset = yIndex % 8;
|
const uint8_t lineOffset = yIndex % 8;
|
||||||
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
const uint8_t tileRowData1 =
|
||||||
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
||||||
|
const uint8_t tileRowData2 =
|
||||||
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||||
|
|
||||||
// get pixel data (2 bits)
|
// get pixel data (2 bits)
|
||||||
const uint8_t colourBit = 7 - (xIndex % 8);
|
const uint8_t colourBit = 7 - (xIndex % 8);
|
||||||
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) &
|
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
|
||||||
0x1);
|
((tileRowData1 >> colourBit) & 0x1);
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
// Apply the BGP register for palette mapping
|
||||||
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
const uint8_t palette =
|
||||||
|
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
||||||
|
|
||||||
currentLinePixels[pixel] = getColourFromPalette(palette);
|
currentLinePixels[pixel] = getColourFromPalette(palette);
|
||||||
}
|
}
|
||||||
@@ -248,8 +265,7 @@ void GameBoy::drawLine() {
|
|||||||
uint32_t oamPixels[RESOLUTION_X];
|
uint32_t oamPixels[RESOLUTION_X];
|
||||||
std::fill_n(oamPixels, RESOLUTION_X, 0);
|
std::fill_n(oamPixels, RESOLUTION_X, 0);
|
||||||
|
|
||||||
constexpr
|
constexpr Word oamAddrStart = 0xFE00;
|
||||||
Word oamAddrStart = 0xFE00;
|
|
||||||
const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8;
|
const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8;
|
||||||
|
|
||||||
int objects[10] = {0};
|
int objects[10] = {0};
|
||||||
@@ -267,7 +283,8 @@ void GameBoy::drawLine() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//sort by xPos (lower has higher priority when rendering) and then earlier objects
|
// sort by xPos (lower has higher priority when rendering) and then earlier
|
||||||
|
// objects
|
||||||
for (int i = 0; i < found; i++) {
|
for (int i = 0; i < found; i++) {
|
||||||
for (int j = 0; j < found - i - 1; j++) {
|
for (int j = 0; j < found - i - 1; j++) {
|
||||||
const int xPos1 = readOnlyAddressSpace[objects[j] + 1];
|
const int xPos1 = readOnlyAddressSpace[objects[j] + 1];
|
||||||
@@ -281,7 +298,6 @@ void GameBoy::drawLine() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) {
|
for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) {
|
||||||
const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16;
|
const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16;
|
||||||
const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8;
|
const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8;
|
||||||
@@ -296,9 +312,9 @@ void GameBoy::drawLine() {
|
|||||||
? addressSpace.memoryLayout.OBP1
|
? addressSpace.memoryLayout.OBP1
|
||||||
: addressSpace.memoryLayout.OBP0;
|
: addressSpace.memoryLayout.OBP0;
|
||||||
|
|
||||||
for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8; pixel++) {
|
for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8;
|
||||||
constexpr
|
pixel++) {
|
||||||
Word objectTileAddr = 0x8000;
|
constexpr Word objectTileAddr = 0x8000;
|
||||||
if (pixel < 0)
|
if (pixel < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -306,8 +322,7 @@ void GameBoy::drawLine() {
|
|||||||
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP;
|
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP;
|
||||||
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) ||
|
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) ||
|
||||||
colour == getColourFromPalette((BGP >> 4) & 0x3) ||
|
colour == getColourFromPalette((BGP >> 4) & 0x3) ||
|
||||||
colour == getColourFromPalette((BGP >> 6) & 0x3)
|
colour == getColourFromPalette((BGP >> 6) & 0x3))) {
|
||||||
)) {
|
|
||||||
oamPixels[pixel] = colour;
|
oamPixels[pixel] = colour;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -319,24 +334,26 @@ void GameBoy::drawLine() {
|
|||||||
if (yFlip)
|
if (yFlip)
|
||||||
objectY = (spriteHeight - 1) - objectY;
|
objectY = (spriteHeight - 1) - objectY;
|
||||||
|
|
||||||
const Word objTileDataAddr = spriteHeight == 8
|
const Word objTileDataAddr =
|
||||||
? objectTileAddr + (tileIndex * 16)
|
spriteHeight == 8 ? objectTileAddr + (tileIndex * 16)
|
||||||
: objectTileAddr + ((tileIndex & 0xFE) * 16);
|
: objectTileAddr + ((tileIndex & 0xFE) * 16);
|
||||||
|
|
||||||
const Byte tileRow = objectY * 2;
|
const Byte tileRow = objectY * 2;
|
||||||
const Byte tileRowData1 = readOnlyAddressSpace[objTileDataAddr + tileRow];
|
const Byte tileRowData1 =
|
||||||
const Byte tileRowData2 = readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
|
readOnlyAddressSpace[objTileDataAddr + tileRow];
|
||||||
|
const Byte tileRowData2 =
|
||||||
|
readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
|
||||||
|
|
||||||
const int bit = 7 - objectX;
|
const int bit = 7 - objectX;
|
||||||
const int colorIndex = ((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
|
const int colorIndex =
|
||||||
|
((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
|
||||||
|
|
||||||
// 0 is always transparent
|
// 0 is always transparent
|
||||||
if (colorIndex != 0) {
|
if (colorIndex != 0) {
|
||||||
const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
|
const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
|
||||||
const uint32_t finalColor = getColourFromPalette(paletteColor);
|
const uint32_t finalColor = getColourFromPalette(paletteColor);
|
||||||
oamPixels[pixel] = finalColor;
|
oamPixels[pixel] = finalColor;
|
||||||
}
|
} else if (oamPixels[pixel] == 0) {
|
||||||
else if (oamPixels[pixel] == 0) {
|
|
||||||
oamPixels[pixel] = currentLinePixels[pixel];
|
oamPixels[pixel] = currentLinePixels[pixel];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,17 +369,18 @@ void GameBoy::drawLine() {
|
|||||||
|
|
||||||
void GameBoy::SDL2setup() {
|
void GameBoy::SDL2setup() {
|
||||||
SDL_Init(SDL_INIT_EVERYTHING);
|
SDL_Init(SDL_INIT_EVERYTHING);
|
||||||
screen = SDL_CreateWindow("GameBoy++",
|
screen =
|
||||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
SDL_CreateWindow("GameBoy++", SDL_WINDOWPOS_UNDEFINED,
|
||||||
RESOLUTION_X, RESOLUTION_Y,
|
SDL_WINDOWPOS_UNDEFINED, RESOLUTION_X, RESOLUTION_Y, 0);
|
||||||
0);
|
|
||||||
|
|
||||||
// Create an SDL renderer to draw on the window
|
// Create an SDL renderer to draw on the window
|
||||||
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
renderer = SDL_CreateRenderer(
|
||||||
|
screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
// Create an SDL texture to hold the framebuffer data
|
// Create an SDL texture to hold the framebuffer data
|
||||||
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||||
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y);
|
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X,
|
||||||
|
RESOLUTION_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2destroy() const {
|
void GameBoy::SDL2destroy() const {
|
||||||
@@ -373,11 +391,11 @@ void GameBoy::SDL2destroy() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2present() {
|
void GameBoy::SDL2present() {
|
||||||
SDL_UpdateTexture(texture, nullptr, framebuffer, RESOLUTION_X * sizeof(uint32_t));
|
SDL_UpdateTexture(texture, nullptr, framebuffer,
|
||||||
|
RESOLUTION_X * sizeof(uint32_t));
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||||
|
|
||||||
|
|
||||||
frameTime = SDL_GetTicks() - frameStart;
|
frameTime = SDL_GetTicks() - frameStart;
|
||||||
|
|
||||||
if (frameDelay > frameTime) {
|
if (frameDelay > frameTime) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#ifndef TESTING_HPP
|
#ifndef TESTING_HPP
|
||||||
#define TESTING_HPP
|
#define TESTING_HPP
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
|
||||||
#include <tuple>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "defines.hpp"
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct GameboyTestState {
|
struct GameboyTestState {
|
||||||
Word PC;
|
Word PC;
|
||||||
@@ -25,19 +25,15 @@ struct GameboyTestState {
|
|||||||
std::vector<std::tuple<Word, Byte>> RAM;
|
std::vector<std::tuple<Word, Byte>> RAM;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const GameboyTestState& lhs, const GameboyTestState& rhs) {
|
inline bool operator==(const GameboyTestState &lhs,
|
||||||
|
const GameboyTestState &rhs) {
|
||||||
for (int i = 0; i < lhs.RAM.size(); i++) {
|
for (int i = 0; i < lhs.RAM.size(); i++) {
|
||||||
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
|
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (lhs.A == rhs.A &&
|
return (lhs.A == rhs.A && lhs.F == rhs.F && lhs.B == rhs.B &&
|
||||||
lhs.F == rhs.F &&
|
lhs.C == rhs.C && lhs.D == rhs.D && lhs.E == rhs.E &&
|
||||||
lhs.B == rhs.B &&
|
lhs.H == rhs.H && lhs.L == rhs.L);
|
||||||
lhs.C == rhs.C &&
|
|
||||||
lhs.D == rhs.D &&
|
|
||||||
lhs.E == rhs.E &&
|
|
||||||
lhs.H == rhs.H &&
|
|
||||||
lhs.L == rhs.L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // TESTING_HPP
|
#endif // TESTING_HPP
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
//handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
|
// handles most of the behavoir as described here:
|
||||||
|
// https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
|
||||||
void GameBoy::timingHandler() {
|
void GameBoy::timingHandler() {
|
||||||
//can't do this as we use cycles for PPU timing but this is what should happen
|
// can't do this as we use cycles for PPU timing but this is what should
|
||||||
//addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF;
|
// happen addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF;
|
||||||
|
|
||||||
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
||||||
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
||||||
@@ -28,14 +29,15 @@ void GameBoy::timingHandler() {
|
|||||||
TIMAFrequency = 256;
|
TIMAFrequency = 256;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use prevTMA regardless)
|
// if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use
|
||||||
|
// prevTMA regardless)
|
||||||
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
|
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
|
||||||
if (cycles - lastTIMAUpdate >= TIMAFrequency) {
|
if (cycles - lastTIMAUpdate >= TIMAFrequency) {
|
||||||
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
|
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
|
||||||
addressSpace.memoryLayout.TIMA = prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
|
addressSpace.memoryLayout.TIMA =
|
||||||
|
prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
|
||||||
setInterrupt(TIMER_INTERRUPT);
|
setInterrupt(TIMER_INTERRUPT);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
addressSpace.memoryLayout.TIMA += increments;
|
addressSpace.memoryLayout.TIMA += increments;
|
||||||
lastTIMAUpdate += increments * TIMAFrequency;
|
lastTIMAUpdate += increments * TIMAFrequency;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user