2239 lines
42 KiB
C++
2239 lines
42 KiB
C++
/* z80emu.c
|
|
* Z80 processor emulator.
|
|
*
|
|
* Copyright (c) 2012-2017 Lin Ke-Fong
|
|
*
|
|
* This code is free, do whatever you want with it.
|
|
*/
|
|
|
|
#include "z80emu.h"
|
|
|
|
#include "instructions.h"
|
|
#include "macros.h"
|
|
#include "tables.h"
|
|
|
|
namespace z80emu {
|
|
namespace {
|
|
|
|
/* The main registers are stored inside Z80_STATE as an union of arrays named
|
|
* registers. They are referenced using indexes. Words are stored in the
|
|
* endianness of the host processor. The alternate set of word registers AF',
|
|
* BC', DE', and HL' is stored in the alternates member of Z80_STATE, as an
|
|
* array using the same ordering.
|
|
*/
|
|
|
|
#ifdef Z80_BIG_ENDIAN
|
|
|
|
#define Z80_B 0
|
|
#define Z80_C 1
|
|
#define Z80_D 2
|
|
#define Z80_E 3
|
|
#define Z80_H 4
|
|
#define Z80_L 5
|
|
#define Z80_A 6
|
|
#define Z80_F 7
|
|
|
|
#define Z80_IXH 8
|
|
#define Z80_IXL 9
|
|
#define Z80_IYH 10
|
|
#define Z80_IYL 11
|
|
|
|
#else
|
|
|
|
#define Z80_B 1
|
|
#define Z80_C 0
|
|
#define Z80_D 3
|
|
#define Z80_E 2
|
|
#define Z80_H 5
|
|
#define Z80_L 4
|
|
#define Z80_A 7
|
|
#define Z80_F 6
|
|
|
|
#define Z80_IXH 9
|
|
#define Z80_IXL 8
|
|
#define Z80_IYH 11
|
|
#define Z80_IYL 10
|
|
|
|
#endif
|
|
|
|
#define Z80_BC 0
|
|
#define Z80_DE 1
|
|
#define Z80_HL 2
|
|
#define Z80_AF 3
|
|
|
|
#define Z80_IX 4
|
|
#define Z80_IY 5
|
|
#define Z80_SP 6
|
|
|
|
/* Z80's flags. */
|
|
|
|
#define Z80_S_FLAG_SHIFT 7
|
|
#define Z80_Z_FLAG_SHIFT 6
|
|
#define Z80_Y_FLAG_SHIFT 5
|
|
#define Z80_H_FLAG_SHIFT 4
|
|
#define Z80_X_FLAG_SHIFT 3
|
|
#define Z80_PV_FLAG_SHIFT 2
|
|
#define Z80_N_FLAG_SHIFT 1
|
|
#define Z80_C_FLAG_SHIFT 0
|
|
|
|
#define Z80_S_FLAG (1 << Z80_S_FLAG_SHIFT)
|
|
#define Z80_Z_FLAG (1 << Z80_Z_FLAG_SHIFT)
|
|
#define Z80_Y_FLAG (1 << Z80_Y_FLAG_SHIFT)
|
|
#define Z80_H_FLAG (1 << Z80_H_FLAG_SHIFT)
|
|
#define Z80_X_FLAG (1 << Z80_X_FLAG_SHIFT)
|
|
#define Z80_PV_FLAG (1 << Z80_PV_FLAG_SHIFT)
|
|
#define Z80_N_FLAG (1 << Z80_N_FLAG_SHIFT)
|
|
#define Z80_C_FLAG (1 << Z80_C_FLAG_SHIFT)
|
|
|
|
#define Z80_P_FLAG_SHIFT Z80_PV_FLAG_SHIFT
|
|
#define Z80_V_FLAG_SHIFT Z80_PV_FLAG_SHIFT
|
|
#define Z80_P_FLAG Z80_PV_FLAG
|
|
#define Z80_V_FLAG Z80_PV_FLAG
|
|
|
|
/* Z80's three interrupt modes. */
|
|
|
|
enum {
|
|
|
|
Z80_INTERRUPT_MODE_0,
|
|
Z80_INTERRUPT_MODE_1,
|
|
Z80_INTERRUPT_MODE_2
|
|
|
|
};
|
|
|
|
#define Z80_READ_BYTE(address, x) x = board_.ReadByte(address)
|
|
#define Z80_FETCH_BYTE(address, x) x = board_.FetchByte(address)
|
|
#define Z80_READ_WORD(address, x) x = board_.ReadWord(address)
|
|
#define Z80_FETCH_WORD(address, x) x = board_.FetchWord(address)
|
|
#define Z80_WRITE_BYTE(address, x) board_.WriteByte(address, x)
|
|
#define Z80_WRITE_WORD(address, x) board_.WriteWord(address, x)
|
|
#define Z80_READ_WORD_INTERRUPT(address, x) \
|
|
x = board_.ReadWordInterrupt(address)
|
|
#define Z80_WRITE_WORD_INTERRUPT(address, x) \
|
|
board_.WriteWordInterrupt(address, x)
|
|
#define Z80_INPUT_BYTE(port, x) x = board_.InputByte(*this, port)
|
|
#define Z80_OUTPUT_BYTE(port, x) board_.OutputByte(port, x)
|
|
|
|
#define Z80_CATCH_HALT
|
|
#define Z80_CATCH_EI
|
|
|
|
/* Indirect (HL) or prefixed indexed (IX + d) and (IY + d) memory operands are
|
|
* encoded using the 3 bits "110" (0x06).
|
|
*/
|
|
|
|
#define INDIRECT_HL 0x06
|
|
|
|
/* Condition codes are encoded using 2 or 3 bits. The xor table is needed for
|
|
* negated conditions, it is used along with the and table.
|
|
*/
|
|
|
|
constexpr uint8_t XOR_CONDITION_TABLE[8] = {
|
|
|
|
Z80_Z_FLAG, 0, Z80_C_FLAG, 0, Z80_P_FLAG, 0, Z80_S_FLAG, 0,
|
|
|
|
};
|
|
|
|
constexpr uint8_t AND_CONDITION_TABLE[8] = {
|
|
|
|
Z80_Z_FLAG, Z80_Z_FLAG, Z80_C_FLAG, Z80_C_FLAG,
|
|
Z80_P_FLAG, Z80_P_FLAG, Z80_S_FLAG, Z80_S_FLAG,
|
|
|
|
};
|
|
|
|
/* RST instruction restart addresses, encoded by Y() bits of the opcode. */
|
|
|
|
constexpr uint8_t RST_TABLE[8] = {
|
|
|
|
0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38,
|
|
|
|
};
|
|
|
|
/* There is an overflow if the xor of the carry out and the carry of the most
|
|
* significant bit is not zero.
|
|
*/
|
|
|
|
constexpr uint8_t OVERFLOW_TABLE[4] = {
|
|
|
|
0,
|
|
Z80_V_FLAG,
|
|
Z80_V_FLAG,
|
|
0,
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
template <typename Board>
|
|
void Z80Emu<Board>::Reset(uint16_t pc) {
|
|
int i;
|
|
|
|
status_ = Status::UNDEFINED;
|
|
AF = 0xffff;
|
|
SP = 0xffff;
|
|
i_ = iff1_ = iff2_ = 0;
|
|
im_ = Z80_INTERRUPT_MODE_0;
|
|
pc_ = pc;
|
|
|
|
/* Build register decoding tables for both 3-bit encoded 8-bit
|
|
* registers and 2-bit encoded 16-bit registers. When an opcode is
|
|
* prefixed by 0xdd, HL is replaced by IX. When 0xfd prefixed, HL is
|
|
* replaced by IY.
|
|
*/
|
|
|
|
/* 8-bit "R" registers. */
|
|
|
|
register_table_[0] = ®isters_.byte[Z80_B];
|
|
register_table_[1] = ®isters_.byte[Z80_C];
|
|
register_table_[2] = ®isters_.byte[Z80_D];
|
|
register_table_[3] = ®isters_.byte[Z80_E];
|
|
register_table_[4] = ®isters_.byte[Z80_H];
|
|
register_table_[5] = ®isters_.byte[Z80_L];
|
|
|
|
/* Encoding 0x06 is used for indexed memory operands and direct HL or
|
|
* IX/IY register access.
|
|
*/
|
|
|
|
register_table_[6] = ®isters_.word[Z80_HL];
|
|
register_table_[7] = ®isters_.byte[Z80_A];
|
|
|
|
/* "Regular" 16-bit "RR" registers. */
|
|
|
|
register_table_[8] = ®isters_.word[Z80_BC];
|
|
register_table_[9] = ®isters_.word[Z80_DE];
|
|
register_table_[10] = ®isters_.word[Z80_HL];
|
|
register_table_[11] = ®isters_.word[Z80_SP];
|
|
|
|
/* 16-bit "SS" registers for PUSH and POP instructions (note that SP is
|
|
* replaced by AF).
|
|
*/
|
|
|
|
register_table_[12] = ®isters_.word[Z80_BC];
|
|
register_table_[13] = ®isters_.word[Z80_DE];
|
|
register_table_[14] = ®isters_.word[Z80_HL];
|
|
register_table_[15] = ®isters_.word[Z80_AF];
|
|
|
|
/* 0xdd and 0xfd prefixed register decoding tables. */
|
|
|
|
for (i = 0; i < 16; i++)
|
|
|
|
dd_register_table_[i] = fd_register_table_[i] = register_table_[i];
|
|
|
|
dd_register_table_[4] = ®isters_.byte[Z80_IXH];
|
|
dd_register_table_[5] = ®isters_.byte[Z80_IXL];
|
|
dd_register_table_[6] = ®isters_.word[Z80_IX];
|
|
dd_register_table_[10] = ®isters_.word[Z80_IX];
|
|
dd_register_table_[14] = ®isters_.word[Z80_IX];
|
|
|
|
fd_register_table_[4] = ®isters_.byte[Z80_IYH];
|
|
fd_register_table_[5] = ®isters_.byte[Z80_IYL];
|
|
fd_register_table_[6] = ®isters_.word[Z80_IY];
|
|
fd_register_table_[10] = ®isters_.word[Z80_IY];
|
|
fd_register_table_[14] = ®isters_.word[Z80_IY];
|
|
}
|
|
|
|
template <typename Board>
|
|
int Z80Emu<Board>::Interrupt(int data_on_bus) {
|
|
status_ = Status::UNDEFINED;
|
|
if (iff1_) {
|
|
iff1_ = iff2_ = 0;
|
|
r_ = (r_ & 0x80) | ((r_ + 1) & 0x7f);
|
|
switch (im_) {
|
|
case Z80_INTERRUPT_MODE_0: {
|
|
/* Assuming the opcode in data_on_bus is an
|
|
* RST instruction, accepting the interrupt
|
|
* should take 2 + 11 = 13 cycles.
|
|
*/
|
|
|
|
return Emulate(data_on_bus, 2, 4);
|
|
}
|
|
|
|
case Z80_INTERRUPT_MODE_1: {
|
|
int elapsed_cycles;
|
|
|
|
elapsed_cycles = 0;
|
|
SP -= 2;
|
|
Z80_WRITE_WORD_INTERRUPT(SP, pc_);
|
|
pc_ = 0x0038;
|
|
return elapsed_cycles + 13;
|
|
}
|
|
|
|
case Z80_INTERRUPT_MODE_2:
|
|
default: {
|
|
int elapsed_cycles, vector;
|
|
|
|
elapsed_cycles = 0;
|
|
SP -= 2;
|
|
Z80_WRITE_WORD_INTERRUPT(SP, pc_);
|
|
vector = i_ << 8 | data_on_bus;
|
|
|
|
#ifdef Z80_MASK_IM2_VECTOR_ADDRESS
|
|
|
|
vector &= 0xfffe;
|
|
|
|
#endif
|
|
|
|
Z80_READ_WORD_INTERRUPT(vector, pc_);
|
|
return elapsed_cycles + 19;
|
|
}
|
|
}
|
|
|
|
} else
|
|
|
|
return 0;
|
|
}
|
|
|
|
template <typename Board>
|
|
int Z80Emu<Board>::NonMaskableInterrupt() {
|
|
int elapsed_cycles;
|
|
|
|
status_ = Status::UNDEFINED;
|
|
|
|
iff2_ = iff1_;
|
|
iff1_ = 0;
|
|
r_ = (r_ & 0x80) | ((r_ + 1) & 0x7f);
|
|
|
|
elapsed_cycles = 0;
|
|
SP -= 2;
|
|
Z80_WRITE_WORD_INTERRUPT(SP, pc_);
|
|
pc_ = 0x0066;
|
|
|
|
return elapsed_cycles + 11;
|
|
}
|
|
|
|
template <typename Board>
|
|
int Z80Emu<Board>::Emulate(int number_cycles) {
|
|
int elapsed_cycles, pc, opcode;
|
|
|
|
status_ = Status::UNDEFINED;
|
|
elapsed_cycles = 0;
|
|
pc = pc_;
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc_ = pc + 1;
|
|
|
|
return Emulate(opcode, elapsed_cycles, number_cycles);
|
|
}
|
|
|
|
/* Actual emulation function. opcode is the first opcode to emulate, this is
|
|
* needed by Z80Interrupt() for interrupt mode 0.
|
|
*/
|
|
|
|
template <typename Board>
|
|
int Z80Emu<Board>::Emulate(int opcode, int elapsed_cycles, int number_cycles) {
|
|
int pc, r;
|
|
|
|
pc = pc_;
|
|
r = r_ & 0x7f;
|
|
goto start_emulation;
|
|
|
|
for (;;) {
|
|
void **registers;
|
|
Instruction instruction;
|
|
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
|
|
start_emulation:
|
|
|
|
registers = register_table_;
|
|
|
|
emulate_next_opcode:
|
|
|
|
instruction = (Instruction)INSTRUCTION_TABLE[opcode];
|
|
// ++counts_[instruction];
|
|
|
|
emulate_next_instruction:
|
|
|
|
elapsed_cycles += 4;
|
|
r++;
|
|
switch (instruction) {
|
|
/* 8-bit load group. */
|
|
|
|
case Instruction::LD_R_R: {
|
|
R(Y(opcode)) = R(Z(opcode));
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_R_N: {
|
|
READ_N(R(Y(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_R_INDIRECT_HL: {
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, R(Y(opcode)));
|
|
|
|
} else {
|
|
int d;
|
|
|
|
READ_D(d);
|
|
d += HL_IX_IY;
|
|
READ_BYTE(d, S(Y(opcode)));
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_HL_R: {
|
|
if (registers == register_table_) {
|
|
WRITE_BYTE(HL, R(Z(opcode)));
|
|
|
|
} else {
|
|
int d;
|
|
|
|
READ_D(d);
|
|
d += HL_IX_IY;
|
|
WRITE_BYTE(d, S(Z(opcode)));
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_HL_N: {
|
|
int n;
|
|
|
|
if (registers == register_table_) {
|
|
READ_N(n);
|
|
WRITE_BYTE(HL, n);
|
|
|
|
} else {
|
|
int d;
|
|
|
|
READ_D(d);
|
|
d += HL_IX_IY;
|
|
READ_N(n);
|
|
WRITE_BYTE(d, n);
|
|
|
|
elapsed_cycles += 2;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_A_INDIRECT_BC: {
|
|
READ_BYTE(BC, A);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_A_INDIRECT_DE: {
|
|
READ_BYTE(DE, A);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_A_INDIRECT_NN: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
READ_BYTE(nn, A);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_BC_A: {
|
|
WRITE_BYTE(BC, A);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_DE_A: {
|
|
WRITE_BYTE(DE, A);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_NN_A: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
WRITE_BYTE(nn, A);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_A_I_LD_A_R: {
|
|
int a, f;
|
|
|
|
a = opcode == OPCODE_LD_A_I ? i_ : (r_ & 0x80) | (r & 0x7f);
|
|
f = SZYX_FLAGS_TABLE[a];
|
|
|
|
/* Note: On a real processor, if an interrupt
|
|
* occurs during the execution of either
|
|
* "LD A, I" or "LD A, R", the parity flag is
|
|
* reset. That can never happen here.
|
|
*/
|
|
|
|
f |= iff2_ << Z80_P_FLAG_SHIFT;
|
|
f |= F & Z80_C_FLAG;
|
|
|
|
AF = (a << 8) | f;
|
|
|
|
elapsed_cycles++;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_I_A_LD_R_A: {
|
|
if (opcode == OPCODE_LD_I_A)
|
|
|
|
i_ = A;
|
|
|
|
else {
|
|
r_ = A;
|
|
r = A & 0x7f;
|
|
}
|
|
|
|
elapsed_cycles++;
|
|
|
|
break;
|
|
}
|
|
|
|
/* 16-bit load group. */
|
|
|
|
case Instruction::LD_RR_NN: {
|
|
READ_NN(RR(P(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_HL_INDIRECT_NN: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
READ_WORD(nn, HL_IX_IY);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_RR_INDIRECT_NN: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
READ_WORD(nn, RR(P(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_NN_HL: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
WRITE_WORD(nn, HL_IX_IY);
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_INDIRECT_NN_RR: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
WRITE_WORD(nn, RR(P(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::LD_SP_HL: {
|
|
SP = HL_IX_IY;
|
|
elapsed_cycles += 2;
|
|
break;
|
|
}
|
|
|
|
case Instruction::PUSH_SS: {
|
|
PUSH(SS(P(opcode)));
|
|
elapsed_cycles++;
|
|
break;
|
|
}
|
|
|
|
case Instruction::POP_SS: {
|
|
POP(SS(P(opcode)));
|
|
break;
|
|
}
|
|
|
|
/* Exchange, block transfer and search group. */
|
|
|
|
case Instruction::EX_DE_HL: {
|
|
EXCHANGE(DE, HL);
|
|
break;
|
|
}
|
|
|
|
case Instruction::EX_AF_AF_PRIME: {
|
|
EXCHANGE(AF, alternates_[Z80_AF]);
|
|
break;
|
|
}
|
|
|
|
case Instruction::EXX: {
|
|
EXCHANGE(BC, alternates_[Z80_BC]);
|
|
EXCHANGE(DE, alternates_[Z80_DE]);
|
|
EXCHANGE(HL, alternates_[Z80_HL]);
|
|
break;
|
|
}
|
|
|
|
case Instruction::EX_INDIRECT_SP_HL: {
|
|
int t;
|
|
|
|
READ_WORD(SP, t);
|
|
WRITE_WORD(SP, HL_IX_IY);
|
|
HL_IX_IY = t;
|
|
|
|
elapsed_cycles += 3;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::LDI_LDD: {
|
|
int n, f, d;
|
|
|
|
READ_BYTE(HL, n);
|
|
WRITE_BYTE(DE, n);
|
|
|
|
f = F & SZC_FLAGS;
|
|
f |= --BC ? Z80_P_FLAG : 0;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
n += A;
|
|
f |= n & Z80_X_FLAG;
|
|
f |= (n << (Z80_Y_FLAG_SHIFT - 1)) & Z80_Y_FLAG;
|
|
}
|
|
|
|
F = f;
|
|
|
|
d = opcode == OPCODE_LDI ? +1 : -1;
|
|
DE += d;
|
|
HL += d;
|
|
|
|
elapsed_cycles += 2;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::LDIR_LDDR: {
|
|
int d, f, bc, de, hl, n;
|
|
|
|
#ifdef Z80_HANDLE_SELF_MODIFYING_CODE
|
|
|
|
int p, q;
|
|
|
|
p = (pc - 2) & 0xffff;
|
|
q = (pc - 1) & 0xffff;
|
|
|
|
#endif
|
|
|
|
d = opcode == OPCODE_LDIR ? +1 : -1;
|
|
|
|
f = F & SZC_FLAGS;
|
|
bc = BC;
|
|
de = DE;
|
|
hl = HL;
|
|
|
|
r -= 2;
|
|
elapsed_cycles -= 8;
|
|
for (;;) {
|
|
r += 2;
|
|
|
|
Z80_READ_BYTE(hl, n);
|
|
Z80_WRITE_BYTE(de, n);
|
|
|
|
hl += d;
|
|
de += d;
|
|
|
|
if (--bc)
|
|
|
|
elapsed_cycles += 21;
|
|
|
|
else {
|
|
elapsed_cycles += 16;
|
|
break;
|
|
}
|
|
|
|
#ifdef Z80_HANDLE_SELF_MODIFYING_CODE
|
|
|
|
if (((de - d) & 0xffff) == p || ((de - d) & 0xffff) == q) {
|
|
f |= Z80_P_FLAG;
|
|
pc -= 2;
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
|
|
if (elapsed_cycles < number_cycles)
|
|
|
|
continue;
|
|
|
|
else {
|
|
f |= Z80_P_FLAG;
|
|
pc -= 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
HL = hl;
|
|
DE = de;
|
|
BC = bc;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
n += A;
|
|
f |= n & Z80_X_FLAG;
|
|
f |= (n << (Z80_Y_FLAG_SHIFT - 1)) & Z80_Y_FLAG;
|
|
}
|
|
|
|
F = f;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::CPI_CPD: {
|
|
int a, n, z, f;
|
|
|
|
a = A;
|
|
READ_BYTE(HL, n);
|
|
z = a - n;
|
|
|
|
HL += opcode == OPCODE_CPI ? +1 : -1;
|
|
|
|
f = (a ^ n ^ z) & Z80_H_FLAG;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
n = z - (f >> Z80_H_FLAG_SHIFT);
|
|
f |= (n << (Z80_Y_FLAG_SHIFT - 1)) & Z80_Y_FLAG;
|
|
f |= n & Z80_X_FLAG;
|
|
}
|
|
|
|
f |= SZYX_FLAGS_TABLE[z & 0xff] & SZ_FLAGS;
|
|
f |= --BC ? Z80_P_FLAG : 0;
|
|
F = f | Z80_N_FLAG | (F & Z80_C_FLAG);
|
|
|
|
elapsed_cycles += 5;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::CPIR_CPDR: {
|
|
int d, a, bc, hl, n, z, f;
|
|
|
|
d = opcode == OPCODE_CPIR ? +1 : -1;
|
|
|
|
a = A;
|
|
bc = BC;
|
|
hl = HL;
|
|
|
|
r -= 2;
|
|
elapsed_cycles -= 8;
|
|
for (;;) {
|
|
r += 2;
|
|
|
|
Z80_READ_BYTE(hl, n);
|
|
z = a - n;
|
|
|
|
hl += d;
|
|
if (--bc && z)
|
|
|
|
elapsed_cycles += 21;
|
|
|
|
else {
|
|
elapsed_cycles += 16;
|
|
break;
|
|
}
|
|
|
|
if (elapsed_cycles < number_cycles)
|
|
|
|
continue;
|
|
|
|
else {
|
|
pc -= 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
HL = hl;
|
|
BC = bc;
|
|
|
|
f = (a ^ n ^ z) & Z80_H_FLAG;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
n = z - (f >> Z80_H_FLAG_SHIFT);
|
|
f |= (n << (Z80_Y_FLAG_SHIFT - 1)) & Z80_Y_FLAG;
|
|
f |= n & Z80_X_FLAG;
|
|
}
|
|
|
|
f |= SZYX_FLAGS_TABLE[z & 0xff] & SZ_FLAGS;
|
|
f |= bc ? Z80_P_FLAG : 0;
|
|
F = f | Z80_N_FLAG | (F & Z80_C_FLAG);
|
|
|
|
break;
|
|
}
|
|
|
|
/* 8-bit arithmetic and logical group. */
|
|
|
|
case Instruction::ADD_R: {
|
|
ADD(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::ADD_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
ADD(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::ADD_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
ADD(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::ADC_R: {
|
|
ADC(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::ADC_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
ADC(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::ADC_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
ADC(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::SUB_R: {
|
|
SUB(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::SUB_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
SUB(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::SUB_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
SUB(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::SBC_R: {
|
|
SBC(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::SBC_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
SBC(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::SBC_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
SBC(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::AND_R: {
|
|
AND(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::AND_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
AND(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::AND_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
AND(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::OR_R: {
|
|
OR(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::OR_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
OR(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::OR_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
OR(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::XOR_R: {
|
|
XOR(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::XOR_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
XOR(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::XOR_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
XOR(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::CP_R: {
|
|
CP(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::CP_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
CP(n);
|
|
break;
|
|
}
|
|
|
|
case Instruction::CP_INDIRECT_HL: {
|
|
int x;
|
|
|
|
READ_INDIRECT_HL(x);
|
|
CP(x);
|
|
break;
|
|
}
|
|
|
|
case Instruction::INC_R: {
|
|
INC(R(Y(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::INC_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
INC(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
READ_D(d);
|
|
d += HL_IX_IY;
|
|
READ_BYTE(d, x);
|
|
INC(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
elapsed_cycles += 6;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::DEC_R: {
|
|
DEC(R(Y(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::DEC_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
DEC(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
READ_D(d);
|
|
d += HL_IX_IY;
|
|
READ_BYTE(d, x);
|
|
DEC(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
elapsed_cycles += 6;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* General-purpose arithmetic and CPU control group. */
|
|
|
|
case Instruction::DAA: {
|
|
int a, c, d;
|
|
|
|
/* The following algorithm is from
|
|
* comp.sys.sinclair's FAQ.
|
|
*/
|
|
|
|
a = A;
|
|
if (a > 0x99 || (F & Z80_C_FLAG)) {
|
|
c = Z80_C_FLAG;
|
|
d = 0x60;
|
|
|
|
} else
|
|
|
|
c = d = 0;
|
|
|
|
if ((a & 0x0f) > 0x09 || (F & Z80_H_FLAG)) d += 0x06;
|
|
|
|
A += F & Z80_N_FLAG ? -d : +d;
|
|
F = SZYXP_FLAGS_TABLE[A] | ((A ^ a) & Z80_H_FLAG) | (F & Z80_N_FLAG) |
|
|
c;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::CPL: {
|
|
A = ~A;
|
|
F = (F & (SZPV_FLAGS | Z80_C_FLAG)) | Z80_H_FLAG | Z80_N_FLAG;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (A & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::NEG: {
|
|
int a, f, z, c;
|
|
|
|
a = A;
|
|
z = -a;
|
|
|
|
c = a ^ z;
|
|
f = Z80_N_FLAG | (c & Z80_H_FLAG);
|
|
f |= SZYX_FLAGS_TABLE[z &= 0xff];
|
|
c &= 0x0180;
|
|
f |= OVERFLOW_TABLE[c >> 7];
|
|
f |= c >> (8 - Z80_C_FLAG_SHIFT);
|
|
|
|
A = z;
|
|
F = f;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::CCF: {
|
|
int c;
|
|
|
|
c = F & Z80_C_FLAG;
|
|
F = (F & SZPV_FLAGS) | (c << Z80_H_FLAG_SHIFT) | (c ^ Z80_C_FLAG);
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (A & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::SCF: {
|
|
F = (F & SZPV_FLAGS) | Z80_C_FLAG;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (A & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::NOP: {
|
|
break;
|
|
}
|
|
|
|
case Instruction::HALT: {
|
|
board_.Halt();
|
|
|
|
#ifdef Z80_CATCH_HALT
|
|
|
|
status_ = Status::HALT;
|
|
|
|
#else
|
|
/* If an HALT instruction is executed, the Z80
|
|
* keeps executing NOPs until an interrupt is
|
|
* generated. Basically nothing happens for the
|
|
* remaining number of cycles.
|
|
*/
|
|
|
|
if (elapsed_cycles < number_cycles) elapsed_cycles = number_cycles;
|
|
|
|
#endif
|
|
|
|
goto stop_emulation;
|
|
}
|
|
|
|
case Instruction::DI: {
|
|
iff1_ = iff2_ = 0;
|
|
|
|
#ifdef Z80_CATCH_DI
|
|
|
|
status_ = Z80_STATUS_FLAG_DI;
|
|
goto stop_emulation;
|
|
|
|
#else
|
|
|
|
/* No interrupt can be accepted right after
|
|
* a DI or EI instruction on an actual Z80
|
|
* processor. By adding 4 cycles to
|
|
* number_cycles, at least one more
|
|
* instruction will be executed. However, this
|
|
* will fail if the next instruction has
|
|
* multiple 0xdd or 0xfd prefixes and
|
|
* Z80_PREFIX_FAILSAFE is defined, but that
|
|
* is an unlikely pathological case.
|
|
*/
|
|
|
|
number_cycles += 4;
|
|
break;
|
|
|
|
#endif
|
|
}
|
|
|
|
case Instruction::EI: {
|
|
iff1_ = iff2_ = 1;
|
|
|
|
#ifdef Z80_CATCH_EI
|
|
|
|
status_ = Status::EI;
|
|
goto stop_emulation;
|
|
|
|
#else
|
|
|
|
/* See comment for DI. */
|
|
|
|
number_cycles += 4;
|
|
break;
|
|
|
|
#endif
|
|
}
|
|
|
|
case Instruction::IM_N: {
|
|
/* "IM 0/1" (0xed prefixed opcodes 0x4e and
|
|
* 0x6e) is treated like a "IM 0".
|
|
*/
|
|
|
|
if ((Y(opcode) & 0x03) <= 0x01)
|
|
|
|
im_ = Z80_INTERRUPT_MODE_0;
|
|
|
|
else if (!(Y(opcode) & 1))
|
|
|
|
im_ = Z80_INTERRUPT_MODE_1;
|
|
|
|
else
|
|
|
|
im_ = Z80_INTERRUPT_MODE_2;
|
|
|
|
break;
|
|
}
|
|
|
|
/* 16-bit arithmetic group. */
|
|
|
|
case Instruction::ADD_HL_RR: {
|
|
int x, y, z, f, c;
|
|
|
|
x = HL_IX_IY;
|
|
y = RR(P(opcode));
|
|
z = x + y;
|
|
|
|
c = x ^ y ^ z;
|
|
f = F & SZPV_FLAGS;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
f |= (z >> 8) & YX_FLAGS;
|
|
f |= (c >> 8) & Z80_H_FLAG;
|
|
}
|
|
|
|
f |= c >> (16 - Z80_C_FLAG_SHIFT);
|
|
|
|
HL_IX_IY = z;
|
|
F = f;
|
|
|
|
elapsed_cycles += 7;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::ADC_HL_RR: {
|
|
int x, y, z, f, c;
|
|
|
|
x = HL;
|
|
y = RR(P(opcode));
|
|
z = x + y + (F & Z80_C_FLAG);
|
|
|
|
c = x ^ y ^ z;
|
|
f = z & 0xffff ? (z >> 8) & SYX_FLAGS : Z80_Z_FLAG;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
f |= (c >> 8) & Z80_H_FLAG;
|
|
}
|
|
|
|
f |= OVERFLOW_TABLE[c >> 15];
|
|
f |= z >> (16 - Z80_C_FLAG_SHIFT);
|
|
|
|
HL = z;
|
|
F = f;
|
|
|
|
elapsed_cycles += 7;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::SBC_HL_RR: {
|
|
int x, y, z, f, c;
|
|
|
|
x = HL;
|
|
y = RR(P(opcode));
|
|
z = x - y - (F & Z80_C_FLAG);
|
|
|
|
c = x ^ y ^ z;
|
|
f = Z80_N_FLAG;
|
|
f |= z & 0xffff ? (z >> 8) & SYX_FLAGS : Z80_Z_FLAG;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
f |= (c >> 8) & Z80_H_FLAG;
|
|
}
|
|
|
|
c &= 0x018000;
|
|
f |= OVERFLOW_TABLE[c >> 15];
|
|
f |= c >> (16 - Z80_C_FLAG_SHIFT);
|
|
|
|
HL = z;
|
|
F = f;
|
|
|
|
elapsed_cycles += 7;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::INC_RR: {
|
|
int x;
|
|
|
|
x = RR(P(opcode));
|
|
x++;
|
|
RR(P(opcode)) = x;
|
|
|
|
elapsed_cycles += 2;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::DEC_RR: {
|
|
int x;
|
|
|
|
x = RR(P(opcode));
|
|
x--;
|
|
RR(P(opcode)) = x;
|
|
|
|
elapsed_cycles += 2;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Rotate and shift group. */
|
|
|
|
case Instruction::RLCA: {
|
|
A = (A << 1) | (A >> 7);
|
|
F = (F & SZPV_FLAGS) | (A & (YX_FLAGS | Z80_C_FLAG));
|
|
break;
|
|
}
|
|
|
|
case Instruction::RLA: {
|
|
int a, f;
|
|
|
|
a = A << 1;
|
|
f = (F & SZPV_FLAGS) | (A >> 7);
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
f |= (a & YX_FLAGS);
|
|
}
|
|
|
|
A = a | (F & Z80_C_FLAG);
|
|
F = f;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::RRCA: {
|
|
int c;
|
|
|
|
c = A & 0x01;
|
|
A = (A >> 1) | (A << 7);
|
|
F = (F & SZPV_FLAGS) | c;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (A & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::RRA: {
|
|
int c;
|
|
|
|
c = A & 0x01;
|
|
A = (A >> 1) | ((F & Z80_C_FLAG) << 7);
|
|
F = (F & SZPV_FLAGS) | c;
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (A & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::RLC_R: {
|
|
RLC(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::RLC_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
RLC(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
RLC(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::RL_R: {
|
|
RL(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::RL_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
RL(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
RL(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::RRC_R: {
|
|
RRC(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::RRC_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
RRC(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
RRC(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::RR_R: {
|
|
RR_INSTRUCTION(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::RR_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
RR_INSTRUCTION(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
RR_INSTRUCTION(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::SLA_R: {
|
|
SLA(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::SLA_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
SLA(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
SLA(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::SLL_R: {
|
|
SLL(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::SLL_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
SLL(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
SLL(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::SRA_R: {
|
|
SRA(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::SRA_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
SRA(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
SRA(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::SRL_R: {
|
|
SRL(R(Z(opcode)));
|
|
break;
|
|
}
|
|
|
|
case Instruction::SRL_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
SRL(x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
SRL(x);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::RLD_RRD: {
|
|
int x, y;
|
|
|
|
READ_BYTE(HL, x);
|
|
y = (A & 0xf0) << 8;
|
|
y |= opcode == OPCODE_RLD
|
|
? (x << 4) | (A & 0x0f)
|
|
: ((x & 0x0f) << 8) | ((A & 0x0f) << 4) | (x >> 4);
|
|
WRITE_BYTE(HL, y);
|
|
y >>= 8;
|
|
|
|
A = y;
|
|
F = SZYXP_FLAGS_TABLE[y] | (F & Z80_C_FLAG);
|
|
|
|
elapsed_cycles += 4;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Bit set, reset, and test group. */
|
|
|
|
case Instruction::BIT_B_R: {
|
|
int x;
|
|
|
|
x = R(Z(opcode)) & (1 << Y(opcode));
|
|
F = (x ? 0 : Z80_Z_FLAG | Z80_P_FLAG) | Z80_H_FLAG | (F & Z80_C_FLAG);
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (x & Z80_S_FLAG) | (R(Z(opcode)) & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::BIT_B_INDIRECT_HL: {
|
|
int d, x;
|
|
|
|
if (registers == register_table_) {
|
|
d = HL;
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
|
|
READ_BYTE(d, x);
|
|
x &= 1 << Y(opcode);
|
|
F = (x ? 0 : Z80_Z_FLAG | Z80_P_FLAG) | Z80_H_FLAG | (F & Z80_C_FLAG);
|
|
|
|
if constexpr (!Board::kDocumentedFlagsOnly) {
|
|
F |= (x & Z80_S_FLAG) | (d & YX_FLAGS);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::SET_B_R: {
|
|
R(Z(opcode)) |= 1 << Y(opcode);
|
|
break;
|
|
}
|
|
|
|
case Instruction::SET_B_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
x |= 1 << Y(opcode);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
x |= 1 << Y(opcode);
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::RES_B_R: {
|
|
R(Z(opcode)) &= ~(1 << Y(opcode));
|
|
break;
|
|
}
|
|
|
|
case Instruction::RES_B_INDIRECT_HL: {
|
|
int x;
|
|
|
|
if (registers == register_table_) {
|
|
READ_BYTE(HL, x);
|
|
x &= ~(1 << Y(opcode));
|
|
WRITE_BYTE(HL, x);
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
int d;
|
|
|
|
Z80_FETCH_BYTE(pc, d);
|
|
d = ((signed char)d) + HL_IX_IY;
|
|
|
|
READ_BYTE(d, x);
|
|
x &= ~(1 << Y(opcode));
|
|
WRITE_BYTE(d, x);
|
|
|
|
if (Z(opcode) != INDIRECT_HL) R(Z(opcode)) = x;
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 5;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Jump group. */
|
|
|
|
case Instruction::JP_NN: {
|
|
int nn;
|
|
|
|
Z80_FETCH_WORD(pc, nn);
|
|
pc = nn;
|
|
|
|
elapsed_cycles += 6;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::JP_CC_NN: {
|
|
int nn;
|
|
|
|
if (CC(Y(opcode))) {
|
|
Z80_FETCH_WORD(pc, nn);
|
|
pc = nn;
|
|
|
|
} else {
|
|
#ifdef Z80_FALSE_CONDITION_FETCH
|
|
|
|
Z80_FETCH_WORD(pc, nn);
|
|
|
|
#endif
|
|
|
|
pc += 2;
|
|
}
|
|
|
|
elapsed_cycles += 6;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::JR_E: {
|
|
int e;
|
|
|
|
Z80_FETCH_BYTE(pc, e);
|
|
pc += ((signed char)e) + 1;
|
|
|
|
elapsed_cycles += 8;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::JR_DD_E: {
|
|
int e;
|
|
|
|
if (DD(Q(opcode))) {
|
|
Z80_FETCH_BYTE(pc, e);
|
|
pc += ((signed char)e) + 1;
|
|
|
|
elapsed_cycles += 8;
|
|
|
|
} else {
|
|
#ifdef Z80_FALSE_CONDITION_FETCH
|
|
|
|
Z80_FETCH_BYTE(pc, e);
|
|
|
|
#endif
|
|
|
|
pc++;
|
|
|
|
elapsed_cycles += 3;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::JP_HL: {
|
|
pc = HL_IX_IY;
|
|
break;
|
|
}
|
|
|
|
case Instruction::DJNZ_E: {
|
|
int e;
|
|
|
|
if (--B) {
|
|
Z80_FETCH_BYTE(pc, e);
|
|
pc += ((signed char)e) + 1;
|
|
|
|
elapsed_cycles += 9;
|
|
|
|
} else {
|
|
#ifdef Z80_FALSE_CONDITION_FETCH
|
|
|
|
Z80_FETCH_BYTE(pc, e);
|
|
|
|
#endif
|
|
|
|
pc++;
|
|
|
|
elapsed_cycles += 4;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Call and return group. */
|
|
|
|
case Instruction::CALL_NN: {
|
|
int nn;
|
|
|
|
READ_NN(nn);
|
|
PUSH(pc);
|
|
pc = nn;
|
|
|
|
elapsed_cycles++;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::CALL_CC_NN: {
|
|
int nn;
|
|
|
|
if (CC(Y(opcode))) {
|
|
READ_NN(nn);
|
|
PUSH(pc);
|
|
pc = nn;
|
|
|
|
elapsed_cycles++;
|
|
|
|
} else {
|
|
#ifdef Z80_FALSE_CONDITION_FETCH
|
|
|
|
Z80_FETCH_WORD(pc, nn);
|
|
|
|
#endif
|
|
|
|
pc += 2;
|
|
|
|
elapsed_cycles += 6;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Instruction::RET: {
|
|
POP(pc);
|
|
if (elapsed_cycles >= number_cycles) goto stop_emulation;
|
|
break;
|
|
}
|
|
|
|
case Instruction::RET_CC: {
|
|
if (CC(Y(opcode))) {
|
|
POP(pc);
|
|
}
|
|
elapsed_cycles++;
|
|
if (elapsed_cycles >= number_cycles) goto stop_emulation;
|
|
break;
|
|
}
|
|
|
|
case Instruction::RETI_RETN: {
|
|
iff1_ = iff2_;
|
|
POP(pc);
|
|
|
|
#if defined(Z80_CATCH_RETI) && defined(Z80_CATCH_RETN)
|
|
|
|
status_ =
|
|
opcode == OPCODE_RETI ? Z80_STATUS_FLAG_RETI : Z80_STATUS_FLAG_RETN;
|
|
goto stop_emulation;
|
|
|
|
#elif defined(Z80_CATCH_RETI)
|
|
|
|
status_ = Z80_STATUS_FLAG_RETI;
|
|
goto stop_emulation;
|
|
|
|
#elif defined(Z80_CATCH_RETN)
|
|
|
|
status_ = Z80_STATUS_FLAG_RETN;
|
|
goto stop_emulation;
|
|
|
|
#else
|
|
|
|
break;
|
|
|
|
#endif
|
|
}
|
|
|
|
case Instruction::RST_P: {
|
|
PUSH(pc);
|
|
pc = RST_TABLE[Y(opcode)];
|
|
elapsed_cycles++;
|
|
break;
|
|
}
|
|
|
|
/* Input and output group. */
|
|
|
|
case Instruction::IN_A_N: {
|
|
int n;
|
|
|
|
READ_N(n);
|
|
Z80_INPUT_BYTE(n, A);
|
|
|
|
elapsed_cycles += 4;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::IN_R_C: {
|
|
int x;
|
|
Z80_INPUT_BYTE(BC, x);
|
|
if (Y(opcode) != INDIRECT_HL) R(Y(opcode)) = x;
|
|
F = SZYXP_FLAGS_TABLE[x] | (F & Z80_C_FLAG);
|
|
|
|
elapsed_cycles += 4;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Some of the undocumented flags for "INI", "IND",
|
|
* "INIR", "INDR", "OUTI", "OUTD", "OTIR", and
|
|
* "OTDR" are really really strange. The emulator
|
|
* implements the specifications described in "The
|
|
* Undocumented Z80 Documented Version 0.91".
|
|
*/
|
|
|
|
case Instruction::INI_IND: {
|
|
int x, f;
|
|
|
|
Z80_INPUT_BYTE(C, x);
|
|
WRITE_BYTE(HL, x);
|
|
|
|
f = SZYX_FLAGS_TABLE[--B & 0xff] | (x >> (7 - Z80_N_FLAG_SHIFT));
|
|
if (opcode == OPCODE_INI) {
|
|
HL++;
|
|
x += (C + 1) & 0xff;
|
|
|
|
} else {
|
|
HL--;
|
|
x += (C - 1) & 0xff;
|
|
}
|
|
f |= x & 0x0100 ? HC_FLAGS : 0;
|
|
f |= SZYXP_FLAGS_TABLE[(x & 0x07) ^ B] & Z80_P_FLAG;
|
|
F = f;
|
|
|
|
elapsed_cycles += 5;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::INIR_INDR: {
|
|
int d, b, hl, x, f;
|
|
|
|
#ifdef Z80_HANDLE_SELF_MODIFYING_CODE
|
|
|
|
int p, q;
|
|
|
|
p = (pc - 2) & 0xffff;
|
|
q = (pc - 1) & 0xffff;
|
|
|
|
#endif
|
|
|
|
d = opcode == OPCODE_INIR ? +1 : -1;
|
|
|
|
b = B;
|
|
hl = HL;
|
|
|
|
r -= 2;
|
|
elapsed_cycles -= 8;
|
|
for (;;) {
|
|
r += 2;
|
|
|
|
Z80_INPUT_BYTE(C, x);
|
|
Z80_WRITE_BYTE(hl, x);
|
|
|
|
hl += d;
|
|
|
|
if (--b)
|
|
|
|
elapsed_cycles += 21;
|
|
|
|
else {
|
|
f = Z80_Z_FLAG;
|
|
elapsed_cycles += 16;
|
|
break;
|
|
}
|
|
|
|
#ifdef Z80_HANDLE_SELF_MODIFYING_CODE
|
|
|
|
if (((hl - d) & 0xffff) == p || ((hl - d) & 0xffff) == q) {
|
|
f = SZYX_FLAGS_TABLE[b];
|
|
pc -= 2;
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
|
|
if (elapsed_cycles < number_cycles)
|
|
|
|
continue;
|
|
|
|
else {
|
|
f = SZYX_FLAGS_TABLE[b];
|
|
pc -= 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
HL = hl;
|
|
B = b;
|
|
|
|
f |= x >> (7 - Z80_N_FLAG_SHIFT);
|
|
x += (C + d) & 0xff;
|
|
f |= x & 0x0100 ? HC_FLAGS : 0;
|
|
f |= SZYXP_FLAGS_TABLE[(x & 0x07) ^ b] & Z80_P_FLAG;
|
|
F = f;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::OUT_N_A: {
|
|
int n;
|
|
|
|
elapsed_cycles += 4;
|
|
READ_N(n);
|
|
if (Z80_OUTPUT_BYTE(n, A)) {
|
|
goto stop_emulation;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::OUT_C_R: {
|
|
int x;
|
|
|
|
elapsed_cycles += 4;
|
|
x = Y(opcode) != INDIRECT_HL ? R(Y(opcode)) : 0;
|
|
if (Z80_OUTPUT_BYTE(C, x)) {
|
|
goto stop_emulation;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::OUTI_OUTD: {
|
|
int x, f;
|
|
|
|
READ_BYTE(HL, x);
|
|
Z80_OUTPUT_BYTE(C, x);
|
|
|
|
HL += opcode == OPCODE_OUTI ? +1 : -1;
|
|
|
|
f = SZYX_FLAGS_TABLE[--B & 0xff] | (x >> (7 - Z80_N_FLAG_SHIFT));
|
|
x += HL & 0xff;
|
|
f |= x & 0x0100 ? HC_FLAGS : 0;
|
|
f |= SZYXP_FLAGS_TABLE[(x & 0x07) ^ B] & Z80_P_FLAG;
|
|
F = f;
|
|
|
|
break;
|
|
}
|
|
|
|
case Instruction::OTIR_OTDR: {
|
|
int d, b, hl, x, f;
|
|
|
|
d = opcode == OPCODE_OTIR ? +1 : -1;
|
|
|
|
b = B;
|
|
hl = HL;
|
|
|
|
r -= 2;
|
|
elapsed_cycles -= 8;
|
|
for (;;) {
|
|
r += 2;
|
|
|
|
Z80_READ_BYTE(hl, x);
|
|
Z80_OUTPUT_BYTE(C, x);
|
|
|
|
hl += d;
|
|
if (--b)
|
|
|
|
elapsed_cycles += 21;
|
|
|
|
else {
|
|
f = Z80_Z_FLAG;
|
|
elapsed_cycles += 16;
|
|
break;
|
|
}
|
|
|
|
if (elapsed_cycles < number_cycles)
|
|
|
|
continue;
|
|
|
|
else {
|
|
f = SZYX_FLAGS_TABLE[b];
|
|
pc -= 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
HL = hl;
|
|
B = b;
|
|
|
|
f |= x >> (7 - Z80_N_FLAG_SHIFT);
|
|
x += hl & 0xff;
|
|
f |= x & 0x0100 ? HC_FLAGS : 0;
|
|
f |= SZYXP_FLAGS_TABLE[(x & 0x07) ^ b] & Z80_P_FLAG;
|
|
F = f;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Prefix group. */
|
|
|
|
case Instruction::CB_PREFIX: {
|
|
/* Special handling if the 0xcb prefix is
|
|
* prefixed by a 0xdd or 0xfd prefix.
|
|
*/
|
|
|
|
if (registers != register_table_) {
|
|
r--;
|
|
|
|
/* Indexed memory access routine will
|
|
* correctly update pc.
|
|
*/
|
|
|
|
Z80_FETCH_BYTE(pc + 1, opcode);
|
|
|
|
} else {
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
}
|
|
instruction = (Instruction)CB_INSTRUCTION_TABLE[opcode];
|
|
|
|
goto emulate_next_instruction;
|
|
}
|
|
|
|
case Instruction::DD_PREFIX: {
|
|
registers = dd_register_table_;
|
|
|
|
#ifdef Z80_PREFIX_FAILSAFE
|
|
|
|
/* Ensure that at least number_cycles cycles
|
|
* are executed.
|
|
*/
|
|
|
|
if (elapsed_cycles < number_cycles) {
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
goto emulate_next_opcode;
|
|
|
|
} else {
|
|
status_ = Z80_STATUS_PREFIX;
|
|
pc--;
|
|
elapsed_cycles -= 4;
|
|
goto stop_emulation;
|
|
}
|
|
|
|
#else
|
|
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
goto emulate_next_opcode;
|
|
|
|
#endif
|
|
}
|
|
|
|
case Instruction::FD_PREFIX: {
|
|
registers = fd_register_table_;
|
|
|
|
#ifdef Z80_PREFIX_FAILSAFE
|
|
|
|
if (elapsed_cycles < number_cycles) {
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
goto emulate_next_opcode;
|
|
|
|
} else {
|
|
status_ = Z80_STATUS_PREFIX;
|
|
pc--;
|
|
elapsed_cycles -= 4;
|
|
goto stop_emulation;
|
|
}
|
|
|
|
#else
|
|
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
goto emulate_next_opcode;
|
|
|
|
#endif
|
|
}
|
|
|
|
case Instruction::ED_PREFIX: {
|
|
registers = register_table_;
|
|
Z80_FETCH_BYTE(pc, opcode);
|
|
pc++;
|
|
instruction = (Instruction)ED_INSTRUCTION_TABLE[opcode];
|
|
|
|
goto emulate_next_instruction;
|
|
}
|
|
|
|
/* Special/pseudo instruction group. */
|
|
|
|
case Instruction::ED_UNDEFINED: {
|
|
#ifdef Z80_CATCH_ED_UNDEFINED
|
|
|
|
status_ = Z80_STATUS_FLAG_ED_UNDEFINED;
|
|
pc -= 2;
|
|
goto stop_emulation;
|
|
|
|
#else
|
|
|
|
break;
|
|
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// if (elapsed_cycles >= number_cycles) goto stop_emulation;
|
|
}
|
|
|
|
stop_emulation:
|
|
|
|
r_ = (r_ & 0x80) | (r & 0x7f);
|
|
pc_ = pc & 0xffff;
|
|
|
|
return elapsed_cycles;
|
|
}
|
|
|
|
template <typename Board>
|
|
uint8_t Z80Emu<Board>::ReadReg(int reg) {
|
|
return registers_.byte[reg];
|
|
}
|
|
|
|
template <typename Board>
|
|
uint16_t Z80Emu<Board>::ReadReg16(int reg) {
|
|
return registers_.word[reg];
|
|
}
|
|
|
|
} // namespace z80emu
|