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;
}
}
}