diff --git a/cpu/cpu.cpp b/cpu/cpu.cpp index dfa88e5..94d9580 100644 --- a/cpu/cpu.cpp +++ b/cpu/cpu.cpp @@ -1,5 +1,9 @@ #include -#include + +#include +#include +#include +#include void Cpu_state::setAF(u16 v) { @@ -19,7 +23,7 @@ u16 Cpu_state::getAF() (carry ? 0x10 : 0); } -u8& Cpu_state::reg8(u8 idx) +u8& Cpu_state::reg8(Cpu& cpu, u8 idx) { switch(idx) { @@ -30,11 +34,11 @@ u8& Cpu_state::reg8(u8 idx) case 0x4: return H; break; case 0x5: return L; break; case 0x7: return A; break; - default: panic("Invalid 8-bit register access idx=%d\n", idx); + default: throw CpuException(cpu, "Invalid 8-bit register access"); } } -u16& Cpu_state::reg16(u8 idx) +u16& Cpu_state::reg16(Cpu& cpu, u8 idx) { switch(idx) { @@ -42,7 +46,7 @@ u16& Cpu_state::reg16(u8 idx) case 0x1: return DE; break; case 0x2: return HL; break; case 0x3: return SP; break; - default: panic("Invalid 16-bit register access idx=%d\n", idx); + default: throw CpuException(cpu, "Invalid 16-bit register access"); } } @@ -243,6 +247,16 @@ void Cpu::aluop8(AluOp op, u8 lhs, u8 rhs, u8& out, bool update_carry) out = res; } +void Cpu::add16(u16& out, u16 lhs, u16 rhs) +{ + u16 res11 = (lhs & 0x0FFF) + (rhs & 0x0FFF); + state.halfcarry = (res11 & 0x1000); + u32 res32 = lhs + rhs; + state.carry = (res32 & 0x10000); + state.subtract = false; + lhs = (u16)res32; +} + bool Cpu::handleInterrupts() { // Once there's an interrupt we exit halt mode @@ -261,7 +275,7 @@ bool Cpu::handleInterrupts() else if (state.SI() & INT_Timer) { it = INT_Timer; isr = 0x50; } else if (state.SI() & INT_Serial) { it = INT_Serial; isr = 0x58; } else if (state.SI() & INT_Joypad) { it = INT_Joypad; isr = 0x60; } - else panic("Can't find pending interrupt IE=%02x IF=%02x\n", state.IE, state.IF); + else throw CpuException(*this, "Unable to find interrupt"); state.IME = IME_OFF; // Disable IME state.IF &= ~it; // clear interrupt in IF @@ -274,3 +288,36 @@ bool Cpu::handleInterrupts() return false; } + +CpuException::CpuException(Cpu& cpu, const char* msg) + : EmulatorException(msg), state(cpu.state), instaddr(cpu.last_instr_addr) +{ + for(u16 offset; offset < 4; offset++) + instmem[offset] = cpu.bus->read8(cpu.last_instr_addr + offset); +} + +const char* CpuException::what() const noexcept +{ + std::stringstream s; + +#define FORMAT16(x) std::uppercase << std::hex << std::setfill('0') << std::setw(4) << x +#define FORMAT8(x) std::uppercase << std::hex << std::setfill('0') << std::setw(2) << ((unsigned)(x)) + + s << "CpuException: " << std::runtime_error::what() << std::endl + << "Last Instruction @" << FORMAT16(instaddr) << " : " + << FORMAT8(instmem[0]) << " " + << FORMAT8(instmem[1]) << " " + << FORMAT8(instmem[2]) << " " + << FORMAT8(instmem[3]) << std::endl + << "Registers:" << std::endl + << " A=" << FORMAT8(state.A) << " Z=" << state.zero << " N=" << state.subtract << " H=" << state.halfcarry << " C=" << state.carry + << " IME=" << state.IME << " IE=" << FORMAT8(state.IE) << " IF=" << FORMAT8(state.IF) << std::endl + << " BC=" << FORMAT16(state.BC) << " DE=" << FORMAT16(state.DE) << " HL=" << FORMAT16(state.HL) << " SP=" << FORMAT16(state.SP) << std::endl + << " PC=" << FORMAT16(state.PC) + << " HALT=" << state.halted << " HALTBUG=" << state.haltbug << " STOP=" << state.stopped; + + const std::string str = s.str(); + char* buf = new char[str.length()]; + std::strcpy(buf, str.c_str()); + return buf; +} diff --git a/cpu/cpu.h b/cpu/cpu.h index 61603c9..37ba825 100644 --- a/cpu/cpu.h +++ b/cpu/cpu.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -87,6 +88,8 @@ struct opcode { { return (u16)(value & 0x38); } }; +class Cpu; + struct Cpu_state { // Registers union { @@ -116,6 +119,7 @@ struct Cpu_state { u8 IE; u8 IF; + // servicable interrupts inline u8 SI() const { return INT_MASK & IE & IF; } @@ -127,8 +131,8 @@ struct Cpu_state { void setAF(u16 v); u16 getAF(); - u8& reg8(u8 idx); - u16& reg16(u8 idx); + u8& reg8(Cpu& cpu, u8 idx); + u16& reg16(Cpu& cpu, u8 idx); }; class Cpu { @@ -143,6 +147,7 @@ private: u16 popStack16(); void aluop8(AluOp op, u8 lhs, u8 rhs, u8& out, bool update_carry = true); + void add16(u16& out, u16 lhs, u16 rhs); inline void aluop8(AluOp op, u8 rhs, bool update_carry = true) @@ -166,9 +171,21 @@ public: Cpu_state state; Mem_device* bus; unsigned long processed_mcycles; - + u16 last_instr_addr; void signalInterrupt(InterruptType it); void step(); }; + +class CpuException : public EmulatorException { +private: + Cpu_state state; + u16 instaddr; + u8 instmem[4]; + +public: + CpuException(Cpu& cpu, const char* msg); + + virtual const char* what() const noexcept override; +}; diff --git a/cpu/decoder.cpp b/cpu/decoder.cpp index 6c21e1d..d7c2c0b 100644 --- a/cpu/decoder.cpp +++ b/cpu/decoder.cpp @@ -1,30 +1,19 @@ #include -#include static inline u16 make_u16(u8 msb, u8 lsb) { return (((u16)msb << 8) | (u16)lsb); } -static inline void add16(Cpu& cpu, u16& out, u16 lhs, u16 rhs) -{ - u16 res11 = (lhs & 0x0FFF) + (rhs & 0x0FFF); - cpu.state.halfcarry = (res11 & 0x1000); - u32 res32 = lhs + rhs; - cpu.state.carry = (res32 & 0x10000); - cpu.state.subtract = false; - lhs = (u16)res32; -} - void Cpu::executeInstruction() { - u16 currentpc = state.PC; + last_instr_addr = state.PC; opcode op{ readPC8() }; int mcycles = 1; #if 0 - printf("@0x%04x: opcode %02X\n",currentpc,op); + printf("@0x%04x: opcode %02X\n", last_instr_addr, op); #endif if ((op & 0xC0) == 0x40 && op != 0x76) // LD r, r'; LD r, [HL]; LD [HL], r @@ -33,13 +22,13 @@ void Cpu::executeInstruction() switch(op.reg8idxlo()) { case 0x6: tmp = bus->read8(state.HL); break; - default: tmp = state.reg8(op.reg8idxlo()); break; + default: tmp = state.reg8(*this, op.reg8idxlo()); break; }; switch(op.reg8idxhi()) { case 0x6: bus->write8(state.HL, tmp); break; - default: state.reg8(op.reg8idxhi()) = tmp; break; + default: state.reg8(*this, op.reg8idxhi()) = tmp; break; } } else if((op & 0xC7) == 0x06) // LD r, n @@ -49,13 +38,13 @@ void Cpu::executeInstruction() switch(op.reg8idxhi()) { case 0x6: bus->write8(state.HL, imm); break; - default: state.reg8(op.reg8idxhi()) = imm; break; + default: state.reg8(*this, op.reg8idxhi()) = imm; break; } } else if((op & 0xCF) == 0x01) // LD rr, nn { u16 data = readPC16(); - state.reg16(op.reg16idx()) = data; + state.reg16(*this, op.reg16idx()) = data; mcycles = 3; } else if((op & 0xCF) == 0xC5) // PUSH rr @@ -64,7 +53,7 @@ void Cpu::executeInstruction() switch(op.reg16idx()) { case 0x3: data = state.getAF(); break; - default: data = state.reg16(op.reg16idx()); + default: data = state.reg16(*this, op.reg16idx()); } pushStack16(data); @@ -78,7 +67,7 @@ void Cpu::executeInstruction() switch(op.reg16idx()) { case 0x3: state.setAF(data); break; - default: state.reg16(op.reg16idx()) = data; break; + default: state.reg16(*this, op.reg16idx()) = data; break; } mcycles = 4; @@ -89,7 +78,7 @@ void Cpu::executeInstruction() switch(op.reg8idxlo()) { case 0x6: rhs = bus->read8(state.HL); mcycles = 2; break; - default: rhs = state.reg8(op.reg8idxlo()); break; + default: rhs = state.reg8(*this, op.reg8idxlo()); break; } aluop8(op.aluop(), rhs); @@ -115,7 +104,7 @@ void Cpu::executeInstruction() break; default: { - u8& reg = state.reg8(op.reg8idxhi()); + u8& reg = state.reg8(*this, op.reg8idxhi()); aluop8(aluop, reg, 1, reg, false); break; } break; @@ -123,7 +112,7 @@ void Cpu::executeInstruction() } else if((op & 0xC7) == 0x03) // INC rr; DEC rr { - state.reg16(op.reg16idx()) += ((op & 0x08) ? -1 : 1); + state.reg16(*this, op.reg16idx()) += ((op & 0x08) ? -1 : 1); mcycles = 2; } else if((op & 0xE7) == 0xC2) // JP cc, nn: @@ -165,7 +154,7 @@ void Cpu::executeInstruction() } else if((op & 0xCF) == 0x09) // ADD HL, rr { - add16(*this, state.HL, state.HL, state.reg16(op.reg16idx())); + add16(state.HL, state.HL, state.reg16(*this, op.reg16idx())); mcycles = 2; } else if((op & 0xE7) == 0xC0) // RET cc @@ -184,18 +173,17 @@ void Cpu::executeInstruction() } else if(op == 0xCB) // PREFIX { - currentpc = state.PC; opcode prefix_op{ readPC8() }; #if 0 - printf("@0x%04x: CB opcode %02X\n", currentpc, prefix_op); + printf("@0x%04x: CB opcode %02X\n", last_instr_addr + 1, prefix_op); #endif u8 data; switch(prefix_op.reg8idxlo()) { case 0x6: data = bus->read8(state.HL); mcycles = 3; break; - default: data = state.reg8(prefix_op.reg8idxlo()); mcycles = 2; break; + default: data = state.reg8(*this, prefix_op.reg8idxlo()); mcycles = 2; break; } // For BIT, RES, SET @@ -274,7 +262,7 @@ void Cpu::executeInstruction() switch(prefix_op.reg8idxlo()) { case 0x6: bus->write8(state.HL, data); mcycles = 4; break; - default: state.reg8(prefix_op.reg8idxlo()) = data; break; + default: state.reg8(*this, prefix_op.reg8idxlo()) = data; break; } } } @@ -282,18 +270,6 @@ void Cpu::executeInstruction() { switch(op) { - case 0xD3: // Undefined, treat as NOP - case 0xE3: - case 0xE4: - case 0xF4: - case 0xDB: - case 0xEB: - case 0xEC: - case 0xFC: - case 0xDD: - case 0xED: - case 0xFD: - break; case 0x00: // defined NOP break; case 0x0A: // Load A, [BC] @@ -451,18 +427,32 @@ void Cpu::executeInstruction() state.stopped = true; break; case 0xE8: // ADD SP, e8 - add16(*this, state.SP, state.SP, (s32)((s8)readPC8())); + add16(state.SP, state.SP, (s32)((s8)readPC8())); state.zero = false; mcycles = 4; break; case 0xF8: // LD HL, SP + e8 - add16(*this, state.HL, state.SP, (s32)((s8)readPC8())); + add16(state.HL, state.SP, (s32)((s8)readPC8())); state.zero = false; mcycles = 3; break; + case 0xD3: // Undefined, throw exception + case 0xE3: + case 0xE4: + case 0xF4: + case 0xDB: + case 0xEB: + case 0xEC: + case 0xFC: + case 0xDD: + case 0xED: + case 0xFD: + throw CpuException(*this, "Undefined opcode"); + break; + default: - panic("Unknown opcode 0x%x\n",op); + throw CpuException(*this, "Unknown opcode"); } } diff --git a/memory/mbc/mbc1.cpp b/memory/mbc/mbc1.cpp index 9941764..682b584 100644 --- a/memory/mbc/mbc1.cpp +++ b/memory/mbc/mbc1.cpp @@ -1,5 +1,5 @@ #include -#include +#include MBC1::MBC1(Cartridge& cart) : cart(cart), @@ -35,5 +35,5 @@ void MBC1::write8(u16 addr, u8 data) else if((addr & 0xE000) == 0x4000) ram_bankreg = data & 0x03; else if((addr & 0xE000) == 0x6000) - panic("Banking mode not implemented"); + throw EmulatorException("MBC1: banking mode not supported"); } diff --git a/misc/exception.h b/misc/exception.h new file mode 100644 index 0000000..24108f6 --- /dev/null +++ b/misc/exception.h @@ -0,0 +1,6 @@ +#include + +class EmulatorException : public std::runtime_error { + using std::runtime_error::runtime_error; + using std::runtime_error::what; +}; diff --git a/misc/panic.h b/misc/panic.h deleted file mode 100644 index f571326..0000000 --- a/misc/panic.h +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -template -inline void panic [[noreturn]] (Args... args) -{ - printf(args...); - exit(1); -} diff --git a/misc/types.h b/misc/types.h index 3489fe5..8c61475 100644 --- a/misc/types.h +++ b/misc/types.h @@ -1,9 +1,11 @@ #pragma once -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned int u32; +#include -typedef signed char s8; -typedef signed short s16; -typedef signed int s32; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; diff --git a/tests/test_cpu_exception.cpp b/tests/test_cpu_exception.cpp new file mode 100644 index 0000000..079e612 --- /dev/null +++ b/tests/test_cpu_exception.cpp @@ -0,0 +1,13 @@ +#include "doctest.h" +#include +#include +#include + +TEST_CASE("Undefined instructions throw exception") +{ + u8 test_mem[] = { 0xd3, 0x00, 0x00, 0x00 }; + RAM r(test_mem, 0x4, true); + Cpu c(&r); + + REQUIRE_THROWS_AS(c.step(), CpuException); +}