Files
EightBitNet/Z80/Z80.cs
2025-05-06 22:51:30 +01:00

2383 lines
85 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// <copyright file="Z80.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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<EventArgs>? RaisingNMI;
public event EventHandler<EventArgs>? RaisedNMI;
public event EventHandler<EventArgs>? LoweringNMI;
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingM1;
public event EventHandler<EventArgs>? RaisedM1;
public event EventHandler<EventArgs>? LoweringM1;
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingRFSH;
public event EventHandler<EventArgs>? RaisedRFSH;
public event EventHandler<EventArgs>? LoweringRFSH;
public event EventHandler<EventArgs>? LoweredRFSH;
// ** From the Z80 CPU User Manual
// RFSH.Refresh(output, active Low). RFSH, together with MREQ, indicates that the lower
// seven bits of the systems address bus can be used as a _refresh address to the systems
// 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<EventArgs>? RaisingMREQ;
public event EventHandler<EventArgs>? RaisedMREQ;
public event EventHandler<EventArgs>? LoweringMREQ;
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingIORQ;
public event EventHandler<EventArgs>? RaisedIORQ;
public event EventHandler<EventArgs>? LoweringIORQ;
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingRD;
public event EventHandler<EventArgs>? RaisedRD;
public event EventHandler<EventArgs>? LoweringRD;
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingWR;
public event EventHandler<EventArgs>? RaisedWR;
public event EventHandler<EventArgs>? LoweringWR;
public event EventHandler<EventArgs>? 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
}
}