GBA.Unity/Assets/Iris/Iris.GBA/Timer.cs
2024-08-14 16:02:39 +08:00

354 lines
13 KiB
C#

using System.Runtime.CompilerServices;
namespace Iris.GBA
{
internal sealed class Timer
{
internal enum Register
{
TM0CNT_L,
TM0CNT_H,
TM1CNT_L,
TM1CNT_H,
TM2CNT_L,
TM2CNT_H,
TM3CNT_L,
TM3CNT_H
}
private readonly Common.Scheduler _scheduler;
private InterruptControl _interruptControl;
private struct Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt)
{
internal UInt16 _counter;
internal UInt16 _reload;
internal UInt16 _control;
internal UInt64 _cycleCount; // only used in non-cascading mode
internal bool _running;
internal readonly GBA_System.TaskId _startTaskId = startTaskId;
internal readonly GBA_System.TaskId _handleOverflowTaskId = handleOverflowTaskId;
internal readonly InterruptControl.Interrupt _interrupt = interrupt;
}
private readonly Channel[] _channels;
internal Timer(Common.Scheduler scheduler)
{
_scheduler = scheduler;
_channels =
[
new(GBA_System.TaskId.StartTimer_Channel0, GBA_System.TaskId.HandleTimerOverflow_Channel0, InterruptControl.Interrupt.Timer0),
new(GBA_System.TaskId.StartTimer_Channel1, GBA_System.TaskId.HandleTimerOverflow_Channel1, InterruptControl.Interrupt.Timer1),
new(GBA_System.TaskId.StartTimer_Channel2, GBA_System.TaskId.HandleTimerOverflow_Channel2, InterruptControl.Interrupt.Timer2),
new(GBA_System.TaskId.StartTimer_Channel3, GBA_System.TaskId.HandleTimerOverflow_Channel3, InterruptControl.Interrupt.Timer3)
];
for (int channelIndex = 0; channelIndex < 4; ++channelIndex)
{
int channelIndexCopy = channelIndex;
_scheduler.RegisterTask((int)_channels[channelIndex]._startTaskId, cycleCountDelay => Start(channelIndexCopy, cycleCountDelay));
_scheduler.RegisterTask((int)_channels[channelIndex]._handleOverflowTaskId, cycleCountDelay => HandleOverflow(channelIndexCopy, cycleCountDelay));
}
}
internal void Initialize(InterruptControl interruptControl)
{
_interruptControl = interruptControl;
}
internal void ResetState()
{
foreach (ref Channel channel in _channels.AsSpan())
{
channel._counter = 0;
channel._reload = 0;
channel._control = 0;
channel._cycleCount = 0;
channel._running = false;
}
}
internal void LoadState(BinaryReader reader)
{
foreach (ref Channel channel in _channels.AsSpan())
{
channel._counter = reader.ReadUInt16();
channel._reload = reader.ReadUInt16();
channel._control = reader.ReadUInt16();
channel._cycleCount = reader.ReadUInt64();
channel._running = reader.ReadBoolean();
}
}
internal void SaveState(BinaryWriter writer)
{
foreach (Channel channel in _channels)
{
writer.Write(channel._counter);
writer.Write(channel._reload);
writer.Write(channel._control);
writer.Write(channel._cycleCount);
writer.Write(channel._running);
}
}
internal UInt16 ReadRegister(Register register)
{
UInt16 ReadCounter(int channelIndex)
{
ref Channel channel = ref _channels[channelIndex];
if (channel._running && (((channel._control & 0x0004) == 0) || (channelIndex == 0)))
UpdateCounter(ref channel, channel._control);
return channel._counter;
}
return register switch
{
Register.TM0CNT_L => ReadCounter(0),
Register.TM0CNT_H => _channels[0]._control,
Register.TM1CNT_L => ReadCounter(1),
Register.TM1CNT_H => _channels[1]._control,
Register.TM2CNT_L => ReadCounter(2),
Register.TM2CNT_H => _channels[2]._control,
Register.TM3CNT_L => ReadCounter(3),
Register.TM3CNT_H => _channels[3]._control,
// should never happen
_ => throw new Exception("Iris.GBA.Timer: Register read error"),
};
}
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
{
void WriteReload(ref Channel channel)
{
UInt16 reload = channel._reload;
Memory.WriteRegisterHelper(ref reload, value, mode);
channel._reload = reload;
}
void WriteControl(int channelIndex)
{
ref Channel channel = ref _channels[channelIndex];
UInt16 previousControl = channel._control;
UInt16 newControl = channel._control;
Memory.WriteRegisterHelper(ref newControl, value, mode);
channel._control = newControl;
CheckControl(ref channel, channelIndex, previousControl, newControl);
}
switch (register)
{
case Register.TM0CNT_L:
WriteReload(ref _channels[0]);
break;
case Register.TM0CNT_H:
WriteControl(0);
break;
case Register.TM1CNT_L:
WriteReload(ref _channels[1]);
break;
case Register.TM1CNT_H:
WriteControl(1);
break;
case Register.TM2CNT_L:
WriteReload(ref _channels[2]);
break;
case Register.TM2CNT_H:
WriteControl(2);
break;
case Register.TM3CNT_L:
WriteReload(ref _channels[3]);
break;
case Register.TM3CNT_H:
WriteControl(3);
break;
// should never happen
default:
throw new Exception("Iris.GBA.Timer: Register write error");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckControl(ref Channel channel, int channelIndex, UInt16 previousControl, UInt16 newControl)
{
if ((previousControl & 0x0080) == 0)
{
if ((newControl & 0x0080) == 0x0080)
_scheduler.ScheduleTask((int)channel._startTaskId, 2);
}
else
{
if ((newControl & 0x0080) == 0)
{
if (channel._running)
{
if (((previousControl & 0x0004) == 0) || (channelIndex == 0))
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
}
channel._running = false;
}
else
{
_scheduler.CancelTask((int)channel._startTaskId);
}
}
else
{
if (channel._running)
{
if (channelIndex == 0)
{
if ((previousControl & 0b11) != (newControl & 0b11))
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
}
}
else
{
if ((previousControl & 0x0004) == 0)
{
if ((newControl & 0x0004) == 0)
{
if ((previousControl & 0b11) != (newControl & 0b11))
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
}
}
else
{
UpdateCounter(ref channel, previousControl);
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
}
}
else
{
if ((newControl & 0x0004) == 0)
{
channel._cycleCount = _scheduler.GetCycleCounter();
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
}
}
}
}
}
}
}
private void UpdateCounter(ref Channel channel, UInt16 control)
{
UInt64 currentCycleCount = _scheduler.GetCycleCounter();
UInt64 cycleCountDelta = currentCycleCount - channel._cycleCount;
UInt64 prescaler = GetPrescaler(control);
channel._counter += (UInt16)(cycleCountDelta / prescaler);
channel._cycleCount = currentCycleCount - (UInt16)(cycleCountDelta % prescaler);
}
private void Start(int channelIndex, UInt64 cycleCountDelay)
{
ref Channel channel = ref _channels[channelIndex];
channel._counter = channel._reload;
channel._running = true;
if (((channel._control & 0x0004) == 0) || (channelIndex == 0))
{
channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay;
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay);
}
}
private void HandleOverflow(int channelIndex, UInt64 cycleCountDelay)
{
ref Channel channel = ref _channels[channelIndex];
channel._counter = channel._reload;
channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay;
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay);
if ((channel._control & 0x0040) == 0x0040)
_interruptControl.RequestInterrupt(channel._interrupt);
CascadeOverflow(channelIndex);
}
private void CascadeOverflow(int channelIndex)
{
if (channelIndex == 3)
return;
++channelIndex;
ref Channel channel = ref _channels[channelIndex];
if (!channel._running || ((channel._control & 0x0004) != 0x0004))
return;
if (channel._counter == 0xffff)
{
channel._counter = channel._reload;
if ((channel._control & 0x0040) == 0x0040)
_interruptControl.RequestInterrupt(channel._interrupt);
CascadeOverflow(channelIndex);
}
else
{
++channel._counter;
}
}
private static UInt64 ComputeCycleCountUntilOverflow(ref readonly Channel channel)
{
return (0x1_0000u - channel._counter) * GetPrescaler(channel._control);
}
private static UInt64 GetPrescaler(UInt16 control)
{
return (control & 0b11) switch
{
0b00 => 1,
0b01 => 64,
0b10 => 256,
0b11 => 1024,
_ => 0, // cannot happen
};
}
}
}