#include "stdafx.h" #include "DmaController.h" #include "DmaControllerTypes.h" #include "MemoryManager.h" #include "MessageManager.h" #include "../Utilities/Serializer.h" static constexpr uint8_t _transferByteCount[8] = {1, 2, 2, 4, 4, 4, 2, 4}; static constexpr uint8_t _transferOffset[8][4] = { {0, 0, 0, 0}, {0, 1, 0, 1}, {0, 0, 0, 0}, {0, 0, 1, 1}, {0, 1, 2, 3}, {0, 1, 0, 1}, {0, 0, 0, 0}, {0, 0, 1, 1} }; DmaController::DmaController(MemoryManager* memoryManager) { _memoryManager = memoryManager; Reset(); for (int j = 0; j < 8; j++) { for (int i = 0; i <= 0x0A; i++) { Write(0x4300 | i | (j << 4), 0xFF); } } } void DmaController::Reset() { _hdmaChannels = 0; _hdmaPending = false; _hdmaInitPending = false; _dmaStartDelay = false; _dmaPending = false; _needToProcess = false; for (int i = 0; i < 8; i++) { _channel[i].DmaActive = false; } } void DmaController::CopyDmaByte(uint32_t addressBusA, uint16_t addressBusB, bool fromBtoA) { if (fromBtoA) { if (addressBusB != 0x2180 || !_memoryManager->IsWorkRam(addressBusA)) { uint8_t valToWrite = _memoryManager->ReadDma(addressBusB, false); _memoryManager->WriteDma(addressBusA, valToWrite, true); } else { //$2180->WRAM do cause a write to occur (but no read), but the value written is invalid _memoryManager->IncMasterClock4(); _memoryManager->WriteDma(addressBusA, 0xFF, true); } } else { if (addressBusB != 0x2180 || !_memoryManager->IsWorkRam(addressBusA)) { uint8_t valToWrite = _memoryManager->ReadDma(addressBusA, true); _memoryManager->WriteDma(addressBusB, valToWrite, false); } else { //WRAM->$2180 does not cause a write to occur _memoryManager->IncMasterClock8(); } } } void DmaController::RunDma(DmaChannelConfig& channel) { if (!channel.DmaActive) { return; } //"Then perform the DMA: 8 master cycles overhead and 8 master cycles per byte per channel" _memoryManager->IncMasterClock8(); ProcessPendingTransfers(); const uint8_t* transferOffsets = _transferOffset[channel.TransferMode]; uint8_t i = 0; do { //Manual DMA transfers run to the end of the transfer when started CopyDmaByte( (channel.SrcBank << 16) | channel.SrcAddress, 0x2100 | (channel.DestAddress + transferOffsets[i & 0x03]), channel.InvertDirection ); if (!channel.FixedTransfer) { channel.SrcAddress += channel.Decrement ? -1 : 1; } channel.TransferSize--; i++; ProcessPendingTransfers(); } while (channel.TransferSize > 0 && channel.DmaActive); channel.DmaActive = false; } bool DmaController::InitHdmaChannels() { _hdmaInitPending = false; for (int i = 0; i < 8; i++) { //Reset internal flags on every frame, whether or not the channels are enabled _channel[i].HdmaFinished = false; _channel[i].DoTransfer = false; //not resetting this causes graphical glitches in some games (Aladdin, Super Ghouls and Ghosts) } if (!_hdmaChannels) { //No channels are enabled, no more processing needs to be done UpdateNeedToProcessFlag(); return false; } bool needSync = !HasActiveDmaChannel(); if (needSync) { SyncStartDma(); } _memoryManager->IncMasterClock8(); for (int i = 0; i < 8; i++) { DmaChannelConfig& ch = _channel[i]; //Set DoTransfer to true for all channels if any HDMA channel is enabled ch.DoTransfer = true; if (_hdmaChannels & (1 << i)) { //"1. Copy AAddress into Address." ch.HdmaTableAddress = ch.SrcAddress; ch.DmaActive = false; //"2. Load $43xA (Line Counter and Repeat) from the table. I believe $00 will terminate this channel immediately." ch.HdmaLineCounterAndRepeat = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress, true); _memoryManager->IncMasterClock4(); ch.HdmaTableAddress++; if (ch.HdmaLineCounterAndRepeat == 0) { ch.HdmaFinished = true; } //3. Load Indirect Address, if necessary. if (ch.HdmaIndirectAddressing) { uint8_t lsb = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress++, true); _memoryManager->IncMasterClock4(); uint8_t msb = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress++, true); _memoryManager->IncMasterClock4(); ch.TransferSize = (msb << 8) | lsb; } } } if (needSync) { SyncEndDma(); } UpdateNeedToProcessFlag(); return true; } void DmaController::RunHdmaTransfer(DmaChannelConfig& channel) { const uint8_t* transferOffsets = _transferOffset[channel.TransferMode]; uint8_t transferByteCount = _transferByteCount[channel.TransferMode]; channel.DmaActive = false; uint8_t i = 0; if (channel.HdmaIndirectAddressing) { do { CopyDmaByte( (channel.HdmaBank << 16) | channel.TransferSize, 0x2100 | (channel.DestAddress + transferOffsets[i]), channel.InvertDirection ); channel.TransferSize++; i++; } while (i < transferByteCount); } else { do { CopyDmaByte( (channel.SrcBank << 16) | channel.HdmaTableAddress, 0x2100 | (channel.DestAddress + transferOffsets[i]), channel.InvertDirection ); channel.HdmaTableAddress++; i++; } while (i < transferByteCount); } } void DmaController::SyncStartDma() { //"after the pause, wait 2-8 master cycles to reach a whole multiple of 8 master cycles since reset" _dmaStartClock = _memoryManager->GetMasterClock(); _memoryManager->IncrementMasterClockValue(8 - (_memoryManager->GetMasterClock() & 0x07)); } void DmaController::SyncEndDma() { //"Then wait 2-8 master cycles to reach a whole number of CPU Clock cycles since the pause" uint8_t cpuSpeed = _memoryManager->GetCpuSpeed(); _memoryManager->IncrementMasterClockValue( cpuSpeed - ((_memoryManager->GetMasterClock() - _dmaStartClock) % cpuSpeed)); } bool DmaController::HasActiveDmaChannel() { for (int i = 0; i < 8; i++) { if (_channel[i].DmaActive) { return true; } } return false; } bool DmaController::ProcessHdmaChannels() { _hdmaPending = false; if (!_hdmaChannels) { UpdateNeedToProcessFlag(); return false; } bool needSync = !HasActiveDmaChannel(); if (needSync) { SyncStartDma(); } _memoryManager->IncMasterClock8(); uint8_t originalActiveChannel = _activeChannel; //Run all the DMA transfers for each channel first, before fetching data for the next scanline for (int i = 0; i < 8; i++) { DmaChannelConfig& ch = _channel[i]; if ((_hdmaChannels & (1 << i)) == 0) { continue; } ch.DmaActive = false; if (ch.HdmaFinished) { continue; } //1. If DoTransfer is false, skip to step 3. if (ch.DoTransfer) { //2. For the number of bytes (1, 2, or 4) required for this Transfer Mode... _activeChannel = DmaController::HdmaChannelFlag | i; RunHdmaTransfer(ch); } } //Update the channel's state & fetch data for the next scanline for (int i = 0; i < 8; i++) { DmaChannelConfig& ch = _channel[i]; if ((_hdmaChannels & (1 << i)) == 0 || ch.HdmaFinished) { continue; } //3. Decrement $43xA. ch.HdmaLineCounterAndRepeat--; //4. Set DoTransfer to the value of Repeat. ch.DoTransfer = (ch.HdmaLineCounterAndRepeat & 0x80) != 0; //"a. Read the next byte from Address into $43xA (thus, into both Line Counter and Repeat)." //This value is discarded if the line counter isn't 0 uint8_t newCounter = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress, true); _memoryManager->IncMasterClock4(); //5. If Line Counter is zero... if ((ch.HdmaLineCounterAndRepeat & 0x7F) == 0) { ch.HdmaLineCounterAndRepeat = newCounter; ch.HdmaTableAddress++; //"b. If Addressing Mode is Indirect, read two bytes from Address into Indirect Address(and increment Address by two bytes)." if (ch.HdmaIndirectAddressing) { if (ch.HdmaLineCounterAndRepeat == 0 && IsLastActiveHdmaChannel(i)) { //"One oddity: if $43xA is 0 and this is the last active HDMA channel for this scanline, only load one byte for Address, //and use the $00 for the low byte.So Address ends up incremented one less than otherwise expected, and one less CPU Cycle is used." uint8_t msb = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress++, true); _memoryManager->IncMasterClock4(); ch.TransferSize = (msb << 8); } else { //"If a new indirect address is required, 16 master cycles are taken to load it." uint8_t lsb = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress++, true); _memoryManager->IncMasterClock4(); uint8_t msb = _memoryManager->ReadDma((ch.SrcBank << 16) | ch.HdmaTableAddress++, true); _memoryManager->IncMasterClock4(); ch.TransferSize = (msb << 8) | lsb; } } //"c. If $43xA is zero, terminate this HDMA channel for this frame. The bit in $420c is not cleared, though, so it may be automatically restarted next frame." if (ch.HdmaLineCounterAndRepeat == 0) { ch.HdmaFinished = true; } //"d. Set DoTransfer to true." ch.DoTransfer = true; } } if (needSync) { //If we ran a HDMA transfer, sync SyncEndDma(); } _activeChannel = originalActiveChannel; UpdateNeedToProcessFlag(); return true; } bool DmaController::IsLastActiveHdmaChannel(uint8_t channel) { for (int i = channel + 1; i < 8; i++) { if ((_hdmaChannels & (1 << i)) && !_channel[i].HdmaFinished) { return false; } } return true; } void DmaController::UpdateNeedToProcessFlag() { //Slightly faster execution time by doing this rather than processing all 4 flags on each cycle _needToProcess = _hdmaPending || _hdmaInitPending || _dmaStartDelay || _dmaPending; } void DmaController::BeginHdmaTransfer() { if (_hdmaChannels) { _hdmaPending = true; UpdateNeedToProcessFlag(); } } void DmaController::BeginHdmaInit() { _hdmaInitPending = true; UpdateNeedToProcessFlag(); } bool DmaController::ProcessPendingTransfers() { if (!_needToProcess) { return false; } if (_dmaStartDelay) { _dmaStartDelay = false; return false; } if (_hdmaPending) { return ProcessHdmaChannels(); } else if (_hdmaInitPending) { return InitHdmaChannels(); } else if (_dmaPending) { _dmaPending = false; SyncStartDma(); _memoryManager->IncMasterClock8(); ProcessPendingTransfers(); for (int i = 0; i < 8; i++) { if (_channel[i].DmaActive) { _activeChannel = i; RunDma(_channel[i]); } } SyncEndDma(); UpdateNeedToProcessFlag(); return true; } return false; } void DmaController::Write(uint16_t addr, uint8_t value) { switch (addr) { case 0x420B: { //MDMAEN - DMA Enable for (int i = 0; i < 8; i++) { if (value & (1 << i)) { _channel[i].DmaActive = true; } } if (value) { _dmaPending = true; _dmaStartDelay = true; UpdateNeedToProcessFlag(); } break; } case 0x420C: //HDMAEN - HDMA Enable _hdmaChannels = value; break; case 0x4300: case 0x4310: case 0x4320: case 0x4330: case 0x4340: case 0x4350: case 0x4360: case 0x4370: { //DMAPx - DMA Control for Channel x DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.InvertDirection = (value & 0x80) != 0; channel.HdmaIndirectAddressing = (value & 0x40) != 0; channel.UnusedFlag = (value & 0x20) != 0; channel.Decrement = (value & 0x10) != 0; channel.FixedTransfer = (value & 0x08) != 0; channel.TransferMode = value & 0x07; break; } case 0x4301: case 0x4311: case 0x4321: case 0x4331: case 0x4341: case 0x4351: case 0x4361: case 0x4371: { //BBADx - DMA Destination Register for Channel x DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.DestAddress = value; break; } case 0x4302: case 0x4312: case 0x4322: case 0x4332: case 0x4342: case 0x4352: case 0x4362: case 0x4372: { DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.SrcAddress = (channel.SrcAddress & 0xFF00) | value; break; } case 0x4303: case 0x4313: case 0x4323: case 0x4333: case 0x4343: case 0x4353: case 0x4363: case 0x4373: { DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.SrcAddress = (channel.SrcAddress & 0xFF) | (value << 8); break; } case 0x4304: case 0x4314: case 0x4324: case 0x4334: case 0x4344: case 0x4354: case 0x4364: case 0x4374: { DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.SrcBank = value; break; } case 0x4305: case 0x4315: case 0x4325: case 0x4335: case 0x4345: case 0x4355: case 0x4365: case 0x4375: { //DASxL - DMA Size / HDMA Indirect Address low byte(x = 0 - 7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.TransferSize = (channel.TransferSize & 0xFF00) | value; break; } case 0x4306: case 0x4316: case 0x4326: case 0x4336: case 0x4346: case 0x4356: case 0x4366: case 0x4376: { //DASxL - DMA Size / HDMA Indirect Address low byte(x = 0 - 7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.TransferSize = (channel.TransferSize & 0xFF) | (value << 8); break; } case 0x4307: case 0x4317: case 0x4327: case 0x4337: case 0x4347: case 0x4357: case 0x4367: case 0x4377: { //DASBx - HDMA Indirect Address bank byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.HdmaBank = value; break; } case 0x4308: case 0x4318: case 0x4328: case 0x4338: case 0x4348: case 0x4358: case 0x4368: case 0x4378: { //A2AxL - HDMA Table Address low byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.HdmaTableAddress = (channel.HdmaTableAddress & 0xFF00) | value; break; } case 0x4309: case 0x4319: case 0x4329: case 0x4339: case 0x4349: case 0x4359: case 0x4369: case 0x4379: { //A2AxH - HDMA Table Address high byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.HdmaTableAddress = (value << 8) | (channel.HdmaTableAddress & 0xFF); break; } case 0x430A: case 0x431A: case 0x432A: case 0x433A: case 0x434A: case 0x435A: case 0x436A: case 0x437A: { //DASBx - HDMA Indirect Address bank byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.HdmaLineCounterAndRepeat = value; break; } case 0x430B: case 0x431B: case 0x432B: case 0x433B: case 0x434B: case 0x435B: case 0x436B: case 0x437B: case 0x430F: case 0x431F: case 0x432F: case 0x433F: case 0x434F: case 0x435F: case 0x436F: case 0x437F: { //UNUSEDx - HDMA Indirect Address bank byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; channel.UnusedByte = value; } } } uint8_t DmaController::Read(uint16_t addr) { switch (addr) { case 0x4300: case 0x4310: case 0x4320: case 0x4330: case 0x4340: case 0x4350: case 0x4360: case 0x4370: { //DMAPx - DMA Control for Channel x DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return ( (channel.InvertDirection ? 0x80 : 0) | (channel.HdmaIndirectAddressing ? 0x40 : 0) | (channel.UnusedFlag ? 0x20 : 0) | (channel.Decrement ? 0x10 : 0) | (channel.FixedTransfer ? 0x08 : 0) | (channel.TransferMode & 0x07) ); } case 0x4301: case 0x4311: case 0x4321: case 0x4331: case 0x4341: case 0x4351: case 0x4361: case 0x4371: { //BBADx - DMA Destination Register for Channel x DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.DestAddress; } case 0x4302: case 0x4312: case 0x4322: case 0x4332: case 0x4342: case 0x4352: case 0x4362: case 0x4372: { DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.SrcAddress & 0xFF; } case 0x4303: case 0x4313: case 0x4323: case 0x4333: case 0x4343: case 0x4353: case 0x4363: case 0x4373: { DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return (channel.SrcAddress >> 8) & 0xFF; } case 0x4304: case 0x4314: case 0x4324: case 0x4334: case 0x4344: case 0x4354: case 0x4364: case 0x4374: { DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.SrcBank; } case 0x4305: case 0x4315: case 0x4325: case 0x4335: case 0x4345: case 0x4355: case 0x4365: case 0x4375: { //DASxL - DMA Size / HDMA Indirect Address low byte(x = 0 - 7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.TransferSize & 0xFF; } case 0x4306: case 0x4316: case 0x4326: case 0x4336: case 0x4346: case 0x4356: case 0x4366: case 0x4376: { //DASxL - DMA Size / HDMA Indirect Address low byte(x = 0 - 7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return (channel.TransferSize >> 8) & 0xFF; } case 0x4307: case 0x4317: case 0x4327: case 0x4337: case 0x4347: case 0x4357: case 0x4367: case 0x4377: { //DASBx - HDMA Indirect Address bank byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.HdmaBank; } case 0x4308: case 0x4318: case 0x4328: case 0x4338: case 0x4348: case 0x4358: case 0x4368: case 0x4378: { //A2AxL - HDMA Table Address low byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.HdmaTableAddress & 0xFF; } case 0x4309: case 0x4319: case 0x4329: case 0x4339: case 0x4349: case 0x4359: case 0x4369: case 0x4379: { //A2AxH - HDMA Table Address high byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return (channel.HdmaTableAddress >> 8) & 0xFF; } case 0x430A: case 0x431A: case 0x432A: case 0x433A: case 0x434A: case 0x435A: case 0x436A: case 0x437A: { //DASBx - HDMA Indirect Address bank byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.HdmaLineCounterAndRepeat; } case 0x430B: case 0x431B: case 0x432B: case 0x433B: case 0x434B: case 0x435B: case 0x436B: case 0x437B: case 0x430F: case 0x431F: case 0x432F: case 0x433F: case 0x434F: case 0x435F: case 0x436F: case 0x437F: { //UNUSEDx - HDMA Indirect Address bank byte (x=0-7) DmaChannelConfig& channel = _channel[(addr & 0x70) >> 4]; return channel.UnusedByte; } } return _memoryManager->GetOpenBus(); } uint8_t DmaController::GetActiveChannel() { return _activeChannel; } DmaChannelConfig DmaController::GetChannelConfig(uint8_t channel) { return _channel[channel]; } void DmaController::Serialize(Serializer& s) { s.Stream(_hdmaPending, _hdmaChannels, _dmaPending, _dmaStartClock, _hdmaInitPending, _dmaStartDelay, _needToProcess); for (int i = 0; i < 8; i++) { s.Stream( _channel[i].Decrement, _channel[i].DestAddress, _channel[i].DoTransfer, _channel[i].FixedTransfer, _channel[i].HdmaBank, _channel[i].HdmaFinished, _channel[i].HdmaIndirectAddressing, _channel[i].HdmaLineCounterAndRepeat, _channel[i].HdmaTableAddress, _channel[i].InvertDirection, _channel[i].SrcAddress, _channel[i].SrcBank, _channel[i].TransferMode, _channel[i].TransferSize, _channel[i].UnusedFlag, _channel[i].DmaActive, _channel[i].UnusedByte ); } }