// // Copyright (c) Adrian Conlon. All rights reserved. // namespace Z80 { using EightBit; public class Z80 : IntelProcessor { public Z80(Bus bus, InputOutput ports) : base(bus) { this._ports = ports; this.RaisedPOWER += this.Z80_RaisedPOWER; this.RaisedRFSH += this.Z80_RaisedRFSH; this.ExecutingInstruction += this.Z80_ExecutingInstruction; this.ExecutedInstruction += this.Z80_ExecutedInstruction; } private readonly InputOutput _ports; public InputOutput Ports => this._ports; private readonly Register16[] _accumulatorFlags = [new Register16(), new Register16()]; private readonly Register16[][] _registers = [ [new Register16(), new Register16(), new Register16()], [new Register16(), new Register16(), new Register16()] ]; private RefreshRegister _refresh = new(0x7f); private bool _prefixCB; private bool _prefixDD; private bool _prefixED; private bool _prefixFD; private int _accumulatorFlagsSet; private int _registerSet; private sbyte _displacement; private bool _displaced; public byte IV { get; set; } = 0xff; public int IM { get; set; } public bool IFF1 { get; set; } public bool IFF2 { get; set; } public byte Q { get; set; } // Previously modified instruction status register private byte _modifiedF; // In-flight status register. Used to build "Q" public override Register16 AF => this._accumulatorFlags[this._accumulatorFlagsSet]; private Register16[] CurrentRegisterSet => this._registers[this._registerSet]; public override Register16 BC => this.CurrentRegisterSet[(int)RegisterIndex.IndexBC]; public override Register16 DE => this.CurrentRegisterSet[(int)RegisterIndex.IndexDE]; public override Register16 HL => this.CurrentRegisterSet[(int)RegisterIndex.IndexHL]; public Register16 IX { get; } = new(0xffff); public byte IXH { get => this.IX.High; set => this.IX.High = value; } public byte IXL { get => this.IX.Low; set => this.IX.Low = value; } public Register16 IY { get; } = new(0xffff); public byte IYH { get => this.IY.High; set => this.IY.High = value; } public byte IYL { get => this.IY.Low; set => this.IY.Low = value; } // ** From the Z80 CPU User Manual // Memory Refresh(R) Register.The Z80 CPU contains a memory _refresh counter, // enabling dynamic memories to be used with the same ease as static memories.Seven bits // of this 8-bit register are automatically incremented after each instruction fetch.The eighth // bit remains as programmed, resulting from an LD R, A instruction. The data in the _refresh // counter is sent out on the lower portion of the address bus along with a _refresh control // signal while the CPU is decoding and executing the fetched instruction. This mode of _refresh // is transparent to the programmer and does not slow the CPU operation.The programmer // can load the R register for testing purposes, but this register is normally not used by the // programmer. During _refresh, the contents of the I Register are placed on the upper eight // bits of the address bus. public ref RefreshRegister REFRESH => ref this._refresh; private void DisplaceAddress() { var displacement = (this._prefixDD ? this.IX : this.IY).Word + this._displacement; this.MEMPTR.Word = (ushort)displacement; } public void Exx() => this._registerSet ^= 1; public void ExxAF() => this._accumulatorFlagsSet ^= 1; public override void Execute() { 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; 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); } } private void Z80_ExecutingInstruction(object? sender, EventArgs e) { this._modifiedF = 0; this._displaced = this._prefixCB = this._prefixDD = this._prefixED = this._prefixFD = false; } private void Z80_ExecutedInstruction(object? sender, EventArgs e) { this.Q = this._modifiedF; } public override void PoweredStep() { var handled = false; if (this.RESET.Lowered()) { this.HandleRESET(); handled = true; } else if (this.NMI.Lowered()) { this.HandleNMI(); handled = true; } else if (this.INT.Lowered()) { this.RaiseINT(); this.RaiseHALT(); if (this.IFF1) { this.HandleINT(); handled = true; } } if (!handled) { // ** From the Z80 CPU User Manual // When a software HALT instruction is executed, the CPU executes NOPs until an interrupt // is received(either a nonmaskable or a maskable interrupt while the interrupt flip-flop is // enabled). The two interrupt lines are sampled with the rising clock edge during each T4 // state as depicted in Figure 11.If a nonmaskable interrupt is received or a maskable interrupt // is received and the interrupt enable flip-flop is set, then the HALT state is exited on // the next rising clock edge.The following cycle is an interrupt acknowledge cycle corresponding // to the type of interrupt that was received.If both are received at this time, then // the nonmaskable interrupt is acknowledged because it is the highest priority.The purpose // of executing NOP instructions while in the HALT state is to keep the memory _refresh signals // active.Each cycle in the HALT state is a normal M1(fetch) cycle except that the data // received from the memory is ignored and an NOP instruction is forced internally to the // CPU.The HALT acknowledge signal is active during this time indicating that the processor // is in the HALT state. this.Execute(this.FetchInstruction()); } } private void Z80_RaisedPOWER(object? sender, EventArgs e) { this.RaiseM1(); this.RaiseRFSH(); this.RaiseIORQ(); this.RaiseMREQ(); this.RaiseRD(); this.RaiseWR(); this.DisableInterrupts(); this.IM = 0; this.REFRESH = 0; this.IV = (byte)Mask.Eight; this.IX.Word = this.IY.Word = (ushort)Mask.Sixteen; // One of the register sets has been initialised // (by the IntelProcessor base class) // so now lets initialise the other register set. this.Exx(); this.ExxAF(); this.ResetRegisterSet(); } private void Z80_RaisedRFSH(object? sender, EventArgs e) { ++this.REFRESH; } #region Z80 specific pins #region NMI pin public event EventHandler? RaisingNMI; public event EventHandler? RaisedNMI; public event EventHandler? LoweringNMI; public event EventHandler? LoweredNMI; private PinLevel _nmiLine = PinLevel.Low; public ref PinLevel NMI => ref this._nmiLine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseNMI() { if (this.NMI.Lowered()) { RaisingNMI?.Invoke(this, EventArgs.Empty); this.NMI.Raise(); RaisedNMI?.Invoke(this, EventArgs.Empty); } } public virtual void LowerNMI() { if (this.NMI.Raised()) { LoweringNMI?.Invoke(this, EventArgs.Empty); this.NMI.Lower(); LoweredNMI?.Invoke(this, EventArgs.Empty); } } #endregion #region M1 pin public event EventHandler? RaisingM1; public event EventHandler? RaisedM1; public event EventHandler? LoweringM1; public event EventHandler? LoweredM1; private PinLevel _m1Line = PinLevel.Low; public ref PinLevel M1 => ref this._m1Line; protected virtual void OnLoweringM1() => LoweringM1?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweredM1() => LoweredM1?.Invoke(this, EventArgs.Empty); [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseM1() { if (this.M1.Lowered()) { RaisingM1?.Invoke(this, EventArgs.Empty); this.M1.Raise(); RaisedM1?.Invoke(this, EventArgs.Empty); } } public virtual void LowerM1() { if (this.M1.Raised()) { LoweringM1?.Invoke(this, EventArgs.Empty); this.M1.Lower(); LoweredM1?.Invoke(this, EventArgs.Empty); } } #endregion #region RFSH pin public event EventHandler? RaisingRFSH; public event EventHandler? RaisedRFSH; public event EventHandler? LoweringRFSH; public event EventHandler? LoweredRFSH; // ** From the Z80 CPU User Manual // RFSH.Refresh(output, active Low). RFSH, together with MREQ, indicates that the lower // seven bits of the system’s address bus can be used as a _refresh address to the system’s // dynamic memories. private PinLevel _refreshLine = PinLevel.Low; public ref PinLevel RFSH => ref this._refreshLine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseRFSH() { if (this.RFSH.Lowered()) { RaisingRFSH?.Invoke(this, EventArgs.Empty); this.RFSH.Raise(); RaisedRFSH?.Invoke(this, EventArgs.Empty); } } public virtual void LowerRFSH() { if (this.RFSH.Raised()) { LoweringRFSH?.Invoke(this, EventArgs.Empty); this.RFSH.Lower(); LoweredRFSH?.Invoke(this, EventArgs.Empty); } } #endregion #region MREQ pin public event EventHandler? RaisingMREQ; public event EventHandler? RaisedMREQ; public event EventHandler? LoweringMREQ; public event EventHandler? LoweredMREQ; private PinLevel _memoryRequestLine = PinLevel.Low; public ref PinLevel MREQ => ref this._memoryRequestLine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseMREQ() { if (this.MREQ.Lowered()) { RaisingMREQ?.Invoke(this, EventArgs.Empty); this.MREQ.Raise(); RaisedMREQ?.Invoke(this, EventArgs.Empty); } } public virtual void LowerMREQ() { if (this.MREQ.Raised()) { LoweringMREQ?.Invoke(this, EventArgs.Empty); this.MREQ.Lower(); LoweredMREQ?.Invoke(this, EventArgs.Empty); } } #endregion #region IORQ pin public event EventHandler? RaisingIORQ; public event EventHandler? RaisedIORQ; public event EventHandler? LoweringIORQ; public event EventHandler? LoweredIORQ; private PinLevel _ioRequestLine = PinLevel.Low; public ref PinLevel IORQ => ref this._ioRequestLine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseIORQ() { if (this.IORQ.Lowered()) { RaisingIORQ?.Invoke(this, EventArgs.Empty); this.IORQ.Raise(); RaisedIORQ?.Invoke(this, EventArgs.Empty); } } public virtual void LowerIORQ() { if (this.IORQ.Raised()) { LoweringIORQ?.Invoke(this, EventArgs.Empty); this.IORQ.Lower(); LoweredIORQ?.Invoke(this, EventArgs.Empty); } } #endregion #region RD pin public event EventHandler? RaisingRD; public event EventHandler? RaisedRD; public event EventHandler? LoweringRD; public event EventHandler? LoweredRD; private PinLevel _rdLine = PinLevel.Low; public ref PinLevel RD => ref this._rdLine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseRD() { if (this.RD.Lowered()) { RaisingRD?.Invoke(this, EventArgs.Empty); this.RD.Raise(); RaisedRD?.Invoke(this, EventArgs.Empty); } } public virtual void LowerRD() { if (this.RD.Raised()) { LoweringRD?.Invoke(this, EventArgs.Empty); this.RD.Lower(); LoweredRD?.Invoke(this, EventArgs.Empty); } } #endregion #region WR pin public event EventHandler? RaisingWR; public event EventHandler? RaisedWR; public event EventHandler? LoweringWR; public event EventHandler? LoweredWR; private PinLevel _wrLine = PinLevel.Low; public ref PinLevel WR => ref this._wrLine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] public virtual void RaiseWR() { if (this.WR.Lowered()) { RaisingWR?.Invoke(this, EventArgs.Empty); this.WR.Raise(); RaisedWR?.Invoke(this, EventArgs.Empty); } } public virtual void LowerWR() { if (this.WR.Raised()) { LoweringWR?.Invoke(this, EventArgs.Empty); this.WR.Lower(); LoweredWR?.Invoke(this, EventArgs.Empty); } } #endregion #endregion protected void MemoryUpdate(int ticks) { this.OnWritingMemory(); this.LowerMREQ(); this.LowerWR(); this.Tick(ticks); base.MemoryWrite(); this.RaiseWR(); this.RaiseMREQ(); this.OnWroteMemory(); } protected override void MemoryWrite() { this.MemoryUpdate(3); } protected override byte MemoryRead() { this.OnReadingMemory(); this.Tick(); this.LowerMREQ(); this.LowerRD(); this.Tick(); var returned = base.MemoryRead(); this.RaiseRD(); this.RaiseMREQ(); if (this.M1.Lowered()) { this.Bus.Address.Assign(this.REFRESH, this.IV); this.LowerRFSH(); this.Tick(); this.LowerMREQ(); this.RaiseMREQ(); this.RaiseRFSH(); } this.Tick(); this.OnReadMemory(); return returned; } // From Zilog Z80 manual // https://www.zilog.com/docs/z80/um0080.pdf // // RESET. Reset (input, active Low). // _____ // RESET initializes the CPU as follows: it resets the // interrupt enable flip-flop, clears the Program Counter and registers I and R, and sets the // interrupt status to Mode 0. During reset time, the address and data bus enter a high-impedance // state, and all control output signals enter an inactive state.RESET must be active for // a minimum of three full clock cycles before a reset operation is complete. protected override void HandleRESET() { base.HandleRESET(); this.IV = this.REFRESH = 0; this.IM = 0; this.SP.Word = this.AF.Word = (ushort)Mask.Sixteen; this.Tick(3); } protected override void HandleINT() { base.HandleINT(); this.LowerM1(); this.LowerIORQ(); var data = this.Bus.Data; this.RaiseIORQ(); this.RaiseM1(); this.DisableInterrupts(); this.Tick(5); switch (this.IM) { case 0: // i8080 equivalent this.Execute(data); break; case 1: this.Tick(); this.Restart(7 << 3); // 7 cycles break; case 2: this.Tick(7); this.MEMPTR.Assign(data, this.IV); this.Call(this.MEMPTR); break; default: throw new NotSupportedException("Invalid interrupt mode"); } } protected override void Call(Register16 destination) { this.Tick(); base.Call(destination); } protected override void JumpRelative(sbyte offset) { base.JumpRelative(offset); this.Tick(5); } private int Zero() => ZeroTest(this.F); private int Carry() => CarryTest(this.F); private int Parity() => ParityTest(this.F); private int Sign() => SignTest(this.F); private int HalfCarry() => HalfCarryTest(this.F); private int Subtracting() => SubtractingTest(this.F); private static int ZeroTest(byte data) => data & (byte)StatusBits.ZF; private static int CarryTest(byte data) => data & (byte)StatusBits.CF; private static int ParityTest(byte data) => data & (byte)StatusBits.PF; private static int SignTest(byte data) => data & (byte)StatusBits.SF; private static int HalfCarryTest(byte data) => data & (byte)StatusBits.HC; private static int SubtractingTest(byte data) => data & (byte)StatusBits.NF; private static int XTest(byte data) => data & (byte)StatusBits.XF; private static int YTest(byte data) => data & (byte)StatusBits.YF; private void AdjustStatusFlags(byte value) => this._modifiedF = this.F = value; private void SetBit(StatusBits flag) => this.AdjustStatusFlags(SetBit(this.F, flag)); private static byte SetBit(byte f, StatusBits flag) => SetBit(f, (byte)flag); private void SetBit(StatusBits flag, int condition) => this.AdjustStatusFlags(SetBit(this.F, flag, condition)); private static byte SetBit(byte f, StatusBits flag, int condition) => SetBit(f, (byte)flag, condition); private void SetBit(StatusBits flag, bool condition) => this.AdjustStatusFlags(SetBit(this.F, flag, condition)); private static byte SetBit(byte f, StatusBits flag, bool condition) => SetBit(f, (byte)flag, condition); private void ClearBit(StatusBits flag) => this.AdjustStatusFlags(ClearBit(this.F, flag)); private static byte ClearBit(byte f, StatusBits flag) => ClearBit(f, (byte)flag); private void ClearBit(StatusBits flag, int condition) => this.AdjustStatusFlags(ClearBit(this.F, flag, condition)); private static byte ClearBit(byte f, StatusBits flag, int condition) => ClearBit(f, (byte)flag, condition); private void AdjustSign(byte value) => this.AdjustStatusFlags(AdjustSign(this.F, value)); private static byte AdjustSign(byte input, byte value) => SetBit(input, StatusBits.SF, SignTest(value)); private void AdjustZero(byte value) => this.AdjustStatusFlags(AdjustZero(this.F, value)); private static byte AdjustZero(byte input, byte value) => ClearBit(input, StatusBits.ZF, value); private void AdjustParity(byte value) => this.AdjustStatusFlags(AdjustParity(this.F, value)); private static byte AdjustParity(byte input, byte value) => SetBit(input, StatusBits.PF, EvenParity(value)); private void AdjustSZ(byte value) => this.AdjustStatusFlags(AdjustSZ(this.F, value)); private static byte AdjustSZ(byte input, byte value) { input = AdjustSign(input, value); return AdjustZero(input, value); } private void AdjustSZP(byte value) => this.AdjustStatusFlags(AdjustSZP(this.F, value)); private static byte AdjustSZP(byte input, byte value) { input = AdjustSZ(input, value); return AdjustParity(input, value); } private void AdjustXY(byte value) => this.AdjustStatusFlags(AdjustXY(this.F, value)); private static byte AdjustXY(byte input, byte value) { input = SetBit(input, StatusBits.XF, XTest(value)); return SetBit(input, StatusBits.YF, YTest(value)); } private void AdjustSZPXY(byte value) => this.AdjustStatusFlags(AdjustSZPXY(this.F, value)); private static byte AdjustSZPXY(byte input, byte value) { input = AdjustSZP(input, value); return AdjustXY(input, value); } private void AdjustSZXY(byte value) => this.AdjustStatusFlags(AdjustSZXY(this.F, value)); private static byte AdjustSZXY(byte input, byte value) { input = AdjustSZ(input, value); return AdjustXY(input, value); } private void AdjustHalfCarryAdd(byte before, byte value, int calculation) => this.AdjustStatusFlags(AdjustHalfCarryAdd(this.F, before, value, calculation)); private static byte AdjustHalfCarryAdd(byte input, byte before, byte value, int calculation) => SetBit(input, StatusBits.HC, CalculateHalfCarryAdd(before, value, calculation)); private void AdjustHalfCarrySub(byte before, byte value, int calculation) => this.AdjustStatusFlags(AdjustHalfCarrySub(this.F, before, value, calculation)); private static byte AdjustHalfCarrySub(byte input, byte before, byte value, int calculation) => SetBit(input, StatusBits.HC, CalculateHalfCarrySub(before, value, calculation)); private void AdjustOverflowAdd(int beforeNegative, int valueNegative, int afterNegative) => this.AdjustStatusFlags(AdjustOverflowAdd(this.F, beforeNegative, valueNegative, afterNegative)); private static byte AdjustOverflowAdd(byte input, int beforeNegative, int valueNegative, int afterNegative) { var overflow = beforeNegative == valueNegative && beforeNegative != afterNegative; return SetBit(input, StatusBits.VF, overflow); } private void AdjustOverflowAdd(byte before, byte value, byte calculation) => this.AdjustStatusFlags(AdjustOverflowAdd(this.F, before, value, calculation)); private static byte AdjustOverflowAdd(byte input, byte before, byte value, byte calculation) => AdjustOverflowAdd(input, SignTest(before), SignTest(value), SignTest(calculation)); private void AdjustOverflowSub(int beforeNegative, int valueNegative, int afterNegative) => this.AdjustStatusFlags(AdjustOverflowSub(this.F, beforeNegative, valueNegative, afterNegative)); private static byte AdjustOverflowSub(byte input, int beforeNegative, int valueNegative, int afterNegative) { var overflow = beforeNegative != valueNegative && beforeNegative != afterNegative; return SetBit(input, StatusBits.VF, overflow); } private void AdjustOverflowSub(byte before, byte value, byte calculation) => this.AdjustStatusFlags(AdjustOverflowSub(this.F, before, value, calculation)); private static byte AdjustOverflowSub(byte input, byte before, byte value, byte calculation) => AdjustOverflowSub(input, SignTest(before), SignTest(value), SignTest(calculation)); private static byte RES(int n, byte operand) => ClearBit(operand, Bit(n)); private static byte SET(int n, byte operand) => SetBit(operand, Bit(n)); protected override void DisableInterrupts() => this.IFF1 = this.IFF2 = false; protected override void EnableInterrupts() => this.IFF1 = this.IFF2 = true; private Register16 HL2() => this._prefixDD ? this.IX : this._prefixFD ? this.IY : this.HL; private Register16 RP(int rp) => rp switch { 0 => this.BC, 1 => this.DE, 2 => this.HL2(), 3 => this.SP, _ => throw new ArgumentOutOfRangeException(nameof(rp)), }; private Register16 RP2(int rp) => rp switch { 0 => this.BC, 1 => this.DE, 2 => this.HL2(), 3 => this.AF, _ => throw new ArgumentOutOfRangeException(nameof(rp)), }; private ref byte R(int r, AccessLevel access = AccessLevel.ReadOnly) { switch (r) { case 0: return ref this.B; case 1: return ref this.C; case 2: return ref this.D; case 3: return ref this.E; case 4: return ref this.HL2().High; case 5: return ref this.HL2().Low; case 6: if (this._displaced) { this.DisplaceAddress(); this.Bus.Address.Assign(this.MEMPTR); } else { this.Bus.Address.Assign(this.HL); } switch (access) { case AccessLevel.ReadOnly: this.MemoryRead(); break; case AccessLevel.WriteOnly: this.Tick(); break; default: throw new NotSupportedException("Invalid access level"); } // Will need a post-MemoryWrite return ref this.Bus.Data; case 7: return ref this.A; default: throw new ArgumentOutOfRangeException(nameof(r)); } } private void R(int r, byte value, int ticks = 0) { this.R(r, AccessLevel.WriteOnly) = value; if (r == 6) { this.Tick(ticks); this.MemoryUpdate(1); this.Tick(); } } private ref byte R2(int r) { switch (r) { case 0: return ref this.B; case 1: return ref this.C; case 2: return ref this.D; case 3: return ref this.E; case 4: return ref this.H; case 5: return ref this.L; case 6: // N.B. Write not possible, when r == 6 _ = this.MemoryRead(this.HL); return ref this.Bus.Data; case 7: return ref this.A; default: throw new ArgumentOutOfRangeException(nameof(r)); } } private void ExecuteCB(int x, int y, int z) { var memoryZ = z == 6; var indirect = (!this._displaced && memoryZ) || this._displaced; var direct = !indirect; byte operand; if (this._displaced) { this.Tick(2); this.DisplaceAddress(); operand = this.MemoryRead(this.MEMPTR); } else { operand = this.R(z); } var update = x != 1; // BIT does not update switch (x) { case 0: // rot[y] r[z] operand = y switch { 0 => this.RLC(operand), 1 => this.RRC(operand), 2 => this.RL(operand), 3 => this.RR(operand), 4 => this.SLA(operand), 5 => this.SRA(operand), 6 => this.SLL(operand), 7 => this.SRL(operand), _ => throw new NotSupportedException("Invalid operation mode"), }; this.AdjustSZP(operand); break; case 1: // BIT y, r[z] this.BIT(y, operand); this.AdjustXY(direct ? operand : this.MEMPTR.High); if (indirect) this.Tick(); break; case 2: // RES y, r[z] operand = RES(y, operand); break; case 3: // SET y, r[z] operand = SET(y, operand); break; default: throw new NotSupportedException("Invalid operation mode"); } if (update) { if (this._displaced) { this.Tick(); this.MemoryWrite(operand); if (!memoryZ) { this.R2(z) = operand; } } else { if (memoryZ) this.Tick(); this.R(z, operand); } } } private void ExecuteED(int x, int y, int z, int p, int q) { switch (x) { case 0: case 3: // Invalid instruction, equivalent to NONI followed by NOP break; case 1: switch (z) { case 0: // Input from port with 16-bit address this.Bus.Address.Assign(this.BC); this.MEMPTR.Assign(this.Bus.Address); this.MEMPTR.Word++; this.ReadPort(); if (y != 6) { this.R(y, AccessLevel.WriteOnly) = this.Bus.Data; // IN r[y],(C) } this.AdjustSZPXY(this.Bus.Data); this.ClearBit(StatusBits.NF | StatusBits.HC); break; case 1: // Output to port with 16-bit address this.WritePort(this.BC, y == 6 ? (byte)0 : this.R(y)); this.MEMPTR.Word++; break; case 2: // 16-bit add/subtract with carry this.HL2().Assign(q switch { 0 => this.SBC(this.HL2(), this.RP(p)), // SBC HL, rp[p] 1 => this.ADC(this.HL2(), this.RP(p)), // ADC HL, rp[p] _ => throw new NotSupportedException("Invalid operation mode"), }); this.Tick(7); break; case 3: // Retrieve/store register pair from/to immediate address this.FetchWordAddress(); switch (q) { case 0: // LD (nn), rp[p] this.SetWord(this.RP(p)); break; case 1: // LD rp[p], (nn) this.RP(p).Assign(this.GetWord()); break; default: throw new NotSupportedException("Invalid operation mode"); } break; case 4: // Negate accumulator this.NEG(); break; case 5: // Return from interrupt switch (y) { case 1: this.RetI(); // RETI break; default: this.RetN(); // RETN break; } break; case 6: // Set interrupt mode this.IM = y switch { 0 or 1 or 4 or 5 => 0, 2 or 6 => 1, 3 or 7 => 2, _ => throw new NotSupportedException("Invalid operation mode"), }; break; case 7: // Assorted ops switch (y) { case 0: // LD I,A this.IV = this.A; this.Tick(); break; case 1: // LD R,A this.REFRESH = this.A; this.Tick(); break; case 2: // LD A,I this.AdjustSZXY(this.A = this.IV); this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.PF, this.IFF2); this.Tick(); break; case 3: // LD A,R this.AdjustSZXY(this.A = this.REFRESH); this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.PF, this.IFF2); this.Tick(); break; case 4: // RRD this.RRD(); break; case 5: // RLD this.RLD(); break; case 6: // NOP case 7: // NOP 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 this.LDIR(); break; case 7: // LDDR this.LDDR(); break; default: break; } break; case 1: // CP switch (y) { case 4: // CPI this.CPI(); break; case 5: // CPD this.CPD(); break; case 6: // CPIR this.CPIR(); break; case 7: // CPDR this.CPDR(); break; default: break; } break; case 2: // IN switch (y) { case 4: // INI this.INI(); break; case 5: // IND this.IND(); break; case 6: // INIR this.INIR(); break; case 7: // INDR this.INDR(); break; default: break; } break; case 3: // OUT switch (y) { case 4: // OUTI this.OUTI(); break; case 5: // OUTD this.OUTD(); break; case 6: // OTIR this.OTIR(); break; case 7: // OTDR this.OTDR(); break; default: break; } break; default: break; } break; default: 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 break; case 1: // EX AF AF' this.ExxAF(); break; case 2: // DJNZ d this.Tick(); _ = this.JumpRelativeConditional(--this.B != 0); break; case 3: // JR d this.JumpRelative(this.FetchByte()); break; case 4: // JR cc,d case 5: case 6: case 7: _ = this.JumpRelativeConditionalFlag(y - 4); 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).Assign(this.FetchWord()); break; case 1: // ADD HL,rp this.HL2().Assign(this.Add(this.HL2(), this.RP(p))); this.Tick(7); 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.Bus.Address.Assign(this.BC); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; this.MEMPTR.High = this.Bus.Data = this.A; this.MemoryWrite(); break; case 1: // LD (DE),A this.Bus.Address.Assign(this.DE); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; this.MEMPTR.High = this.Bus.Data = this.A; this.MemoryWrite(); break; case 2: // LD (nn),HL this.FetchWordAddress(); this.SetWord(this.HL2()); break; case 3: // LD (nn),A this.FetchWordMEMPTR(); this.Bus.Address.Assign(this.MEMPTR); ++this.MEMPTR.Word; this.MEMPTR.High = this.Bus.Data = this.A; this.MemoryWrite(); break; default: throw new NotSupportedException("Invalid operation mode"); } break; case 1: switch (p) { case 0: // LD A,(BC) this.Bus.Address.Assign(this.BC); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; this.A = this.MemoryRead(); break; case 1: // LD A,(DE) this.Bus.Address.Assign(this.DE); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; this.A = this.MemoryRead(); break; case 2: // LD HL,(nn) this.FetchWordAddress(); this.HL2().Assign(this.GetWord()); break; case 3: // LD A,(nn) this.FetchWordMEMPTR(); this.Bus.Address.Assign(this.MEMPTR); ++this.MEMPTR.Word; this.A = this.MemoryRead(); 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).Word; break; case 1: // DEC rp --this.RP(p).Word; break; default: throw new NotSupportedException("Invalid operation mode"); } this.Tick(2); break; case 4: // 8-bit INC if (memoryY && this._displaced) { this.FetchDisplacement(); this.Tick(5); } this.R(y, this.Increment(this.R(y)), 1); break; case 5: // 8-bit DEC if (memoryY && this._displaced) { this.FetchDisplacement(); this.Tick(5); } this.R(y, this.Decrement(this.R(y)), 1); break; case 6: // 8-bit load immediate { var displacing = memoryY && this._displaced; if (displacing) { this.FetchDisplacement(); } var value = this.FetchByte(); if (displacing) { this.Tick(2); } this.R(y, value); // LD r,n 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"); } break; default: throw new NotSupportedException("Invalid operation mode"); } break; case 1: // 8-bit loading if (!(memoryZ && memoryY)) { var normal = true; if (this._displaced) { if (memoryZ || memoryY) { this.FetchDisplacement(); this.Tick(5); } if (memoryZ) { switch (y) { case 4: this.H = this.R(z); normal = false; break; case 5: this.L = this.R(z); normal = false; break; default: 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; default: break; } } } if (normal) { var value = this.R(z); this.R(y, value); } } else { this.LowerHALT(); // Exception (replaces LD (HL), (HL)) } break; case 2: { // Operate on accumulator and register/memory location if (memoryZ && this._displaced) { this.FetchDisplacement(); this.Tick(5); } var value = this.R(z); switch (y) { case 0: // ADD A,r this.A = this.Add(this.A, value); break; case 1: // ADC A,r this.A = this.ADC(this.A, value); break; case 2: // SUB r this.A = this.SUB(this.A, value); break; case 3: // SBC A,r this.A = this.SBC(this.A, 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"); } break; } case 3: switch (z) { case 0: // Conditional return _ = this.ReturnConditionalFlag(y); break; case 1: // POP & various ops switch (q) { case 0: // POP rp2[p] this.RP2(p).Assign(this.PopWord()); break; case 1: switch (p) { case 0: // RET this.Return(); break; case 1: // EXX this.Exx(); break; case 2: // JP HL this.Jump(this.HL2()); break; case 3: // LD SP,HL this.SP.Assign(this.HL2()); this.Tick(2); break; default: throw new NotSupportedException("Invalid operation mode"); } break; default: throw new NotSupportedException("Invalid operation mode"); } break; case 2: // Conditional jump _ = this.JumpConditionalFlag(y); break; case 3: // Assorted operations switch (y) { case 0: // JP nn this.JumpIndirect(); break; case 1: // CB prefix this._prefixCB = true; if (this._displaced) { this.FetchDisplacement(); this.Execute(this.FetchByte()); } else { this.Execute(this.FetchInstruction()); } break; case 2: // OUT (n),A this.WritePort(this.FetchByte()); break; case 3: // IN A,(n) this.ReadPort(this.FetchByte()); this.A = this.Bus.Data; break; case 4: // EX (SP),HL this.XHTL(this.HL2()); break; case 5: // EX DE,HL { this.Intermediate.Assign(this.DE); this.DE.Assign(this.HL); this.HL.Assign(this.Intermediate); } break; case 6: // DI this.DisableInterrupts(); break; case 7: // EI this.EnableInterrupts(); break; default: throw new NotSupportedException("Invalid operation mode"); } break; case 4: // Conditional call: CALL cc[y], nn _ = this.CallConditionalFlag(y); break; case 5: // PUSH & various ops switch (q) { case 0: // PUSH rp2[p] this.Tick(); this.PushWord(this.RP2(p)); break; case 1: switch (p) { case 0: // CALL nn this.CallIndirect(); break; case 1: // DD prefix this._displaced = this._prefixDD = true; this.Execute(this.FetchInstruction()); break; case 2: // ED prefix this._prefixED = true; this.Execute(this.FetchInstruction()); break; case 3: // FD prefix this._displaced = this._prefixFD = true; this.Execute(this.FetchInstruction()); 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.A = this.Add(this.A, operand); break; case 1: // ADC A,n this.A = this.ADC(this.A, operand); break; case 2: // SUB n this.A = this.SUB(this.A, operand); break; case 3: // SBC A,n this.A = this.SBC(this.A, 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"); } break; } case 7: // Restart: RST y * 8 this.Restart((byte)(y << 3)); break; default: throw new NotSupportedException("Invalid operation mode"); } break; default: break; } } private void HandleNMI() { this.RaiseNMI(); this.RaiseHALT(); this.IFF2 = this.IFF1; this.IFF1 = false; this.LowerM1(); _ = this.Bus.Data; this.RaiseM1(); this.Restart(0x66); } private void FetchDisplacement() => this._displacement = (sbyte)this.FetchByte(); // ** From the Z80 CPU User Manual // Figure 5 depicts the timing during an M1 (op code fetch) cycle. The Program Counter is // placed on the address bus at the beginning of the M1 cycle. One half clock cycle later, the // MREQ signal goes active. At this time, the address to memory has had time to stabilize so // that the falling edge of MREQ can be used directly as a chip enable clock to dynamic // memories. The RD line also goes active to indicate that the memory read data should be // enabled onto the CPU data bus. The CPU samples the data from the memory space on the // data bus with the rising edge of the clock of state T3, and this same edge is used by the // CPU to turn off the RD and MREQ signals. As a result, the data is sampled by the CPU // before the RD signal becomes inactive. Clock states T3 and T4 of a fetch cycle are used to // _refresh dynamic memories. The CPU uses this time to decode and execute the fetched // instruction so that no other concurrent operation can be performed. protected override byte FetchInstruction() { this.LowerM1(); var returned = base.FetchInstruction(); this.RaiseM1(); return returned; } private byte Subtract(byte operand, byte value, int carry = 0) { this.Intermediate.Word = (ushort)(operand - value - carry); var result = this.Intermediate.Low; this.AdjustHalfCarrySub(operand, value, result); this.AdjustOverflowSub(operand, value, result); this.SetBit(StatusBits.NF); this.SetBit(StatusBits.CF, CarryTest(this.Intermediate.High)); this.AdjustSZ(result); return result; } private byte Increment(byte operand) { this.ClearBit(StatusBits.NF); var result = ++operand; this.AdjustSZXY(result); this.SetBit(StatusBits.VF, result == (byte)Bits.Bit7); this.ClearBit(StatusBits.HC, LowNibble(result)); return result; } private byte Decrement(byte operand) { this.SetBit(StatusBits.NF); this.ClearBit(StatusBits.HC, LowNibble(operand)); var result = --operand; this.AdjustSZXY(result); this.SetBit(StatusBits.VF, result == (byte)Mask.Seven); return result; } private void RetN() { this.Return(); this.IFF1 = this.IFF2; } private void RetI() => this.RetN(); protected sealed override bool ConvertCondition(int flag) => flag switch { 0 => this.Zero() == 0, 1 => this.Zero() != 0, 2 => this.Carry() == 0, 3 => this.Carry() != 0, 4 => this.Parity() == 0, 5 => this.Parity() != 0, 6 => this.Sign() == 0, 7 => this.Sign() != 0, _ => throw new ArgumentOutOfRangeException(nameof(flag)), }; protected sealed override bool ReturnConditionalFlag(int flag) { var condition = this.ConvertCondition(flag); this.Tick(); if (condition) { this.Return(); } return condition; } private Register16 SBC(Register16 operand, Register16 value) { var subtraction = operand.Word - value.Word - this.Carry(); this.Intermediate.Word = (ushort)subtraction; this.SetBit(StatusBits.NF); this.ClearBit(StatusBits.ZF, this.Intermediate.Word); this.SetBit(StatusBits.CF, subtraction & (int)Bits.Bit16); this.AdjustHalfCarrySub(operand.High, value.High, this.Intermediate.High); this.AdjustXY(this.Intermediate.High); var beforeNegative = SignTest(operand.High); var valueNegative = SignTest(value.High); var afterNegative = SignTest(this.Intermediate.High); this.SetBit(StatusBits.SF, afterNegative); this.AdjustOverflowSub(beforeNegative, valueNegative, afterNegative); this.MEMPTR.Word = (ushort)(operand.Word + 1); return this.Intermediate; } private Register16 ADC(Register16 operand, Register16 value) { _ = this.Add(operand, value, this.Carry()); this.ClearBit(StatusBits.ZF, this.Intermediate.Word); var beforeNegative = SignTest(operand.High); var valueNegative = SignTest(value.High); var afterNegative = SignTest(this.Intermediate.High); this.SetBit(StatusBits.SF, afterNegative); this.AdjustOverflowAdd(beforeNegative, valueNegative, afterNegative); return this.Intermediate; } private Register16 Add(Register16 operand, Register16 value, int carry = 0) { var addition = operand.Word + value.Word + carry; this.Intermediate.Word = (ushort)addition; this.ClearBit(StatusBits.NF); this.SetBit(StatusBits.CF, addition & (int)Bits.Bit16); this.AdjustHalfCarryAdd(operand.High, value.High, this.Intermediate.High); this.AdjustXY(this.Intermediate.High); this.MEMPTR.Word = (ushort)(operand.Word + 1); return this.Intermediate; } private byte Add(byte operand, byte value, int carry = 0) { this.Intermediate.Word = (ushort)(operand + value + carry); var result = this.Intermediate.Low; this.AdjustHalfCarryAdd(operand, value, result); this.AdjustOverflowAdd(operand, value, result); this.ClearBit(StatusBits.NF); this.SetBit(StatusBits.CF, CarryTest(this.Intermediate.High)); this.AdjustSZXY(result); return result; } private byte ADC(byte operand, byte value) => this.Add(operand, value, this.Carry()); private byte SUB(byte operand, byte value, int carry = 0) { var subtraction = this.Subtract(operand, value, carry); this.AdjustXY(subtraction); return subtraction; } private byte SBC(byte operand, byte value) => this.SUB(operand, value, this.Carry()); private void AndR(byte value) { this.SetBit(StatusBits.HC); this.ClearBit(StatusBits.CF | StatusBits.NF); this.AdjustSZPXY(this.A &= value); } private void XorR(byte value) { this.ClearBit(StatusBits.HC | StatusBits.CF | StatusBits.NF); this.AdjustSZPXY(this.A ^= value); } private void OrR(byte value) { this.ClearBit(StatusBits.HC | StatusBits.CF | StatusBits.NF); this.AdjustSZPXY(this.A |= value); } private void Compare(byte value) { _ = this.Subtract(this.A, value); this.AdjustXY(value); } private byte RLC(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); var carry = operand & (byte)Bits.Bit7; this.SetBit(StatusBits.CF, carry); var result = (byte)((operand << 1) | (carry >> 7)); this.AdjustXY(result); return result; } private byte RRC(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); var carry = operand & (byte)Bits.Bit0; this.SetBit(StatusBits.CF, carry); var result = (byte)((operand >> 1) | (carry << 7)); this.AdjustXY(result); return result; } private byte RL(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); var carry = this.Carry(); this.SetBit(StatusBits.CF, operand & (byte)Bits.Bit7); var result = (byte)((operand << 1) | carry); this.AdjustXY(result); return result; } private byte RR(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); var carry = this.Carry(); this.SetBit(StatusBits.CF, operand & (byte)Bits.Bit0); var result = (byte)((operand >> 1) | (carry << 7)); this.AdjustXY(result); return result; } private byte SLA(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.CF, operand & (byte)Bits.Bit7); var result = (byte)(operand << 1); this.AdjustXY(result); return result; } private byte SRA(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.CF, operand & (byte)Bits.Bit0); var result = (byte)((operand >> 1) | (operand & (byte)Bits.Bit7)); this.AdjustXY(result); return result; } private byte SLL(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.CF, operand & (byte)Bits.Bit7); var result = (byte)((operand << 1) | (byte)Bits.Bit0); this.AdjustXY(result); return result; } private byte SRL(byte operand) { this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.CF, operand & (byte)Bits.Bit0); var result = (byte)((operand >> 1) & ~(byte)Bits.Bit7); this.AdjustXY(result); this.SetBit(StatusBits.ZF, result); return result; } private void BIT(int n, byte operand) { this.SetBit(StatusBits.HC); this.ClearBit(StatusBits.NF); var discarded = (byte)(operand & Bit(n)); this.AdjustSZ(discarded); this.ClearBit(StatusBits.PF, discarded); } private void DAA() { var updated = this.A; var lowAdjust = this.HalfCarry() != 0 || LowNibble(this.A) > 9; var highAdjust = this.Carry() != 0 || this.A > 0x99; if (this.Subtracting() != 0) { if (lowAdjust) { updated -= 6; } if (highAdjust) { updated -= 0x60; } } else { if (lowAdjust) { updated += 6; } if (highAdjust) { updated += 0x60; } } this.AdjustStatusFlags((byte)((this.F & (byte)(StatusBits.CF | StatusBits.NF)) | (this.A > 0x99 ? (byte)StatusBits.CF : 0) | HalfCarryTest((byte)(this.A ^ updated)))); this.AdjustSZPXY(this.A = updated); } // https://worldofspectrum.org/forums/discussion/comment/666508/#Comment_666508 private void SCF() { this.SetBit(StatusBits.CF); this.ClearBit(StatusBits.HC | StatusBits.NF); this.AdjustXY((byte)((this.Q ^ this.F) | this.A)); } private void CCF() { this.ClearBit(StatusBits.NF); var carry = this.Carry(); this.SetBit(StatusBits.HC, carry); this.ClearBit(StatusBits.CF, carry); this.AdjustXY((byte)((this.Q ^ this.F) | this.A)); } private void CPL() { this.SetBit(StatusBits.HC | StatusBits.NF); this.AdjustXY(this.A = (byte)~this.A); } private void XHTL(Register16 exchange) { this.MEMPTR.Low = this.MemoryRead(this.SP); ++this.Bus.Address.Word; this.MEMPTR.High = this.MemoryRead(); this.Tick(); --this.Bus.Address.Word; this.Tick(); this.Bus.Data = exchange.Low; exchange.Low = this.MEMPTR.Low; this.MemoryUpdate(1); this.Tick(); ++this.Bus.Address.Word; this.Tick(); this.Bus.Data = exchange.High; exchange.High = this.MEMPTR.High; this.MemoryUpdate(1); this.Tick(3); } #region Block instructions private void RepeatBlockInstruction() { this.DecrementPC(); this.MEMPTR.Assign(this.PC); this.DecrementPC(); this.AdjustXY(this.PC.High); } #region Block compare private void BlockCompare() { _ = this.MemoryRead(this.HL); var result = (byte)(this.A - this.Bus.Data); this.SetBit(StatusBits.PF, --this.BC.Word); this.AdjustSZ(result); this.AdjustHalfCarrySub(this.A, this.Bus.Data, result); this.SetBit(StatusBits.NF); result -= (byte)(this.HalfCarry() >> 4); this.SetBit(StatusBits.YF, result & (byte)Bits.Bit1); this.SetBit(StatusBits.XF, result & (byte)Bits.Bit3); this.Tick(5); } #region Block compare single private void CPI() { this.BlockCompare(); ++this.HL.Word; ++this.MEMPTR.Word; } private void CPD() { this.BlockCompare(); --this.HL.Word; --this.MEMPTR.Word; } #endregion #region Block compare repeated private void CPIR() { this.CPI(); if (this.Parity() != 0 && this.Zero() == 0) { this.RepeatBlockInstruction(); this.Tick(5); } } private void CPDR() { this.CPD(); if (this.Parity() != 0 && this.Zero() == 0) { this.RepeatBlockInstruction(); this.Tick(5); } else { this.MEMPTR.Word = (ushort)(this.PC.Word - 2); this.Tick(2); } } #endregion #endregion #region Block load private void BlockLoad() { this.MemoryRead(this.HL); this.Bus.Address.Assign(this.DE); this.Tick(); this.MemoryUpdate(1); this.Tick(3); var xy = this.A + this.Bus.Data; this.SetBit(StatusBits.XF, xy & (int)Bits.Bit3); this.SetBit(StatusBits.YF, xy & (int)Bits.Bit1); this.ClearBit(StatusBits.NF | StatusBits.HC); this.SetBit(StatusBits.PF, --this.BC.Word); } #region Block load single private void LDI() { this.BlockLoad(); ++this.HL.Word; ++this.DE.Word; } private void LDD() { this.BlockLoad(); --this.HL.Word; --this.DE.Word; } #endregion #region Block load repeated private void LDIR() { this.LDI(); if (this.Parity() != 0) { this.RepeatBlockInstruction(); } this.Tick(5); } private void LDDR() { this.LDD(); if (this.Parity() != 0) { this.RepeatBlockInstruction(); } this.Tick(5); } #endregion #endregion #region Block input/output private void AdjustBlockRepeatFlagsIO() { if (this.Carry() != 0) { var negative = SignTest(this.Bus.Data) != 0; var direction = (byte)(this.B + (negative ? -1 : +1)); var parity = (this.Parity() >> 2) ^ (EvenParity((byte)(direction & (byte)Mask.Three)) ? 1 : 0) ^ 1; this.SetBit(StatusBits.PF, parity != 0); this.SetBit(StatusBits.HC, (this.B & (byte)Mask.Four) == (negative ? 0 : (int)Mask.Four)); } else { this.AdjustParity((byte)(this.B & (byte)Mask.Three)); } } private void AdjustBlockInputOutputFlags(int basis) { this.SetBit(StatusBits.HC | StatusBits.CF, basis > 0xff); this.AdjustParity((byte)((basis & (int)Mask.Three) ^ this.B)); } private void AdjustBlockInFlagsIncrement() { AdjustBlockInputOutputFlags((byte)(this.C + 1) + this.Bus.Data); } private void AdjustBlockInFlagsDecrement() { AdjustBlockInputOutputFlags((byte)(this.C - 1) + this.Bus.Data); } private void AdjustBlockOutFlags() { // HL needs to have been incremented or decremented prior to this call this.SetBit(StatusBits.NF, SignTest(this.Bus.Data)); AdjustBlockInputOutputFlags(this.L + this.Bus.Data); } #region Block input private void BlockIn() { this.Tick(); this.Bus.Address.Assign(this.BC); this.MEMPTR.Assign(this.Bus.Address); this.ReadPort(); this.Bus.Address.Assign(this.HL); this.Tick(); this.MemoryUpdate(1); this.Tick(); this.AdjustSZXY(--this.B); this.SetBit(StatusBits.NF, this.Bus.Data & (byte)StatusBits.SF); } #region Block input single private void INI() { this.BlockIn(); this.AdjustBlockInFlagsIncrement(); ++this.HL.Word; ++this.MEMPTR.Word; } private void IND() { this.BlockIn(); this.AdjustBlockInFlagsDecrement(); --this.HL.Word; --this.MEMPTR.Word; } #endregion #region Block input repeated private void INIR() { this.INI(); if (this.Zero() == 0) { this.RepeatBlockInstruction(); this.AdjustBlockRepeatFlagsIO(); this.Tick(5); } } private void INDR() { this.IND(); if (this.Zero() == 0) { this.RepeatBlockInstruction(); this.AdjustBlockRepeatFlagsIO(); this.Tick(5); } } #endregion #endregion #region Block output private void BlockOut() { this.Tick(); _ = this.MemoryRead(this.HL); this.B = this.Decrement(this.B); this.WritePort(this.BC); } #region Block output single private void OUTI() { this.BlockOut(); ++this.HL.Word; this.AdjustBlockOutFlags(); ++this.MEMPTR.Word; } private void OUTD() { this.BlockOut(); --this.HL.Word; this.AdjustBlockOutFlags(); --this.MEMPTR.Word; } #endregion #region Block output repeated private void OTIR() { this.OUTI(); if (this.Zero() == 0) { this.RepeatBlockInstruction(); this.AdjustBlockRepeatFlagsIO(); this.Tick(5); } } private void OTDR() { this.OUTD(); if (this.Zero() == 0) { this.RepeatBlockInstruction(); this.AdjustBlockRepeatFlagsIO(); this.Tick(5); } } #endregion #endregion #endregion #endregion private void NEG() { this.SetBit(StatusBits.PF, this.A == (byte)Bits.Bit7); this.SetBit(StatusBits.CF, this.A); this.SetBit(StatusBits.NF); var original = this.A; this.A = (byte)(~this.A + 1); // two's complement this.AdjustHalfCarrySub(0, original, this.A); this.AdjustOverflowSub((byte)0, original, this.A); this.AdjustSZXY(this.A); } private void RRD() { this.Bus.Address.Assign(this.HL); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; var memory = this.MemoryRead(); this.Tick(2); this.Bus.Data = (byte)(PromoteNibble(this.A) | HighNibble(memory)); this.MemoryUpdate(1); this.Tick(4); this.A = (byte)(HigherNibble(this.A) | LowerNibble(memory)); this.AdjustSZPXY(this.A); this.ClearBit(StatusBits.NF | StatusBits.HC); } private void RLD() { this.Bus.Address.Assign(this.HL); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; var memory = this.MemoryRead(); this.Tick(2); this.Bus.Data = (byte)(PromoteNibble(memory) | LowNibble(this.A)); this.MemoryUpdate(1); this.Tick(4); this.A = (byte)(HigherNibble(this.A) | HighNibble(memory)); this.AdjustSZPXY(this.A); this.ClearBit(StatusBits.NF | StatusBits.HC); } #region Input/output port control private void WritePort(byte port) { this.Bus.Address.Assign(port, this.Bus.Data = this.A); this.WritePort(); ++this.MEMPTR.Low; } private void WritePort(Register16 port, byte data) { this.Bus.Data = data; this.WritePort(port); } private void WritePort(Register16 port) { this.Bus.Address.Assign(port); this.WritePort(); } private void WritePort() { this.MEMPTR.Assign(this.Bus.Address); this.Tick(2); this.LowerIORQ(); this.LowerWR(); this.Ports.Write(this.Bus.Address.Low, this.Bus.Data); this.Tick(); this.RaiseWR(); this.RaiseIORQ(); this.Tick(); } private void ReadPort(byte port) { this.Bus.Address.Assign(port, this.Bus.Data = this.A); this.MEMPTR.Assign(this.Bus.Address); ++this.MEMPTR.Word; this.ReadPort(); } private void ReadPort() { this.Tick(2); this.LowerIORQ(); this.LowerRD(); this.Bus.Data = this.Ports.Read(this.Bus.Address.Low); this.Tick(); this.RaiseRD(); this.RaiseIORQ(); this.Tick(); } #endregion } }