From 221bc4470005675d42b4dc7d9c620f981190ca9b Mon Sep 17 00:00:00 2001 From: Sour Date: Tue, 19 Feb 2019 21:09:12 -0500 Subject: [PATCH] DMA: Added support for HDMA (incorrect timings) --- Core/Console.cpp | 9 ++ Core/Console.h | 3 + Core/DmaController.cpp | 195 ++++++++++++++++++++++++++++++++++++----- Core/DmaController.h | 23 ++++- Core/MemoryManager.h | 11 ++- Core/Ppu.cpp | 5 ++ 6 files changed, 215 insertions(+), 31 deletions(-) diff --git a/Core/Console.cpp b/Core/Console.cpp index e2330ef..e5b0baa 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -6,6 +6,7 @@ #include "InternalRegisters.h" #include "ControlManager.h" #include "MemoryManager.h" +#include "DmaController.h" #include "Debugger.h" #include "NotificationManager.h" #include "SoundMixer.h" @@ -73,6 +74,7 @@ void Console::Stop() _internalRegisters.reset(); _controlManager.reset(); _memoryManager.reset(); + _dmaController.reset(); } void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile) @@ -88,6 +90,8 @@ void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile) _cart = cart; _controlManager.reset(new ControlManager(shared_from_this())); _memoryManager.reset(new MemoryManager()); + _dmaController.reset(new DmaController(_memoryManager.get())); + _memoryManager->Initialize(shared_from_this()); _cpu.reset(new Cpu(_memoryManager)); @@ -161,6 +165,11 @@ shared_ptr Console::GetControlManager() return _controlManager; } +shared_ptr Console::GetDmaController() +{ + return _dmaController; +} + shared_ptr Console::GetDebugger(bool autoStart) { shared_ptr debugger = _debugger; diff --git a/Core/Console.h b/Core/Console.h index 13b3fdf..a9c1a6c 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -10,6 +10,7 @@ class BaseCartridge; class MemoryManager; class InternalRegisters; class ControlManager; +class DmaController; class Debugger; class DebugHud; class SoundMixer; @@ -28,6 +29,7 @@ private: shared_ptr _cart; shared_ptr _internalRegisters; shared_ptr _controlManager; + shared_ptr _dmaController; shared_ptr _debugger; @@ -64,6 +66,7 @@ public: shared_ptr GetMemoryManager(); shared_ptr GetInternalRegisters(); shared_ptr GetControlManager(); + shared_ptr GetDmaController(); shared_ptr GetDebugger(bool autoStart = true); bool IsRunning(); diff --git a/Core/DmaController.cpp b/Core/DmaController.cpp index 491c16b..2991887 100644 --- a/Core/DmaController.cpp +++ b/Core/DmaController.cpp @@ -8,41 +8,158 @@ DmaController::DmaController(MemoryManager *memoryManager) _memoryManager = memoryManager; } -void DmaController::RunSingleTransfer(DmaChannelConfig &channel, uint32_t &bytesLeft) +void DmaController::RunSingleTransfer(DmaChannelConfig &channel) { const uint8_t *transferOffsets = _transferOffset[channel.TransferMode]; uint8_t transferByteCount = _transferByteCount[channel.TransferMode]; uint8_t i = 0; - while(bytesLeft > 0 && transferByteCount > 0) { + do { if(channel.InvertDirection) { uint8_t valToWrite = _memoryManager->Read(0x2100 | channel.DestAddress + transferOffsets[i], MemoryOperationType::DmaRead); - _memoryManager->Write(channel.SrcAddress, valToWrite, MemoryOperationType::DmaWrite); + _memoryManager->Write((channel.SrcBank << 16) | channel.SrcAddress, valToWrite, MemoryOperationType::DmaWrite); } else { - uint8_t valToWrite = _memoryManager->Read(channel.SrcAddress, MemoryOperationType::DmaRead); + uint8_t valToWrite = _memoryManager->Read((channel.SrcBank << 16) | channel.SrcAddress, MemoryOperationType::DmaRead); _memoryManager->Write(0x2100 | channel.DestAddress + transferOffsets[i], valToWrite, MemoryOperationType::DmaWrite); } if(!channel.FixedTransfer) { - channel.SrcAddress = (channel.SrcAddress + (channel.Decrement ? -1 : 1)) & 0xFFFFFF; + channel.SrcAddress += channel.Decrement ? -1 : 1; } + channel.TransferSize--; transferByteCount--; - bytesLeft--; i++; - } + } while(channel.TransferSize > 0 && transferByteCount > 0); } void DmaController::RunDma(DmaChannelConfig &channel) { - //"Note, however, that writing $0000 to this register actually results in a transfer of $10000 bytes, not 0." - uint32_t bytesLeft = channel.TransferSize ? channel.TransferSize : 0x10000; - - MessageManager::Log("Run DMA: " + HexUtilities::ToHex(channel.DestAddress) + " -> " + HexUtilities::ToHex(channel.SrcAddress) + " Bytes: " + std::to_string(bytesLeft)); - - while(bytesLeft > 0) { + do { //Manual DMA transfers run to the end of the transfer when started - RunSingleTransfer(channel, bytesLeft); + RunSingleTransfer(channel); + + //TODO : Run HDMA when needed, between 2 DMA transfers + } while(channel.TransferSize > 0); +} + +void DmaController::InitHdmaChannels() +{ + for(int i = 0; i < 8; i++) { + DmaChannelConfig &ch = _channel[i]; + ch.HdmaFinished = false; + if(_hdmaChannels & (1 << i)) { + //"1. Copy AAddress into Address." + ch.HdmaTableAddress = ch.SrcAddress; + + //"2. Load $43xA (Line Counter and Repeat) from the table. I believe $00 will terminate this channel immediately." + ch.HdmaLineCounterAndRepeat = _memoryManager->Read((ch.SrcBank << 16) | ch.HdmaTableAddress, MemoryOperationType::DmaRead); + ch.HdmaTableAddress++; + if(ch.HdmaLineCounterAndRepeat == 0) { + ch.HdmaFinished = true; + } + + //3. Load Indirect Address, if necessary. + if(ch.HdmaIndirectAddressing) { + uint8_t lsb = _memoryManager->Read((ch.SrcBank << 16) | ch.HdmaTableAddress++, MemoryOperationType::DmaRead); + uint8_t msb = _memoryManager->Read((ch.SrcBank << 16) | ch.HdmaTableAddress++, MemoryOperationType::DmaRead); + ch.TransferSize = (msb << 8) | lsb; + } + + //4. Set DoTransfer to true. + ch.DoTransfer = true; + } + } +} + +void DmaController::RunHdmaTransfer(DmaChannelConfig &channel) +{ + const uint8_t *transferOffsets = _transferOffset[channel.TransferMode]; + uint8_t transferByteCount = _transferByteCount[channel.TransferMode]; + + uint32_t srcAddress; + if(channel.HdmaIndirectAddressing) { + srcAddress = (channel.HdmaBank << 16) | channel.TransferSize; + } else { + srcAddress = (channel.SrcBank << 16) | channel.HdmaTableAddress; + } + + uint8_t i = 0; + do { + if(channel.InvertDirection) { + uint8_t valToWrite = _memoryManager->Read(0x2100 | channel.DestAddress + transferOffsets[i], MemoryOperationType::DmaRead); + _memoryManager->Write(srcAddress, valToWrite, MemoryOperationType::DmaWrite); + } else { + uint8_t valToWrite = _memoryManager->Read(srcAddress, MemoryOperationType::DmaRead); + _memoryManager->Write(0x2100 | channel.DestAddress + transferOffsets[i], valToWrite, MemoryOperationType::DmaWrite); + } + + if(!channel.FixedTransfer) { + srcAddress = (srcAddress + (channel.Decrement ? -1 : 1)) & 0xFFFFFF; + } + + transferByteCount--; + i++; + } while(transferByteCount > 0); + + if(channel.HdmaIndirectAddressing) { + channel.TransferSize = srcAddress; + } else { + channel.HdmaTableAddress = srcAddress; + } +} + +void DmaController::ProcessHdmaChannels() +{ + if(_hdmaChannels) { + _hdmaPending = true; + + for(int i = 0; i < 8; i++) { + DmaChannelConfig &ch = _channel[i]; + if((_hdmaChannels & (1 << i)) == 0 || ch.HdmaFinished) { + return; + } + + //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... + RunHdmaTransfer(ch); + } + + //3. Decrement $43xA. + ch.HdmaLineCounterAndRepeat--; + + //4. Set DoTransfer to the value of Repeat. + ch.DoTransfer = (ch.HdmaLineCounterAndRepeat & 0x80) != 0; + + //5. If Line Counter is zero... + if((ch.HdmaLineCounterAndRepeat & 0x7F) == 0) { + //"a. Read the next byte from Address into $43xA (thus, into both Line Counter and Repeat)." + ch.HdmaLineCounterAndRepeat = _memoryManager->Read(ch.HdmaTableAddress++, MemoryOperationType::DmaRead); + + //"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) { + //"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->Read(ch.HdmaTableAddress++, MemoryOperationType::DmaRead); + ch.TransferSize = (msb << 8); + } else { + uint8_t lsb = _memoryManager->Read(ch.HdmaTableAddress++, MemoryOperationType::DmaRead); + uint8_t msb = _memoryManager->Read(ch.HdmaTableAddress++, MemoryOperationType::DmaRead); + 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; + } + } } } @@ -60,9 +177,7 @@ void DmaController::Write(uint16_t addr, uint8_t value) case 0x420C: //HDMAEN - HDMA Enable - if(value > 0) { - MessageManager::DisplayMessage("Debug", "Unsupported HDMA operation"); - } + _hdmaChannels = value; break; case 0x4300: case 0x4310: case 0x4320: case 0x4330: case 0x4340: case 0x4350: case 0x4360: case 0x4370: @@ -70,7 +185,7 @@ void DmaController::Write(uint16_t addr, uint8_t value) //DMAPx - DMA Control for Channel x DmaChannelConfig &channel = _channel[(addr & 0x70) >> 4]; channel.InvertDirection = (value & 0x80) != 0; - channel.HdmaPointers = (value & 0x40) != 0; + channel.HdmaIndirectAddressing = (value & 0x40) != 0; channel.Decrement = (value & 0x10) != 0; channel.FixedTransfer = (value & 0x08) != 0; channel.TransferMode = value & 0x07; @@ -85,6 +200,27 @@ void DmaController::Write(uint16_t addr, uint8_t 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) @@ -101,24 +237,35 @@ void DmaController::Write(uint16_t addr, uint8_t value) break; } - case 0x4302: case 0x4312: case 0x4322: case 0x4332: case 0x4342: case 0x4352: case 0x4362: case 0x4372: + 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.SrcAddress = (channel.SrcAddress & 0xFFFF00) | value; + channel.HdmaBank = value; break; } - case 0x4303: case 0x4313: case 0x4323: case 0x4333: case 0x4343: case 0x4353: case 0x4363: case 0x4373: + 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.SrcAddress = (channel.SrcAddress & 0xFF00FF) | (value << 8); + channel.HdmaTableAddress = (channel.HdmaTableAddress & 0xFF00) | value; break; } - case 0x4304: case 0x4314: case 0x4324: case 0x4334: case 0x4344: case 0x4354: case 0x4364: case 0x4374: + 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.SrcAddress = (channel.SrcAddress & 0x00FFFF) | (value << 16); + 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; } } diff --git a/Core/DmaController.h b/Core/DmaController.h index 669d80d..22cfa3e 100644 --- a/Core/DmaController.h +++ b/Core/DmaController.h @@ -9,12 +9,20 @@ struct DmaChannelConfig bool InvertDirection; bool Decrement; bool FixedTransfer; - bool HdmaPointers; + bool HdmaIndirectAddressing; uint8_t TransferMode; - uint32_t SrcAddress; + uint16_t SrcAddress; + uint16_t SrcBank; + uint8_t DestAddress; uint16_t TransferSize; + + uint8_t HdmaBank; + uint16_t HdmaTableAddress; + uint8_t HdmaLineCounterAndRepeat; + bool DoTransfer; + bool HdmaFinished; }; class DmaController @@ -26,14 +34,21 @@ private: { 0, 1, 2, 3 }, { 0, 1, 0, 1 }, { 0, 0, 0, 0 }, { 0, 0, 1, 1 } }; + bool _hdmaPending = false; + uint8_t _hdmaChannels = 0; + DmaChannelConfig _channel[8] = {}; MemoryManager *_memoryManager; - void RunSingleTransfer(DmaChannelConfig &channel, uint32_t &bytesLeft); + void RunSingleTransfer(DmaChannelConfig &channel); void RunDma(DmaChannelConfig &channel); - + void RunHdmaTransfer(DmaChannelConfig &channel); + public: DmaController(MemoryManager *memoryManager); + + void InitHdmaChannels(); + void ProcessHdmaChannels(); void Write(uint16_t addr, uint8_t value); }; \ No newline at end of file diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index 3ba3b96..3bb9f78 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -89,7 +89,6 @@ private: shared_ptr _cart; shared_ptr _cpuRegisterHandler; shared_ptr _ppu; - shared_ptr _dmaController; IMemoryHandler* _handlers[0x100 * 0x10]; vector> _workRamHandlers; @@ -110,8 +109,14 @@ public: _workRam = new uint8_t[MemoryManager::WorkRamSize]; - _dmaController.reset(new DmaController(console->GetMemoryManager().get())); - _cpuRegisterHandler.reset(new CpuRegisterHandler(_ppu.get(), console->GetSpc().get(), _dmaController.get(), console->GetInternalRegisters().get(), console->GetControlManager().get(), _workRam)); + _cpuRegisterHandler.reset(new CpuRegisterHandler( + _ppu.get(), + console->GetSpc().get(), + console->GetDmaController().get(), + console->GetInternalRegisters().get(), + console->GetControlManager().get(), + _workRam + )); memset(_handlers, 0, sizeof(_handlers)); //memset(_workRam, 0, 128 * 1024); diff --git a/Core/Ppu.cpp b/Core/Ppu.cpp index 20be42b..794fbd8 100644 --- a/Core/Ppu.cpp +++ b/Core/Ppu.cpp @@ -87,6 +87,7 @@ void Ppu::Exec() } else if(_scanline == 261) { _regs->SetNmiFlag(false); _scanline = 0; + _console->GetDmaController()->InitHdmaChannels(); RenderScanline(); } @@ -102,6 +103,10 @@ void Ppu::Exec() } _cycle++; + + if(_cycle == 278 && _scanline < 225) { + _console->GetDmaController()->ProcessHdmaChannels(); + } } struct SpriteInfo