// // Copyright (c) Adrian Conlon. All rights reserved. // namespace EightBit { using System; public class M6502 : LittleEndianProcessor { private const byte IRQvector = 0xfe; // IRQ vector private const byte RSTvector = 0xfc; // RST vector private const byte NMIvector = 0xfa; // NMI vector private byte x = 0; private byte y = 0; private byte a = 0; private byte s = 0; private byte p = 0; private ushort intermediate; private bool handlingRESET = false; private bool handlingNMI = false; private bool handlingINT = false; private PinLevel nmiLine = PinLevel.Low; private PinLevel soLine = PinLevel.Low; private PinLevel syncLine = PinLevel.Low; private PinLevel rdyLine = PinLevel.Low; public M6502(Bus bus) : base(bus) { } public event EventHandler ExecutingInstruction; public event EventHandler ExecutedInstruction; public event EventHandler RaisingNMI; public event EventHandler RaisedNMI; public event EventHandler LoweringNMI; public event EventHandler LoweredNMI; public event EventHandler RaisingSO; public event EventHandler RaisedSO; public event EventHandler LoweringSO; public event EventHandler LoweredSO; public event EventHandler RaisingSYNC; public event EventHandler RaisedSYNC; public event EventHandler LoweringSYNC; public event EventHandler LoweredSYNC; public event EventHandler RaisingRDY; public event EventHandler RaisedRDY; public event EventHandler LoweringRDY; public event EventHandler LoweredRDY; public byte X { get => this.x; set => this.x = value; } public byte Y { get => this.y; set => this.y = value; } public byte A { get => this.a; set => this.a = value; } public byte S { get => this.s; set => this.s = value; } public byte P { get => this.p; set => this.p = value; } private int InterruptMasked => this.P & (byte)StatusBits.IF; private int Decimal => this.P & (byte)StatusBits.DF; private int Negative => this.P & (byte)StatusBits.NF; private int Zero => this.P & (byte)StatusBits.ZF; private int Overflow => this.P & (byte)StatusBits.VF; private int Carry => this.P & (byte)StatusBits.CF; public ref PinLevel NMI() => ref this.nmiLine; public ref PinLevel SO() => ref this.soLine; public ref PinLevel SYNC() => ref this.syncLine; public ref PinLevel RDY() => ref this.rdyLine; public override void RaisePOWER() { base.RaisePOWER(); this.X = (byte)Bits.Bit7; this.Y = 0; this.A = 0; this.P = (byte)StatusBits.RF; this.S = (byte)Mask.Mask8; this.LowerSYNC(); } public virtual void RaiseNMI() { this.OnRaisingNMI(); this.NMI().Raise(); this.OnRaisedNMI(); } public virtual void LowerNMI() { this.OnLoweringNMI(); this.NMI().Lower(); this.OnLoweredNMI(); } public virtual void RaiseSO() { this.OnRaisingSO(); this.SO().Raise(); this.OnRaisedSO(); } public virtual void LowerSO() { this.OnLoweringSO(); this.SO().Lower(); this.OnLoweredSO(); } public virtual void RaiseRDY() { this.OnRaisingRDY(); this.RDY().Raise(); this.OnRaisedRDY(); } public virtual void LowerRDY() { this.OnLoweringRDY(); this.RDY().Lower(); this.OnLoweredRDY(); } public override int Execute() { this.RaiseSYNC(); // Instruction fetch has now completed switch (this.OpCode) { case 0x00: this.FetchByte(); this.Interrupt(); break; // BRK (implied) case 0x01: this.A = this.OrR(this.A, this.AM_IndexedIndirectX()); break; // ORA (indexed indirect X) case 0x02: break; case 0x03: this.SLO(this.AM_IndexedIndirectX()); break; // *SLO (indexed indirect X) case 0x04: this.AM_ZeroPage(); break; // *NOP (zero page) case 0x05: this.A = this.OrR(this.A, this.AM_ZeroPage()); break; // ORA (zero page) case 0x06: this.BusReadModifyWrite(this.ASL(this.AM_ZeroPage())); break; // ASL (zero page) case 0x07: this.SLO(this.AM_ZeroPage()); break; // *SLO (zero page) case 0x08: this.BusRead(); this.PHP(); break; // PHP (implied) case 0x09: this.A = this.OrR(this.A, this.AM_Immediate()); break; // ORA (immediate) case 0x0a: this.BusRead(); this.A = this.ASL(this.A); break; // ASL A (implied) case 0x0b: this.ANC(this.AM_Immediate()); break; // *ANC (immediate) case 0x0c: this.AM_Absolute(); break; // *NOP (absolute) case 0x0d: this.A = this.OrR(this.A, this.AM_Absolute()); break; // ORA (absolute) case 0x0e: this.BusReadModifyWrite(this.ASL(this.AM_Absolute())); break; // ASL (absolute) case 0x0f: this.SLO(this.AM_Absolute()); break; // *SLO (absolute) case 0x10: this.Branch(this.Negative == 0); break; // BPL (relative) case 0x11: this.A = this.OrR(this.A, this.AM_IndirectIndexedY()); break; // ORA (indirect indexed Y) case 0x12: break; case 0x13: this.SLO(this.AM_IndirectIndexedY()); break; // *SLO (indirect indexed Y) case 0x14: this.AM_ZeroPageX(); break; // *NOP (zero page, X) case 0x15: this.A = this.OrR(this.A, this.AM_ZeroPageX()); break; // ORA (zero page, X) case 0x16: this.BusReadModifyWrite(this.ASL(this.AM_ZeroPageX())); break; // ASL (zero page, X) case 0x17: this.SLO(this.AM_ZeroPageX()); break; // *SLO (zero page, X) case 0x18: this.BusRead(); this.P = ClearFlag(this.P, StatusBits.CF); break; // CLC (implied) case 0x19: this.A = this.OrR(this.A, this.AM_AbsoluteY()); break; // ORA (absolute, Y) case 0x1a: this.BusRead(); break; // *NOP (implied) case 0x1b: this.SLO(this.AM_AbsoluteY()); break; // *SLO (absolute, Y) case 0x1c: this.AM_AbsoluteX(); break; // *NOP (absolute, X) case 0x1d: this.A = this.OrR(this.A, this.AM_AbsoluteX()); break; // ORA (absolute, X) case 0x1e: this.BusReadModifyWrite(this.ASL(this.AM_AbsoluteX(PageCrossingBehavior.AlwaysReadTwice))); break; // ASL (absolute, X) case 0x1f: this.SLO(this.AM_AbsoluteX()); break; // *SLO (absolute, X) case 0x20: this.JSR(); break; // JSR (absolute) case 0x21: this.A = this.AndR(this.A, this.AM_IndexedIndirectX()); break; // AND (indexed indirect X) case 0x22: break; case 0x23: this.RLA(this.AM_IndexedIndirectX()); break; // *RLA (indexed indirect X) case 0x24: this.BIT(this.A, this.AM_ZeroPage()); break; // BIT (zero page) case 0x25: this.A = this.AndR(this.A, this.AM_ZeroPage()); break; // AND (zero page) case 0x26: this.BusReadModifyWrite(this.ROL(this.AM_ZeroPage())); break; // ROL (zero page) case 0x27: this.RLA(this.AM_ZeroPage()); break; // *RLA (zero page) case 0x28: this.BusRead(); this.BusRead(this.S, 1); this.PLP(); break; // PLP (implied) case 0x29: this.A = this.AndR(this.A, this.AM_Immediate()); break; // AND (immediate) case 0x2a: this.BusRead(); this.A = this.ROL(this.A); break; // ROL A (implied) case 0x2b: this.ANC(this.AM_Immediate()); break; // *ANC (immediate) case 0x2c: this.BIT(this.A, this.AM_Absolute()); break; // BIT (absolute) case 0x2d: this.A = this.AndR(this.A, this.AM_Absolute()); break; // AND (absolute) case 0x2e: this.BusReadModifyWrite(this.ROL(this.AM_Absolute())); break; // ROL (absolute) case 0x2f: this.RLA(this.AM_Absolute()); break; // *RLA (absolute) case 0x30: this.Branch(this.Negative != 0); break; // BMI (relative) case 0x31: this.A = this.AndR(this.A, this.AM_IndirectIndexedY()); break; // AND (indirect indexed Y) case 0x32: break; case 0x33: this.RLA(this.AM_IndirectIndexedY()); break; // *RLA (indirect indexed Y) case 0x34: this.AM_ZeroPageX(); break; // *NOP (zero page, X) case 0x35: this.A = this.AndR(this.A, this.AM_ZeroPageX()); break; // AND (zero page, X) case 0x36: this.BusReadModifyWrite(this.ROL(this.AM_ZeroPageX())); break; // ROL (zero page, X) case 0x37: this.RLA(this.AM_ZeroPageX()); break; // *RLA (zero page, X) case 0x38: this.BusRead(); this.P = SetFlag(this.P, StatusBits.CF); break; // SEC (implied) case 0x39: this.A = this.AndR(this.A, this.AM_AbsoluteY()); break; // AND (absolute, Y) case 0x3a: this.BusRead(); break; // *NOP (implied) case 0x3b: this.RLA(this.AM_AbsoluteY()); break; // *RLA (absolute, Y) case 0x3c: this.AM_AbsoluteX(); break; // *NOP (absolute, X) case 0x3d: this.A = this.AndR(this.A, this.AM_AbsoluteX()); break; // AND (absolute, X) case 0x3e: this.BusReadModifyWrite(this.ROL(this.AM_AbsoluteX(PageCrossingBehavior.AlwaysReadTwice))); break; // ROL (absolute, X) case 0x3f: this.RLA(this.AM_AbsoluteX()); break; // *RLA (absolute, X) case 0x40: this.BusRead(); this.RTI(); break; // RTI (implied) case 0x41: this.A = this.EorR(this.A, this.AM_IndexedIndirectX()); break; // EOR (indexed indirect X) case 0x42: break; case 0x43: this.SRE(this.AM_IndexedIndirectX()); break; // *SRE (indexed indirect X) case 0x44: this.AM_ZeroPage(); break; // *NOP (zero page) case 0x45: this.A = this.EorR(this.A, this.AM_ZeroPage()); break; // EOR (zero page) case 0x46: this.BusReadModifyWrite(this.LSR(this.AM_ZeroPage())); break; // LSR (zero page) case 0x47: this.SRE(this.AM_ZeroPage()); break; // *SRE (zero page) case 0x48: this.BusRead(); this.Push(this.A); break; // PHA (implied) case 0x49: this.A = this.EorR(this.A, this.AM_Immediate()); break; // EOR (immediate) case 0x4a: this.BusRead(); this.A = this.LSR(this.A); break; // LSR A (implied) case 0x4b: this.ASR(this.AM_Immediate()); break; // *ASR (immediate) case 0x4c: this.Jump(this.Address_Absolute()); break; // JMP (absolute) case 0x4d: this.A = this.EorR(this.A, this.AM_Absolute()); break; // EOR (absolute) case 0x4e: this.BusReadModifyWrite(this.LSR(this.AM_Absolute())); break; // LSR (absolute) case 0x4f: this.SRE(this.AM_Absolute()); break; // *SRE (absolute) case 0x50: this.Branch(this.Overflow == 0); break; // BVC (relative) case 0x51: this.A = this.EorR(this.A, this.AM_IndirectIndexedY()); break; // EOR (indirect indexed Y) case 0x52: break; case 0x53: this.SRE(this.AM_IndirectIndexedY()); break; // *SRE (indirect indexed Y) case 0x54: this.AM_ZeroPageX(); break; // *NOP (zero page, X) case 0x55: this.A = this.EorR(this.A, this.AM_ZeroPageX()); break; // EOR (zero page, X) case 0x56: this.BusReadModifyWrite(this.LSR(this.AM_ZeroPageX())); break; // LSR (zero page, X) case 0x57: this.SRE(this.AM_ZeroPageX()); break; // *SRE (zero page, X) case 0x58: this.BusRead(); this.P = ClearFlag(this.P, StatusBits.IF); break; // CLI (implied) case 0x59: this.A = this.EorR(this.A, this.AM_AbsoluteY()); break; // EOR (absolute, Y) case 0x5a: this.BusRead(); break; // *NOP (implied) case 0x5b: this.SRE(this.AM_AbsoluteY()); break; // *SRE (absolute, Y) case 0x5c: this.AM_AbsoluteX(); break; // *NOP (absolute, X) case 0x5d: this.A = this.EorR(this.A, this.AM_AbsoluteX()); break; // EOR (absolute, X) case 0x5e: this.BusReadModifyWrite(this.LSR(this.AM_AbsoluteX(PageCrossingBehavior.AlwaysReadTwice))); break; // LSR (absolute, X) case 0x5f: this.SRE(this.AM_AbsoluteX()); break; // *SRE (absolute, X) case 0x60: this.BusRead(); this.RTS(); break; // RTS (implied) case 0x61: this.A = this.ADC(this.A, this.AM_IndexedIndirectX()); break; // ADC (indexed indirect X) case 0x62: break; case 0x63: this.RRA(this.AM_IndexedIndirectX()); break; // *RRA (indexed indirect X) case 0x64: this.AM_ZeroPage(); break; // *NOP (zero page) case 0x65: this.A = this.ADC(this.A, this.AM_ZeroPage()); break; // ADC (zero page) case 0x66: this.BusReadModifyWrite(this.ROR(this.AM_ZeroPage())); break; // ROR (zero page) case 0x67: this.RRA(this.AM_ZeroPage()); break; // *RRA (zero page) case 0x68: this.BusRead(); this.BusRead(this.S, 1); this.A = this.Through(this.Pop()); break; // PLA (implied) case 0x69: this.A = this.ADC(this.A, this.AM_Immediate()); break; // ADC (immediate) case 0x6a: this.BusRead(); this.A = this.ROR(this.A); break; // ROR A (implied) case 0x6b: this.ARR(this.AM_Immediate()); break; // *ARR (immediate) case 0x6c: this.Jump(this.Address_Indirect()); break; // JMP (indirect) case 0x6d: this.A = this.ADC(this.A, this.AM_Absolute()); break; // ADC (absolute) case 0x6e: this.BusReadModifyWrite(this.ROR(this.AM_Absolute())); break; // ROR (absolute) case 0x6f: this.RRA(this.AM_Absolute()); break; // *RRA (absolute) case 0x70: this.Branch(this.Overflow != 0); break; // BVS (relative) case 0x71: this.A = this.ADC(this.A, this.AM_IndirectIndexedY()); break; // ADC (indirect indexed Y) case 0x72: break; case 0x73: this.RRA(this.AM_IndirectIndexedY()); break; // *RRA (indirect indexed Y) case 0x74: this.AM_ZeroPageX(); break; // *NOP (zero page, X) case 0x75: this.A = this.ADC(this.A, this.AM_ZeroPageX()); break; // ADC (zero page, X) case 0x76: this.BusReadModifyWrite(this.ROR(this.AM_ZeroPageX())); break; // ROR (zero page, X) case 0x77: this.RRA(this.AM_ZeroPageX()); break; // *RRA (zero page, X) case 0x78: this.BusRead(); this.P = SetFlag(this.P, StatusBits.IF); break; // SEI (implied) case 0x79: this.A = this.ADC(this.A, this.AM_AbsoluteY()); break; // ADC (absolute, Y) case 0x7a: this.BusRead(); break; // *NOP (implied) case 0x7b: this.RRA(this.AM_AbsoluteY()); break; // *RRA (absolute, Y) case 0x7c: this.AM_AbsoluteX(); break; // *NOP (absolute, X) case 0x7d: this.A = this.ADC(this.A, this.AM_AbsoluteX()); break; // ADC (absolute, X) case 0x7e: this.BusReadModifyWrite(this.ROR(this.AM_AbsoluteX(PageCrossingBehavior.AlwaysReadTwice))); break; // ROR (absolute, X) case 0x7f: this.RRA(this.AM_AbsoluteX()); break; // *RRA (absolute, X) case 0x80: this.AM_Immediate(); break; // *NOP (immediate) case 0x81: this.BusWrite(this.Address_IndexedIndirectX(), this.A); break; // STA (indexed indirect X) case 0x82: this.AM_Immediate(); break; // *NOP (immediate) case 0x83: this.BusWrite(this.Address_IndexedIndirectX(), (byte)(this.A & this.X)); break; // *SAX (indexed indirect X) case 0x84: this.BusWrite(this.Address_ZeroPage(), this.Y); break; // STY (zero page) case 0x85: this.BusWrite(this.Address_ZeroPage(), this.A); break; // STA (zero page) case 0x86: this.BusWrite(this.Address_ZeroPage(), this.X); break; // STX (zero page) case 0x87: this.BusWrite(this.Address_ZeroPage(), (byte)(this.A & this.X)); break; // *SAX (zero page) case 0x88: this.BusRead(); this.Y = this.DEC(this.Y); break; // DEY (implied) case 0x89: this.AM_Immediate(); break; // *NOP (immediate) case 0x8a: this.BusRead(); this.A = this.Through(this.X); break; // TXA (implied) case 0x8b: break; case 0x8c: this.BusWrite(this.Address_Absolute(), this.Y); break; // STY (absolute) case 0x8d: this.BusWrite(this.Address_Absolute(), this.A); break; // STA (absolute) case 0x8e: this.BusWrite(this.Address_Absolute(), this.X); break; // STX (absolute) case 0x8f: this.BusWrite(this.Address_Absolute(), (byte)(this.A & this.X)); break; // *SAX (absolute) case 0x90: this.Branch(this.Carry == 0); break; // BCC (relative) case 0x91: this.AM_IndirectIndexedY(); this.BusWrite(this.A); break; // STA (indirect indexed Y) case 0x92: break; case 0x93: break; case 0x94: this.BusWrite(this.Address_ZeroPageX(), this.Y); break; // STY (zero page, X) case 0x95: this.BusWrite(this.Address_ZeroPageX(), this.A); break; // STA (zero page, X) case 0x96: this.BusWrite(this.Address_ZeroPageY(), this.X); break; // STX (zero page, Y) case 0x97: this.BusWrite(this.Address_ZeroPageY(), (byte)(this.A & this.X)); break; // *SAX (zero page, Y) case 0x98: this.BusRead(); this.A = this.Through(this.Y); break; // TYA (implied) case 0x99: this.STA_AbsoluteY(); break; // STA (absolute, Y) case 0x9a: this.BusRead(); this.S = this.X; break; // TXS (implied) case 0x9b: break; case 0x9c: break; case 0x9d: this.STA_AbsoluteX(); break; // STA (absolute, X) case 0x9e: break; case 0x9f: break; case 0xa0: this.Y = this.Through(this.AM_Immediate()); break; // LDY (immediate) case 0xa1: this.A = this.Through(this.AM_IndexedIndirectX()); break; // LDA (indexed indirect X) case 0xa2: this.X = this.Through(this.AM_Immediate()); break; // LDX (immediate) case 0xa3: this.A = this.X = this.Through(this.AM_IndexedIndirectX()); break; // *LAX (indexed indirect X) case 0xa4: this.Y = this.Through(this.AM_ZeroPage()); break; // LDY (zero page) case 0xa5: this.A = this.Through(this.AM_ZeroPage()); break; // LDA (zero page) case 0xa6: this.X = this.Through(this.AM_ZeroPage()); break; // LDX (zero page) case 0xa7: this.A = this.X = this.Through(this.AM_ZeroPage()); break; // *LAX (zero page) case 0xa8: this.BusRead(); this.Y = this.Through(this.A); break; // TAY (implied) case 0xa9: this.A = this.Through(this.AM_Immediate()); break; // LDA (immediate) case 0xaa: this.BusRead(); this.X = this.Through(this.A); break; // TAX (implied) case 0xab: this.A = this.X = this.Through(this.AM_Immediate()); break; // *ATX (immediate) case 0xac: this.Y = this.Through(this.AM_Absolute()); break; // LDY (absolute) case 0xad: this.A = this.Through(this.AM_Absolute()); break; // LDA (absolute) case 0xae: this.X = this.Through(this.AM_Absolute()); break; // LDX (absolute) case 0xaf: this.A = this.X = this.Through(this.AM_Absolute()); break; // *LAX (absolute) case 0xb0: this.Branch(this.Carry != 0); break; // BCS (relative) case 0xb1: this.A = this.Through(this.AM_IndirectIndexedY()); break; // LDA (indirect indexed Y) case 0xb2: break; case 0xb3: this.A = this.X = this.Through(this.AM_IndirectIndexedY()); break; // *LAX (indirect indexed Y) case 0xb4: this.Y = this.Through(this.AM_ZeroPageX()); break; // LDY (zero page, X) case 0xb5: this.A = this.Through(this.AM_ZeroPageX()); break; // LDA (zero page, X) case 0xb6: this.X = this.Through(this.AM_ZeroPageY()); break; // LDX (zero page, Y) case 0xb7: this.A = this.X = this.Through(this.AM_ZeroPageY()); break; // *LAX (zero page, Y) case 0xb8: this.BusRead(); this.P = ClearFlag(this.P, StatusBits.VF); break; // CLV (implied) case 0xb9: this.A = this.Through(this.AM_AbsoluteY()); break; // LDA (absolute, Y) case 0xba: this.BusRead(); this.X = this.Through(this.S); break; // TSX (implied) case 0xbb: break; case 0xbc: this.Y = this.Through(this.AM_AbsoluteX()); break; // LDY (absolute, X) case 0xbd: this.A = this.Through(this.AM_AbsoluteX()); break; // LDA (absolute, X) case 0xbe: this.X = this.Through(this.AM_AbsoluteY()); break; // LDX (absolute, Y) case 0xbf: this.A = this.X = this.Through(this.AM_AbsoluteY()); break; // *LAX (absolute, Y) case 0xc0: this.CMP(this.Y, this.AM_Immediate()); break; // CPY (immediate) case 0xc1: this.CMP(this.A, this.AM_IndexedIndirectX()); break; // CMP (indexed indirect X) case 0xc2: this.AM_Immediate(); break; // *NOP (immediate) case 0xc3: this.DCP(this.AM_IndexedIndirectX()); break; // *DCP (indexed indirect X) case 0xc4: this.CMP(this.Y, this.AM_ZeroPage()); break; // CPY (zero page) case 0xc5: this.CMP(this.A, this.AM_ZeroPage()); break; // CMP (zero page) case 0xc6: this.BusReadModifyWrite(this.DEC(this.AM_ZeroPage())); break; // DEC (zero page) case 0xc7: this.DCP(this.AM_ZeroPage()); break; // *DCP (zero page) case 0xc8: this.BusRead(); this.Y = this.INC(this.Y); break; // INY (implied) case 0xc9: this.CMP(this.A, this.AM_Immediate()); break; // CMP (immediate) case 0xca: this.BusRead(); this.X = this.DEC(this.X); break; // DEX (implied) case 0xcb: this.AXS(this.AM_Immediate()); break; // *AXS (immediate) case 0xcc: this.CMP(this.Y, this.AM_Absolute()); break; // CPY (absolute) case 0xcd: this.CMP(this.A, this.AM_Absolute()); break; // CMP (absolute) case 0xce: this.BusReadModifyWrite(this.DEC(this.AM_Absolute())); break; // DEC (absolute) case 0xcf: this.DCP(this.AM_Absolute()); break; // *DCP (absolute) case 0xd0: this.Branch(this.Zero == 0); break; // BNE (relative) case 0xd1: this.CMP(this.A, this.AM_IndirectIndexedY()); break; // CMP (indirect indexed Y) case 0xd2: break; case 0xd3: this.DCP(this.AM_IndirectIndexedY()); break; // *DCP (indirect indexed Y) case 0xd4: this.AM_ZeroPageX(); break; // *NOP (zero page, X) case 0xd5: this.CMP(this.A, this.AM_ZeroPageX()); break; // CMP (zero page, X) case 0xd6: this.BusReadModifyWrite(this.DEC(this.AM_ZeroPageX())); break; // DEC (zero page, X) case 0xd7: this.DCP(this.AM_ZeroPageX()); break; // *DCP (zero page, X) case 0xd8: this.BusRead(); this.P = ClearFlag(this.P, StatusBits.DF); break; // CLD (implied) case 0xd9: this.CMP(this.A, this.AM_AbsoluteY()); break; // CMP (absolute, Y) case 0xda: this.BusRead(); break; // *NOP (implied) case 0xdb: this.DCP(this.AM_AbsoluteY()); break; // *DCP (absolute, Y) case 0xdc: this.AM_AbsoluteX(); break; // *NOP (absolute, X) case 0xdd: this.CMP(this.A, this.AM_AbsoluteX()); break; // CMP (absolute, X) case 0xde: this.BusReadModifyWrite(this.DEC(this.AM_AbsoluteX(PageCrossingBehavior.AlwaysReadTwice))); break; // DEC (absolute, X) case 0xdf: this.DCP(this.AM_AbsoluteX()); break; // *DCP (absolute, X) case 0xe0: this.CMP(this.X, this.AM_Immediate()); break; // CPX (immediate) case 0xe1: this.A = this.SBC(this.A, this.AM_IndexedIndirectX()); break; // SBC (indexed indirect X) case 0xe2: this.AM_Immediate(); break; // *NOP (immediate) case 0xe3: this.ISB(this.AM_IndexedIndirectX()); break; // *ISB (indexed indirect X) case 0xe4: this.CMP(this.X, this.AM_ZeroPage()); break; // CPX (zero page) case 0xe5: this.A = this.SBC(this.A, this.AM_ZeroPage()); break; // SBC (zero page) case 0xe6: this.BusReadModifyWrite(this.INC(this.AM_ZeroPage())); break; // INC (zero page) case 0xe7: this.ISB(this.AM_ZeroPage()); break; // *ISB (zero page) case 0xe8: this.BusRead(); this.X = this.INC(this.X); break; // INX (implied) case 0xe9: this.A = this.SBC(this.A, this.AM_Immediate()); break; // SBC (immediate) case 0xea: this.BusRead(); break; // NOP (implied) case 0xeb: this.A = this.SBC(this.A, this.AM_Immediate()); break; // *SBC (immediate) case 0xec: this.CMP(this.X, this.AM_Absolute()); break; // CPX (absolute) case 0xed: this.A = this.SBC(this.A, this.AM_Absolute()); break; // SBC (absolute) case 0xee: this.BusReadModifyWrite(this.INC(this.AM_Absolute())); break; // *ISB (absolute) case 0xf0: this.Branch(this.Zero != 0); break; // BEQ (relative) case 0xf1: this.A = this.SBC(this.A, this.AM_IndirectIndexedY()); break; // SBC (indirect indexed Y) case 0xf2: break; case 0xf3: this.ISB(this.AM_IndirectIndexedY()); break; // *ISB (indirect indexed Y) case 0xf4: this.AM_ZeroPageX(); break; // *NOP (zero page, X) case 0xf5: this.A = this.SBC(this.A, this.AM_ZeroPageX()); break; // SBC (zero page, X) case 0xf6: this.BusReadModifyWrite(this.INC(this.AM_ZeroPageX())); break; // INC (zero page, X) case 0xf7: this.ISB(this.AM_ZeroPageX()); break; // *ISB (zero page, X) case 0xf8: this.BusRead(); this.P = SetFlag(this.P, StatusBits.DF); break; // SED (implied) case 0xf9: this.A = this.SBC(this.A, this.AM_AbsoluteY()); break; // SBC (absolute, Y) case 0xfa: this.BusRead(); break; // *NOP (implied) case 0xfb: this.ISB(this.AM_AbsoluteY()); break; // *ISB (absolute, Y) case 0xfc: this.AM_AbsoluteX(); break; // *NOP (absolute, X) case 0xfd: this.A = this.SBC(this.A, this.AM_AbsoluteX()); break; // SBC (absolute, X) case 0xfe: this.BusReadModifyWrite(this.INC(this.AM_AbsoluteX(PageCrossingBehavior.AlwaysReadTwice))); break; // INC (absolute, X) case 0xff: this.ISB(this.AM_AbsoluteX()); break; // *ISB (absolute, X) } return 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.LowerSYNC(); // Instruction fetch beginning this.OpCode = this.Bus.Read(this.PC++); // can't use fetchByte if (this.RESET().Lowered()) { this.HandleRESET(); } else if (this.NMI().Lowered()) { this.HandleNMI(); } else if (this.INT().Lowered() && (this.InterruptMasked == 0)) { this.HandleINT(); } this.Execute(); } } this.OnExecutedInstruction(); return this.Cycles; } protected virtual void OnExecutingInstruction() => this.ExecutingInstruction?.Invoke(this, EventArgs.Empty); protected virtual void OnExecutedInstruction() => this.ExecutedInstruction?.Invoke(this, EventArgs.Empty); 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); 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); 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); 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); protected override byte Pop() => this.BusRead(++this.S, 1); protected override void Push(byte value) => this.BusWrite(this.S--, 1, value); protected virtual void RaiseSYNC() { this.OnRaisingSYNC(); this.SYNC().Raise(); this.OnRaisedSYNC(); } protected virtual void LowerSYNC() { this.OnLoweringSYNC(); this.SYNC().Lower(); this.OnLoweredSYNC(); } protected override sealed void HandleRESET() { this.RaiseRESET(); this.handlingRESET = true; this.OpCode = 0x00; // BRK } protected override sealed void HandleINT() { this.RaiseINT(); this.handlingINT = true; this.OpCode = 0x00; // BRK } private static byte SetFlag(byte f, StatusBits flag) => SetFlag(f, (byte)flag); private static byte SetFlag(byte f, StatusBits flag, int condition) => SetFlag(f, (byte)flag, condition); private static byte SetFlag(byte f, StatusBits flag, bool condition) => SetFlag(f, (byte)flag, condition); private static byte ClearFlag(byte f, StatusBits flag) => ClearFlag(f, (byte)flag); private static byte ClearFlag(byte f, StatusBits flag, int condition) => ClearFlag(f, (byte)flag, condition); private static byte ClearFlag(byte f, StatusBits flag, bool condition) => ClearFlag(f, (byte)flag, condition); private void HandleNMI() { this.RaiseNMI(); this.handlingNMI = true; this.OpCode = 0x00; // BRK } private void HandleSO() { this.RaiseSO(); this.P |= (byte)StatusBits.VF; } private void Interrupt() { var reset = this.handlingRESET; var nmi = this.handlingNMI; var irq = this.handlingINT; var hardware = nmi || irq || reset; var software = !hardware; if (reset) { this.DummyPush(Chip.HighByte(this.PC)); this.DummyPush(Chip.LowByte(this.PC)); this.DummyPush(this.P); } else { this.PushWord(this.PC); this.Push((byte)(this.P | (int)(software ? StatusBits.BF : 0))); } this.P = SetFlag(this.P, StatusBits.IF); // Disable IRQ var vector = reset ? RSTvector : (nmi ? NMIvector : IRQvector); this.Jump(this.GetWordPaged(0xff, vector)); this.handlingRESET = this.handlingNMI = this.handlingINT = false; } private void DummyPush(byte value) { this.Tick(); this.Bus.Data = value; this.Bus.Address = Chip.MakeWord(this.S--, 1); } private ushort Address_Absolute() => this.FetchWord(); private byte Address_ZeroPage() => this.FetchByte(); private ushort Address_ZeroPageIndirect() => this.GetWordPaged(0, this.Address_ZeroPage()); private ushort Address_Indirect() { var address = this.Address_Absolute(); return this.GetWordPaged(Chip.HighByte(address), Chip.LowByte(address)); } private byte Address_ZeroPageX() { var address = this.Address_ZeroPage(); this.BusRead(address); return Chip.LowByte(address + this.X); } private byte Address_ZeroPageY() { var address = this.Address_ZeroPage(); this.BusRead(address); return Chip.LowByte(address + this.Y); } private Tuple Address_AbsoluteX() { var address = this.Address_Absolute(); var page = HighByte(address); address += this.X; return new Tuple(address, page); } private Tuple Address_AbsoluteY() { var address = this.Address_Absolute(); var page = HighByte(address); address += this.Y; return new Tuple(address, page); } private ushort Address_IndexedIndirectX() => this.GetWordPaged(0, this.Address_ZeroPageX()); private Tuple Address_IndirectIndexedY() { var address = this.Address_ZeroPageIndirect(); var page = Chip.HighByte(address); address += this.Y; return new Tuple(address, page); } private ushort Address_relative_byte() { var offset = (sbyte)this.FetchByte(); this.intermediate = (ushort)(this.PC + offset); return this.intermediate; } private byte AM_Immediate() => this.FetchByte(); private byte AM_Absolute() => this.BusRead(this.Address_Absolute()); private byte AM_ZeroPage() => this.BusRead(this.Address_ZeroPage()); private byte AM_AbsoluteX(PageCrossingBehavior behaviour = PageCrossingBehavior.MaybeReadTwice) { var crossed = this.Address_AbsoluteX(); var address = crossed.Item1; var page = crossed.Item2; var possible = this.BusRead(Chip.LowByte(address), page); if ((behaviour == PageCrossingBehavior.AlwaysReadTwice) || (page != Chip.HighByte(address))) { possible = this.BusRead(address); } return possible; } private byte AM_AbsoluteY() { var crossed = this.Address_AbsoluteY(); var address = crossed.Item1; var page = crossed.Item2; var possible = this.BusRead(Chip.LowByte(address), page); if (page != Chip.HighByte(address)) { possible = this.BusRead(address); } return possible; } private byte AM_ZeroPageX() => this.BusRead(this.Address_ZeroPageX()); private byte AM_ZeroPageY() => this.BusRead(this.Address_ZeroPageY()); private byte AM_IndexedIndirectX() => this.BusRead(this.Address_IndexedIndirectX()); private byte AM_IndirectIndexedY() { var crossed = this.Address_IndirectIndexedY(); var address = crossed.Item1; var page = crossed.Item2; var possible = this.BusRead(LowByte(address), page); if (page != Chip.HighByte(address)) { possible = this.BusRead(address); } return possible; } private void AdjustZero(byte datum) => this.P = ClearFlag(this.P, StatusBits.ZF, datum); private void AdjustNegative(byte datum) => this.P = SetFlag(this.P, StatusBits.NF, datum & (byte)StatusBits.NF); private void AdjustNZ(byte datum) { this.AdjustZero(datum); this.AdjustNegative(datum); } private void Branch(bool condition) { var destination = this.Address_relative_byte(); if (condition) { this.BusRead(); var page = HighByte(this.PC); this.Jump(destination); if (Chip.HighByte(this.PC) != page) { this.BusRead(Chip.LowByte(this.PC), page); } } } private byte Through(int data) => this.Through((byte)data); private byte Through(byte data) { this.AdjustNZ(data); return data; } private void BusReadModifyWrite(byte data) { // The read will have already taken place... this.BusWrite(); this.BusWrite(data); } private byte SBC(byte operand, byte data) { var returned = this.SUB(operand, data, ~this.P & (int)StatusBits.CF); var difference = this.intermediate; this.AdjustNZ(Chip.LowByte(difference)); this.P = SetFlag(this.P, StatusBits.VF, (operand ^ data) & (operand ^ Chip.LowByte(difference)) & (int)StatusBits.NF); this.P = ClearFlag(this.P, StatusBits.CF, Chip.HighByte(difference)); return returned; } private byte SUB(byte operand, byte data, int borrow = 0) { return this.Decimal != 0 ? this.SUB_d(operand, data, borrow) : this.SUB_b(operand, data, borrow); } private byte SUB_b(byte operand, byte data, int borrow) { this.intermediate = (ushort)(operand - data - borrow); return Chip.LowByte(this.intermediate); } private byte SUB_d(byte operand, byte data, int borrow) { this.intermediate = (ushort)(operand - data - borrow); var low = (byte)(LowNibble(operand) - LowNibble(data) - borrow); var lowNegative = low & (byte)StatusBits.NF; if (lowNegative != 0) { low -= 6; } var high = (byte)(HighNibble(operand) - HighNibble(data) - (lowNegative >> 7)); var highNegative = high & (byte)StatusBits.NF; if (highNegative != 0) { high -= 6; } return (byte)(PromoteNibble(high) | LowNibble(low)); } private byte ADC(byte operand, byte data) { var returned = this.ADD(operand, data, this.Carry); this.AdjustNZ(Chip.LowByte(this.intermediate)); return returned; } private byte ADD(byte operand, byte data, int carry = 0) { return this.Decimal != 0 ? this.ADD_d(operand, data, carry) : this.ADD_b(operand, data, carry); } private byte ADD_b(byte operand, byte data, int carry) { this.intermediate = (ushort)(operand + data + carry); this.P = SetFlag(this.P, StatusBits.VF, ~(operand ^ data) & (operand ^ Chip.LowByte(this.intermediate)) & (int)StatusBits.NF); this.P = SetFlag(this.P, StatusBits.CF, Chip.HighByte(this.intermediate) & (int)StatusBits.CF); return Chip.LowByte(this.intermediate); } private byte ADD_d(byte operand, byte data, int carry) { this.intermediate = (ushort)(operand + data + carry); var low = (byte)(LowNibble(operand) + LowNibble(data) + carry); if (low > 9) { low += 6; } var high = (byte)(HighNibble(operand) + HighNibble(data) + (low > 0xf ? 1 : 0)); this.P = SetFlag(this.P, StatusBits.VF, ~(operand ^ data) & (operand ^ Chip.PromoteNibble(high)) & (int)StatusBits.NF); if (high > 9) { high += 6; } this.P = SetFlag(this.P, StatusBits.CF, high > 0xf); return (byte)(PromoteNibble(high) | LowNibble(low)); } private byte AndR(byte operand, byte data) => this.Through(operand & data); private byte ASL(byte value) { this.P = SetFlag(this.P, StatusBits.CF, value & (byte)Bits.Bit7); return this.Through(value << 1); } private void BIT(byte operand, byte data) { this.P = SetFlag(this.P, StatusBits.VF, data & (byte)StatusBits.VF); this.AdjustZero((byte)(operand & data)); this.AdjustNegative(data); } private void CMP(byte first, byte second) { this.intermediate = (ushort)(first - second); this.AdjustNZ(Chip.LowByte(this.intermediate)); this.P = ClearFlag(this.P, StatusBits.CF, Chip.HighByte(this.intermediate)); } private byte DEC(byte value) => this.Through(value - 1); private byte EorR(byte operand, byte data) => this.Through(operand ^ data); private byte INC(byte value) => this.Through(value + 1); private void JSR() { var low = this.FetchByte(); this.BusRead(this.S, 1); // dummy read this.PushWord(this.PC); this.PC = Chip.MakeWord(low, this.FetchByte()); } private byte LSR(byte value) { this.P = SetFlag(this.P, StatusBits.CF, value & (byte)Bits.Bit0); return this.Through(value >> 1); } private byte OrR(byte operand, byte data) => this.Through(operand | data); private void PHP() => this.Push((byte)(this.P | (byte)StatusBits.BF)); private void PLP() => this.P = (byte)((this.Pop() | (byte)StatusBits.RF) & (byte)~StatusBits.BF); private byte ROL(byte operand) { var carryIn = this.Carry; this.P = SetFlag(this.P, StatusBits.CF, operand & (byte)Bits.Bit7); var result = (operand << 1) | carryIn; return this.Through(result); } private byte ROR(byte operand) { var carryIn = this.Carry; this.P = SetFlag(this.P, StatusBits.CF, operand & (byte)Bits.Bit0); var result = (operand >> 1) | (carryIn << 7); return this.Through(result); } private void RTI() { this.BusRead(this.S, 1); // dummy read this.PLP(); this.Return(); } private void RTS() { this.BusRead(this.S, 1); // dummy read this.Return(); this.FetchByte(); } private void ANC(byte value) { this.A = this.AndR(this.A, value); this.P = SetFlag(this.P, StatusBits.CF, this.A & (byte)Bits.Bit7); } private void ARR(byte value) { this.A = this.AndR(this.A, value); this.A = this.ROR(this.A); this.P = SetFlag(this.P, StatusBits.CF, this.A & (byte)Bits.Bit6); this.P = SetFlag(this.P, StatusBits.VF, ((this.A & (byte)Bits.Bit6) >> 6) ^ ((this.A & (byte)Bits.Bit5) >> 5)); } private void ASR(byte value) { this.A = this.AndR(this.A, value); this.A = this.LSR(this.A); } private void AXS(byte value) { this.X = this.Through(this.SUB((byte)(this.A & this.X), value)); this.P = ClearFlag(this.P, StatusBits.CF, Chip.HighByte(this.intermediate)); } private void DCP(byte value) { this.BusReadModifyWrite(this.DEC(value)); this.CMP(this.A, this.Bus.Data); } private void ISB(byte value) { this.BusReadModifyWrite(this.INC(value)); this.A = this.SBC(this.A, this.Bus.Data); } private void RLA(byte value) { this.BusReadModifyWrite(this.ROL(value)); this.A = this.AndR(this.A, this.Bus.Data); } private void RRA(byte value) { this.BusReadModifyWrite(this.ROR(value)); this.A = this.ADC(this.A, this.Bus.Data); } private void SLO(byte value) { this.BusReadModifyWrite(this.ASL(value)); this.A = this.OrR(this.A, this.Bus.Data); } private void SRE(byte value) { this.BusReadModifyWrite(this.LSR(value)); this.A = this.EorR(this.A, this.Bus.Data); } private void STA_AbsoluteX() { var crossed = this.Address_AbsoluteX(); var address = crossed.Item1; var page = crossed.Item2; this.BusRead(Chip.LowByte(address), page); this.BusWrite(address, this.A); } private void STA_AbsoluteY() { var crossed = this.Address_AbsoluteY(); var address = crossed.Item1; var page = crossed.Item2; this.BusRead(Chip.LowByte(address), page); this.BusWrite(address, this.A); } } }