From 9e733730b9201461e417e638e91fd2f7f125beeb Mon Sep 17 00:00:00 2001 From: Adrian Conlon <98398945+AdrianConlon@users.noreply.github.com> Date: Sat, 29 Jun 2024 21:20:25 +0100 Subject: [PATCH] Split the 6502 implementation, so I can implement variants --- M6502/M6502.cs | 1271 ++++---------------------------------------- M6502/M6502Core.cs | 1119 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1221 insertions(+), 1169 deletions(-) create mode 100644 M6502/M6502Core.cs diff --git a/M6502/M6502.cs b/M6502/M6502.cs index 5f35c62..62861b9 100644 --- a/M6502/M6502.cs +++ b/M6502/M6502.cs @@ -4,1103 +4,157 @@ namespace EightBit { - public class M6502(Bus bus) : LittleEndianProcessor(bus) + public class M6502(Bus bus) : M6502Core(bus) { - private readonly Register16 intermediate = new(); - - #region Instruction execution events - - public event EventHandler? ExecutingInstruction; - public event EventHandler? ExecutedInstruction; - protected virtual void OnExecutedInstruction() => this.ExecutedInstruction?.Invoke(this, EventArgs.Empty); - protected virtual void OnExecutingInstruction() => this.ExecutingInstruction?.Invoke(this, EventArgs.Empty); - - #endregion - - #region Pin controls - - #region NMI pin - - public ref PinLevel NMI => ref this.nmiLine; - private PinLevel nmiLine = PinLevel.Low; - public event EventHandler? RaisingNMI; - public event EventHandler? RaisedNMI; - public event EventHandler? LoweringNMI; - public event EventHandler? LoweredNMI; - protected virtual void OnRaisingNMI() => this.RaisingNMI?.Invoke(this, EventArgs.Empty); - protected virtual void OnRaisedNMI() => this.RaisedNMI?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweringNMI() => this.LoweringNMI?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweredNMI() => this.LoweredNMI?.Invoke(this, EventArgs.Empty); - - - [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()) - { - this.OnRaisingNMI(); - this.NMI.Raise(); - this.OnRaisedNMI(); - } - } - - public virtual void LowerNMI() - { - if (this.NMI.Raised()) - { - this.OnLoweringNMI(); - this.NMI.Lower(); - this.OnLoweredNMI(); - } - } - - #endregion - - #region SO pin - - public ref PinLevel SO => ref this.soLine; - private PinLevel soLine = PinLevel.Low; - public event EventHandler? RaisingSO; - public event EventHandler? RaisedSO; - public event EventHandler? LoweringSO; - public event EventHandler? LoweredSO; - - protected virtual void OnRaisingSO() => this.RaisingSO?.Invoke(this, EventArgs.Empty); - protected virtual void OnRaisedSO() => this.RaisedSO?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweringSO() => this.LoweringSO?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweredSO() => this.LoweredSO?.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 RaiseSO() - { - if (this.SO.Lowered()) - { - this.OnRaisingSO(); - this.SO.Raise(); - this.OnRaisedSO(); - } - } - - public virtual void LowerSO() - { - if (this.SO.Raised()) - { - this.OnLoweringSO(); - this.SO.Lower(); - this.OnLoweredSO(); - } - } - - #endregion - - #region SYNC pin - - public ref PinLevel SYNC => ref this.syncLine; - private PinLevel syncLine = PinLevel.Low; - public event EventHandler? RaisingSYNC; - public event EventHandler? RaisedSYNC; - public event EventHandler? LoweringSYNC; - public event EventHandler? LoweredSYNC; - - protected virtual void OnRaisingSYNC() => this.RaisingSYNC?.Invoke(this, EventArgs.Empty); - protected virtual void OnRaisedSYNC() => this.RaisedSYNC?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweringSYNC() => this.LoweringSYNC?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweredSYNC() => this.LoweredSYNC?.Invoke(this, EventArgs.Empty); - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] - protected virtual void RaiseSYNC() - { - this.OnRaisingSYNC(); - this.SYNC.Raise(); - this.OnRaisedSYNC(); - } - - protected virtual void LowerSYNC() - { - this.OnLoweringSYNC(); - this.SYNC.Lower(); - this.OnLoweredSYNC(); - } - - #endregion - - #region RDY pin - - public ref PinLevel RDY => ref this.rdyLine; - private PinLevel rdyLine = PinLevel.Low; - public event EventHandler? RaisingRDY; - public event EventHandler? RaisedRDY; - public event EventHandler? LoweringRDY; - public event EventHandler? LoweredRDY; - protected virtual void OnRaisingRDY() => this.RaisingRDY?.Invoke(this, EventArgs.Empty); - protected virtual void OnRaisedRDY() => this.RaisedRDY?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweringRDY() => this.LoweringRDY?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweredRDY() => this.LoweredRDY?.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 RaiseRDY() - { - if (this.RDY.Lowered()) - { - this.OnRaisingRDY(); - this.RDY.Raise(); - this.OnRaisedRDY(); - } - } - - public virtual void LowerRDY() - { - if (this.RDY.Raised()) - { - this.OnLoweringRDY(); - this.RDY.Lower(); - this.OnLoweredRDY(); - } - } - - #endregion - - #region RW pin - - public ref PinLevel RW => ref this.rwLine; - private PinLevel rwLine = PinLevel.Low; - public event EventHandler? RaisingRW; - public event EventHandler? RaisedRW; - public event EventHandler? LoweringRW; - public event EventHandler? LoweredRW; - protected virtual void OnRaisingRW() => this.RaisingRW?.Invoke(this, EventArgs.Empty); - protected virtual void OnRaisedRW() => this.RaisedRW?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweringRW() => this.LoweringRW?.Invoke(this, EventArgs.Empty); - protected virtual void OnLoweredRW() => this.LoweredRW?.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 RaiseRW() - { - if (this.RW.Lowered()) - { - this.OnRaisingRW(); - this.RW.Raise(); - this.OnRaisedRW(); - } - } - - public virtual void LowerRW() - { - if (this.RW.Raised()) - { - this.OnLoweringRW(); - this.RW.Lower(); - this.OnLoweredRW(); - } - } - - #endregion - - protected override void OnRaisedPOWER() - { - this.X = (byte)Bits.Bit7; - this.Y = 0; - this.A = 0; - this.P = (byte)StatusBits.RF; - this.S = (byte)Mask.Eight; - this.LowerSYNC(); - this.LowerRW(); - base.OnRaisedPOWER(); - } - - #endregion - - #region Interrupts - - private const byte IRQvector = 0xfe; // IRQ vector - private const byte RSTvector = 0xfc; // RST vector - private const byte NMIvector = 0xfa; // NMI vector - - enum InterruptSource { hardware, software }; - - enum InterruptType { reset, non_reset }; - - private void Interrupt(byte vector, InterruptSource source = InterruptSource.hardware, InterruptType type = InterruptType.non_reset) - { - if (type == InterruptType.reset) - { - this.DummyPush(); - this.DummyPush(); - this.DummyPush(); - } - else - { - this.PushWord(this.PC); - this.Push((byte)(this.P | (source == InterruptSource.hardware ? 0 : (byte)StatusBits.BF))); - } - this.SetFlag(StatusBits.IF); // Disable IRQ - this.Jump(this.GetWordPaged(0xff, vector).Word); - } - - #region Interrupt etc. handlers - - protected override sealed void HandleRESET() - { - this.RaiseRESET(); - this.Interrupt(RSTvector, InterruptSource.hardware, InterruptType.reset); - } - - protected override sealed void HandleINT() - { - this.RaiseINT(); - this.Interrupt(IRQvector); - } - - private void HandleNMI() - { - this.RaiseNMI(); - this.Interrupt(NMIvector); - } - - private void HandleSO() - { - this.RaiseSO(); - this.SetFlag(StatusBits.VF); - } - - #endregion - - #endregion - - #region Registers - - public byte X { get; set; } - public byte Y { get; set; } - public byte A { get; set; } - public byte S { get; set; } - public byte P { get; set; } - - #endregion - - #region Processor state helpers - - private int InterruptMasked => this.P & (byte)StatusBits.IF; - private int Decimal => this.P & (byte)StatusBits.DF; - private int Negative => NegativeTest(this.P); - private int Zero => ZeroTest(this.P); - private int Overflow => OverflowTest(this.P); - private int Carry => CarryTest(this.P); - - private static int NegativeTest(byte data) => data & (byte)StatusBits.NF; - private static int ZeroTest(byte data) => data & (byte)StatusBits.ZF; - private static int OverflowTest(byte data) => data & (byte)StatusBits.VF; - private static int CarryTest(byte data) => data & (byte)StatusBits.CF; - - #endregion - - #region Bit/state twiddling - - #region Bit twiddling - - private static byte SetBit(byte f, StatusBits flag) => SetBit(f, (byte)flag); - - private static byte SetBit(byte f, StatusBits flag, int condition) => SetBit(f, (byte)flag, condition); - - private static byte SetBit(byte f, StatusBits flag, bool condition) => SetBit(f, (byte)flag, condition); - - private static byte ClearBit(byte f, StatusBits flag) => ClearBit(f, (byte)flag); - - private static byte ClearBit(byte f, StatusBits flag, int condition) => ClearBit(f, (byte)flag, condition); - - #endregion - - #region State flag twiddling - - private void SetFlag(StatusBits flag) - { - this.P = SetBit(this.P, flag); - } - - private void SetFlag(StatusBits which, int condition) - { - this.P = SetBit(this.P, which, condition); - } - - private void SetFlag(StatusBits which, bool condition) - { - this.P = SetBit(this.P, which, condition); - } - - private void ResetFlag(StatusBits which) - { - this.P = ClearBit(this.P, which); - } - - private void ResetFlag(StatusBits which, int condition) - { - this.P = ClearBit(this.P, which, condition); - } - - #endregion - - #endregion - - #region Cycle wastage - - private void Swallow() => this.MemoryRead(this.PC); - - private void SwallowStack() => this.MemoryRead(this.S, 1); - - private void SwallowFetch() => this.FetchByte(); - - #endregion - #region Core instruction dispatching - public override void Execute() + protected override bool MaybeExecute() { + if (base.MaybeExecute()) + { + return true; + } + + var cycles = this.Cycles; switch (this.OpCode) { - case 0x00: this.SwallowFetch(); this.Interrupt(IRQvector, InterruptSource.software); break; // BRK (implied) - case 0x01: this.AM_IndexedIndirectX(); this.OrR(); break; // ORA (indexed indirect X) case 0x02: this.Jam(); break; // *JAM - case 0x03: this.AM_IndexedIndirectX(); this.SLO(); break; // *SLO (indexed indirect X) - case 0x04: this.AM_ZeroPage(); break; // *NOP (zero page) - case 0x05: this.AM_ZeroPage(); this.OrR(); break; // ORA (zero page) - case 0x06: this.AM_ZeroPage(); this.ModifyWrite(this.ASL()); break; // ASL (zero page) - case 0x07: this.AM_ZeroPage(); this.SLO(); break; // *SLO (zero page) - case 0x08: this.Swallow(); this.PHP(); break; // PHP (implied) - case 0x09: this.AM_Immediate(); this.OrR(); break; // ORA (immediate) - case 0x0a: this.Swallow(); A = this.ASL(A); break; // ASL A (implied) - case 0x0b: this.AM_Immediate(); this.ANC(); break; // *ANC (immediate) - case 0x0c: this.AM_Absolute(); break; // *NOP (absolute) - case 0x0d: this.AM_Absolute(); this.OrR(); break; // ORA (absolute) - case 0x0e: this.AM_Absolute(); this.ModifyWrite(this.ASL()); break; // ASL (absolute) - case 0x0f: this.AM_Absolute(); this.SLO(); break; // *SLO (absolute) + case 0x03: this.IndexedIndirectXRead(); this.SLO(); break; // *SLO (indexed indirect X) + case 0x04: this.ZeroPageRead(); break; // *NOP (zero page) + case 0x07: this.ZeroPageRead(); this.SLO(); break; // *SLO (zero page) + case 0x0b: this.ImmediateRead(); this.ANC(); break; // *ANC (immediate) + case 0x0c: this.AbsoluteRead(); break; // *NOP (absolute) + case 0x0f: this.AbsoluteRead(); this.SLO(); break; // *SLO (absolute) - case 0x10: this.BranchNot(this.Negative); break; // BPL (relative) - case 0x11: this.AM_IndirectIndexedY(); this.OrR(); break; // ORA (indirect indexed Y) case 0x12: this.Jam(); break; // *JAM - case 0x13: this.Address_IndirectIndexedY(); this.FixupR(); this.SLO(); break; // *SLO (indirect indexed Y) - case 0x14: this.AM_ZeroPageX(); break; // *NOP (zero page, X) - case 0x15: this.AM_ZeroPageX(); this.OrR(); break; // ORA (zero page, X) - case 0x16: this.AM_ZeroPageX(); this.ModifyWrite(this.ASL()); break; // ASL (zero page, X) - case 0x17: this.AM_ZeroPageX(); this.SLO(); break; // *SLO (zero page, X) - case 0x18: this.Swallow(); this.ResetFlag(StatusBits.CF); break; // CLC (implied) - case 0x19: this.AM_AbsoluteY(); this.OrR(); break; // ORA (absolute, Y) + case 0x13: this.IndirectIndexedYAddress(); this.FixupR(); this.SLO(); break; // *SLO (indirect indexed Y) + case 0x14: this.ZeroPageXRead(); break; // *NOP (zero page, X) + case 0x17: this.ZeroPageXRead(); this.SLO(); break; // *SLO (zero page, X) case 0x1a: this.Swallow(); break; // *NOP (implied) - case 0x1b: this.Address_AbsoluteY(); this.FixupR(); this.SLO(); break; // *SLO (absolute, Y) - case 0x1c: this.Address_AbsoluteX(); this.MaybeFixupR(); break; // *NOP (absolute, X) - case 0x1d: this.AM_AbsoluteX(); this.OrR(); break; // ORA (absolute, X) - case 0x1e: this.Address_AbsoluteX(); this.FixupR(); this.ModifyWrite(this.ASL()); break; // ASL (absolute, X) - case 0x1f: this.Address_AbsoluteX(); this.FixupR(); this.SLO(); break; // *SLO (absolute, X) + case 0x1b: this.AbsoluteYAddress(); this.FixupR(); this.SLO(); break; // *SLO (absolute, Y) + case 0x1c: this.AbsoluteXAddress(); this.MaybeFixupR(); break; // *NOP (absolute, X) + case 0x1f: this.AbsoluteXAddress(); this.FixupR(); this.SLO(); break; // *SLO (absolute, X) - case 0x20: this.JSR(); break; // JSR (absolute) - case 0x21: this.AM_IndexedIndirectX(); this.AndR(); break; // AND (indexed indirect X) case 0x22: this.Jam(); break; // *JAM - case 0x23: this.AM_IndexedIndirectX(); this.RLA(); ; break; // *RLA (indexed indirect X) - case 0x24: this.AM_ZeroPage(); this.BIT(); break; // BIT (zero page) - case 0x25: this.AM_ZeroPage(); this.AndR(); break; // AND (zero page) - case 0x26: this.AM_ZeroPage(); this.ModifyWrite(this.ROL()); break; // ROL (zero page) - case 0x27: this.AM_ZeroPage(); this.RLA(); ; break; // *RLA (zero page) - case 0x28: this.Swallow(); this.PLP(); break; // PLP (implied) - case 0x29: this.AM_Immediate(); this.AndR(); break; // AND (immediate) - case 0x2a: this.Swallow(); this.A = this.ROL(this.A); break; // ROL A (implied) - case 0x2b: this.AM_Immediate(); this.ANC(); break; // *ANC (immediate) - case 0x2c: this.AM_Absolute(); this.BIT(); break; // BIT (absolute) - case 0x2d: this.AM_Absolute(); this.AndR(); break; // AND (absolute) - case 0x2e: this.AM_Absolute(); this.ModifyWrite(this.ROL()); break; // ROL (absolute) - case 0x2f: this.AM_Absolute(); this.RLA(); break; // *RLA (absolute) + case 0x23: this.IndexedIndirectXRead(); this.RLA(); ; break; // *RLA (indexed indirect X) + case 0x27: this.ZeroPageRead(); this.RLA(); ; break; // *RLA (zero page) + case 0x2b: this.ImmediateRead(); this.ANC(); break; // *ANC (immediate) + case 0x2f: this.AbsoluteRead(); this.RLA(); break; // *RLA (absolute) - case 0x30: this.Branch(this.Negative); break; // BMI (relative) - case 0x31: this.AM_IndirectIndexedY(); this.AndR(); break; // AND (indirect indexed Y) case 0x32: this.Jam(); break; // *JAM - case 0x33: this.Address_IndirectIndexedY(); this.FixupR(); this.RLA(); break; // *RLA (indirect indexed Y) - case 0x34: this.AM_ZeroPageX(); break; // *NOP (zero page, X) - case 0x35: this.AM_ZeroPageX(); this.AndR(); break; // AND (zero page, X) - case 0x36: this.AM_ZeroPageX(); this.ModifyWrite(this.ROL()); break; // ROL (zero page, X) - case 0x37: this.AM_ZeroPageX(); this.RLA(); ; break; // *RLA (zero page, X) - case 0x38: this.Swallow(); this.SetFlag(StatusBits.CF); break; // SEC (implied) - case 0x39: this.AM_AbsoluteY(); this.AndR(); break; // AND (absolute, Y) + case 0x33: this.IndirectIndexedYAddress(); this.FixupR(); this.RLA(); break; // *RLA (indirect indexed Y) + case 0x34: this.ZeroPageXRead(); break; // *NOP (zero page, X) + case 0x37: this.ZeroPageXRead(); this.RLA(); ; break; // *RLA (zero page, X) case 0x3a: this.Swallow(); break; // *NOP (implied) - case 0x3b: this.Address_AbsoluteY(); this.FixupR(); this.RLA(); break; // *RLA (absolute, Y) - case 0x3c: this.Address_AbsoluteX(); this.MaybeFixupR(); break; // *NOP (absolute, X) - case 0x3d: this.AM_AbsoluteX(); this.AndR(); break; // AND (absolute, X) - case 0x3e: this.Address_AbsoluteX(); this.FixupR(); this.ModifyWrite(this.ROL()); break; // ROL (absolute, X) - case 0x3f: this.Address_AbsoluteX(); this.FixupR(); this.RLA(); break; // *RLA (absolute, X) + case 0x3b: this.AbsoluteYAddress(); this.FixupR(); this.RLA(); break; // *RLA (absolute, Y) + case 0x3c: this.AbsoluteXAddress(); this.MaybeFixupR(); break; // *NOP (absolute, X) + case 0x3f: this.AbsoluteXAddress(); this.FixupR(); this.RLA(); break; // *RLA (absolute, X) - case 0x40: this.Swallow(); this.RTI(); break; // RTI (implied) - case 0x41: this.AM_IndexedIndirectX(); this.EorR(); break; // EOR (indexed indirect X) case 0x42: this.Jam(); break; // *JAM - case 0x43: this.AM_IndexedIndirectX(); this.SRE(); break; // *SRE (indexed indirect X) - case 0x44: this.AM_ZeroPage(); break; // *NOP (zero page) - case 0x45: this.AM_ZeroPage(); this.EorR(); break; // EOR (zero page) - case 0x46: this.AM_ZeroPage(); this.ModifyWrite(this.LSR()); break; // LSR (zero page) - case 0x47: this.AM_ZeroPage(); this.SRE(); break; // *SRE (zero page) - case 0x48: this.Swallow(); this.Push(this.A); break; // PHA (implied) - case 0x49: this.AM_Immediate(); this.EorR(); break; // EOR (immediate) - case 0x4a: this.Swallow(); this.A = this.LSR(this.A); break; // LSR A (implied) - case 0x4b: this.AM_Immediate(); this.ASR(); break; // *ASR (immediate) - case 0x4c: this.Address_Absolute(); this.Jump(this.Bus.Address.Word); break; // JMP (absolute) - case 0x4d: this.AM_Absolute(); this.EorR(); break; // EOR (absolute) - case 0x4e: this.AM_Absolute(); this.ModifyWrite(this.LSR()); break; // LSR (absolute) - case 0x4f: this.AM_Absolute(); this.SRE(); break; // *SRE (absolute) + case 0x43: this.IndexedIndirectXRead(); this.SRE(); break; // *SRE (indexed indirect X) + case 0x44: this.ZeroPageRead(); break; // *NOP (zero page) + case 0x47: this.ZeroPageRead(); this.SRE(); break; // *SRE (zero page) + case 0x4b: this.ImmediateRead(); this.ASR(); break; // *ASR (immediate) + case 0x4f: this.AbsoluteRead(); this.SRE(); break; // *SRE (absolute) - case 0x50: this.BranchNot(this.Overflow); break; // BVC (relative) - case 0x51: this.AM_IndirectIndexedY(); this.EorR(); break; // EOR (indirect indexed Y) case 0x52: this.Jam(); break; // *JAM - case 0x53: this.Address_IndirectIndexedY(); this.FixupR(); this.SRE(); break; // *SRE (indirect indexed Y) - case 0x54: this.AM_ZeroPageX(); break; // *NOP (zero page, X) - case 0x55: this.AM_ZeroPageX(); this.EorR(); break; // EOR (zero page, X) - case 0x56: this.AM_ZeroPageX(); this.ModifyWrite(this.LSR()); break; // LSR (zero page, X) - case 0x57: this.AM_ZeroPageX(); this.SRE(); break; // *SRE (zero page, X) - case 0x58: this.Swallow(); this.ResetFlag(StatusBits.IF); break; // CLI (implied) - case 0x59: this.AM_AbsoluteY(); this.EorR(); break; // EOR (absolute, Y) + case 0x53: this.IndirectIndexedYAddress(); this.FixupR(); this.SRE(); break; // *SRE (indirect indexed Y) + case 0x54: this.ZeroPageXRead(); break; // *NOP (zero page, X) + case 0x57: this.ZeroPageXRead(); this.SRE(); break; // *SRE (zero page, X) case 0x5a: this.Swallow(); break; // *NOP (implied) - case 0x5b: this.Address_AbsoluteY(); this.FixupR(); this.SRE(); break; // *SRE (absolute, Y) - case 0x5c: this.Address_AbsoluteX(); this.MaybeFixupR(); break; // *NOP (absolute, X) - case 0x5d: this.AM_AbsoluteX(); this.EorR(); break; // EOR (absolute, X) - case 0x5e: this.Address_AbsoluteX(); this.FixupR(); this.ModifyWrite(this.LSR()); break; // LSR (absolute, X) - case 0x5f: this.Address_AbsoluteX(); this.FixupR(); this.SRE(); break; // *SRE (absolute, X) + case 0x5b: this.AbsoluteYAddress(); this.FixupR(); this.SRE(); break; // *SRE (absolute, Y) + case 0x5c: this.AbsoluteXAddress(); this.MaybeFixupR(); break; // *NOP (absolute, X) + case 0x5f: this.AbsoluteXAddress(); this.FixupR(); this.SRE(); break; // *SRE (absolute, X) - case 0x60: this.Swallow(); this.RTS(); break; // RTS (implied) - case 0x61: this.AM_IndexedIndirectX(); this.ADC(); break; // ADC (indexed indirect X) case 0x62: this.Jam(); break; // *JAM - case 0x63: this.AM_IndexedIndirectX(); this.RRA(); break; // *RRA (indexed indirect X) - case 0x64: this.AM_ZeroPage(); break; // *NOP (zero page) - case 0x65: this.AM_ZeroPage(); this.ADC(); break; // ADC (zero page) - case 0x66: this.AM_ZeroPage(); this.ModifyWrite(this.ROR()); break; // ROR (zero page) - case 0x67: this.AM_ZeroPage(); this.RRA(); break; // *RRA (zero page) - case 0x68: this.Swallow(); this.SwallowStack(); this.A = this.Through(this.Pop()); break; // PLA (implied) - case 0x69: this.AM_Immediate(); this.ADC(); break; // ADC (immediate) - case 0x6a: this.Swallow(); this.A = this.ROR(this.A); break; // ROR A (implied) - case 0x6b: this.AM_Immediate(); this.ARR(); break; // *ARR (immediate) - case 0x6c: this.Address_Indirect(); this.Jump(this.Bus.Address.Word); break; // JMP (indirect) - case 0x6d: this.AM_Absolute(); this.ADC(); break; // ADC (absolute) - case 0x6e: this.AM_Absolute(); this.ModifyWrite(this.ROR()); break; // ROR (absolute) - case 0x6f: this.AM_Absolute(); this.RRA(); break; // *RRA (absolute) + case 0x63: this.IndexedIndirectXRead(); this.RRA(); break; // *RRA (indexed indirect X) + case 0x64: this.ZeroPageRead(); break; // *NOP (zero page) + case 0x67: this.ZeroPageRead(); this.RRA(); break; // *RRA (zero page) + case 0x6b: this.ImmediateRead(); this.ARR(); break; // *ARR (immediate) + case 0x6f: this.AbsoluteRead(); this.RRA(); break; // *RRA (absolute) - case 0x70: this.Branch(this.Overflow); break; // BVS (relative) - case 0x71: this.AM_IndirectIndexedY(); this.ADC(); break; // ADC (indirect indexed Y) case 0x72: this.Jam(); break; // *JAM - case 0x73: this.Address_IndirectIndexedY(); this.FixupR(); this.RRA(); break; // *RRA (indirect indexed Y) - case 0x74: this.AM_ZeroPageX(); break; // *NOP (zero page, X) - case 0x75: this.AM_ZeroPageX(); this.ADC(); break; // ADC (zero page, X) - case 0x76: this.AM_ZeroPageX(); this.ModifyWrite(this.ROR()); break; // ROR (zero page, X) - case 0x77: this.AM_ZeroPageX(); this.RRA(); break; // *RRA (zero page, X) - case 0x78: this.Swallow(); this.SetFlag(StatusBits.IF); break; // SEI (implied) - case 0x79: this.AM_AbsoluteY(); this.ADC(); break; // ADC (absolute, Y) + case 0x73: this.IndirectIndexedYAddress(); this.FixupR(); this.RRA(); break; // *RRA (indirect indexed Y) + case 0x74: this.ZeroPageXRead(); break; // *NOP (zero page, X) + case 0x77: this.ZeroPageXRead(); this.RRA(); break; // *RRA (zero page, X) case 0x7a: this.Swallow(); break; // *NOP (implied) - case 0x7b: this.Address_AbsoluteY(); this.FixupR(); this.RRA(); break; // *RRA (absolute, Y) - case 0x7c: this.Address_AbsoluteX(); this.MaybeFixupR(); break; // *NOP (absolute, X) - case 0x7d: this.AM_AbsoluteX(); this.ADC(); break; // ADC (absolute, X) - case 0x7e: this.Address_AbsoluteX(); this.FixupR(); this.ModifyWrite(this.ROR()); break; // ROR (absolute, X) - case 0x7f: this.Address_AbsoluteX(); this.FixupR(); this.RRA(); break; // *RRA (absolute, X) + case 0x7b: this.AbsoluteYAddress(); this.FixupR(); this.RRA(); break; // *RRA (absolute, Y) + case 0x7c: this.AbsoluteXAddress(); this.MaybeFixupR(); break; // *NOP (absolute, X) + case 0x7f: this.AbsoluteXAddress(); this.FixupR(); this.RRA(); break; // *RRA (absolute, X) - case 0x80: this.AM_Immediate(); break; // *NOP (immediate) - case 0x81: this.Address_IndexedIndirectX(); this.MemoryWrite(A); break; // STA (indexed indirect X) - case 0x82: this.AM_Immediate(); break; // *NOP (immediate) - case 0x83: this.Address_IndexedIndirectX(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (indexed indirect X) - case 0x84: this.Address_ZeroPage(); this.MemoryWrite(this.Y); break; // STY (zero page) - case 0x85: this.Address_ZeroPage(); this.MemoryWrite(this.A); break; // STA (zero page) - case 0x86: this.Address_ZeroPage(); this.MemoryWrite(this.X); break; // STX (zero page) - case 0x87: this.Address_ZeroPage(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (zero page) - case 0x88: this.Swallow(); this.Y = this.DEC(this.Y); break; // DEY (implied) - case 0x89: this.AM_Immediate(); break; // *NOP (immediate) - case 0x8a: this.Swallow(); this.A = this.Through(this.X); break; // TXA (implied) - case 0x8b: this.AM_Immediate(); this.ANE(); break; // *ANE (immediate) - case 0x8c: this.Address_Absolute(); this.MemoryWrite(this.Y); break; // STY (absolute) - case 0x8d: this.Address_Absolute(); this.MemoryWrite(this.A); break; // STA (absolute) - case 0x8e: this.Address_Absolute(); this.MemoryWrite(this.X); break; // STX (absolute) - case 0x8f: this.Address_Absolute(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (absolute) + case 0x80: this.ImmediateRead(); break; // *NOP (immediate) + case 0x82: this.ImmediateRead(); break; // *NOP (immediate) + case 0x83: this.IndexedIndirectXAddress(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (indexed indirect X) + case 0x87: this.ZeroPageAddress(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (zero page) + case 0x89: this.ImmediateRead(); break; // *NOP (immediate) + case 0x8b: this.ImmediateRead(); this.ANE(); break; // *ANE (immediate) + case 0x8f: this.AbsoluteAddress(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (absolute) - case 0x90: this.BranchNot(this.Carry); break; // BCC (relative) - case 0x91: this.Address_IndirectIndexedY(); this.Fixup(); this.MemoryWrite(this.A); break; // STA (indirect indexed Y) case 0x92: this.Jam(); break; // *JAM - case 0x93: this.Address_IndirectIndexedY(); this.Fixup(); this.SHA(); break; // *SHA (indirect indexed, Y) - case 0x94: this.Address_ZeroPageX(); this.MemoryWrite(this.Y); break; // STY (zero page, X) - case 0x95: this.Address_ZeroPageX(); this.MemoryWrite(this.A); break; // STA (zero page, X) - case 0x96: this.Address_ZeroPageY(); this.MemoryWrite(this.X); break; // STX (zero page, Y) - case 0x97: this.Address_ZeroPageY(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (zero page, Y) - case 0x98: this.Swallow(); this.A = this.Through(this.Y); break; // TYA (implied) - case 0x99: this.Address_AbsoluteY(); this.Fixup(); this.MemoryWrite(this.A); break; // STA (absolute, Y) - case 0x9a: this.Swallow(); this.S = this.X; break; // TXS (implied) - case 0x9b: this.Address_AbsoluteY(); this.Fixup(); this.TAS(); break; // *TAS (absolute, Y) - case 0x9c: this.Address_AbsoluteX(); this.Fixup(); this.SYA(); break; // *SYA (absolute, X) - case 0x9d: this.Address_AbsoluteX(); this.Fixup(); this.MemoryWrite(this.A); break; // STA (absolute, X) - case 0x9e: this.Address_AbsoluteY(); this.Fixup(); this.SXA(); break; // *SXA (absolute, Y) - case 0x9f: this.Address_AbsoluteY(); this.Fixup(); this.SHA(); break; // *SHA (absolute, Y) + case 0x93: this.IndirectIndexedYAddress(); this.Fixup(); this.SHA(); break; // *SHA (indirect indexed, Y) + case 0x97: this.ZeroPageYAddress(); this.MemoryWrite((byte)(this.A & this.X)); break; // *SAX (zero page, Y) + case 0x9b: this.AbsoluteYAddress(); this.Fixup(); this.TAS(); break; // *TAS (absolute, Y) + case 0x9c: this.AbsoluteXAddress(); this.Fixup(); this.SYA(); break; // *SYA (absolute, X) + case 0x9e: this.AbsoluteYAddress(); this.Fixup(); this.SXA(); break; // *SXA (absolute, Y) + case 0x9f: this.AbsoluteYAddress(); this.Fixup(); this.SHA(); break; // *SHA (absolute, Y) - case 0xa0: this.AM_Immediate(); this.Y = this.Through(); break; // LDY (immediate) - case 0xa1: this.AM_IndexedIndirectX(); this.A = this.Through(); break; // LDA (indexed indirect X) - case 0xa2: this.AM_Immediate(); this.X = this.Through(); break; // LDX (immediate) - case 0xa3: this.AM_IndexedIndirectX(); this.A = this.X = this.Through(); break; // *LAX (indexed indirect X) - case 0xa4: this.AM_ZeroPage(); this.Y = this.Through(); break; // LDY (zero page) - case 0xa5: this.AM_ZeroPage(); this.A = this.Through(); break; // LDA (zero page) - case 0xa6: this.AM_ZeroPage(); this.X = this.Through(); break; // LDX (zero page) - case 0xa7: this.AM_ZeroPage(); this.A = this.X = this.Through(); break; // *LAX (zero page) - case 0xa8: this.Swallow(); this.Y = Through(this.A); break; // TAY (implied) - case 0xa9: this.AM_Immediate(); this.A = this.Through(); break; // LDA (immediate) - case 0xaa: this.Swallow(); this.X = this.Through(this.A); break; // TAX (implied) - case 0xab: this.AM_Immediate(); this.ATX(); break; // *ATX (immediate) - case 0xac: this.AM_Absolute(); this.Y = this.Through(); break; // LDY (absolute) - case 0xad: this.AM_Absolute(); this.A = this.Through(); break; // LDA (absolute) - case 0xae: this.AM_Absolute(); this.X = this.Through(); break; // LDX (absolute) - case 0xaf: this.AM_Absolute(); this.A = this.X = this.Through(); break; // *LAX (absolute) + case 0xa3: this.IndexedIndirectXRead(); this.A = this.X = this.Through(); break; // *LAX (indexed indirect X) + case 0xa7: this.ZeroPageRead(); this.A = this.X = this.Through(); break; // *LAX (zero page) + case 0xab: this.ImmediateRead(); this.ATX(); break; // *ATX (immediate) + case 0xaf: this.AbsoluteRead(); this.A = this.X = this.Through(); break; // *LAX (absolute) - case 0xb0: this.Branch(this.Carry); break; // BCS (relative) - case 0xb1: this.AM_IndirectIndexedY(); this.A = this.Through(); break; // LDA (indirect indexed Y) case 0xb2: this.Jam(); break; // *JAM - case 0xb3: this.AM_IndirectIndexedY(); this.A = this.X = this.Through(); break; // *LAX (indirect indexed Y) - case 0xb4: this.AM_ZeroPageX(); this.Y = this.Through(); break; // LDY (zero page, X) - case 0xb5: this.AM_ZeroPageX(); this.A = this.Through(); break; // LDA (zero page, X) - case 0xb6: this.AM_ZeroPageY(); this.X = this.Through(); break; // LDX (zero page, Y) - case 0xb7: this.AM_ZeroPageY(); this.A = this.X = this.Through(); break; // *LAX (zero page, Y) - case 0xb8: this.Swallow(); this.ResetFlag(StatusBits.VF); break; // CLV (implied) - case 0xb9: this.AM_AbsoluteY(); this.A = this.Through(); break; // LDA (absolute, Y) - case 0xba: this.Swallow(); this.X = this.Through(this.S); break; // TSX (implied) - case 0xbb: this.Address_AbsoluteY(); this.MaybeFixup(); this.LAS(); break; // *LAS (absolute, Y) - case 0xbc: this.AM_AbsoluteX(); this.Y = this.Through(); break; // LDY (absolute, X) - case 0xbd: this.AM_AbsoluteX(); this.A = this.Through(); break; // LDA (absolute, X) - case 0xbe: this.AM_AbsoluteY(); this.X = this.Through(); break; // LDX (absolute, Y) - case 0xbf: this.AM_AbsoluteY(); this.A = this.X = this.Through(); break; // *LAX (absolute, Y) + case 0xb3: this.IndirectIndexedYRead(); this.A = this.X = this.Through(); break; // *LAX (indirect indexed Y) + case 0xb7: this.ZeroPageYRead(); this.A = this.X = this.Through(); break; // *LAX (zero page, Y) + case 0xbb: this.AbsoluteYAddress(); this.MaybeFixup(); this.LAS(); break; // *LAS (absolute, Y) + case 0xbf: this.AbsoluteYRead(); this.A = this.X = this.Through(); break; // *LAX (absolute, Y) - case 0xc0: this.AM_Immediate(); this.CMP(this.Y); break; // CPY (immediate) - case 0xc1: this.AM_IndexedIndirectX(); this.CMP(this.A); break; // CMP (indexed indirect X) - case 0xc2: this.AM_Immediate(); break; // *NOP (immediate) - case 0xc3: this.AM_IndexedIndirectX(); this.DCP(); break; // *DCP (indexed indirect X) - case 0xc4: this.AM_ZeroPage(); this.CMP(this.Y); break; // CPY (zero page) - case 0xc5: this.AM_ZeroPage(); this.CMP(this.A); break; // CMP (zero page) - case 0xc6: this.AM_ZeroPage(); this.ModifyWrite(this.DEC()); break; // DEC (zero page) - case 0xc7: this.AM_ZeroPage(); this.DCP(); break; // *DCP (zero page) - case 0xc8: this.Swallow(); this.Y = this.INC(this.Y); break; // INY (implied) - case 0xc9: this.AM_Immediate(); this.CMP(this.A); break; // CMP (immediate) - case 0xca: this.Swallow(); this.X = this.DEC(this.X); break; // DEX (implied) - case 0xcb: this.AM_Immediate(); this.AXS(); break; // *AXS (immediate) - case 0xcc: this.AM_Absolute(); this.CMP(this.Y); break; // CPY (absolute) - case 0xcd: this.AM_Absolute(); this.CMP(this.A); break; // CMP (absolute) - case 0xce: this.AM_Absolute(); this.ModifyWrite(this.DEC()); break; // DEC (absolute) - case 0xcf: this.AM_Absolute(); this.DCP(); break; // *DCP (absolute) + case 0xc2: this.ImmediateRead(); break; // *NOP (immediate) + case 0xc3: this.IndexedIndirectXRead(); this.DCP(); break; // *DCP (indexed indirect X) + case 0xc7: this.ZeroPageRead(); this.DCP(); break; // *DCP (zero page) + case 0xcb: this.ImmediateRead(); this.AXS(); break; // *AXS (immediate) + case 0xcf: this.AbsoluteRead(); this.DCP(); break; // *DCP (absolute) - case 0xd0: this.BranchNot(this.Zero); break; // BNE (relative) - case 0xd1: this.AM_IndirectIndexedY(); this.CMP(this.A); break; // CMP (indirect indexed Y) case 0xd2: this.Jam(); break; // *JAM - case 0xd3: this.Address_IndirectIndexedY(); this.FixupR(); this.DCP(); break; // *DCP (indirect indexed Y) - case 0xd4: this.AM_ZeroPageX(); break; // *NOP (zero page, X) - case 0xd5: this.AM_ZeroPageX(); this.CMP(this.A); break; // CMP (zero page, X) - case 0xd6: this.AM_ZeroPageX(); this.ModifyWrite(this.DEC()); break; // DEC (zero page, X) - case 0xd7: this.AM_ZeroPageX(); this.DCP(); break; // *DCP (zero page, X) - case 0xd8: this.Swallow(); this.ResetFlag(StatusBits.DF); break; // CLD (implied) - case 0xd9: this.AM_AbsoluteY(); this.CMP(this.A); break; // CMP (absolute, Y) + case 0xd3: this.IndirectIndexedYAddress(); this.FixupR(); this.DCP(); break; // *DCP (indirect indexed Y) + case 0xd4: this.ZeroPageXRead(); break; // *NOP (zero page, X) + case 0xd7: this.ZeroPageXRead(); this.DCP(); break; // *DCP (zero page, X) case 0xda: this.Swallow(); break; // *NOP (implied) - case 0xdb: this.Address_AbsoluteY(); this.FixupR(); this.DCP(); break; // *DCP (absolute, Y) - case 0xdc: this.Address_AbsoluteX(); this.MaybeFixupR(); break; // *NOP (absolute, X) - case 0xdd: this.AM_AbsoluteX(); this.CMP(this.A); break; // CMP (absolute, X) - case 0xde: this.Address_AbsoluteX(); this.FixupR(); this.ModifyWrite(this.DEC()); break; // DEC (absolute, X) - case 0xdf: this.Address_AbsoluteX(); this.FixupR(); this.DCP(); break; // *DCP (absolute, X) + case 0xdb: this.AbsoluteYAddress(); this.FixupR(); this.DCP(); break; // *DCP (absolute, Y) + case 0xdc: this.AbsoluteXAddress(); this.MaybeFixupR(); break; // *NOP (absolute, X) + case 0xdf: this.AbsoluteXAddress(); this.FixupR(); this.DCP(); break; // *DCP (absolute, X) - case 0xe0: this.AM_Immediate(); this.CMP(this.X); break; // CPX (immediate) - case 0xe1: this.AM_IndexedIndirectX(); this.SBC(); break; // SBC (indexed indirect X) - case 0xe2: this.AM_Immediate(); break; // *NOP (immediate) - case 0xe3: this.AM_IndexedIndirectX(); this.ISB(); break; // *ISB (indexed indirect X) - case 0xe4: this.AM_ZeroPage(); this.CMP(this.X); break; // CPX (zero page) - case 0xe5: this.AM_ZeroPage(); this.SBC(); break; // SBC (zero page) - case 0xe6: this.AM_ZeroPage(); this.ModifyWrite(INC()); break; // INC (zero page) - case 0xe7: this.AM_ZeroPage(); this.ISB(); break; // *ISB (zero page) - case 0xe8: this.Swallow(); this.X = this.INC(this.X); break; // INX (implied) - case 0xe9: this.AM_Immediate(); this.SBC(); break; // SBC (immediate) - case 0xea: this.Swallow(); break; // NOP (implied) - case 0xeb: this.AM_Immediate(); this.SBC(); break; // *SBC (immediate) - case 0xec: this.AM_Absolute(); this.CMP(this.X); break; // CPX (absolute) - case 0xed: this.AM_Absolute(); this.SBC(); break; // SBC (absolute) - case 0xee: this.AM_Absolute(); this.ModifyWrite(this.INC()); break; // INC (absolute) - case 0xef: this.AM_Absolute(); this.ISB(); break; // *ISB (absolute) + case 0xe2: this.ImmediateRead(); break; // *NOP (immediate) + case 0xe3: this.IndexedIndirectXRead(); this.ISB(); break; // *ISB (indexed indirect X) + case 0xe7: this.ZeroPageRead(); this.ISB(); break; // *ISB (zero page) + case 0xeb: this.ImmediateRead(); this.SBC(); break; // *SBC (immediate) + case 0xef: this.AbsoluteRead(); this.ISB(); break; // *ISB (absolute) - case 0xf0: this.Branch(this.Zero); break; // BEQ (relative) - case 0xf1: this.AM_IndirectIndexedY(); this.SBC(); break; // SBC (indirect indexed Y) case 0xf2: this.Jam(); break; // *JAM - case 0xf3: this.Address_IndirectIndexedY(); this.FixupR(); this.ISB(); break; // *ISB (indirect indexed Y) - case 0xf4: this.AM_ZeroPageX(); break; // *NOP (zero page, X) - case 0xf5: this.AM_ZeroPageX(); this.SBC(); break; // SBC (zero page, X) - case 0xf6: this.AM_ZeroPageX(); this.ModifyWrite(this.INC()); break; // INC (zero page, X) - case 0xf7: this.AM_ZeroPageX(); this.ISB(); break; // *ISB (zero page, X) - case 0xf8: this.Swallow(); this.SetFlag(StatusBits.DF); break; // SED (implied) - case 0xf9: this.AM_AbsoluteY(); this.SBC(); break; // SBC (absolute, Y) + case 0xf3: this.IndirectIndexedYAddress(); this.FixupR(); this.ISB(); break; // *ISB (indirect indexed Y) + case 0xf4: this.ZeroPageXRead(); break; // *NOP (zero page, X) + case 0xf7: this.ZeroPageXRead(); this.ISB(); break; // *ISB (zero page, X) case 0xfa: this.Swallow(); break; // *NOP (implied) - case 0xfb: this.Address_AbsoluteY(); this.FixupR(); this.ISB(); break; // *ISB (absolute, Y) - case 0xfc: this.Address_AbsoluteX(); this.MaybeFixupR(); break; // *NOP (absolute, X) - case 0xfd: this.AM_AbsoluteX(); this.SBC(); break; // SBC (absolute, X) - case 0xfe: this.Address_AbsoluteX(); this.FixupR(); this.ModifyWrite(this.INC()); break; // INC (absolute, X) - case 0xff: this.Address_AbsoluteX(); this.FixupR(); this.ISB(); break; // *ISB (absolute, X) - } - } - - public override int Step() - { - this.ResetCycles(); - this.OnExecutingInstruction(); - if (this.Powered) - { - this.Tick(); - if (this.SO.Lowered()) - { - this.HandleSO(); - } - - if (this.RDY.Raised()) - { - this.FetchInstruction(); - if (this.RESET.Lowered()) - { - this.HandleRESET(); - } - else if (this.NMI.Lowered()) - { - this.HandleNMI(); - } - else if (this.INT.Lowered() && (this.InterruptMasked == 0)) - { - this.HandleINT(); - } - else - { - this.Execute(); - } - } + case 0xfb: this.AbsoluteYAddress(); this.FixupR(); this.ISB(); break; // *ISB (absolute, Y) + case 0xfc: this.AbsoluteXAddress(); this.MaybeFixupR(); break; // *NOP (absolute, X) + case 0xff: this.AbsoluteXAddress(); this.FixupR(); this.ISB(); break; // *ISB (absolute, X) } - this.OnExecutedInstruction(); - return this.Cycles; - } - - private void FetchInstruction() - { - // Instruction fetch beginning - this.LowerSYNC(); - - System.Diagnostics.Debug.Assert(this.Cycles == 1, "An extra cycle has occurred"); - - // Can't use fetchByte, since that would add an extra tick. - this.Address_Immediate(); - this.OpCode = this.ReadFromBus(); - - System.Diagnostics.Debug.Assert(this.Cycles == 1, "BUS read has introduced stray cycles"); - - // Instruction fetch has now completed - this.RaiseSYNC(); - } - - #endregion - - #region Bus/Memory access - - protected override sealed void BusWrite() - { - this.Tick(); - this.WriteToBus(); - } - - protected override sealed byte BusRead() - { - this.Tick(); - return this.ReadFromBus(); - } - - private byte ReadFromBus() - { - this.RaiseRW(); - return base.BusRead(); - } - - private void WriteToBus() - { - this.LowerRW(); - base.BusWrite(); - } - - private void ModifyWrite(byte data) - { - // The read will have already taken place... - this.MemoryWrite(); - this.MemoryWrite(data); - } - - #endregion - - #region Stack access - - protected override byte Pop() - { - this.RaiseStack(); - return this.MemoryRead(); - } - - protected override void Push(byte value) - { - this.LowerStack(); - this.MemoryWrite(value); - } - - private void UpdateStack(byte position) - { - this.Bus.Address.Low = position; - this.Bus.Address.High = 1; - } - - private void LowerStack() => this.UpdateStack(this.S--); - - private void RaiseStack() => this.UpdateStack(++this.S); - - private void DummyPush() - { - this.LowerStack(); - this.Tick(); // In place of the memory write - } - - #endregion - - #region Addressing modes - - #region Address page fixup - - private byte unfixedPage; - private byte fixedPage; - - public byte UnfixedPage - { - get => this.unfixedPage; - private set => this.unfixedPage = value; - } - - public byte FixedPage - { - get => this.fixedPage; - private set => this.fixedPage = value; - } - - private void MaybeFixup() - { - if (this.Bus.Address.High != this.FixedPage) - { - this.Fixup(); - } - } - - private void Fixup() - { - this.MemoryRead(); - this.UnfixedPage = this.Bus.Address.High; - this.Bus.Address.High = this.FixedPage; - } - - private void MaybeFixupR() - { - this.MaybeFixup(); - this.MemoryRead(); - } - - private void FixupR() - { - this.Fixup(); - this.MemoryRead(); - } - - #endregion - - #region Address resolution - - private void NoteFixedAddress(int address) - { - this.NoteFixedAddress((ushort)address); - } - - private void NoteFixedAddress(ushort address) - { - this.intermediate.Word = address; - this.FixedPage = this.intermediate.High; - this.Bus.Address.Low = this.intermediate.Low; - } - - private void Address_Immediate() => this.Bus.Address.Word = this.PC.Word++; - - private void Address_Absolute() => this.Bus.Address.Word = this.FetchWord().Word; - - private void Address_ZeroPage() - { - this.Bus.Address.Low = this.FetchByte(); - this.Bus.Address.High = 0; - } - - private void Address_ZeroPageIndirect() - { - this.Address_ZeroPage(); - this.Bus.Address.Word = this.GetWordPaged().Word; - } - - private void Address_Indirect() - { - this.Address_Absolute(); - this.Bus.Address.Word = this.GetWordPaged().Word; - } - - private void Address_ZeroPageWithIndex(byte index) - { - this.AM_ZeroPage(); - this.Bus.Address.Low += index; - } - - private void Address_ZeroPageX() => this.Address_ZeroPageWithIndex(this.X); - - private void Address_ZeroPageY() => this.Address_ZeroPageWithIndex(this.Y); - - private void Address_AbsoluteWithIndex(byte index) - { - this.Address_Absolute(); - this.NoteFixedAddress(this.Bus.Address.Word + index); - } - - private void Address_AbsoluteX() => this.Address_AbsoluteWithIndex(X); - - private void Address_AbsoluteY() => this.Address_AbsoluteWithIndex(Y); - - private void Address_IndexedIndirectX() - { - this.Address_ZeroPageX(); - this.Bus.Address.Word = this.GetWordPaged().Word; - } - - private void Address_IndirectIndexedY() - { - this.Address_ZeroPageIndirect(); - this.NoteFixedAddress(this.Bus.Address.Word + Y); - } - - #endregion - - #region Address and read - - private void AM_Immediate() - { - this.Address_Immediate(); - this.MemoryRead(); - } - - private void AM_Absolute() - { - this.Address_Absolute(); - this.MemoryRead(); - } - - private void AM_ZeroPage() - { - this.Address_ZeroPage(); - this.MemoryRead(); - } - - private void AM_ZeroPageX() - { - this.Address_ZeroPageX(); - this.MemoryRead(); - } - - private void AM_ZeroPageY() - { - this.Address_ZeroPageY(); - this.MemoryRead(); - } - - private void AM_IndexedIndirectX() - { - this.Address_IndexedIndirectX(); - this.MemoryRead(); - } - - private void AM_AbsoluteX() - { - this.Address_AbsoluteX(); - this.MaybeFixupR(); - } - - private void AM_AbsoluteY() - { - this.Address_AbsoluteY(); - this.MaybeFixupR(); - } - - private void AM_IndirectIndexedY() - { - this.Address_IndirectIndexedY(); - this.MaybeFixupR(); - } - - #endregion - - #endregion - - #region Branching - - private void BranchNot(int condition) => this.Branch(condition == 0); - - private void Branch(int condition) => this.Branch(condition != 0); - - private void Branch(bool condition) - { - this.AM_Immediate(); - if (condition) - { - var relative = (sbyte)this.Bus.Data; - this.Swallow(); - this.NoteFixedAddress(this.PC.Word + relative); - this.MaybeFixup(); - this.Jump(this.Bus.Address.Word); - } - } - - #endregion - - #region Data flag adjustment - - private void AdjustZero(byte datum) => this.ResetFlag(StatusBits.ZF, datum); - - private void AdjustNegative(byte datum) => this.SetFlag(StatusBits.NF, NegativeTest(datum)); - - private void AdjustNZ(byte datum) - { - this.AdjustZero(datum); - this.AdjustNegative(datum); - } - - private byte Through() => this.Through(this.Bus.Data); - - private byte Through(int data) => this.Through((byte)data); - - private byte Through(byte data) - { - this.AdjustNZ(data); - return data; + return cycles != this.Cycles; } #endregion #region Instruction implementations - #region Instructions with BCD effects + #region Undocumented instructions - private void AdjustOverflow_add(byte operand) - { - var data = Bus.Data; - var intermediate = this.intermediate.Low; - this.SetFlag(StatusBits.VF, NegativeTest((byte)(~(operand ^ data) & (operand ^ intermediate)))); - } - - private void AdjustOverflow_subtract(byte operand) - { - var data = Bus.Data; - var intermediate = this.intermediate.Low; - this.SetFlag(StatusBits.VF, NegativeTest((byte)((operand ^ data) & (operand ^ intermediate)))); - } - - #region Addition/subtraction - - #region Subtraction - - private void SBC() - { - var operand = this.A; - A = this.SUB(operand, CarryTest((byte)~this.P)); - - this.AdjustOverflow_subtract(operand); - this.AdjustNZ(this.intermediate.Low); - this.ResetFlag(StatusBits.CF, this.intermediate.High); - } - - private byte SUB(byte operand, int borrow) => this.Decimal != 0 ? SUB_d(operand, borrow) : SUB_b(operand, borrow); - - private byte SUB_b(byte operand, int borrow = 0) - { - var data = Bus.Data; - this.intermediate.Word = (ushort)(operand - data - borrow); - return this.intermediate.Low; - } - - private byte SUB_d(byte operand, int borrow) - { - _ = this.SUB_b(operand, borrow); - - var data = this.Bus.Data; - var low = (byte)(LowNibble(operand) - LowNibble(data) - borrow); - var lowNegative = NegativeTest(low); - if (lowNegative != 0) - { - low -= 6; - } - - var high = (byte)(HighNibble(operand) - HighNibble(data) - (lowNegative >> 7)); - var highNegative = NegativeTest(high); - if (highNegative != 0) - { - high -= 6; - } - - return (byte)(PromoteNibble(high) | LowNibble(low)); - } - - #endregion - - #region Addition - - private void ADC() - { - this.A = this.Decimal != 0 ? this.ADC_d() : this.ADC_b(); - } - - private byte ADC_b() - { - var operand = A; - var data = Bus.Data; - this.intermediate.Word = (ushort)(operand + data + this.Carry); - - this.AdjustOverflow_add(operand); - this.SetFlag(StatusBits.CF, CarryTest(this.intermediate.High)); - - this.AdjustNZ(intermediate.Low); - - return intermediate.Low; - } - - private byte ADC_d() - { - var operand = this.A; - var data = this.Bus.Data; - - var low = (ushort)(LowerNibble(operand) + LowerNibble(data) + this.Carry); - this.intermediate.Word = (ushort)(HigherNibble(operand) + HigherNibble(data)); - - this.AdjustZero(LowByte((ushort)(low + this.intermediate.Word))); - - if (low > 0x09) - { - this.intermediate.Word += 0x10; - low += 0x06; - } - - this.AdjustNegative(this.intermediate.Low); - this.AdjustOverflow_add(operand); - - if (this.intermediate.Word > 0x90) - this.intermediate.Word += 0x60; - - this.SetFlag(StatusBits.CF, this.intermediate.High); - - return (byte)(LowerNibble(LowByte(low)) | HigherNibble(this.intermediate.Low)); - } - - #endregion - - #endregion - - #region ARR (undocument instruction) + #region Undocumented instructions with BCD effects private void ARR() { var value = this.Bus.Data; - if (this.Decimal != 0) + if (this.DecimalMasked != 0) this.ARR_d(value); else this.ARR_b(value); @@ -1135,128 +189,8 @@ namespace EightBit #endregion - #endregion - - #region Bitwise operations - - private void OrR() => this.A = this.Through(this.A | this.Bus.Data); - - private void AndR() => this.A = this.Through(this.A & this.Bus.Data); - - private void EorR() => this.A = this.Through(this.A ^ this.Bus.Data); - - private void BIT() - { - var data = this.Bus.Data; - this.SetFlag(StatusBits.VF, OverflowTest(data)); - this.AdjustZero((byte)(this.A & data)); - this.AdjustNegative(data); - } - - #endregion - - private void CMP(byte first) - { - var second = Bus.Data; - this.intermediate.Word = (ushort)(first - second); - AdjustNZ(this.intermediate.Low); - ResetFlag(StatusBits.CF, this.intermediate.High); - } - - #region Increment/decrement - - private byte DEC() => this.DEC(this.Bus.Data); - - private byte DEC(byte value) => this.Through(value - 1); - - private byte INC() => this.INC(this.Bus.Data); - - private byte INC(byte value) => this.Through(value + 1); - - #endregion - - #region Stack operations - - private void JSR() - { - this.intermediate.Low = this.FetchByte(); - this.SwallowStack(); - this.PushWord(this.PC); - this.intermediate.High = this.FetchByte(); - this.PC.Word = this.intermediate.Word; - } - - private void PHP() => this.Push(SetBit(this.P, StatusBits.BF)); - - private void PLP() - { - this.SwallowStack(); - this.P = ClearBit(SetBit(this.Pop(), StatusBits.RF), StatusBits.BF); - } - - private void RTI() - { - this.PLP(); - this.Return(); - } - - private void RTS() - { - this.SwallowStack(); - this.Return(); - this.SwallowFetch(); - } - - #endregion - - #region Shift/rotate operations - - #region Shift - - private byte ASL() => this.ASL(this.Bus.Data); - - private byte ASL(byte value) - { - this.SetFlag(StatusBits.CF, NegativeTest(value)); - return this.Through(value << 1); - } - - private byte LSR() => this.LSR(this.Bus.Data); - - private byte LSR(byte value) - { - this.SetFlag(StatusBits.CF, CarryTest(value)); - return this.Through(value >> 1); - } - - #endregion - - #region Rotate - - private byte ROL() => this.ROL(this.Bus.Data); - - private byte ROL(byte value) - { - var carryIn = this.Carry; - return this.Through(this.ASL(value) | carryIn); - } - - private byte ROR() => this.ROR(this.Bus.Data); - - private byte ROR(byte value) - { - var carryIn = this.Carry; - return this.Through(this.LSR(value) | (carryIn << 7)); - } - - #endregion - - #endregion - - #region Undocumented instructions - #region Undocumented instructions with fixup effects - + private void StoreFixupEffect(byte data) { var fixedAddress = (byte)(this.Bus.Address.High + 1); @@ -1279,8 +213,8 @@ namespace EightBit private void AXS() { - this.X = this.Through(this.SUB_b((byte)(this.A & this.X))); - this.ResetFlag(StatusBits.CF, this.intermediate.High); + this.X = this.Through(this.BinarySUB((byte)(this.A & this.X))); + this.ResetFlag(StatusBits.CF, this.Intermediate.High); } private void Jam() @@ -1290,7 +224,6 @@ namespace EightBit this.MemoryRead(); } - private void TAS() { this.S = (byte)(this.A & this.X); diff --git a/M6502/M6502Core.cs b/M6502/M6502Core.cs new file mode 100644 index 0000000..fb232b2 --- /dev/null +++ b/M6502/M6502Core.cs @@ -0,0 +1,1119 @@ +// +// Copyright (c) Adrian Conlon. All rights reserved. +// + +namespace EightBit +{ + public class M6502Core(Bus bus) : LittleEndianProcessor(bus) + { + #region Instruction execution events + + public event EventHandler? ExecutingInstruction; + public event EventHandler? ExecutedInstruction; + protected virtual void OnExecutedInstruction() => this.ExecutedInstruction?.Invoke(this, EventArgs.Empty); + protected virtual void OnExecutingInstruction() => this.ExecutingInstruction?.Invoke(this, EventArgs.Empty); + + #endregion + + #region Pin controls + + #region NMI pin + + public ref PinLevel NMI => ref this.nmiLine; + private PinLevel nmiLine = PinLevel.Low; + public event EventHandler? RaisingNMI; + public event EventHandler? RaisedNMI; + public event EventHandler? LoweringNMI; + public event EventHandler? LoweredNMI; + protected virtual void OnRaisingNMI() => this.RaisingNMI?.Invoke(this, EventArgs.Empty); + protected virtual void OnRaisedNMI() => this.RaisedNMI?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweringNMI() => this.LoweringNMI?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweredNMI() => this.LoweredNMI?.Invoke(this, EventArgs.Empty); + + + [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()) + { + this.OnRaisingNMI(); + this.NMI.Raise(); + this.OnRaisedNMI(); + } + } + + public virtual void LowerNMI() + { + if (this.NMI.Raised()) + { + this.OnLoweringNMI(); + this.NMI.Lower(); + this.OnLoweredNMI(); + } + } + + #endregion + + #region SO pin + + public ref PinLevel SO => ref this.soLine; + private PinLevel soLine = PinLevel.Low; + public event EventHandler? RaisingSO; + public event EventHandler? RaisedSO; + public event EventHandler? LoweringSO; + public event EventHandler? LoweredSO; + + protected virtual void OnRaisingSO() => this.RaisingSO?.Invoke(this, EventArgs.Empty); + protected virtual void OnRaisedSO() => this.RaisedSO?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweringSO() => this.LoweringSO?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweredSO() => this.LoweredSO?.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 RaiseSO() + { + if (this.SO.Lowered()) + { + this.OnRaisingSO(); + this.SO.Raise(); + this.OnRaisedSO(); + } + } + + public virtual void LowerSO() + { + if (this.SO.Raised()) + { + this.OnLoweringSO(); + this.SO.Lower(); + this.OnLoweredSO(); + } + } + + #endregion + + #region SYNC pin + + public ref PinLevel SYNC => ref this.syncLine; + private PinLevel syncLine = PinLevel.Low; + public event EventHandler? RaisingSYNC; + public event EventHandler? RaisedSYNC; + public event EventHandler? LoweringSYNC; + public event EventHandler? LoweredSYNC; + + protected virtual void OnRaisingSYNC() => this.RaisingSYNC?.Invoke(this, EventArgs.Empty); + protected virtual void OnRaisedSYNC() => this.RaisedSYNC?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweringSYNC() => this.LoweringSYNC?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweredSYNC() => this.LoweredSYNC?.Invoke(this, EventArgs.Empty); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] + protected virtual void RaiseSYNC() + { + this.OnRaisingSYNC(); + this.SYNC.Raise(); + this.OnRaisedSYNC(); + } + + protected virtual void LowerSYNC() + { + this.OnLoweringSYNC(); + this.SYNC.Lower(); + this.OnLoweredSYNC(); + } + + #endregion + + #region RDY pin + + public ref PinLevel RDY => ref this.rdyLine; + private PinLevel rdyLine = PinLevel.Low; + public event EventHandler? RaisingRDY; + public event EventHandler? RaisedRDY; + public event EventHandler? LoweringRDY; + public event EventHandler? LoweredRDY; + protected virtual void OnRaisingRDY() => this.RaisingRDY?.Invoke(this, EventArgs.Empty); + protected virtual void OnRaisedRDY() => this.RaisedRDY?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweringRDY() => this.LoweringRDY?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweredRDY() => this.LoweredRDY?.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 RaiseRDY() + { + if (this.RDY.Lowered()) + { + this.OnRaisingRDY(); + this.RDY.Raise(); + this.OnRaisedRDY(); + } + } + + public virtual void LowerRDY() + { + if (this.RDY.Raised()) + { + this.OnLoweringRDY(); + this.RDY.Lower(); + this.OnLoweredRDY(); + } + } + + #endregion + + #region RW pin + + public ref PinLevel RW => ref this.rwLine; + private PinLevel rwLine = PinLevel.Low; + public event EventHandler? RaisingRW; + public event EventHandler? RaisedRW; + public event EventHandler? LoweringRW; + public event EventHandler? LoweredRW; + protected virtual void OnRaisingRW() => this.RaisingRW?.Invoke(this, EventArgs.Empty); + protected virtual void OnRaisedRW() => this.RaisedRW?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweringRW() => this.LoweringRW?.Invoke(this, EventArgs.Empty); + protected virtual void OnLoweredRW() => this.LoweredRW?.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 RaiseRW() + { + if (this.RW.Lowered()) + { + this.OnRaisingRW(); + this.RW.Raise(); + this.OnRaisedRW(); + } + } + + public virtual void LowerRW() + { + if (this.RW.Raised()) + { + this.OnLoweringRW(); + this.RW.Lower(); + this.OnLoweredRW(); + } + } + + #endregion + + protected override void OnRaisedPOWER() + { + this.X = (byte)Bits.Bit7; + this.Y = 0; + this.A = 0; + this.P = (byte)StatusBits.RF; + this.S = (byte)Mask.Eight; + this.LowerSYNC(); + this.LowerRW(); + base.OnRaisedPOWER(); + } + + #endregion + + #region Interrupts + + private const byte IRQvector = 0xfe; // IRQ vector + private const byte RSTvector = 0xfc; // RST vector + private const byte NMIvector = 0xfa; // NMI vector + + enum InterruptSource { hardware, software }; + + enum InterruptType { reset, non_reset }; + + private void Interrupt(byte vector, InterruptSource source = InterruptSource.hardware, InterruptType type = InterruptType.non_reset) + { + if (type == InterruptType.reset) + { + this.DummyPush(); + this.DummyPush(); + this.DummyPush(); + } + else + { + this.PushWord(this.PC); + this.Push((byte)(this.P | (source == InterruptSource.hardware ? 0 : (byte)StatusBits.BF))); + } + this.SetFlag(StatusBits.IF); // Disable IRQ + this.Jump(this.GetWordPaged(0xff, vector).Word); + } + + #region Interrupt etc. handlers + + protected override sealed void HandleRESET() + { + this.RaiseRESET(); + this.Interrupt(RSTvector, InterruptSource.hardware, InterruptType.reset); + } + + protected override sealed void HandleINT() + { + this.RaiseINT(); + this.Interrupt(IRQvector); + } + + private void HandleNMI() + { + this.RaiseNMI(); + this.Interrupt(NMIvector); + } + + private void HandleSO() + { + this.RaiseSO(); + this.SetFlag(StatusBits.VF); + } + + #endregion + + #endregion + + #region Registers + + public byte X { get; set; } + public byte Y { get; set; } + public byte A { get; set; } + public byte S { get; set; } + public byte P { get; set; } + + #endregion + + #region Processor state helpers + + protected int InterruptMasked => this.P & (byte)StatusBits.IF; + protected int DecimalMasked => this.P & (byte)StatusBits.DF; + protected int Negative => NegativeTest(this.P); + protected int Zero => ZeroTest(this.P); + protected int Overflow => OverflowTest(this.P); + protected int Carry => CarryTest(this.P); + + protected static int NegativeTest(byte data) => data & (byte)StatusBits.NF; + protected static int ZeroTest(byte data) => data & (byte)StatusBits.ZF; + protected static int OverflowTest(byte data) => data & (byte)StatusBits.VF; + protected static int CarryTest(byte data) => data & (byte)StatusBits.CF; + + #endregion + + #region Bit/state twiddling + + #region Bit twiddling + + private static byte SetBit(byte f, StatusBits flag) => SetBit(f, (byte)flag); + + private static byte SetBit(byte f, StatusBits flag, int condition) => SetBit(f, (byte)flag, condition); + + private static byte SetBit(byte f, StatusBits flag, bool condition) => SetBit(f, (byte)flag, condition); + + private static byte ClearBit(byte f, StatusBits flag) => ClearBit(f, (byte)flag); + + private static byte ClearBit(byte f, StatusBits flag, int condition) => ClearBit(f, (byte)flag, condition); + + #endregion + + #region State flag twiddling + + protected void SetFlag(StatusBits flag) + { + this.P = SetBit(this.P, flag); + } + + protected void SetFlag(StatusBits which, int condition) + { + this.P = SetBit(this.P, which, condition); + } + + protected void SetFlag(StatusBits which, bool condition) + { + this.P = SetBit(this.P, which, condition); + } + + protected void ResetFlag(StatusBits which) + { + this.P = ClearBit(this.P, which); + } + + protected void ResetFlag(StatusBits which, int condition) + { + this.P = ClearBit(this.P, which, condition); + } + + #endregion + + #endregion + + #region Cycle wastage + + protected void Swallow() => this.MemoryRead(this.PC.Word); + + protected void SwallowStack() => this.MemoryRead(this.S, 1); + + protected void SwallowFetch() => this.FetchByte(); + + #endregion + + #region Core instruction dispatching + + public override void Execute() + { + this.MaybeExecute(); + } + + protected virtual bool MaybeExecute() + { + var cycles = this.Cycles; + switch (this.OpCode) + { + case 0x00: this.SwallowFetch(); this.Interrupt(IRQvector, InterruptSource.software); break; // BRK (implied) + case 0x01: this.IndexedIndirectXRead(); this.OrR(); break; // ORA (indexed indirect X) + case 0x05: this.ZeroPageRead(); this.OrR(); break; // ORA (zero page) + case 0x06: this.ZeroPageRead(); this.ModifyWrite(this.ASL()); break; // ASL (zero page) + case 0x08: this.Swallow(); this.PHP(); break; // PHP (implied) + case 0x09: this.ImmediateRead(); this.OrR(); break; // ORA (immediate) + case 0x0a: this.Swallow(); A = this.ASL(A); break; // ASL A (implied) + case 0x0d: this.AbsoluteRead(); this.OrR(); break; // ORA (absolute) + case 0x0e: this.AbsoluteRead(); this.ModifyWrite(this.ASL()); break; // ASL (absolute) + + case 0x10: this.BranchNot(this.Negative); break; // BPL (relative) + case 0x11: this.IndirectIndexedYRead(); this.OrR(); break; // ORA (indirect indexed Y) + case 0x15: this.ZeroPageXRead(); this.OrR(); break; // ORA (zero page, X) + case 0x16: this.ZeroPageXRead(); this.ModifyWrite(this.ASL()); break; // ASL (zero page, X) + case 0x18: this.Swallow(); this.ResetFlag(StatusBits.CF); break; // CLC (implied) + case 0x19: this.AbsoluteYRead(); this.OrR(); break; // ORA (absolute, Y) + case 0x1d: this.AbsoluteXRead(); this.OrR(); break; // ORA (absolute, X) + case 0x1e: this.AbsoluteXAddress(); this.FixupR(); this.ModifyWrite(this.ASL()); break; // ASL (absolute, X) + + case 0x20: this.JSR(); break; // JSR (absolute) + case 0x21: this.IndexedIndirectXRead(); this.AndR(); break; // AND (indexed indirect X) + case 0x24: this.ZeroPageRead(); this.BIT(); break; // BIT (zero page) + case 0x25: this.ZeroPageRead(); this.AndR(); break; // AND (zero page) + case 0x26: this.ZeroPageRead(); this.ModifyWrite(this.ROL()); break; // ROL (zero page) + case 0x28: this.Swallow(); this.PLP(); break; // PLP (implied) + case 0x29: this.ImmediateRead(); this.AndR(); break; // AND (immediate) + case 0x2a: this.Swallow(); this.A = this.ROL(this.A); break; // ROL A (implied) + case 0x2c: this.AbsoluteRead(); this.BIT(); break; // BIT (absolute) + case 0x2d: this.AbsoluteRead(); this.AndR(); break; // AND (absolute) + case 0x2e: this.AbsoluteRead(); this.ModifyWrite(this.ROL()); break; // ROL (absolute) + + case 0x30: this.Branch(this.Negative); break; // BMI (relative) + case 0x31: this.IndirectIndexedYRead(); this.AndR(); break; // AND (indirect indexed Y) + case 0x35: this.ZeroPageXRead(); this.AndR(); break; // AND (zero page, X) + case 0x36: this.ZeroPageXRead(); this.ModifyWrite(this.ROL()); break; // ROL (zero page, X) + case 0x38: this.Swallow(); this.SetFlag(StatusBits.CF); break; // SEC (implied) + case 0x39: this.AbsoluteYRead(); this.AndR(); break; // AND (absolute, Y) + case 0x3d: this.AbsoluteXRead(); this.AndR(); break; // AND (absolute, X) + case 0x3e: this.AbsoluteXAddress(); this.FixupR(); this.ModifyWrite(this.ROL()); break; // ROL (absolute, X) + + case 0x40: this.Swallow(); this.RTI(); break; // RTI (implied) + case 0x41: this.IndexedIndirectXRead(); this.EorR(); break; // EOR (indexed indirect X) + case 0x45: this.ZeroPageRead(); this.EorR(); break; // EOR (zero page) + case 0x46: this.ZeroPageRead(); this.ModifyWrite(this.LSR()); break; // LSR (zero page) + case 0x48: this.Swallow(); this.Push(this.A); break; // PHA (implied) + case 0x49: this.ImmediateRead(); this.EorR(); break; // EOR (immediate) + case 0x4a: this.Swallow(); this.A = this.LSR(this.A); break; // LSR A (implied) + case 0x4c: this.AbsoluteAddress(); this.Jump(this.Bus.Address.Word); break; // JMP (absolute) + case 0x4d: this.AbsoluteRead(); this.EorR(); break; // EOR (absolute) + case 0x4e: this.AbsoluteRead(); this.ModifyWrite(this.LSR()); break; // LSR (absolute) + + case 0x50: this.BranchNot(this.Overflow); break; // BVC (relative) + case 0x51: this.IndirectIndexedYRead(); this.EorR(); break; // EOR (indirect indexed Y) + case 0x55: this.ZeroPageXRead(); this.EorR(); break; // EOR (zero page, X) + case 0x56: this.ZeroPageXRead(); this.ModifyWrite(this.LSR()); break; // LSR (zero page, X) + case 0x58: this.Swallow(); this.ResetFlag(StatusBits.IF); break; // CLI (implied) + case 0x59: this.AbsoluteYRead(); this.EorR(); break; // EOR (absolute, Y) + case 0x5d: this.AbsoluteXRead(); this.EorR(); break; // EOR (absolute, X) + case 0x5e: this.AbsoluteXAddress(); this.FixupR(); this.ModifyWrite(this.LSR()); break; // LSR (absolute, X) + + case 0x60: this.Swallow(); this.RTS(); break; // RTS (implied) + case 0x61: this.IndexedIndirectXRead(); this.ADC(); break; // ADC (indexed indirect X) + case 0x65: this.ZeroPageRead(); this.ADC(); break; // ADC (zero page) + case 0x66: this.ZeroPageRead(); this.ModifyWrite(this.ROR()); break; // ROR (zero page) + case 0x68: this.Swallow(); this.SwallowStack(); this.A = this.Through(this.Pop()); break; // PLA (implied) + case 0x69: this.ImmediateRead(); this.ADC(); break; // ADC (immediate) + case 0x6a: this.Swallow(); this.A = this.ROR(this.A); break; // ROR A (implied) + case 0x6c: this.IndirectAddress(); this.Jump(this.Bus.Address.Word); break; // JMP (indirect) + case 0x6d: this.AbsoluteRead(); this.ADC(); break; // ADC (absolute) + case 0x6e: this.AbsoluteRead(); this.ModifyWrite(this.ROR()); break; // ROR (absolute) + + case 0x70: this.Branch(this.Overflow); break; // BVS (relative) + case 0x71: this.IndirectIndexedYRead(); this.ADC(); break; // ADC (indirect indexed Y) + case 0x75: this.ZeroPageXRead(); this.ADC(); break; // ADC (zero page, X) + case 0x76: this.ZeroPageXRead(); this.ModifyWrite(this.ROR()); break; // ROR (zero page, X) + case 0x78: this.Swallow(); this.SetFlag(StatusBits.IF); break; // SEI (implied) + case 0x79: this.AbsoluteYRead(); this.ADC(); break; // ADC (absolute, Y) + case 0x7d: this.AbsoluteXRead(); this.ADC(); break; // ADC (absolute, X) + case 0x7e: this.AbsoluteXAddress(); this.FixupR(); this.ModifyWrite(this.ROR()); break; // ROR (absolute, X) + + case 0x81: this.IndexedIndirectXAddress(); this.MemoryWrite(A); break; // STA (indexed indirect X) + case 0x84: this.ZeroPageAddress(); this.MemoryWrite(this.Y); break; // STY (zero page) + case 0x85: this.ZeroPageAddress(); this.MemoryWrite(this.A); break; // STA (zero page) + case 0x86: this.ZeroPageAddress(); this.MemoryWrite(this.X); break; // STX (zero page) + case 0x88: this.Swallow(); this.Y = this.DEC(this.Y); break; // DEY (implied) + case 0x8a: this.Swallow(); this.A = this.Through(this.X); break; // TXA (implied) + case 0x8c: this.AbsoluteAddress(); this.MemoryWrite(this.Y); break; // STY (absolute) + case 0x8d: this.AbsoluteAddress(); this.MemoryWrite(this.A); break; // STA (absolute) + case 0x8e: this.AbsoluteAddress(); this.MemoryWrite(this.X); break; // STX (absolute) + + case 0x90: this.BranchNot(this.Carry); break; // BCC (relative) + case 0x91: this.IndirectIndexedYAddress(); this.Fixup(); this.MemoryWrite(this.A); break; // STA (indirect indexed Y) + case 0x94: this.ZeroPageXAddress(); this.MemoryWrite(this.Y); break; // STY (zero page, X) + case 0x95: this.ZeroPageXAddress(); this.MemoryWrite(this.A); break; // STA (zero page, X) + case 0x96: this.ZeroPageYAddress(); this.MemoryWrite(this.X); break; // STX (zero page, Y) + case 0x98: this.Swallow(); this.A = this.Through(this.Y); break; // TYA (implied) + case 0x99: this.AbsoluteYAddress(); this.Fixup(); this.MemoryWrite(this.A); break; // STA (absolute, Y) + case 0x9a: this.Swallow(); this.S = this.X; break; // TXS (implied) + case 0x9d: this.AbsoluteXAddress(); this.Fixup(); this.MemoryWrite(this.A); break; // STA (absolute, X) + + case 0xa0: this.ImmediateRead(); this.Y = this.Through(); break; // LDY (immediate) + case 0xa1: this.IndexedIndirectXRead(); this.A = this.Through(); break; // LDA (indexed indirect X) + case 0xa2: this.ImmediateRead(); this.X = this.Through(); break; // LDX (immediate) + case 0xa4: this.ZeroPageRead(); this.Y = this.Through(); break; // LDY (zero page) + case 0xa5: this.ZeroPageRead(); this.A = this.Through(); break; // LDA (zero page) + case 0xa6: this.ZeroPageRead(); this.X = this.Through(); break; // LDX (zero page) + case 0xa8: this.Swallow(); this.Y = Through(this.A); break; // TAY (implied) + case 0xa9: this.ImmediateRead(); this.A = this.Through(); break; // LDA (immediate) + case 0xaa: this.Swallow(); this.X = this.Through(this.A); break; // TAX (implied) + case 0xac: this.AbsoluteRead(); this.Y = this.Through(); break; // LDY (absolute) + case 0xad: this.AbsoluteRead(); this.A = this.Through(); break; // LDA (absolute) + case 0xae: this.AbsoluteRead(); this.X = this.Through(); break; // LDX (absolute) + + case 0xb0: this.Branch(this.Carry); break; // BCS (relative) + case 0xb1: this.IndirectIndexedYRead(); this.A = this.Through(); break; // LDA (indirect indexed Y) + case 0xb4: this.ZeroPageXRead(); this.Y = this.Through(); break; // LDY (zero page, X) + case 0xb5: this.ZeroPageXRead(); this.A = this.Through(); break; // LDA (zero page, X) + case 0xb6: this.ZeroPageYRead(); this.X = this.Through(); break; // LDX (zero page, Y) + case 0xb8: this.Swallow(); this.ResetFlag(StatusBits.VF); break; // CLV (implied) + case 0xb9: this.AbsoluteYRead(); this.A = this.Through(); break; // LDA (absolute, Y) + case 0xba: this.Swallow(); this.X = this.Through(this.S); break; // TSX (implied) + case 0xbc: this.AbsoluteXRead(); this.Y = this.Through(); break; // LDY (absolute, X) + case 0xbd: this.AbsoluteXRead(); this.A = this.Through(); break; // LDA (absolute, X) + case 0xbe: this.AbsoluteYRead(); this.X = this.Through(); break; // LDX (absolute, Y) + + case 0xc0: this.ImmediateRead(); this.CMP(this.Y); break; // CPY (immediate) + case 0xc1: this.IndexedIndirectXRead(); this.CMP(this.A); break; // CMP (indexed indirect X) + case 0xc4: this.ZeroPageRead(); this.CMP(this.Y); break; // CPY (zero page) + case 0xc5: this.ZeroPageRead(); this.CMP(this.A); break; // CMP (zero page) + case 0xc6: this.ZeroPageRead(); this.ModifyWrite(this.DEC()); break; // DEC (zero page) + case 0xc8: this.Swallow(); this.Y = this.INC(this.Y); break; // INY (implied) + case 0xc9: this.ImmediateRead(); this.CMP(this.A); break; // CMP (immediate) + case 0xca: this.Swallow(); this.X = this.DEC(this.X); break; // DEX (implied) + case 0xcc: this.AbsoluteRead(); this.CMP(this.Y); break; // CPY (absolute) + case 0xcd: this.AbsoluteRead(); this.CMP(this.A); break; // CMP (absolute) + case 0xce: this.AbsoluteRead(); this.ModifyWrite(this.DEC()); break; // DEC (absolute) + + case 0xd0: this.BranchNot(this.Zero); break; // BNE (relative) + case 0xd1: this.IndirectIndexedYRead(); this.CMP(this.A); break; // CMP (indirect indexed Y) + case 0xd5: this.ZeroPageXRead(); this.CMP(this.A); break; // CMP (zero page, X) + case 0xd6: this.ZeroPageXRead(); this.ModifyWrite(this.DEC()); break; // DEC (zero page, X) + case 0xd8: this.Swallow(); this.ResetFlag(StatusBits.DF); break; // CLD (implied) + case 0xd9: this.AbsoluteYRead(); this.CMP(this.A); break; // CMP (absolute, Y) + case 0xdd: this.AbsoluteXRead(); this.CMP(this.A); break; // CMP (absolute, X) + case 0xde: this.AbsoluteXAddress(); this.FixupR(); this.ModifyWrite(this.DEC()); break; // DEC (absolute, X) + + case 0xe0: this.ImmediateRead(); this.CMP(this.X); break; // CPX (immediate) + case 0xe1: this.IndexedIndirectXRead(); this.SBC(); break; // SBC (indexed indirect X) + case 0xe4: this.ZeroPageRead(); this.CMP(this.X); break; // CPX (zero page) + case 0xe5: this.ZeroPageRead(); this.SBC(); break; // SBC (zero page) + case 0xe6: this.ZeroPageRead(); this.ModifyWrite(INC()); break; // INC (zero page) + case 0xe8: this.Swallow(); this.X = this.INC(this.X); break; // INX (implied) + case 0xe9: this.ImmediateRead(); this.SBC(); break; // SBC (immediate) + case 0xea: this.Swallow(); break; // NOP (implied) + case 0xec: this.AbsoluteRead(); this.CMP(this.X); break; // CPX (absolute) + case 0xed: this.AbsoluteRead(); this.SBC(); break; // SBC (absolute) + case 0xee: this.AbsoluteRead(); this.ModifyWrite(this.INC()); break; // INC (absolute) + + case 0xf0: this.Branch(this.Zero); break; // BEQ (relative) + case 0xf1: this.IndirectIndexedYRead(); this.SBC(); break; // SBC (indirect indexed Y) + case 0xf5: this.ZeroPageXRead(); this.SBC(); break; // SBC (zero page, X) + case 0xf6: this.ZeroPageXRead(); this.ModifyWrite(this.INC()); break; // INC (zero page, X) + case 0xf8: this.Swallow(); this.SetFlag(StatusBits.DF); break; // SED (implied) + case 0xf9: this.AbsoluteYRead(); this.SBC(); break; // SBC (absolute, Y) + case 0xfd: this.AbsoluteXRead(); this.SBC(); break; // SBC (absolute, X) + case 0xfe: this.AbsoluteXAddress(); this.FixupR(); this.ModifyWrite(this.INC()); break; // INC (absolute, X) + } + + return cycles != this.Cycles; + } + + public override int Step() + { + this.ResetCycles(); + this.OnExecutingInstruction(); + if (this.Powered) + { + this.Tick(); + if (this.SO.Lowered()) + { + this.HandleSO(); + } + + if (this.RDY.Raised()) + { + this.FetchInstruction(); + if (this.RESET.Lowered()) + { + this.HandleRESET(); + } + else if (this.NMI.Lowered()) + { + this.HandleNMI(); + } + else if (this.INT.Lowered() && (this.InterruptMasked == 0)) + { + this.HandleINT(); + } + else + { + this.Execute(); + } + } + } + + this.OnExecutedInstruction(); + return this.Cycles; + } + + private void FetchInstruction() + { + // Instruction fetch beginning + this.LowerSYNC(); + + System.Diagnostics.Debug.Assert(this.Cycles == 1, "An extra cycle has occurred"); + + // Can't use fetchByte, since that would add an extra tick. + this.ImmediateAddress(); + this.OpCode = this.ReadFromBus(); + + System.Diagnostics.Debug.Assert(this.Cycles == 1, "BUS read has introduced stray cycles"); + + // Instruction fetch has now completed + this.RaiseSYNC(); + } + + #endregion + + #region Bus/Memory access + + protected override sealed void BusWrite() + { + this.Tick(); + this.WriteToBus(); + } + + protected override sealed byte BusRead() + { + this.Tick(); + return this.ReadFromBus(); + } + + private byte ReadFromBus() + { + this.RaiseRW(); + return base.BusRead(); + } + + private void WriteToBus() + { + this.LowerRW(); + base.BusWrite(); + } + + protected void ModifyWrite(byte data) + { + // The read will have already taken place... + this.MemoryWrite(); + this.MemoryWrite(data); + } + + #endregion + + #region Stack access + + protected override byte Pop() + { + this.RaiseStack(); + return this.MemoryRead(); + } + + protected override void Push(byte value) + { + this.LowerStack(); + this.MemoryWrite(value); + } + + private void UpdateStack(byte position) + { + this.Bus.Address.Low = position; + this.Bus.Address.High = 1; + } + + private void LowerStack() => this.UpdateStack(this.S--); + + private void RaiseStack() => this.UpdateStack(++this.S); + + private void DummyPush() + { + this.LowerStack(); + this.Tick(); // In place of the memory write + } + + #endregion + + #region Addressing modes + + #region Address page fixup + + private byte unfixedPage; + private byte fixedPage; + + public byte UnfixedPage + { + get => this.unfixedPage; + private set => this.unfixedPage = value; + } + + public byte FixedPage + { + get => this.fixedPage; + private set => this.fixedPage = value; + } + + protected void MaybeFixup() + { + if (this.Bus.Address.High != this.FixedPage) + { + this.Fixup(); + } + } + + protected void Fixup() + { + this.MemoryRead(); + this.UnfixedPage = this.Bus.Address.High; + this.Bus.Address.High = this.FixedPage; + } + + protected void MaybeFixupR() + { + this.MaybeFixup(); + this.MemoryRead(); + } + + protected void FixupR() + { + this.Fixup(); + this.MemoryRead(); + } + + #endregion + + #region Address resolution + + private void NoteFixedAddress(int address) + { + this.NoteFixedAddress((ushort)address); + } + + private void NoteFixedAddress(ushort address) + { + this.Intermediate.Word = address; + this.FixedPage = this.Intermediate.High; + this.Bus.Address.Low = this.Intermediate.Low; + } + + protected void ImmediateAddress() => this.Bus.Address.Word = this.PC.Word++; + + protected void AbsoluteAddress() => this.Bus.Address.Word = this.FetchWord().Word; + + protected void ZeroPageAddress() + { + this.Bus.Address.Low = this.FetchByte(); + this.Bus.Address.High = 0; + } + + protected void ZeroPageIndirectAddress() + { + this.ZeroPageAddress(); + this.Bus.Address.Word = this.GetWordPaged().Word; + } + + protected void IndirectAddress() + { + this.AbsoluteAddress(); + this.Bus.Address.Word = this.GetWordPaged().Word; + } + + protected void ZeroPageWithIndexAddress(byte index) + { + this.ZeroPageRead(); + this.Bus.Address.Low += index; + } + + protected void ZeroPageXAddress() => this.ZeroPageWithIndexAddress(this.X); + + protected void ZeroPageYAddress() => this.ZeroPageWithIndexAddress(this.Y); + + private void AbsoluteWithIndexAddress(byte index) + { + this.AbsoluteAddress(); + this.NoteFixedAddress(this.Bus.Address.Word + index); + } + + protected void AbsoluteXAddress() => this.AbsoluteWithIndexAddress(X); + + protected void AbsoluteYAddress() => this.AbsoluteWithIndexAddress(Y); + + protected void IndexedIndirectXAddress() + { + this.ZeroPageXAddress(); + this.Bus.Address.Word = this.GetWordPaged().Word; + } + + protected void IndirectIndexedYAddress() + { + this.ZeroPageIndirectAddress(); + this.NoteFixedAddress(this.Bus.Address.Word + Y); + } + + #endregion + + #region Address and read + + protected void ImmediateRead() + { + this.ImmediateAddress(); + this.MemoryRead(); + } + + protected void AbsoluteRead() + { + this.AbsoluteAddress(); + this.MemoryRead(); + } + + protected void ZeroPageRead() + { + this.ZeroPageAddress(); + this.MemoryRead(); + } + + protected void ZeroPageXRead() + { + this.ZeroPageXAddress(); + this.MemoryRead(); + } + + protected void ZeroPageYRead() + { + this.ZeroPageYAddress(); + this.MemoryRead(); + } + + protected void IndexedIndirectXRead() + { + this.IndexedIndirectXAddress(); + this.MemoryRead(); + } + + protected void AbsoluteXRead() + { + this.AbsoluteXAddress(); + this.MaybeFixupR(); + } + + protected void AbsoluteYRead() + { + this.AbsoluteYAddress(); + this.MaybeFixupR(); + } + + protected void IndirectIndexedYRead() + { + this.IndirectIndexedYAddress(); + this.MaybeFixupR(); + } + + #endregion + + #endregion + + #region Branching + + private void BranchNot(int condition) => this.Branch(condition == 0); + + private void Branch(int condition) => this.Branch(condition != 0); + + private void Branch(bool condition) + { + this.ImmediateRead(); + if (condition) + { + var relative = (sbyte)this.Bus.Data; + this.Swallow(); + this.NoteFixedAddress(this.PC.Word + relative); + this.MaybeFixup(); + this.Jump(this.Bus.Address.Word); + } + } + + #endregion + + #region Data flag adjustment + + protected void AdjustZero(byte datum) => this.ResetFlag(StatusBits.ZF, datum); + + protected void AdjustNegative(byte datum) => this.SetFlag(StatusBits.NF, NegativeTest(datum)); + + protected void AdjustNZ(byte datum) + { + this.AdjustZero(datum); + this.AdjustNegative(datum); + } + + protected byte Through() => this.Through(this.Bus.Data); + + protected byte Through(int data) => this.Through((byte)data); + + protected byte Through(byte data) + { + this.AdjustNZ(data); + return data; + } + + #endregion + + #region Instruction implementations + + #region Instructions with BCD effects + + #region Addition/subtraction + + #region Subtraction + + protected void AdjustOverflowSubtract(byte operand) + { + var data = Bus.Data; + var intermediate = this.Intermediate.Low; + this.SetFlag(StatusBits.VF, NegativeTest((byte)((operand ^ data) & (operand ^ intermediate)))); + } + + protected void SBC() + { + var operand = this.A; + A = this.SUB(operand, CarryTest((byte)~this.P)); + + this.AdjustOverflowSubtract(operand); + this.AdjustNZ(this.Intermediate.Low); + this.ResetFlag(StatusBits.CF, this.Intermediate.High); + } + + private byte SUB(byte operand, int borrow) => this.DecimalMasked != 0 ? DecimalSUB(operand, borrow) : BinarySUB(operand, borrow); + + protected byte BinarySUB(byte operand, int borrow = 0) + { + var data = Bus.Data; + this.Intermediate.Word = (ushort)(operand - data - borrow); + return this.Intermediate.Low; + } + + private byte DecimalSUB(byte operand, int borrow) + { + _ = this.BinarySUB(operand, borrow); + + var data = this.Bus.Data; + var low = (byte)(LowNibble(operand) - LowNibble(data) - borrow); + var lowNegative = NegativeTest(low); + if (lowNegative != 0) + { + low -= 6; + } + + var high = (byte)(HighNibble(operand) - HighNibble(data) - (lowNegative >> 7)); + var highNegative = NegativeTest(high); + if (highNegative != 0) + { + high -= 6; + } + + return (byte)(PromoteNibble(high) | LowNibble(low)); + } + + #endregion + + #region Addition + + protected void AdjustOverflowAdd(byte operand) + { + var data = Bus.Data; + var intermediate = this.Intermediate.Low; + this.SetFlag(StatusBits.VF, NegativeTest((byte)(~(operand ^ data) & (operand ^ intermediate)))); + } + + protected void ADC() + { + this.A = this.DecimalMasked != 0 ? this.DecimalADC() : this.BinaryADC(); + } + + private byte BinaryADC() + { + var operand = A; + var data = Bus.Data; + this.Intermediate.Word = (ushort)(operand + data + this.Carry); + + this.AdjustOverflowAdd(operand); + this.SetFlag(StatusBits.CF, CarryTest(this.Intermediate.High)); + + this.AdjustNZ(this.Intermediate.Low); + + return this.Intermediate.Low; + } + + private byte DecimalADC() + { + var operand = this.A; + var data = this.Bus.Data; + + var low = (ushort)(LowerNibble(operand) + LowerNibble(data) + this.Carry); + this.Intermediate.Word = (ushort)(HigherNibble(operand) + HigherNibble(data)); + + this.AdjustZero(LowByte((ushort)(low + this.Intermediate.Word))); + + if (low > 0x09) + { + this.Intermediate.Word += 0x10; + low += 0x06; + } + + this.AdjustNegative(this.Intermediate.Low); + this.AdjustOverflowAdd(operand); + + if (this.Intermediate.Word > 0x90) + this.Intermediate.Word += 0x60; + + this.SetFlag(StatusBits.CF, this.Intermediate.High); + + return (byte)(LowerNibble(LowByte(low)) | HigherNibble(this.Intermediate.Low)); + } + + #endregion + + #endregion + + #endregion + + #region Bitwise operations + + protected void OrR() => this.A = this.Through(this.A | this.Bus.Data); + + protected void AndR() => this.A = this.Through(this.A & this.Bus.Data); + + protected void EorR() => this.A = this.Through(this.A ^ this.Bus.Data); + + private void BIT() + { + var data = this.Bus.Data; + this.SetFlag(StatusBits.VF, OverflowTest(data)); + this.AdjustZero((byte)(this.A & data)); + this.AdjustNegative(data); + } + + #endregion + + protected void CMP(byte first) + { + var second = Bus.Data; + this.Intermediate.Word = (ushort)(first - second); + AdjustNZ(this.Intermediate.Low); + ResetFlag(StatusBits.CF, this.Intermediate.High); + } + + #region Increment/decrement + + protected byte DEC() => this.DEC(this.Bus.Data); + + protected byte DEC(byte value) => this.Through(value - 1); + + protected byte INC() => this.INC(this.Bus.Data); + + protected byte INC(byte value) => this.Through(value + 1); + + #endregion + + #region Stack operations + + private void JSR() + { + this.Intermediate.Low = this.FetchByte(); + this.SwallowStack(); + this.PushWord(this.PC); + this.Intermediate.High = this.FetchByte(); + this.PC.Word = this.Intermediate.Word; + } + + private void PHP() => this.Push(SetBit(this.P, StatusBits.BF)); + + private void PLP() + { + this.SwallowStack(); + this.P = ClearBit(SetBit(this.Pop(), StatusBits.RF), StatusBits.BF); + } + + private void RTI() + { + this.PLP(); + this.Return(); + } + + private void RTS() + { + this.SwallowStack(); + this.Return(); + this.SwallowFetch(); + } + + #endregion + + #region Shift/rotate operations + + #region Shift + + protected byte ASL() => this.ASL(this.Bus.Data); + + protected byte ASL(byte value) + { + this.SetFlag(StatusBits.CF, NegativeTest(value)); + return this.Through(value << 1); + } + + protected byte LSR() => this.LSR(this.Bus.Data); + + protected byte LSR(byte value) + { + this.SetFlag(StatusBits.CF, CarryTest(value)); + return this.Through(value >> 1); + } + + #endregion + + #region Rotate + + protected byte ROL() => this.ROL(this.Bus.Data); + + protected byte ROL(byte value) + { + var carryIn = this.Carry; + return this.Through(this.ASL(value) | carryIn); + } + + protected byte ROR() => this.ROR(this.Bus.Data); + + protected byte ROR(byte value) + { + var carryIn = this.Carry; + return this.Through(this.LSR(value) | (carryIn << 7)); + } + + #endregion + + #endregion + + #endregion + } +} \ No newline at end of file