2023-08-26 19:04:02 +02:00
|
|
|
#pragma once
|
|
|
|
|
2023-08-29 23:16:09 +02:00
|
|
|
#include <misc/types.h>
|
2023-09-01 15:11:27 +02:00
|
|
|
#include <misc/exception.h>
|
2023-08-26 19:04:02 +02:00
|
|
|
|
2023-08-30 22:34:45 +02:00
|
|
|
#include <memory/device.h>
|
2023-08-28 21:56:33 +02:00
|
|
|
|
|
|
|
typedef u8 opcode_t;
|
2023-08-26 19:04:02 +02:00
|
|
|
|
|
|
|
enum Flags {
|
2023-09-01 23:43:24 +02:00
|
|
|
F_ZERO = 0x80,
|
|
|
|
F_SUB = 0x40,
|
|
|
|
F_HALF = 0x20,
|
|
|
|
F_CARRY = 0x10,
|
2023-08-26 19:04:02 +02:00
|
|
|
};
|
|
|
|
|
2023-08-27 22:19:02 +02:00
|
|
|
enum AluOp : int {
|
|
|
|
ADD = 0,
|
|
|
|
ADC = 1,
|
|
|
|
SUB = 2,
|
|
|
|
SBC = 3,
|
|
|
|
AND = 4,
|
|
|
|
XOR = 5,
|
|
|
|
OR = 6,
|
|
|
|
CP = 7,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum CC
|
|
|
|
{
|
|
|
|
COND_NZ = 0,
|
|
|
|
COND_Z = 1,
|
|
|
|
COND_NC = 2,
|
|
|
|
COND_C = 3,
|
|
|
|
};
|
|
|
|
|
2023-08-28 21:56:33 +02:00
|
|
|
enum InterruptType : u8
|
|
|
|
{
|
|
|
|
INT_VBlank = 0x1,
|
|
|
|
INT_LCDSTAT = 0x2,
|
|
|
|
INT_Timer = 0x4,
|
|
|
|
INT_Serial = 0x8,
|
|
|
|
INT_Joypad = 0x10,
|
|
|
|
|
|
|
|
INT_MASK = 0x1F,
|
|
|
|
};
|
|
|
|
|
2023-08-29 12:10:10 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
IME - Interrupt Master Enable
|
|
|
|
|
|
|
|
An EI instruction will enable the interrupts, but delayed. During the next instruction after EI,
|
|
|
|
interrupts are still disabled. For this to be emulated we use a small state machine. which works as follows
|
|
|
|
|
|
|
|
instruction EI - sets IME_SCHEDULED
|
|
|
|
handleInterrupts -> IME_SCHEDULED to IME_ON (but no call to isr yet)
|
|
|
|
instruction any - is IME_ON, but no chance for call to isr yet
|
|
|
|
handleInterrupts -> is IME_ON, do a call to isr if necessary
|
|
|
|
*/
|
2023-08-28 21:56:33 +02:00
|
|
|
enum IME_state
|
|
|
|
{
|
|
|
|
IME_OFF,
|
|
|
|
IME_SCHEDULED,
|
|
|
|
IME_ON,
|
|
|
|
};
|
2023-08-27 22:19:02 +02:00
|
|
|
|
2023-08-30 18:27:53 +02:00
|
|
|
struct opcode {
|
|
|
|
const opcode_t value;
|
|
|
|
|
|
|
|
inline operator opcode_t() const
|
|
|
|
{ return value; }
|
|
|
|
|
|
|
|
inline u8 reg8idxlo() const
|
|
|
|
{ return value & 0x7; }
|
|
|
|
|
|
|
|
inline u8 reg8idxhi() const
|
|
|
|
{ return (value >> 3) & 0x7; }
|
|
|
|
|
|
|
|
inline u8 reg16idx() const
|
|
|
|
{ return (value >> 4) & 0x3; }
|
|
|
|
|
|
|
|
inline AluOp aluop() const
|
|
|
|
{ return (AluOp)((value >> 3) & 0x7); }
|
|
|
|
|
|
|
|
inline u8 cc() const
|
|
|
|
{ return (value >> 3) & 0x3; }
|
|
|
|
|
|
|
|
inline u16 rst_addr() const
|
|
|
|
{ return (u16)(value & 0x38); }
|
|
|
|
};
|
|
|
|
|
2023-09-01 15:11:27 +02:00
|
|
|
class Cpu;
|
|
|
|
|
2023-08-26 19:04:02 +02:00
|
|
|
struct Cpu_state {
|
|
|
|
// Registers
|
|
|
|
union {
|
|
|
|
u16 BC;
|
2023-08-29 19:42:01 +02:00
|
|
|
struct { u8 C; u8 B; };
|
2023-08-26 19:04:02 +02:00
|
|
|
};
|
|
|
|
union {
|
|
|
|
u16 DE;
|
2023-08-29 19:42:01 +02:00
|
|
|
struct { u8 E; u8 D; };
|
2023-08-26 19:04:02 +02:00
|
|
|
};
|
|
|
|
union {
|
|
|
|
u16 HL;
|
2023-08-29 19:42:01 +02:00
|
|
|
struct { u8 L; u8 H; };
|
2023-08-26 19:04:02 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
u8 A;
|
|
|
|
u16 SP;
|
|
|
|
u16 PC;
|
|
|
|
|
|
|
|
bool zero;
|
|
|
|
bool subtract;
|
|
|
|
bool halfcarry;
|
|
|
|
bool carry;
|
2023-08-27 22:19:02 +02:00
|
|
|
|
2023-08-28 21:56:33 +02:00
|
|
|
IME_state IME;
|
2023-08-27 22:19:02 +02:00
|
|
|
|
2023-08-28 21:56:33 +02:00
|
|
|
u8 IE;
|
|
|
|
u8 IF;
|
2023-08-27 22:19:02 +02:00
|
|
|
|
2023-09-01 15:11:27 +02:00
|
|
|
|
2023-08-30 21:47:38 +02:00
|
|
|
// servicable interrupts
|
|
|
|
inline u8 SI() const
|
|
|
|
{ return INT_MASK & IE & IF; }
|
|
|
|
|
2023-08-29 23:30:31 +02:00
|
|
|
bool halted;
|
2023-08-30 21:47:38 +02:00
|
|
|
bool haltbug;
|
2023-08-29 23:46:36 +02:00
|
|
|
bool stopped;
|
2023-08-29 23:30:31 +02:00
|
|
|
|
2023-08-27 22:19:02 +02:00
|
|
|
void setAF(u16 v);
|
|
|
|
u16 getAF();
|
2023-09-01 23:19:02 +02:00
|
|
|
u8 getF();
|
2023-08-29 16:53:20 +02:00
|
|
|
|
2023-09-01 15:11:27 +02:00
|
|
|
u8& reg8(Cpu& cpu, u8 idx);
|
|
|
|
u16& reg16(Cpu& cpu, u8 idx);
|
2023-08-26 19:04:02 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
class Cpu {
|
|
|
|
private:
|
2023-08-26 21:17:47 +02:00
|
|
|
u8 readPC8();
|
|
|
|
u16 readPC16();
|
2023-08-26 19:04:02 +02:00
|
|
|
|
2023-08-26 21:17:47 +02:00
|
|
|
void pushStack8(u8 data);
|
|
|
|
u8 popStack8();
|
2023-08-26 19:04:02 +02:00
|
|
|
|
2023-08-26 21:17:47 +02:00
|
|
|
void pushStack16(u16 data);
|
|
|
|
u16 popStack16();
|
2023-08-27 22:19:02 +02:00
|
|
|
|
|
|
|
void aluop8(AluOp op, u8 lhs, u8 rhs, u8& out, bool update_carry = true);
|
2023-09-01 15:11:27 +02:00
|
|
|
void add16(u16& out, u16 lhs, u16 rhs);
|
2023-09-02 00:38:01 +02:00
|
|
|
void add16_8(u16& out, u16 lhs, s8 rhs);
|
2023-08-27 22:19:02 +02:00
|
|
|
|
|
|
|
inline
|
|
|
|
void aluop8(AluOp op, u8 rhs, bool update_carry = true)
|
|
|
|
{
|
|
|
|
aluop8(op, state.A, rhs, state.A, update_carry);
|
|
|
|
}
|
|
|
|
|
|
|
|
void doCall(u16 target);
|
|
|
|
void doRet();
|
|
|
|
|
|
|
|
bool decodeCond(u8 cc);
|
|
|
|
|
2023-08-29 13:45:17 +02:00
|
|
|
bool handleInterrupts();
|
2023-08-28 21:56:33 +02:00
|
|
|
void executeInstruction();
|
|
|
|
|
|
|
|
void reset();
|
|
|
|
|
2023-08-26 19:04:02 +02:00
|
|
|
public:
|
2023-08-28 21:56:33 +02:00
|
|
|
Cpu(Mem_device* bus);
|
|
|
|
|
|
|
|
Cpu_state state;
|
|
|
|
Mem_device* bus;
|
|
|
|
unsigned long processed_mcycles;
|
2023-09-01 15:11:27 +02:00
|
|
|
u16 last_instr_addr;
|
2023-08-28 21:56:33 +02:00
|
|
|
|
2023-08-29 13:45:55 +02:00
|
|
|
void signalInterrupt(InterruptType it);
|
2023-08-26 19:04:02 +02:00
|
|
|
|
|
|
|
void step();
|
|
|
|
};
|
2023-09-01 15:11:27 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
};
|