@@ -24,6 +24,7 @@ GameGear* gamegear_create() | |||||
// mmu_init(&gg->mmu, ...); | // mmu_init(&gg->mmu, ...); | ||||
z80_init(&gg->cpu, &gg->mmu); | z80_init(&gg->cpu, &gg->mmu); | ||||
gg->powered = false; | gg->powered = false; | ||||
gg->exc_buffer[0] = '\0'; | |||||
return gg; | return gg; | ||||
} | } | ||||
@@ -57,7 +58,7 @@ void gamegear_load(GameGear *gg, ROM *rom) | |||||
Powering on the GameGear executes boot code (e.g. clearing memory and | Powering on the GameGear executes boot code (e.g. clearing memory and | ||||
setting initial register values) and starts the clock. Powering it off | setting initial register values) and starts the clock. Powering it off | ||||
stops the clock. | |||||
stops the clock and clears any exception data. | |||||
Setting the power state to its current value has no effect. | Setting the power state to its current value has no effect. | ||||
*/ | */ | ||||
@@ -71,7 +72,7 @@ void gamegear_power(GameGear *gg, bool state) | |||||
z80_power(&gg->cpu); | z80_power(&gg->cpu); | ||||
gg->last_tick = get_time_ns(); | gg->last_tick = get_time_ns(); | ||||
} else { | } else { | ||||
// TODO: free exception buffer | |||||
gg->exc_buffer[0] = '\0'; | |||||
} | } | ||||
gg->powered = state; | gg->powered = state; | ||||
} | } | ||||
@@ -105,15 +106,33 @@ bool gamegear_simulate(GameGear *gg) | |||||
This function returns a const pointer to a buffer holding a human-readable | This function returns a const pointer to a buffer holding a human-readable | ||||
exception string (although it may be cryptic to an end-user). The buffer is | exception string (although it may be cryptic to an end-user). The buffer is | ||||
owned by the GameGear object and should not be freed - it lasts until the | |||||
GameGear's power state is changed. If no exception flag is set, this | |||||
function returns NULL. | |||||
owned by the GameGear object and should not be freed - its contents last | |||||
until the GameGear's power state is changed. If no exception flag is set, | |||||
this function returns NULL. | |||||
*/ | */ | ||||
const char* gamegear_get_exception(GameGear *gg) | const char* gamegear_get_exception(GameGear *gg) | ||||
{ | { | ||||
#define SET_EXC(...) snprintf(gg->exc_buffer, GG_EXC_BUFF_SIZE, __VA_ARGS__); | |||||
if (!gg->powered) | if (!gg->powered) | ||||
return NULL; | return NULL; | ||||
// TODO: return ptr to exception buffer | |||||
return "Unknown exception"; | |||||
if (!gg->exc_buffer[0]) { | |||||
if (gg->cpu.except) { | |||||
switch (gg->cpu.exc_code) { | |||||
case Z80_EXC_NOT_POWERED: | |||||
SET_EXC("CPU not powered") | |||||
break; | |||||
case Z80_EXC_UNIMPLEMENTED_OPCODE: | |||||
SET_EXC("Unimplemented opcode: 0x%02X", gg->cpu.exc_data) | |||||
break; | |||||
default: | |||||
SET_EXC("Unknown exception") | |||||
break; | |||||
} | |||||
} else { | |||||
return NULL; | |||||
} | |||||
} | |||||
return gg->exc_buffer; | |||||
#undef SET_EXC | |||||
} | } |
@@ -10,6 +10,8 @@ | |||||
#include "rom.h" | #include "rom.h" | ||||
#include "z80.h" | #include "z80.h" | ||||
#define GG_EXC_BUFF_SIZE 128 | |||||
/* Structs */ | /* Structs */ | ||||
typedef struct { | typedef struct { | ||||
@@ -17,6 +19,7 @@ typedef struct { | |||||
Z80 cpu; | Z80 cpu; | ||||
bool powered; | bool powered; | ||||
uint64_t last_tick; | uint64_t last_tick; | ||||
char exc_buffer[GG_EXC_BUFF_SIZE]; | |||||
} GameGear; | } GameGear; | ||||
/* Functions */ | /* Functions */ | ||||
@@ -22,6 +22,8 @@ void z80_init(Z80 *z80, MMU *mmu) | |||||
{ | { | ||||
z80->mmu = mmu; | z80->mmu = mmu; | ||||
z80->except = true; | z80->except = true; | ||||
z80->exc_code = Z80_EXC_NOT_POWERED; | |||||
z80->exc_data = 0; | |||||
} | } | ||||
/* | /* | ||||
@@ -86,18 +88,7 @@ static inline uint8_t get_interrupt_mode(Z80 *z80) | |||||
return 2; | return 2; | ||||
} | } | ||||
/* ------------------------------------------------------------------------- */ | |||||
static uint8_t z80_inst_nop(Z80 *z80) { | |||||
z80->regfile.pc++; | |||||
return 4; | |||||
} | |||||
static uint8_t (*instruction_lookup_table[256])(Z80*) = { | |||||
z80_inst_nop | |||||
}; | |||||
/* ------------------------------------------------------------------------- */ | |||||
#include "z80_instructions.inc" | |||||
/* | /* | ||||
Emulate the given number of cycles of the Z80, or until an exception. | Emulate the given number of cycles of the Z80, or until an exception. | ||||
@@ -108,18 +99,15 @@ static uint8_t (*instruction_lookup_table[256])(Z80*) = { | |||||
*/ | */ | ||||
bool z80_do_cycles(Z80 *z80, double cycles) | bool z80_do_cycles(Z80 *z80, double cycles) | ||||
{ | { | ||||
if (z80->except) | |||||
return true; | |||||
cycles -= z80->pending_cycles; | cycles -= z80->pending_cycles; | ||||
while (cycles > 0) { | |||||
while (cycles > 0 && !z80->except) { | |||||
// uint8_t opcode = mmu_read_byte(&z80->mmu, z80->regfile.pc); | // uint8_t opcode = mmu_read_byte(&z80->mmu, z80->regfile.pc); | ||||
uint8_t opcode = 0x00; | uint8_t opcode = 0x00; | ||||
cycles -= (*instruction_lookup_table[opcode])(z80) - 2; | |||||
cycles -= (*instruction_lookup_table[opcode])(z80, opcode) - 2; | |||||
} | } | ||||
z80->pending_cycles = -cycles; | z80->pending_cycles = -cycles; | ||||
return false; | |||||
return z80->except; | |||||
} | } | ||||
#ifdef DEBUG_MODE | #ifdef DEBUG_MODE | ||||
@@ -8,6 +8,9 @@ | |||||
#include "mmu.h" | #include "mmu.h" | ||||
#define Z80_EXC_NOT_POWERED 0 | |||||
#define Z80_EXC_UNIMPLEMENTED_OPCODE 1 | |||||
/* Structs */ | /* Structs */ | ||||
typedef struct { | typedef struct { | ||||
@@ -23,6 +26,7 @@ typedef struct { | |||||
Z80RegFile regfile; | Z80RegFile regfile; | ||||
MMU *mmu; | MMU *mmu; | ||||
bool except; | bool except; | ||||
uint8_t exc_code, exc_data; | |||||
double pending_cycles; | double pending_cycles; | ||||
} Z80; | } Z80; | ||||
@@ -0,0 +1,34 @@ | |||||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Released under the terms of the MIT License. See LICENSE for details. */ | |||||
/* | |||||
This file contains code to implement the Z80 instruction set. Since there | |||||
are a lot of functions, it is kept separate from the main z80.c file. It is | |||||
included in the middle of z80.c and should not be compiled separately. | |||||
*/ | |||||
/* | |||||
0x00: NOP | |||||
*/ | |||||
static uint8_t z80_inst_nop(Z80 *z80, uint8_t opcode) | |||||
{ | |||||
(void) opcode; | |||||
z80->regfile.pc++; | |||||
return 4; | |||||
} | |||||
/* | |||||
Unimplemented opcode handler. | |||||
*/ | |||||
static uint8_t z80_inst_ERR(Z80 *z80, uint8_t opcode) | |||||
{ | |||||
z80->except = true; | |||||
z80->exc_code = Z80_EXC_UNIMPLEMENTED_OPCODE; | |||||
z80->exc_data = opcode; | |||||
return 2; | |||||
} | |||||
static uint8_t (*instruction_lookup_table[256])(Z80*, uint8_t) = { | |||||
z80_inst_nop, | |||||
z80_inst_ERR | |||||
}; |