passed json tests

This commit is contained in:
2024-04-04 02:56:13 -07:00
parent 5c573789c9
commit 028eca6ffc
511 changed files with 25664 additions and 115 deletions

24765
src/3rdParty/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -45,3 +45,7 @@ void AddressSpace::loadGame(const std::string& filename) {
memoryLayout.romBank0 = game.data();
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
}
void AddressSpace::setTesting(const bool state) {
testing = state;
}

View File

@@ -13,6 +13,9 @@ class AddressSpace {
bool bootromLoaded = true;
Byte bootrom[BOOTROM_SIZE] = {0};
std::vector<Byte> game;
bool testing;
Byte testRam[0xFFFF];
Byte* cartridgeRam = nullptr;
public:
AddressSpace() {
@@ -20,8 +23,6 @@ public:
memoryLayout = {};
}
Byte* cartridgeRam = nullptr;
struct {
Byte* romBank0; //[ROM_BANK_SIZE] Mapped to 0x0000
Byte* romBankSwitch; //[ROM_BANK_SIZE] Mapped to 0x4000
@@ -117,8 +118,12 @@ public:
Byte latchClockData = 0x00;
Byte ramBankRTCRegister = 0x00;
void setTesting(bool state);
//read
Byte operator[](const Word address) const {
if (testing)
return testRam[address];
if (address < 0x0100 && bootromLoaded)
return bootrom[address];
if (address < 0x4000)
@@ -248,6 +253,8 @@ public:
//write
Byte& operator[](const Word address) {
if (testing)
return testRam[address];
if (address < 0x0100 && bootromLoaded)
return bootrom[address];
if (address < 0x8000)

View File

@@ -767,7 +767,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x7F:
bit(3, AF.hi);
bit(7, AF.hi);
PC += 1;
addCycles(8);
break;

View File

@@ -9,6 +9,42 @@ void GameBoy::addCycles(const uint8_t ticks) {
lastOpTicks = ticks;
}
GameboyTestState GameBoy::runTest(GameboyTestState initial) {
addressSpace.setTesting(true);
PC = initial.PC;
SP = initial.SP;
AF.hi = initial.A;
AF.lo = initial.F;
BC.hi = initial.B;
BC.lo = initial.C;
DE.hi = initial.D;
DE.lo = initial.E;
HL.hi = initial.H;
HL.lo = initial.L;
addressSpace.memoryLayout.IE = 1;
for (const auto& [addr, val] : initial.RAM) {
addressSpace[addr] = val;
}
opcodeResolver();
std::vector<std::tuple<Word, Byte>> returnRAM;
for (const auto& [addr, val] : initial.RAM) {
returnRAM.emplace_back(addr, addressSpace[addr]);
}
return {
PC, SP,
AF.hi, AF.lo,
BC.hi, BC.lo,
DE.hi, DE.lo,
HL.hi, HL.lo,
returnRAM
};
}
void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.loadBootrom(bootrom);
addressSpace.loadGame(game);
@@ -20,7 +56,7 @@ void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.memoryLayout.SC = 0x7E;
bool quit = false;
bool setIME = false;
bool display = false;
while (!quit) {
@@ -69,6 +105,14 @@ void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.memoryLayout.LY = 0x00;
addressSpace.memoryLayout.STAT &= 0xfc;
}
if (setIME) {
IME = 1;
setIME = false;
}
if (IME_togge) {
setIME = true;
IME_togge = false;
}
}
rendered = false;
}

View File

@@ -3,13 +3,11 @@
#include <filesystem>
#include <cstdint>
#include <cstring>
#include <string>
#include <fstream>
#include <vector>
#include <SDL.h>
#include "defines.hpp"
#include "addressSpace.hpp"
#include "testing.hpp"
union RegisterPair {
Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo)
@@ -34,6 +32,9 @@ class GameBoy {
bool rendered = false;
uint8_t IME = 0; //enables interupts
// EI is actually "disable interrupts for one instruction, then enable them"
// This keeps track of that
bool IME_togge = false;
//Accumulator and flags
RegisterPair AF = {0};
@@ -97,7 +98,7 @@ class GameBoy {
//OPCODE FUNCTIONS
template <typename T>
void ld(T& dest, T src);
void ldW(Byte& dest, Word src);
void ldW(Word destAddr, Word src);
template <typename T>
void orBitwise(T& dest, T src);
template <typename T>
@@ -123,20 +124,16 @@ class GameBoy {
void halt();
void daa();
void stop();
template <typename T>
void cp(T value);
void cp(Byte value);
template <typename T>
void dec(T& reg);
template <typename T>
bool jrZ(T offset);
template <typename T>
void sub(T value);
template <class T>
void sbc(T value);
void sub(Byte value);
void sbc(Byte value);
template <typename T>
void jr(T OFFSET);
template <typename T>
void push(T reg);
void push(Word reg);
void rl(Byte& reg);
void sla(Byte& reg);
void sra(uint8_t& reg);
@@ -155,8 +152,7 @@ class GameBoy {
void ret();
template <typename T>
void add(T& reg, T value);
template <class T>
void adc(T& reg, T value);
void adc(Byte value);
void cpl();
void scf();
void ccf();
@@ -166,6 +162,8 @@ public:
void start(std::string bootrom, std::string game);
void SDL2setup();
void SDL2destroy() const;
GameboyTestState runTest(GameboyTestState initial);
};
#endif //GBPP_SRC_GAMEBOY_HPP_

View File

@@ -1,12 +1,89 @@
#include <string>
#include <filesystem>
#include <vector>
#include "3rdParty/json.hpp"
#include "gameboy.hpp"
namespace fs = std::filesystem;
using json = nlohmann::json;
void runJSONTests(GameBoy* gb);
int main(int argc, char** argv) {
auto* gb = new GameBoy();
gb->SDL2setup();
gb->start("../dmg_boot.bin", "../roms/03-op_sp,hl.gb");
//runJSONTests(gb);
gb->start("../dmg_boot.bin", "../roms/07-jr,jp,call,ret,rst.gb");
gb->SDL2destroy();
delete gb;
return 0;
}
void runJSONTests(GameBoy* gb) {
std::string path = "../tests/sm83/v1";
std::vector<std::string> testFiles;
int failed = 0;
for (const auto& entry : fs::directory_iterator(path))
testFiles.emplace_back(entry.path());
for (const auto& testFile : testFiles) {
std::ifstream file(testFile);
std::cout << "Running test: " << testFile << std::endl;
const json tests = json::parse(file);
for (auto& test : tests) {
//create state
std::vector<std::tuple<Word, Byte>> initialRAM;
for (int i = 0; i < test["initial"]["ram"].size(); i++)
initialRAM.emplace_back(test["initial"]["ram"][i][0], test["initial"]["ram"][i][1]);
GameboyTestState initialState = {
test["initial"]["pc"],
test["initial"]["sp"],
test["initial"]["a"],
test["initial"]["f"],
test["initial"]["b"],
test["initial"]["c"],
test["initial"]["d"],
test["initial"]["e"],
test["initial"]["h"],
test["initial"]["l"],
initialRAM
};
//run
GameboyTestState result = gb->runTest(initialState);
//compare new state to expected
std::vector<std::tuple<Word, Byte>> finalRAM;
for (int i = 0; i < test["final"]["ram"].size(); i++)
finalRAM.emplace_back(test["final"]["ram"][i][0], test["final"]["ram"][i][1]);
GameboyTestState finalState = {
test["final"]["pc"],
test["final"]["sp"],
test["final"]["a"],
test["final"]["f"],
test["final"]["b"],
test["final"]["c"],
test["final"]["d"],
test["final"]["e"],
test["final"]["h"],
test["final"]["l"],
finalRAM
};
if (finalState != result) {
std::cout << "Test " << testFile << " failed!" << std::endl;
failed += 1;
break;
}
}
}
if (!failed)
std::cout << "Success!" << std::endl;
else
std::cout << failed << "/" << testFiles.size() << " failed!" << std::endl;
}

View File

@@ -41,7 +41,8 @@ Byte GameBoy::getByteSP() {
}
void GameBoy::ret() {
pop(PC);
PC = readOnlyAddressSpace[SP++];
PC |= readOnlyAddressSpace[SP++] << 8;
}
template <typename T>
@@ -61,22 +62,18 @@ void GameBoy::ld(T& dest, T src) {
}
}
void GameBoy::ldW(Byte& dest, const Word src) {
if (sizeof(src) == sizeof(Word)) {
addressSpace[dest] = static_cast<Byte>(src & 0xFF00) >> 8;
addressSpace[dest + 1] = static_cast<Byte>(src & 0xFF);
}
void GameBoy::ldW(const Word destAddr, const Word src) {
addressSpace[destAddr] = static_cast<Byte>(src & 0xFF);
addressSpace[destAddr + 1] = static_cast<Byte>((src & 0xFF00) >> 8);
}
template <typename T>
void GameBoy::add(T& reg, T value) {
if (sizeof(reg) == sizeof(Byte)) {
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xFF) + (reg & 0xFF)) & 0x100) == 0x100)
setFlag(CARRY_FLAG);
else
@@ -84,12 +81,10 @@ void GameBoy::add(T& reg, T value) {
}
if (sizeof(reg) == sizeof(Word)) {
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xFFF) + (reg & 0xFFF)) & 0x1000) == 0x1000)
if (((value & 0xFFF) + (reg & 0xFFF)) & 0x1000)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((static_cast<unsigned>(value) + static_cast<unsigned>(reg)) & 0x10000)
setFlag(CARRY_FLAG);
else
@@ -108,39 +103,22 @@ void GameBoy::add(T& reg, T value) {
resetFlag(SUBTRACT_FLAG);
}
template <typename T>
void GameBoy::adc(T& reg, T value) {
T carry = getFlag(CARRY_FLAG) ? 1 : 0;
void GameBoy::adc(const Byte value) {
Byte carry = getFlag(CARRY_FLAG) ? 1 : 0;
if (sizeof(reg) == sizeof(Byte)) {
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xFF) + (reg & 0xFF)) & 0x100) == 0x100)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
}
if ((AF.hi & 0xF) + (value & 0xF) + carry > 0xF)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
if ((value & 0xFF) + (AF.hi & 0xFF) + carry > 0xFF)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
if (sizeof(reg) == sizeof(Word)) {
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xFFF) + (reg & 0xFFF)) & 0x1000) == 0x1000)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xFFFF) + (reg & 0xFFFF)) & 0x10000) == 0x10000)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
}
reg += value + carry;
AF.hi += value + carry;
if (reg == 0)
if (AF.hi == 0)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
@@ -148,33 +126,30 @@ void GameBoy::adc(T& reg, T value) {
resetFlag(SUBTRACT_FLAG);
}
template <typename T>
void GameBoy::sub(T value) {
if (AF.hi < value) {
void GameBoy::sub(const Byte value) {
if (AF.hi < value)
setFlag(CARRY_FLAG);
resetFlag(ZERO_FLAG);
}
else if (AF.hi == value) {
setFlag(ZERO_FLAG);
else
resetFlag(CARRY_FLAG);
}
if (AF.hi == value)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
if ((AF.hi & 0xf) < (value & 0xf))
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
AF.hi -= value;
setFlag(SUBTRACT_FLAG);
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
if (0 > (((AF.hi) & 0xf) - (value & 0xf)))
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
}
template <typename T>
void GameBoy::sbc(T value) {
T carry = getFlag(CARRY_FLAG) ? 1 : 0;
T result = AF.hi - value - carry;
void GameBoy::sbc(const Byte value) {
const Byte carry = getFlag(CARRY_FLAG) ? 1 : 0;
const Byte result = AF.hi - value - carry;
if (AF.hi < value + carry)
if ((static_cast<unsigned>(AF.hi) - static_cast<unsigned>(value) - carry) > 0xFF)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
@@ -184,14 +159,14 @@ void GameBoy::sbc(T value) {
else
resetFlag(ZERO_FLAG);
setFlag(SUBTRACT_FLAG);
if ((AF.hi & 0xF) < (value & 0xF) + carry)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
AF.hi = result;
setFlag(SUBTRACT_FLAG);
}
//https://gbdev.gg8.se/wiki/articles/DAA
@@ -361,25 +336,23 @@ void GameBoy::call(T address) {
PC = address;
}
template <typename T>
void GameBoy::cp(T value) //compare
void GameBoy::cp(const Byte value) //compare
{
resetFlag(ZERO_FLAG);
resetFlag(CARRY_FLAG);
resetFlag(HALFCARRY_FLAG);
if (AF.hi == value) {
setFlag(ZERO_FLAG);
}
if ((AF.hi & 0xF) < (value & 0xF)) {
setFlag(HALFCARRY_FLAG);
}
if (AF.hi < value) {
setFlag(CARRY_FLAG);
resetFlag(ZERO_FLAG);
}
else if (AF.hi == value) {
setFlag(ZERO_FLAG);
resetFlag(CARRY_FLAG);
}
setFlag(SUBTRACT_FLAG);
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
if (0 > (((AF.hi) & 0xf) - (value & 0xf)))
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
}
template <typename T>
@@ -639,16 +612,10 @@ void GameBoy::pop(T& reg) {
AF.reg &= 0xFFF0;
}
template <typename T>
void GameBoy::push(T reg) {
void GameBoy::push(const Word reg) {
//little endian
RegisterPair temp = {0};
temp.lo = reg & 0xFF;
temp.hi = reg >> 8;
SP -= 1;
addressSpace[SP] = temp.hi;
SP -= 1;
addressSpace[SP] = temp.lo;
addressSpace[--SP] = reg >> 8;
addressSpace[--SP] = reg & 0xFF;
}
template <typename T>
@@ -658,6 +625,7 @@ void GameBoy::jp(T address) {
template <typename T>
void GameBoy::rst(T address) {
PC += 1;
push(PC);
PC = address;
}
@@ -739,7 +707,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x08:
ldW(addressSpace[getWordPC()], SP);
ldW(getWordPC(), SP);
PC += 3;
addCycles(20);
break;
@@ -840,7 +808,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x19:
add(HL.reg, BC.reg);
add(HL.reg, DE.reg);
PC += 1;
addCycles(8);
break;
@@ -1530,49 +1498,49 @@ void GameBoy::opcodeResolver() {
break;
case 0x88:
adc(AF.hi, BC.hi);
adc(BC.hi);
PC += 1;
addCycles(4);
break;
case 0x89:
adc(AF.hi, BC.lo);
adc(BC.lo);
PC += 1;
addCycles(4);
break;
case 0x8A:
adc(AF.hi, DE.hi);
adc(DE.hi);
PC += 1;
addCycles(4);
break;
case 0x8B:
adc(AF.hi, DE.lo);
adc(DE.lo);
PC += 1;
addCycles(4);
break;
case 0x8C:
adc(AF.hi, HL.hi);
adc(HL.hi);
PC += 1;
addCycles(4);
break;
case 0x8D:
adc(AF.hi, HL.lo);
adc(HL.lo);
PC += 1;
addCycles(4);
break;
case 0x8E:
adc(AF.hi, readOnlyAddressSpace[HL.reg]);
adc(readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
case 0x8F:
adc(AF.hi, AF.hi);
adc(AF.hi);
PC += 1;
addCycles(4);
break;
@@ -1970,7 +1938,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xCE:
adc(AF.hi, getBytePC());
adc(getBytePC());
PC += 2;
addCycles(8);
break;
@@ -2246,10 +2214,10 @@ void GameBoy::opcodeResolver() {
addCycles(16);
break;
//should not actually enable until the next opcode
//EI (0xFB) then DI (0xF3) does not allow interrupts to happen
//EI (0xFB) then DI (0xF3) never allows interrupts to happen
case 0xFB:
IME = 1;
IME = 0;
IME_togge = true;
PC += 1;
addCycles(4);
break;

43
src/testing.hpp Normal file
View File

@@ -0,0 +1,43 @@
#ifndef TESTING_HPP
#define TESTING_HPP
#include <cstdint>
#include <vector>
#include <tuple>
#include <iostream>
#include <stdexcept>
#include <string>
#include "defines.hpp"
struct GameboyTestState {
Word PC;
Word SP;
Byte A;
Byte F;
Byte B;
Byte C;
Byte D;
Byte E;
Byte H;
Byte L;
//Byte IME;
//Byte IE;
std::vector<std::tuple<Word, Byte>> RAM;
};
inline bool operator==(const GameboyTestState& lhs, const GameboyTestState& rhs) {
for (int i = 0; i < lhs.RAM.size(); i++) {
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
return false;
}
return (lhs.A == rhs.A &&
lhs.F == rhs.F &&
lhs.B == rhs.B &&
lhs.C == rhs.C &&
lhs.D == rhs.D &&
lhs.E == rhs.E &&
lhs.H == rhs.H &&
lhs.L == rhs.L);
}
#endif //TESTING_HPP