1505 lines
38 KiB
C#
1505 lines
38 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using System.Threading.Tasks;
|
|||
|
|
|||
|
using Essgee.Exceptions;
|
|||
|
using Essgee.Utilities;
|
|||
|
|
|||
|
using static Essgee.Emulation.Utilities;
|
|||
|
|
|||
|
namespace Essgee.Emulation.CPU
|
|||
|
{
|
|||
|
public partial class Z80A : ICPU
|
|||
|
{
|
|||
|
[Flags]
|
|||
|
enum Flags : byte
|
|||
|
{
|
|||
|
Carry = (1 << 0), /* C */
|
|||
|
Subtract = (1 << 1), /* N */
|
|||
|
ParityOrOverflow = (1 << 2), /* P */
|
|||
|
UnusedBitX = (1 << 3), /* (X) */
|
|||
|
HalfCarry = (1 << 4), /* H */
|
|||
|
UnusedBitY = (1 << 5), /* (Y) */
|
|||
|
Zero = (1 << 6), /* Z */
|
|||
|
Sign = (1 << 7) /* S */
|
|||
|
}
|
|||
|
|
|||
|
public delegate byte MemoryReadDelegate(ushort address);
|
|||
|
public delegate void MemoryWriteDelegate(ushort address, byte value);
|
|||
|
public delegate byte PortReadDelegate(byte port);
|
|||
|
public delegate void PortWriteDelegate(byte port, byte value);
|
|||
|
|
|||
|
delegate void SimpleOpcodeDelegate(Z80A c);
|
|||
|
delegate void DDFDOpcodeDelegate(Z80A c, ref Register register);
|
|||
|
delegate void DDFDCBOpcodeDelegate(Z80A c, ref Register register, ushort address);
|
|||
|
|
|||
|
MemoryReadDelegate memoryReadDelegate;
|
|||
|
MemoryWriteDelegate memoryWriteDelegate;
|
|||
|
PortReadDelegate portReadDelegate;
|
|||
|
PortWriteDelegate portWriteDelegate;
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
protected Register af, bc, de, hl;
|
|||
|
[StateRequired]
|
|||
|
protected Register af_, bc_, de_, hl_;
|
|||
|
[StateRequired]
|
|||
|
protected Register ix, iy;
|
|||
|
[StateRequired]
|
|||
|
protected byte i, r;
|
|||
|
[StateRequired]
|
|||
|
protected ushort sp, pc;
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
protected bool iff1, iff2, eiDelay, halt;
|
|||
|
[StateRequired]
|
|||
|
protected byte im;
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
protected byte op;
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
InterruptState intState, nmiState;
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
int currentCycles;
|
|||
|
|
|||
|
public Z80A(MemoryReadDelegate memoryRead, MemoryWriteDelegate memoryWrite, PortReadDelegate portRead, PortWriteDelegate portWrite)
|
|||
|
{
|
|||
|
af = bc = de = hl = new Register();
|
|||
|
af_ = bc_ = de_ = hl_ = new Register();
|
|||
|
ix = iy = new Register();
|
|||
|
|
|||
|
memoryReadDelegate = memoryRead;
|
|||
|
memoryWriteDelegate = memoryWrite;
|
|||
|
portReadDelegate = portRead;
|
|||
|
portWriteDelegate = portWrite;
|
|||
|
}
|
|||
|
|
|||
|
public virtual void Startup()
|
|||
|
{
|
|||
|
Reset();
|
|||
|
|
|||
|
if (memoryReadDelegate == null) throw new EmulationException("Z80A: Memory read method is null");
|
|||
|
if (memoryWriteDelegate == null) throw new EmulationException("Z80A: Memory write method is null");
|
|||
|
if (portReadDelegate == null) throw new EmulationException("Z80A: Port read method is null");
|
|||
|
if (portWriteDelegate == null) throw new EmulationException("Z80A: Port write method is null");
|
|||
|
}
|
|||
|
|
|||
|
public virtual void Shutdown()
|
|||
|
{
|
|||
|
//
|
|||
|
}
|
|||
|
|
|||
|
public virtual void Reset()
|
|||
|
{
|
|||
|
af.Word = bc.Word = de.Word = hl.Word = 0;
|
|||
|
af_.Word = bc_.Word = de_.Word = hl_.Word = 0;
|
|||
|
ix.Word = iy.Word = 0;
|
|||
|
i = r = 0;
|
|||
|
pc = 0;
|
|||
|
sp = 0;
|
|||
|
|
|||
|
iff1 = iff2 = eiDelay = halt = false;
|
|||
|
im = 0;
|
|||
|
|
|||
|
intState = nmiState = InterruptState.Clear;
|
|||
|
|
|||
|
currentCycles = 0;
|
|||
|
}
|
|||
|
|
|||
|
public int Step()
|
|||
|
{
|
|||
|
currentCycles = 0;
|
|||
|
|
|||
|
/* Handle delayed interrupt enable */
|
|||
|
if (eiDelay)
|
|||
|
{
|
|||
|
eiDelay = false;
|
|||
|
iff1 = iff2 = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Check INT line */
|
|||
|
if (intState == InterruptState.Assert)
|
|||
|
{
|
|||
|
ServiceInterrupt();
|
|||
|
}
|
|||
|
|
|||
|
/* Check NMI line */
|
|||
|
if (nmiState == InterruptState.Assert)
|
|||
|
{
|
|||
|
nmiState = InterruptState.Clear;
|
|||
|
ServiceNonMaskableInterrupt();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (AppEnvironment.EnableSuperSlowCPULogger)
|
|||
|
{
|
|||
|
string disasm = string.Format("{0} | {1} | {2} | {3}\n", DisassembleOpcode(this, pc).PadRight(48), PrintRegisters(this), PrintFlags(this), PrintInterrupt(this));
|
|||
|
System.IO.File.AppendAllText(@"D:\Temp\Essgee\log.txt", disasm);
|
|||
|
}
|
|||
|
|
|||
|
/* Fetch and execute opcode */
|
|||
|
op = ReadMemory8(pc++);
|
|||
|
switch (op)
|
|||
|
{
|
|||
|
case 0xCB: ExecuteOpCB(); break;
|
|||
|
case 0xDD: ExecuteOpDD(); break;
|
|||
|
case 0xED: ExecuteOpED(); break;
|
|||
|
case 0xFD: ExecuteOpFD(); break;
|
|||
|
default: ExecuteOpcodeNoPrefix(op); break;
|
|||
|
}
|
|||
|
|
|||
|
return currentCycles;
|
|||
|
}
|
|||
|
|
|||
|
#region Opcode Execution and Cycle Management
|
|||
|
|
|||
|
private void ExecuteOpcodeNoPrefix(byte op)
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
opcodesNoPrefix[op](this);
|
|||
|
|
|||
|
currentCycles += CycleCounts.NoPrefix[op];
|
|||
|
}
|
|||
|
|
|||
|
private void ExecuteOpED()
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
byte edOp = ReadMemory8(pc++);
|
|||
|
|
|||
|
IncrementRefresh();
|
|||
|
opcodesPrefixED[edOp](this);
|
|||
|
|
|||
|
currentCycles += CycleCounts.PrefixED[edOp];
|
|||
|
}
|
|||
|
|
|||
|
private void ExecuteOpCB()
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
byte cbOp = ReadMemory8(pc++);
|
|||
|
|
|||
|
IncrementRefresh();
|
|||
|
opcodesPrefixCB[cbOp](this);
|
|||
|
|
|||
|
currentCycles += CycleCounts.PrefixCB[cbOp];
|
|||
|
}
|
|||
|
|
|||
|
private void ExecuteOpDD()
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
byte ddOp = ReadMemory8(pc++);
|
|||
|
|
|||
|
if (ddOp != 0xDD)
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
opcodesPrefixDDFD[ddOp](this, ref ix);
|
|||
|
}
|
|||
|
|
|||
|
currentCycles += (CycleCounts.PrefixDDFD[ddOp] != 0 ? CycleCounts.PrefixDDFD[ddOp] : CycleCounts.NoPrefix[ddOp] + CycleCounts.AdditionalDDFDOps);
|
|||
|
}
|
|||
|
|
|||
|
private void ExecuteOpFD()
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
byte fdOp = ReadMemory8(pc++);
|
|||
|
|
|||
|
if (fdOp != 0xFD)
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
opcodesPrefixDDFD[fdOp](this, ref iy);
|
|||
|
}
|
|||
|
|
|||
|
currentCycles += (CycleCounts.PrefixDDFD[fdOp] != 0 ? CycleCounts.PrefixDDFD[fdOp] : CycleCounts.NoPrefix[fdOp] + CycleCounts.AdditionalDDFDOps);
|
|||
|
}
|
|||
|
|
|||
|
private void ExecuteOpDDFDCB(byte op, ref Register register)
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
sbyte operand = (sbyte)ReadMemory8(pc);
|
|||
|
ushort address = (ushort)(register.Word + operand);
|
|||
|
pc += 2;
|
|||
|
|
|||
|
IncrementRefresh();
|
|||
|
opcodesPrefixDDFDCB[op](this, ref register, address);
|
|||
|
|
|||
|
currentCycles += (CycleCounts.PrefixCB[op] + CycleCounts.AdditionalDDFDCBOps);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Helpers (Refresh Register, Flags, etc.)
|
|||
|
|
|||
|
public void SetStackPointer(ushort value)
|
|||
|
{
|
|||
|
sp = value;
|
|||
|
}
|
|||
|
|
|||
|
public void SetProgramCounter(ushort value)
|
|||
|
{
|
|||
|
pc = value;
|
|||
|
}
|
|||
|
|
|||
|
private void IncrementRefresh()
|
|||
|
{
|
|||
|
r = (byte)(((r + 1) & 0x7F) | (r & 0x80));
|
|||
|
}
|
|||
|
|
|||
|
private void SetFlag(Flags flags)
|
|||
|
{
|
|||
|
af.Low |= (byte)flags;
|
|||
|
}
|
|||
|
|
|||
|
private void ClearFlag(Flags flags)
|
|||
|
{
|
|||
|
af.Low &= (byte)~flags;
|
|||
|
}
|
|||
|
|
|||
|
private void SetClearFlagConditional(Flags flags, bool condition)
|
|||
|
{
|
|||
|
if (condition)
|
|||
|
af.Low |= (byte)flags;
|
|||
|
else
|
|||
|
af.Low &= (byte)~flags;
|
|||
|
}
|
|||
|
|
|||
|
private bool IsFlagSet(Flags flags)
|
|||
|
{
|
|||
|
return (((Flags)af.Low & flags) == flags);
|
|||
|
}
|
|||
|
|
|||
|
private void CalculateAndSetParity(byte value)
|
|||
|
{
|
|||
|
int bitsSet = 0;
|
|||
|
while (value != 0) { bitsSet += (value & 0x01); value >>= 1; }
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (bitsSet == 0 || (bitsSet % 2) == 0));
|
|||
|
}
|
|||
|
|
|||
|
private ushort CalculateIXIYAddress(Register register)
|
|||
|
{
|
|||
|
return (ushort)(register.Word + (sbyte)ReadMemory8(pc++));
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Interrupt and Halt State Handling
|
|||
|
|
|||
|
public void SetInterruptLine(InterruptType type, InterruptState state)
|
|||
|
{
|
|||
|
switch (type)
|
|||
|
{
|
|||
|
case InterruptType.Maskable:
|
|||
|
intState = state;
|
|||
|
break;
|
|||
|
|
|||
|
case InterruptType.NonMaskable:
|
|||
|
nmiState = state;
|
|||
|
break;
|
|||
|
|
|||
|
default: throw new EmulationException("Z80A: Unknown interrupt type");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ServiceInterrupt()
|
|||
|
{
|
|||
|
if (!iff1) return;
|
|||
|
|
|||
|
LeaveHaltState();
|
|||
|
iff1 = iff2 = false;
|
|||
|
|
|||
|
switch (im)
|
|||
|
{
|
|||
|
case 0x00:
|
|||
|
/* Execute opcode(s) from data bus */
|
|||
|
/* TODO: no real data bus emulation, just execute opcode 0xFF instead (Xenon 2 SMS, http://www.smspower.org/forums/1172-EmulatingInterrupts#5395) */
|
|||
|
ExecuteOpcodeNoPrefix(0xFF);
|
|||
|
currentCycles += 30;
|
|||
|
break;
|
|||
|
|
|||
|
case 0x01:
|
|||
|
/* Restart to location 0x0038, same as opcode 0xFF */
|
|||
|
ExecuteOpcodeNoPrefix(0xFF);
|
|||
|
currentCycles += 30;
|
|||
|
break;
|
|||
|
|
|||
|
case 0x02:
|
|||
|
/* Indirect call via I register */
|
|||
|
/* TODO: unsupported at the moment, not needed in currently emulated systems */
|
|||
|
IncrementRefresh();
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ServiceNonMaskableInterrupt()
|
|||
|
{
|
|||
|
IncrementRefresh();
|
|||
|
Restart(0x0066);
|
|||
|
|
|||
|
iff2 = iff1;
|
|||
|
iff1 = halt = false;
|
|||
|
|
|||
|
currentCycles += 11;
|
|||
|
}
|
|||
|
|
|||
|
private void EnterHaltState()
|
|||
|
{
|
|||
|
halt = true;
|
|||
|
pc--;
|
|||
|
}
|
|||
|
|
|||
|
private void LeaveHaltState()
|
|||
|
{
|
|||
|
if (halt)
|
|||
|
{
|
|||
|
halt = false;
|
|||
|
pc++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Memory and Port Access Functions
|
|||
|
|
|||
|
private byte ReadMemory8(ushort address)
|
|||
|
{
|
|||
|
return memoryReadDelegate(address);
|
|||
|
}
|
|||
|
|
|||
|
private void WriteMemory8(ushort address, byte value)
|
|||
|
{
|
|||
|
memoryWriteDelegate(address, value);
|
|||
|
}
|
|||
|
|
|||
|
private ushort ReadMemory16(ushort address)
|
|||
|
{
|
|||
|
return (ushort)((memoryReadDelegate((ushort)(address + 1)) << 8) | memoryReadDelegate(address));
|
|||
|
}
|
|||
|
|
|||
|
private void WriteMemory16(ushort address, ushort value)
|
|||
|
{
|
|||
|
memoryWriteDelegate(address, (byte)(value & 0xFF));
|
|||
|
memoryWriteDelegate((ushort)(address + 1), (byte)(value >> 8));
|
|||
|
}
|
|||
|
|
|||
|
private byte ReadPort(byte port)
|
|||
|
{
|
|||
|
return portReadDelegate(port);
|
|||
|
}
|
|||
|
|
|||
|
private void WritePort(byte port, byte value)
|
|||
|
{
|
|||
|
portWriteDelegate(port, value);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: 8-Bit Load Group
|
|||
|
|
|||
|
protected void LoadRegisterFromMemory8(ref byte register, ushort address, bool specialRegs)
|
|||
|
{
|
|||
|
LoadRegister8(ref register, ReadMemory8(address), specialRegs);
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadRegisterImmediate8(ref byte register, bool specialRegs)
|
|||
|
{
|
|||
|
LoadRegister8(ref register, ReadMemory8(pc++), specialRegs);
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadRegister8(ref byte register, byte value, bool specialRegs)
|
|||
|
{
|
|||
|
register = value;
|
|||
|
|
|||
|
// Register is I or R?
|
|||
|
if (specialRegs)
|
|||
|
{
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(register, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (register == 0x00));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (iff2));
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadMemory8(ushort address, byte value)
|
|||
|
{
|
|||
|
WriteMemory8(address, value);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: 16-Bit Load Group
|
|||
|
|
|||
|
protected void LoadRegisterImmediate16(ref ushort register)
|
|||
|
{
|
|||
|
LoadRegister16(ref register, ReadMemory16(pc));
|
|||
|
pc += 2;
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadRegister16(ref ushort register, ushort value)
|
|||
|
{
|
|||
|
register = value;
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadMemory16(ushort address, ushort value)
|
|||
|
{
|
|||
|
WriteMemory16(address, value);
|
|||
|
}
|
|||
|
|
|||
|
protected void Push(Register register)
|
|||
|
{
|
|||
|
WriteMemory8(--sp, register.High);
|
|||
|
WriteMemory8(--sp, register.Low);
|
|||
|
}
|
|||
|
|
|||
|
protected void Pop(ref Register register)
|
|||
|
{
|
|||
|
register.Low = ReadMemory8(sp++);
|
|||
|
register.High = ReadMemory8(sp++);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: Exchange, Block Transfer and Search Group
|
|||
|
|
|||
|
protected void ExchangeRegisters16(ref Register reg1, ref Register reg2)
|
|||
|
{
|
|||
|
ushort tmp = reg1.Word;
|
|||
|
reg1.Word = reg2.Word;
|
|||
|
reg2.Word = tmp;
|
|||
|
}
|
|||
|
|
|||
|
protected void ExchangeStackRegister16(ref Register reg)
|
|||
|
{
|
|||
|
byte sl = ReadMemory8(sp);
|
|||
|
byte sh = ReadMemory8((ushort)(sp + 1));
|
|||
|
|
|||
|
WriteMemory8(sp, reg.Low);
|
|||
|
WriteMemory8((ushort)(sp + 1), reg.High);
|
|||
|
|
|||
|
reg.Low = sl;
|
|||
|
reg.High = sh;
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadIncrement()
|
|||
|
{
|
|||
|
byte hlValue = ReadMemory8(hl.Word);
|
|||
|
WriteMemory8(de.Word, hlValue);
|
|||
|
Increment16(ref de.Word);
|
|||
|
Increment16(ref hl.Word);
|
|||
|
Decrement16(ref bc.Word);
|
|||
|
|
|||
|
byte n = (byte)(hlValue + af.High);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0));
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadIncrementRepeat()
|
|||
|
{
|
|||
|
LoadIncrement();
|
|||
|
|
|||
|
if (bc.Word != 0)
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadDecrement()
|
|||
|
{
|
|||
|
byte hlValue = ReadMemory8(hl.Word);
|
|||
|
WriteMemory8(de.Word, hlValue);
|
|||
|
Decrement16(ref de.Word);
|
|||
|
Decrement16(ref hl.Word);
|
|||
|
Decrement16(ref bc.Word);
|
|||
|
|
|||
|
byte n = (byte)(hlValue + af.High);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0));
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void LoadDecrementRepeat()
|
|||
|
{
|
|||
|
LoadDecrement();
|
|||
|
|
|||
|
if (bc.Word != 0)
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void CompareIncrement()
|
|||
|
{
|
|||
|
byte operand = ReadMemory8(hl.Word);
|
|||
|
int result = (af.High - (sbyte)operand);
|
|||
|
|
|||
|
hl.Word++;
|
|||
|
bc.Word--;
|
|||
|
|
|||
|
bool halfCarry = (((af.High ^ result ^ operand) & 0x10) != 0);
|
|||
|
byte n = (byte)(result - (halfCarry ? 1 : 0));
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (af.High == operand));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, halfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void CompareIncrementRepeat()
|
|||
|
{
|
|||
|
CompareIncrement();
|
|||
|
|
|||
|
if (bc.Word != 0 && !IsFlagSet(Flags.Zero))
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void CompareDecrement()
|
|||
|
{
|
|||
|
byte operand = ReadMemory8(hl.Word);
|
|||
|
int result = (af.High - (sbyte)operand);
|
|||
|
|
|||
|
hl.Word--;
|
|||
|
bc.Word--;
|
|||
|
|
|||
|
bool halfCarry = (((af.High ^ result ^ operand) & 0x10) != 0);
|
|||
|
byte n = (byte)(result - (halfCarry ? 1 : 0));
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (af.High == operand));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, halfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void CompareDecrementRepeat()
|
|||
|
{
|
|||
|
CompareDecrement();
|
|||
|
|
|||
|
if (bc.Word != 0 && !IsFlagSet(Flags.Zero))
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: 8-Bit Arithmetic Group
|
|||
|
|
|||
|
protected void Add8(byte operand, bool withCarry)
|
|||
|
{
|
|||
|
int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0));
|
|||
|
int result = (af.High + operandWithCarry);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ af.High ^ 0x80) & (af.High ^ result) & 0x80) != 0));
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, (result > 0xFF));
|
|||
|
|
|||
|
af.High = (byte)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Subtract8(byte operand, bool withCarry)
|
|||
|
{
|
|||
|
int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0));
|
|||
|
int result = (af.High - operandWithCarry);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ af.High) & (af.High ^ result) & 0x80) != 0));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, (af.High < operandWithCarry));
|
|||
|
|
|||
|
af.High = (byte)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void And8(byte operand)
|
|||
|
{
|
|||
|
int result = (af.High & operand);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5));
|
|||
|
SetFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3));
|
|||
|
CalculateAndSetParity((byte)result);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
ClearFlag(Flags.Carry);
|
|||
|
|
|||
|
af.High = (byte)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Or8(byte operand)
|
|||
|
{
|
|||
|
int result = (af.High | operand);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3));
|
|||
|
CalculateAndSetParity((byte)result);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
ClearFlag(Flags.Carry);
|
|||
|
|
|||
|
af.High = (byte)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Xor8(byte operand)
|
|||
|
{
|
|||
|
int result = (af.High ^ operand);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3));
|
|||
|
CalculateAndSetParity((byte)result);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
ClearFlag(Flags.Carry);
|
|||
|
|
|||
|
af.High = (byte)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Cp8(byte operand)
|
|||
|
{
|
|||
|
int result = (af.High - operand);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(operand, 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(operand, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ af.High) & (af.High ^ result) & 0x80) != 0));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, (af.High < operand));
|
|||
|
}
|
|||
|
|
|||
|
protected void Increment8(ref byte register)
|
|||
|
{
|
|||
|
byte result = (byte)(register + 1);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (result == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(result, 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x0F));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(result, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (register == 0x7F));
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
|
|||
|
register = result;
|
|||
|
}
|
|||
|
|
|||
|
protected void IncrementMemory8(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
Increment8(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
}
|
|||
|
|
|||
|
protected void Decrement8(ref byte register)
|
|||
|
{
|
|||
|
byte result = (byte)(register - 1);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (result == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(result, 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(result, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (register == 0x80));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
|
|||
|
register = result;
|
|||
|
}
|
|||
|
|
|||
|
protected void DecrementMemory8(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
Decrement8(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: General-Purpose Arithmetic and CPU Control Group
|
|||
|
|
|||
|
protected void DecimalAdjustAccumulator()
|
|||
|
{
|
|||
|
/* "The Undocumented Z80 Documented" by Sean Young, chapter 4.7, http://www.z80.info/zip/z80-documented.pdf */
|
|||
|
|
|||
|
byte before = af.High, diff = 0x00, result;
|
|||
|
bool carry = IsFlagSet(Flags.Carry), halfCarry = IsFlagSet(Flags.HalfCarry);
|
|||
|
byte highNibble = (byte)((before & 0xF0) >> 4), lowNibble = (byte)(before & 0x0F);
|
|||
|
|
|||
|
if (carry)
|
|||
|
{
|
|||
|
diff |= 0x60;
|
|||
|
if ((halfCarry && lowNibble <= 0x09) || lowNibble >= 0x0A)
|
|||
|
diff |= 0x06;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (lowNibble >= 0x0A && lowNibble <= 0x0F)
|
|||
|
{
|
|||
|
diff |= 0x06;
|
|||
|
if (highNibble >= 0x09 && highNibble <= 0x0F)
|
|||
|
diff |= 0x60;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (highNibble >= 0x0A && highNibble <= 0x0F)
|
|||
|
diff |= 0x60;
|
|||
|
if (halfCarry)
|
|||
|
diff |= 0x06;
|
|||
|
}
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Carry, (
|
|||
|
((highNibble >= 0x09 && highNibble <= 0x0F) && (lowNibble >= 0x0A && lowNibble <= 0x0F)) ||
|
|||
|
((highNibble >= 0x0A && highNibble <= 0x0F) && (lowNibble >= 0x00 && lowNibble <= 0x09))));
|
|||
|
}
|
|||
|
|
|||
|
if (!IsFlagSet(Flags.Subtract))
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, (lowNibble >= 0x0A && lowNibble <= 0x0F));
|
|||
|
else
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, (halfCarry && (lowNibble >= 0x00 && lowNibble <= 0x05)));
|
|||
|
|
|||
|
if (!IsFlagSet(Flags.Subtract))
|
|||
|
result = (byte)(before + diff);
|
|||
|
else
|
|||
|
result = (byte)(before - diff);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(result, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (result == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(result, 5));
|
|||
|
// H (set above)
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(result, 3));
|
|||
|
CalculateAndSetParity(result);
|
|||
|
// N
|
|||
|
// C (set above)
|
|||
|
|
|||
|
af.High = result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Negate()
|
|||
|
{
|
|||
|
int result = (0 - af.High);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, ((result & 0xFF) >= 0x80));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(result & 0xFF), 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, ((0 - (af.High & 0x0F)) < 0));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(result & 0xFF), 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (af.High == 0x80));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, (af.High != 0x00));
|
|||
|
|
|||
|
af.High = (byte)result;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: 16-Bit Arithmetic Group
|
|||
|
|
|||
|
protected void Add16(ref Register dest, ushort operand, bool withCarry)
|
|||
|
{
|
|||
|
int operandWithCarry = ((short)operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0));
|
|||
|
int result = (dest.Word + operandWithCarry);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(result >> 8), 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, (((dest.Word & 0x0FFF) + (operandWithCarry & 0x0FFF)) > 0x0FFF));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(result >> 8), 3));
|
|||
|
// PV
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, (((dest.Word & 0xFFFF) + (operandWithCarry & 0xFFFF)) > 0xFFFF));
|
|||
|
|
|||
|
if (withCarry)
|
|||
|
{
|
|||
|
SetClearFlagConditional(Flags.Sign, ((result & 0x8000) != 0x0000));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFFFF) == 0x0000));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (((dest.Word ^ operandWithCarry) & 0x8000) == 0 && ((dest.Word ^ (result & 0xFFFF)) & 0x8000) != 0));
|
|||
|
}
|
|||
|
|
|||
|
dest.Word = (ushort)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Subtract16(ref Register dest, ushort operand, bool withCarry)
|
|||
|
{
|
|||
|
int result = (dest.Word - operand - (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0));
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, ((result & 0x8000) != 0x0000));
|
|||
|
SetClearFlagConditional(Flags.Zero, ((result & 0xFFFF) == 0x0000));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(result >> 8), 5));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, ((((dest.Word ^ result ^ operand) >> 8) & 0x10) != 0));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(result >> 8), 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ dest.Word) & (dest.Word ^ result) & 0x8000) != 0));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, ((result & 0x10000) != 0));
|
|||
|
|
|||
|
dest.Word = (ushort)result;
|
|||
|
}
|
|||
|
|
|||
|
protected void Increment16(ref ushort register)
|
|||
|
{
|
|||
|
register++;
|
|||
|
}
|
|||
|
|
|||
|
protected void Decrement16(ref ushort register)
|
|||
|
{
|
|||
|
register--;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: Rotate and Shift Group
|
|||
|
|
|||
|
protected byte RotateLeft(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
RotateLeft(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateLeft(ref byte value)
|
|||
|
{
|
|||
|
bool isCarrySet = IsFlagSet(Flags.Carry);
|
|||
|
bool isMsbSet = IsBitSet(value, 7);
|
|||
|
value <<= 1;
|
|||
|
if (isCarrySet) SetBit(ref value, 0);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isMsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected byte RotateLeftCircular(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
RotateLeftCircular(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateLeftCircular(ref byte value)
|
|||
|
{
|
|||
|
bool isMsbSet = IsBitSet(value, 7);
|
|||
|
value <<= 1;
|
|||
|
if (isMsbSet) SetBit(ref value, 0);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isMsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected byte RotateRight(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
RotateRight(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateRight(ref byte value)
|
|||
|
{
|
|||
|
bool isCarrySet = IsFlagSet(Flags.Carry);
|
|||
|
bool isLsbSet = IsBitSet(value, 0);
|
|||
|
value >>= 1;
|
|||
|
if (isCarrySet) SetBit(ref value, 7);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isLsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected byte RotateRightCircular(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
RotateRightCircular(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateRightCircular(ref byte value)
|
|||
|
{
|
|||
|
bool isLsbSet = IsBitSet(value, 0);
|
|||
|
value >>= 1;
|
|||
|
if (isLsbSet) SetBit(ref value, 7);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isLsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateLeftAccumulator()
|
|||
|
{
|
|||
|
bool isCarrySet = IsFlagSet(Flags.Carry);
|
|||
|
bool isMsbSet = IsBitSet(af.High, 7);
|
|||
|
af.High <<= 1;
|
|||
|
if (isCarrySet) SetBit(ref af.High, 0);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3));
|
|||
|
// PV
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isMsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateLeftAccumulatorCircular()
|
|||
|
{
|
|||
|
bool isMsbSet = IsBitSet(af.High, 7);
|
|||
|
af.High <<= 1;
|
|||
|
if (isMsbSet) SetBit(ref af.High, 0);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3));
|
|||
|
// PV
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isMsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateRightAccumulator()
|
|||
|
{
|
|||
|
bool isCarrySet = IsFlagSet(Flags.Carry);
|
|||
|
bool isLsbSet = IsBitSet(af.High, 0);
|
|||
|
af.High >>= 1;
|
|||
|
if (isCarrySet) SetBit(ref af.High, 7);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3));
|
|||
|
// PV
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isLsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateRightAccumulatorCircular()
|
|||
|
{
|
|||
|
bool isLsbSet = IsBitSet(af.High, 0);
|
|||
|
af.High >>= 1;
|
|||
|
if (isLsbSet) SetBit(ref af.High, 7);
|
|||
|
|
|||
|
// S
|
|||
|
// Z
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3));
|
|||
|
// PV
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isLsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateRight4B()
|
|||
|
{
|
|||
|
byte hlValue = ReadMemory8(hl.Word);
|
|||
|
|
|||
|
// A=WX (HL)=YZ
|
|||
|
// A=WZ (HL)=XY
|
|||
|
byte a1 = (byte)(af.High >> 4); //W
|
|||
|
byte a2 = (byte)(af.High & 0xF); //X
|
|||
|
byte hl1 = (byte)(hlValue >> 4); //Y
|
|||
|
byte hl2 = (byte)(hlValue & 0xF); //Z
|
|||
|
|
|||
|
af.High = (byte)((a1 << 4) | hl2);
|
|||
|
hlValue = (byte)((a2 << 4) | hl1);
|
|||
|
|
|||
|
WriteMemory8(hl.Word, hlValue);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(af.High, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (af.High == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3));
|
|||
|
CalculateAndSetParity(af.High);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void RotateLeft4B()
|
|||
|
{
|
|||
|
byte hlValue = ReadMemory8(hl.Word);
|
|||
|
|
|||
|
// A=WX (HL)=YZ
|
|||
|
// A=WY (HL)=ZX
|
|||
|
byte a1 = (byte)(af.High >> 4); //W
|
|||
|
byte a2 = (byte)(af.High & 0xF); //X
|
|||
|
byte hl1 = (byte)(hlValue >> 4); //Y
|
|||
|
byte hl2 = (byte)(hlValue & 0xF); //Z
|
|||
|
|
|||
|
af.High = (byte)((a1 << 4) | hl1);
|
|||
|
hlValue = (byte)((hl2 << 4) | a2);
|
|||
|
|
|||
|
WriteMemory8(hl.Word, hlValue);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(af.High, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (af.High == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3));
|
|||
|
CalculateAndSetParity(af.High);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected byte ShiftLeftArithmetic(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
ShiftLeftArithmetic(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void ShiftLeftArithmetic(ref byte value)
|
|||
|
{
|
|||
|
bool isMsbSet = IsBitSet(value, 7);
|
|||
|
value <<= 1;
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isMsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected byte ShiftRightArithmetic(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
ShiftRightArithmetic(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void ShiftRightArithmetic(ref byte value)
|
|||
|
{
|
|||
|
bool isLsbSet = IsBitSet(value, 0);
|
|||
|
bool isMsbSet = IsBitSet(value, 7);
|
|||
|
value >>= 1;
|
|||
|
if (isMsbSet) SetBit(ref value, 7);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isLsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected byte ShiftLeftLogical(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
ShiftLeftLogical(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void ShiftLeftLogical(ref byte value)
|
|||
|
{
|
|||
|
bool isMsbSet = IsBitSet(value, 7);
|
|||
|
value <<= 1;
|
|||
|
value |= 0x01;
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isMsbSet);
|
|||
|
}
|
|||
|
|
|||
|
protected byte ShiftRightLogical(ushort address)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
ShiftRightLogical(ref value);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void ShiftRightLogical(ref byte value)
|
|||
|
{
|
|||
|
bool isLsbSet = IsBitSet(value, 0);
|
|||
|
value >>= 1;
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (value == 0x00));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
CalculateAndSetParity(value);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, isLsbSet);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: Bit Set, Reset and Test Group
|
|||
|
|
|||
|
protected byte SetBit(ushort address, int bit)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
SetBit(ref value, bit);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void SetBit(ref byte value, int bit)
|
|||
|
{
|
|||
|
value |= (byte)(1 << bit);
|
|||
|
}
|
|||
|
|
|||
|
protected byte ResetBit(ushort address, int bit)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
ResetBit(ref value, bit);
|
|||
|
WriteMemory8(address, value);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
protected void ResetBit(ref byte value, int bit)
|
|||
|
{
|
|||
|
value &= (byte)~(1 << bit);
|
|||
|
}
|
|||
|
|
|||
|
protected void TestBit(ushort address, int bit)
|
|||
|
{
|
|||
|
byte value = ReadMemory8(address);
|
|||
|
|
|||
|
TestBit(value, bit);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(address >> 8), 5));
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(address >> 8), 3));
|
|||
|
}
|
|||
|
|
|||
|
protected void TestBit(byte value, int bit)
|
|||
|
{
|
|||
|
bool isBitSet = ((value & (1 << bit)) != 0);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, (bit == 7 && isBitSet));
|
|||
|
SetClearFlagConditional(Flags.Zero, !isBitSet);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5));
|
|||
|
SetFlag(Flags.HalfCarry);
|
|||
|
SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3));
|
|||
|
SetClearFlagConditional(Flags.ParityOrOverflow, !isBitSet);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: Jump Group
|
|||
|
|
|||
|
protected void DecrementJumpNonZero()
|
|||
|
{
|
|||
|
bc.High--;
|
|||
|
JumpConditional8(bc.High != 0);
|
|||
|
}
|
|||
|
|
|||
|
protected void Jump8()
|
|||
|
{
|
|||
|
pc += (ushort)(((sbyte)ReadMemory8(pc)) + 1);
|
|||
|
}
|
|||
|
|
|||
|
protected void JumpConditional8(bool condition)
|
|||
|
{
|
|||
|
if (condition)
|
|||
|
{
|
|||
|
Jump8();
|
|||
|
currentCycles += CycleCounts.AdditionalJumpCond8Taken;
|
|||
|
}
|
|||
|
else
|
|||
|
pc++;
|
|||
|
}
|
|||
|
|
|||
|
protected void JumpConditional16(bool condition)
|
|||
|
{
|
|||
|
if (condition)
|
|||
|
pc = ReadMemory16(pc);
|
|||
|
else
|
|||
|
pc += 2;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: Call and Return Group
|
|||
|
|
|||
|
protected void Call16()
|
|||
|
{
|
|||
|
WriteMemory8(--sp, (byte)((pc + 2) >> 8));
|
|||
|
WriteMemory8(--sp, (byte)((pc + 2) & 0xFF));
|
|||
|
pc = ReadMemory16(pc);
|
|||
|
}
|
|||
|
|
|||
|
protected void CallConditional16(bool condition)
|
|||
|
{
|
|||
|
if (condition)
|
|||
|
{
|
|||
|
Call16();
|
|||
|
currentCycles += CycleCounts.AdditionalCallCondTaken;
|
|||
|
}
|
|||
|
else
|
|||
|
pc += 2;
|
|||
|
}
|
|||
|
|
|||
|
protected void Return()
|
|||
|
{
|
|||
|
pc = ReadMemory16(sp);
|
|||
|
sp += 2;
|
|||
|
}
|
|||
|
|
|||
|
protected void ReturnConditional(bool condition)
|
|||
|
{
|
|||
|
if (condition)
|
|||
|
{
|
|||
|
Return();
|
|||
|
currentCycles += CycleCounts.AdditionalRetCondTaken;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void Restart(ushort address)
|
|||
|
{
|
|||
|
WriteMemory8(--sp, (byte)(pc >> 8));
|
|||
|
WriteMemory8(--sp, (byte)(pc & 0xFF));
|
|||
|
pc = address;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Opcodes: Input and Output Group
|
|||
|
|
|||
|
protected void PortInput(ref byte dest, byte port)
|
|||
|
{
|
|||
|
dest = ReadPort(port);
|
|||
|
|
|||
|
SetClearFlagConditional(Flags.Sign, IsBitSet(dest, 7));
|
|||
|
SetClearFlagConditional(Flags.Zero, (dest == 0x00));
|
|||
|
ClearFlag(Flags.HalfCarry);
|
|||
|
CalculateAndSetParity(dest);
|
|||
|
ClearFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void PortInputFlagsOnly(byte port)
|
|||
|
{
|
|||
|
byte temp = 0;
|
|||
|
|
|||
|
PortInput(ref temp, port);
|
|||
|
}
|
|||
|
|
|||
|
protected void PortInputIncrement()
|
|||
|
{
|
|||
|
WriteMemory8(hl.Word, ReadPort(bc.Low));
|
|||
|
Increment16(ref hl.Word);
|
|||
|
Decrement8(ref bc.High);
|
|||
|
|
|||
|
// S
|
|||
|
SetClearFlagConditional(Flags.Zero, (bc.High == 0x00));
|
|||
|
// H
|
|||
|
// PV
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void PortInputIncrementRepeat()
|
|||
|
{
|
|||
|
PortInputIncrement();
|
|||
|
|
|||
|
if (bc.High != 0)
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// S
|
|||
|
SetFlag(Flags.Zero);
|
|||
|
// H
|
|||
|
// PV
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void PortInputDecrement()
|
|||
|
{
|
|||
|
WriteMemory8(hl.Word, ReadPort(bc.Low));
|
|||
|
Decrement16(ref hl.Word);
|
|||
|
Decrement8(ref bc.High);
|
|||
|
|
|||
|
// S
|
|||
|
SetClearFlagConditional(Flags.Zero, (bc.High == 0x00));
|
|||
|
// H
|
|||
|
// PV
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
|
|||
|
protected void PortInputDecrementRepeat()
|
|||
|
{
|
|||
|
PortInputDecrement();
|
|||
|
|
|||
|
if (bc.High != 0)
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// S
|
|||
|
SetFlag(Flags.Zero);
|
|||
|
// H
|
|||
|
// PV
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void PortOutputIncrement()
|
|||
|
{
|
|||
|
byte value = ReadMemory8(hl.Word);
|
|||
|
WritePort(bc.Low, value);
|
|||
|
Increment16(ref hl.Word);
|
|||
|
Decrement8(ref bc.High);
|
|||
|
|
|||
|
bool setHC = ((value + hl.Low) > 255);
|
|||
|
|
|||
|
// S
|
|||
|
SetClearFlagConditional(Flags.Zero, (bc.High == 0x00));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, setHC);
|
|||
|
CalculateAndSetParity((byte)(((value + hl.Low) & 0x07) ^ bc.High));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, setHC);
|
|||
|
}
|
|||
|
|
|||
|
protected void PortOutputIncrementRepeat()
|
|||
|
{
|
|||
|
PortOutputIncrement();
|
|||
|
|
|||
|
if (bc.High != 0)
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// S
|
|||
|
SetFlag(Flags.Zero);
|
|||
|
// H
|
|||
|
// PV
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void PortOutputDecrement()
|
|||
|
{
|
|||
|
byte value = ReadMemory8(hl.Word);
|
|||
|
WritePort(bc.Low, value);
|
|||
|
Decrement16(ref hl.Word);
|
|||
|
Decrement8(ref bc.High);
|
|||
|
|
|||
|
bool setHC = ((value + hl.Low) > 255);
|
|||
|
|
|||
|
// S
|
|||
|
SetClearFlagConditional(Flags.Zero, (bc.High == 0x00));
|
|||
|
SetClearFlagConditional(Flags.HalfCarry, setHC);
|
|||
|
CalculateAndSetParity((byte)(((value + hl.Low) & 0x07) ^ bc.High));
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
SetClearFlagConditional(Flags.Carry, setHC);
|
|||
|
}
|
|||
|
|
|||
|
protected void PortOutputDecrementRepeat()
|
|||
|
{
|
|||
|
PortOutputDecrement();
|
|||
|
|
|||
|
if (bc.High != 0)
|
|||
|
{
|
|||
|
currentCycles += CycleCounts.AdditionalRepeatByteOps;
|
|||
|
pc -= 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// S
|
|||
|
SetFlag(Flags.Zero);
|
|||
|
// H
|
|||
|
// PV
|
|||
|
SetFlag(Flags.Subtract);
|
|||
|
// C
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|