| @@ -24,6 +24,7 @@ GameGear* gamegear_create() | |||
| // mmu_init(&gg->mmu, ...); | |||
| z80_init(&gg->cpu, &gg->mmu); | |||
| gg->powered = false; | |||
| gg->exc_buffer[0] = '\0'; | |||
| return gg; | |||
| } | |||
| @@ -57,7 +58,7 @@ void gamegear_load(GameGear *gg, ROM *rom) | |||
| Powering on the GameGear executes boot code (e.g. clearing memory and | |||
| setting initial register values) and starts the clock. Powering it off | |||
| stops the clock. | |||
| stops the clock and clears any exception data. | |||
| Setting the power state to its current value has no effect. | |||
| */ | |||
| @@ -71,7 +72,7 @@ void gamegear_power(GameGear *gg, bool state) | |||
| z80_power(&gg->cpu); | |||
| gg->last_tick = get_time_ns(); | |||
| } else { | |||
| // TODO: free exception buffer | |||
| gg->exc_buffer[0] = '\0'; | |||
| } | |||
| gg->powered = state; | |||
| } | |||
| @@ -105,15 +106,33 @@ bool gamegear_simulate(GameGear *gg) | |||
| 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. | |||
| owned by the GameGear object and should not be freed - its contents last | |||
| 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) | |||
| { | |||
| #define SET_EXC(...) snprintf(gg->exc_buffer, GG_EXC_BUFF_SIZE, __VA_ARGS__); | |||
| if (!gg->powered) | |||
| return NULL; | |||
| // TODO: return ptr to exception buffer | |||
| return "Unknown exception"; | |||
| if (!gg->exc_buffer[0]) { | |||
| if (gg->cpu.except) { | |||
| switch (gg->cpu.exc_code) { | |||
| case Z80_EXC_NOT_POWERED: | |||
| SET_EXC("CPU not powered") | |||
| break; | |||
| case Z80_EXC_UNIMPLEMENTED_OPCODE: | |||
| SET_EXC("Unimplemented opcode: 0x%02X", gg->cpu.exc_data) | |||
| break; | |||
| default: | |||
| SET_EXC("Unknown exception") | |||
| break; | |||
| } | |||
| } else { | |||
| return NULL; | |||
| } | |||
| } | |||
| return gg->exc_buffer; | |||
| #undef SET_EXC | |||
| } | |||
| @@ -10,6 +10,8 @@ | |||
| #include "rom.h" | |||
| #include "z80.h" | |||
| #define GG_EXC_BUFF_SIZE 128 | |||
| /* Structs */ | |||
| typedef struct { | |||
| @@ -17,6 +19,7 @@ typedef struct { | |||
| Z80 cpu; | |||
| bool powered; | |||
| uint64_t last_tick; | |||
| char exc_buffer[GG_EXC_BUFF_SIZE]; | |||
| } GameGear; | |||
| /* Functions */ | |||
| @@ -22,6 +22,8 @@ void z80_init(Z80 *z80, MMU *mmu) | |||
| { | |||
| z80->mmu = mmu; | |||
| z80->except = true; | |||
| z80->exc_code = Z80_EXC_NOT_POWERED; | |||
| z80->exc_data = 0; | |||
| } | |||
| /* | |||
| @@ -86,18 +88,7 @@ static inline uint8_t get_interrupt_mode(Z80 *z80) | |||
| return 2; | |||
| } | |||
| /* ------------------------------------------------------------------------- */ | |||
| static uint8_t z80_inst_nop(Z80 *z80) { | |||
| z80->regfile.pc++; | |||
| return 4; | |||
| } | |||
| static uint8_t (*instruction_lookup_table[256])(Z80*) = { | |||
| z80_inst_nop | |||
| }; | |||
| /* ------------------------------------------------------------------------- */ | |||
| #include "z80_instructions.inc" | |||
| /* | |||
| Emulate the given number of cycles of the Z80, or until an exception. | |||
| @@ -108,18 +99,15 @@ static uint8_t (*instruction_lookup_table[256])(Z80*) = { | |||
| */ | |||
| bool z80_do_cycles(Z80 *z80, double cycles) | |||
| { | |||
| if (z80->except) | |||
| return true; | |||
| cycles -= z80->pending_cycles; | |||
| while (cycles > 0) { | |||
| while (cycles > 0 && !z80->except) { | |||
| // uint8_t opcode = mmu_read_byte(&z80->mmu, z80->regfile.pc); | |||
| uint8_t opcode = 0x00; | |||
| cycles -= (*instruction_lookup_table[opcode])(z80) - 2; | |||
| cycles -= (*instruction_lookup_table[opcode])(z80, opcode) - 2; | |||
| } | |||
| z80->pending_cycles = -cycles; | |||
| return false; | |||
| return z80->except; | |||
| } | |||
| #ifdef DEBUG_MODE | |||
| @@ -8,6 +8,9 @@ | |||
| #include "mmu.h" | |||
| #define Z80_EXC_NOT_POWERED 0 | |||
| #define Z80_EXC_UNIMPLEMENTED_OPCODE 1 | |||
| /* Structs */ | |||
| typedef struct { | |||
| @@ -23,6 +26,7 @@ typedef struct { | |||
| Z80RegFile regfile; | |||
| MMU *mmu; | |||
| bool except; | |||
| uint8_t exc_code, exc_data; | |||
| double pending_cycles; | |||
| } Z80; | |||
| @@ -0,0 +1,34 @@ | |||
| /* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
| Released under the terms of the MIT License. See LICENSE for details. */ | |||
| /* | |||
| This file contains code to implement the Z80 instruction set. Since there | |||
| are a lot of functions, it is kept separate from the main z80.c file. It is | |||
| included in the middle of z80.c and should not be compiled separately. | |||
| */ | |||
| /* | |||
| 0x00: NOP | |||
| */ | |||
| static uint8_t z80_inst_nop(Z80 *z80, uint8_t opcode) | |||
| { | |||
| (void) opcode; | |||
| z80->regfile.pc++; | |||
| return 4; | |||
| } | |||
| /* | |||
| Unimplemented opcode handler. | |||
| */ | |||
| static uint8_t z80_inst_ERR(Z80 *z80, uint8_t opcode) | |||
| { | |||
| z80->except = true; | |||
| z80->exc_code = Z80_EXC_UNIMPLEMENTED_OPCODE; | |||
| z80->exc_data = opcode; | |||
| return 2; | |||
| } | |||
| static uint8_t (*instruction_lookup_table[256])(Z80*, uint8_t) = { | |||
| z80_inst_nop, | |||
| z80_inst_ERR | |||
| }; | |||