using System; using static AxibugEmuOnline.Client.UNES.CPU.AddressingMode; namespace AxibugEmuOnline.Client.UNES { public sealed partial class CPU { [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] public class OpCodeDef : Attribute { public int OpCode; public int Cycles = 1; public bool PageBoundary; public bool RMW; public AddressingMode Mode = None; } [OpCodeDef(OpCode = 0x20, Cycles = 6)] private void JSR() { PushWord(PC + 1); PC = NextWord(); } [OpCodeDef(OpCode = 0x40, Cycles = 6)] private void RTI() { // TODO: this dummy fetch should happen for all single-byte instructions NextByte(); P = Pop(); PC = PopWord(); } [OpCodeDef(OpCode = 0x60, Cycles = 6)] private void RTS() { NextByte(); PC = PopWord() + 1; } [OpCodeDef(OpCode = 0xC8, Cycles = 2)] private void INY() => Y++; [OpCodeDef(OpCode = 0x88, Cycles = 2)] private void DEY() => Y--; [OpCodeDef(OpCode = 0xE8, Cycles = 2)] private void INX() => X++; [OpCodeDef(OpCode = 0xCA, Cycles = 2, RMW = true)] private void DEX() => X--; [OpCodeDef(OpCode = 0xA8, Cycles = 2)] private void TAY() => Y = A; [OpCodeDef(OpCode = 0x98, Cycles = 2)] private void TYA() => A = Y; [OpCodeDef(OpCode = 0xAA, Cycles = 2, RMW = true)] private void TAX() => X = A; [OpCodeDef(OpCode = 0x8A, Cycles = 2, RMW = true)] private void TXA() => A = X; [OpCodeDef(OpCode = 0xBA, Cycles = 2)] private void TSX() => X = SP; [OpCodeDef(OpCode = 0x9A, Cycles = 2, RMW = true)] private void TXS() => SP = X; [OpCodeDef(OpCode = 0x08, Cycles = 3)] private void PHP() => Push(P | BreakSourceBit); [OpCodeDef(OpCode = 0x28, Cycles = 4)] private void PLP() => P = (uint)(Pop() & ~BreakSourceBit); [OpCodeDef(OpCode = 0x68, Cycles = 4)] private void PLA() => A = Pop(); [OpCodeDef(OpCode = 0x48, Cycles = 3)] private void PHA() => Push(A); [OpCodeDef(OpCode = 0x24, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x2C, Mode = Absolute, Cycles = 4)] private void BIT() { uint val = AddressRead(); F.Overflow = (val & 0x40) > 0; F.Zero = (val & A) == 0; F.Negative = (val & 0x80) > 0; } private void Branch(bool cond) { uint nPC = (uint)(PC + NextSByte() + 1); if (cond) { PC = nPC; Cycle++; } } [OpCodeDef(OpCode = 0x4C, Cycles = 3)] [OpCodeDef(OpCode = 0x6C, Cycles = 5)] private void JMP() { if (_currentInstruction == 0x4C) { PC = NextWord(); } else if (_currentInstruction == 0x6C) { uint off = NextWord(); // AN INDIRECT JUMP MUST NEVER USE A VECTOR BEGINNING ON THE LAST BYTE OF A PAGE // // If address $3000 contains $40, $30FF contains $80, and $3100 contains $50, // the result of JMP ($30FF) will be a transfer of control to $4080 rather than // $5080 as you intended i.e. the 6502 took the low byte of the address from // $30FF and the high byte from $3000. // // http://www.6502.org/tutorials/6502opcodes.html uint hi = (off & 0xFF) == 0xFF ? off - 0xFF : off + 1; uint oldPC = PC; PC = ReadByte(off) | (ReadByte(hi) << 8); if ((oldPC & 0xFF00) != (PC & 0xFF00)) { Cycle += 2; } } else { throw new NotImplementedException(); } } [OpCodeDef(OpCode = 0xB0, Cycles = 2)] private void BCS() => Branch(F.Carry); [OpCodeDef(OpCode = 0x90, Cycles = 2)] private void BCC() => Branch(!F.Carry); [OpCodeDef(OpCode = 0xF0, Cycles = 2)] private void BEQ() => Branch(F.Zero); [OpCodeDef(OpCode = 0xD0, Cycles = 2)] private void BNE() => Branch(!F.Zero); [OpCodeDef(OpCode = 0x70, Cycles = 2)] private void BVS() => Branch(F.Overflow); [OpCodeDef(OpCode = 0x50, Cycles = 2)] private void BVC() => Branch(!F.Overflow); [OpCodeDef(OpCode = 0x10, Cycles = 2)] private void BPL() => Branch(!F.Negative); [OpCodeDef(OpCode = 0x30, Cycles = 2)] private void BMI() => Branch(F.Negative); [OpCodeDef(OpCode = 0x81, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0x91, Mode = IndirectY, Cycles = 6)] [OpCodeDef(OpCode = 0x95, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0x99, Mode = AbsoluteY, Cycles = 5)] [OpCodeDef(OpCode = 0x9D, Mode = AbsoluteX, Cycles = 5)] [OpCodeDef(OpCode = 0x85, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x8D, Mode = Absolute, Cycles = 4)] private void STA() => AddressWrite(A); [OpCodeDef(OpCode = 0x96, Mode = ZeroPageY, Cycles = 4)] [OpCodeDef(OpCode = 0x86, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x8E, Mode = Absolute, Cycles = 4)] private void STX() => AddressWrite(X); [OpCodeDef(OpCode = 0x94, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0x84, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x8C, Mode = Absolute, Cycles = 4)] private void STY() => AddressWrite(Y); [OpCodeDef(OpCode = 0x18, Cycles = 2)] private void CLC() => F.Carry = false; [OpCodeDef(OpCode = 0x38, Cycles = 2)] private void SEC() => F.Carry = true; [OpCodeDef(OpCode = 0x58, Cycles = 2)] private void CLI() => F.InterruptsDisabled = false; [OpCodeDef(OpCode = 0x78, Cycles = 2)] private void SEI() => F.InterruptsDisabled = true; [OpCodeDef(OpCode = 0xB8, Cycles = 2)] private void CLV() => F.Overflow = false; [OpCodeDef(OpCode = 0xD8, Cycles = 2)] private void CLD() => F.DecimalMode = false; [OpCodeDef(OpCode = 0xF8, Cycles = 2)] private void SED() => F.DecimalMode = true; [OpCodeDef(OpCode = 0xEA, Cycles = 2)] [OpCodeDef(OpCode = 0x1A, Cycles = 2)] // Unofficial [OpCodeDef(OpCode = 0x3A, Cycles = 2)] // Unofficial [OpCodeDef(OpCode = 0x5A, Cycles = 2)] // Unofficial [OpCodeDef(OpCode = 0x7A, Cycles = 2)] // Unofficial [OpCodeDef(OpCode = 0xDA, Cycles = 2)] // Unofficial [OpCodeDef(OpCode = 0xFA, Cycles = 2)] // Unofficial private void NOP() { } [OpCodeDef(OpCode = 0xA1, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0xA5, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0xA9, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0xAD, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0xB1, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0xB5, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0xB9, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0xBD, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void LDA() => A = AddressRead(); [OpCodeDef(OpCode = 0xA0, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0xA4, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0xAC, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0xB4, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0xBC, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void LDY() => Y = AddressRead(); [OpCodeDef(OpCode = 0xA2, Mode = Immediate, Cycles = 2, RMW = true)] [OpCodeDef(OpCode = 0xA6, Mode = ZeroPage, Cycles = 3, RMW = true)] [OpCodeDef(OpCode = 0xAE, Mode = Absolute, Cycles = 4, RMW = true)] [OpCodeDef(OpCode = 0xB6, Mode = ZeroPageY, Cycles = 4, RMW = true)] [OpCodeDef(OpCode = 0xBE, Mode = AbsoluteY, Cycles = 4, PageBoundary = true, RMW = true)] private void LDX() => X = AddressRead(); [OpCodeDef(OpCode = 0x01, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0x05, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x09, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0x0D, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0x11, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0x15, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0x19, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0x1D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void ORA() => A |= AddressRead(); [OpCodeDef(OpCode = 0x21, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0x25, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x29, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0x2D, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0x31, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0x35, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0x39, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0x3D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void AND() => A &= AddressRead(); [OpCodeDef(OpCode = 0x41, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0x45, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x49, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0x4D, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0x51, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0x55, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0x59, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0x5D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void EOR() => A ^= AddressRead(); [OpCodeDef(OpCode = 0xE1, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0xE5, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x69, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0xE9, Mode = Immediate, Cycles = 2)] // Official duplicate of $69 [OpCodeDef(OpCode = 0xEB, Mode = Immediate, Cycles = 2)] // Unofficial duplicate of $69 [OpCodeDef(OpCode = 0xED, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0xF1, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0xF5, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0xF9, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0xFD, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void SBC() => ADCImpl((byte)~AddressRead()); [OpCodeDef(OpCode = 0x61, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0x65, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0x69, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0x6D, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0x71, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0x75, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0x79, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0x7D, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void ADC() => ADCImpl(AddressRead()); private void ADCImpl(uint val) { int nA = (sbyte)A + (sbyte)val + (sbyte)(F.Carry ? 1 : 0); F.Overflow = nA < -128 || nA > 127; F.Carry = (A + val + (F.Carry ? 1 : 0)) > 0xFF; A = (byte)(nA & 0xFF); } [OpCodeDef(OpCode = 0x00, Cycles = 7)] private void BRK() { NextByte(); Push(P | BreakSourceBit); F.InterruptsDisabled = true; PC = ReadByte(0xFFFE) | (ReadByte(0xFFFF) << 8); } [OpCodeDef(OpCode = 0xC1, Mode = IndirectX, Cycles = 6)] [OpCodeDef(OpCode = 0xC5, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0xC9, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0xCD, Mode = Absolute, Cycles = 4)] [OpCodeDef(OpCode = 0xD1, Mode = IndirectY, Cycles = 5, PageBoundary = true)] [OpCodeDef(OpCode = 0xD5, Mode = ZeroPageX, Cycles = 4)] [OpCodeDef(OpCode = 0xD9, Mode = AbsoluteY, Cycles = 4, PageBoundary = true)] [OpCodeDef(OpCode = 0xDD, Mode = AbsoluteX, Cycles = 4, PageBoundary = true)] private void CMP() => CMPImpl(A); [OpCodeDef(OpCode = 0xE0, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0xE4, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0xEC, Mode = Absolute, Cycles = 4)] private void CPX() => CMPImpl(X); [OpCodeDef(OpCode = 0xC0, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0xC4, Mode = ZeroPage, Cycles = 3)] [OpCodeDef(OpCode = 0xCC, Mode = Absolute, Cycles = 4)] private void CPY() => CMPImpl(Y); private void CMPImpl(uint reg) { long d = reg - (int)AddressRead(); F.Negative = (d & 0x80) > 0 && d != 0; F.Carry = d >= 0; F.Zero = d == 0; } [OpCodeDef(OpCode = 0x46, Mode = ZeroPage, Cycles = 5, RMW = true)] [OpCodeDef(OpCode = 0x4E, Mode = Absolute, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x56, Mode = ZeroPageX, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x5E, Mode = AbsoluteX, Cycles = 7, RMW = true)] [OpCodeDef(OpCode = 0x4A, Mode = Direct, Cycles = 2, RMW = true)] private void LSR() { uint D = AddressRead(); F.Carry = (D & 0x1) > 0; D >>= 1; _F(D); AddressWrite(D); } [OpCodeDef(OpCode = 0x06, Mode = ZeroPage, Cycles = 5, RMW = true)] [OpCodeDef(OpCode = 0x0E, Mode = Absolute, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x16, Mode = ZeroPageX, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x1E, Mode = AbsoluteX, Cycles = 7, RMW = true)] [OpCodeDef(OpCode = 0x0A, Mode = Direct, Cycles = 2, RMW = true)] private void ASL() { uint D = AddressRead(); F.Carry = (D & 0x80) > 0; D <<= 1; _F(D); AddressWrite(D); } [OpCodeDef(OpCode = 0x66, Mode = ZeroPage, Cycles = 5, RMW = true)] [OpCodeDef(OpCode = 0x6E, Mode = Absolute, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x76, Mode = ZeroPageX, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x7E, Mode = AbsoluteX, Cycles = 7, RMW = true)] [OpCodeDef(OpCode = 0x6A, Mode = Direct, Cycles = 2, RMW = true)] private void ROR() { uint D = AddressRead(); bool c = F.Carry; F.Carry = (D & 0x1) > 0; D >>= 1; if (c) D |= 0x80; _F(D); AddressWrite(D); } [OpCodeDef(OpCode = 0x26, Mode = ZeroPage, Cycles = 5, RMW = true)] [OpCodeDef(OpCode = 0x2E, Mode = Absolute, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x36, Mode = ZeroPageX, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0x3E, Mode = AbsoluteX, Cycles = 7, RMW = true)] [OpCodeDef(OpCode = 0x2A, Mode = Direct, Cycles = 2, RMW = true)] private void ROL() { uint D = AddressRead(); bool c = F.Carry; F.Carry = (D & 0x80) > 0; D <<= 1; if (c) D |= 0x1; _F(D); AddressWrite(D); } [OpCodeDef(OpCode = 0xE6, Mode = ZeroPage, Cycles = 5, RMW = true)] [OpCodeDef(OpCode = 0xEE, Mode = Absolute, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0xF6, Mode = ZeroPageX, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0xFE, Mode = AbsoluteX, Cycles = 7, RMW = true)] private void INC() { byte D = (byte)(AddressRead() + 1); _F(D); AddressWrite(D); } [OpCodeDef(OpCode = 0xC6, Mode = ZeroPage, Cycles = 5, RMW = true)] [OpCodeDef(OpCode = 0xCE, Mode = Absolute, Cycles = 3, RMW = true)] [OpCodeDef(OpCode = 0xD6, Mode = ZeroPageX, Cycles = 6, RMW = true)] [OpCodeDef(OpCode = 0xDE, Mode = AbsoluteX, Cycles = 7, RMW = true)] private void DEC() { byte D = (byte)(AddressRead() - 1); _F(D); AddressWrite(D); } #region Unofficial Opcodes [OpCodeDef(OpCode = 0x80, Cycles = 2)] [OpCodeDef(OpCode = 0x82, Cycles = 2)] [OpCodeDef(OpCode = 0x89, Cycles = 2)] [OpCodeDef(OpCode = 0xC2, Cycles = 2)] [OpCodeDef(OpCode = 0xE2, Cycles = 2)] private void SKB() => NextByte(); // Essentially a 2-byte NOP [OpCodeDef(OpCode = 0x0B, Mode = Immediate, Cycles = 2)] [OpCodeDef(OpCode = 0x2B, Mode = Immediate, Cycles = 2)] private void ANC() { A &= AddressRead(); F.Carry = F.Negative; } [OpCodeDef(OpCode = 0x4B, Mode = Immediate, Cycles = 2)] private void ALR() { A &= AddressRead(); F.Carry = (A & 0x1) > 0; A >>= 1; _F(A); } [OpCodeDef(OpCode = 0x6B, Mode = Immediate, Cycles = 2)] private void ARR() { A &= AddressRead(); bool c = F.Carry; F.Carry = (A & 0x1) > 0; A >>= 1; if (c) A |= 0x80; _F(A); } [OpCodeDef(OpCode = 0xAB, Mode = Immediate, Cycles = 2)] private void ATX() { // This opcode ORs the A register with #$EE, ANDs the result with an immediate // value, and then stores the result in both A and X. A |= ReadByte(0xEE); A &= AddressRead(); X = A; } #endregion } }