1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-17 10:28:06 +11:00
collapseos/emul/z80/z80.c
Virgil Dupras efe4b13a4e Move /emul to /emul/z80
I'm planning on adding other subfolders. 8086 for example...
2020-10-24 16:50:22 -04:00

886 lines
17 KiB
C

// This unit has been copied from libz80 into Collapse OS and was slighly changed
/* =========================================================
* libz80 - Z80 emulation library
* =========================================================
*
* (C) Gabriel Gambetta (gabriel.gambetta@gmail.com) 2000 - 2012
*
* Version 2.1.0
*
* ---------------------------------------------------------
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "z80.h"
#include "string.h"
#define BR (ctx->R1.br)
#define WR (ctx->R1.wr)
#define SETFLAG(F) setFlag(ctx, F)
#define RESFLAG(F) resFlag(ctx, F)
#define GETFLAG(F) getFlag(ctx, F)
#define VALFLAG(F,V) valFlag(ctx, F, V)
/* ---------------------------------------------------------
* Flag tricks
* ---------------------------------------------------------
*
* To avoid repeating entries in the spec files, many operations that look similar are treated as special cases
* of a more general operation.
*
* For example, ADD and ADC are similar in syntax and operation - the difference is that ADC takes the carry flag
* into account.
*
* So we define a general operation doArithmetic(...) which accepts a boolean parameter specifying whether to do
* a Carry-operation or not. Then, when we parse, we can say
*
* (ADD|ADC) ....
* doArithmetic(FLAG_FOR_%1)
*
* and everything works fine.
*
*/
/* Flags for doIncDec() */
static const int ID_INC = 0;
static const int ID_DEC = 1;
/* Flags for enable / disable interrupts */
static const int IE_DI = 0;
static const int IE_EI = 1;
/* Flags for doSetRes() */
static const int SR_RES = 0;
static const int SR_SET = 1;
/* Flags for logical / arithmetic operations */
static const int IA_L = 0;
static const int IA_A = 1;
/* Flags for doArithmetic() - F1 = withCarry, F2 = isSub */
static const int F1_ADC = 1;
static const int F1_SBC = 1;
static const int F1_ADD = 0;
static const int F1_SUB = 0;
static const int F2_ADC = 0;
static const int F2_SBC = 1;
static const int F2_ADD = 0;
static const int F2_SUB = 1;
/* Increment or decrement R, preserving bit 7 */
#define INCR (ctx->R = (ctx->R & 0x80) | ((ctx->R + 1) & 0x7f))
#define DECR (ctx->R = (ctx->R & 0x80) | ((ctx->R - 1) & 0x7f))
/* ---------------------------------------------------------
* The opcode implementations
* ---------------------------------------------------------
*/
#include "opcodes_decl.h"
typedef enum
{
OP_NONE,
OP_BYTE,
OP_OFFSET,
OP_WORD
} Z80OperandType;
typedef void (*Z80OpcodeFunc) (Z80Context* ctx);
struct Z80OpcodeEntry
{
Z80OpcodeFunc func;
int operand_type;
char* format;
struct Z80OpcodeTable* table;
};
struct Z80OpcodeTable
{
int opcode_offset;
struct Z80OpcodeEntry entries[256];
};
#include "opcodes_table.h"
/* ---------------------------------------------------------
* Data operations
* ---------------------------------------------------------
*/
static void write8 (Z80Context* ctx, ushort addr, byte val)
{
ctx->tstates += 3;
ctx->memWrite(ctx->memParam, addr, val);
}
static void write16 (Z80Context* ctx, ushort addr, ushort val)
{
write8(ctx, addr, val);
write8(ctx, addr + 1, val >> 8);
}
static byte read8 (Z80Context* ctx, ushort addr)
{
ctx->tstates += 3;
return ctx->memRead(ctx->memParam, addr);
}
static ushort read16 (Z80Context* ctx, ushort addr)
{
byte lsb = read8(ctx, addr);
byte msb = read8(ctx, addr + 1);
return msb << 8 | lsb;
}
static byte ioRead (Z80Context* ctx, ushort addr)
{
ctx->tstates += 4;
return ctx->ioRead(ctx->ioParam, addr);
}
static void ioWrite (Z80Context* ctx, ushort addr, byte val)
{
ctx->tstates += 4;
ctx->ioWrite(ctx->ioParam, addr, val);
}
/* ---------------------------------------------------------
* Flag operations
* ---------------------------------------------------------
*/
/** Sets a flag */
static void setFlag(Z80Context* ctx, Z80Flags flag)
{
BR.F |= flag;
}
/** Resets a flag */
static void resFlag(Z80Context* ctx, Z80Flags flag)
{
BR.F &= ~flag;
}
/** Puts a value in a flag */
static void valFlag(Z80Context* ctx, Z80Flags flag, int val)
{
if (val)
SETFLAG(flag);
else
RESFLAG(flag);
}
/** Returns a flag */
static int getFlag(Z80Context* ctx, Z80Flags flag)
{
return (BR.F & flag) != 0;
}
/* ---------------------------------------------------------
* Flag adjustments
* ---------------------------------------------------------
*/
static int parityBit[256] = {
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 };
static void adjustFlags (Z80Context* ctx, byte val)
{
VALFLAG(F_5, (val & F_5) != 0);
VALFLAG(F_3, (val & F_3) != 0);
}
static void adjustFlagSZP (Z80Context* ctx, byte val)
{
VALFLAG(F_S, (val & 0x80) != 0);
VALFLAG(F_Z, (val == 0));
VALFLAG(F_PV, parityBit[val]);
}
/* Adjust flags after AND, OR, XOR */
static void adjustLogicFlag (Z80Context* ctx, int flagH)
{
VALFLAG(F_S, (BR.A & 0x80) != 0);
VALFLAG(F_Z, (BR.A == 0));
VALFLAG(F_H, flagH);
VALFLAG(F_N, 0);
VALFLAG(F_C, 0);
VALFLAG(F_PV, parityBit[BR.A]);
adjustFlags(ctx, BR.A);
}
/* ---------------------------------------------------------
* Condition checks
* ---------------------------------------------------------
*/
typedef enum
{
C_,
C_Z,
C_NZ,
C_C,
C_NC,
C_M,
C_P,
C_PE,
C_PO
} Z80Condition;
static int condition(Z80Context* ctx, Z80Condition cond)
{
if (cond == C_)
return 1;
if (cond == C_Z)
return GETFLAG(F_Z);
if (cond == C_NZ)
return !GETFLAG(F_Z);
if (cond == C_C)
return GETFLAG(F_C);
if (cond == C_NC)
return !GETFLAG(F_C);
if (cond == C_M)
return GETFLAG(F_S);
if (cond == C_P)
return !GETFLAG(F_S);
if (cond == C_PE)
return GETFLAG(F_PV);
/* if (cond == C_PO)*/
return !GETFLAG(F_PV);
}
/* ---------------------------------------------------------
* Generic operations
* ---------------------------------------------------------
*/
static int doComplement(byte v)
{
if ((v & 0x80) == 0)
return v;
v = ~v;
v &= 0x7F;
v++;
return -v;
}
/** Do an arithmetic operation (ADD, SUB, ADC, SBC y CP) */
static byte doArithmetic (Z80Context* ctx, byte value, int withCarry, int isSub)
{
ushort res; /* To detect carry */
if (isSub)
{
SETFLAG(F_N);
VALFLAG(F_H, (((BR.A & 0x0F) - (value & 0x0F)) & 0x10) != 0);
res = BR.A - value;
if (withCarry && GETFLAG(F_C))
res--;
}
else
{
RESFLAG(F_N);
VALFLAG(F_H, (((BR.A & 0x0F) + (value & 0x0F)) & 0x10) != 0);
res = BR.A + value;
if (withCarry && GETFLAG(F_C))
res++;
}
VALFLAG(F_S, ((res & 0x80) != 0));
VALFLAG(F_C, ((res & 0x100) != 0));
VALFLAG(F_Z, ((res & 0xff) == 0));
int minuend_sign = BR.A & 0x80;
int subtrahend_sign = value & 0x80;
int result_sign = res & 0x80;
int overflow;
if(isSub)
overflow = minuend_sign != subtrahend_sign && result_sign != minuend_sign;
else
overflow = minuend_sign == subtrahend_sign && result_sign != minuend_sign;
VALFLAG(F_PV, overflow);
adjustFlags(ctx, res);
return (byte)(res & 0xFF);
}
/* Do a 16-bit addition, setting the appropriate flags. */
static ushort doAddWord(Z80Context* ctx, ushort a1, ushort a2, int withCarry, int isSub)
{
if(withCarry && GETFLAG(F_C))
a2++;
int sum = a1;
if(isSub)
{
sum -= a2;
VALFLAG(F_H, ((a1 & 0x0fff) - (a2 & 0x0fff)) & 0x1000);
}
else
{
sum += a2;
VALFLAG(F_H, ((a1 & 0x0fff) + (a2 & 0x0fff)) & 0x1000);
}
VALFLAG(F_C, sum & 0x10000);
if(withCarry || isSub)
{
int minuend_sign = a1 & 0x8000;
int subtrahend_sign = a2 & 0x8000;
int result_sign = sum & 0x8000;
int overflow;
if(isSub)
overflow = minuend_sign != subtrahend_sign && result_sign != minuend_sign;
else
overflow = minuend_sign == subtrahend_sign && result_sign != minuend_sign;
VALFLAG(F_PV, overflow);
VALFLAG(F_S, (sum & 0x8000) != 0);
VALFLAG(F_Z, (sum & 0xFFFF) == 0);
}
VALFLAG(F_N, isSub);
adjustFlags(ctx, sum >> 8);
return sum;
}
static void doAND (Z80Context* ctx, byte value)
{
BR.A &= value;
adjustLogicFlag(ctx, 1);
}
static void doOR (Z80Context* ctx, byte value)
{
BR.A |= value;
adjustLogicFlag(ctx, 0);
}
static void doXOR (Z80Context* ctx, byte value)
{
BR.A ^= value;
adjustLogicFlag(ctx, 0);
}
static void doBIT (Z80Context* ctx, int b, byte val)
{
if (val & (1 << b))
RESFLAG(F_Z | F_PV);
else
SETFLAG(F_Z | F_PV);
SETFLAG(F_H);
RESFLAG(F_N);
RESFLAG(F_S);
if ((b == 7) && !GETFLAG(F_Z))
SETFLAG(F_S);
}
static void doBIT_r(Z80Context* ctx, int b, byte val)
{
doBIT(ctx, b, val);
VALFLAG(F_5, val & F_5);
VALFLAG(F_3, val & F_3);
}
static void doBIT_indexed(Z80Context* ctx, int b, ushort address)
{
byte val = read8(ctx, address);
doBIT(ctx, b, val);
VALFLAG(F_5, (address >> 8) & F_5);
VALFLAG(F_3, (address >> 8) & F_3);
}
byte doSetRes (Z80Context* ctx, int bit, int pos, byte val)
{
if (bit)
val |= (1 << pos);
else
val &= ~(1 << pos);
return val;
}
static byte doIncDec (Z80Context* ctx, byte val, int isDec)
{
if (isDec)
{
VALFLAG(F_PV, (val & 0x80) && !((val - 1) & 0x80));
val--;
VALFLAG(F_H, (val & 0x0F) == 0x0F);
}
else
{
VALFLAG(F_PV, !(val & 0x80) && ((val + 1) & 0x80));
val++;
VALFLAG(F_H, !(val & 0x0F));
}
VALFLAG(F_S, ((val & 0x80) != 0));
VALFLAG(F_Z, (val == 0));
VALFLAG(F_N, isDec);
adjustFlags(ctx, val);
return val;
}
static byte doRLC (Z80Context* ctx, int adjFlags, byte val)
{
VALFLAG(F_C, (val & 0x80) != 0);
val <<= 1;
val |= (byte)GETFLAG(F_C);
adjustFlags(ctx, val);
RESFLAG(F_H | F_N);
if (adjFlags)
adjustFlagSZP(ctx, val);
return val;
}
static byte doRL (Z80Context* ctx, int adjFlags, byte val)
{
int CY = GETFLAG(F_C);
VALFLAG(F_C, (val & 0x80) != 0);
val <<= 1;
val |= (byte)CY;
adjustFlags(ctx, val);
RESFLAG(F_H | F_N);
if (adjFlags)
adjustFlagSZP(ctx, val);
return val;
}
static byte doRRC (Z80Context* ctx, int adjFlags, byte val)
{
VALFLAG(F_C, (val & 0x01) != 0);
val >>= 1;
val |= ((byte)GETFLAG(F_C) << 7);
adjustFlags(ctx, val);
RESFLAG(F_H | F_N);
if (adjFlags)
adjustFlagSZP(ctx, val);
return val;
}
static byte doRR (Z80Context* ctx, int adjFlags, byte val)
{
int CY = GETFLAG(F_C);
VALFLAG(F_C, (val & 0x01));
val >>= 1;
val |= (CY << 7);
adjustFlags(ctx, val);
RESFLAG(F_H | F_N);
if (adjFlags)
adjustFlagSZP(ctx, val);
return val;
}
static byte doSL (Z80Context* ctx, byte val, int isArith)
{
VALFLAG(F_C, (val & 0x80) != 0);
val <<= 1;
if (!isArith)
val |= 1;
adjustFlags(ctx, val);
RESFLAG(F_H | F_N);
adjustFlagSZP(ctx, val);
return val;
}
static byte doSR (Z80Context* ctx, byte val, int isArith)
{
int b = val & 0x80;
VALFLAG(F_C, (val & 0x01) != 0);
val >>= 1;
if (isArith)
val |= b;
adjustFlags(ctx, val);
RESFLAG(F_H | F_N);
adjustFlagSZP(ctx, val);
return val;
}
static void doPush (Z80Context* ctx, ushort val)
{
WR.SP--;
WR.SP--;
write16(ctx, WR.SP, val);
}
static ushort doPop (Z80Context* ctx)
{
ushort val;
val = read16(ctx, WR.SP);
WR.SP++;
WR.SP++;
return val;
}
static byte doCP_HL(Z80Context * ctx)
{
byte val = read8(ctx, WR.HL);
byte result = doArithmetic(ctx, val, 0, 1);
adjustFlags(ctx, val);
return result;
}
/* The DAA opcode
* According to the value in A and the flags set, add a value to A
* This algorithm taken from:
* http://www.worldofspectrum.org/faq/reference/z80reference.htm
* and verified against the specification in the Zilog
* Z80 Family CPU User Manual, rev. 04, Dec. 2004, pp. 166-167
*/
static void doDAA(Z80Context * ctx) {
int correction_factor = 0x00;
int carry = 0;
if(BR.A > 0x99 || GETFLAG(F_C)) {
correction_factor |= 0x60;
carry = 1;
}
if((BR.A & 0x0f) > 9 || GETFLAG(F_H))
correction_factor |= 0x06;
int a_before = BR.A;
if(GETFLAG(F_N))
BR.A -= correction_factor;
else
BR.A += correction_factor;
VALFLAG(F_H, (a_before ^ BR.A) & 0x10);
VALFLAG(F_C, carry);
VALFLAG(F_S, (BR.A & 0x80) != 0);
VALFLAG(F_Z, (BR.A == 0));
VALFLAG(F_PV, parityBit[BR.A]);
adjustFlags(ctx, BR.A);
}
#include "opcodes_impl.c"
/* ---------------------------------------------------------
* The top-level functions
* ---------------------------------------------------------
*/
static void do_execute(Z80Context* ctx)
{
struct Z80OpcodeTable* current = &opcodes_main;
struct Z80OpcodeEntry* entries = current->entries;
Z80OpcodeFunc func;
byte opcode;
int offset = 0;
do
{
if (ctx->exec_int_vector)
{
opcode = ctx->int_vector;
ctx->tstates += 6;
}
else
{
opcode = read8(ctx, ctx->PC + offset);
ctx->PC++;
ctx->tstates += 1;
}
INCR;
func = entries[opcode].func;
if (func != NULL)
{
ctx->PC -= offset;
func(ctx);
ctx->PC += offset;
break;
}
else if (entries[opcode].table != NULL)
{
current = entries[opcode].table;
entries = current->entries;
offset = current->opcode_offset;
if (offset > 0)
DECR;
}
else
{
/* NOP */
break;
}
} while(1);
}
static void unhalt(Z80Context* ctx)
{
if (ctx->halted)
{
ctx->halted = 0;
ctx->PC++;
}
}
static void do_nmi(Z80Context* ctx)
{
unhalt(ctx);
ctx->IFF2 = ctx->IFF1;
ctx->IFF1 = 0;
doPush(ctx, ctx->PC);
ctx->PC = 0x0066;
ctx->nmi_req = 0;
ctx->tstates += 5;
}
static void do_int(Z80Context* ctx)
{
unhalt(ctx);
ctx->IFF1 = 0;
ctx->IFF2 = 0;
ctx->int_req = 0;
if (ctx->IM == 0)
{
ctx->exec_int_vector = 1;
do_execute(ctx);
ctx->exec_int_vector = 0;
}
else if (ctx->IM == 1)
{
doPush(ctx, ctx->PC);
ctx->PC = 0x0038;
ctx->tstates += 7;
}
else if (ctx->IM == 2)
{
doPush(ctx, ctx->PC);
ushort vector_address = (ctx->I << 8) | ctx->int_vector;
ctx->PC = read16(ctx, vector_address);
ctx->tstates += 7;
}
}
void Z80Execute (Z80Context* ctx)
{
if (ctx->nmi_req)
do_nmi(ctx);
else if (ctx->int_req && !ctx->defer_int && ctx->IFF1)
do_int(ctx);
else
{
ctx->defer_int = 0;
do_execute(ctx);
}
}
unsigned Z80ExecuteTStates(Z80Context* ctx, unsigned tstates)
{
ctx->tstates = 0;
while (ctx->tstates < tstates)
Z80Execute(ctx);
return ctx->tstates;
}
void Z80Debug (Z80Context* ctx, char* dump, char* decode)
{
char tmp[20];
struct Z80OpcodeTable* current = &opcodes_main;
struct Z80OpcodeEntry* entries = current->entries;
char* fmt;
byte opcode;
ushort parm;
int offset = 0;
int PC = ctx->PC;
int size = 0;
if (dump)
dump[0] = 0;
if (decode)
decode[0] = 0;
do
{
opcode = read8(ctx, PC + offset);
size++;
PC++;
fmt = entries[opcode].format;
if (fmt != NULL)
{
PC -= offset;
parm = read16(ctx, PC);
if (entries[opcode].operand_type == OP_NONE)
size++;
else
size += 2;
if (entries[opcode].operand_type != OP_WORD)
{
parm &= 0xFF;
size--;
}
if (decode)
sprintf(decode, fmt, parm);
PC += offset;
break;
}
else if (entries[opcode].table != NULL)
{
current = entries[opcode].table;
entries = current->entries;
offset = current->opcode_offset;
}
else
{
if (decode != NULL)
strcpy(decode, "NOP (ignored)");
break;
}
} while(1);
if (dump)
{
for (offset = 0; offset < size; offset++)
{
sprintf(tmp, "%02X", read8(ctx, ctx->PC + offset));
strcat(dump, tmp);
}
}
}
void Z80RESET (Z80Context* ctx)
{
ctx->PC = 0x0000;
BR.F = 0;
ctx->IM = 0;
ctx->IFF1 = ctx->IFF2 = 0;
ctx->R = 0;
ctx->I = 0;
ctx->halted = 0;
ctx->tstates = 0;
ctx->nmi_req = 0;
ctx->int_req = 0;
ctx->defer_int = 0;
ctx->exec_int_vector = 0;
}
void Z80INT (Z80Context* ctx, byte value)
{
ctx->int_req = 1;
ctx->int_vector = value;
}
void Z80NMI (Z80Context* ctx)
{
ctx->nmi_req = 1;
}