mirror of https://github.com/Washi1337/Emux.git
262 lines
6.7 KiB
C#
262 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using Emux.GameBoy.Timer;
|
|
|
|
namespace Emux.GameBoy.Cpu
|
|
{
|
|
public class GameBoyCpu
|
|
{
|
|
public const int VerticalBlankIsr = 0x0040;
|
|
public const int LcdStatusIsr = 0x0048;
|
|
public const int TimerOverflowIsr = 0x0050;
|
|
public const int SerialLinkIsr = 0x0058;
|
|
public const int JoypadPressIsr = 0x0060;
|
|
public const double OfficialClockSpeed = 4.194304 * 1000000;
|
|
|
|
public event EventHandler Paused;
|
|
public event EventHandler Terminated;
|
|
|
|
private readonly GameBoy _device;
|
|
private ulong _ticks;
|
|
private readonly ManualResetEvent _continue = new ManualResetEvent(false);
|
|
private readonly ManualResetEvent _terminate = new ManualResetEvent(false);
|
|
private bool _break = true;
|
|
private bool _halt = false;
|
|
|
|
public GameBoyCpu(GameBoy device)
|
|
{
|
|
if (device == null)
|
|
throw new ArgumentNullException(nameof(device));
|
|
_device = device;
|
|
Registers = new RegisterBank();
|
|
Alu = new GameBoyAlu(Registers);
|
|
Breakpoints = new HashSet<ushort>();
|
|
|
|
new Thread(CpuLoop)
|
|
{
|
|
Name = "Z80CPULOOP",
|
|
IsBackground = true
|
|
}.Start();
|
|
}
|
|
|
|
public RegisterBank Registers
|
|
{
|
|
get;
|
|
}
|
|
|
|
internal GameBoyAlu Alu
|
|
{
|
|
get;
|
|
}
|
|
|
|
public ulong TickCount
|
|
{
|
|
get { return _ticks; }
|
|
}
|
|
|
|
public bool Running
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public ISet<ushort> Breakpoints
|
|
{
|
|
get;
|
|
}
|
|
|
|
private void CpuLoop()
|
|
{
|
|
bool enabled = true;
|
|
while (enabled)
|
|
{
|
|
if (WaitHandle.WaitAny(new WaitHandle[] { _continue, _terminate }) == 1)
|
|
{
|
|
enabled = false;
|
|
}
|
|
else
|
|
{
|
|
Running = true;
|
|
_continue.Reset();
|
|
|
|
do
|
|
{
|
|
CpuStep();
|
|
if (Breakpoints.Contains(Registers.PC))
|
|
_break = true;
|
|
} while (!_break);
|
|
|
|
Running = false;
|
|
OnPaused();
|
|
}
|
|
}
|
|
OnTerminated();
|
|
}
|
|
|
|
private void CpuStep()
|
|
{
|
|
Registers.IMESet = false;
|
|
|
|
int cycles;
|
|
if (_halt)
|
|
{
|
|
cycles = 4;
|
|
}
|
|
else
|
|
{
|
|
// Execute the next instruction.
|
|
var nextInstruction = ReadNextInstruction();
|
|
cycles = nextInstruction.Execute(_device);
|
|
}
|
|
|
|
// Check for interrupts.
|
|
bool interrupted = false;
|
|
if (Registers.IME && !Registers.IMESet
|
|
&& Registers.IE != InterruptFlags.None
|
|
&& Registers.IF != (InterruptFlags) 0xE0)
|
|
{
|
|
byte firedAndEnabled = (byte) (Registers.IE & Registers.IF);
|
|
for (int i = 0; i < 5 && !interrupted; i++)
|
|
{
|
|
if ((firedAndEnabled & (1 << i)) == (1 << i))
|
|
{
|
|
Registers.IF &= (InterruptFlags) ~(1u << i);
|
|
Registers.IME = false;
|
|
interrupted = true;
|
|
Rst((byte) (0x40 + (i << 3)));
|
|
cycles += 12;
|
|
_halt = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update cycle dependent components.
|
|
_device.Gpu.GpuStep(cycles);
|
|
_device.Timer.TimerStep(cycles);
|
|
|
|
_ticks = (_ticks + (ulong) cycles) & long.MaxValue;
|
|
}
|
|
|
|
public void Step()
|
|
{
|
|
_break = true;
|
|
_continue.Set();
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
_break = false;
|
|
_continue.Set();
|
|
}
|
|
|
|
public void Break()
|
|
{
|
|
_continue.Reset();
|
|
_break = true;
|
|
}
|
|
|
|
public void Terminate()
|
|
{
|
|
_continue.Reset();
|
|
_terminate.Set();
|
|
}
|
|
|
|
private Z80Instruction ReadNextInstruction()
|
|
{
|
|
ushort offset = Registers.PC;
|
|
byte code = _device.Memory.ReadByte(Registers.PC++);
|
|
|
|
var opcode = code != 0xCB
|
|
? Z80OpCodes.SingleByteOpCodes[code]
|
|
: Z80OpCodes.PrefixedOpCodes[_device.Memory.ReadByte(Registers.PC++)];
|
|
|
|
byte[] operand = _device.Memory.ReadBytes(Registers.PC, opcode.OperandLength);
|
|
Registers.PC += (ushort) operand.Length;
|
|
|
|
var instruction = new Z80Instruction(offset, opcode, operand);
|
|
return instruction;
|
|
}
|
|
|
|
internal void Push(ushort value)
|
|
{
|
|
Registers.SP -= 2;
|
|
_device.Memory.WriteUInt16(Registers.SP, value);
|
|
}
|
|
|
|
internal ushort Pop()
|
|
{
|
|
ushort value = _device.Memory.ReadUInt16(Registers.SP);
|
|
Registers.SP += 2;
|
|
return value;
|
|
}
|
|
|
|
internal void Jump(ushort address)
|
|
{
|
|
Registers.PC = address;
|
|
}
|
|
|
|
internal int JumpFlag(Z80OpCode opcode, ushort address, bool flag)
|
|
{
|
|
if (flag)
|
|
{
|
|
Jump(address);
|
|
return opcode.ClockCycles;
|
|
}
|
|
return opcode.ClockCyclesAlt;
|
|
}
|
|
|
|
internal void Call(ushort address)
|
|
{
|
|
Push(Registers.PC);
|
|
Registers.PC = address;
|
|
}
|
|
|
|
internal int CallFlag(Z80OpCode opcode, ushort address, bool flag)
|
|
{
|
|
if (flag)
|
|
{
|
|
Call(address);
|
|
return opcode.ClockCycles;
|
|
}
|
|
return opcode.ClockCyclesAlt;
|
|
}
|
|
|
|
internal void Ret()
|
|
{
|
|
Registers.PC = Pop();
|
|
}
|
|
|
|
internal int RetFlag(Z80OpCode opcode, bool flag)
|
|
{
|
|
if (flag)
|
|
{
|
|
Ret();
|
|
return opcode.ClockCycles;
|
|
}
|
|
return opcode.ClockCyclesAlt;
|
|
}
|
|
|
|
internal void Rst(byte isr)
|
|
{
|
|
Call(isr);
|
|
}
|
|
|
|
internal void Halt()
|
|
{
|
|
_halt = true;
|
|
}
|
|
|
|
protected virtual void OnPaused()
|
|
{
|
|
Paused?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
protected virtual void OnTerminated()
|
|
{
|
|
Terminated?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|