| @@ -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*); | ||||