From ea82c587770a4ed773950ae1896a537366e5bcf4 Mon Sep 17 00:00:00 2001 From: Adrian Conlon Date: Sat, 16 Feb 2019 21:32:34 +0000 Subject: [PATCH] Add Z80 processor (untested, but complete) Signed-off-by: Adrian Conlon --- EightBit.sln | 14 + EightBit/Chip.cs | 18 + EightBit/IntelOpCodeDecoded.cs | 26 +- EightBit/IntelProcessor.cs | 22 + EightBit/Mask.cs | 2 - EightBit/Register16.cs | 2 +- Z80/Properties/AssemblyInfo.cs | 36 + Z80/RefreshRegister.cs | 59 + Z80/StatusBits.cs | 39 + Z80/Z80.cs | 1951 ++++++++++++++++++++++++++++++++ Z80/Z80.csproj | 58 + 11 files changed, 2213 insertions(+), 14 deletions(-) create mode 100644 Z80/Properties/AssemblyInfo.cs create mode 100644 Z80/RefreshRegister.cs create mode 100644 Z80/StatusBits.cs create mode 100644 Z80/Z80.cs create mode 100644 Z80/Z80.csproj 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