@@ -5,6 +5,10 @@ | |||||
#include "gamegear.h" | #include "gamegear.h" | ||||
#include "logging.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. | Create and return a pointer to a new GameGear object. | ||||
@@ -18,7 +22,7 @@ GameGear* gamegear_create() | |||||
OUT_OF_MEMORY() | OUT_OF_MEMORY() | ||||
// mmu_init(&gg->mmu, ...); | // mmu_init(&gg->mmu, ...); | ||||
z80_init(&gg->cpu, CPU_CLOCK_SPEED); | |||||
z80_init(&gg->cpu, &gg->mmu); | |||||
gg->powered = false; | gg->powered = false; | ||||
return gg; | return gg; | ||||
} | } | ||||
@@ -30,6 +34,7 @@ GameGear* gamegear_create() | |||||
*/ | */ | ||||
void gamegear_destroy(GameGear *gg) | void gamegear_destroy(GameGear *gg) | ||||
{ | { | ||||
// mmu_free(&gg->mmu); | |||||
free(gg); | free(gg); | ||||
} | } | ||||
@@ -64,6 +69,9 @@ void gamegear_power(GameGear *gg, bool state) | |||||
if (state) { | if (state) { | ||||
// mmu_power(&gg->mmu); | // mmu_power(&gg->mmu); | ||||
z80_power(&gg->cpu); | z80_power(&gg->cpu); | ||||
gg->last_tick = get_time_ns(); | |||||
} else { | |||||
// TODO: free exception buffer | |||||
} | } | ||||
gg->powered = state; | 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 | 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 | system was just powered on. If the system is powered off, this function | ||||
does nothing. | 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) | 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 | #pragma once | ||||
#include <stdbool.h> | #include <stdbool.h> | ||||
#include <stdint.h> | |||||
#include "mmu.h" | #include "mmu.h" | ||||
#include "rom.h" | #include "rom.h" | ||||
#include "z80.h" | #include "z80.h" | ||||
/* Clock speed in Hz was taken from the official Sega GG documentation */ | |||||
#define CPU_CLOCK_SPEED 3579545 | |||||
/* Structs */ | /* Structs */ | ||||
typedef struct { | typedef struct { | ||||
MMU mmu; | MMU mmu; | ||||
Z80 cpu; | Z80 cpu; | ||||
bool powered; | bool powered; | ||||
uint64_t last_tick; | |||||
} GameGear; | } GameGear; | ||||
/* Functions */ | /* Functions */ | ||||
@@ -26,4 +25,5 @@ GameGear* gamegear_create(); | |||||
void gamegear_destroy(GameGear*); | void gamegear_destroy(GameGear*); | ||||
void gamegear_load(GameGear*, ROM*); | void gamegear_load(GameGear*, ROM*); | ||||
void gamegear_power(GameGear*, bool); | 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") | DEBUG("IOManager powering GameGear") | ||||
gamegear_power(gg, true); | gamegear_power(gg, true); | ||||
#ifdef DEBUG_MODE | |||||
z80_dump_registers(&gg->cpu); | |||||
#endif | |||||
// TODO: use SDL events | // TODO: use SDL events | ||||
while (1) { | 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); | usleep(1000 * 1000 / 60); | ||||
} | } | ||||
@@ -3,6 +3,19 @@ | |||||
#include "util.h" | #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. | 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 ((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 */ | /* Functions */ | ||||
uint8_t bcd_decode(uint8_t); | uint8_t bcd_decode(uint8_t); | ||||
uint64_t get_time_ns(); |
@@ -14,14 +14,21 @@ | |||||
/* | /* | ||||
Initialize a Z80 object. | 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. | 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) | void z80_power(Z80 *z80) | ||||
{ | { | ||||
@@ -46,6 +53,9 @@ void z80_power(Z80 *z80) | |||||
regfile->im_a = regfile->im_b = 0; | regfile->im_a = regfile->im_b = 0; | ||||
regfile->iff1 = regfile->iff2 = 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; | 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 | #ifdef DEBUG_MODE | ||||
/* | /* | ||||
DEBUG FUNCTION: Print out all register values to stdout. | 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("- I: 0x%2X", regfile->i) | ||||
DEBUG("- R: 0x%2X", regfile->r) | 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("- IFF1: %u", regfile->iff1) | ||||
DEBUG("- IFF2: %u", regfile->iff2) | DEBUG("- IFF2: %u", regfile->iff2) | ||||
} | } | ||||
@@ -6,6 +6,8 @@ | |||||
#include <stdbool.h> | #include <stdbool.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include "mmu.h" | |||||
/* Structs */ | /* Structs */ | ||||
typedef struct { | typedef struct { | ||||
@@ -19,13 +21,16 @@ typedef struct { | |||||
typedef struct { | typedef struct { | ||||
Z80RegFile regfile; | Z80RegFile regfile; | ||||
uint64_t clock_speed; | |||||
MMU *mmu; | |||||
bool except; | |||||
double pending_cycles; | |||||
} Z80; | } Z80; | ||||
/* Functions */ | /* Functions */ | ||||
void z80_init(Z80*, uint64_t); | |||||
void z80_init(Z80*, MMU*); | |||||
void z80_power(Z80*); | void z80_power(Z80*); | ||||
bool z80_do_cycles(Z80*, double); | |||||
#ifdef DEBUG_MODE | #ifdef DEBUG_MODE | ||||
void z80_dump_registers(Z80*); | void z80_dump_registers(Z80*); | ||||