// // Copyright (c) Adrian Conlon. All rights reserved. // namespace M6502 { using EightBit; public abstract class Core : LittleEndianProcessor { protected Core(Bus bus) : base(bus) { this.RaisedPOWER += this.Core_RaisedPOWER; } protected bool ImmediateInstruction { get; private set; } #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; [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 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; [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()) { RaisingSO?.Invoke(this, EventArgs.Empty); this.SO.Raise(); RaisedSO?.Invoke(this, EventArgs.Empty); } } public virtual void LowerSO() { if (this.SO.Raised()) { LoweringSO?.Invoke(this, EventArgs.Empty); this.SO.Lower(); LoweredSO?.Invoke(this, EventArgs.Empty); } } #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; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] protected virtual void RaiseSYNC() { if (this.SYNC.Lowered()) { RaisingSYNC?.Invoke(this, EventArgs.Empty); this.SYNC.Raise(); RaisedSYNC?.Invoke(this, EventArgs.Empty); } } protected virtual void LowerSYNC() { if (this.SYNC.Raised()) { LoweringSYNC?.Invoke(this, EventArgs.Empty); this.SYNC.Lower(); LoweredSYNC?.Invoke(this, EventArgs.Empty); } } #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; [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()) { RaisingRDY?.Invoke(this, EventArgs.Empty); this.RDY.Raise(); RaisedRDY?.Invoke(this, EventArgs.Empty); } } public virtual void LowerRDY() { if (this.RDY.Raised()) { LoweringRDY?.Invoke(this, EventArgs.Empty); this.RDY.Lower(); LoweredRDY?.Invoke(this, EventArgs.Empty); } } #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; [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()) { RaisingRW?.Invoke(this, EventArgs.Empty); this.RW.Raise(); RaisedRW?.Invoke(this, EventArgs.Empty); } } public virtual void LowerRW() { if (this.RW.Raised()) { LoweringRW?.Invoke(this, EventArgs.Empty); this.RW.Lower(); LoweredRW?.Invoke(this, EventArgs.Empty); } } #endregion private void Core_RaisedPOWER(object? sender, EventArgs e) { 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(); } #endregion #region Interrupts private const byte IRQ_vector = 0xfe; // IRQ vector private const byte RST_vector = 0xfc; // RST vector private const byte NMI_vector = 0xfa; // NMI vector protected enum InterruptSource { hardware, software }; protected enum InterruptType { reset, nonReset }; protected virtual void Interrupt(byte vector, InterruptSource source = InterruptSource.hardware, InterruptType type = InterruptType.nonReset) { if (type == InterruptType.reset) { this.DummyPush(); this.DummyPush(); this.DummyPush(); } else { this.PushShort(this.PC); this.Push((byte)(this.P | (source == InterruptSource.hardware ? 0 : (byte)StatusBits.BF))); } this.SEI(); // Disable IRQ this.GetPagedInto(0xff, vector, this.PC); } #region Interrupt etc. handlers protected sealed override void HandleRESET() { this.RaiseRESET(); this.Interrupt(RST_vector, InterruptSource.hardware, InterruptType.reset); } protected sealed override void HandleINT() { this.RaiseINT(); this.Interrupt(IRQ_vector); } private void HandleNMI() { this.RaiseNMI(); this.Interrupt(NMI_vector); } private void HandleSO() { this.RaiseSO(); this.SEV(); } #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 Denary => 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 SwallowRead() => this.MemoryRead(this.PC); protected void SwallowPop() => 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.BRK(); break; // BRK (implied) case 0x01: this.IndexedIndirectX(); this.ORA(); break; // ORA (indexed indirect X) case 0x05: this.ZeroPage(); this.ORA(); break; // ORA (zero page) case 0x06: this.ZeroPage(); this.ASL(); break; // ASL (zero page) case 0x08: this.SwallowRead(); this.PHP(); break; // PHP (implied) case 0x09: this.Immediate(); this.ORA(); break; // ORA (immediate) case 0x0a: this.SwallowRead(); this.ASLA(); break; // ASL A (implied) case 0x0d: this.Absolute(); this.ORA(); break; // ORA (absolute) case 0x0e: this.Absolute(); this.ASL(); break; // ASL (absolute) case 0x10: this.Immediate(); this.BPL(); break; // BPL (relative) case 0x11: this.IndirectIndexedY(); this.ORA(); break; // ORA (indirect indexed Y) case 0x15: this.ZeroPageX(); this.ORA(); break; // ORA (zero page, X) case 0x16: this.ZeroPageX(); this.ASL(); break; // ASL (zero page, X) case 0x18: this.SwallowRead(); this.CLC(); break; // CLC (implied) case 0x19: this.AbsoluteY(); this.ORA(); break; // ORA (absolute, Y) case 0x1d: this.AbsoluteX(); this.ORA(); break; // ORA (absolute, X) case 0x20: this.JSR(); break; // JSR (absolute) case 0x21: this.IndexedIndirectX(); this.AND(); break; // AND (indexed indirect X) case 0x24: this.ZeroPage(); this.BIT(); break; // BIT (zero page) case 0x25: this.ZeroPage(); this.AND(); break; // AND (zero page) case 0x26: this.ZeroPage(); this.ROL(); break; // ROL (zero page) case 0x28: this.SwallowRead(); this.PLP(); break; // PLP (implied) case 0x29: this.Immediate(); this.AND(); break; // AND (immediate) case 0x2a: this.SwallowRead(); this.ROLA(); break; // ROL A (implied) case 0x2c: this.Absolute(); this.BIT(); break; // BIT (absolute) case 0x2d: this.Absolute(); this.AND(); break; // AND (absolute) case 0x2e: this.Absolute(); this.ROL(); break; // ROL (absolute) case 0x30: this.Immediate(); this.BMI(); break; // BMI (relative) case 0x31: this.IndirectIndexedY(); this.AND(); break; // AND (indirect indexed Y) case 0x35: this.ZeroPageX(); this.AND(); break; // AND (zero page, X) case 0x36: this.ZeroPageX(); this.ROL(); break; // ROL (zero page, X) case 0x38: this.SwallowRead(); this.SEC(); break; // SEC (implied) case 0x39: this.AbsoluteY(); this.AND(); break; // AND (absolute, Y) case 0x3d: this.AbsoluteX(); this.AND(); break; // AND (absolute, X) case 0x40: this.SwallowRead(); this.RTI(); break; // RTI (implied) case 0x41: this.IndexedIndirectX(); this.EOR(); break; // EOR (indexed indirect X) case 0x44: this.ZeroPage(); NOP(); break; // *NOP (zero page) case 0x45: this.ZeroPage(); this.EOR(); break; // EOR (zero page) case 0x46: this.ZeroPage(); this.LSR(); break; // LSR (zero page) case 0x48: this.SwallowRead(); this.PHA(); break; // PHA (implied) case 0x49: this.Immediate(); this.EOR(); break; // EOR (immediate) case 0x4a: this.SwallowRead(); this.LSRA(); break; // LSR A (implied) case 0x4c: this.AbsoluteAddress(); this.JMP(); break; // JMP (absolute) case 0x4d: this.Absolute(); this.EOR(); break; // EOR (absolute) case 0x4e: this.Absolute(); this.LSR(); break; // LSR (absolute) case 0x50: this.Immediate(); this.BVC(); break; // BVC (relative) case 0x51: this.IndirectIndexedY(); this.EOR(); break; // EOR (indirect indexed Y) case 0x54: this.ZeroPageX(); NOP(); break; // *NOP (zero page, X) case 0x55: this.ZeroPageX(); this.EOR(); break; // EOR (zero page, X) case 0x56: this.ZeroPageX(); this.LSR(); break; // LSR (zero page, X) case 0x58: this.SwallowRead(); this.CLI(); break; // CLI (implied) case 0x59: this.AbsoluteY(); this.EOR(); break; // EOR (absolute, Y) case 0x5d: this.AbsoluteX(); this.EOR(); break; // EOR (absolute, X) case 0x60: this.SwallowRead(); this.RTS(); break; // RTS (implied) case 0x61: this.IndexedIndirectX(); this.ADC(); break; // ADC (indexed indirect X) case 0x65: this.ZeroPage(); this.ADC(); break; // ADC (zero page) case 0x66: this.ZeroPage(); this.ROR(); break; // ROR (zero page) case 0x68: this.SwallowRead(); this.PLA(); break; // PLA (implied) case 0x69: this.Immediate(); this.ADC(); break; // ADC (immediate) case 0x6a: this.SwallowRead(); this.RORA(); break; // ROR A (implied) case 0x6c: this.IndirectAddress(); this.JMP(); break; // JMP (indirect) case 0x6d: this.Absolute(); this.ADC(); break; // ADC (absolute) case 0x6e: this.Absolute(); this.ROR(); break; // ROR (absolute) case 0x70: this.Immediate(); this.BVS(); break; // BVS (relative) case 0x71: this.IndirectIndexedY(); this.ADC(); break; // ADC (indirect indexed Y) case 0x75: this.ZeroPageX(); this.ADC(); break; // ADC (zero page, X) case 0x76: this.ZeroPageX(); this.ROR(); break; // ROR (zero page, X) case 0x78: this.SwallowRead(); this.SEI(); break; // SEI (implied) case 0x79: this.AbsoluteY(); this.ADC(); break; // ADC (absolute, Y) case 0x7d: this.AbsoluteX(); this.ADC(); break; // ADC (absolute, X) case 0x81: this.IndexedIndirectXAddress(); this.STA(); break; // STA (indexed indirect X) case 0x82: this.Immediate(); NOP(); break; // *NOP (immediate) case 0x84: this.ZeroPageAddress(); this.STY(); break; // STY (zero page) case 0x85: this.ZeroPageAddress(); this.STA(); break; // STA (zero page) case 0x86: this.ZeroPageAddress(); this.STX(); break; // STX (zero page) case 0x88: this.SwallowRead(); this.DEY(); break; // DEY (implied) case 0x8a: this.SwallowRead(); this.TXA(); break; // TXA (implied) case 0x8c: this.AbsoluteAddress(); this.STY(); break; // STY (absolute) case 0x8d: this.AbsoluteAddress(); this.STA(); break; // STA (absolute) case 0x8e: this.AbsoluteAddress(); this.STX(); break; // STX (absolute) case 0x90: this.Immediate(); this.BCC(); break; // BCC (relative) case 0x91: this.IndirectIndexedYAddress(); this.Fixup(); this.STA(); break; // STA (indirect indexed Y) case 0x94: this.ZeroPageXAddress(); this.STY(); break; // STY (zero page, X) case 0x95: this.ZeroPageXAddress(); this.STA(); break; // STA (zero page, X) case 0x96: this.ZeroPageYAddress(); this.STX(); break; // STX (zero page, Y) case 0x98: this.SwallowRead(); this.TYA(); break; // TYA (implied) case 0x99: this.AbsoluteYAddress(); this.Fixup(); this.STA(); break; // STA (absolute, Y) case 0x9a: this.SwallowRead(); this.TXS(); break; // TXS (implied) case 0x9d: this.AbsoluteXAddress(); this.Fixup(); this.STA(); break; // STA (absolute, X) case 0xa0: this.Immediate(); this.LDY(); break; // LDY (immediate) case 0xa1: this.IndexedIndirectX(); this.LDA(); break; // LDA (indexed indirect X) case 0xa2: this.Immediate(); this.LDX(); break; // LDX (immediate) case 0xa4: this.ZeroPage(); this.LDY(); break; // LDY (zero page) case 0xa5: this.ZeroPage(); this.LDA(); break; // LDA (zero page) case 0xa6: this.ZeroPage(); this.LDX(); break; // LDX (zero page) case 0xa8: this.SwallowRead(); this.TAY(); break; // TAY (implied) case 0xa9: this.Immediate(); this.LDA(); break; // LDA (immediate) case 0xaa: this.SwallowRead(); this.TAX(); break; // TAX (implied) case 0xac: this.Absolute(); this.LDY(); break; // LDY (absolute) case 0xad: this.Absolute(); this.LDA(); break; // LDA (absolute) case 0xae: this.Absolute(); this.LDX(); break; // LDX (absolute) case 0xb0: this.Immediate(); this.BCS(); break; // BCS (relative) case 0xb1: this.IndirectIndexedY(); this.LDA(); break; // LDA (indirect indexed Y) case 0xb4: this.ZeroPageX(); this.LDY(); break; // LDY (zero page, X) case 0xb5: this.ZeroPageX(); this.LDA(); break; // LDA (zero page, X) case 0xb6: this.ZeroPageY(); this.LDX(); break; // LDX (zero page, Y) case 0xb8: this.SwallowRead(); this.CLV(); break; // CLV (implied) case 0xb9: this.AbsoluteY(); this.LDA(); break; // LDA (absolute, Y) case 0xba: this.SwallowRead(); this.TSX(); break; // TSX (implied) case 0xbc: this.AbsoluteX(); this.LDY(); break; // LDY (absolute, X) case 0xbd: this.AbsoluteX(); this.LDA(); break; // LDA (absolute, X) case 0xbe: this.AbsoluteY(); this.LDX(); break; // LDX (absolute, Y) case 0xc0: this.Immediate(); this.CPY(); break; // CPY (immediate) case 0xc1: this.IndexedIndirectX(); this.CMP(); break; // CMP (indexed indirect X) case 0xc2: this.Immediate(); NOP(); break; // *NOP (immediate) case 0xc4: this.ZeroPage(); this.CPY(); break; // CPY (zero page) case 0xc5: this.ZeroPage(); this.CMP(); break; // CMP (zero page) case 0xc6: this.ZeroPage(); this.DEC(); break; // DEC (zero page) case 0xc8: this.SwallowRead(); this.INY(); break; // INY (implied) case 0xc9: this.Immediate(); this.CMP(); break; // CMP (immediate) case 0xca: this.SwallowRead(); this.DEX(); break; // DEX (implied) case 0xcc: this.Absolute(); this.CPY(); break; // CPY (absolute) case 0xcd: this.Absolute(); this.CMP(); break; // CMP (absolute) case 0xce: this.Absolute(); this.DEC(); break; // DEC (absolute) case 0xd0: this.Immediate(); this.BNE(); break; // BNE (relative) case 0xd1: this.IndirectIndexedY(); this.CMP(); break; // CMP (indirect indexed Y) case 0xd4: this.ZeroPageX(); NOP(); break; // *NOP (zero page, X) case 0xd5: this.ZeroPageX(); this.CMP(); break; // CMP (zero page, X) case 0xd6: this.ZeroPageX(); this.DEC(); break; // DEC (zero page, X) case 0xd8: this.SwallowRead(); this.CLD(); break; // CLD (implied) case 0xd9: this.AbsoluteY(); this.CMP(); break; // CMP (absolute, Y) case 0xdd: this.AbsoluteX(); this.CMP(); break; // CMP (absolute, X) case 0xde: this.AbsoluteXAddress(); this.FixupRead(); this.DEC(); break; // DEC (absolute, X) case 0xe0: this.Immediate(); this.CPX(); break; // CPX (immediate) case 0xe1: this.IndexedIndirectX(); this.SBC(); break; // SBC (indexed indirect X) case 0xe2: this.Immediate(); NOP(); break; // *NOP (immediate) case 0xe4: this.ZeroPage(); this.CPX(); break; // CPX (zero page) case 0xe5: this.ZeroPage(); this.SBC(); break; // SBC (zero page) case 0xe6: this.ZeroPage(); this.INC(); break; // INC (zero page) case 0xe8: this.SwallowRead(); this.INX(); break; // INX (implied) case 0xe9: this.Immediate(); this.SBC(); break; // SBC (immediate) case 0xea: this.SwallowRead(); NOP(); break; // NOP (implied) case 0xec: this.Absolute(); this.CPX(); break; // CPX (absolute) case 0xed: this.Absolute(); this.SBC(); break; // SBC (absolute) case 0xee: this.Absolute(); this.INC(); break; // INC (absolute) case 0xf0: this.Immediate(); this.BEQ(); break; // BEQ (relative) case 0xf1: this.IndirectIndexedY(); this.SBC(); break; // SBC (indirect indexed Y) case 0xf4: this.ZeroPageX(); NOP(); break; // *NOP (zero page, X) case 0xf5: this.ZeroPageX(); this.SBC(); break; // SBC (zero page, X) case 0xf6: this.ZeroPageX(); this.INC(); break; // INC (zero page, X) case 0xf8: this.SwallowRead(); this.SED(); break; // SED (implied) case 0xf9: this.AbsoluteY(); this.SBC(); break; // SBC (absolute, Y) case 0xfd: this.AbsoluteX(); this.SBC(); break; // SBC (absolute, X) case 0xfe: this.AbsoluteXAddress(); this.FixupRead(); this.INC(); break; // INC (absolute, X) default: break; } return cycles != this.Cycles; } public override void PoweredStep() { this.Tick(); if (this.SO.Lowered()) { this.HandleSO(); } if (this.RDY.Raised()) { this.ImmediateInstruction = false; this.OpCode = 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(); } } } protected override byte FetchInstruction() { 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.ReadFromBus(); System.Diagnostics.Debug.Assert(this.Cycles == 1, "BUS read has introduced stray cycles"); this.RaiseSYNC(); return this.Bus.Data; } #endregion #region Bus/Memory access protected sealed override void BusWrite() { this.OnWritingMemory(); this.Tick(); this.WriteToBus(); this.OnWrittenMemory(); } protected sealed override void BusRead() { this.OnReadingMemory(); this.Tick(); this.ReadFromBus(); this.OnReadMemory(); } private void ReadFromBus() { this.RaiseRW(); base.BusRead(); } private void WriteToBus() { this.LowerRW(); base.BusWrite(); } protected abstract void ModifyWrite(byte data); #endregion #region Stack access protected override void Pop() { this.RaiseStack(); this.MemoryRead(); } protected override void Push(byte value) { this.LowerStack(); this.MemoryWrite(value); } private void UpdateStack(byte position) => this.Bus.Address.Assign(position, 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 public byte FixedPage { get; protected set; } public byte UnfixedPage { get; protected set; } public bool Fixed => this.FixedPage != this.UnfixedPage; protected void MaybeFixup() { if (this.Bus.Address.High != this.FixedPage) { this.Fixup(); } } protected abstract void Fixup(); protected void MaybeFixupRead() { this.MaybeFixup(); this.MemoryRead(); } protected void FixupRead() { this.Fixup(); this.MemoryRead(); } #endregion #region Address resolution protected void NoteFixedAddress(int address) => this.NoteFixedAddress((ushort)address); protected void NoteFixedAddress(ushort address) { this.UnfixedPage = this.Bus.Address.High; this.Intermediate.Joined = address; this.FixedPage = this.Intermediate.High; this.Bus.Address.Low = this.Intermediate.Low; } protected void GetAddressPaged() { this.GetShortPaged(); this.Bus.Address.Assign(this.Intermediate); } protected void AbsoluteAddress() => this.FetchShortAddress(); protected void ZeroPageAddress() { this.FetchByte(); this.Bus.Address.Assign(this.Bus.Data); } protected void ZeroPageIndirectAddress() { this.ZeroPageAddress(); this.GetAddressPaged(); } protected abstract void IndirectAddress(); protected void ZeroPageWithIndexAddress(byte index) { this.ZeroPage(); 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.Joined + index); } protected void AbsoluteXAddress() => this.AbsoluteWithIndexAddress(this.X); protected void AbsoluteYAddress() => this.AbsoluteWithIndexAddress(this.Y); protected void IndexedIndirectXAddress() { this.ZeroPageXAddress(); this.GetAddressPaged(); } protected void IndirectIndexedYAddress() { this.ZeroPageIndirectAddress(); this.NoteFixedAddress(this.Bus.Address.Joined + this.Y); } #endregion #region Address and read protected void Immediate() { this.ImmediateInstruction = true; this.FetchByte(); } protected void Absolute() { this.AbsoluteAddress(); this.MemoryRead(); } protected void ZeroPage() { this.ZeroPageAddress(); this.MemoryRead(); } protected void ZeroPageX() { this.ZeroPageXAddress(); this.MemoryRead(); } protected void ZeroPageY() { this.ZeroPageYAddress(); this.MemoryRead(); } protected void IndexedIndirectX() { this.IndexedIndirectXAddress(); this.MemoryRead(); } protected void AbsoluteX() { this.AbsoluteXAddress(); this.MaybeFixupRead(); } protected void AbsoluteY() { this.AbsoluteYAddress(); this.MaybeFixupRead(); } protected void IndirectIndexedY() { this.IndirectIndexedYAddress(); this.MaybeFixupRead(); } #endregion #endregion #region Branching protected void BranchNot(int condition) => this.Branch(condition == 0); protected void Branch(int condition) => this.Branch(condition != 0); protected void Branch(bool condition) { if (condition) { var relative = (sbyte)this.Bus.Data; this.SwallowRead(); this.FixupBranch(relative); this.JMP(); } } protected abstract void FixupBranch(sbyte relative); #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 Miscellaneous protected static void NOP() { // No operation! } protected void JMP() => this.Jump(this.Bus.Address); private void BRK() => this.Interrupt(IRQ_vector, InterruptSource.software); #endregion #region Register to register transfer private void TAX() => this.X = this.Through(this.A); private void TXA() => this.A = this.Through(this.X); private void TAY() => this.Y = this.Through(this.A); private void TYA() => this.A = this.Through(this.Y); private void TXS() => this.S = this.X; private void TSX() => this.X = this.Through(this.S); #endregion #region Load and store protected void LDA() => this.A = this.Through(); protected void STA() => this.MemoryWrite(this.A); protected void LDX() => this.X = this.Through(); private void STX() => this.MemoryWrite(this.X); protected void LDY() => this.Y = this.Through(); private void STY() => this.MemoryWrite(this.Y); #endregion #region Branching private void BCS() => this.Branch(this.Carry); private void BCC() => this.BranchNot(this.Carry); private void BVC() => this.BranchNot(this.Overflow); private void BMI() => this.Branch(this.Negative); private void BPL() => this.BranchNot(this.Negative); private void BVS() => this.Branch(this.Overflow); private void BEQ() => this.Branch(this.Zero); private void BNE() => this.BranchNot(this.Zero); #endregion #region Status flag operations private void SEI() => this.SetFlag(StatusBits.IF); private void CLI() => this.ResetFlag(StatusBits.IF); private void SEV() => this.SetFlag(StatusBits.VF); private void CLV() => this.ResetFlag(StatusBits.VF); private void SEC() => this.SetFlag(StatusBits.CF); private void CLC() => this.ResetFlag(StatusBits.CF); private void SED() => this.SetFlag(StatusBits.DF); protected void CLD() => this.ResetFlag(StatusBits.DF); #endregion #region Instructions with BCD effects #region Addition/subtraction #region Subtraction protected void AdjustOverflowSubtract(byte operand) { var data = this.Bus.Data; var intermediate = this.Intermediate.Low; this.SetFlag(StatusBits.VF, NegativeTest((byte)((operand ^ data) & (operand ^ intermediate)))); } protected virtual void SBC() { var operand = this.A; this.A = this.SUB(operand, CarryTest((byte)~this.P)); this.PostSUB(operand); } private byte SUB(byte operand, int borrow) => this.Denary != 0 ? this.DecimalSUB(operand, borrow) : this.BinarySUB(operand, borrow); protected virtual byte BinarySUB(byte operand, int borrow = 0) { var data = this.Bus.Data; this.Intermediate.Joined = (ushort)(operand - data - borrow); return this.Intermediate.Low; } protected virtual 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)); } protected virtual void PostSUB(byte operand) { this.AdjustOverflowSubtract(operand); this.ResetFlag(StatusBits.CF, this.Intermediate.High); } #endregion #region Addition protected void AdjustOverflowAdd(byte operand) { var data = this.Bus.Data; var intermediate = this.Intermediate.Low; this.SetFlag(StatusBits.VF, NegativeTest((byte)(~(operand ^ data) & (operand ^ intermediate)))); } protected virtual void ADC() => this.ADC(this.Bus.Data); protected void ADC(byte data) => this.A = this.Denary != 0 ? this.DecimalADC(data) : this.BinaryADC(data); protected virtual byte BinaryADC(byte data) { var operand = this.A; this.Intermediate.Joined = (ushort)(operand + data + this.Carry); this.AdjustOverflowAdd(operand); this.SetFlag(StatusBits.CF, CarryTest(this.Intermediate.High)); return this.Intermediate.Low; } protected virtual byte DecimalADC(byte data) { var operand = this.A; var low = (ushort)(LowerNibble(operand) + LowerNibble(data) + this.Carry); this.Intermediate.Joined = (ushort)(HigherNibble(operand) + HigherNibble(data)); this.AdjustZero(LowByte((ushort)(low + this.Intermediate.Joined))); if (low > 0x09) { this.Intermediate.Joined += 0x10; low += 0x06; } this.AdjustNegative(this.Intermediate.Low); this.AdjustOverflowAdd(operand); if (this.Intermediate.Joined > 0x90) { this.Intermediate.Joined += 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 ORA() => this.ORA(this.Bus.Data); protected void ORA(byte data) => this.A = this.Through(this.A | data); protected void AND() => this.AND(this.Bus.Data); protected void AND(byte data) => this.A = this.Through(this.A & data); protected void EOR() => this.EOR(this.Bus.Data); protected void EOR(byte data) => this.A = this.Through(this.A ^ data); protected void BitSet(byte mask) => this.AdjustZero((byte)(this.A & mask)); protected void BIT() { var data = this.Bus.Data; this.SetFlag(StatusBits.VF, OverflowTest(data)); this.BitSet(data); this.AdjustNegative(data); } #endregion #region Comparison operations protected void CMP() => this.CMP(this.A, this.Bus.Data); private void CPX() => this.CMP(this.X, this.Bus.Data); private void CPY() => this.CMP(this.Y, this.Bus.Data); protected void CMP(byte first, byte second) { this.Intermediate.Joined = (ushort)(first - second); this.AdjustNZ(this.Intermediate.Low); this.ResetFlag(StatusBits.CF, this.Intermediate.High); } #endregion #region Increment/decrement private void DEX() => this.X = this.DEC(this.X); private void DEY() => this.Y = this.DEC(this.Y); protected void DEC() => this.ModifyWrite(this.DEC(this.Bus.Data)); protected byte DEC(byte value) => this.Through(value - 1); private void INX() => this.X = this.INC(this.X); private void INY() => this.Y = this.INC(this.Y); protected void INC() => this.ModifyWrite(this.INC(this.Bus.Data)); protected byte INC(byte value) => this.Through(value + 1); #endregion #region Stack operations private void JSR() { this.FetchByte(); var low = this.Bus.Data; this.SwallowPop(); this.PushShort(this.PC); this.FetchByte(); var high = this.Bus.Data; this.PC.Assign(low, high); } private void PHA() => this.Push(this.A); private void PLA() { this.SwallowPop(); this.Pop(); this.LDA(); } private void PHP() => this.Push(SetBit(this.P, StatusBits.BF)); private void PLP() { this.SwallowPop(); this.Pop(); this.P = ClearBit(SetBit(this.Bus.Data, StatusBits.RF), StatusBits.BF); } private void RTI() { this.PLP(); base.Return(); } protected void RTS() => this.Return(); protected override void Return() { this.SwallowPop(); base.Return(); this.SwallowFetch(); } #endregion #region Shift/rotate operations #region Shift private void ASLA() => this.A = this.ASL(this.A); protected void ASL() => this.ModifyWrite(this.ASL(this.Bus.Data)); protected byte ASL(byte value) { this.SetFlag(StatusBits.CF, NegativeTest(value)); return this.Through(value << 1); } protected void LSRA() => this.A = this.LSR(this.A); protected void LSR() => this.ModifyWrite(this.LSR(this.Bus.Data)); protected byte LSR(byte value) { this.SetFlag(StatusBits.CF, CarryTest(value)); return this.Through(value >> 1); } #endregion #region Rotate private void ROLA() => this.A = this.ROL(this.A); protected void ROL() => this.ModifyWrite(this.ROL(this.Bus.Data)); protected byte ROL(byte value) { var carryIn = this.Carry; return this.Through(this.ASL(value) | carryIn); } private void RORA() => this.A = this.ROR(this.A); protected void ROR() => this.ModifyWrite(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 } }