GBA.Unity/Assets/emulator/Timers.cs
2024-08-16 14:51:15 +08:00

265 lines
7.2 KiB
C#

using System;
using static OptimeGBA.Bits;
namespace OptimeGBA
{
public sealed class Timer
{
public uint Id = 0;
public Timers Timers;
public uint CounterVal = 0;
public uint ReloadVal = 0;
public long EnableCycles = 0;
public long Interval;
public static readonly int[] PrescalerShifts = {
0, 6, 8, 10
};
public static readonly uint[] PrescalerDivs = {
1, 64, 256, 1024
};
public uint PrescalerSel = 0;
public bool CountUpTiming = false;
public bool EnableIrq = false;
public bool Enabled = false;
public Timer(Timers timers, uint id)
{
Id = id;
Timers = timers;
}
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x00: // TMCNT_L B0
val = (byte)(CalculateCounter() >> 0);
break;
case 0x01: // TMCNT_L B1
val = (byte)(CalculateCounter() >> 8);
break;
case 0x02: // TMCNT_H B0
val |= (byte)(PrescalerSel & 0b11);
if (CountUpTiming) val = BitSet(val, 2);
if (EnableIrq) val = BitSet(val, 6);
if (Enabled) val = BitSet(val, 7);
break;
case 0x03: // TMCNT_H B1
break;
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
switch (addr)
{
case 0x00: // TMCNT_L B0
ReloadVal &= 0xFF00;
ReloadVal |= ((uint)val << 0);
RecalculateInterval();
break;
case 0x01: // TMCNT_L B1
ReloadVal &= 0x00FF;
ReloadVal |= ((uint)val << 8);
RecalculateInterval();
break;
case 0x02: // TMCNT_H B0
PrescalerSel = (uint)(val & 0b11);
CountUpTiming = BitTest(val, 2);
EnableIrq = BitTest(val, 6);
if (BitTest(val, 7))
{
Enable();
}
else
{
Disable();
}
RecalculateInterval();
break;
case 0x03: // TMCNT_H B1
break;
}
}
public SchedulerId GetSchedulerId()
{
uint id = (uint)SchedulerId.Timer90 + Id;
if (Timers.IsNds7) id += 4;
return (SchedulerId)id;
}
public void Enable()
{
if (!Enabled)
{
Reload();
Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles(), TimerOverflow);
EnableCycles = CalculateAlignedCurrentTicks();
// Debug.Log($"[Timer] {Id} Enable");
}
Enabled = true;
}
public uint CalculateCounter()
{
long diff = Timers.Scheduler.CurrentTicks - EnableCycles;
diff >>= PrescalerShifts[PrescalerSel];
if (Enabled)
{
return (ushort)(CounterVal + diff);
}
else
{
return (ushort)CounterVal;
}
}
public void Reschedule()
{
if (Enabled)
{
CounterVal = ReloadVal;
EnableCycles = CalculateAlignedCurrentTicks();
Timers.Scheduler.CancelEventsById(GetSchedulerId());
Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles(), TimerOverflow);
}
}
public void RecalculateInterval()
{
uint max = 0x10000;
uint diff = max - ReloadVal;
Interval = diff * PrescalerDivs[PrescalerSel];
}
public long CalculateOverflowCycles()
{
uint max = 0xFFFF;
uint diff = max - CounterVal;
// Align to the master clock
uint prescalerMod = diff % PrescalerDivs[PrescalerSel];
diff -= prescalerMod;
diff += PrescalerDivs[PrescalerSel];
return diff * PrescalerDivs[PrescalerSel];
}
public long CalculateAlignedCurrentTicks()
{
long ticks = Timers.Scheduler.CurrentTicks;
long prescalerMod = Timers.Scheduler.CurrentTicks % PrescalerDivs[PrescalerSel];
ticks -= prescalerMod;
ticks += PrescalerDivs[PrescalerSel];
return ticks;
}
public void Disable()
{
if (Enabled)
{
CounterVal = CalculateCounter();
Enabled = false;
Timers.Scheduler.CancelEventsById(GetSchedulerId());
}
}
public void Reload()
{
CounterVal = ReloadVal;
}
public void TimerOverflow(long cyclesLate)
{
// On overflow, refill with reload value
CounterVal = ReloadVal;
if (Id < 2 && Timers.GbaAudio != null)
{
Timers.GbaAudio.TimerOverflow(cyclesLate, Id);
}
if (EnableIrq)
{
Timers.HwControl.FlagInterrupt((uint)InterruptGba.Timer0Overflow + Id);
}
if (Id < 3)
{
if (Timers.T[Id + 1].CountUpTiming)
{
UnscheduledTimerIncrement();
}
}
if (!CountUpTiming)
{
Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles() - cyclesLate, TimerOverflow);
}
EnableCycles = CalculateAlignedCurrentTicks() - cyclesLate;
// Debug.Log($"[Timer] {Id} Overflow");
}
public void UnscheduledTimerIncrement()
{
CounterVal++;
if (CounterVal > 0xFFFF)
{
TimerOverflow(0);
}
}
}
public sealed class Timers
{
public HwControl HwControl;
public GbaAudio GbaAudio;
public bool NdsMode;
public bool IsNds7;
public Scheduler Scheduler;
public Timers(GbaAudio gbaAudio, HwControl hwControl, Scheduler scheduler, bool ndsMode, bool isNds7)
{
GbaAudio = gbaAudio;
HwControl = hwControl;
NdsMode = ndsMode;
IsNds7 = isNds7;
Scheduler = scheduler;
T = new Timer[4] {
new Timer(this, 0),
new Timer(this, 1),
new Timer(this, 2),
new Timer(this, 3),
};
}
public Timer[] T;
public byte ReadHwio8(uint addr)
{
return T[(addr >> 2) & 3].ReadHwio8(addr & 3);
}
public void WriteHwio8(uint addr, byte val)
{
T[(addr >> 2) & 3].WriteHwio8(addr & 3, val);
}
}
}