2017-08-12 14:33:28 -05:00
|
|
|
/* Copyright (c) 2008, 2017 Stephen Checkoway
|
2017-08-12 13:12:35 -05:00
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <zel/z80.h>
|
|
|
|
#include <zel/z80_instructions.h>
|
|
|
|
|
2017-08-12 14:54:40 -05:00
|
|
|
#include "z80_types.h"
|
|
|
|
|
2017-08-12 13:12:35 -05:00
|
|
|
struct Z80_t
|
|
|
|
{
|
|
|
|
/* C guarantees consecutive layout */
|
|
|
|
word word_reg[13];
|
|
|
|
byte *byte_reg;
|
|
|
|
bool iff1;
|
|
|
|
bool iff2;
|
|
|
|
bool can_handle_interrupt;
|
|
|
|
int interrupt_mode;
|
|
|
|
bool interrupt;
|
|
|
|
bool nmi;
|
|
|
|
bool halt;
|
|
|
|
bool restart_io;
|
|
|
|
|
|
|
|
byte (*ReadMem)(word, bool, Z80);
|
|
|
|
void (*WriteMem)(word, byte, Z80);
|
|
|
|
byte (*ReadInterruptData)(word, Z80);
|
|
|
|
byte (*ReadIO)(word, Z80);
|
|
|
|
void (*WriteIO)(word, byte, Z80);
|
|
|
|
void (*InterruptComplete)(Z80);
|
|
|
|
void (*ControlFlow)(word, word, ControlFlowType, Z80);
|
|
|
|
};
|
|
|
|
// short cuts
|
|
|
|
#define WORD_REG (cpu->word_reg)
|
|
|
|
#define BYTE_REG (cpu->byte_reg)
|
|
|
|
#define SP (cpu->word_reg[REG_SP])
|
|
|
|
#define PC (cpu->word_reg[REG_PC])
|
|
|
|
#define PCH (cpu->byte_reg[REG_PCH])
|
|
|
|
#define PCL (cpu->byte_reg[REG_PCL])
|
|
|
|
#define BC (cpu->word_reg[REG_BC])
|
|
|
|
#define DE (cpu->word_reg[REG_DE])
|
|
|
|
#define HL (cpu->word_reg[REG_HL])
|
|
|
|
#define AF (cpu->word_reg[REG_AF])
|
|
|
|
#define B (cpu->byte_reg[REG_B])
|
|
|
|
#define C (cpu->byte_reg[REG_C])
|
|
|
|
#define D (cpu->byte_reg[REG_D])
|
|
|
|
#define E (cpu->byte_reg[REG_E])
|
|
|
|
#define H (cpu->byte_reg[REG_H])
|
|
|
|
#define L (cpu->byte_reg[REG_L])
|
|
|
|
#define A (cpu->byte_reg[REG_A])
|
|
|
|
#define FlagIsSet(f) (!FlagIsReset((f)))
|
|
|
|
#define FlagIsReset(f) (!(cpu->byte_reg[REG_F]&(1<<(f))))
|
|
|
|
#define SetFlag(f) (void)(cpu->byte_reg[REG_F]|=(1<<(f)))
|
|
|
|
#define ResetFlag(f) (void)(cpu->byte_reg[REG_F]&=~(1<<(f)))
|
|
|
|
#define SetFlagValue(f,v) \
|
|
|
|
(void)(cpu->byte_reg[REG_F] = (cpu->byte_reg[REG_F] & ~(1<<(f))) | (!!(v)<<(f)))
|
|
|
|
#define CondIsMet(c) ( ((c)>=0 && FlagIsSet((c))) || ((c)<0 && FlagIsReset(-(c+1))) )
|
|
|
|
|
|
|
|
|
|
|
|
void IgnoreControlFlow( word pc, word target, ControlFlowType cf, Z80 cpu ) { }
|
|
|
|
|
|
|
|
Z80 Z80_New( const Z80FunctionBlock *blk )
|
|
|
|
{
|
|
|
|
Z80 cpu = malloc( sizeof(struct Z80_t) );
|
|
|
|
assert( cpu != NULL );
|
|
|
|
memset( cpu, 0, sizeof(struct Z80_t) );
|
|
|
|
cpu->byte_reg = (byte*)cpu->word_reg;
|
|
|
|
SP = 0xffff;
|
|
|
|
AF = 0xffff;
|
|
|
|
cpu->can_handle_interrupt = true;
|
|
|
|
#define REQUIRE(x) assert(blk->x); cpu->x = blk->x
|
|
|
|
REQUIRE(ReadMem);
|
|
|
|
REQUIRE(WriteMem);
|
|
|
|
REQUIRE(ReadInterruptData);
|
|
|
|
REQUIRE(ReadIO);
|
|
|
|
REQUIRE(WriteIO);
|
|
|
|
REQUIRE(InterruptComplete);
|
|
|
|
#undef REQUIRE
|
|
|
|
cpu->ControlFlow = blk->ControlFlow? blk->ControlFlow:IgnoreControlFlow;
|
|
|
|
return cpu;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z80_Free( Z80 cpu )
|
|
|
|
{
|
|
|
|
/* nothing special needed */
|
|
|
|
free( cpu );
|
|
|
|
}
|
|
|
|
|
|
|
|
static byte ReadInstructionMemory( word address, void *data )
|
|
|
|
{
|
|
|
|
Z80 cpu = data;
|
|
|
|
return (cpu->ReadMem)( address, true, cpu );
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool ParityIsEven( uint_fast8_t a )
|
|
|
|
{
|
|
|
|
uint_fast8_t b = a & 0x55;
|
|
|
|
uint_fast8_t c = (a>>1) & 0x55;
|
|
|
|
a = b + c;
|
|
|
|
b = a & 0x33;
|
|
|
|
c = (a>>2) & 0x33;
|
|
|
|
a = b + c;
|
|
|
|
b = a & 0x0f;
|
|
|
|
c = (a>>4) & 0x0f;
|
|
|
|
return !((b + c)&1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Z80_Step( word *outPC, Z80 cpu )
|
|
|
|
{
|
|
|
|
Instruction inst;
|
|
|
|
uint_fast8_t r;
|
|
|
|
const uint_fast16_t oldPC = PC;
|
|
|
|
int ticks = 0;
|
|
|
|
|
|
|
|
cpu->restart_io = false;
|
|
|
|
if( cpu->nmi ||
|
|
|
|
(cpu->can_handle_interrupt && cpu->interrupt && cpu->iff1) )
|
|
|
|
{
|
|
|
|
cpu->halt = false;
|
|
|
|
// Any interrupt increases R by one.
|
|
|
|
r = BYTE_REG[REG_R];
|
|
|
|
r = ((r + 1) & 0x7f) | (r & 0x80);
|
|
|
|
BYTE_REG[REG_R] = r;
|
|
|
|
if( cpu->nmi )
|
|
|
|
{
|
|
|
|
cpu->nmi = false;
|
|
|
|
cpu->iff1 = false; /* Disable interrupts. */
|
|
|
|
// 5 cycles fetching and ignoring the opcode
|
|
|
|
// This can cause another nmi.
|
|
|
|
(void)(cpu->ReadMem)( PC, true, cpu );
|
|
|
|
// 6 cycles writing the PC
|
|
|
|
(cpu->WriteMem)( --SP, PCH, cpu );
|
|
|
|
(cpu->WriteMem)( --SP, PCL, cpu );
|
|
|
|
PC = 0x0066; // Fixed location
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_NMI, cpu );
|
|
|
|
ticks = 11;
|
|
|
|
goto interrupt_exit;
|
|
|
|
}
|
|
|
|
cpu->interrupt = false;
|
|
|
|
// This depends on the mode
|
|
|
|
switch( cpu->interrupt_mode )
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
// interrupting device supplies instruction.
|
|
|
|
IF_ID( &inst, 0, (byte (*)(word, void*))cpu->ReadInterruptData, cpu );
|
|
|
|
inst.additional_tstates += 2; // 2 wait states add to M1 cycle
|
|
|
|
(cpu->ControlFlow)( oldPC, 0xffff, CF_INTERRUPT, cpu );
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
// Insert a restart instruction + 2 cycles.
|
|
|
|
// Handle it here since we know exactly what
|
|
|
|
// it is supposed to do.
|
|
|
|
(cpu->WriteMem)( --SP, PCH, cpu );
|
|
|
|
(cpu->WriteMem)( --SP, PCL, cpu );
|
|
|
|
PC = 0x0038; // Fixed location
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_INTERRUPT, cpu );
|
|
|
|
ticks = 13;
|
|
|
|
goto interrupt_exit;
|
|
|
|
case 2:
|
|
|
|
// 7 cycles to read the 7 bits from the
|
|
|
|
// interrupting device, 6 to push the PC, and
|
|
|
|
// 6 to load the jump address.
|
|
|
|
(cpu->WriteMem)( --SP, PCH, cpu );
|
|
|
|
(cpu->WriteMem)( --SP, PCL, cpu );
|
|
|
|
word address = ((word)BYTE_REG[REG_I]) << 8;
|
|
|
|
address |= (cpu->ReadInterruptData)( 0, cpu ) & 0xfe;
|
|
|
|
PCL = (cpu->ReadMem)( address, true, cpu );
|
|
|
|
PCH = (cpu->ReadMem)( address+1, true, cpu );
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_INTERRUPT, cpu );
|
|
|
|
ticks = 19;
|
|
|
|
goto interrupt_exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// ei re-enables iff1 but interrupts cannot be handled
|
|
|
|
// for another instruction.
|
|
|
|
cpu->can_handle_interrupt = true;
|
|
|
|
// Fetch the next instruction from memory.
|
|
|
|
if( !cpu->halt )
|
|
|
|
{
|
|
|
|
PC += IF_ID( &inst, PC, ReadInstructionMemory, cpu );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
inst.additional_tstates = 0;
|
|
|
|
inst.offset = 0;
|
|
|
|
inst.immediate = 0;
|
|
|
|
inst.r_increment = 1;
|
|
|
|
inst.IT = &Unprefixed[0x76]; // NOP
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define OP1 (inst.IT->operand1)
|
|
|
|
#define OP2 (inst.IT->operand2)
|
|
|
|
#define OFFSET (inst.offset)
|
|
|
|
#define IMM (inst.immediate)
|
|
|
|
uint_fast32_t op1 = 0;
|
|
|
|
uint_fast32_t op2 = 0;
|
|
|
|
uint_fast32_t carry = 0;
|
|
|
|
uint_fast32_t result = 0;
|
|
|
|
int i = 0;
|
|
|
|
bool took_branch = true;
|
|
|
|
|
|
|
|
switch( inst.IT->type )
|
|
|
|
{
|
|
|
|
/* 8-Bit Load Group */
|
|
|
|
case LD_I_N:
|
|
|
|
case LD_MRR_N:
|
|
|
|
(cpu->WriteMem)( WORD_REG[OP1]+OFFSET, IMM, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_I_R:
|
|
|
|
case LD_MRR_R:
|
|
|
|
(cpu->WriteMem)( WORD_REG[OP1]+OFFSET, BYTE_REG[OP2], cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_MNN_R:
|
|
|
|
(cpu->WriteMem)( IMM, BYTE_REG[OP2], cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_R_I:
|
|
|
|
case LD_R_MRR:
|
|
|
|
result = (cpu->ReadMem)( WORD_REG[OP2]+OFFSET, false, cpu );
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_R_MNN:
|
|
|
|
BYTE_REG[OP1] = (cpu->ReadMem)( IMM, false, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_R_N:
|
|
|
|
BYTE_REG[OP1] = IMM;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_R_R:
|
|
|
|
result = BYTE_REG[OP2];
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
if( OP2 == REG_I || OP2 == REG_R )
|
|
|
|
{
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, cpu->iff2 );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* 16-Bit Load Group */
|
|
|
|
case LD_RR_MNN:
|
|
|
|
result = (cpu->ReadMem)( IMM, false, cpu );
|
|
|
|
result |= (cpu->ReadMem)( IMM+1, false, cpu ) << 8;
|
|
|
|
WORD_REG[OP1] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_RR_NN:
|
|
|
|
WORD_REG[OP1] = IMM;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_RR_RR:
|
|
|
|
WORD_REG[OP1] = WORD_REG[OP2];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LD_MNN_RR:
|
|
|
|
result = WORD_REG[OP2];
|
|
|
|
(cpu->WriteMem)( IMM, result & 0xff, cpu );
|
|
|
|
(cpu->WriteMem)( IMM+1, result >> 8, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POP_RR:
|
|
|
|
result = (cpu->ReadMem)( SP++, false, cpu );
|
|
|
|
result |= (cpu->ReadMem)( SP++, false, cpu ) << 8;
|
|
|
|
WORD_REG[OP1] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PUSH_RR:
|
|
|
|
result = WORD_REG[OP1];
|
|
|
|
(cpu->WriteMem)( --SP, result >> 8, cpu );
|
|
|
|
(cpu->WriteMem)( --SP, result & 0xff, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* Exchange, Block Transfer, Search Group */
|
|
|
|
case CPD:
|
|
|
|
carry = -1;
|
|
|
|
goto cpx;
|
|
|
|
case CPI:
|
|
|
|
carry = 1;
|
|
|
|
cpx:
|
|
|
|
op1 = A;
|
|
|
|
op2 = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
HL += carry;
|
|
|
|
--BC;
|
|
|
|
result = op1 - op2;
|
|
|
|
goto cp_flags;
|
|
|
|
case CPDR:
|
|
|
|
carry = -1;
|
|
|
|
goto cpxr;
|
|
|
|
case CPIR:
|
|
|
|
carry = 1;
|
|
|
|
cpxr:
|
|
|
|
op1 = A;
|
|
|
|
op2 = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
HL += carry;
|
|
|
|
--BC;
|
|
|
|
result = op1 - op2;
|
|
|
|
if( (result&0xff) && BC )
|
|
|
|
PC -= 2;
|
|
|
|
cp_flags:
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result&0xff) );
|
|
|
|
result -= FlagIsSet( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_H, (op1&0x03) < (op2&0x03) );
|
|
|
|
SetFlagValue( FLAG_P, BC != 0 );
|
|
|
|
SetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x02 ); // bit 1
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 ); // bit 3
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EX_MRR_RR:
|
|
|
|
result = WORD_REG[OP1];
|
|
|
|
op1 = (cpu->ReadMem)( result, false, cpu );
|
|
|
|
op1 |= (cpu->ReadMem)( result+1, false, cpu ) << 8;
|
|
|
|
op2 = WORD_REG[OP2];
|
|
|
|
(cpu->WriteMem)( result, op2&0xff, cpu );
|
|
|
|
(cpu->WriteMem)( result+1, op2>>8, cpu );
|
|
|
|
WORD_REG[OP2] = op1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EX_RR_RR:
|
|
|
|
result = WORD_REG[OP1];
|
|
|
|
WORD_REG[OP1] = WORD_REG[OP2];
|
|
|
|
WORD_REG[OP2] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXX:
|
|
|
|
op1 = BC;
|
|
|
|
op2 = DE;
|
|
|
|
result = HL;
|
|
|
|
BC = WORD_REG[REG_BCP];
|
|
|
|
DE = WORD_REG[REG_DEP];
|
|
|
|
HL = WORD_REG[REG_HLP];
|
|
|
|
WORD_REG[REG_BCP] = op1;
|
|
|
|
WORD_REG[REG_DEP] = op2;
|
|
|
|
WORD_REG[REG_HLP] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LDD:
|
|
|
|
carry = -1;
|
|
|
|
goto ldx;
|
|
|
|
case LDI:
|
|
|
|
carry = 1;
|
|
|
|
ldx:
|
|
|
|
result = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
(cpu->WriteMem)( DE, result, cpu );
|
|
|
|
HL += carry;
|
|
|
|
DE += carry;
|
|
|
|
--BC;
|
|
|
|
goto ld_flags;
|
|
|
|
case LDDR:
|
|
|
|
carry = -1;
|
|
|
|
goto ldxr;
|
|
|
|
case LDIR:
|
|
|
|
carry = 1;
|
|
|
|
ldxr:
|
|
|
|
result = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
(cpu->WriteMem)( DE, result, cpu );
|
|
|
|
HL += carry;
|
|
|
|
DE += carry;
|
|
|
|
if( --BC )
|
|
|
|
PC -= 2;
|
|
|
|
ld_flags:
|
|
|
|
// Very strange here
|
|
|
|
op2 = result + A;
|
|
|
|
SetFlagValue( FLAG_Y, op2&0x02 ); // bit 1
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_X, op2&0x08 ); // bit 3
|
|
|
|
SetFlagValue( FLAG_P, BC != 0 );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* 8-Bit Arithmetic and Logical Group */
|
|
|
|
case ADC_R_I:
|
|
|
|
case ADC_R_MRR:
|
|
|
|
op2 = (cpu->ReadMem)( WORD_REG[OP2]+OFFSET, false, cpu );
|
|
|
|
goto adc_r;
|
|
|
|
case ADC_R_N:
|
|
|
|
op2 = IMM;
|
|
|
|
goto adc_r;
|
|
|
|
case ADC_R_R:
|
|
|
|
op2 = BYTE_REG[OP2];
|
|
|
|
goto adc_r;
|
|
|
|
|
|
|
|
case ADD_R_I:
|
|
|
|
case ADD_R_MRR:
|
|
|
|
op2 = (cpu->ReadMem)( WORD_REG[OP2]+OFFSET, false, cpu );
|
|
|
|
goto add_r;
|
|
|
|
case ADD_R_N:
|
|
|
|
op2 = IMM;
|
|
|
|
goto add_r;
|
|
|
|
case ADD_R_R:
|
|
|
|
op2 = BYTE_REG[OP2];
|
|
|
|
goto add_r;
|
|
|
|
adc_r:
|
|
|
|
carry = FlagIsSet( FLAG_C );
|
|
|
|
add_r:
|
|
|
|
op1 = BYTE_REG[OP1];
|
|
|
|
result = op1 + op2 + carry;
|
|
|
|
BYTE_REG[OP1] = result & 0xff;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result & 0xff) );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 ); // bit 5
|
|
|
|
SetFlagValue( FLAG_H, (op1&0x0f)+(op2&0x0f)+carry>0x0f );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 ); // bit 3
|
|
|
|
SetFlagValue( FLAG_P, (op2 == 0x7f && carry) ||
|
|
|
|
((op1&0x80)==(op1&0x80) &&
|
|
|
|
(op1&0x80)!=(result&0x80)) );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_C, result > 0xff );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SBC_R_I:
|
|
|
|
case SBC_R_MRR:
|
|
|
|
op2 = (cpu->ReadMem)( WORD_REG[OP2]+OFFSET, false, cpu );
|
|
|
|
goto sbc_r;
|
|
|
|
case SBC_R_N:
|
|
|
|
op2 = IMM;
|
|
|
|
goto sbc_r;
|
|
|
|
case SBC_R_R:
|
|
|
|
op2 = BYTE_REG[OP2];
|
|
|
|
goto sbc_r;
|
|
|
|
|
|
|
|
case SUB_I:
|
|
|
|
case SUB_MRR:
|
|
|
|
op2 = (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
goto sub_r;
|
|
|
|
case SUB_N:
|
|
|
|
op2 = IMM;
|
|
|
|
goto sub_r;
|
|
|
|
case SUB_R:
|
|
|
|
op2 = BYTE_REG[OP1];
|
|
|
|
goto sub_r;
|
|
|
|
sbc_r:
|
|
|
|
carry = FlagIsSet( FLAG_C );
|
|
|
|
sub_r:
|
|
|
|
op1 = A;
|
|
|
|
result = op1 - op2 - carry;
|
|
|
|
A = result & 0xff;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result & 0xff) );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, (op1&0x0f) < (op2&0x0f)+carry );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, (carry && op1-op2 == 0x80) ||
|
|
|
|
((op1&0x80) != (op2&0x80) &&
|
|
|
|
(op1&0x80) != (result&0x80)) );
|
|
|
|
SetFlagValue( FLAG_C, op1 < op2 + carry );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DEC_I:
|
|
|
|
case DEC_MRR:
|
|
|
|
result = (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
carry = result;
|
|
|
|
--result;
|
|
|
|
(cpu->WriteMem)( WORD_REG[OP1]+OFFSET, result & 0xff, cpu );
|
|
|
|
goto dec_x;
|
|
|
|
case DEC_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result;
|
|
|
|
BYTE_REG[OP1] = --result;
|
|
|
|
dec_x:
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result&0xff) );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, !(carry&0x0f) );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, carry == 0x80 );
|
|
|
|
SetFlag( FLAG_N );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case INC_I:
|
|
|
|
case INC_MRR:
|
|
|
|
result = (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
carry = result;
|
|
|
|
++result;
|
|
|
|
(cpu->WriteMem)( WORD_REG[OP1]+OFFSET, result, cpu );
|
|
|
|
goto inc_x;
|
|
|
|
case INC_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result;
|
|
|
|
++result;
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
inc_x:
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result&0xff) );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, (carry&0x0f)+1 > 0x0f );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, carry & 0x7f );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CP_I:
|
|
|
|
case CP_MRR:
|
|
|
|
op2 = (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
goto cp;
|
|
|
|
case CP_N:
|
|
|
|
op2 = IMM;
|
|
|
|
goto cp;
|
|
|
|
case CP_R:
|
|
|
|
op2 = BYTE_REG[OP1];
|
|
|
|
cp:
|
|
|
|
op1 = A;
|
|
|
|
result = op1 - op2;
|
|
|
|
SetFlagValue( FLAG_S, result&0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result&0xff) );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, (op1&0x0f) < (op2&0x0f) );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, (op1&0x80) != (op2&0x80) &&
|
|
|
|
(op1&0x80) != (result&0x80) );
|
|
|
|
SetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_C, op1 < op2 );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AND_I:
|
|
|
|
case AND_MRR:
|
|
|
|
result = A &= (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
SetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
case AND_N:
|
|
|
|
result = A &= IMM;
|
|
|
|
SetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
case AND_R:
|
|
|
|
result = A &= BYTE_REG[OP1];
|
|
|
|
SetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
|
|
|
|
case OR_I:
|
|
|
|
case OR_MRR:
|
|
|
|
result = A | (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
goto logical_flags; /* Same flags as and */
|
|
|
|
case OR_N:
|
|
|
|
result = A | IMM;
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
case OR_R:
|
|
|
|
result = A | BYTE_REG[OP1];
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
|
|
|
|
case XOR_I:
|
|
|
|
case XOR_MRR:
|
|
|
|
result = A ^ (cpu->ReadMem)( WORD_REG[OP1]+OFFSET, false, cpu );
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
case XOR_N:
|
|
|
|
result = A ^ IMM;
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
goto logical_flags;
|
|
|
|
case XOR_R:
|
|
|
|
result = A ^ BYTE_REG[OP1];
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
logical_flags:
|
|
|
|
A = result;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x02 );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, ParityIsEven(result) );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
ResetFlag( FLAG_C );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* General-Purpose Arithmetic and CPU Control Group */
|
|
|
|
case CCF:
|
|
|
|
result = FlagIsSet( FLAG_C );
|
|
|
|
SetFlagValue( FLAG_Y, A & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, result );
|
|
|
|
SetFlagValue( FLAG_X, A & 0x08 );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_C, !result );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CPL:
|
|
|
|
result = ~A;
|
|
|
|
A = result;
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlag( FLAG_N );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DAA:
|
|
|
|
op1 = FlagIsSet( FLAG_N );
|
|
|
|
op2 = FlagIsSet( FLAG_H );
|
|
|
|
carry = FlagIsSet( FLAG_C );
|
|
|
|
result = A;
|
|
|
|
|
|
|
|
static const uint8_t daa_table[13][9] =
|
|
|
|
{
|
|
|
|
/*N C hi hi H lo lo add C */
|
|
|
|
{ 0, 0, 0x0, 0x9, 0, 0x0, 0x9, 0x00, 0 },
|
|
|
|
{ 0, 0, 0x0, 0x8, 0, 0xA, 0xF, 0x06, 0 },
|
|
|
|
{ 0, 0, 0x0, 0x9, 1, 0x0, 0x3, 0x06, 0 },
|
|
|
|
{ 0, 0, 0xA, 0xF, 0, 0x0, 0x9, 0x60, 1 },
|
|
|
|
{ 0, 0, 0x9, 0xF, 0, 0xA, 0xF, 0x66, 1 },
|
|
|
|
{ 0, 0, 0xA, 0xF, 1, 0x0, 0x3, 0x66, 1 },
|
|
|
|
{ 0, 1, 0x0, 0x2, 0, 0x0, 0x9, 0x60, 1 },
|
|
|
|
{ 0, 1, 0x0, 0x2, 0, 0xA, 0xF, 0x66, 1 },
|
|
|
|
{ 0, 1, 0x0, 0x3, 1, 0x0, 0x3, 0x66, 1 },
|
|
|
|
{ 1, 0, 0x0, 0x9, 0, 0x0, 0x9, 0x00, 0 },
|
|
|
|
{ 1, 0, 0x0, 0x8, 1, 0x6, 0xF, 0xFA, 0 },
|
|
|
|
{ 1, 1, 0x7, 0xF, 0, 0x0, 0x9, 0xA0, 1 },
|
|
|
|
{ 1, 1, 0x6, 0xF, 1, 0x6, 0xF, 0x9A, 1 },
|
|
|
|
};
|
|
|
|
for( i = 0; i < 13; ++i )
|
|
|
|
{
|
|
|
|
if( daa_table[i][0] == op1 &&
|
|
|
|
daa_table[i][1] == carry &&
|
|
|
|
daa_table[i][2] <= result >> 4 &&
|
|
|
|
daa_table[i][3] >= result >> 4 &&
|
|
|
|
daa_table[i][4] == op2 &&
|
|
|
|
daa_table[i][5] <= (result & 0x0f) &&
|
|
|
|
daa_table[i][6] >= (result & 0x0f) )
|
|
|
|
{
|
|
|
|
result = (result + daa_table[i][7]) & 0xff;
|
|
|
|
SetFlagValue( FLAG_C, daa_table[i][7] );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, ParityIsEven(result) );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DI:
|
|
|
|
cpu->iff1 = false;
|
|
|
|
cpu->iff2 = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EI:
|
|
|
|
cpu->iff1 = true;
|
|
|
|
cpu->iff2 = true;
|
|
|
|
cpu->can_handle_interrupt = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case HALT:
|
|
|
|
cpu->halt = true;
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_HALT, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IM:
|
|
|
|
cpu->interrupt_mode = OP1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NEG: // This does A <- 0 - A, flags set accordingly.
|
|
|
|
result = -A;
|
|
|
|
A = result & 0xff;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, result & 0x0f );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, (result&0xff) == 0x80 );
|
|
|
|
SetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_C, !result );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NOP:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SCF:
|
|
|
|
SetFlagValue( FLAG_Y, A & 0x20 );
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_X, A & 0x08 );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
SetFlag( FLAG_C );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* 16-Bit Arithmetic Group */
|
|
|
|
case ADD_RR_RR:
|
|
|
|
op1 = WORD_REG[OP1];
|
|
|
|
op2 = WORD_REG[OP2];
|
|
|
|
result = op1 + op2;
|
|
|
|
goto add_rr_flags;
|
|
|
|
case ADC_RR_RR:
|
|
|
|
op1 = WORD_REG[OP1];
|
|
|
|
op2 = WORD_REG[OP2];
|
|
|
|
carry = FlagIsSet( FLAG_C );
|
|
|
|
result = op1 + op2 + carry;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x8000 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result & 0xffff) );
|
|
|
|
SetFlagValue( FLAG_P, (op2 == 0x7fff && carry) ||
|
|
|
|
((op1&0x8000) == ((op2+carry)&0x8000) &&
|
|
|
|
(op1&0x8000) != (result&0x8000)) );
|
|
|
|
add_rr_flags:
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x2000 ); // bit 13
|
|
|
|
SetFlagValue( FLAG_H, (op1&0x0fff)+(op2&0x0fff)+carry>0x0fff );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x0800 ); // bit 11
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_C, result > 0xffff );
|
|
|
|
WORD_REG[OP1] = result & 0xffff;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DEC_RR:
|
|
|
|
--WORD_REG[OP1];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INC_RR:
|
|
|
|
++WORD_REG[OP1];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SBC_RR_RR:
|
|
|
|
op1 = WORD_REG[OP1];
|
|
|
|
op2 = WORD_REG[OP2];
|
|
|
|
carry = FlagIsSet( FLAG_C );
|
|
|
|
result = op1 - op2 - carry;
|
|
|
|
WORD_REG[OP1] = result & 0xffff;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x8000 );
|
|
|
|
SetFlagValue( FLAG_Z, !(result & 0xffff) );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x2000 );
|
|
|
|
SetFlagValue( FLAG_H, (op1&0x0fff) < (op2&0x0fff)+carry );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x0800 );
|
|
|
|
SetFlagValue( FLAG_P, (carry && op1-op2 == 0x8000) ||
|
|
|
|
((op1&0x8000) != (op2&0x8000) &&
|
|
|
|
(op1&0x8000) != (result&0x8000)) );
|
|
|
|
SetFlagValue( FLAG_C, op1 < op2 + carry );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* Rotate and Shift Group
|
|
|
|
* Almost all flags are set the same so jump to a common block
|
|
|
|
* of flag setting. */
|
|
|
|
case RLCA:
|
|
|
|
result = A;
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | carry;
|
|
|
|
A = result & 0xff;
|
|
|
|
goto rotate_accum_flags;
|
|
|
|
case RLA:
|
|
|
|
result = A;
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | FlagIsSet(FLAG_C);
|
|
|
|
A = result & 0xff;
|
|
|
|
goto rotate_accum_flags;
|
|
|
|
case RRCA:
|
|
|
|
result = A;
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result >> 1) | (carry << 7);
|
|
|
|
A = result;
|
|
|
|
goto rotate_accum_flags;
|
|
|
|
|
|
|
|
case RRA:
|
|
|
|
result = A;
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result >> 1) | (FlagIsSet(FLAG_C) << 7);
|
|
|
|
A = result;
|
|
|
|
goto rotate_accum_flags;
|
|
|
|
case RLC_I:
|
|
|
|
case RLC_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | carry;
|
|
|
|
(cpu->WriteMem)( op1, result & 0xff, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case RLC_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | carry;
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
goto shift_flags;
|
|
|
|
case RL_I:
|
|
|
|
case RL_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | FlagIsSet(FLAG_C);
|
|
|
|
(cpu->WriteMem)( op1, result & 0xff, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case RL_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | FlagIsSet(FLAG_C);
|
|
|
|
BYTE_REG[OP1] = result & 0xff;
|
|
|
|
goto shift_flags;
|
|
|
|
case RRC_I:
|
|
|
|
case RRC_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result >> 1) | (carry << 7);
|
|
|
|
(cpu->WriteMem)( op1, result, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case RRC_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result >> 1) | (carry << 7);
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
goto shift_flags;
|
|
|
|
case RR_I:
|
|
|
|
case RR_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result >> 1) | (FlagIsSet(FLAG_C) << 7);
|
|
|
|
(cpu->WriteMem)( op1, result, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case RR_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result >> 1) | (FlagIsSet(FLAG_C) << 7);
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
goto shift_flags;
|
|
|
|
case SLA_I:
|
|
|
|
case SLA_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result >> 7;
|
|
|
|
result <<= 1;
|
|
|
|
(cpu->WriteMem)( op1, result & 0xff, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case SLA_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result >> 7;
|
|
|
|
result <<= 1;
|
|
|
|
BYTE_REG[OP1] = result & 0xff;
|
|
|
|
goto shift_flags;
|
|
|
|
case SLL_I:
|
|
|
|
case SLL_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu);
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | 0x1;
|
|
|
|
(cpu->WriteMem)( op1, result & 0xff, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case SLL_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result >> 7;
|
|
|
|
result = (result << 1) | 0x1;
|
|
|
|
BYTE_REG[OP1] = result & 0xff;
|
|
|
|
goto shift_flags;
|
|
|
|
case SRA_I:
|
|
|
|
case SRA_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result & 0x80) | (result >> 1);
|
|
|
|
(cpu->WriteMem)( op1, result, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case SRA_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result & 0x1;
|
|
|
|
result = (result & 0x80) | (result >> 1);
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
goto shift_flags;
|
|
|
|
case SRL_I:
|
|
|
|
case SRL_MRR:
|
|
|
|
op1 = WORD_REG[OP1]+OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op1, false, cpu );
|
|
|
|
carry = result & 0x1;
|
|
|
|
result >>= 1;
|
|
|
|
(cpu->WriteMem)( op1, result, cpu );
|
|
|
|
goto shift_flags;
|
|
|
|
case SRL_R:
|
|
|
|
result = BYTE_REG[OP1];
|
|
|
|
carry = result & 0x1;
|
|
|
|
result >>= 1;
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
goto shift_flags;
|
|
|
|
case RLD:
|
|
|
|
op1 = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
op2 = A;
|
|
|
|
result = (op2 & 0xf0) | (op1 >> 4);
|
|
|
|
op1 = (op1 << 4) | (op2 & 0x0f);
|
|
|
|
(cpu->WriteMem)( HL, op1 & 0xff, cpu );
|
|
|
|
A = result;
|
|
|
|
carry = FlagIsSet( FLAG_C ); // makes code simpler
|
|
|
|
goto shift_flags;
|
|
|
|
case RRD:
|
|
|
|
op1 = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
op2 = A;
|
|
|
|
result = (op2 & 0xf0) | (op1 & 0x0f);
|
|
|
|
op1 = (op1 >> 4) | (op2 << 4);
|
|
|
|
(cpu->WriteMem)( HL, op1 & 0xff, cpu );
|
|
|
|
A = result;
|
|
|
|
carry = FlagIsSet( FLAG_C ); // simpler code
|
|
|
|
shift_flags:
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlagValue( FLAG_P, ParityIsEven(result) );
|
|
|
|
rotate_accum_flags:
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
SetFlagValue( FLAG_C, carry );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* Bit Set, Reset, and Test Group
|
|
|
|
* In this group, the first operand is the bit to
|
|
|
|
* set/reset/test. */
|
|
|
|
case BIT_I:
|
|
|
|
case BIT_MRR:
|
|
|
|
op2 = (cpu->ReadMem)( WORD_REG[OP2]+OFFSET, false, cpu );
|
|
|
|
result = op2 & (0x1<<OP1);
|
|
|
|
// XXX: This is wrong for BIT_MRR, but right for BIT_I
|
|
|
|
// Does anyone know what the right thing for BIT_MRR
|
|
|
|
// is?
|
|
|
|
SetFlagValue( FLAG_Y, (WORD_REG[OP2]+OFFSET)&0x20 );
|
|
|
|
SetFlagValue( FLAG_X, (WORD_REG[OP2]+OFFSET)&0x08 );
|
|
|
|
goto bit;
|
|
|
|
case BIT_R:
|
|
|
|
op2 = BYTE_REG[OP2];
|
|
|
|
result = op2 & (0x1<<OP1);
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
bit:
|
|
|
|
SetFlagValue( FLAG_S, OP1==7 && result );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_P, !result );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RES_I:
|
|
|
|
case RES_MRR:
|
|
|
|
op2 = WORD_REG[OP2] + OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op2, false, cpu ) & ~(1<<OP1);
|
|
|
|
(cpu->WriteMem)( op2, result, cpu );
|
|
|
|
if( inst.IT->extra != INV )
|
|
|
|
BYTE_REG[inst.IT->extra] = result;
|
|
|
|
break;
|
|
|
|
case RES_R:
|
|
|
|
result = BYTE_REG[OP2] & ~(1<<OP1);
|
|
|
|
BYTE_REG[OP2] = result;
|
|
|
|
if( inst.IT->extra != INV )
|
|
|
|
BYTE_REG[inst.IT->extra] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SET_I:
|
|
|
|
case SET_MRR:
|
|
|
|
op2 = WORD_REG[OP2] + OFFSET;
|
|
|
|
result = (cpu->ReadMem)( op2, false, cpu ) | (1<<OP1);
|
|
|
|
(cpu->WriteMem)( op2, result, cpu );
|
|
|
|
if( inst.IT->extra != INV )
|
|
|
|
BYTE_REG[inst.IT->extra] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SET_R:
|
|
|
|
result = BYTE_REG[OP2] | (1<<OP1);
|
|
|
|
BYTE_REG[OP2] = result;
|
|
|
|
if( inst.IT->extra != INV )
|
|
|
|
BYTE_REG[inst.IT->extra] = result;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
/* Jump Group */
|
|
|
|
case DJNZ:
|
|
|
|
if( --B )
|
|
|
|
PC += OFFSET;
|
|
|
|
else
|
|
|
|
took_branch = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JP_C_MNN:
|
|
|
|
if( !CondIsMet(OP1) )
|
|
|
|
{
|
|
|
|
took_branch = false;
|
|
|
|
break; // condition isn't met
|
|
|
|
}
|
|
|
|
case JP_MNN:
|
|
|
|
PC = IMM;
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_JUMP, cpu );
|
|
|
|
break;
|
|
|
|
case JP_MRR: // jp (hl); jp (ix); jp (iy)
|
|
|
|
PC = WORD_REG[OP1];
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_JUMP, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JR_C:
|
|
|
|
if( !CondIsMet(OP1) )
|
|
|
|
{
|
|
|
|
took_branch = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case JR:
|
|
|
|
PC += OFFSET;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Call and Return Group */
|
|
|
|
case CALL_C_MNN:
|
|
|
|
if( !CondIsMet(OP1) )
|
|
|
|
{
|
|
|
|
took_branch = false;
|
|
|
|
break; // condition isn't met
|
|
|
|
}
|
|
|
|
case CALL_MNN:
|
|
|
|
(cpu->WriteMem)( --SP, PCH, cpu );
|
|
|
|
(cpu->WriteMem)( --SP, PCL, cpu );
|
|
|
|
PC = IMM;
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_CALL, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RETI:
|
|
|
|
// Some docs say that iff2 is copied to iff1 like in
|
|
|
|
// reti. Some simulatores do that. Zilog docs are very
|
|
|
|
// clear that this doesn't happen, but they're often
|
|
|
|
// wrong.
|
|
|
|
(cpu->InterruptComplete)( cpu );
|
|
|
|
result = CF_RETURN_I;
|
|
|
|
goto ret;
|
|
|
|
case RETN:
|
|
|
|
cpu->iff1 = cpu->iff2;
|
|
|
|
result = CF_RETURN_N;
|
|
|
|
goto ret;
|
|
|
|
case RET_C:
|
|
|
|
if( !CondIsMet(OP1) )
|
|
|
|
{
|
|
|
|
took_branch = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case RET:
|
|
|
|
result = CF_RETURN;
|
|
|
|
ret:
|
|
|
|
PCL = (cpu->ReadMem)( SP++, false, cpu );
|
|
|
|
PCH = (cpu->ReadMem)( SP++, false, cpu );
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, result, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RST:
|
|
|
|
(cpu->WriteMem)( --SP, PCH, cpu );
|
|
|
|
(cpu->WriteMem)( --SP, PCL, cpu );
|
|
|
|
PC = OP1;
|
|
|
|
(cpu->ControlFlow)( oldPC, PC, CF_RESTART, cpu );
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Input and Output Group */
|
|
|
|
case IND:
|
|
|
|
carry = -1;
|
|
|
|
goto inx;
|
|
|
|
case INI:
|
|
|
|
carry = 1;
|
|
|
|
inx:
|
|
|
|
result = (cpu->ReadIO)( BC, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
(cpu->WriteMem)( HL, result, cpu );
|
|
|
|
op1 = --B;
|
|
|
|
HL += carry;
|
|
|
|
in_flags:
|
|
|
|
SetFlagValue( FLAG_S, op1 & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !(op1&0xff) );
|
|
|
|
SetFlagValue( FLAG_Y, op1 & 0x20 );
|
|
|
|
SetFlagValue( FLAG_X, op1 & 0x08 );
|
|
|
|
SetFlagValue( FLAG_N, result & 0x80 );
|
|
|
|
op2 = result + ((C+carry) & 0xff);
|
|
|
|
SetFlagValue( FLAG_H, op2 > 0xff );
|
|
|
|
SetFlagValue( FLAG_C, op2 > 0xff );
|
|
|
|
SetFlagValue( FLAG_P, ParityIsEven((op2&0x07)^op1) );
|
|
|
|
break;
|
|
|
|
case INDR:
|
|
|
|
carry = -1;
|
|
|
|
goto inxr;
|
|
|
|
case INIR:
|
|
|
|
carry = 1;
|
|
|
|
inxr:
|
|
|
|
result = (cpu->ReadIO)( BC, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
(cpu->WriteMem)( HL, result, cpu );
|
|
|
|
op1 = --B;
|
|
|
|
HL += carry;
|
|
|
|
if( op1 == 0 )
|
|
|
|
took_branch = false;
|
|
|
|
else
|
|
|
|
PC -= 2;
|
|
|
|
goto in_flags;
|
|
|
|
|
|
|
|
case IN_R_MN: //in a,(n)
|
|
|
|
result = (cpu->ReadIO)( (A<<8)|IMM, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
A = result;
|
|
|
|
break;
|
|
|
|
case IN_R_R: // in r,(c)
|
|
|
|
result = (cpu->ReadIO)( BC, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
if( OP1 != REG_F )
|
|
|
|
BYTE_REG[OP1] = result;
|
|
|
|
SetFlagValue( FLAG_S, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !result );
|
|
|
|
SetFlagValue( FLAG_Y, result & 0x20 );
|
|
|
|
ResetFlag( FLAG_H );
|
|
|
|
SetFlagValue( FLAG_X, result & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, ParityIsEven(result) );
|
|
|
|
ResetFlag( FLAG_N );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OTDR:
|
|
|
|
carry = -1;
|
|
|
|
goto otxr;
|
|
|
|
case OTIR:
|
|
|
|
carry = 1;
|
|
|
|
otxr:
|
|
|
|
result = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
op1 = --B;
|
|
|
|
(cpu->WriteIO)( BC, result, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
++B; // Fix up
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
HL += carry;
|
|
|
|
if( op1 )
|
|
|
|
PC -= 2;
|
|
|
|
else
|
|
|
|
took_branch = false;
|
|
|
|
out_flags:
|
|
|
|
// More flag strangeness
|
|
|
|
op2 = result + L;
|
|
|
|
SetFlagValue( FLAG_S, op1 & 0x80 );
|
|
|
|
SetFlagValue( FLAG_Z, !op1 );
|
|
|
|
SetFlagValue( FLAG_Y, op1 & 0x20 );
|
|
|
|
SetFlagValue( FLAG_H, op2 > 0xff );
|
|
|
|
SetFlagValue( FLAG_X, op1 & 0x08 );
|
|
|
|
SetFlagValue( FLAG_P, ParityIsEven((op2&0x07) ^ op1) );
|
|
|
|
SetFlagValue( FLAG_N, result & 0x80 );
|
|
|
|
SetFlagValue( FLAG_C, op2 > 0xff );
|
|
|
|
break;
|
|
|
|
case OUTD:
|
|
|
|
carry = -1;
|
|
|
|
goto outx;
|
|
|
|
case OUTI:
|
|
|
|
carry = 1;
|
|
|
|
outx:
|
|
|
|
result = (cpu->ReadMem)( HL, false, cpu );
|
|
|
|
op1 = --B;
|
|
|
|
(cpu->WriteIO)( BC, result, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
++B; // fix up
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
HL += carry;
|
|
|
|
goto out_flags;
|
|
|
|
|
|
|
|
case OUT_MN_R: // out (n),a
|
|
|
|
(cpu->WriteIO)( (A<<8)|IMM, A, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OUT_R: // out (c),0
|
|
|
|
// I don't know what is on the top half of the address
|
|
|
|
// bus during this, I'm guessing B.
|
|
|
|
(cpu->WriteIO)( BC, 0, cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OUT_R_R: // out (c),r
|
|
|
|
(cpu->WriteIO)( BC, BYTE_REG[OP2], cpu );
|
|
|
|
if( cpu->restart_io )
|
|
|
|
{
|
|
|
|
PC = oldPC;
|
|
|
|
goto early_exit;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#undef OP1
|
|
|
|
#undef OP2
|
|
|
|
#undef OFFSET
|
|
|
|
#undef IMM
|
|
|
|
|
|
|
|
ticks = took_branch? inst.IT->tstates:inst.IT->extra;
|
|
|
|
ticks += inst.additional_tstates;
|
|
|
|
|
|
|
|
interrupt_exit:
|
|
|
|
r = BYTE_REG[REG_R];
|
|
|
|
r = ((r + inst.r_increment) & 0x7f) | (r & 0x80);
|
|
|
|
BYTE_REG[REG_R] = r;
|
|
|
|
|
|
|
|
early_exit:
|
|
|
|
if( outPC )
|
|
|
|
*outPC = PC;
|
|
|
|
return ticks;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Z80_Disassemble( word address, char *buffer, Z80 cpu )
|
|
|
|
{
|
|
|
|
Instruction inst;
|
|
|
|
int length = IF_ID( &inst, address, ReadInstructionMemory, cpu );
|
|
|
|
if( buffer != NULL )
|
|
|
|
DisassembleInstruction( &inst, buffer );
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Z80_HasHalted( Z80 cpu )
|
|
|
|
{
|
|
|
|
return cpu->halt;
|
|
|
|
}
|
|
|
|
|
|
|
|
word Z80_GetReg( int reg, Z80 cpu )
|
|
|
|
{
|
|
|
|
assert( reg >= 0 && reg < NUM_REG );
|
|
|
|
return WORD_REG[reg];
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z80_SetReg( int reg, word value, Z80 cpu )
|
|
|
|
{
|
|
|
|
assert( reg >= 0 && reg < NUM_REG );
|
|
|
|
WORD_REG[reg] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z80_RaiseNMI( Z80 cpu )
|
|
|
|
{
|
|
|
|
cpu->nmi = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z80_RaiseInterrupt( Z80 cpu )
|
|
|
|
{
|
|
|
|
cpu->interrupt = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z80_RestartIO( Z80 cpu )
|
|
|
|
{
|
|
|
|
cpu->restart_io = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z80_ClearHalt( Z80 cpu )
|
|
|
|
{
|
|
|
|
cpu->halt = false;
|
|
|
|
}
|