// This file is part of the Noddybox.Emulation C# suite.
//
// Noddybox.Emulation 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 3 of the License, or
// (at your option) any later version.
//
// Noddybox.Emulation 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 Noddybox.Emulation. If not, see .
//
// Copyright (c) 2012 Ian Cowburn
//
// Some of the concepts and code in here are taken from FUSE, the open source UNIX
// Spectrum emulator Copyright (c) 1999-2003 Philip Kendall.
// See
//
// Any introduced bugs in that code are mine, and not the original authors.
//
using System;
namespace Noddybox.Emulation.EightBit.Z80
{
public partial class Z80Cpu
{
#region Fetch and helper operations
///
/// Fetch the next word from the PC.
///
/// The word.
ushort FetchWord()
{
Register16 r = new Register16(0);
r.low = memory.Read(PC++);
r.high = memory.Read(PC++);
return r.reg;
}
///
/// Swap two 16-bit registers.
///
/// The first register.
/// The second register.
void Swap(ref Register16 a, ref Register16 b)
{
Register16 t = a;
a = b;
b = t;
}
///
/// Fetch the offset if the current opcode is shifted.
///
/// The offset, or zero if the current opcode is not shifted.
sbyte Offset()
{
if (shift == 0xdd || shift == 0xfd)
{
clock.Add(8);
byte b = memory.Read(PC++);
return (sbyte)b;
}
else
{
return 0;
}
}
#endregion
#region Status/special register helpers
///
/// Set a flag in the status register.
///
/// The flag.
private void SetFlag(Z80Flags flag)
{
F |= flag;
}
///
/// Clear a flag in the status register.
///
/// The flag.
private void ClearFlag(Z80Flags flag)
{
F &= ~flag;
}
///
/// Add a value to the R (refresh) register.
///
/// The value.
private void AddR(byte v)
{
R = (byte)((R & 0x80) | (R + v) & 0x7f);
}
#endregion
#region Stack commands
///
/// Push a value on the stack.
///
/// The value.
private void PUSH(ushort val)
{
Register16 r = new Register16(val);
memory.Write(--SP, r.high);
memory.Write(--SP, r.low);
}
///
/// Pop a value from the stack.
///
///
private ushort POP()
{
Register16 r = new Register16(0);
r.low = memory.Read(SP++);
r.high = memory.Read(SP++);
return r.reg;
}
#endregion
#region ALU arithmetic and comparison
///
/// Add an 8-bit value to the accumulator without carry.
///
/// The vakue.
private void ADD8(byte b)
{
int w = A + b;
int lookup = ((A & 0x88) >> 3) | ((b & 0x88) >> 2) | ((w & 0x88) >> 1);
F = SZtable[w] | H35table[w & 0xff] |
halfcarry_add_table[lookup & 0x07] | overflow_add_table[lookup >> 4];
A = (byte)(w & 0xff);
}
///
/// Add an 8-bit value to the accumulator with carry.
///
/// The vakue.
private void ADC8(byte b)
{
int w = A + b + (int)(F & Z80Flags.Carry);
int lookup = ((A & 0x88) >> 3) | ((b & 0x88) >> 2) | ((w & 0x88) >> 1);
F = SZtable[w] | H35table[w & 0xff] |
halfcarry_add_table[lookup & 0x07] | overflow_add_table[lookup >> 4];
A = (byte)(w & 0xff);
}
///
/// Subtract an 8-bit value from the accumulator without carry.
///
/// The vakue.
private void SUB8(byte b)
{
int w = A - b;
if (w < 0)
{
w += 0x200;
}
int lookup = ((A & 0x88) >> 3) | ((b & 0x88) >> 2) | ((w & 0x88) >> 1);
F = SZtable[w] | H35table[w] | Z80Flags.Neg |
halfcarry_sub_table[lookup & 0x07] | overflow_sub_table[lookup >> 4];
A = (byte)(w & 0xff);
}
///
/// Compare an 8-bit value with the accumulator.
///
/// The vakue.
private void CP(byte b)
{
int w = A - b;
if (w < 0)
{
w += 0x200;
}
int lookup = ((A & 0x88) >> 3) | ((b & 0x88) >> 2) | ((w & 0x88) >> 1);
F = SZtable[w] | H35table[b] | Z80Flags.Neg |
halfcarry_sub_table[lookup & 0x07] | overflow_sub_table[lookup >> 4];
}
///
/// Subtract an 8-bit value from the accumulator with carry.
///
/// The vakue.
private void SBC8(byte b)
{
int w = A - b - (int)(F & Z80Flags.Carry);
if (w < 0)
{
w += 0x200;
}
int lookup = ((A & 0x88) >> 3) | ((b & 0x88) >> 2) | ((w & 0x88) >> 1);
F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg |
halfcarry_sub_table[lookup & 0x07] | overflow_sub_table[lookup >> 4];
A = (byte)(w & 0xff);
}
///
/// Add a 16-bit value to a register without carry.
///
/// The vakue.
private void ADD16(ref ushort reg, ushort b)
{
int w = reg + b;
F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV;
if (w > 0xffff)
{
F |= Z80Flags.Carry;
}
int lookup = ((reg & 0x8800) >> 11) |
((b & 0x8800) >> 10) |
((w & 0x8800) >> 9);
reg = (ushort)(w & 0xffff);
F |= halfcarry_add_table[lookup & 0x07] | H35table[reg >> 8];
}
///
/// Add a 16-bit value to a register with carry.
///
/// The vakue.
private void ADC16(ref ushort reg, ushort b)
{
int w = reg + b + (int)(F & Z80Flags.Carry);
F = Z80Flags.None;
if ((w & 0xffff) == 0)
{
F |= Z80Flags.Zero;
}
if ((w & 0x8000) == 0x8000)
{
F |= Z80Flags.Sign;
}
if (w > 0xffff)
{
F |= Z80Flags.Carry;
}
int lookup = ((reg & 0x8800) >> 11) |
((b & 0x8800) >> 10) |
((w & 0x8800) >> 9);
reg = (ushort)(w & 0xffff);
F |= halfcarry_add_table[lookup & 0x07] | overflow_add_table[lookup >> 4] |
H35table[reg >> 8];
}
///
/// Subtract a 16-bit value from a register with carry.
///
/// The vakue.
private void SBC(ref ushort reg, ushort b)
{
int w = reg - b - (int)(F & Z80Flags.Carry);
F = Z80Flags.Neg;
if (w < 0)
{
w += 0x10000;
F |= Z80Flags.Carry;
}
if ((w & 0xffff) == 0)
{
F |= Z80Flags.Zero;
}
if ((w & 0x8000) == 0x8000)
{
F |= Z80Flags.Sign;
}
int lookup = ((reg & 0x8800) >> 11) |
((b & 0x8800) >> 10) |
((w & 0x8800) >> 9);
reg = (ushort)(w & 0xffff);
F |= halfcarry_sub_table[lookup & 0x07] | overflow_sub_table[lookup >> 4] |
H35table[reg >> 8];
}
///
/// Increment an 8-bit register.
///
/// The register to increment.
void INC8(ref byte reg)
{
reg++;
F &= Z80Flags.Carry;
if (reg == 0x80)
{
F |= Z80Flags.PV;
}
if ((reg & 0x0f) == 0x00)
{
F |= Z80Flags.HalfCarry;
}
F |= SZtable[reg] | H35table[reg];
}
///
/// Decrement an 8-bit register.
///
/// The register to decrement.
void DEC8(ref byte reg)
{
reg--;
F &= Z80Flags.Carry;
F |= Z80Flags.Neg;
if (reg == 0x7f)
{
F |= Z80Flags.PV;
}
if ((reg & 0x0f) == 0x0f)
{
F |= Z80Flags.HalfCarry;
}
F |= SZtable[reg] | H35table[reg];
}
///
/// Decimally adjust the accumulator. A bugger of an opcode.
/// Based on info from http://www.worldofspectrum.org/faq/reference/z80reference.htm
///
void DAA()
{
byte add = 0;
Z80Flags carry = Z80Flags.None;
Z80Flags nf = F & Z80Flags.Neg;
byte acc = A;
if (acc>0x99 || (F & Z80Flags.Carry) == Z80Flags.Carry)
{
add |= 0x60;
carry = Z80Flags.Carry;
}
if ((acc & 0xf) > 0x9 || (F & Z80Flags.HalfCarry) == Z80Flags.HalfCarry)
{
add|=0x06;
}
if (nf == Z80Flags.Neg)
{
A -= add;
}
else
{
A += add;
}
F = PSZtable[A]
| carry
| nf
| H35table[A];
if (((A ^ acc) & 0x10) == 0x10)
{
F |= Z80Flags.HalfCarry;
}
}
#endregion
#region ALU rotate and shift operations
///
/// Do RRCA.
///
private void RRCA()
{
F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV;
F |= (Z80Flags)(A & 1);
A = (byte)(Binary.ShiftRight(A, 1) | Binary.ShiftLeft(A, 7));
F |= H35table[A];
}
///
/// Do RRA.
///
private void RRA()
{
byte carry = (byte)(F & Z80Flags.Carry);
F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV;
F |= (Z80Flags)(A & 1);
A = (byte)(Binary.ShiftRight(A, 1) | Binary.ShiftLeft(carry, 7));
F |= H35table[A];
}
///
/// Do RRC.
///
/// The register to operate on.
private void RRC(ref byte reg)
{
F = (Z80Flags)(reg & (int)Z80Flags.Carry);
reg = (byte)(Binary.ShiftRight(reg, 1) | Binary.ShiftLeft(reg, 7));
F |= PSZtable[reg] | H35table[reg];
}
///
/// Do RR.
///
/// The register to operate on.
private void RR(ref byte reg)
{
byte carry = (byte)(F & Z80Flags.Carry);
F = (Z80Flags)(reg & (int)Z80Flags.Carry);
reg = (byte)(Binary.ShiftRight(reg, 1) | Binary.ShiftLeft(carry, 7));
F |= PSZtable[reg] | H35table[reg];
}
///
/// Do RLCA.
///
private void RLCA()
{
F = (F & (Z80Flags.PV | Z80Flags.Sign | Z80Flags.Zero))
| (Z80Flags)Binary.ShiftRight(A, 7);
A = (byte)(Binary.ShiftLeft(A, 1) | Binary.ShiftRight(A, 7));
F |= H35table[A];
}
///
/// Do RLA.
///
private void RLA()
{
byte carry = (byte)(F & Z80Flags.Carry);
F = (F & (Z80Flags.PV | Z80Flags.Sign | Z80Flags.Zero))
| (Z80Flags)Binary.ShiftRight(A, 7);
A = (byte)(Binary.ShiftLeft(A, 1) | carry);
F |= H35table[A];
}
///
/// Do RLC.
///
/// The register to operate on.
private void RLC(ref byte reg)
{
byte carry = Binary.ShiftRight(reg, 7);
reg = (byte)(Binary.ShiftLeft(reg, 1) | carry);
F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
}
///
/// Do RL.
///
/// The register to operate on.
private void RL(ref byte reg)
{
byte carry = Binary.ShiftRight(reg, 7);
reg = (byte)(Binary.ShiftLeft(reg, 1) | (int)(F & Z80Flags.Carry));
F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
}
///
/// Do SRL.
///
/// The register to operate on.
private void SRL(ref byte reg)
{
byte carry = (byte)(reg & 1);
reg = Binary.ShiftRight(reg, 1);
F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
}
///
/// Do SRA.
///
/// The register to operate on.
private void SRA(ref byte reg)
{
byte carry = (byte)(reg & 1);
reg = (byte)(Binary.ShiftRight(reg, 1) | (reg & 0x80));
F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
}
///
/// Do SLL.
///
/// The register to operate on.
private void SLL(ref byte reg)
{
byte carry = Binary.ShiftRight(reg, 7);
reg = (byte)(Binary.ShiftLeft(reg, 1) | 0x01);
F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
}
///
/// Do SLA.
///
/// The register to operate on.
private void SLA(ref byte reg)
{
byte carry = Binary.ShiftRight(reg, 7);
reg = Binary.ShiftLeft(reg, 1);
F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
}
#endregion
#region ALU boolean operations
///
/// AND a value with the accumulator.
///
/// The value.
void AND(byte val)
{
A &= val;
F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A];
}
///
/// OR a value with the accumulator.
///
/// The value.
void OR(byte val)
{
A |= val;
F = PSZtable[A] | H35table[A];
}
///
/// XOR a value with the accumulator.
///
/// The value.
void XOR(byte val)
{
A ^= val;
F = PSZtable[A] | H35table[A];
}
///
/// Perform the BIT operation.
///
/// The register to operate on.
/// The bit to test.
void BIT(ref byte reg, int bit)
{
F &= Z80Flags.Carry;
F |= Z80Flags.HalfCarry | H35table[reg];
if ((reg & (1 << bit)) != 0)
{
if (bit == 7 && (reg & (int)Z80Flags.Sign) != 0)
{
F |= Z80Flags.Sign;
}
}
else
{
F |= Z80Flags.Zero | Z80Flags.PV;
}
}
///
/// Perform the BIT operation on an indexed location.
///
/// The register to operate on.
/// The bit to test.
/// The address being checked.
void BIT(ref byte reg, int bit, ushort addr)
{
F &= Z80Flags.Carry;
F |= Z80Flags.HalfCarry | H35table[addr>>8];
if ((reg & (1 << bit)) != 0)
{
if (bit == 7 && (reg & (int)Z80Flags.Sign) != 0)
{
F |= Z80Flags.Sign;
}
}
else
{
F |= Z80Flags.Zero | Z80Flags.PV;
}
}
///
/// Perform the bit set operation.
///
/// The register to operate on.
/// The bit to test.
void BIT_SET(ref byte reg, int bit)
{
reg |= (byte)(1 << bit);
}
///
/// Perform the bit clear operation.
///
/// The register to operate on.
/// The bit to test.
void BIT_RES(ref byte reg, int bit)
{
reg &= (byte)~(1 << bit);
}
#endregion
#region Jump operations
///
/// The call operation.
///
private void CALL()
{
PUSH((ushort)(PC + 2));
PC = FetchWord();
}
///
/// The jump operation.
///
private void JP()
{
PC = FetchWord();
}
///
/// The jump relative operation.
///
private void JR()
{
PC = (ushort)(PC + (sbyte)memory.Read(PC) + 1);
}
///
/// Jump relative if the passed condition flag ANDed with the flag
/// register equals the passed check value.
///
/// The condition flag.
/// The check value.
private void JR_COND(Z80Flags cond, Z80Flags val)
{
if ((F & cond) == val)
{
clock.Add(12);
JR();
}
else
{
clock.Add(7);
PC++;
}
}
///
/// Jump if the passed condition flag ANDed with the flag
/// register equals the passed check value.
///
/// The condition flag.
/// The check value.
private void JP_COND(Z80Flags cond, Z80Flags val)
{
clock.Add(10);
if ((F & cond) == val)
{
JP();
}
else
{
PC+=2;
}
}
///
/// Call if the passed condition flag ANDed with the flag
/// register equals the passed check value.
///
/// The condition flag.
/// The check value.
private void CALL_COND(Z80Flags cond, Z80Flags val)
{
if ((F & cond) == val)
{
clock.Add(17);
CALL();
}
else
{
clock.Add(10);
PC+=2;
}
}
///
/// Return if the passed condition flag ANDed with the flag
/// register equals the passed check value.
///
/// The condition flag.
/// The check value.
private void RET_COND(Z80Flags cond, Z80Flags val)
{
if ((F & cond) == val)
{
clock.Add(11);
PC = POP();
}
else
{
clock.Add(5);
}
}
///
/// Reset the PC to an address.
///
/// The address.
private void RST(ushort addr)
{
clock.Add(11);
PUSH(PC);
PC = addr;
}
#endregion
#region Block operations
///
/// LDI instruction.
///
private void LDI()
{
byte b = memory.Read(HL.reg);
memory.Write(DE.reg, b);
DE.reg++;
HL.reg++;
BC.reg--;
ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg | Z80Flags.Hidden | Z80Flags.PV);
if (BC.reg != 0)
{
SetFlag(Z80Flags.PV);
}
b += A;
if ((b & 2) == 2)
{
F |= Z80Flags.Hidden5;
}
F |= (Z80Flags)(b & 8);
}
///
/// LDD instruction.
///
private void LDD()
{
byte b = memory.Read(HL.reg);
memory.Write(DE.reg, b);
DE.reg--;
HL.reg--;
BC.reg--;
ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg | Z80Flags.Hidden | Z80Flags.PV);
if (BC.reg != 0)
{
SetFlag(Z80Flags.PV);
}
b += A;
if ((b & 2) == 2)
{
F |= Z80Flags.Hidden5;
}
F |= (Z80Flags)(b & 8);
}
///
/// CPI instruction.
///
private void CPI()
{
byte b = memory.Read(HL.reg);
byte tmp = (byte)(A - b);
int w = A - b;
if (w < 0)
{
w += 0x200;
}
int lookup = ((A & 0x08) >> 3) | ((b & 0x08) >> 2) | ((tmp & 0x08) >> 1);
F = (F & Z80Flags.Carry) | SZtable[w] | Z80Flags.Neg | halfcarry_sub_table[lookup];
HL.reg++;
BC.reg--;
if (BC.reg != 0)
{
SetFlag(Z80Flags.PV);
}
else
{
ClearFlag(Z80Flags.PV);
}
if ((F & Z80Flags.HalfCarry) == Z80Flags.HalfCarry)
{
tmp--;
}
if ((tmp & 2) == 2)
{
F |= Z80Flags.Hidden5;
}
F |= (Z80Flags)(tmp & 8);
}
///
/// CPD instruction.
///
private void CPD()
{
byte b = memory.Read(HL.reg);
byte tmp = (byte)(A - b);
int w = A - b;
if (w < 0)
{
w += 0x200;
}
int lookup = ((A & 0x08) >> 3) | ((b & 0x08) >> 2) | ((tmp & 0x08) >> 1);
F = (F & Z80Flags.Carry) | SZtable[w] | Z80Flags.Neg | halfcarry_sub_table[lookup];
HL.reg--;
BC.reg--;
if (BC.reg != 0)
{
SetFlag(Z80Flags.PV);
}
else
{
ClearFlag(Z80Flags.PV);
}
if ((F & Z80Flags.HalfCarry) == Z80Flags.HalfCarry)
{
tmp--;
}
if ((tmp & 2) == 2)
{
F |= Z80Flags.Hidden5;
}
F |= (Z80Flags)(tmp & 8);
}
///
/// INI instruction.
///
private void INI()
{
byte w;
byte b = device.Read(BC.reg);
memory.Write(HL.reg, b);
BC.high--;
HL.reg++;
w = (byte)(BC.low + b + 1);
F = SZtable[BC.high] | H35table[BC.high] | Ptable[(w & 0x07) ^ BC.high];
if ((b & 0x80) == 0x80)
{
SetFlag(Z80Flags.Neg);
}
if (w < b)
{
SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
}
}
///
/// IND instruction.
///
private void IND()
{
byte w;
byte b = device.Read(BC.reg);
memory.Write(HL.reg, b);
BC.high--;
HL.reg--;
w = (byte)(BC.low + b - 1);
F = SZtable[BC.high] | H35table[BC.high] | Ptable[(w & 0x07) ^ BC.high];
if ((b & 0x80) == 0x80)
{
SetFlag(Z80Flags.Neg);
}
if (w < b)
{
SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
}
}
///
/// OUTI instruction.
///
private void OUTI()
{
BC.high--;
byte b = memory.Read(HL.reg);
byte w = b;
device.Write(BC.reg, b);
HL.reg++;
w += HL.low;
F = SZtable[BC.high] | H35table[BC.high] | Ptable[(w & 0x07) ^ BC.high];
if ((b & 0x80) == 0x80)
{
SetFlag(Z80Flags.Neg);
}
if (w < b)
{
SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
}
}
///
/// OUTD instruction.
///
private void OUTD()
{
BC.high--;
byte b = memory.Read(HL.reg);
byte w = b;
device.Write(BC.reg, b);
HL.reg--;
w += HL.low;
F = SZtable[BC.high] | H35table[BC.high] | Ptable[(w & 0x07) ^ BC.high];
if ((b & 0x80) == 0x80)
{
SetFlag(Z80Flags.Neg);
}
if (w < b)
{
SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
}
}
#endregion
}
}