| @@ -24,6 +24,7 @@ GameGear* gamegear_create() | |||||
| // mmu_init(&gg->mmu, ...); | // mmu_init(&gg->mmu, ...); | ||||
| z80_init(&gg->cpu, &gg->mmu); | z80_init(&gg->cpu, &gg->mmu); | ||||
| gg->powered = false; | gg->powered = false; | ||||
| gg->exc_buffer[0] = '\0'; | |||||
| return gg; | 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 | Powering on the GameGear executes boot code (e.g. clearing memory and | ||||
| setting initial register values) and starts the clock. Powering it off | 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. | 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); | z80_power(&gg->cpu); | ||||
| gg->last_tick = get_time_ns(); | gg->last_tick = get_time_ns(); | ||||
| } else { | } else { | ||||
| // TODO: free exception buffer | |||||
| gg->exc_buffer[0] = '\0'; | |||||
| } | } | ||||
| gg->powered = state; | 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 | 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 | 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) | const char* gamegear_get_exception(GameGear *gg) | ||||
| { | { | ||||
| #define SET_EXC(...) snprintf(gg->exc_buffer, GG_EXC_BUFF_SIZE, __VA_ARGS__); | |||||
| if (!gg->powered) | if (!gg->powered) | ||||
| return NULL; | 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 "rom.h" | ||||
| #include "z80.h" | #include "z80.h" | ||||
| #define GG_EXC_BUFF_SIZE 128 | |||||
| /* Structs */ | /* Structs */ | ||||
| typedef struct { | typedef struct { | ||||
| @@ -17,6 +19,7 @@ typedef struct { | |||||
| Z80 cpu; | Z80 cpu; | ||||
| bool powered; | bool powered; | ||||
| uint64_t last_tick; | uint64_t last_tick; | ||||
| char exc_buffer[GG_EXC_BUFF_SIZE]; | |||||
| } GameGear; | } GameGear; | ||||
| /* Functions */ | /* Functions */ | ||||
| @@ -22,6 +22,8 @@ void z80_init(Z80 *z80, MMU *mmu) | |||||
| { | { | ||||
| z80->mmu = mmu; | z80->mmu = mmu; | ||||
| z80->except = true; | 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; | 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. | 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) | bool z80_do_cycles(Z80 *z80, double cycles) | ||||
| { | { | ||||
| if (z80->except) | |||||
| return true; | |||||
| cycles -= z80->pending_cycles; | 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 = mmu_read_byte(&z80->mmu, z80->regfile.pc); | ||||
| uint8_t opcode = 0x00; | uint8_t opcode = 0x00; | ||||
| cycles -= (*instruction_lookup_table[opcode])(z80) - 2; | |||||
| cycles -= (*instruction_lookup_table[opcode])(z80, opcode) - 2; | |||||
| } | } | ||||
| z80->pending_cycles = -cycles; | z80->pending_cycles = -cycles; | ||||
| return false; | |||||
| return z80->except; | |||||
| } | } | ||||
| #ifdef DEBUG_MODE | #ifdef DEBUG_MODE | ||||
| @@ -8,6 +8,9 @@ | |||||
| #include "mmu.h" | #include "mmu.h" | ||||
| #define Z80_EXC_NOT_POWERED 0 | |||||
| #define Z80_EXC_UNIMPLEMENTED_OPCODE 1 | |||||
| /* Structs */ | /* Structs */ | ||||
| typedef struct { | typedef struct { | ||||
| @@ -23,6 +26,7 @@ typedef struct { | |||||
| Z80RegFile regfile; | Z80RegFile regfile; | ||||
| MMU *mmu; | MMU *mmu; | ||||
| bool except; | bool except; | ||||
| uint8_t exc_code, exc_data; | |||||
| double pending_cycles; | double pending_cycles; | ||||
| } Z80; | } 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 | |||||
| }; | |||||