After doing a little reading about the way the memory banks are mapped, it looks like this code is going to grow. Separate it into it's own file. While we're at it, make gb_mem_read() a proper function instead of a callback. Because these functions are used so frequently, this corresponds to a ~10-20% performance benefit (due to LTO).
145 lines
3.3 KiB
C
145 lines
3.3 KiB
C
/*
|
|
* A simulation of the LR35902 CPU used by the Nintendo Gameboy
|
|
*
|
|
* Author: Max Regan
|
|
* Last Modified: 11-17-2015
|
|
*/
|
|
|
|
#ifndef GB_CPU_H
|
|
#define GB_CPU_H
|
|
|
|
#include "gbemu/memory.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#define CPU_F_BIT_POS_C 4
|
|
#define CPU_F_BIT_POS_H 5
|
|
#define CPU_F_BIT_POS_N 6
|
|
#define CPU_F_BIT_POS_Z 7
|
|
|
|
#define LR35902_MAX_EVENTS 16
|
|
|
|
typedef enum {
|
|
LR35902_REG_BC = 0,
|
|
LR35902_REG_DE = 1,
|
|
LR35902_REG_HL = 2,
|
|
LR35902_REG_AF = 3,
|
|
LR35902_REG_SP,
|
|
LR35902_REG_PC,
|
|
NUM_LR35902_REGS_16
|
|
} lr35902_regs_16;
|
|
|
|
typedef enum {
|
|
LR35902_REG_B = 0,
|
|
LR35902_REG_C = 1,
|
|
LR35902_REG_D = 2,
|
|
LR35902_REG_E = 3,
|
|
LR35902_REG_H = 4,
|
|
LR35902_REG_L = 5,
|
|
LR35902_REG_HL_DEREF = 6, //Not a real register
|
|
LR35902_REG_A = 7, //Accumulator
|
|
LR35902_REG_F = 8, //Flags
|
|
NUM_LR35902_REGS_8
|
|
} lr35902_regs_8;
|
|
|
|
typedef enum {
|
|
LR35902_INT_ON,
|
|
LR35902_INT_OFF,
|
|
} lr35902_interrupt_state;
|
|
|
|
struct lr35902_state;
|
|
|
|
typedef uint8_t (*lr35902_mem_read_fn)(struct lr35902_state *cpu,
|
|
uint16_t address);
|
|
|
|
typedef void (*lr35902_mem_write_fn)(struct lr35902_state *cpu,
|
|
uint16_t address,
|
|
uint8_t value);
|
|
|
|
typedef void (*lr35902_instr)(struct lr35902_state *cpu, uint8_t instr);
|
|
|
|
typedef void (*lr35902_cb_instr)(struct lr35902_state *cpu, uint8_t instr);
|
|
|
|
typedef void (*lr35902_event_fn)(struct lr35902_state *cpu);
|
|
|
|
struct lr35902_event {
|
|
uint8_t cycles;
|
|
lr35902_event_fn run;
|
|
};
|
|
|
|
struct lr35902_state {
|
|
|
|
struct gb_memory *memory;
|
|
|
|
union {
|
|
/*
|
|
* As a convenience, give ourselves a bunch of different ways to
|
|
* access the registers. They are carefully written below so they
|
|
* overlap correctly, Per C99, as long as uint{8,16}_t are implemented,
|
|
* there is guaranteed to be no padding and no trap representations.
|
|
*/
|
|
uint16_t regs_16[NUM_LR35902_REGS_16];
|
|
struct {
|
|
uint16_t bc, de, hl, af, pc, sp;
|
|
};
|
|
uint8_t regs_8[NUM_LR35902_REGS_8];
|
|
|
|
struct {
|
|
uint8_t c, b, e, d, l, h, f_dummy, a;
|
|
};
|
|
};
|
|
|
|
/* Only two operations access the flags as a collective, PUSH
|
|
* AF and POP AF. It's much easier to represent them as
|
|
* individual flags and combine them when needed */
|
|
int zf;
|
|
int nf;
|
|
int hf;
|
|
int cf;
|
|
|
|
int stall_cycles;
|
|
int halted;
|
|
|
|
void (*undef_d3)(struct lr35902_state *reg);
|
|
|
|
lr35902_interrupt_state int_state;
|
|
struct lr35902_event events[LR35902_MAX_EVENTS];
|
|
|
|
//Cool data
|
|
struct {
|
|
uint64_t cycles;
|
|
uint64_t retired_instrs;
|
|
uint64_t mem_reads;
|
|
uint64_t mem_writes;
|
|
} metrics;
|
|
};
|
|
|
|
struct lr35902_ops {
|
|
lr35902_mem_read_fn mem_read;
|
|
lr35902_mem_write_fn mem_write;
|
|
|
|
void (*undef_d3)(struct lr35902_state *reg);
|
|
};
|
|
|
|
void lr35902_init(struct lr35902_state *cpu,
|
|
struct gb_memory *memory,
|
|
const struct lr35902_ops *ops);
|
|
|
|
uint16_t lr35902_get_reg_16(const struct lr35902_state *cpu,
|
|
lr35902_regs_16 reg);
|
|
|
|
uint8_t lr35902_get_reg_8(const struct lr35902_state *cpu,
|
|
lr35902_regs_8 reg);
|
|
|
|
void lr35902_set_reg_16(struct lr35902_state *cpu,
|
|
lr35902_regs_16 reg,
|
|
uint16_t val);
|
|
|
|
void lr35902_set_reg_8(struct lr35902_state *cpu,
|
|
lr35902_regs_8 reg,
|
|
uint8_t val);
|
|
|
|
int lr35902_cycle(struct lr35902_state *cpu);
|
|
|
|
#endif
|