Use exceptions instead of panic
This commit is contained in:
parent
a0377959dc
commit
66c19caaee
8 changed files with 134 additions and 68 deletions
59
cpu/cpu.cpp
59
cpu/cpu.cpp
|
@ -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;
|
||||
}
|
||||
|
|
23
cpu/cpu.h
23
cpu/cpu.h
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
6
misc/exception.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include <stdexcept>
|
||||
|
||||
class EmulatorException : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
using std::runtime_error::what;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
template <typename... Args>
|
||||
inline void panic [[noreturn]] (Args... args)
|
||||
{
|
||||
printf(args...);
|
||||
exit(1);
|
||||
}
|
14
misc/types.h
14
misc/types.h
|
@ -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;
|
||||
|
|
13
tests/test_cpu_exception.cpp
Normal file
13
tests/test_cpu_exception.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue