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); for (; channel._length > 0; --channel._length) { _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); 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; } } } }