From a202baa3a63228c55ae0ed56a9a3e5462a31b803 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Thu, 26 Mar 2015 00:36:26 -0400 Subject: [PATCH] Add proper timing code, etc. --- src/gamegear.c | 43 ++++++++++++++++++++++++++++++++++++++----- src/gamegear.h | 8 ++++---- src/iomanager.c | 12 +++++++----- src/util.c | 31 +++++++++++++++++++++++++++++++ src/util.h | 1 + src/z80.c | 35 ++++++++++++++++++++++++++++++++--- src/z80.h | 9 +++++++-- 7 files changed, 120 insertions(+), 19 deletions(-) diff --git a/src/gamegear.c b/src/gamegear.c index 02ac623..5fc760e 100644 --- a/src/gamegear.c +++ b/src/gamegear.c @@ -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"; } diff --git a/src/gamegear.h b/src/gamegear.h index 2f461ed..759714f 100644 --- a/src/gamegear.h +++ b/src/gamegear.h @@ -4,20 +4,19 @@ #pragma once #include +#include #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*); diff --git a/src/iomanager.c b/src/iomanager.c index 80a9233..22af649 100644 --- a/src/iomanager.c +++ b/src/iomanager.c @@ -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); } diff --git a/src/util.c b/src/util.c index b904600..e6294f2 100644 --- a/src/util.c +++ b/src/util.c @@ -3,6 +3,19 @@ #include "util.h" +#if defined __APPLE__ + #include +#elif defined __linux__ + #include + #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 +} diff --git a/src/util.h b/src/util.h index a867a8b..367f678 100644 --- a/src/util.h +++ b/src/util.h @@ -8,3 +8,4 @@ /* Functions */ uint8_t bcd_decode(uint8_t); +uint64_t get_time_ns(); diff --git a/src/z80.c b/src/z80.c index 5949ce8..ba55b39 100644 --- a/src/z80.c +++ b/src/z80.c @@ -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) } diff --git a/src/z80.h b/src/z80.h index b3486db..2b17b0d 100644 --- a/src/z80.h +++ b/src/z80.h @@ -6,6 +6,8 @@ #include #include +#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*);