Use exceptions instead of panic

This commit is contained in:
madmaurice 2023-09-01 15:11:27 +02:00
parent a0377959dc
commit 66c19caaee
8 changed files with 134 additions and 68 deletions

View file

@ -1,5 +1,9 @@
#include <cpu/cpu.h>
#include <misc/panic.h>
#include <iostream>
#include <sstream>
#include <cstring>
#include <iomanip>
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;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <misc/types.h>
#include <misc/exception.h>
#include <memory/device.h>
@ -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;
};

View file

@ -1,30 +1,19 @@
#include <cpu/cpu.h>
#include <misc/panic.h>
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");
}
}

View file

@ -1,5 +1,5 @@
#include <memory/mbc/mbc1.h>
#include <misc/panic.h>
#include <misc/exception.h>
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");
}

6
misc/exception.h Normal file
View file

@ -0,0 +1,6 @@
#include <stdexcept>
class EmulatorException : public std::runtime_error {
using std::runtime_error::runtime_error;
using std::runtime_error::what;
};

View file

@ -1,9 +0,0 @@
#include <cstdio>
#include <cstdlib>
template <typename... Args>
inline void panic [[noreturn]] (Args... args)
{
printf(args...);
exit(1);
}

View file

@ -1,9 +1,11 @@
#pragma once
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
#include <cstdint>
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;

View file

@ -0,0 +1,13 @@
#include "doctest.h"
#include <cpu/cpu.h>
#include <memory/ram.h>
#include <iostream>
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);
}