using System; using System.Runtime.InteropServices; using D = System.Diagnostics.Debug; namespace NXSNES { public class Ricoh5A22 : ICyclic { private IMemory mem; [Flags] public enum Status : int { C = 0x001, Z = 0x002, I = 0x004, D = 0x008, X = 0x010, M = 0x020, V = 0x040, N = 0x080, E = 0x100 }; /* FIXME: not portable to big-endian systems */ [StructLayout(LayoutKind.Explicit)] public struct Registers { [FieldOffset(0)] public ushort A; [FieldOffset(0)] public byte AL; [FieldOffset(1)] public byte AH; [FieldOffset(2)] public byte DB; /* Data Bank Register */ [FieldOffset(4)] public ushort X; [FieldOffset(4)] public byte XL; [FieldOffset(5)] public byte XH; [FieldOffset(6)] public ushort Y; [FieldOffset(6)] public byte YL; [FieldOffset(7)] public byte YH; [FieldOffset(8)] public ushort D; /* Direct Page Pointer */ [FieldOffset(8)] public byte DL; [FieldOffset(9)] public byte DH; [FieldOffset(10)] public ushort SP; /* Stack Pointer */ [FieldOffset(12)] public int PL; /* Program Counter Long */ [FieldOffset(12)] public ushort PC; /* Program Counter */ [FieldOffset(14)] public byte PB; /* Program Bank */ [FieldOffset(16)] public Status S; /* Status */ } public Registers R; private const long OneCycle = 6; private delegate Microcode Microcode(out long time); private int operand; public static class EmulationVectors { public const int IRQBRK = 0xfffe; public const int Reset = 0xfffc; public const int NMI = 0xfffa; public const int Abort = 0xfff8; public const int COP = 0xfff4; } public static class NativeVectors { public const int IRQ = 0xffee; public const int NMI = 0xffea; public const int Abort = 0xffe8; public const int BRK = 0xffe6; public const int COP = 0xffe5; } /* ---------------------------------------------------------------------------- * Processor subroutines * ------------------------------------------------------------------------- */ private void SetZN(byte x) { if (x == 0) R.S |= Status.Z; else R.S &= Status.Z; if ((x & 0x80) == 0x80) R.S |= Status.N; else R.S &= ~Status.N; } private void SetZN(ushort x) { if (x == 0) R.S |= Status.Z; else R.S &= ~Status.Z; if ((x & 0x8000) == 0x8000) R.S |= Status.N; else R.S &= ~Status.N; } /* ---------------------------------------------------------------------------- * Archetypes * ------------------------------------------------------------------------- */ /* Archetypes are generally calculations, such as ADC, LDA, and so * forth. In fact, every operation is at least a combination of an * addressing mode and an archetype on the 65816. Archetypes are * distinguished from microcode by the fact that microcode takes time * to execute, while archetypes are simply operations that the real * 65816 would perform as loading takes place. They are therefore best * viewed as "extensions" or "plugins" to an addressing mode instead of * actual microcode steps. */ private delegate void Archetype(); private void ADC() { ushort d = (ushort)operand; /* TODO: 8-bit version */ if ((R.S & Status.M) == 0) { d += (ushort)(R.S & Status.C); uint r = (uint)(R.A + d); if ((r & 0xffff0000) != 0) R.S |= Status.C; else R.S &= ~Status.C; if (((R.A & d & 0x8000) != 0 && (r & 0x8000) == 0) || ((R.A & 0x8000) == 0 && (d & 0x8000) == 0 && (r & 0x8000) != 0)) R.S |= Status.V; else R.S &= ~Status.V; R.A = (ushort)r; SetZN(R.A); } } private void LDA() { if ((R.S & Status.M) == 0) { R.A = (ushort)operand; SetZN(R.A); } else { R.AL = (byte)operand; SetZN(R.AL); } } /* ---------------------------------------------------------------------------- * Addressing modes * ------------------------------------------------------------------------- */ private int offset; private Microcode Immediate(Archetype arch) { Microcode cycle2 = null, cycle2a = null; cycle2 = (out long time) => { /* IDL */ operand = mem.GetByte(R.PC++, out time); if ((R.S & Status.M) == 0) return cycle2a; else { arch(); return null; } }; cycle2a = (out long time) => { /* IDH */ operand |= ((int)(mem.GetByte(R.PC++, out time)) << 8); arch(); return null; }; return cycle2; } private Microcode Direct(Archetype arch, bool write) { if (!write) { Microcode cycle2 = null, cycle2a = null; Microcode cycle3 = null, cycle3a = null; cycle2 = (out long time) => { /* DO */ offset = mem.GetByte(R.PC, out time); if (R.DL != 0) return cycle2a; /* one extra cycle */ else { R.PC++; return cycle3; } }; cycle2a = (out long time) => { /* IO */ R.PC++; time = OneCycle; return cycle3; }; cycle3 = (out long time) => { /* Data Low */ operand = mem.GetByte(R.D + offset, out time); if ((R.S & Status.M) == 0) return cycle3a; else { arch(); return null; } }; cycle3a = (out long time) => { /* Data High */ operand |= ((int)(mem.GetByte(R.D + offset + 1, out time)) << 8); arch(); return null; }; return cycle2; } else throw new Exception(); } /* ---------------------------------------------------------------------------- * Operation implementations * ------------------------------------------------------------------------- */ Microcode[] ops = new Microcode[256]; private void InitOps() { ops[0x69] = Immediate (ADC); ops[0xa5] = Direct (LDA, false); ops[0xa9] = Immediate (LDA); } /* ---------------------------------------------------------------------------- * Initialization and interrupts * ------------------------------------------------------------------------- */ public Ricoh5A22(IMemory m) { mem = m; InitOps(); } public void Reset() { long time; R.S = Status.E; R.PC = (ushort)(mem.GetByte(EmulationVectors.Reset, out time) | (mem.GetByte(EmulationVectors.Reset + 1, out time) << 8)); R.PB = 0; } /* ---------------------------------------------------------------------------- * The main run loop * ------------------------------------------------------------------------- */ private Microcode currentMicroOp = null; private long nextClock = 0; public long Step(long clock) { long time; if (clock < nextClock) return nextClock; if (currentMicroOp == null) /* Null means do an opcode fetch-and-decode, which always takes * (mem speed) cycles. */ currentMicroOp = ops[mem.GetByte(R.PC++, out time)]; else currentMicroOp = currentMicroOp(out time); nextClock = clock + time; return nextClock; } } }