// // Copyright (c) Adrian Conlon. All rights reserved. // //using EightBit; namespace M6502 { using EightBit; public abstract class Core(Bus bus) : LittleEndianProcessor(bus) { #region Pin controls #region NMI pin public ref PinLevel NMI => ref 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() => RaisingNMI?.Invoke(this, EventArgs.Empty); protected virtual void OnRaisedNMI() => RaisedNMI?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweringNMI() => LoweringNMI?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweredNMI() => 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 (NMI.Lowered()) { OnRaisingNMI(); NMI.Raise(); OnRaisedNMI(); } } public virtual void LowerNMI() { if (NMI.Raised()) { OnLoweringNMI(); NMI.Lower(); OnLoweredNMI(); } } #endregion #region SO pin public ref PinLevel SO => ref 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() => RaisingSO?.Invoke(this, EventArgs.Empty); protected virtual void OnRaisedSO() => RaisedSO?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweringSO() => LoweringSO?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweredSO() => 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 (SO.Lowered()) { OnRaisingSO(); SO.Raise(); OnRaisedSO(); } } public virtual void LowerSO() { if (SO.Raised()) { OnLoweringSO(); SO.Lower(); OnLoweredSO(); } } #endregion #region SYNC pin public ref PinLevel SYNC => ref 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() => RaisingSYNC?.Invoke(this, EventArgs.Empty); protected virtual void OnRaisedSYNC() => RaisedSYNC?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweringSYNC() => LoweringSYNC?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweredSYNC() => 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() { OnRaisingSYNC(); SYNC.Raise(); OnRaisedSYNC(); } protected virtual void LowerSYNC() { OnLoweringSYNC(); SYNC.Lower(); OnLoweredSYNC(); } #endregion #region RDY pin public ref PinLevel RDY => ref 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() => RaisingRDY?.Invoke(this, EventArgs.Empty); protected virtual void OnRaisedRDY() => RaisedRDY?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweringRDY() => LoweringRDY?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweredRDY() => 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 (RDY.Lowered()) { OnRaisingRDY(); RDY.Raise(); OnRaisedRDY(); } } public virtual void LowerRDY() { if (RDY.Raised()) { OnLoweringRDY(); RDY.Lower(); OnLoweredRDY(); } } #endregion #region RW pin public ref PinLevel RW => ref 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() => RaisingRW?.Invoke(this, EventArgs.Empty); protected virtual void OnRaisedRW() => RaisedRW?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweringRW() => LoweringRW?.Invoke(this, EventArgs.Empty); protected virtual void OnLoweredRW() => 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 (RW.Lowered()) { OnRaisingRW(); RW.Raise(); OnRaisedRW(); } } public virtual void LowerRW() { if (RW.Raised()) { OnLoweringRW(); RW.Lower(); OnLoweredRW(); } } #endregion protected override void OnRaisedPOWER() { X = (byte)Bits.Bit7; Y = 0; A = 0; P = (byte)StatusBits.RF; S = (byte)Mask.Eight; LowerSYNC(); 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 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) { DummyPush(); DummyPush(); DummyPush(); } else { PushWord(PC); Push((byte)(P | (source == InterruptSource.hardware ? 0 : (byte)StatusBits.BF))); } SetFlag(StatusBits.IF); // Disable IRQ Jump(GetWordPaged(0xff, vector)); } #region Interrupt etc. handlers protected override sealed void HandleRESET() { RaiseRESET(); Interrupt(RSTvector, InterruptSource.hardware, InterruptType.reset); } protected override sealed void HandleINT() { RaiseINT(); Interrupt(IRQvector); } private void HandleNMI() { RaiseNMI(); Interrupt(NMIvector); } private void HandleSO() { RaiseSO(); 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 => P & (byte)StatusBits.IF; protected int DecimalMasked => P & (byte)StatusBits.DF; protected int Negative => NegativeTest(P); protected int Zero => ZeroTest(P); protected int Overflow => OverflowTest(P); protected int Carry => CarryTest(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) { P = SetBit(P, flag); } protected void SetFlag(StatusBits which, int condition) { P = SetBit(P, which, condition); } protected void SetFlag(StatusBits which, bool condition) { P = SetBit(P, which, condition); } protected void ResetFlag(StatusBits which) { P = ClearBit(P, which); } protected void ResetFlag(StatusBits which, int condition) { P = ClearBit(P, which, condition); } #endregion #endregion #region Cycle wastage protected void SwallowRead() => MemoryRead(PC); protected void SwallowPop() => MemoryRead(S, 1); protected void SwallowFetch() => FetchByte(); #endregion #region Core instruction dispatching public override void Execute() { MaybeExecute(); } protected virtual bool MaybeExecute() { var cycles = Cycles; switch (OpCode) { case 0x00: SwallowFetch(); Interrupt(IRQvector, InterruptSource.software); break; // BRK (implied) case 0x01: IndexedIndirectXRead(); OrR(); break; // ORA (indexed indirect X) case 0x05: ZeroPageRead(); OrR(); break; // ORA (zero page) case 0x06: ZeroPageRead(); ModifyWrite(ASL()); break; // ASL (zero page) case 0x08: SwallowRead(); PHP(); break; // PHP (implied) case 0x09: ImmediateRead(); OrR(); break; // ORA (immediate) case 0x0a: SwallowRead(); A = ASL(A); break; // ASL A (implied) case 0x0d: AbsoluteRead(); OrR(); break; // ORA (absolute) case 0x0e: AbsoluteRead(); ModifyWrite(ASL()); break; // ASL (absolute) case 0x10: BranchNot(Negative); break; // BPL (relative) case 0x11: IndirectIndexedYRead(); OrR(); break; // ORA (indirect indexed Y) case 0x15: ZeroPageXRead(); OrR(); break; // ORA (zero page, X) case 0x16: ZeroPageXRead(); ModifyWrite(ASL()); break; // ASL (zero page, X) case 0x18: SwallowRead(); ResetFlag(StatusBits.CF); break; // CLC (implied) case 0x19: AbsoluteYRead(); OrR(); break; // ORA (absolute, Y) case 0x1d: AbsoluteXRead(); OrR(); break; // ORA (absolute, X) case 0x1e: AbsoluteXAddress(); FixupRead(); ModifyWrite(ASL()); break; // ASL (absolute, X) case 0x20: JSR(); break; // JSR (absolute) case 0x21: IndexedIndirectXRead(); AndR(); break; // AND (indexed indirect X) case 0x24: ZeroPageRead(); BIT(); break; // BIT (zero page) case 0x25: ZeroPageRead(); AndR(); break; // AND (zero page) case 0x26: ZeroPageRead(); ModifyWrite(ROL()); break; // ROL (zero page) case 0x28: SwallowRead(); PLP(); break; // PLP (implied) case 0x29: ImmediateRead(); AndR(); break; // AND (immediate) case 0x2a: SwallowRead(); A = ROL(A); break; // ROL A (implied) case 0x2c: AbsoluteRead(); BIT(); break; // BIT (absolute) case 0x2d: AbsoluteRead(); AndR(); break; // AND (absolute) case 0x2e: AbsoluteRead(); ModifyWrite(ROL()); break; // ROL (absolute) case 0x30: Branch(Negative); break; // BMI (relative) case 0x31: IndirectIndexedYRead(); AndR(); break; // AND (indirect indexed Y) case 0x35: ZeroPageXRead(); AndR(); break; // AND (zero page, X) case 0x36: ZeroPageXRead(); ModifyWrite(ROL()); break; // ROL (zero page, X) case 0x38: SwallowRead(); SetFlag(StatusBits.CF); break; // SEC (implied) case 0x39: AbsoluteYRead(); AndR(); break; // AND (absolute, Y) case 0x3d: AbsoluteXRead(); AndR(); break; // AND (absolute, X) case 0x3e: AbsoluteXAddress(); FixupRead(); ModifyWrite(ROL()); break; // ROL (absolute, X) case 0x40: SwallowRead(); RTI(); break; // RTI (implied) case 0x41: IndexedIndirectXRead(); EorR(); break; // EOR (indexed indirect X) case 0x44: ZeroPageRead(); break; // *NOP (zero page) case 0x45: ZeroPageRead(); EorR(); break; // EOR (zero page) case 0x46: ZeroPageRead(); ModifyWrite(LSR()); break; // LSR (zero page) case 0x48: SwallowRead(); Push(A); break; // PHA (implied) case 0x49: ImmediateRead(); EorR(); break; // EOR (immediate) case 0x4a: SwallowRead(); A = LSR(A); break; // LSR A (implied) case 0x4c: AbsoluteAddress(); Jump(Bus.Address); break; // JMP (absolute) case 0x4d: AbsoluteRead(); EorR(); break; // EOR (absolute) case 0x4e: AbsoluteRead(); ModifyWrite(LSR()); break; // LSR (absolute) case 0x50: BranchNot(Overflow); break; // BVC (relative) case 0x51: IndirectIndexedYRead(); EorR(); break; // EOR (indirect indexed Y) case 0x54: ZeroPageXRead(); break; // *NOP (zero page, X) case 0x55: ZeroPageXRead(); EorR(); break; // EOR (zero page, X) case 0x56: ZeroPageXRead(); ModifyWrite(LSR()); break; // LSR (zero page, X) case 0x58: SwallowRead(); ResetFlag(StatusBits.IF); break; // CLI (implied) case 0x59: AbsoluteYRead(); EorR(); break; // EOR (absolute, Y) case 0x5d: AbsoluteXRead(); EorR(); break; // EOR (absolute, X) case 0x5e: AbsoluteXAddress(); FixupRead(); ModifyWrite(LSR()); break; // LSR (absolute, X) case 0x60: SwallowRead(); Return(); break; // RTS (implied) case 0x61: IndexedIndirectXRead(); ADC(); break; // ADC (indexed indirect X) case 0x65: ZeroPageRead(); ADC(); break; // ADC (zero page) case 0x66: ZeroPageRead(); ModifyWrite(ROR()); break; // ROR (zero page) case 0x68: SwallowRead(); SwallowPop(); A = Through(Pop()); break; // PLA (implied) case 0x69: ImmediateRead(); ADC(); break; // ADC (immediate) case 0x6a: SwallowRead(); A = ROR(A); break; // ROR A (implied) case 0x6c: IndirectAddress(); Jump(Bus.Address); break; // JMP (indirect) case 0x6d: AbsoluteRead(); ADC(); break; // ADC (absolute) case 0x6e: AbsoluteRead(); ModifyWrite(ROR()); break; // ROR (absolute) case 0x70: Branch(Overflow); break; // BVS (relative) case 0x71: IndirectIndexedYRead(); ADC(); break; // ADC (indirect indexed Y) case 0x75: ZeroPageXRead(); ADC(); break; // ADC (zero page, X) case 0x76: ZeroPageXRead(); ModifyWrite(ROR()); break; // ROR (zero page, X) case 0x78: SwallowRead(); SetFlag(StatusBits.IF); break; // SEI (implied) case 0x79: AbsoluteYRead(); ADC(); break; // ADC (absolute, Y) case 0x7d: AbsoluteXRead(); ADC(); break; // ADC (absolute, X) case 0x7e: AbsoluteXAddress(); FixupRead(); ModifyWrite(ROR()); break; // ROR (absolute, X) case 0x81: IndexedIndirectXAddress(); MemoryWrite(A); break; // STA (indexed indirect X) case 0x82: ImmediateRead(); break; // *NOP (immediate) case 0x84: ZeroPageAddress(); MemoryWrite(Y); break; // STY (zero page) case 0x85: ZeroPageAddress(); MemoryWrite(A); break; // STA (zero page) case 0x86: ZeroPageAddress(); MemoryWrite(X); break; // STX (zero page) case 0x88: SwallowRead(); Y = DEC(Y); break; // DEY (implied) case 0x8a: SwallowRead(); A = Through(X); break; // TXA (implied) case 0x8c: AbsoluteAddress(); MemoryWrite(Y); break; // STY (absolute) case 0x8d: AbsoluteAddress(); MemoryWrite(A); break; // STA (absolute) case 0x8e: AbsoluteAddress(); MemoryWrite(X); break; // STX (absolute) case 0x90: BranchNot(Carry); break; // BCC (relative) case 0x91: IndirectIndexedYAddress(); Fixup(); MemoryWrite(A); break; // STA (indirect indexed Y) case 0x94: ZeroPageXAddress(); MemoryWrite(Y); break; // STY (zero page, X) case 0x95: ZeroPageXAddress(); MemoryWrite(A); break; // STA (zero page, X) case 0x96: ZeroPageYAddress(); MemoryWrite(X); break; // STX (zero page, Y) case 0x98: SwallowRead(); A = Through(Y); break; // TYA (implied) case 0x99: AbsoluteYAddress(); Fixup(); MemoryWrite(A); break; // STA (absolute, Y) case 0x9a: SwallowRead(); S = X; break; // TXS (implied) case 0x9d: AbsoluteXAddress(); Fixup(); MemoryWrite(A); break; // STA (absolute, X) case 0xa0: ImmediateRead(); Y = Through(); break; // LDY (immediate) case 0xa1: IndexedIndirectXRead(); A = Through(); break; // LDA (indexed indirect X) case 0xa2: ImmediateRead(); X = Through(); break; // LDX (immediate) case 0xa4: ZeroPageRead(); Y = Through(); break; // LDY (zero page) case 0xa5: ZeroPageRead(); A = Through(); break; // LDA (zero page) case 0xa6: ZeroPageRead(); X = Through(); break; // LDX (zero page) case 0xa8: SwallowRead(); Y = Through(A); break; // TAY (implied) case 0xa9: ImmediateRead(); A = Through(); break; // LDA (immediate) case 0xaa: SwallowRead(); X = Through(A); break; // TAX (implied) case 0xac: AbsoluteRead(); Y = Through(); break; // LDY (absolute) case 0xad: AbsoluteRead(); A = Through(); break; // LDA (absolute) case 0xae: AbsoluteRead(); X = Through(); break; // LDX (absolute) case 0xb0: Branch(Carry); break; // BCS (relative) case 0xb1: IndirectIndexedYRead(); A = Through(); break; // LDA (indirect indexed Y) case 0xb4: ZeroPageXRead(); Y = Through(); break; // LDY (zero page, X) case 0xb5: ZeroPageXRead(); A = Through(); break; // LDA (zero page, X) case 0xb6: ZeroPageYRead(); X = Through(); break; // LDX (zero page, Y) case 0xb8: SwallowRead(); ResetFlag(StatusBits.VF); break; // CLV (implied) case 0xb9: AbsoluteYRead(); A = Through(); break; // LDA (absolute, Y) case 0xba: SwallowRead(); X = Through(S); break; // TSX (implied) case 0xbc: AbsoluteXRead(); Y = Through(); break; // LDY (absolute, X) case 0xbd: AbsoluteXRead(); A = Through(); break; // LDA (absolute, X) case 0xbe: AbsoluteYRead(); X = Through(); break; // LDX (absolute, Y) case 0xc0: ImmediateRead(); CMP(Y); break; // CPY (immediate) case 0xc1: IndexedIndirectXRead(); CMP(A); break; // CMP (indexed indirect X) case 0xc2: ImmediateRead(); break; // *NOP (immediate) case 0xc4: ZeroPageRead(); CMP(Y); break; // CPY (zero page) case 0xc5: ZeroPageRead(); CMP(A); break; // CMP (zero page) case 0xc6: ZeroPageRead(); ModifyWrite(DEC()); break; // DEC (zero page) case 0xc8: SwallowRead(); Y = INC(Y); break; // INY (implied) case 0xc9: ImmediateRead(); CMP(A); break; // CMP (immediate) case 0xca: SwallowRead(); X = DEC(X); break; // DEX (implied) case 0xcc: AbsoluteRead(); CMP(Y); break; // CPY (absolute) case 0xcd: AbsoluteRead(); CMP(A); break; // CMP (absolute) case 0xce: AbsoluteRead(); ModifyWrite(DEC()); break; // DEC (absolute) case 0xd0: BranchNot(Zero); break; // BNE (relative) case 0xd1: IndirectIndexedYRead(); CMP(A); break; // CMP (indirect indexed Y) case 0xd4: ZeroPageXRead(); break; // *NOP (zero page, X) case 0xd5: ZeroPageXRead(); CMP(A); break; // CMP (zero page, X) case 0xd6: ZeroPageXRead(); ModifyWrite(DEC()); break; // DEC (zero page, X) case 0xd8: SwallowRead(); ResetFlag(StatusBits.DF); break; // CLD (implied) case 0xd9: AbsoluteYRead(); CMP(A); break; // CMP (absolute, Y) case 0xdd: AbsoluteXRead(); CMP(A); break; // CMP (absolute, X) case 0xde: AbsoluteXAddress(); FixupRead(); ModifyWrite(DEC()); break; // DEC (absolute, X) case 0xe0: ImmediateRead(); CMP(X); break; // CPX (immediate) case 0xe1: IndexedIndirectXRead(); SBC(); break; // SBC (indexed indirect X) case 0xe2: ImmediateRead(); break; // *NOP (immediate) case 0xe4: ZeroPageRead(); CMP(X); break; // CPX (zero page) case 0xe5: ZeroPageRead(); SBC(); break; // SBC (zero page) case 0xe6: ZeroPageRead(); ModifyWrite(INC()); break; // INC (zero page) case 0xe8: SwallowRead(); X = INC(X); break; // INX (implied) case 0xe9: ImmediateRead(); SBC(); break; // SBC (immediate) case 0xea: SwallowRead(); break; // NOP (implied) case 0xec: AbsoluteRead(); CMP(X); break; // CPX (absolute) case 0xed: AbsoluteRead(); SBC(); break; // SBC (absolute) case 0xee: AbsoluteRead(); ModifyWrite(INC()); break; // INC (absolute) case 0xf0: Branch(Zero); break; // BEQ (relative) case 0xf1: IndirectIndexedYRead(); SBC(); break; // SBC (indirect indexed Y) case 0xf4: ZeroPageXRead(); break; // *NOP (zero page, X) case 0xf5: ZeroPageXRead(); SBC(); break; // SBC (zero page, X) case 0xf6: ZeroPageXRead(); ModifyWrite(INC()); break; // INC (zero page, X) case 0xf8: SwallowRead(); SetFlag(StatusBits.DF); break; // SED (implied) case 0xf9: AbsoluteYRead(); SBC(); break; // SBC (absolute, Y) case 0xfd: AbsoluteXRead(); SBC(); break; // SBC (absolute, X) case 0xfe: AbsoluteXAddress(); FixupRead(); ModifyWrite(INC()); break; // INC (absolute, X) } return cycles != Cycles; } public override void PoweredStep() { Tick(); if (SO.Lowered()) { HandleSO(); } if (RDY.Raised()) { FetchInstruction(); if (RESET.Lowered()) { HandleRESET(); } else if (NMI.Lowered()) { HandleNMI(); } else if (INT.Lowered() && InterruptMasked == 0) { HandleINT(); } else { Execute(); } } } private void FetchInstruction() { // Instruction fetch beginning LowerSYNC(); System.Diagnostics.Debug.Assert(Cycles == 1, "An extra cycle has occurred"); // Can't use fetchByte, since that would add an extra tick. ImmediateAddress(); OpCode = ReadFromBus(); System.Diagnostics.Debug.Assert(Cycles == 1, "BUS read has introduced stray cycles"); // Instruction fetch has now completed RaiseSYNC(); } #endregion #region Bus/Memory access protected override sealed void BusWrite() { Tick(); WriteToBus(); } protected override sealed byte BusRead() { Tick(); return ReadFromBus(); } private byte ReadFromBus() { RaiseRW(); return base.BusRead(); } private void WriteToBus() { LowerRW(); base.BusWrite(); } protected abstract void ModifyWrite(byte data); #endregion #region Stack access protected override byte Pop() { RaiseStack(); return MemoryRead(); } protected override void Push(byte value) { LowerStack(); MemoryWrite(value); } private void UpdateStack(byte position) { Bus.Address.Assign(position, 1); } private void LowerStack() => UpdateStack(S--); private void RaiseStack() => UpdateStack(++S); private void DummyPush() { LowerStack(); Tick(); // In place of the memory write } #endregion #region Addressing modes #region Address page fixup private byte fixedPage; private byte unfixedPage; public byte FixedPage { get => fixedPage; protected set => fixedPage = value; } public byte UnfixedPage { get => unfixedPage; protected set => unfixedPage = value; } public bool Fixed => FixedPage != UnfixedPage; protected void MaybeFixup() { if (Bus.Address.High != FixedPage) { Fixup(); } } protected abstract void Fixup(); protected void MaybeFixupRead() { MaybeFixup(); MemoryRead(); } protected void FixupRead() { Fixup(); MemoryRead(); } #endregion #region Address resolution protected void NoteFixedAddress(int address) { NoteFixedAddress((ushort)address); } protected void NoteFixedAddress(ushort address) { UnfixedPage = Bus.Address.High; Intermediate.Word = address; FixedPage = Intermediate.High; Bus.Address.Low = Intermediate.Low; } protected void GetAddressPaged() { GetWordPaged(); Bus.Address.Assign(Intermediate); } protected void ImmediateAddress() { Bus.Address.Assign(PC); ++PC.Word; } protected void AbsoluteAddress() => FetchWordAddress(); protected void ZeroPageAddress() { Bus.Address.Assign(FetchByte(), 0); } protected void ZeroPageIndirectAddress() { ZeroPageAddress(); GetAddressPaged(); } protected abstract void IndirectAddress(); protected void ZeroPageWithIndexAddress(byte index) { ZeroPageRead(); Bus.Address.Low += index; } protected void ZeroPageXAddress() => ZeroPageWithIndexAddress(X); protected void ZeroPageYAddress() => ZeroPageWithIndexAddress(Y); private void AbsoluteWithIndexAddress(byte index) { AbsoluteAddress(); NoteFixedAddress(Bus.Address.Word + index); } protected void AbsoluteXAddress() => AbsoluteWithIndexAddress(X); protected void AbsoluteYAddress() => AbsoluteWithIndexAddress(Y); protected void IndexedIndirectXAddress() { ZeroPageXAddress(); GetAddressPaged(); } protected void IndirectIndexedYAddress() { ZeroPageIndirectAddress(); NoteFixedAddress(Bus.Address.Word + Y); } #endregion #region Address and read protected void ImmediateRead() { ImmediateAddress(); MemoryRead(); } protected void AbsoluteRead() { AbsoluteAddress(); MemoryRead(); } protected void ZeroPageRead() { ZeroPageAddress(); MemoryRead(); } protected void ZeroPageXRead() { ZeroPageXAddress(); MemoryRead(); } protected void ZeroPageYRead() { ZeroPageYAddress(); MemoryRead(); } protected void IndexedIndirectXRead() { IndexedIndirectXAddress(); MemoryRead(); } protected void AbsoluteXRead() { AbsoluteXAddress(); MaybeFixupRead(); } protected void AbsoluteYRead() { AbsoluteYAddress(); MaybeFixupRead(); } protected void IndirectIndexedYRead() { IndirectIndexedYAddress(); MaybeFixupRead(); } #endregion #endregion #region Branching protected void BranchNot(int condition) => Branch(condition == 0); protected void Branch(int condition) => Branch(condition != 0); protected void Branch(bool condition) { ImmediateRead(); if (condition) { var relative = (sbyte)Bus.Data; SwallowRead(); FixupBranch(relative); Jump(Bus.Address); } } protected abstract void FixupBranch(sbyte relative); #endregion #region Data flag adjustment protected void AdjustZero(byte datum) => ResetFlag(StatusBits.ZF, datum); protected void AdjustNegative(byte datum) => SetFlag(StatusBits.NF, NegativeTest(datum)); protected void AdjustNZ(byte datum) { AdjustZero(datum); AdjustNegative(datum); } protected byte Through() => Through(Bus.Data); protected byte Through(int data) => Through((byte)data); protected byte Through(byte data) { 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 = Intermediate.Low; SetFlag(StatusBits.VF, NegativeTest((byte)((operand ^ data) & (operand ^ intermediate)))); } protected void SBC() { var operand = A; A = SUB(operand, CarryTest((byte)~P)); AdjustOverflowSubtract(operand); AdjustNZ(Intermediate.Low); ResetFlag(StatusBits.CF, Intermediate.High); } private byte SUB(byte operand, int borrow) => DecimalMasked != 0 ? DecimalSUB(operand, borrow) : BinarySUB(operand, borrow); protected byte BinarySUB(byte operand, int borrow = 0) { var data = Bus.Data; Intermediate.Word = (ushort)(operand - data - borrow); return Intermediate.Low; } private byte DecimalSUB(byte operand, int borrow) { _ = BinarySUB(operand, borrow); var data = 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 = Intermediate.Low; SetFlag(StatusBits.VF, NegativeTest((byte)(~(operand ^ data) & (operand ^ intermediate)))); } protected void ADC() { A = DecimalMasked != 0 ? DecimalADC() : BinaryADC(); } private byte BinaryADC() { var operand = A; var data = Bus.Data; Intermediate.Word = (ushort)(operand + data + Carry); AdjustOverflowAdd(operand); SetFlag(StatusBits.CF, CarryTest(Intermediate.High)); AdjustNZ(Intermediate.Low); return Intermediate.Low; } private byte DecimalADC() { var operand = A; var data = Bus.Data; var low = (ushort)(LowerNibble(operand) + LowerNibble(data) + Carry); Intermediate.Word = (ushort)(HigherNibble(operand) + HigherNibble(data)); AdjustZero(LowByte((ushort)(low + Intermediate.Word))); if (low > 0x09) { Intermediate.Word += 0x10; low += 0x06; } AdjustNegative(Intermediate.Low); AdjustOverflowAdd(operand); if (Intermediate.Word > 0x90) Intermediate.Word += 0x60; SetFlag(StatusBits.CF, Intermediate.High); return (byte)(LowerNibble(LowByte(low)) | HigherNibble(Intermediate.Low)); } #endregion #endregion #endregion #region Bitwise operations protected void OrR() => A = Through(A | Bus.Data); protected void AndR() => A = Through(A & Bus.Data); protected void EorR() => A = Through(A ^ Bus.Data); protected void BIT() { var data = Bus.Data; SetFlag(StatusBits.VF, OverflowTest(data)); AdjustZero((byte)(A & data)); AdjustNegative(data); } #endregion protected void CMP(byte first) { var second = Bus.Data; Intermediate.Word = (ushort)(first - second); AdjustNZ(Intermediate.Low); ResetFlag(StatusBits.CF, Intermediate.High); } #region Increment/decrement protected byte DEC() => DEC(Bus.Data); protected byte DEC(byte value) => Through(value - 1); protected byte INC() => INC(Bus.Data); protected byte INC(byte value) => Through(value + 1); #endregion #region Stack operations private void JSR() { Intermediate.Low = FetchByte(); SwallowPop(); PushWord(PC); PC.High = FetchByte(); PC.Low = Intermediate.Low; } private void PHP() => Push(SetBit(P, StatusBits.BF)); private void PLP() { SwallowPop(); P = ClearBit(SetBit(Pop(), StatusBits.RF), StatusBits.BF); } private void RTI() { PLP(); base.Return(); } protected override void Return() { SwallowPop(); base.Return(); SwallowFetch(); } #endregion #region Shift/rotate operations #region Shift protected byte ASL() => ASL(Bus.Data); protected byte ASL(byte value) { SetFlag(StatusBits.CF, NegativeTest(value)); return Through(value << 1); } protected byte LSR() => LSR(Bus.Data); protected byte LSR(byte value) { SetFlag(StatusBits.CF, CarryTest(value)); return Through(value >> 1); } #endregion #region Rotate protected byte ROL() => ROL(Bus.Data); protected byte ROL(byte value) { var carryIn = Carry; return Through(ASL(value) | carryIn); } protected byte ROR() => ROR(Bus.Data); protected byte ROR(byte value) { var carryIn = Carry; return Through(LSR(value) | carryIn << 7); } #endregion #endregion #endregion } }