diff options
Diffstat (limited to 'src/emu/cpu/z80gb/z80gb.c')
-rw-r--r-- | src/emu/cpu/z80gb/z80gb.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/src/emu/cpu/z80gb/z80gb.c b/src/emu/cpu/z80gb/z80gb.c new file mode 100644 index 00000000000..99ca7773bdd --- /dev/null +++ b/src/emu/cpu/z80gb/z80gb.c @@ -0,0 +1,485 @@ +/*************************************************************/ +/** **/ +/** z80gb.c **/ +/** **/ +/** This file contains implementation for the GameBoy CPU. **/ +/** See z80gb.h for the relevant definitions. Please, note **/ +/** that this code can not be used to emulate a generic Z80 **/ +/** because the GameBoy version of it differs from Z80 in **/ +/** many ways. **/ +/** **/ +/** Orginal cpu code (PlayBoy) Carsten Sorensen 1998 **/ +/** MESS modifications Hans de Goede 1998 **/ +/** Adapted to new cpuintrf Juergen Buchmueller 2000 **/ +/** Adapted to new cpuintrf Anthony Kruize 2002 **/ +/** Changed reset function to **/ +/** reset all registers instead **/ +/** of just AF. Wilbert Pol 2004 **/ +/** **/ +/** 1.1: **/ +/** Removed dependency on the mess gameboy driver **/ +/** **/ +/** 1.2: **/ +/** Fixed cycle count for taking an interrupt **/ +/** Fixed cycle count for BIT X,(HL) instructions **/ +/** Fixed flags in RRCA instruction **/ +/** Fixed DAA instruction **/ +/** Fixed flags in ADD SP,n8 instruction **/ +/** Fixed flags in LD HL,SP+n8 instruction **/ +/** **/ +/** 1.3: **/ +/** Improved triggering of the HALT bug **/ +/** Added 4 cycle penalty when leaving HALT state for **/ +/** newer versions of the cpu core **/ +/** **/ +/*************************************************************/ +#include "z80gb.h" +#include "debugger.h" + +#define FLAG_Z 0x80 +#define FLAG_N 0x40 +#define FLAG_H 0x20 +#define FLAG_C 0x10 + +#define CYCLES_PASSED(X) z80gb_ICount -= ((X) / (Regs.w.gb_speed)); \ + if ( Regs.w.timer_callback ) { \ + Regs.w.timer_callback( X ); \ + } + +typedef struct { + UINT16 AF; + UINT16 BC; + UINT16 DE; + UINT16 HL; + + UINT16 SP; + UINT16 PC; + /* Interrupt related */ + UINT8 IE; + UINT8 IF; + int irq_state; + int ei_delay; + int (*irq_callback)(int irqline); + /* Timer stuff */ + void (*timer_callback)(int cycles); + /* Others */ + int gb_speed; + int gb_speed_change_pending; + int enable; + int doHALTbug; + int haltIFstatus; + UINT8 features; + const Z80GB_CONFIG *config; +} z80gb_16BitRegs; + +#ifdef LSB_FIRST +typedef struct { + UINT8 F; + UINT8 A; + UINT8 C; + UINT8 B; + UINT8 E; + UINT8 D; + UINT8 L; + UINT8 H; +} z80gb_8BitRegs; +#else +typedef struct { + UINT8 A; + UINT8 F; + UINT8 B; + UINT8 C; + UINT8 D; + UINT8 E; + UINT8 H; + UINT8 L; +} z80gb_8BitRegs; +#endif + +typedef union { + z80gb_16BitRegs w; + z80gb_8BitRegs b; +} z80gb_regs; + +typedef int (*OpcodeEmulator) (void); + +static z80gb_regs Regs; + +#define IME 0x01 +#define HALTED 0x02 + +/****************************************************************************/ +/* Memory functions */ +/****************************************************************************/ + +#define mem_ReadByte(A) ((UINT8)program_read_byte_8(A)) +#define mem_WriteByte(A,V) (program_write_byte_8(A,V)) + +INLINE UINT16 mem_ReadWord (UINT32 address) +{ + UINT16 value = (UINT16) mem_ReadByte ((address + 1) & 0xffff) << 8; + value |= mem_ReadByte (address); + return value; +} + +INLINE void mem_WriteWord (UINT32 address, UINT16 value) +{ + mem_WriteByte (address, value & 0xFF); + mem_WriteByte ((address + 1) & 0xffff, value >> 8); +} + +/* Nr of cycles to run */ +int z80gb_ICount; + +static int Cycles[256] = +{ + 4,12, 8, 8, 4, 4, 8, 4,20, 8, 8, 8, 4, 4, 8, 4, + 4,12, 8, 8, 4, 4, 8, 4,12, 8, 8, 8, 4, 4, 8, 4, + 8,12, 8, 8, 4, 4, 8, 4, 8, 8, 8, 8, 4, 4, 8, 4, + 8,12, 8, 8,12,12,12, 4, 8, 8, 8, 8, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 8, 8, 8, 8, 8, 8, 4, 8, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 8,12,12,16,12,16, 8,16, 8,16,12, 0,12,24, 8,16, + 8,12,12, 4,12,16, 8,16, 8,16,12, 4,12, 4, 8,16, + 12,12, 8, 4, 4,16, 8,16,16, 4,16, 4, 4, 4, 8,16, + 12,12, 8, 4, 4,16, 8,16,12, 8,16, 4, 4, 4, 8,16 +}; + +static int CyclesCB[256] = +{ + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8, + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8, + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8, + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8, + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8 +}; + +static void z80gb_init(int index, int clock, const void *config, int (*irqcallback)(int)) +{ + Regs.w.config = (const Z80GB_CONFIG *) config; + Regs.w.irq_callback = irqcallback; +} + +/*** Reset Z80 registers: *********************************/ +/*** This function can be used to reset the register ***/ +/*** file before starting execution with z80gb_execute()***/ +/*** It sets the registers to their initial values. ***/ +/**********************************************************/ +static void z80gb_reset(void) +{ + Regs.w.AF = 0x0000; + Regs.w.BC = 0x0000; + Regs.w.DE = 0x0000; + Regs.w.HL = 0x0000; + Regs.w.SP = 0x0000; + Regs.w.PC = 0x0000; + Regs.w.timer_callback = NULL; + Regs.w.features = Z80GB_FEATURE_HALT_BUG; + if (Regs.w.config) + { + if ( Regs.w.config->regs ) { + Regs.w.AF = Regs.w.config->regs[0]; + Regs.w.BC = Regs.w.config->regs[1]; + Regs.w.DE = Regs.w.config->regs[2]; + Regs.w.HL = Regs.w.config->regs[3]; + Regs.w.SP = Regs.w.config->regs[4]; + Regs.w.PC = Regs.w.config->regs[5]; + } + Regs.w.timer_callback = Regs.w.config->timer_callback; + Regs.w.features = Regs.w.config->features; + } + Regs.w.enable = 0; + Regs.w.IE = 0; + Regs.w.IF = 0; + + Regs.w.doHALTbug = 0; + Regs.w.ei_delay = 0; + Regs.w.gb_speed_change_pending = 0; + Regs.w.gb_speed = 1; +} + +INLINE void z80gb_ProcessInterrupts (void) +{ + UINT8 irq = Regs.w.IE & Regs.w.IF; + + /* Interrupts should be taken after the first instruction after an EI instruction */ + if (Regs.w.ei_delay) { + Regs.w.ei_delay = 0; + return; + } + + /* + logerror("Attempting to process Z80GB Interrupt IRQ $%02X\n", irq); + logerror("Attempting to process Z80GB Interrupt IE $%02X\n", Regs.w.IE); + logerror("Attempting to process Z80GB Interrupt IF $%02X\n", Regs.w.IF); + */ + if (irq) + { + int irqline = 0; + /* + logerror("Z80GB Interrupt IRQ $%02X\n", irq); + */ + + for( ; irqline < 5; irqline++ ) + { + if( irq & (1<<irqline) ) + { + if (Regs.w.enable & HALTED) + { + Regs.w.enable &= ~HALTED; + Regs.w.IF &= ~(1 << irqline); + Regs.w.PC++; + if ( ! Regs.w.enable & IME ) { + if ( Regs.w.features & Z80GB_FEATURE_HALT_BUG ) { + /* Old cpu core (dmg/mgb/sgb) */ + /* check if the HALT bug should be performed */ + if ( Regs.w.haltIFstatus ) { + Regs.w.doHALTbug = 1; + } + } else { + /* New cpu core (cgb/agb/ags) */ + CYCLES_PASSED( 4 ); + } + } + } + if ( Regs.w.enable & IME ) { + if ( Regs.w.irq_callback ) + (*Regs.w.irq_callback)(irqline); + Regs.w.enable &= ~IME; + Regs.w.IF &= ~(1 << irqline); + CYCLES_PASSED( 20 ); + Regs.w.SP -= 2; + mem_WriteWord (Regs.w.SP, Regs.w.PC); + Regs.w.PC = 0x40 + irqline * 8; + /*logerror("Z80GB Interrupt PC $%04X\n", Regs.w.PC );*/ + return; + } + } + } + } +} + +/**********************************************************/ +/*** Execute z80gb code for cycles cycles, return nr of ***/ +/*** cycles actually executed. ***/ +/**********************************************************/ +static int z80gb_execute (int cycles) +{ + UINT8 x; + + z80gb_ICount = cycles; + + do + { + z80gb_ProcessInterrupts (); + CALL_MAME_DEBUG; + if ( Regs.w.enable & HALTED ) { + CYCLES_PASSED( Cycles[0x76] ); + } else { + x = mem_ReadByte (Regs.w.PC++); + if ( Regs.w.doHALTbug ) { + Regs.w.PC--; + Regs.w.doHALTbug = 0; + } + CYCLES_PASSED( Cycles[x] ); + switch (x) + { +#include "opc_main.h" + } + } + } while (z80gb_ICount > 0); + + return cycles - z80gb_ICount; +} + +static void z80gb_burn(int cycles) +{ + if( cycles > 0 ) + { + /* NOP takes 4 cycles per instruction */ + int n = (cycles + 3) / 4; + z80gb_ICount -= 4 * n; + } +} + +/****************************************************************************/ +/* Set all registers to given values */ +/****************************************************************************/ +static void z80gb_set_context (void *src) +{ + if( src ) + Regs = *(z80gb_regs *)src; + change_pc(Regs.w.PC); +} + +/****************************************************************************/ +/* Get all registers in given buffer */ +/****************************************************************************/ +static void z80gb_get_context (void *dst) +{ + if( dst ) + *(z80gb_regs *)dst = Regs; +} + + + +static void z80gb_set_irq_line (int irqline, int state) +{ + /*logerror("setting irq line 0x%02x state 0x%08x\n", irqline, state);*/ + //if( Regs.w.irq_state == state ) + // return; + + Regs.w.irq_state = state; + if( state == ASSERT_LINE ) + { + + Regs.w.IF |= (0x01 << irqline); + /*logerror("Z80GB assert irq line %d ($%02X)\n", irqline, Regs.w.IF);*/ + + } + else + { + + Regs.w.IF &= ~(0x01 << irqline); + /*logerror("Z80GB clear irq line %d ($%02X)\n", irqline, Regs.w.IF);*/ + + } +} + +/*static void z80gb_clear_pending_interrupts (void) +{ + Regs.w.IF = 0; +}*/ + +static void z80gb_set_info(UINT32 state, cpuinfo *info) +{ + switch (state) + { + /* --- the following bits of info are set as 64-bit signed integers --- */ + case CPUINFO_INT_INPUT_STATE + 0: + case CPUINFO_INT_INPUT_STATE + 1: + case CPUINFO_INT_INPUT_STATE + 2: + case CPUINFO_INT_INPUT_STATE + 3: + case CPUINFO_INT_INPUT_STATE + 4: z80gb_set_irq_line(state-CPUINFO_INT_INPUT_STATE, info->i); break; + + case CPUINFO_INT_SP: Regs.w.SP = info->i; break; + case CPUINFO_INT_PC: Regs.w.PC = info->i; change_pc(Regs.w.PC); break; + + case CPUINFO_INT_REGISTER + Z80GB_PC: Regs.w.PC = info->i; change_pc(Regs.w.PC); break; + case CPUINFO_INT_REGISTER + Z80GB_SP: Regs.w.SP = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_AF: Regs.w.AF = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_BC: Regs.w.BC = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_DE: Regs.w.DE = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_HL: Regs.w.HL = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_IE: Regs.w.IE = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_IF: Regs.w.IF = info->i; break; + case CPUINFO_INT_REGISTER + Z80GB_SPEED: Regs.w.gb_speed_change_pending = info->i & 0x01; break; + } +} + +void z80gb_get_info(UINT32 state, cpuinfo *info) +{ + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case CPUINFO_INT_CONTEXT_SIZE: info->i = sizeof(Regs); break; + case CPUINFO_INT_INPUT_LINES: info->i = 5; break; + case CPUINFO_INT_DEFAULT_IRQ_VECTOR: info->i = 0xff; break; + case CPUINFO_INT_ENDIANNESS: info->i = CPU_IS_LE; break; + case CPUINFO_INT_CLOCK_DIVIDER: info->i = 1; break; + case CPUINFO_INT_MIN_INSTRUCTION_BYTES: info->i = 1; break; + case CPUINFO_INT_MAX_INSTRUCTION_BYTES: info->i = 4; break; + case CPUINFO_INT_MIN_CYCLES: info->i = 1; /* right? */ break; + case CPUINFO_INT_MAX_CYCLES: info->i = 16; /* right? */ break; + + case CPUINFO_INT_DATABUS_WIDTH + ADDRESS_SPACE_PROGRAM: info->i = 8; break; + case CPUINFO_INT_ADDRBUS_WIDTH + ADDRESS_SPACE_PROGRAM: info->i = 16; break; + case CPUINFO_INT_ADDRBUS_SHIFT + ADDRESS_SPACE_PROGRAM: info->i = 0; break; + case CPUINFO_INT_DATABUS_WIDTH + ADDRESS_SPACE_DATA: info->i = 0; break; + case CPUINFO_INT_ADDRBUS_WIDTH + ADDRESS_SPACE_DATA: info->i = 0; break; + case CPUINFO_INT_ADDRBUS_SHIFT + ADDRESS_SPACE_DATA: info->i = 0; break; + case CPUINFO_INT_DATABUS_WIDTH + ADDRESS_SPACE_IO: info->i = 8; break; + case CPUINFO_INT_ADDRBUS_WIDTH + ADDRESS_SPACE_IO: info->i = 16; break; + case CPUINFO_INT_ADDRBUS_SHIFT + ADDRESS_SPACE_IO: info->i = 0; break; + + case CPUINFO_INT_SP: info->i = Regs.w.SP; break; + case CPUINFO_INT_PC: info->i = Regs.w.PC; break; + case CPUINFO_INT_PREVIOUSPC: info->i = 0; /* TODO??? */ break; + + case CPUINFO_INT_INPUT_STATE + 0: + case CPUINFO_INT_INPUT_STATE + 1: + case CPUINFO_INT_INPUT_STATE + 2: + case CPUINFO_INT_INPUT_STATE + 3: + case CPUINFO_INT_INPUT_STATE + 4: info->i = Regs.w.IF & (1 << (state-CPUINFO_INT_INPUT_STATE)); break; + + case CPUINFO_INT_REGISTER + Z80GB_PC: info->i = Regs.w.PC; break; + case CPUINFO_INT_REGISTER + Z80GB_SP: info->i = Regs.w.SP; break; + case CPUINFO_INT_REGISTER + Z80GB_AF: info->i = Regs.w.AF; break; + case CPUINFO_INT_REGISTER + Z80GB_BC: info->i = Regs.w.BC; break; + case CPUINFO_INT_REGISTER + Z80GB_DE: info->i = Regs.w.DE; break; + case CPUINFO_INT_REGISTER + Z80GB_HL: info->i = Regs.w.HL; break; + case CPUINFO_INT_REGISTER + Z80GB_IE: info->i = Regs.w.IE; break; + case CPUINFO_INT_REGISTER + Z80GB_IF: info->i = Regs.w.IF; break; + case CPUINFO_INT_REGISTER + Z80GB_SPEED: info->i = 0x7E | ( ( Regs.w.gb_speed - 1 ) << 7 ) | Regs.w.gb_speed_change_pending; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case CPUINFO_PTR_SET_INFO: info->setinfo = z80gb_set_info; break; + case CPUINFO_PTR_GET_CONTEXT: info->getcontext = z80gb_get_context; break; + case CPUINFO_PTR_SET_CONTEXT: info->setcontext = z80gb_set_context; break; + case CPUINFO_PTR_INIT: info->init = z80gb_init; break; + case CPUINFO_PTR_RESET: info->reset = z80gb_reset; break; + case CPUINFO_PTR_EXECUTE: info->execute = z80gb_execute; break; + case CPUINFO_PTR_BURN: info->burn = z80gb_burn; break; + +#ifdef MAME_DEBUG + case CPUINFO_PTR_DISASSEMBLE: info->disassemble = z80gb_dasm; break; +#endif + case CPUINFO_PTR_INSTRUCTION_COUNTER: info->icount = &z80gb_ICount; break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case CPUINFO_STR_NAME: strcpy(info->s, "Z80GB"); break; + case CPUINFO_STR_CORE_FAMILY: strcpy(info->s, "Nintendo Z80"); break; + case CPUINFO_STR_CORE_VERSION: strcpy(info->s, "1.3"); break; + case CPUINFO_STR_CORE_FILE: strcpy(info->s, __FILE__); break; + case CPUINFO_STR_CORE_CREDITS: strcpy(info->s, "Copyright (C) 2000 by The MESS Team."); break; + + case CPUINFO_STR_FLAGS: + sprintf(info->s, "%c%c%c%c%c%c%c%c", + Regs.b.F & 0x80 ? 'Z':'.', + Regs.b.F & 0x40 ? 'N':'.', + Regs.b.F & 0x20 ? 'H':'.', + Regs.b.F & 0x10 ? 'C':'.', + Regs.b.F & 0x08 ? '3':'.', + Regs.b.F & 0x04 ? '2':'.', + Regs.b.F & 0x02 ? '1':'.', + Regs.b.F & 0x01 ? '0':'.'); + break; + + case CPUINFO_STR_REGISTER + Z80GB_PC: sprintf(info->s, "PC:%04X", Regs.w.PC); break; + case CPUINFO_STR_REGISTER + Z80GB_SP: sprintf(info->s, "SP:%04X", Regs.w.SP); break; + case CPUINFO_STR_REGISTER + Z80GB_AF: sprintf(info->s, "AF:%04X", Regs.w.AF); break; + case CPUINFO_STR_REGISTER + Z80GB_BC: sprintf(info->s, "BC:%04X", Regs.w.BC); break; + case CPUINFO_STR_REGISTER + Z80GB_DE: sprintf(info->s, "DE:%04X", Regs.w.DE); break; + case CPUINFO_STR_REGISTER + Z80GB_HL: sprintf(info->s, "HL:%04X", Regs.w.HL); break; + case CPUINFO_STR_REGISTER + Z80GB_IRQ_STATE: sprintf(info->s, "IRQ:%X", Regs.w.enable & IME ); break; + case CPUINFO_STR_REGISTER + Z80GB_IE: sprintf(info->s, "IE:%02X", Regs.w.IE); break; + case CPUINFO_STR_REGISTER + Z80GB_IF: sprintf(info->s, "IF:%02X", Regs.w.IF); break; + } +} |