diff --git a/EightBit.sln b/EightBit.sln
index 31a5b07..a86ef49 100644
--- a/EightBit.sln
+++ b/EightBit.sln
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EightBit.UnitTest", "EightB
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M6502.Test", "M6502\M6502.Test\M6502.Test.csproj", "{854EDE2F-B54D-425C-8F68-EDA68BDC797E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Z80", "Z80\Z80.csproj", "{C00648C1-BAC1-4EFB-816F-E87C091619D7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +71,18 @@ Global
{854EDE2F-B54D-425C-8F68-EDA68BDC797E}.Release|x64.Build.0 = Release|Any CPU
{854EDE2F-B54D-425C-8F68-EDA68BDC797E}.Release|x86.ActiveCfg = Release|Any CPU
{854EDE2F-B54D-425C-8F68-EDA68BDC797E}.Release|x86.Build.0 = Release|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Debug|x64.Build.0 = Debug|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Debug|x86.Build.0 = Debug|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Release|x64.ActiveCfg = Release|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Release|x64.Build.0 = Release|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Release|x86.ActiveCfg = Release|Any CPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/EightBit/Chip.cs b/EightBit/Chip.cs
index 0cc24b3..8f8e24f 100644
--- a/EightBit/Chip.cs
+++ b/EightBit/Chip.cs
@@ -51,5 +51,23 @@ namespace EightBit
public static int PromoteNibble(byte value) => LowByte(value << 4);
public static int DemoteNibble(byte value) => HighNibble(value);
+
+ public static int CountBits(int value)
+ {
+ int count = 0;
+ while (value != 0)
+ {
+ ++count;
+ value &= value - 1;
+ }
+
+ return count;
+ }
+
+ public static int CountBits(byte value) => CountBits((int)value);
+
+ public static bool EvenParity(int value) => CountBits(value) % 2 == 0;
+
+ public static bool EvenParity(byte value) => EvenParity((int)value);
}
}
diff --git a/EightBit/IntelOpCodeDecoded.cs b/EightBit/IntelOpCodeDecoded.cs
index 6c8c8b5..41d6476 100644
--- a/EightBit/IntelOpCodeDecoded.cs
+++ b/EightBit/IntelOpCodeDecoded.cs
@@ -6,23 +6,27 @@ namespace EightBit
{
public class IntelOpCodeDecoded
{
- private readonly int x;
- private readonly int y;
- private readonly int z;
- private readonly int p;
- private readonly int q;
-
public IntelOpCodeDecoded()
{
}
public IntelOpCodeDecoded(byte opCode)
{
- this.x = (opCode & 0b11000000) >> 6; // 0 - 3
- this.y = (opCode & 0b00111000) >> 3; // 0 - 7
- this.z = opCode & 0b00000111; // 0 - 7
- this.p = (this.y & 0b110) >> 1; // 0 - 3
- this.q = this.y & 1; // 0 - 1
+ this.X = (opCode & 0b11000000) >> 6; // 0 - 3
+ this.Y = (opCode & 0b00111000) >> 3; // 0 - 7
+ this.Z = opCode & 0b00000111; // 0 - 7
+ this.P = (this.Y & 0b110) >> 1; // 0 - 3
+ this.Q = this.Y & 1; // 0 - 1
}
+
+ public int X { get; }
+
+ public int Y { get; }
+
+ public int Z { get; }
+
+ public int P { get; }
+
+ public int Q { get; }
}
}
diff --git a/EightBit/IntelProcessor.cs b/EightBit/IntelProcessor.cs
index 2d389d2..b575beb 100644
--- a/EightBit/IntelProcessor.cs
+++ b/EightBit/IntelProcessor.cs
@@ -8,6 +8,9 @@ namespace EightBit
public abstract class IntelProcessor : LittleEndianProcessor
{
+ private static readonly int[] HalfCarryTableAdd = new int[8] { 0, 0, 1, 0, 1, 0, 1, 1 };
+ private static readonly int[] HalfCarryTableSub = new int[8] { 0, 1, 1, 1, 0, 0, 0, 1 };
+
private readonly IntelOpCodeDecoded[] decodedOpCodes = new IntelOpCodeDecoded[0x100];
private Register16 sp = new Register16((ushort)Mask.Mask16);
@@ -86,6 +89,23 @@ namespace EightBit
this.OnLoweredHALT();
}
+ protected static int BuildHalfCarryIndex(byte before, byte value, int calculation)
+ {
+ return ((before & 0x88) >> 1) | ((value & 0x88) >> 2) | ((calculation & 0x88) >> 3);
+ }
+
+ protected static int CalculateHalfCarryAdd(byte before, byte value, int calculation)
+ {
+ var index = BuildHalfCarryIndex(before, value, calculation);
+ return HalfCarryTableAdd[index & (int)Mask.Mask3];
+ }
+
+ protected static int CalculateHalfCarrySub(byte before, byte value, int calculation)
+ {
+ var index = BuildHalfCarryIndex(before, value, calculation);
+ return HalfCarryTableSub[index & (int)Mask.Mask3];
+ }
+
protected virtual void OnRaisingHALT() => this.RaisingHALT?.Invoke(this, EventArgs.Empty);
protected virtual void OnRaisedHALT() => this.RaisedHALT?.Invoke(this, EventArgs.Empty);
@@ -192,5 +212,7 @@ namespace EightBit
++this.PC();
this.RaiseHALT();
}
+
+ protected IntelOpCodeDecoded GetDecodedOpCode(byte opCode) => this.decodedOpCodes[opCode];
}
}
diff --git a/EightBit/Mask.cs b/EightBit/Mask.cs
index 8e0684c..60bb615 100644
--- a/EightBit/Mask.cs
+++ b/EightBit/Mask.cs
@@ -4,8 +4,6 @@
namespace EightBit
{
- using System;
-
public enum Mask
{
None = 0,
diff --git a/EightBit/Register16.cs b/EightBit/Register16.cs
index 79b680b..3f771aa 100644
--- a/EightBit/Register16.cs
+++ b/EightBit/Register16.cs
@@ -76,7 +76,7 @@ namespace EightBit
return false;
}
- Register16 rhs = (Register16)obj;
+ var rhs = (Register16)obj;
return rhs.Word == this.Word;
}
diff --git a/Z80/Properties/AssemblyInfo.cs b/Z80/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..6aede72
--- /dev/null
+++ b/Z80/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Z80")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Z80")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c00648c1-bac1-4efb-816f-e87c091619d7")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Z80/RefreshRegister.cs b/Z80/RefreshRegister.cs
new file mode 100644
index 0000000..5159a65
--- /dev/null
+++ b/Z80/RefreshRegister.cs
@@ -0,0 +1,59 @@
+namespace EightBit
+{
+ public struct RefreshRegister
+ {
+ private readonly byte high;
+ private byte variable;
+
+ public RefreshRegister(byte value)
+ {
+ this.high = (byte)(value & (byte)Bits.Bit7);
+ this.variable = (byte)(value & (byte)Mask.Mask7);
+ }
+
+ public static implicit operator byte(RefreshRegister input)
+ {
+ return ToByte(input);
+ }
+
+ public static implicit operator RefreshRegister(byte input)
+ {
+ return FromByte(input);
+ }
+
+ public static byte ToByte(RefreshRegister input)
+ {
+ return (byte)((input.high << 7) | input.variable);
+ }
+
+ public static RefreshRegister FromByte(byte input)
+ {
+ return new RefreshRegister(input);
+ }
+
+ public static RefreshRegister operator ++(RefreshRegister value) => Increment(value);
+
+ public static bool operator ==(RefreshRegister left, RefreshRegister right) => left.Equals(right);
+
+ public static bool operator !=(RefreshRegister left, RefreshRegister right) => !(left == right);
+
+ public static RefreshRegister Increment(RefreshRegister value)
+ {
+ ++value.variable;
+ return value;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is RefreshRegister))
+ {
+ return false;
+ }
+
+ var rhs = (RefreshRegister)obj;
+ return rhs.high == this.high && rhs.variable == this.variable;
+ }
+
+ public override int GetHashCode() => this.high + this.variable;
+ }
+}
diff --git a/Z80/StatusBits.cs b/Z80/StatusBits.cs
new file mode 100644
index 0000000..179fbfc
--- /dev/null
+++ b/Z80/StatusBits.cs
@@ -0,0 +1,39 @@
+//
+// Copyright (c) Adrian Conlon. All rights reserved.
+//
+
+namespace EightBit
+{
+ using System;
+
+ [Flags]
+ public enum StatusBits
+ {
+ // Negative
+ SF = Bits.Bit7,
+
+ // Zero
+ ZF = Bits.Bit6,
+
+ // Undocumented Y flag
+ YF = Bits.Bit5,
+
+ // Half carry
+ HC = Bits.Bit4,
+
+ // Undocumented X flag
+ XF = Bits.Bit3,
+
+ // Parity
+ PF = Bits.Bit2,
+
+ // Zero
+ VF = Bits.Bit2,
+
+ // Negative?
+ NF = Bits.Bit1,
+
+ // Carry
+ CF = Bits.Bit0,
+ }
+}
diff --git a/Z80/Z80.cs b/Z80/Z80.cs
new file mode 100644
index 0000000..99d20ce
--- /dev/null
+++ b/Z80/Z80.cs
@@ -0,0 +1,1951 @@
+//
+// Copyright (c) Adrian Conlon. All rights reserved.
+//
+
+namespace EightBit
+{
+ using System;
+
+ public class Z80 : IntelProcessor
+ {
+ public enum RegisterIndex { IndexBC, IndexDE, IndexHL };
+
+ private readonly InputOutput ports;
+
+ private RefreshRegister refresh = new RefreshRegister(0x7f);
+
+ private bool prefixCB = false;
+ private bool prefixDD = false;
+ private bool prefixED = false;
+ private bool prefixFD = false;
+
+ private PinLevel nmiLine = PinLevel.Low;
+ private PinLevel m1Line = PinLevel.Low;
+
+ private readonly Register16[] accumulatorFlags = new Register16[2];
+ private int accumulatorFlagsSet = 0;
+
+ private readonly Register16[,] registers = new Register16[2,3];
+ private int registerSet = 0;
+
+ private Register16 ix = new Register16(0xffff);
+ private Register16 iy = new Register16(0xffff);
+
+ private sbyte displacement = 0;
+ private bool displaced = false;
+
+ public Z80(Bus bus, InputOutput ports)
+ : base(bus)
+ {
+ this.ports = ports;
+ }
+
+ public event EventHandler ExecutingInstruction;
+
+ public event EventHandler ExecutedInstruction;
+
+ public event EventHandler RaisingNMI;
+
+ public event EventHandler RaisedNMI;
+
+ public event EventHandler LoweringNMI;
+
+ public event EventHandler LoweredNMI;
+
+ public event EventHandler RaisingM1;
+
+ public event EventHandler RaisedM1;
+
+ public event EventHandler LoweringM1;
+
+ public event EventHandler LoweredM1;
+
+ public byte IV { get; set; } = 0xff;
+
+ public int IM { get; set; } = 0;
+
+ public bool IFF1 { get; set; } = false;
+
+ public bool IFF2 { get; set; } = false;
+
+ private ushort DisplacedAddress
+ {
+ get
+ {
+ var returned = (this.prefixDD ? this.IX() : this.IY()).Word + this.displacement;
+ return MEMPTR().Word = (ushort)returned;
+ }
+ }
+
+ public override ref Register16 AF() => ref this.accumulatorFlags[this.accumulatorFlagsSet];
+
+ public override ref Register16 BC() => ref this.registers[this.registerSet, (int)RegisterIndex.IndexBC];
+
+ public override ref Register16 DE() => ref this.registers[this.registerSet, (int)RegisterIndex.IndexDE];
+
+ public override ref Register16 HL() => ref this.registers[this.registerSet, (int)RegisterIndex.IndexHL];
+
+ public ref Register16 IX() => ref this.ix;
+
+ public ref byte IXH() => ref this.IX().High;
+
+ public ref byte IXL() => ref this.IX().Low;
+
+ public ref Register16 IY() => ref this.iy;
+
+ public ref byte IYH() => ref this.IY().High;
+
+ public ref byte IYL() => ref this.IY().Low;
+
+ public ref RefreshRegister REFRESH() => ref this.refresh;
+
+ public ref PinLevel NMI() => ref this.nmiLine;
+
+ public ref PinLevel M1() => ref this.m1Line;
+
+ public void Exx() => this.registerSet ^= 1;
+
+ public void ExxAF() => this.accumulatorFlagsSet ^= 1;
+
+ public override void RaisePOWER()
+ {
+ base.RaisePOWER();
+
+ this.RaiseM1();
+
+ this.DisableInterrupts();
+ this.IM = 0;
+
+ this.REFRESH() = new RefreshRegister(0);
+ this.IV = (byte)Mask.Mask8;
+
+ this.ExxAF();
+ this.AF().Word = (ushort)Mask.Mask16;
+
+ this.Exx();
+ this.IX().Word = this.IY().Word = this.BC().Word = this.DE().Word = this.HL().Word = (ushort)Mask.Mask16;
+
+ this.prefixCB = this.prefixDD = this.prefixED = this.prefixFD = false;
+ }
+
+ public virtual void RaiseNMI()
+ {
+ this.OnRaisingNMI();
+ this.NMI().Raise();
+ this.OnRaisedNMI();
+ }
+
+ public virtual void LowerNMI()
+ {
+ this.OnLoweringNMI();
+ this.NMI().Lower();
+ this.OnLoweredNMI();
+ }
+
+ public virtual void RaiseM1()
+ {
+ this.OnRaisingM1();
+ this.M1().Raise();
+ this.OnRaisedM1();
+ }
+
+ public virtual void LowerM1()
+ {
+ this.OnLoweringM1();
+ this.M1().Lower();
+ this.OnLoweredM1();
+ }
+
+ public override int Execute()
+ {
+ if (!(this.prefixCB && this.displaced))
+ {
+ ++this.REFRESH();
+ this.RaiseM1();
+ }
+
+ var decoded = this.GetDecodedOpCode(this.OpCode);
+
+ var x = decoded.X;
+ var y = decoded.Y;
+ var z = decoded.Z;
+
+ var p = decoded.P;
+ var q = decoded.Q;
+
+ var prefixed = this.prefixCB || this.prefixED;
+ if (prefixed)
+ {
+ if (this.prefixCB)
+ this.ExecuteCB(x, y, z);
+ else if (this.prefixED)
+ this.ExecuteED(x, y, z, p, q);
+ }
+ else
+ {
+ this.ExecuteOther(x, y, z, p, q);
+ }
+
+ return this.Cycles;
+ }
+
+ public override int Step()
+ {
+ this.ResetCycles();
+ this.OnExecutingInstruction();
+ if (this.Powered)
+ {
+ this.displaced = this.prefixCB = this.prefixDD = this.prefixED = this.prefixFD = false;
+ this.LowerM1();
+ if (this.RESET().Lowered())
+ {
+ this.HandleRESET();
+ }
+ else if (this.NMI().Lowered())
+ {
+ this.HandleNMI();
+ }
+ else if (this.INT().Lowered())
+ {
+ this.HandleINT();
+ }
+ else if (this.Halted)
+ {
+ this.Execute(0); // NOP
+ }
+ else
+ {
+ this.Execute(this.FetchByte());
+ }
+ }
+ this.OnExecutedInstruction();
+ return this.Cycles;
+ }
+
+ protected virtual void OnExecutingInstruction() => this.ExecutingInstruction?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnExecutedInstruction() => this.ExecutedInstruction?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnRaisingNMI() => this.RaisingNMI?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnRaisedNMI() => this.RaisedNMI?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnLoweringNMI() => this.LoweringNMI?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnLoweredNMI() => this.LoweredNMI?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnRaisingM1() => this.RaisingM1?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnRaisedM1() => this.RaisedM1?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnLoweringM1() => this.LoweringM1?.Invoke(this, EventArgs.Empty);
+
+ protected virtual void OnLoweredM1() => this.LoweredM1?.Invoke(this, EventArgs.Empty);
+
+ protected override void HandleRESET()
+ {
+ base.HandleRESET();
+ this.DisableInterrupts();
+ this.Tick(3);
+ }
+
+ protected override void HandleINT()
+ {
+ base.HandleINT();
+ this.RaiseHALT();
+ if (this.IFF1)
+ {
+ this.DisableInterrupts();
+ switch (this.IM)
+ {
+ case 0: // i8080 equivalent
+ this.Execute(this.Bus.Data);
+ break;
+ case 1:
+ this.Restart(7 << 3);
+ this.Tick(13);
+ break;
+ case 2:
+ this.Call(this.MEMPTR() = new Register16(this.Bus.Data, IV));
+ this.Tick(19);
+ break;
+ default:
+ throw new NotSupportedException("Invalid interrupt mode");
+ }
+ }
+ }
+
+ private static byte SetFlag(byte f, StatusBits flag) => SetFlag(f, (byte)flag);
+
+ private static byte SetFlag(byte f, StatusBits flag, int condition) => SetFlag(f, (byte)flag, condition);
+
+ private static byte SetFlag(byte f, StatusBits flag, bool condition) => SetFlag(f, (byte)flag, condition);
+
+ private static byte ClearFlag(byte f, StatusBits flag) => ClearFlag(f, (byte)flag);
+
+ private static byte ClearFlag(byte f, StatusBits flag, int condition) => ClearFlag(f, (byte)flag, condition);
+
+ private static byte ClearFlag(byte f, StatusBits flag, bool condition) => ClearFlag(f, (byte)flag, condition);
+
+ private static byte AdjustSign(byte input, byte value)
+ {
+ return SetFlag(input, StatusBits.SF, value & (byte)StatusBits.SF);
+ }
+
+ private static byte AdjustZero(byte input, byte value)
+ {
+ return ClearFlag(input, StatusBits.ZF, value);
+ }
+
+ private static byte AdjustParity(byte input, byte value)
+ {
+ return SetFlag(input, StatusBits.PF, EvenParity(value));
+ }
+
+ private static byte AdjustSZ(byte input, byte value)
+ {
+ input = AdjustSign(input, value);
+ return AdjustZero(input, value);
+ }
+
+ private static byte AdjustSZP(byte input, byte value)
+ {
+ AdjustSZ(input, value);
+ return AdjustParity(input, value);
+ }
+
+ private static byte AdjustXY(byte input, byte value)
+ {
+ input = SetFlag(input, StatusBits.XF, value & (byte)StatusBits.XF);
+ return SetFlag(input, StatusBits.YF, value & (byte)StatusBits.YF);
+ }
+
+ private static byte AdjustSZPXY(byte input, byte value)
+ {
+ input = AdjustSZP(input, value);
+ return AdjustXY(input, value);
+ }
+
+ private static byte AdjustSZXY(byte input, byte value)
+ {
+ input = AdjustSZ(input, value);
+ return AdjustXY(input, value);
+ }
+
+ private static byte AdjustHalfCarryAdd(byte input, byte before, byte value, int calculation)
+ {
+ return SetFlag(input, StatusBits.HC, CalculateHalfCarryAdd(before, value, calculation));
+ }
+
+ private static byte AdjustHalfCarrySub(byte input, byte before, byte value, int calculation)
+ {
+ return SetFlag(input, StatusBits.HC, CalculateHalfCarrySub(before, value, calculation));
+ }
+
+ private static byte AdjustOverflowAdd(byte input, int beforeNegative, int valueNegative, int afterNegative)
+ {
+ var overflow = (beforeNegative == valueNegative) && (beforeNegative != afterNegative);
+ return SetFlag(input, StatusBits.VF, overflow);
+ }
+
+ private static byte AdjustOverflowAdd(byte input, byte before, byte value, byte calculation)
+ {
+ return AdjustOverflowAdd(input, before & (byte)StatusBits.SF, value & (byte)StatusBits.SF, calculation & (byte)StatusBits.SF);
+ }
+
+ private static byte AdjustOverflowSub(byte input, int beforeNegative, int valueNegative, int afterNegative)
+ {
+ var overflow = (beforeNegative != valueNegative) && (beforeNegative != afterNegative);
+ return SetFlag(input, StatusBits.VF, overflow);
+ }
+
+ private static byte AdjustOverflowSub(byte input, byte before, byte value, byte calculation)
+ {
+ return AdjustOverflowSub(input, before & (byte)StatusBits.SF, value & (byte)StatusBits.SF, calculation & (byte)StatusBits.SF);
+ }
+
+ private void DisableInterrupts() => this.IFF1 = this.IFF2 = false;
+
+ private void EnableInterrupts() => this.IFF1 = this.IFF2 = true;
+
+ private ref Register16 HL2()
+ {
+ if (!this.displaced)
+ {
+ return ref this.HL();
+ }
+
+ if (this.prefixDD)
+ {
+ return ref this.IX();
+ }
+
+ // Must be FD prefix
+ return ref this.IY();
+ }
+
+ private ref Register16 RP(int rp)
+ {
+ switch (rp)
+ {
+ case 0:
+ return ref this.BC();
+ case 1:
+ return ref this.DE();
+ case 2:
+ return ref this.HL2();
+ case 3:
+ return ref this.SP();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(rp));
+ }
+ }
+
+ private ref Register16 RP2(int rp)
+ {
+ switch (rp)
+ {
+ case 0:
+ return ref this.BC();
+ case 1:
+ return ref this.DE();
+ case 2:
+ return ref this.HL2();
+ case 3:
+ return ref this.AF();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(rp));
+ }
+ }
+
+ private byte R(int r)
+ {
+ switch (r)
+ {
+ case 0:
+ return this.B();
+ case 1:
+ return this.C();
+ case 2:
+ return this.D();
+ case 3:
+ return this.E();
+ case 4:
+ return this.HL2().High;
+ case 5:
+ return this.HL2().Low;
+ case 6:
+ return this.Bus.Read(this.displaced ? this.DisplacedAddress : this.HL().Word);
+ case 7:
+ return this.A();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(r));
+ }
+ }
+
+ private void R(int r, byte value)
+ {
+ switch (r)
+ {
+ case 0:
+ this.B() = value;
+ break;
+ case 1:
+ this.C() = value;
+ break;
+ case 2:
+ this.D() = value;
+ break;
+ case 3:
+ this.E() = value;
+ break;
+ case 4:
+ this.HL2().High = value;
+ break;
+ case 5:
+ this.HL2().Low = value;
+ break;
+ case 6:
+ this.Bus.Write(this.displaced ? this.DisplacedAddress : this.HL().Word, value);
+ break;
+ case 7:
+ this.A() = value;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(r));
+ }
+ }
+
+ private void R2(int r, byte value)
+ {
+ switch (r)
+ {
+ case 0:
+ this.B() = value;
+ break;
+ case 1:
+ this.C() = value;
+ break;
+ case 2:
+ this.D() = value;
+ break;
+ case 3:
+ this.E() = value;
+ break;
+ case 4:
+ this.H() = value;
+ break;
+ case 5:
+ this.L() = value;
+ break;
+ case 6:
+ this.Bus.Write(this.HL(), value);
+ break;
+ case 7:
+ A() = value;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(r));
+ }
+ }
+
+ private void ExecuteCB(int x, int y, int z)
+ {
+ var memoryY = y == 6;
+ var memoryZ = z == 6;
+ var indirect = (!this.displaced && memoryZ) || this.displaced;
+ var direct = !indirect;
+ var operand = !this.displaced ? R(z) : Bus.Read(this.DisplacedAddress);
+ var update = x != 1; // BIT does not update
+ switch (x)
+ {
+ case 0: // rot[y] r[z]
+ switch (y)
+ {
+ case 0:
+ operand = this.RLC(operand);
+ break;
+ case 1:
+ operand = this.RRC(operand);
+ break;
+ case 2:
+ operand = this.RL(operand);
+ break;
+ case 3:
+ operand = this.RR(operand);
+ break;
+ case 4:
+ operand = this.SLA(operand);
+ break;
+ case 5:
+ operand = this.SRA(operand);
+ break;
+ case 6:
+ operand = this.SLL(operand);
+ break;
+ case 7:
+ operand = this.SRL(operand);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.F() = AdjustSZP(this.F(), operand);
+ this.Tick(8);
+ break;
+ case 1: // BIT y, r[z]
+ this.Tick(8);
+ this.BIT(y, operand);
+ if (direct)
+ {
+ this.F() = AdjustXY(this.F(), operand);
+ }
+ else
+ {
+ this.F() = AdjustXY(this.F(), this.MEMPTR().High);
+ this.Tick(4);
+ }
+ break;
+ case 2: // RES y, r[z]
+ this.Tick(8);
+ operand = RES(y, operand);
+ break;
+ case 3: // SET y, r[z]
+ this.Tick(8);
+ operand = SET(y, operand);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ if (update)
+ {
+ if (!this.displaced)
+ {
+ this.R(z, operand);
+ if (memoryZ)
+ {
+ this.Tick(7);
+ }
+ }
+ else
+ {
+ this.Bus.Write(operand);
+ this.R2(z, operand);
+ this.Tick(15);
+ }
+ }
+ }
+
+ private void ExecuteED(int x, int y, int z, int p, int q)
+ {
+ var memoryY = y == 6;
+ var memoryZ = z == 6;
+ switch (x)
+ {
+ case 0:
+ case 3: // Invalid instruction, equivalent to NONI followed by NOP
+ this.Tick(8);
+ break;
+ case 1:
+ switch (z)
+ {
+ case 0: // Input from port with 16-bit address
+ this.MEMPTR() = this.Bus.Address() = this.BC();
+ this.MEMPTR()++;
+ this.ReadPort();
+ if (y != 6) // IN r[y],(C)
+ this.R(y, this.Bus.Data);
+ this.F() = AdjustSZPXY(this.F(), this.Bus.Data);
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.Tick(12);
+ break;
+ case 1: // Output to port with 16-bit address
+ this.MEMPTR() = this.Bus.Address() = this.BC();
+ this.MEMPTR()++;
+ if (y != 6) // OUT (C),r[y]
+ this.Bus.Data = R(y);
+ else // OUT (C),0
+ this.Bus.Data = 0;
+ this.WritePort();
+ this.Tick(12);
+ break;
+ case 2: // 16-bit add/subtract with carry
+ switch (q)
+ {
+ case 0: // SBC HL, rp[p]
+ this.SBC(this.RP(p));
+ break;
+ case 1: // ADC HL, rp[p]
+ this.ADC(this.RP(p));
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(15);
+ break;
+ case 3: // Retrieve/store register pair from/to immediate address
+ this.Bus.Address() = this.FetchWord();
+ switch (q)
+ {
+ case 0: // LD (nn), rp[p]
+ this.SetWord(this.RP(p));
+ break;
+ case 1: // LD rp[p], (nn)
+ this.RP(p) = this.GetWord();
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(20);
+ break;
+ case 4: // Negate accumulator
+ this.NEG();
+ this.Tick(8);
+ break;
+ case 5: // Return from interrupt
+ switch (y)
+ {
+ case 1:
+ this.RetI(); // RETI
+ break;
+ default:
+ this.RetN(); // RETN
+ break;
+ }
+ this.Tick(14);
+ break;
+ case 6: // Set interrupt mode
+ switch (y)
+ {
+ case 0:
+ case 1:
+ case 4:
+ case 5:
+ this.IM = 0;
+ break;
+ case 2:
+ case 6:
+ this.IM = 1;
+ break;
+ case 3:
+ case 7:
+ this.IM = 2;
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(8);
+ break;
+ case 7: // Assorted ops
+ switch (y)
+ {
+ case 0: // LD I,A
+ this.IV = this.A();
+ this.Tick(9);
+ break;
+ case 1: // LD R,A
+ this.REFRESH() = this.A();
+ this.Tick(9);
+ break;
+ case 2: // LD A,I
+ this.F() = AdjustSZXY(this.F(), this.A() = this.IV);
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.PF, IFF2);
+ this.Tick(9);
+ break;
+ case 3: // LD A,R
+ this.F() = AdjustSZXY(this.F(), this.A() = REFRESH());
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.PF, IFF2);
+ this.Tick(9);
+ break;
+ case 4: // RRD
+ this.RRD();
+ this.Tick(18);
+ break;
+ case 5: // RLD
+ this.RLD();
+ this.Tick(18);
+ break;
+ case 6: // NOP
+ case 7: // NOP
+ this.Tick(4);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 2:
+ switch (z)
+ {
+ case 0: // LD
+ switch (y)
+ {
+ case 4: // LDI
+ this.LDI();
+ break;
+ case 5: // LDD
+ this.LDD();
+ break;
+ case 6: // LDIR
+ if (this.LDIR())
+ {
+ this.MEMPTR() = --this.PC();
+ --this.PC();
+ this.Tick(5);
+ }
+ break;
+ case 7: // LDDR
+ if (this.LDDR())
+ {
+ this.MEMPTR() = --this.PC();
+ --this.PC();
+ this.Tick(5);
+ }
+ break;
+ }
+ break;
+ case 1: // CP
+ switch (y)
+ {
+ case 4: // CPI
+ this.CPI();
+ break;
+ case 5: // CPD
+ this.CPD();
+ break;
+ case 6: // CPIR
+ if (this.CPIR())
+ {
+ this.MEMPTR() = --this.PC();
+ --this.PC();
+ this.Tick(5);
+ }
+ break;
+ case 7: // CPDR
+ if (this.CPDR())
+ {
+ this.MEMPTR() = --this.PC();
+ --this.PC();
+ this.Tick(5);
+ }
+ else
+ {
+ this.MEMPTR().Word = (ushort)(this.PC().Word - 2);
+ }
+ break;
+ }
+ break;
+ case 2: // IN
+ switch (y)
+ {
+ case 4: // INI
+ this.INI();
+ break;
+ case 5: // IND
+ this.IND();
+ break;
+ case 6: // INIR
+ if (this.INIR())
+ {
+ this.PC().Word -= 2;
+ this.Tick(5);
+ }
+ break;
+ case 7: // INDR
+ if (this.INDR())
+ {
+ this.PC().Word -= 2;
+ this.Tick(5);
+ }
+ break;
+ }
+ break;
+ case 3: // OUT
+ switch (y)
+ {
+ case 4: // OUTI
+ this.OUTI();
+ break;
+ case 5: // OUTD
+ this.OUTD();
+ break;
+ case 6: // OTIR
+ if (this.OTIR())
+ {
+ this.PC().Word -= 2;
+ this.Tick(5);
+ }
+ break;
+ case 7: // OTDR
+ if (this.OTDR())
+ {
+ this.PC().Word -= 2;
+ this.Tick(5);
+ }
+ break;
+ }
+ break;
+ }
+ this.Tick(16);
+ break;
+ }
+ }
+
+ private void ExecuteOther(int x, int y, int z, int p, int q)
+ {
+ var memoryY = y == 6;
+ var memoryZ = z == 6;
+ switch (x)
+ {
+ case 0:
+ switch (z)
+ {
+ case 0: // Relative jumps and assorted ops
+ switch (y)
+ {
+ case 0: // NOP
+ this.Tick(4);
+ break;
+ case 1: // EX AF AF'
+ this.ExxAF();
+ this.Tick(4);
+ break;
+ case 2: // DJNZ d
+ if (this.JumpRelativeConditional(--this.B() != 0))
+ this.Tick(5);
+ this.Tick(8);
+ break;
+ case 3: // JR d
+ this.JumpRelative((sbyte)this.FetchByte());
+ this.Tick(12);
+ break;
+ case 4: // JR cc,d
+ case 5:
+ case 6:
+ case 7:
+ if (this.JumpRelativeConditionalFlag(y - 4))
+ this.Tick(5);
+ this.Tick(5);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 1: // 16-bit load immediate/add
+ switch (q)
+ {
+ case 0: // LD rp,nn
+ this.RP(p) = this.FetchWord();
+ this.Tick(10);
+ break;
+ case 1: // ADD HL,rp
+ this.Add(this.RP(p));
+ this.Tick(11);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 2: // Indirect loading
+ switch (q)
+ {
+ case 0:
+ switch (p)
+ {
+ case 0: // LD (BC),A
+ this.MEMPTR() = this.Bus.Address() = this.BC();
+ this.MEMPTR()++;
+ this.MEMPTR().High = this.Bus.Data = this.A();
+ this.Bus.Write();
+ this.Tick(7);
+ break;
+ case 1: // LD (DE),A
+ this.MEMPTR() = this.Bus.Address() = this.DE();
+ this.MEMPTR()++;
+ this.MEMPTR().High = this.Bus.Data = this.A();
+ this.Bus.Write();
+ this.Tick(7);
+ break;
+ case 2: // LD (nn),HL
+ this.Bus.Address() = this.FetchWord();
+ this.SetWord(this.HL2());
+ this.Tick(16);
+ break;
+ case 3: // LD (nn),A
+ this.MEMPTR() = this.Bus.Address() = this.FetchWord();
+ this.MEMPTR()++;
+ this.MEMPTR().High = this.Bus.Data = this.A();
+ this.Bus.Write();
+ this.Tick(13);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 1:
+ switch (p)
+ {
+ case 0: // LD A,(BC)
+ this.MEMPTR() = this.Bus.Address() = this.BC();
+ this.MEMPTR()++;
+ this.A() = this.Bus.Read();
+ this.Tick(7);
+ break;
+ case 1: // LD A,(DE)
+ this.MEMPTR() = this.Bus.Address() = this.DE();
+ this.MEMPTR()++;
+ this.A() = this.Bus.Read();
+ this.Tick(7);
+ break;
+ case 2: // LD HL,(nn)
+ this.Bus.Address() = this.FetchWord();
+ this.HL2() = this.GetWord();
+ this.Tick(16);
+ break;
+ case 3: // LD A,(nn)
+ this.MEMPTR() = this.Bus.Address() = this.FetchWord();
+ this.MEMPTR()++;
+ this.A() = this.Bus.Read();
+ this.Tick(13);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 3: // 16-bit INC/DEC
+ switch (q)
+ {
+ case 0: // INC rp
+ ++this.RP(p);
+ break;
+ case 1: // DEC rp
+ --this.RP(p);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(6);
+ break;
+ case 4: // 8-bit INC
+ if (this.displaced && memoryY)
+ this.FetchDisplacement();
+ this.R(y, this.Increment(this.R(y)));
+ this.Tick(4);
+ break;
+ case 5: // 8-bit DEC
+ if (memoryY)
+ {
+ this.Tick(7);
+ if (this.displaced)
+ this.FetchDisplacement();
+ }
+ this.R(y, this.Decrement(this.R(y)));
+ this.Tick(4);
+ break;
+ case 6: // 8-bit load immediate
+ if (memoryY)
+ {
+ this.Tick(3);
+ if (this.displaced)
+ this.FetchDisplacement();
+ }
+ this.R(y, this.FetchByte()); // LD r,n
+ this.Tick(7);
+ break;
+ case 7: // Assorted operations on accumulator/flags
+ switch (y)
+ {
+ case 0:
+ this.A() = this.RLC(this.A());
+ break;
+ case 1:
+ this.A() = this.RRC(this.A());
+ break;
+ case 2:
+ this.A() = this.RL(this.A());
+ break;
+ case 3:
+ this.A() = this.RR(this.A());
+ break;
+ case 4:
+ this.DAA();
+ break;
+ case 5:
+ this.CPL();
+ break;
+ case 6:
+ this.SCF();
+ break;
+ case 7:
+ this.CCF();
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(4);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 1: // 8-bit loading
+ if (!(memoryZ && memoryY))
+ {
+ bool normal = true;
+ if (this.displaced)
+ {
+ if (memoryZ || memoryY)
+ this.FetchDisplacement();
+ if (memoryZ)
+ {
+ switch (y)
+ {
+ case 4:
+ this.H() = this.R(z);
+ normal = false;
+ break;
+ case 5:
+ this.L() = this.R(z);
+ normal = false;
+ break;
+ }
+ }
+ if (memoryY)
+ {
+ switch (z)
+ {
+ case 4:
+ this.R(y, this.H());
+ normal = false;
+ break;
+ case 5:
+ this.R(y, this.L());
+ normal = false;
+ break;
+ }
+ }
+ }
+ if (normal)
+ this.R(y, this.R(z));
+ if (memoryY || memoryZ) // M operations
+ this.Tick(3);
+ }
+ else
+ { // Exception (replaces LD (HL), (HL))
+ this.Halt();
+ }
+ this.Tick(4);
+ break;
+ case 2:
+ { // Operate on accumulator and register/memory location
+ if (memoryZ)
+ {
+ this.Tick(3);
+ if (this.displaced)
+ this.FetchDisplacement();
+ }
+ var value = R(z);
+ switch (y)
+ {
+ case 0: // ADD A,r
+ this.Add(value);
+ break;
+ case 1: // ADC A,r
+ this.ADC(value);
+ break;
+ case 2: // SUB r
+ this.SUB(value);
+ break;
+ case 3: // SBC A,r
+ this.SBC(value);
+ break;
+ case 4: // AND r
+ this.AndR(value);
+ break;
+ case 5: // XOR r
+ this.XorR(value);
+ break;
+ case 6: // OR r
+ this.OrR(value);
+ break;
+ case 7: // CP r
+ this.Compare(value);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(4);
+ break;
+ }
+ case 3:
+ switch (z)
+ {
+ case 0: // Conditional return
+ if (this.ReturnConditionalFlag(y))
+ this.Tick(6);
+ this.Tick(5);
+ break;
+ case 1: // POP & various ops
+ switch (q)
+ {
+ case 0: // POP rp2[p]
+ this.RP2(p) = this.PopWord();
+ this.Tick(10);
+ break;
+ case 1:
+ switch (p)
+ {
+ case 0: // RET
+ this.Return();
+ this.Tick(10);
+ break;
+ case 1: // EXX
+ this.Exx();
+ this.Tick(4);
+ break;
+ case 2: // JP HL
+ this.Jump(this.HL2());
+ this.Tick(4);
+ break;
+ case 3: // LD SP,HL
+ this.SP() = this.HL2();
+ this.Tick(4);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 2: // Conditional jump
+ this.JumpConditionalFlag(y);
+ this.Tick(10);
+ break;
+ case 3: // Assorted operations
+ switch (y)
+ {
+ case 0: // JP nn
+ this.Jump(this.MEMPTR() = this.FetchWord());
+ this.Tick(10);
+ break;
+ case 1: // CB prefix
+ this.prefixCB = true;
+ if (this.displaced)
+ this.FetchDisplacement();
+ this.LowerM1();
+ this.Execute(this.FetchByte());
+ break;
+ case 2: // OUT (n),A
+ this.WritePort(this.FetchByte());
+ this.Tick(11);
+ break;
+ case 3: // IN A,(n)
+ this.A() = this.ReadPort(this.FetchByte());
+ this.Tick(11);
+ break;
+ case 4: // EX (SP),HL
+ this.XHTL();
+ this.Tick(19);
+ break;
+ case 5: // EX DE,HL
+ (this.DE(), this.HL()) = (this.HL(), this.DE());
+ this.Tick(4);
+ break;
+ case 6: // DI
+ this.DisableInterrupts();
+ this.Tick(4);
+ break;
+ case 7: // EI
+ this.EnableInterrupts();
+ this.Tick(4);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 4: // Conditional call: CALL cc[y], nn
+ if (this.CallConditionalFlag(y))
+ this.Tick(7);
+ this.Tick(10);
+ break;
+ case 5: // PUSH & various ops
+ switch (q)
+ {
+ case 0: // PUSH rp2[p]
+ this.PushWord(this.RP2(p));
+ this.Tick(11);
+ break;
+ case 1:
+ switch (p)
+ {
+ case 0: // CALL nn
+ this.Call(this.MEMPTR() = this.FetchWord());
+ this.Tick(17);
+ break;
+ case 1: // DD prefix
+ this.displaced = this.prefixDD = true;
+ this.LowerM1();
+ this.Execute(this.FetchByte());
+ break;
+ case 2: // ED prefix
+ this.prefixED = true;
+ this.LowerM1();
+ this.Execute(this.FetchByte());
+ break;
+ case 3: // FD prefix
+ this.displaced = this.prefixFD = true;
+ this.LowerM1();
+ this.Execute(this.FetchByte());
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ case 6:
+ { // Operate on accumulator and immediate operand: alu[y] n
+ var operand = this.FetchByte();
+ switch (y)
+ {
+ case 0: // ADD A,n
+ this.Add(operand);
+ break;
+ case 1: // ADC A,n
+ this.ADC(operand);
+ break;
+ case 2: // SUB n
+ this.SUB(operand);
+ break;
+ case 3: // SBC A,n
+ this.SBC(operand);
+ break;
+ case 4: // AND n
+ this.AndR(operand);
+ break;
+ case 5: // XOR n
+ this.XorR(operand);
+ break;
+ case 6: // OR n
+ this.OrR(operand);
+ break;
+ case 7: // CP n
+ this.Compare(operand);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ this.Tick(7);
+ break;
+ }
+ case 7: // Restart: RST y * 8
+ this.Restart((byte)(y << 3));
+ this.Tick(11);
+ break;
+ default:
+ throw new NotSupportedException("Invalid operation mode");
+ }
+ break;
+ }
+ }
+
+ private void HandleNMI()
+ {
+ this.RaiseNMI();
+ this.RaiseHALT();
+ this.IFF1 = false;
+ this.Restart(0x66);
+ this.Tick(13);
+ }
+
+ private void FetchDisplacement() => this.displacement = (sbyte)this.FetchByte();
+
+ private byte Subtract(byte operand, byte value, int carry = 0)
+ {
+ var subtraction = new Register16(operand - value - carry);
+ var result = subtraction.Low;
+
+ this.F() = AdjustHalfCarrySub(this.F(), operand, value, result);
+ this.F() = AdjustOverflowSub(this.F(), operand, value, result);
+
+ this.F() = SetFlag(this.F(), StatusBits.NF);
+ this.F() = SetFlag(this.F(), StatusBits.CF, subtraction.High & (byte)StatusBits.CF);
+ this.F() = AdjustSZ(this.F(), result);
+
+ return result;
+ }
+
+ private byte Increment(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF);
+ var result = ++operand;
+ this.F() = AdjustSZXY(this.F(), result);
+ this.F() = SetFlag(this.F(), StatusBits.VF, result == (byte)Bits.Bit7);
+ this.F() = ClearFlag(this.F(), StatusBits.HC, LowNibble(result));
+ return result;
+ }
+
+ private byte Decrement(byte operand)
+ {
+ this.F() = SetFlag(this.F(), StatusBits.NF);
+ this.F() = ClearFlag(this.F(), StatusBits.HC, LowNibble(operand));
+ var result = --operand;
+ this.F() = AdjustSZXY(this.F(), result);
+ this.F() = SetFlag(this.F(), StatusBits.VF, result == (byte)Mask.Mask7);
+ return result;
+ }
+
+ private void RetN()
+ {
+ this.Return();
+ this.IFF1 = this.IFF2;
+ }
+
+ private void RetI() => this.RetN();
+
+ private bool JumpRelativeConditionalFlag(int flag)
+ {
+ switch (flag)
+ {
+ case 0: // NZ
+ return this.JumpRelativeConditional((this.F() & (byte)StatusBits.ZF) == 0);
+ case 1: // Z
+ return this.JumpRelativeConditional((this.F() & (byte)StatusBits.ZF) != 0);
+ case 2: // NC
+ return this.JumpRelativeConditional((this.F() & (byte)StatusBits.CF) == 0);
+ case 3: // C
+ return this.JumpRelativeConditional((this.F() & (byte)StatusBits.CF) != 0);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(flag));
+ }
+ }
+
+ private bool ReturnConditionalFlag(int flag)
+ {
+ switch (flag)
+ {
+ case 0: // NZ
+ return this.ReturnConditional((this.F() & (byte)StatusBits.ZF) == 0);
+ case 1: // Z
+ return this.ReturnConditional((this.F() & (byte)StatusBits.ZF) != 0);
+ case 2: // NC
+ return this.ReturnConditional((this.F() & (byte)StatusBits.CF) == 0);
+ case 3: // C
+ return this.ReturnConditional((this.F() & (byte)StatusBits.CF) != 0);
+ case 4: // PO
+ return this.ReturnConditional((this.F() & (byte)StatusBits.PF) == 0);
+ case 5: // PE
+ return this.ReturnConditional((this.F() & (byte)StatusBits.PF) != 0);
+ case 6: // P
+ return this.ReturnConditional((this.F() & (byte)StatusBits.SF) == 0);
+ case 7: // M
+ return this.ReturnConditional((this.F() & (byte)StatusBits.SF) != 0);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(flag));
+ }
+ }
+
+ private bool JumpConditionalFlag(int flag)
+ {
+ switch (flag)
+ {
+ case 0: // NZ
+ return this.JumpConditional((this.F() & (byte)StatusBits.ZF) == 0);
+ case 1: // Z
+ return this.JumpConditional((this.F() & (byte)StatusBits.ZF) != 0);
+ case 2: // NC
+ return this.JumpConditional((this.F() & (byte)StatusBits.CF) == 0);
+ case 3: // C
+ return this.JumpConditional((this.F() & (byte)StatusBits.CF) != 0);
+ case 4: // PO
+ return this.JumpConditional((this.F() & (byte)StatusBits.PF) == 0);
+ case 5: // PE
+ return this.JumpConditional((this.F() & (byte)StatusBits.PF) != 0);
+ case 6: // P
+ return this.JumpConditional((this.F() & (byte)StatusBits.SF) == 0);
+ case 7: // M
+ return this.JumpConditional((this.F() & (byte)StatusBits.SF) != 0);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(flag));
+ }
+ }
+
+ private bool CallConditionalFlag(int flag)
+ {
+ switch (flag)
+ {
+ case 0: // NZ
+ return this.CallConditional((this.F() & (byte)StatusBits.ZF) == 0);
+ case 1: // Z
+ return this.CallConditional((this.F() & (byte)StatusBits.ZF) != 0);
+ case 2: // NC
+ return this.CallConditional((this.F() & (byte)StatusBits.CF) == 0);
+ case 3: // C
+ return this.CallConditional((this.F() & (byte)StatusBits.CF) != 0);
+ case 4: // PO
+ return this.CallConditional((this.F() & (byte)StatusBits.PF) == 0);
+ case 5: // PE
+ return this.CallConditional((this.F() & (byte)StatusBits.PF) != 0);
+ case 6: // P
+ return this.CallConditional((this.F() & (byte)StatusBits.SF) == 0);
+ case 7: // M
+ return this.CallConditional((this.F() & (byte)StatusBits.SF) != 0);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(flag));
+ }
+ }
+
+ private void SBC(Register16 value)
+ {
+ this.MEMPTR().Word = this.HL2().Word;
+
+ var beforeNegative = this.MEMPTR().High & (byte)StatusBits.SF;
+ var valueNegative = value.High & (byte)StatusBits.SF;
+
+ var result = this.MEMPTR().Word - value.Word - (this.F() & (byte)StatusBits.CF);
+ HL2().Word = (ushort)result;
+
+ var afterNegative = this.HL2().High & (byte)StatusBits.SF;
+
+ this.F() = SetFlag(this.F(), StatusBits.SF, afterNegative);
+ this.F() = ClearFlag(this.F(), StatusBits.ZF, this.HL2().Word);
+ this.F() = AdjustHalfCarrySub(this.F(), this.MEMPTR().High, value.High, this.HL2().High);
+ this.F() = AdjustOverflowSub(this.F(), beforeNegative, valueNegative, afterNegative);
+ this.F() = SetFlag(this.F(), StatusBits.NF);
+ this.F() = SetFlag(this.F(), StatusBits.CF, result & (int)Bits.Bit16);
+ this.F() = AdjustXY(this.F(), this.HL2().High);
+
+ ++this.MEMPTR();
+ }
+
+ private void ADC(Register16 value)
+ {
+ this.MEMPTR().Word = this.HL2().Word;
+
+ var beforeNegative = this.MEMPTR().High & (byte)StatusBits.SF;
+ var valueNegative = value.High & (byte)StatusBits.SF;
+
+ var result = this.MEMPTR().Word + value.Word + (this.F() & (byte)StatusBits.CF);
+ this.HL2().Word = (ushort)result;
+
+ var afterNegative = this.HL2().High & (byte)StatusBits.SF;
+
+ this.F() = SetFlag(this.F(), StatusBits.SF, afterNegative);
+ this.F() = ClearFlag(this.F(), StatusBits.ZF, this.HL2().Word);
+ this.F() = AdjustHalfCarryAdd(this.F(), this.MEMPTR().High, value.High, this.HL2().High);
+ this.F() = AdjustOverflowAdd(this.F(), beforeNegative, valueNegative, afterNegative);
+ this.F() = ClearFlag(this.F(), StatusBits.NF);
+ this.F() = SetFlag(this.F(), StatusBits.CF, result & (int)Bits.Bit16);
+ this.F() = AdjustXY(this.F(), this.HL2().High);
+
+ ++this.MEMPTR();
+ }
+
+ private void Add(Register16 value)
+ {
+ this.MEMPTR().Word = this.HL2().Word;
+
+ var result = this.MEMPTR().Word + value.Word;
+
+ this.HL2().Word = (ushort)result;
+
+ this.F() = ClearFlag(this.F(), StatusBits.NF);
+ this.F() = SetFlag(this.F(), StatusBits.CF, result & (int)Bits.Bit16);
+ this.F() = AdjustHalfCarryAdd(this.F(), this.MEMPTR().High, value.High, this.HL2().High);
+ this.F() = AdjustXY(this.F(), this.HL2().High);
+
+ ++this.MEMPTR();
+ }
+
+ private void Add(byte value, int carry = 0)
+ {
+ var result = new Register16(this.A() + value + carry);
+
+ this.F() = AdjustHalfCarryAdd(this.F(), this.A(), value, result.Low);
+ this.F() = AdjustOverflowAdd(this.F(), this.A(), value, result.Low);
+
+ this.F() = ClearFlag(this.F(), StatusBits.NF);
+ this.F() = SetFlag(this.F(), StatusBits.CF, result.High & (byte)StatusBits.CF);
+ this.F() = AdjustSZXY(this.F(), this.A() = result.Low);
+ }
+
+ private void ADC(byte value) => Add(value, this.F() & (byte)StatusBits.CF);
+
+ private void SUB(byte value, int carry = 0)
+ {
+ this.A() = Subtract(this.A(), value, carry);
+ this.F() = AdjustXY(this.F(), this.A());
+ }
+
+ private void SBC(byte value) => SUB(value, this.F() & (byte)StatusBits.CF);
+
+ private void AndR(byte value)
+ {
+ this.F() = SetFlag(this.F(), StatusBits.HC);
+ this.F() = ClearFlag(this.F(), StatusBits.CF | StatusBits.NF);
+ this.F() = AdjustSZPXY(this.F(), this.A() &= value);
+ }
+
+ private void XorR(byte value)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.HC | StatusBits.CF | StatusBits.NF);
+ this.F() = AdjustSZPXY(this.F(), this.A() ^= value);
+ }
+
+ private void OrR(byte value)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.HC | StatusBits.CF | StatusBits.NF);
+ this.F() = AdjustSZPXY(this.F(), this.A() |= value);
+ }
+
+ private void Compare(byte value)
+ {
+ this.F() = Subtract(this.A(), value);
+ this.F() = AdjustXY(this.F(), value);
+ }
+
+ private byte RLC(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ var carry = operand & (byte)Bits.Bit7;
+ this.F() = SetFlag(this.F(), StatusBits.CF, carry);
+ var result = (byte)((operand << 1) | (carry >> 7));
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte RRC(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ var carry = operand & (byte)Bits.Bit0;
+ this.F() = SetFlag(this.F(), StatusBits.CF, carry);
+ var result = (byte)((operand >> 1) | (carry << 7));
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte RL(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ var carry = this.F() & (byte)StatusBits.CF;
+ this.F() = SetFlag(this.F(), StatusBits.CF, operand & (byte)Bits.Bit7);
+ var result = (byte)((operand << 1) | carry);
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte RR(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ var carry = this.F() & (byte)StatusBits.CF;
+ this.F() = SetFlag(this.F(), StatusBits.CF, operand & (byte)Bits.Bit0);
+ var result = (byte)((operand >> 1) | (carry << 7));
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte SLA(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.CF, operand & (byte)Bits.Bit7);
+ var result = (byte)(operand << 1);
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte SRA(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.CF, operand & (byte)Bits.Bit0);
+ var result = (byte)((operand >> 1) | (operand & (byte)Bits.Bit7));
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte SLL(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.CF, operand & (byte)Bits.Bit7);
+ var result = (byte)((operand << 1) | (byte)Bits.Bit0);
+ this.F() = AdjustXY(this.F(), result);
+ return result;
+ }
+
+ private byte SRL(byte operand)
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.CF, operand & (byte)Bits.Bit0);
+ var result = (byte)((operand >> 1) & ~(byte)Bits.Bit7);
+ this.F() = AdjustXY(this.F(), result);
+ this.F() = SetFlag(this.F(), StatusBits.ZF, result);
+ return result;
+ }
+
+ private void BIT(int n, byte operand)
+ {
+ this.F() = SetFlag(this.F(), StatusBits.HC);
+ this.F() = ClearFlag(this.F(), StatusBits.NF);
+ var discarded = (byte)(operand & (1 << n));
+ this.F() = AdjustSZ(this.F(), discarded);
+ this.F() = ClearFlag(this.F(), StatusBits.PF, discarded);
+ }
+
+ private static byte RES(int n, byte operand)
+ {
+ return (byte)(operand & ~(1 << n));
+ }
+
+ private static byte SET(int n, byte operand)
+ {
+ return (byte)(operand | (1 << n));
+ }
+
+ private void DAA()
+ {
+ var updated = this.A();
+
+ var lowAdjust = ((this.F() & (byte)StatusBits.HC) != 0) || (LowNibble(A()) > 9);
+ var highAdjust = ((this.F() & (byte)StatusBits.CF) != 0) || (this.A() > 0x99);
+
+ if ((this.F() & (byte)StatusBits.NF) != 0)
+ {
+ if (lowAdjust)
+ {
+ updated -= 6;
+ }
+
+ if (highAdjust)
+ {
+ updated -= 0x60;
+ }
+ }
+ else
+ {
+ if (lowAdjust)
+ {
+ updated += 6;
+ }
+
+ if (highAdjust)
+ {
+ updated += 0x60;
+ }
+ }
+
+ this.F() = (byte)((this.F() & (byte)(StatusBits.CF | StatusBits.NF)) | (this.A() > 0x99 ? (byte)StatusBits.CF : 0) | ((this.A() ^ updated) & (byte)StatusBits.HC));
+
+ this.F() = AdjustSZPXY(this.F(), this.A() = updated);
+ }
+
+ private void SCF()
+ {
+ this.F() = SetFlag(this.F(), StatusBits.CF);
+ this.F() = ClearFlag(this.F(), StatusBits.HC | StatusBits.NF);
+ this.F() = AdjustXY(this.F(), this.A());
+ }
+
+ private void CCF()
+ {
+ this.F() = ClearFlag(this.F(), StatusBits.NF);
+ var carry = this.F() & (byte)StatusBits.CF;
+ this.F() = SetFlag(this.F(), StatusBits.HC, carry);
+ this.F() = ClearFlag(this.F(), StatusBits.CF, carry);
+ this.F() = AdjustXY(this.F(), this.A());
+ }
+
+ private void CPL()
+ {
+ this.F() = SetFlag(this.F(), StatusBits.HC | StatusBits.NF);
+ this.F() = AdjustXY(this.F(), this.A() = (byte)~this.A());
+ }
+
+ private void XHTL()
+ {
+ this.MEMPTR().Low = this.Bus.Read(this.SP());
+ this.Bus.Write(this.HL2().Low);
+ this.HL2().Low = this.MEMPTR().Low;
+ ++this.Bus.Address();
+ this.MEMPTR().High = this.Bus.Read();
+ this.Bus.Write(this.HL2().High);
+ this.HL2().High = this.MEMPTR().High;
+ }
+
+ private void BlockCompare(Register16 source, Register16 counter)
+ {
+ var value = this.Bus.Read(source);
+ var result = (byte)(this.A() - value);
+
+ this.F() = SetFlag(this.F(), StatusBits.PF, --counter.Word);
+
+ this.F() = AdjustSZ(this.F(), result);
+ this.F() = AdjustHalfCarrySub(this.F(), this.A(), value, result);
+ this.F() = SetFlag(this.F(), StatusBits.NF);
+
+ result -= (byte)((this.F() & (byte)StatusBits.HC) >> 4);
+
+ this.F() = SetFlag(this.F(), StatusBits.YF, result & (byte)Bits.Bit1);
+ this.F() = SetFlag(this.F(), StatusBits.XF, result & (byte)Bits.Bit3);
+ }
+
+ private void CPI()
+ {
+ this.BlockCompare(this.HL()++, this.BC());
+ ++this.MEMPTR();
+ }
+
+ private bool CPIR()
+ {
+ this.CPI();
+ return ((this.F() & (byte)StatusBits.PF) != 0) && ((this.F() & (byte)StatusBits.ZF) == 0); // See CPI
+ }
+
+ private void CPD()
+ {
+ this.BlockCompare(this.HL()--, this.BC());
+ --this.MEMPTR();
+ }
+
+ private bool CPDR()
+ {
+ this.CPD();
+ return ((this.F() & (byte)StatusBits.PF) != 0) && ((this.F() & (byte)StatusBits.ZF) == 0); // See CPD
+ }
+
+ private void BlockLoad(Register16 source, Register16 destination, Register16 counter)
+ {
+ var value = this.Bus.Read(source);
+ this.Bus.Write(destination, value);
+ var xy = this.A() + value;
+ this.F() = SetFlag(this.F(), StatusBits.XF, xy & (int)Bits.Bit3);
+ this.F() = SetFlag(this.F(), StatusBits.YF, xy & (int)Bits.Bit1);
+ F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ this.F() = SetFlag(this.F(), StatusBits.PF, --counter.Word);
+ }
+
+ private void LDI()
+ {
+ this.BlockLoad(this.HL()++, this.DE()++, this.BC());
+ }
+
+ private bool LDIR()
+ {
+ this.LDI();
+ return (this.F() & (byte)StatusBits.PF) != 0; // See LDI
+ }
+
+ private void LDD()
+ {
+ this.BlockLoad(this.HL()--, this.DE()--, this.BC());
+ }
+
+ private bool LDDR()
+ {
+ this.LDD();
+ return (this.F() & (byte)StatusBits.PF) == 0; // See LDD
+ }
+
+ private void BlockIn(Register16 source, Register16 destination)
+ {
+ this.MEMPTR() = this.Bus.Address() = source;
+ var value = this.ReadPort();
+ this.Bus.Write(destination, value);
+ source.High = this.Decrement(source.High);
+ this.F() = SetFlag(this.F(), StatusBits.NF);
+ }
+
+ private void INI()
+ {
+ this.BlockIn(this.BC(), this.HL()++);
+ ++this.MEMPTR();
+ }
+
+ private bool INIR()
+ {
+ this.INI();
+ return (this.F() & (byte)StatusBits.ZF) == 0; // See INI
+ }
+
+ private void IND()
+ {
+ this.BlockIn(this.BC(), this.HL()--);
+ --this.MEMPTR();
+ }
+
+ private bool INDR()
+ {
+ this.IND();
+ return (this.F() & (byte)StatusBits.ZF) == 0; // See IND
+ }
+
+ private void BlockOut(Register16 source, Register16 destination)
+ {
+ var value = this.Bus.Read(source);
+ this.Bus.Address() = destination;
+ this.WritePort();
+ destination.High = this.Decrement(destination.High);
+ this.MEMPTR() = destination;
+ this.F() = SetFlag(this.F(), StatusBits.NF, value & (byte)Bits.Bit7);
+ this.F() = SetFlag(this.F(), StatusBits.HC | StatusBits.CF, (this.L() + value) > 0xff);
+ this.F() = AdjustParity(this.F(), (byte)(((value + this.L()) & (int)Mask.Mask3) ^ this.B()));
+ }
+
+ private void OUTI()
+ {
+ this.BlockOut(this.HL()++, this.BC());
+ ++this.MEMPTR();
+ }
+
+ private bool OTIR()
+ {
+ this.OUTI();
+ return (this.F() & (byte)StatusBits.ZF) == 0; // See OUTI
+ }
+
+ private void OUTD()
+ {
+ this.BlockOut(this.HL()--, this.BC());
+ --this.MEMPTR();
+ }
+
+ private bool OTDR()
+ {
+ this.OUTD();
+ return (this.F() & (byte)StatusBits.ZF) == 0; // See OUTD
+ }
+
+ private void NEG()
+ {
+ this.F() = SetFlag(this.F(), StatusBits.PF, this.A() == (byte)Bits.Bit7);
+ this.F() = SetFlag(this.F(), StatusBits.CF, this.A());
+ this.F() = SetFlag(this.F(), StatusBits.NF);
+
+ var original = this.A();
+
+ this.A() = (byte)(~this.A() + 1); // two's complement
+
+ this.F() = AdjustHalfCarrySub(this.F(), (byte)0, original, this.A());
+ this.F() = AdjustOverflowSub(this.F(), (byte)0, original, this.A());
+
+ this.F() = AdjustSZXY(this.F(), this.A());
+ }
+
+ private void RRD()
+ {
+ this.MEMPTR().Word = this.Bus.Address().Word = this.HL().Word;
+ ++this.MEMPTR();
+ var memory = this.Bus.Read();
+ this.Bus.Write((byte)(PromoteNibble(this.A()) | HighNibble(memory)));
+ this.A() = (byte)(HigherNibble(this.A()) | LowerNibble(memory));
+ this.F() = AdjustSZPXY(this.F(), this.A());
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ }
+
+ private void RLD()
+ {
+ this.MEMPTR().Word = this.Bus.Address().Word = this.HL().Word;
+ ++this.MEMPTR();
+ var memory = this.Bus.Read();
+ this.Bus.Write((byte)(PromoteNibble(memory) | LowNibble(this.A())));
+ this.A() = (byte)(HigherNibble(this.A()) | HighNibble(memory));
+ AdjustSZPXY(this.F(), this.A());
+ this.F() = ClearFlag(this.F(), StatusBits.NF | StatusBits.HC);
+ }
+
+ private void WritePort(byte port)
+ {
+ this.MEMPTR().Word = this.Bus.Address().Word = new Register16(port, this.A()).Word;
+ this.Bus.Data = this.A();
+ this.WritePort();
+ ++this.MEMPTR().Low;
+ }
+
+ private void WritePort()
+ {
+ this.ports.Write(this.Bus.Address().Low, this.Bus.Data);
+ }
+
+ private byte ReadPort(byte port)
+ {
+ this.MEMPTR().Word = this.Bus.Address().Word = new Register16(port, this.A()).Word;
+ ++this.MEMPTR().Low;
+ return this.ReadPort();
+ }
+
+ private byte ReadPort()
+ {
+ return this.Bus.Data = this.ports.Read(this.Bus.Address().Low);
+ }
+ }
+}
diff --git a/Z80/Z80.csproj b/Z80/Z80.csproj
new file mode 100644
index 0000000..f42552e
--- /dev/null
+++ b/Z80/Z80.csproj
@@ -0,0 +1,58 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C00648C1-BAC1-4EFB-816F-E87C091619D7}
+ Library
+ Properties
+ Z80
+ Z80
+ v4.7.2
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ latest
+ AllRules.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6ebf8857-62a3-4ef4-af21-c1844031d7e4}
+ EightBit
+
+
+
+
\ No newline at end of file