Essgee.Unity/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs

978 lines
21 KiB
C#
Raw Normal View History

2025-01-02 17:55:16 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Essgee.Exceptions;
using Essgee.Utilities;
using static Essgee.Emulation.Utilities;
namespace Essgee.Emulation.CPU
{
public partial class SM83 : ICPU
{
[Flags]
enum Flags : byte
{
UnusedBit0 = (1 << 0), /* (0) */
UnusedBit1 = (1 << 1), /* (0) */
UnusedBit2 = (1 << 2), /* (0) */
UnusedBit3 = (1 << 3), /* (0) */
Carry = (1 << 4), /* C */
HalfCarry = (1 << 5), /* H */
Subtract = (1 << 6), /* N */
Zero = (1 << 7) /* Z */
}
[Flags]
public enum InterruptSource : byte
{
VBlank = 0,
LCDCStatus = 1,
TimerOverflow = 2,
SerialIO = 3,
Keypad = 4
}
public delegate byte MemoryReadDelegate(ushort address);
public delegate void MemoryWriteDelegate(ushort address, byte value);
public delegate void RequestInterruptDelegate(InterruptSource source);
delegate void SimpleOpcodeDelegate(SM83 c);
protected MemoryReadDelegate memoryReadDelegate;
protected MemoryWriteDelegate memoryWriteDelegate;
[StateRequired]
protected Register af, bc, de, hl;
[StateRequired]
protected ushort sp, pc;
[StateRequired]
protected bool ime, imeDelay, halt, doHaltBug;
[StateRequired]
protected byte op;
[StateRequired]
protected int currentCycles;
string logFile;
int numLogEntries;
string[] logEntries;
public SM83(MemoryReadDelegate memoryRead, MemoryWriteDelegate memoryWrite)
{
af = bc = de = hl = new Register();
memoryReadDelegate = memoryRead;
memoryWriteDelegate = memoryWrite;
if (AppEnvironment.EnableSuperSlowCPULogger)
{
logFile = @"D:\Temp\Essgee\log-lr35902.txt";
numLogEntries = 0;
logEntries = new string[2000];
}
}
public virtual void Startup()
{
Reset();
if (memoryReadDelegate == null) throw new EmulationException("SM83: Memory read method is null");
if (memoryWriteDelegate == null) throw new EmulationException("SM83: Memory write method is null");
}
public virtual void Shutdown()
{
if (AppEnvironment.EnableSuperSlowCPULogger && logEntries != null)
{
System.IO.File.AppendAllText(logFile, string.Join("", logEntries.Take(numLogEntries)));
}
//
}
public virtual void Reset()
{
af.Word = bc.Word = de.Word = hl.Word = 0;
pc = 0;
sp = 0;
ime = imeDelay = halt = doHaltBug = false;
currentCycles = 0;
}
public int Step()
{
currentCycles = 0;
/* Handle delayed interrupt enable */
if (imeDelay)
{
ime = true;
imeDelay = false;
}
/* Check interrupts */
HandleInterrupts();
if (halt)
{
/* CPU halted */
currentCycles = 4;
}
else
{
if (AppEnvironment.EnableSuperSlowCPULogger && logEntries != null)
{
string disasm = string.Format("{0} | {1} | {2} | {3}\n", DisassembleOpcode(this, pc).PadRight(48), PrintRegisters(this), PrintFlags(this), PrintInterrupt(this));
logEntries[numLogEntries++] = disasm;
if (numLogEntries >= logEntries.Length)
{
System.IO.File.AppendAllText(logFile, string.Join("", logEntries));
numLogEntries = 0;
}
}
/* Do HALT bug */
if (doHaltBug)
{
pc--;
doHaltBug = false;
}
/* Fetch and execute opcode */
op = ReadMemory8(pc++);
switch (op)
{
case 0xCB: ExecuteOpCB(); break;
default: ExecuteOpcodeNoPrefix(op); break;
}
}
return currentCycles;
}
#region Opcode Execution and Cycle Management
private void ExecuteOpcodeNoPrefix(byte op)
{
opcodesNoPrefix[op](this);
currentCycles += CycleCounts.NoPrefix[op];
}
private void ExecuteOpCB()
{
byte cbOp = ReadMemory8(pc++);
opcodesPrefixCB[cbOp](this);
currentCycles += CycleCounts.PrefixCB[cbOp];
}
#endregion
#region Helpers (Flags, etc.)
public void SetStackPointer(ushort value)
{
sp = value;
}
public void SetProgramCounter(ushort value)
{
pc = value;
}
public void SetRegisterAF(ushort value)
{
af.Word = value;
}
public void SetRegisterBC(ushort value)
{
bc.Word = value;
}
public void SetRegisterDE(ushort value)
{
de.Word = value;
}
public void SetRegisterHL(ushort value)
{
hl.Word = value;
}
private void SetFlag(Flags flags)
{
af.Low |= (byte)flags;
}
private void ClearFlag(Flags flags)
{
af.Low &= (byte)~flags;
}
private void SetClearFlagConditional(Flags flags, bool condition)
{
if (condition)
af.Low |= (byte)flags;
else
af.Low &= (byte)~flags;
}
private bool IsFlagSet(Flags flags)
{
return (((Flags)af.Low & flags) == flags);
}
#endregion
#region Interrupt, Halt and Stop State Handling
public void RequestInterrupt(InterruptSource source)
{
memoryWriteDelegate(0xFF0F, (byte)(memoryReadDelegate(0xFF0F) | (byte)(1 << (byte)source)));
}
private void HandleInterrupts()
{
var intEnable = memoryReadDelegate(0xFFFF);
var intFlags = memoryReadDelegate(0xFF0F);
if ((intEnable & intFlags) != 0)
{
LeaveHaltState();
if (ime)
{
if (ServiceInterrupt(InterruptSource.VBlank, intEnable, intFlags)) return;
if (ServiceInterrupt(InterruptSource.LCDCStatus, intEnable, intFlags)) return;
if (ServiceInterrupt(InterruptSource.TimerOverflow, intEnable, intFlags)) return;
if (ServiceInterrupt(InterruptSource.SerialIO, intEnable, intFlags)) return;
if (ServiceInterrupt(InterruptSource.Keypad, intEnable, intFlags)) return;
}
}
}
private bool ServiceInterrupt(InterruptSource intSource, byte intEnable, byte intFlags)
{
var intSourceBit = (byte)(1 << (byte)intSource);
if (((intEnable & intSourceBit) == intSourceBit) && ((intFlags & intSourceBit) == intSourceBit))
{
ime = false;
currentCycles += 20;
return RestartFromInterrupt(intSource);
}
return false;
}
protected virtual void EnterHaltState()
{
if (ime)
{
halt = true;
pc--;
}
else
{
if ((memoryReadDelegate(0xFF0F) & memoryReadDelegate(0xFFFF) & 0x1F) != 0)
doHaltBug = true;
else
halt = true;
}
}
private void LeaveHaltState()
{
if (halt)
{
halt = false;
if (ime)
pc++;
}
}
#endregion
#region Memory Access Functions
private byte ReadMemory8(ushort address)
{
return memoryReadDelegate(address);
}
private void WriteMemory8(ushort address, byte value)
{
memoryWriteDelegate(address, value);
}
private ushort ReadMemory16(ushort address)
{
return (ushort)((memoryReadDelegate((ushort)(address + 1)) << 8) | memoryReadDelegate(address));
}
private void WriteMemory16(ushort address, ushort value)
{
memoryWriteDelegate(address, (byte)(value & 0xFF));
memoryWriteDelegate((ushort)(address + 1), (byte)(value >> 8));
}
#endregion
#region Opcodes: 8-Bit Load Group
protected void LoadRegisterFromMemory8(ref byte register, ushort address, bool specialRegs)
{
LoadRegister8(ref register, ReadMemory8(address), specialRegs);
}
protected void LoadRegisterImmediate8(ref byte register, bool specialRegs)
{
LoadRegister8(ref register, ReadMemory8(pc++), specialRegs);
}
protected void LoadRegister8(ref byte register, byte value, bool specialRegs)
{
register = value;
}
protected void LoadMemory8(ushort address, byte value)
{
WriteMemory8(address, value);
}
#endregion
#region Opcodes: 16-Bit Load Group
protected void LoadRegisterImmediate16(ref ushort register)
{
LoadRegister16(ref register, ReadMemory16(pc));
pc += 2;
}
protected void LoadRegister16(ref ushort register, ushort value)
{
register = value;
}
protected void Push(Register register)
{
WriteMemory8(--sp, register.High);
WriteMemory8(--sp, register.Low);
}
protected void Pop(ref Register register)
{
register.Low = ReadMemory8(sp++);
register.High = ReadMemory8(sp++);
}
#endregion
#region Opcodes: 8-Bit Arithmetic Group
protected void Add8(byte operand, bool withCarry)
{
int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0));
int result = (af.High + operandWithCarry);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
ClearFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0));
SetClearFlagConditional(Flags.Carry, (result > 0xFF));
af.High = (byte)result;
}
protected void Subtract8(byte operand, bool withCarry)
{
int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0));
int result = (af.High - operandWithCarry);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
SetFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0));
SetClearFlagConditional(Flags.Carry, (af.High < operandWithCarry));
af.High = (byte)result;
}
protected void And8(byte operand)
{
int result = (af.High & operand);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
ClearFlag(Flags.Subtract);
SetFlag(Flags.HalfCarry);
ClearFlag(Flags.Carry);
af.High = (byte)result;
}
protected void Or8(byte operand)
{
int result = (af.High | operand);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
ClearFlag(Flags.Carry);
af.High = (byte)result;
}
protected void Xor8(byte operand)
{
int result = (af.High ^ operand);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
ClearFlag(Flags.Carry);
af.High = (byte)result;
}
protected void Cp8(byte operand)
{
int result = (af.High - operand);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
SetFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0));
SetClearFlagConditional(Flags.Carry, (af.High < operand));
}
protected void Increment8(ref byte register)
{
byte result = (byte)(register + 1);
SetClearFlagConditional(Flags.Zero, (result == 0x00));
ClearFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x0F));
// C
register = result;
}
protected void IncrementMemory8(ushort address)
{
byte value = ReadMemory8(address);
Increment8(ref value);
WriteMemory8(address, value);
}
protected void Decrement8(ref byte register)
{
byte result = (byte)(register - 1);
SetClearFlagConditional(Flags.Zero, (result == 0x00));
SetFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x00));
// C
register = result;
}
protected void DecrementMemory8(ushort address)
{
byte value = ReadMemory8(address);
Decrement8(ref value);
WriteMemory8(address, value);
}
#endregion
#region Opcodes: General-Purpose Arithmetic and CPU Control Group
protected void DecimalAdjustAccumulator()
{
int value = af.High;
if (!IsFlagSet(Flags.Subtract))
{
if (IsFlagSet(Flags.HalfCarry) || ((value & 0x0F) > 9))
value += 0x06;
if (IsFlagSet(Flags.Carry) || (value > 0x9F))
value += 0x60;
}
else
{
if (IsFlagSet(Flags.HalfCarry))
value = (value - 0x06) & 0xFF;
if (IsFlagSet(Flags.Carry))
value -= 0x60;
}
ClearFlag(Flags.HalfCarry);
ClearFlag(Flags.Zero);
if ((value & 0x100) != 0) SetFlag(Flags.Carry);
value &= 0xFF;
if (value == 0) SetFlag(Flags.Zero);
af.High = (byte)value;
}
protected void Negate()
{
int result = (0 - af.High);
SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00));
SetFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, ((0 - (af.High & 0x0F)) < 0));
SetClearFlagConditional(Flags.Carry, (af.High != 0x00));
af.High = (byte)result;
}
protected void EnableInterrupts()
{
ime = false;
imeDelay = true;
}
protected void DisableInterrupts()
{
ime = false;
}
#endregion
#region Opcodes: 16-Bit Arithmetic Group
protected void Add16(ref Register dest, ushort operand)
{
int operandWithCarry = (short)operand;
int result = (dest.Word + operandWithCarry);
// Z
ClearFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, (((dest.Word & 0x0FFF) + (operandWithCarry & 0x0FFF)) > 0x0FFF));
SetClearFlagConditional(Flags.Carry, (((dest.Word & 0xFFFF) + (operandWithCarry & 0xFFFF)) > 0xFFFF));
dest.Word = (ushort)result;
}
protected void Increment16(ref ushort register)
{
register++;
}
protected void Decrement16(ref ushort register)
{
register--;
}
#endregion
#region Opcodes: Rotate and Shift Group
protected byte RotateLeft(ushort address)
{
byte value = ReadMemory8(address);
RotateLeft(ref value);
WriteMemory8(address, value);
return value;
}
protected void RotateLeft(ref byte value)
{
bool isCarrySet = IsFlagSet(Flags.Carry);
bool isMsbSet = IsBitSet(value, 7);
value <<= 1;
if (isCarrySet) SetBit(ref value, 0);
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isMsbSet);
}
protected byte RotateLeftCircular(ushort address)
{
byte value = ReadMemory8(address);
RotateLeftCircular(ref value);
WriteMemory8(address, value);
return value;
}
protected void RotateLeftCircular(ref byte value)
{
bool isMsbSet = IsBitSet(value, 7);
value <<= 1;
if (isMsbSet) SetBit(ref value, 0);
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isMsbSet);
}
protected byte RotateRight(ushort address)
{
byte value = ReadMemory8(address);
RotateRight(ref value);
WriteMemory8(address, value);
return value;
}
protected void RotateRight(ref byte value)
{
bool isCarrySet = IsFlagSet(Flags.Carry);
bool isLsbSet = IsBitSet(value, 0);
value >>= 1;
if (isCarrySet) SetBit(ref value, 7);
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isLsbSet);
}
protected byte RotateRightCircular(ushort address)
{
byte value = ReadMemory8(address);
RotateRightCircular(ref value);
WriteMemory8(address, value);
return value;
}
protected void RotateRightCircular(ref byte value)
{
bool isLsbSet = IsBitSet(value, 0);
value >>= 1;
if (isLsbSet) SetBit(ref value, 7);
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isLsbSet);
}
protected void RotateLeftAccumulator()
{
bool isCarrySet = IsFlagSet(Flags.Carry);
bool isMsbSet = IsBitSet(af.High, 7);
af.High <<= 1;
if (isCarrySet) SetBit(ref af.High, 0);
ClearFlag(Flags.Zero);
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isMsbSet);
}
protected void RotateLeftAccumulatorCircular()
{
bool isMsbSet = IsBitSet(af.High, 7);
af.High <<= 1;
if (isMsbSet) SetBit(ref af.High, 0);
ClearFlag(Flags.Zero);
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isMsbSet);
}
protected void RotateRightAccumulator()
{
bool isCarrySet = IsFlagSet(Flags.Carry);
bool isLsbSet = IsBitSet(af.High, 0);
af.High >>= 1;
if (isCarrySet) SetBit(ref af.High, 7);
ClearFlag(Flags.Zero);
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isLsbSet);
}
protected void RotateRightAccumulatorCircular()
{
bool isLsbSet = IsBitSet(af.High, 0);
af.High >>= 1;
if (isLsbSet) SetBit(ref af.High, 7);
ClearFlag(Flags.Zero);
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isLsbSet);
}
protected byte ShiftLeftArithmetic(ushort address)
{
byte value = ReadMemory8(address);
ShiftLeftArithmetic(ref value);
WriteMemory8(address, value);
return value;
}
protected void ShiftLeftArithmetic(ref byte value)
{
bool isMsbSet = IsBitSet(value, 7);
value <<= 1;
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isMsbSet);
}
protected byte ShiftRightArithmetic(ushort address)
{
byte value = ReadMemory8(address);
ShiftRightArithmetic(ref value);
WriteMemory8(address, value);
return value;
}
protected void ShiftRightArithmetic(ref byte value)
{
bool isLsbSet = IsBitSet(value, 0);
bool isMsbSet = IsBitSet(value, 7);
value >>= 1;
if (isMsbSet) SetBit(ref value, 7);
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isLsbSet);
}
protected byte ShiftRightLogical(ushort address)
{
byte value = ReadMemory8(address);
ShiftRightLogical(ref value);
WriteMemory8(address, value);
return value;
}
protected void ShiftRightLogical(ref byte value)
{
bool isLsbSet = IsBitSet(value, 0);
value >>= 1;
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
SetClearFlagConditional(Flags.Carry, isLsbSet);
}
#endregion
#region Opcodes: Bit Set, Reset and Test Group
protected byte SetBit(ushort address, int bit)
{
byte value = ReadMemory8(address);
SetBit(ref value, bit);
WriteMemory8(address, value);
return value;
}
protected void SetBit(ref byte value, int bit)
{
value |= (byte)(1 << bit);
}
protected byte ResetBit(ushort address, int bit)
{
byte value = ReadMemory8(address);
ResetBit(ref value, bit);
WriteMemory8(address, value);
return value;
}
protected void ResetBit(ref byte value, int bit)
{
value &= (byte)~(1 << bit);
}
protected void TestBit(ushort address, int bit)
{
byte value = ReadMemory8(address);
TestBit(value, bit);
}
protected void TestBit(byte value, int bit)
{
bool isBitSet = ((value & (1 << bit)) != 0);
SetClearFlagConditional(Flags.Zero, !isBitSet);
ClearFlag(Flags.Subtract);
SetFlag(Flags.HalfCarry);
// C
}
#endregion
#region Opcodes: Jump Group
protected void Jump8()
{
pc += (ushort)(((sbyte)ReadMemory8(pc)) + 1);
}
protected void JumpConditional8(bool condition)
{
if (condition)
{
Jump8();
currentCycles += CycleCounts.AdditionalJumpCond8Taken;
}
else
pc++;
}
protected void JumpConditional16(bool condition)
{
if (condition)
pc = ReadMemory16(pc);
else
pc += 2;
}
#endregion
#region Opcodes: Call and Return Group
protected void Call16()
{
WriteMemory8(--sp, (byte)((pc + 2) >> 8));
WriteMemory8(--sp, (byte)((pc + 2) & 0xFF));
pc = ReadMemory16(pc);
}
protected void CallConditional16(bool condition)
{
if (condition)
{
Call16();
currentCycles += CycleCounts.AdditionalCallCondTaken;
}
else
pc += 2;
}
protected void Return()
{
pc = ReadMemory16(sp);
sp += 2;
}
protected void ReturnConditional(bool condition)
{
if (condition)
{
Return();
currentCycles += CycleCounts.AdditionalRetCondTaken;
}
}
protected void Restart(ushort address)
{
WriteMemory8(--sp, (byte)(pc >> 8));
WriteMemory8(--sp, (byte)(pc & 0xFF));
pc = address;
}
protected bool RestartFromInterrupt(InterruptSource intSource)
{
// https://github.com/Gekkio/mooneye-gb/blob/ca7ff30/tests/acceptance/interrupts/ie_push.s
var address = (ushort)(0x0040 + (byte)((int)intSource << 3));
var intSourceBit = (byte)(1 << (byte)intSource);
WriteMemory8(--sp, (byte)(pc >> 8));
var newIntEnable = memoryReadDelegate(0xFFFF);
var continueRestart = (newIntEnable & intSourceBit) != 0;
WriteMemory8(--sp, (byte)(pc & 0xFF));
if (continueRestart)
{
pc = address;
memoryWriteDelegate(0xFF0F, (byte)(memoryReadDelegate(0xFF0F) & (byte)~intSourceBit));
}
else
pc = 0x0000;
return continueRestart;
}
#endregion
#region Opcodes: SM83-specific Opcodes
protected void PopAF()
{
af.Low = (byte)(ReadMemory8(sp++) & 0xF0);
af.High = ReadMemory8(sp++);
}
protected void Swap(ushort address)
{
byte value = ReadMemory8(address);
Swap(ref value);
WriteMemory8(address, value);
}
protected void Swap(ref byte value)
{
value = (byte)((value & 0xF0) >> 4 | (value & 0x0F) << 4);
SetClearFlagConditional(Flags.Zero, (value == 0x00));
ClearFlag(Flags.Subtract);
ClearFlag(Flags.HalfCarry);
ClearFlag(Flags.Carry);
}
protected virtual void Stop()
{
pc++;
}
private void AddSPNN()
{
byte offset = ReadMemory8(pc++);
ClearFlag(Flags.Zero);
ClearFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, (((sp & 0x0F) + (offset & 0x0F)) > 0x0F));
SetClearFlagConditional(Flags.Carry, (((sp & 0xFF) + (byte)(offset & 0xFF)) > 0xFF));
sp = (ushort)(sp + (sbyte)offset);
}
private void LoadHLSPNN()
{
byte offset = ReadMemory8(pc++);
ClearFlag(Flags.Zero);
ClearFlag(Flags.Subtract);
SetClearFlagConditional(Flags.HalfCarry, (((sp & 0x0F) + (offset & 0x0F)) > 0x0F));
SetClearFlagConditional(Flags.Carry, (((sp & 0xFF) + (byte)(offset & 0xFF)) > 0xFF));
hl.Word = (ushort)(sp + (sbyte)offset);
}
#endregion
}
}