480 lines
18 KiB
C#
480 lines
18 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Iris.GBA
|
|
{
|
|
internal sealed class DMA
|
|
{
|
|
internal enum Register
|
|
{
|
|
DMA0SAD_L,
|
|
DMA0SAD_H,
|
|
|
|
DMA0DAD_L,
|
|
DMA0DAD_H,
|
|
|
|
DMA0CNT_L,
|
|
DMA0CNT_H,
|
|
|
|
DMA1SAD_L,
|
|
DMA1SAD_H,
|
|
|
|
DMA1DAD_L,
|
|
DMA1DAD_H,
|
|
|
|
DMA1CNT_L,
|
|
DMA1CNT_H,
|
|
|
|
DMA2SAD_L,
|
|
DMA2SAD_H,
|
|
|
|
DMA2DAD_L,
|
|
DMA2DAD_H,
|
|
|
|
DMA2CNT_L,
|
|
DMA2CNT_H,
|
|
|
|
DMA3SAD_L,
|
|
DMA3SAD_H,
|
|
|
|
DMA3DAD_L,
|
|
DMA3DAD_H,
|
|
|
|
DMA3CNT_L,
|
|
DMA3CNT_H
|
|
}
|
|
|
|
internal enum StartTiming
|
|
{
|
|
Immediate = 0b00,
|
|
VBlank = 0b01,
|
|
HBlank = 0b10,
|
|
Special = 0b11
|
|
}
|
|
|
|
private readonly Common.Scheduler _scheduler;
|
|
|
|
private InterruptControl _interruptControl;
|
|
private Memory _memory;
|
|
|
|
private struct Channel
|
|
{
|
|
internal UInt32 _source;
|
|
internal UInt32 _sourceReload;
|
|
internal UInt32 _destination;
|
|
internal UInt32 _destinationReload;
|
|
internal UInt32 _length;
|
|
internal UInt16 _lengthReload;
|
|
internal UInt16 _control;
|
|
internal bool _running;
|
|
}
|
|
|
|
private Channel _channel0;
|
|
private Channel _channel1;
|
|
private Channel _channel2;
|
|
private Channel _channel3;
|
|
|
|
private const UInt32 MaxLengthChannel0 = 0x4000;
|
|
private const UInt32 MaxLengthChannel1 = 0x4000;
|
|
private const UInt32 MaxLengthChannel2 = 0x4000;
|
|
private const UInt32 MaxLengthChannel3 = 0x1_0000;
|
|
|
|
internal DMA(Common.Scheduler scheduler)
|
|
{
|
|
_scheduler = scheduler;
|
|
|
|
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel0, _ => Start(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0));
|
|
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel1, _ => Start(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1));
|
|
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel2, _ => Start(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2));
|
|
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel3, _ => Start(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3));
|
|
}
|
|
|
|
internal void Initialize(InterruptControl interruptControl, Memory memory)
|
|
{
|
|
_interruptControl = interruptControl;
|
|
_memory = memory;
|
|
}
|
|
|
|
internal void ResetState()
|
|
{
|
|
_channel0 = default;
|
|
_channel1 = default;
|
|
_channel2 = default;
|
|
_channel3 = default;
|
|
}
|
|
|
|
internal void LoadState(BinaryReader reader)
|
|
{
|
|
void LoadChannel(ref Channel channel)
|
|
{
|
|
channel._source = reader.ReadUInt32();
|
|
channel._sourceReload = reader.ReadUInt32();
|
|
channel._destination = reader.ReadUInt32();
|
|
channel._destinationReload = reader.ReadUInt32();
|
|
channel._length = reader.ReadUInt32();
|
|
channel._lengthReload = reader.ReadUInt16();
|
|
channel._control = reader.ReadUInt16();
|
|
channel._running = reader.ReadBoolean();
|
|
}
|
|
|
|
LoadChannel(ref _channel0);
|
|
LoadChannel(ref _channel1);
|
|
LoadChannel(ref _channel2);
|
|
LoadChannel(ref _channel3);
|
|
}
|
|
|
|
internal void SaveState(BinaryWriter writer)
|
|
{
|
|
void SaveChannel(Channel channel)
|
|
{
|
|
writer.Write(channel._source);
|
|
writer.Write(channel._sourceReload);
|
|
writer.Write(channel._destination);
|
|
writer.Write(channel._destinationReload);
|
|
writer.Write(channel._length);
|
|
writer.Write(channel._lengthReload);
|
|
writer.Write(channel._control);
|
|
writer.Write(channel._running);
|
|
}
|
|
|
|
SaveChannel(_channel0);
|
|
SaveChannel(_channel1);
|
|
SaveChannel(_channel2);
|
|
SaveChannel(_channel3);
|
|
}
|
|
|
|
internal UInt16 ReadRegister(Register register)
|
|
{
|
|
return register switch
|
|
{
|
|
Register.DMA0CNT_H => _channel0._control,
|
|
Register.DMA1CNT_H => _channel1._control,
|
|
Register.DMA2CNT_H => _channel2._control,
|
|
Register.DMA3CNT_H => _channel3._control,
|
|
|
|
// should never happen
|
|
_ => throw new Exception("Iris.GBA.DMA: Register read error"),
|
|
};
|
|
}
|
|
|
|
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
|
{
|
|
void WriteSourceReload_Low(ref Channel channel)
|
|
{
|
|
UInt16 low = (UInt16)channel._sourceReload;
|
|
Memory.WriteRegisterHelper(ref low, value, mode);
|
|
channel._sourceReload = (channel._sourceReload & 0xffff_0000) | low;
|
|
}
|
|
|
|
void WriteSourceReload_High(ref Channel channel, UInt16 mask)
|
|
{
|
|
UInt16 high = (UInt16)(channel._sourceReload >> 16);
|
|
Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode);
|
|
channel._sourceReload = (channel._sourceReload & 0x0000_ffff) | (UInt32)(high << 16);
|
|
}
|
|
|
|
void WriteDestinationReload_Low(ref Channel channel)
|
|
{
|
|
UInt16 low = (UInt16)channel._destinationReload;
|
|
Memory.WriteRegisterHelper(ref low, value, mode);
|
|
channel._destinationReload = (channel._destinationReload & 0xffff_0000) | low;
|
|
}
|
|
|
|
void WriteDestinationReload_High(ref Channel channel, UInt16 mask)
|
|
{
|
|
UInt16 high = (UInt16)(channel._destinationReload >> 16);
|
|
Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode);
|
|
channel._destinationReload = (channel._destinationReload & 0x0000_ffff) | (UInt32)(high << 16);
|
|
}
|
|
|
|
void WriteLengthReload(ref Channel channel)
|
|
{
|
|
UInt16 reload = channel._lengthReload;
|
|
Memory.WriteRegisterHelper(ref reload, value, mode);
|
|
channel._lengthReload = reload;
|
|
}
|
|
|
|
void WriteControl(ref Channel channel, GBA_System.TaskId startTaskId)
|
|
{
|
|
UInt16 previousControl = channel._control;
|
|
|
|
UInt16 newControl = channel._control;
|
|
Memory.WriteRegisterHelper(ref newControl, value, mode);
|
|
channel._control = newControl;
|
|
|
|
if ((previousControl & 0x8000) == 0)
|
|
{
|
|
if ((newControl & 0x8000) == 0x8000)
|
|
_scheduler.ScheduleTask((int)startTaskId, 2);
|
|
}
|
|
else
|
|
{
|
|
if ((newControl & 0x8000) == 0)
|
|
{
|
|
if (channel._running)
|
|
channel._running = false;
|
|
else
|
|
_scheduler.CancelTask((int)startTaskId);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (register)
|
|
{
|
|
case Register.DMA0SAD_L:
|
|
WriteSourceReload_Low(ref _channel0);
|
|
break;
|
|
case Register.DMA0SAD_H:
|
|
WriteSourceReload_High(ref _channel0, 0x07ff);
|
|
break;
|
|
|
|
case Register.DMA0DAD_L:
|
|
WriteDestinationReload_Low(ref _channel0);
|
|
break;
|
|
case Register.DMA0DAD_H:
|
|
WriteDestinationReload_High(ref _channel0, 0x07ff);
|
|
break;
|
|
|
|
case Register.DMA0CNT_L:
|
|
WriteLengthReload(ref _channel0);
|
|
break;
|
|
case Register.DMA0CNT_H:
|
|
WriteControl(ref _channel0, GBA_System.TaskId.StartDMA_Channel0);
|
|
break;
|
|
|
|
case Register.DMA1SAD_L:
|
|
WriteSourceReload_Low(ref _channel1);
|
|
break;
|
|
case Register.DMA1SAD_H:
|
|
WriteSourceReload_High(ref _channel1, 0x0fff);
|
|
break;
|
|
|
|
case Register.DMA1DAD_L:
|
|
WriteDestinationReload_Low(ref _channel1);
|
|
break;
|
|
case Register.DMA1DAD_H:
|
|
WriteDestinationReload_High(ref _channel1, 0x07ff);
|
|
break;
|
|
|
|
case Register.DMA1CNT_L:
|
|
WriteLengthReload(ref _channel1);
|
|
break;
|
|
case Register.DMA1CNT_H:
|
|
WriteControl(ref _channel1, GBA_System.TaskId.StartDMA_Channel1);
|
|
break;
|
|
|
|
case Register.DMA2SAD_L:
|
|
WriteSourceReload_Low(ref _channel2);
|
|
break;
|
|
case Register.DMA2SAD_H:
|
|
WriteSourceReload_High(ref _channel2, 0x0fff);
|
|
break;
|
|
|
|
case Register.DMA2DAD_L:
|
|
WriteDestinationReload_Low(ref _channel2);
|
|
break;
|
|
case Register.DMA2DAD_H:
|
|
WriteDestinationReload_High(ref _channel2, 0x07ff);
|
|
break;
|
|
|
|
case Register.DMA2CNT_L:
|
|
WriteLengthReload(ref _channel2);
|
|
break;
|
|
case Register.DMA2CNT_H:
|
|
WriteControl(ref _channel2, GBA_System.TaskId.StartDMA_Channel2);
|
|
break;
|
|
|
|
case Register.DMA3SAD_L:
|
|
WriteSourceReload_Low(ref _channel3);
|
|
break;
|
|
case Register.DMA3SAD_H:
|
|
WriteSourceReload_High(ref _channel3, 0x0fff);
|
|
break;
|
|
|
|
case Register.DMA3DAD_L:
|
|
WriteDestinationReload_Low(ref _channel3);
|
|
break;
|
|
case Register.DMA3DAD_H:
|
|
WriteDestinationReload_High(ref _channel3, 0x0fff);
|
|
break;
|
|
|
|
case Register.DMA3CNT_L:
|
|
WriteLengthReload(ref _channel3);
|
|
break;
|
|
case Register.DMA3CNT_H:
|
|
WriteControl(ref _channel3, GBA_System.TaskId.StartDMA_Channel3);
|
|
break;
|
|
|
|
// should never happen
|
|
default:
|
|
throw new Exception("Iris.GBA.DMA: Register write error");
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void PerformVBlankTransfers()
|
|
{
|
|
if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
|
PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0);
|
|
|
|
if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
|
PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1);
|
|
|
|
if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
|
PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2);
|
|
|
|
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
|
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void PerformHBlankTransfers()
|
|
{
|
|
if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
|
PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0);
|
|
|
|
if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
|
PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1);
|
|
|
|
if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
|
PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2);
|
|
|
|
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
|
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
|
|
}
|
|
|
|
internal void PerformVideoTransfer(bool disable)
|
|
{
|
|
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.Special))
|
|
{
|
|
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
|
|
|
|
if (disable)
|
|
{
|
|
_channel3._control = (UInt16)(_channel3._control & ~0x8000);
|
|
_channel3._running = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Start(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength)
|
|
{
|
|
channel._source = channel._sourceReload;
|
|
channel._destination = channel._destinationReload;
|
|
channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload;
|
|
channel._running = true;
|
|
|
|
if (((channel._control >> 12) & 0b11) == (int)StartTiming.Immediate)
|
|
PerformTransfer(ref channel, interrupt, maxLength);
|
|
}
|
|
|
|
private void PerformTransfer(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength)
|
|
{
|
|
UInt16 sourceAddressControlFlag = (UInt16)((channel._control >> 7) & 0b11);
|
|
UInt16 destinationAddressControlFlag = (UInt16)((channel._control >> 5) & 0b11);
|
|
|
|
int GetSourceIncrement(int dataUnitSize)
|
|
{
|
|
return sourceAddressControlFlag switch
|
|
{
|
|
// increment
|
|
0b00 => dataUnitSize,
|
|
// decrement
|
|
0b01 => -dataUnitSize,
|
|
// fixed
|
|
0b10 => 0,
|
|
// prohibited
|
|
0b11 => 0,
|
|
// should never happen
|
|
_ => throw new Exception("Iris.GBA.DMA: Wrong source address control flag"),
|
|
};
|
|
}
|
|
|
|
(int destinationIncrement, bool reloadDestination) GetDestinationIncrement(int dataUnitSize)
|
|
{
|
|
return destinationAddressControlFlag switch
|
|
{
|
|
// increment
|
|
0b00 => (dataUnitSize, false),
|
|
// decrement
|
|
0b01 => (-dataUnitSize, false),
|
|
// fixed
|
|
0b10 => (0, false),
|
|
// increment+reload
|
|
0b11 => (dataUnitSize, true),
|
|
// should never happen
|
|
_ => throw new Exception("Iris.GBA.DMA: Wrong destination address control flag"),
|
|
};
|
|
}
|
|
|
|
bool reloadDestination;
|
|
|
|
// 16 bits
|
|
if ((channel._control & 0x0400) == 0)
|
|
{
|
|
const int DataUnitSize = 2;
|
|
|
|
int sourceIncrement = GetSourceIncrement(DataUnitSize);
|
|
//(int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize);
|
|
|
|
var v2 = GetDestinationIncrement(DataUnitSize);
|
|
int destinationIncrement = v2.destinationIncrement;
|
|
reloadDestination = v2.reloadDestination;
|
|
|
|
for (; channel._length > 0; --channel._length)
|
|
{
|
|
//_memory.Write16(channel._destination, _memory.Read16(channel._source));
|
|
_memory.Write16(channel._destination, _memory.Read16(channel._source));
|
|
|
|
channel._source = (UInt32)(channel._source + sourceIncrement);
|
|
channel._destination = (UInt32)(channel._destination + destinationIncrement);
|
|
|
|
_scheduler.AdvanceCycleCounter(2);
|
|
}
|
|
}
|
|
|
|
// 32 bits
|
|
else
|
|
{
|
|
const int DataUnitSize = 4;
|
|
|
|
int sourceIncrement = GetSourceIncrement(DataUnitSize);
|
|
//(int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize);
|
|
var v2 = GetDestinationIncrement(DataUnitSize);
|
|
int destinationIncrement = v2.destinationIncrement;
|
|
reloadDestination = v2.reloadDestination;
|
|
|
|
for (; channel._length > 0; --channel._length)
|
|
{
|
|
_memory.Write32(channel._destination, _memory.Read32(channel._source));
|
|
|
|
channel._source = (UInt32)(channel._source + sourceIncrement);
|
|
channel._destination = (UInt32)(channel._destination + destinationIncrement);
|
|
|
|
_scheduler.AdvanceCycleCounter(2);
|
|
}
|
|
}
|
|
|
|
if ((channel._control & 0x4000) == 0x4000)
|
|
_interruptControl.RequestInterrupt(interrupt);
|
|
|
|
// Repeat off
|
|
if ((channel._control & 0x0200) == 0)
|
|
{
|
|
channel._control = (UInt16)(channel._control & ~0x8000);
|
|
channel._running = false;
|
|
}
|
|
|
|
// Repeat on
|
|
else
|
|
{
|
|
if (reloadDestination)
|
|
channel._destination = channel._destinationReload;
|
|
|
|
channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload;
|
|
}
|
|
}
|
|
}
|
|
}
|