@@ -5,6 +5,10 @@ | |||
#include "gamegear.h" | |||
#include "logging.h" | |||
#include "util.h" | |||
/* Clock speed in Hz was taken from the official Sega GG documentation */ | |||
#define CPU_CLOCK_SPEED 3579545 | |||
/* | |||
Create and return a pointer to a new GameGear object. | |||
@@ -18,7 +22,7 @@ GameGear* gamegear_create() | |||
OUT_OF_MEMORY() | |||
// mmu_init(&gg->mmu, ...); | |||
z80_init(&gg->cpu, CPU_CLOCK_SPEED); | |||
z80_init(&gg->cpu, &gg->mmu); | |||
gg->powered = false; | |||
return gg; | |||
} | |||
@@ -30,6 +34,7 @@ GameGear* gamegear_create() | |||
*/ | |||
void gamegear_destroy(GameGear *gg) | |||
{ | |||
// mmu_free(&gg->mmu); | |||
free(gg); | |||
} | |||
@@ -64,6 +69,9 @@ void gamegear_power(GameGear *gg, bool state) | |||
if (state) { | |||
// mmu_power(&gg->mmu); | |||
z80_power(&gg->cpu); | |||
gg->last_tick = get_time_ns(); | |||
} else { | |||
// TODO: free exception buffer | |||
} | |||
gg->powered = state; | |||
} | |||
@@ -75,12 +83,37 @@ void gamegear_power(GameGear *gg, bool state) | |||
time since the last call to gamegear_simulate() or gamegear_power() if the | |||
system was just powered on. If the system is powered off, this function | |||
does nothing. | |||
The return value indicates whether an exception flag has been set | |||
somewhere. If true, emulation must be stopped. gamegear_get_exception() can | |||
be used to fetch exception information. Power-cycling the GameGear with | |||
gamegear_power(gg, false) followed by gamegear_power(gg, true) will reset | |||
the exception flag and allow emulation to restart normally. | |||
*/ | |||
void gamegear_simulate(GameGear *gg) | |||
bool gamegear_simulate(GameGear *gg) | |||
{ | |||
if (!gg->powered) | |||
return; | |||
return false; | |||
uint64_t last = gg->last_tick, tick; | |||
tick = gg->last_tick = get_time_ns(); | |||
return z80_do_cycles(&gg->cpu, (tick - last) * CPU_CLOCK_SPEED / 1e9); | |||
} | |||
/* | |||
If an exception flag has been set in the GameGear, return the reason. | |||
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 | |||
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. | |||
*/ | |||
const char* gamegear_get_exception(GameGear *gg) | |||
{ | |||
if (!gg->powered) | |||
return NULL; | |||
// TODO | |||
// z80_do_cycles(&gg->cpu, ...); | |||
// TODO: return ptr to exception buffer | |||
return "Unknown exception"; | |||
} |
@@ -4,20 +4,19 @@ | |||
#pragma once | |||
#include <stdbool.h> | |||
#include <stdint.h> | |||
#include "mmu.h" | |||
#include "rom.h" | |||
#include "z80.h" | |||
/* Clock speed in Hz was taken from the official Sega GG documentation */ | |||
#define CPU_CLOCK_SPEED 3579545 | |||
/* Structs */ | |||
typedef struct { | |||
MMU mmu; | |||
Z80 cpu; | |||
bool powered; | |||
uint64_t last_tick; | |||
} GameGear; | |||
/* Functions */ | |||
@@ -26,4 +25,5 @@ GameGear* gamegear_create(); | |||
void gamegear_destroy(GameGear*); | |||
void gamegear_load(GameGear*, ROM*); | |||
void gamegear_power(GameGear*, bool); | |||
void gamegear_simulate(GameGear*); | |||
bool gamegear_simulate(GameGear*); | |||
const char* gamegear_get_exception(GameGear*); |
@@ -16,13 +16,15 @@ void iomanager_emulate(GameGear *gg) | |||
DEBUG("IOManager powering GameGear") | |||
gamegear_power(gg, true); | |||
#ifdef DEBUG_MODE | |||
z80_dump_registers(&gg->cpu); | |||
#endif | |||
// TODO: use SDL events | |||
while (1) { | |||
gamegear_simulate(gg); | |||
if (gamegear_simulate(gg)) { | |||
DEBUG("IOManager caught exception: %s", gamegear_get_exception(gg)) | |||
#ifdef DEBUG_MODE | |||
z80_dump_registers(&gg->cpu); | |||
#endif | |||
break; | |||
} | |||
usleep(1000 * 1000 / 60); | |||
} | |||
@@ -3,6 +3,19 @@ | |||
#include "util.h" | |||
#if defined __APPLE__ | |||
#include <mach/mach_time.h> | |||
#elif defined __linux__ | |||
#include <time.h> | |||
#ifndef CLOCK_MONOTONIC | |||
#error "Unsupported operating system or compiler." | |||
#endif | |||
#else | |||
#error "Unsupported operating system or compiler." | |||
#endif | |||
#define NS_PER_SEC 1000000000 | |||
/* | |||
Convert a BCD-encoded hexadecimal number to decimal. | |||
*/ | |||
@@ -10,3 +23,21 @@ uint8_t bcd_decode(uint8_t num) | |||
{ | |||
return ((num >> 4) * 10) + (num & 0x0F); | |||
} | |||
/* | |||
Return monotonic time, in nanoseconds. | |||
*/ | |||
uint64_t get_time_ns() | |||
{ | |||
#if defined __APPLE__ | |||
static mach_timebase_info_data_t tb_info; | |||
if (tb_info.denom == 0) | |||
mach_timebase_info(&tb_info); | |||
// Apple's docs pretty much say "screw overflow" here... | |||
return mach_absolute_time() * tb_info.numer / tb_info.denom; | |||
#elif defined __linux__ | |||
struct timespec spec; | |||
clock_gettime(CLOCK_MONOTONIC, &spec); | |||
return spec.tv_sec * NS_PER_SEC + spec.tv_nsec; | |||
#endif | |||
} |
@@ -8,3 +8,4 @@ | |||
/* Functions */ | |||
uint8_t bcd_decode(uint8_t); | |||
uint64_t get_time_ns(); |
@@ -14,14 +14,21 @@ | |||
/* | |||
Initialize a Z80 object. | |||
Register values are invalid until z80_power() is called. No other Z80 | |||
functions should be called before it. | |||
*/ | |||
void z80_init(Z80 *z80, uint64_t clock_speed) | |||
void z80_init(Z80 *z80, MMU *mmu) | |||
{ | |||
z80->clock_speed = clock_speed; | |||
z80->mmu = mmu; | |||
z80->except = true; | |||
} | |||
/* | |||
Power on the Z80, setting registers to their default values. | |||
This also clears the exception flag, which is necessary before the Z80 can | |||
begin emulation. | |||
*/ | |||
void z80_power(Z80 *z80) | |||
{ | |||
@@ -46,6 +53,9 @@ void z80_power(Z80 *z80) | |||
regfile->im_a = regfile->im_b = 0; | |||
regfile->iff1 = regfile->iff2 = 0; | |||
z80->except = false; | |||
z80->pending_cycles = 0; | |||
} | |||
/* | |||
@@ -76,6 +86,24 @@ static inline uint8_t get_interrupt_mode(Z80 *z80) | |||
return 2; | |||
} | |||
/* | |||
Emulate the given number of cycles of the Z80, or until an exception. | |||
The return value indicates whether the exception flag is set. If it is, | |||
then emulation must be stopped because further calls to z80_do_cycles() | |||
will have no effect. The exception flag can be reset with z80_power(). | |||
*/ | |||
bool z80_do_cycles(Z80 *z80, double cycles) | |||
{ | |||
if (z80->except) | |||
return true; | |||
z80->pending_cycles += cycles; | |||
// TODO: fetch instruction... | |||
return false; | |||
} | |||
#ifdef DEBUG_MODE | |||
/* | |||
DEBUG FUNCTION: Print out all register values to stdout. | |||
@@ -119,7 +147,8 @@ void z80_dump_registers(Z80 *z80) | |||
DEBUG("- I: 0x%2X", regfile->i) | |||
DEBUG("- R: 0x%2X", regfile->r) | |||
DEBUG("- IM: 0b%u%u (mode: %u)", regfile->im_a, regfile->im_b, get_interrupt_mode(z80)) | |||
DEBUG("- IM: 0b%u%u (mode: %u)", regfile->im_a, regfile->im_b, | |||
get_interrupt_mode(z80)) | |||
DEBUG("- IFF1: %u", regfile->iff1) | |||
DEBUG("- IFF2: %u", regfile->iff2) | |||
} | |||
@@ -6,6 +6,8 @@ | |||
#include <stdbool.h> | |||
#include <stdint.h> | |||
#include "mmu.h" | |||
/* Structs */ | |||
typedef struct { | |||
@@ -19,13 +21,16 @@ typedef struct { | |||
typedef struct { | |||
Z80RegFile regfile; | |||
uint64_t clock_speed; | |||
MMU *mmu; | |||
bool except; | |||
double pending_cycles; | |||
} Z80; | |||
/* Functions */ | |||
void z80_init(Z80*, uint64_t); | |||
void z80_init(Z80*, MMU*); | |||
void z80_power(Z80*); | |||
bool z80_do_cycles(Z80*, double); | |||
#ifdef DEBUG_MODE | |||
void z80_dump_registers(Z80*); | |||