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