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