From 4715cd30bc95b04fb5bcd169bf078c1c69dfb95f Mon Sep 17 00:00:00 2001 From: scawful Date: Mon, 22 Apr 2024 15:56:24 -0400 Subject: [PATCH] Add DmaChannel, implement DMA functions --- src/app/emu/memory/dma.cc | 368 ++++++++++++++++++++++++++----- src/app/emu/memory/dma.h | 55 ++--- src/app/emu/memory/dma_channel.h | 37 ++++ 3 files changed, 370 insertions(+), 90 deletions(-) create mode 100644 src/app/emu/memory/dma_channel.h diff --git a/src/app/emu/memory/dma.cc b/src/app/emu/memory/dma.cc index bd5c8b68..31cbbb26 100644 --- a/src/app/emu/memory/dma.cc +++ b/src/app/emu/memory/dma.cc @@ -6,72 +6,340 @@ namespace yaze { namespace app { namespace emu { namespace memory { +namespace dma { -void DirectMemoryAccess::StartDMATransfer(uint8_t channelMask) { - for (int i = 0; i < 8; ++i) { - if ((channelMask & (1 << i)) != 0) { - Channel& ch = channels[i]; +static const int bAdrOffsets[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}}; - // Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, DASn) - // ... +static const int transferLength[8] = {1, 2, 2, 4, 4, 4, 2, 4}; - // Determine the transfer direction based on the DMAPn register - bool fromMemory = (ch.DMAPn & 0x80) != 0; +uint8_t Read(MemoryImpl* memory, uint16_t adr) { + auto channel = memory->dma_channels(); + uint8_t c = (adr & 0x70) >> 4; + switch (adr & 0xf) { + case 0x0: { + uint8_t val = channel[c].mode; + val |= channel[c].fixed << 3; + val |= channel[c].decrement << 4; + val |= channel[c].unusedBit << 5; + val |= channel[c].indirect << 6; + val |= channel[c].fromB << 7; + return val; + } + case 0x1: { + return channel[c].bAdr; + } + case 0x2: { + return channel[c].aAdr & 0xff; + } + case 0x3: { + return channel[c].aAdr >> 8; + } + case 0x4: { + return channel[c].aBank; + } + case 0x5: { + return channel[c].size & 0xff; + } + case 0x6: { + return channel[c].size >> 8; + } + case 0x7: { + return channel[c].indBank; + } + case 0x8: { + return channel[c].tableAdr & 0xff; + } + case 0x9: { + return channel[c].tableAdr >> 8; + } + case 0xa: { + return channel[c].repCount; + } + case 0xb: + case 0xf: { + return channel[c].unusedByte; + } + default: { + return memory->open_bus(); + } + } +} - // Determine the transfer size based on the DMAPn register - bool transferTwoBytes = (ch.DMAPn & 0x40) != 0; +void Write(MemoryImpl* memory, uint16_t adr, uint8_t val) { + auto channel = memory->dma_channels(); + uint8_t c = (adr & 0x70) >> 4; + switch (adr & 0xf) { + case 0x0: { + channel[c].mode = val & 0x7; + channel[c].fixed = val & 0x8; + channel[c].decrement = val & 0x10; + channel[c].unusedBit = val & 0x20; + channel[c].indirect = val & 0x40; + channel[c].fromB = val & 0x80; + break; + } + case 0x1: { + channel[c].bAdr = val; + break; + } + case 0x2: { + channel[c].aAdr = (channel[c].aAdr & 0xff00) | val; + break; + } + case 0x3: { + channel[c].aAdr = (channel[c].aAdr & 0xff) | (val << 8); + break; + } + case 0x4: { + channel[c].aBank = val; + break; + } + case 0x5: { + channel[c].size = (channel[c].size & 0xff00) | val; + break; + } + case 0x6: { + channel[c].size = (channel[c].size & 0xff) | (val << 8); + break; + } + case 0x7: { + channel[c].indBank = val; + break; + } + case 0x8: { + channel[c].tableAdr = (channel[c].tableAdr & 0xff00) | val; + break; + } + case 0x9: { + channel[c].tableAdr = (channel[c].tableAdr & 0xff) | (val << 8); + break; + } + case 0xa: { + channel[c].repCount = val; + break; + } + case 0xb: + case 0xf: { + channel[c].unusedByte = val; + break; + } + default: { + break; + } + } +} - // Perform the DirectMemoryAccess transfer based on the channel parameters - std::cout << "Starting DirectMemoryAccess transfer for channel " << i - << std::endl; +void DoDma(SNES* snes, MemoryImpl* memory, int cpuCycles) { + auto channel = memory->dma_channels(); - for (uint16_t j = 0; j < ch.DASn; ++j) { - // Read a byte or two bytes from memory based on the transfer size - // ... + // align to multiple of 8 + snes->SyncCycles(true, 8); - // Write the data to the B-bus address (BBADn) if transferring from - // memory - // ... + // full transfer overhead + WaitCycle(snes, memory); + for (int i = 0; i < 8; i++) { + if (!channel[i].dmaActive) continue; + // do channel i + WaitCycle(snes, memory); // overhead per channel + int offIndex = 0; + while (channel[i].dmaActive) { + WaitCycle(snes, memory); + TransferByte(snes, memory, channel[i].aAdr, channel[i].aBank, + channel[i].bAdr + bAdrOffsets[channel[i].mode][offIndex++], + channel[i].fromB); + offIndex &= 3; + if (!channel[i].fixed) { + channel[i].aAdr += channel[i].decrement ? -1 : 1; + } + channel[i].size--; + if (channel[i].size == 0) { + channel[i].dmaActive = false; + } + } + } - // Update the A1Tn register based on the transfer direction - if (fromMemory) { - ch.A1Tn += transferTwoBytes ? 2 : 1; - } else { - ch.A1Tn -= transferTwoBytes ? 2 : 1; + // re-align to cpu cycles + snes->SyncCycles(false, cpuCycles); +} + +void HandleDma(SNES* snes, MemoryImpl* memory, int cpu_cycles) { + // if hdma triggered, do it, except if dmastate indicates dma will be done now + // (it will be done as part of the dma in that case) + if (memory->hdma_init_requested() && memory->dma_state() != 2) + InitHdma(snes, memory, true, cpu_cycles); + if (memory->hdma_run_requested() && memory->dma_state() != 2) + DoHdma(snes, memory, true, cpu_cycles); + if (memory->dma_state() == 1) { + memory->set_dma_state(2); + return; + } + if (memory->dma_state() == 2) { + // do dma + DoDma(snes, memory, cpu_cycles); + memory->set_dma_state(0); + } +} + +void WaitCycle(SNES* snes, MemoryImpl* memory) { + // run hdma if requested, no sync (already sycned due to dma) + if (memory->hdma_init_requested()) InitHdma(snes, memory, false, 0); + if (memory->hdma_run_requested()) DoHdma(snes, memory, false, 0); + + snes->RunCycles(8); +} + +void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) { + auto channel = memory->dma_channels(); + memory->set_hdma_init_requested(false); + bool hdmaEnabled = false; + // check if a channel is enabled, and do reset + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive) hdmaEnabled = true; + channel[i].doTransfer = false; + channel[i].terminated = false; + } + if (!hdmaEnabled) return; + if (do_sync) snes->SyncCycles(true, 8); + + // full transfer overhead + snes->RunCycles(8); + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive) { + // terminate any dma + channel[i].dmaActive = false; + // load address, repCount, and indirect address if needed + snes->RunCycles(8); + channel[i].tableAdr = channel[i].aAdr; + channel[i].repCount = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++); + if (channel[i].repCount == 0) channel[i].terminated = true; + if (channel[i].indirect) { + snes->RunCycles(8); + channel[i].size = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++); + snes->RunCycles(8); + channel[i].size |= + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8; + } + channel[i].doTransfer = true; + } + } + if (do_sync) snes->SyncCycles(false, cpu_cycles); +} + +void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles) { + auto channel = memory->dma_channels(); + memory->set_hdma_run_requested(false); + bool hdmaActive = false; + int lastActive = 0; + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive) { + hdmaActive = true; + if (!channel[i].terminated) lastActive = i; + } + } + + if (!hdmaActive) return; + + if (do_sync) snes->SyncCycles(true, 8); + + // full transfer overhead + snes->RunCycles(8); + // do all copies + for (int i = 0; i < 8; i++) { + // terminate any dma + if (channel[i].hdmaActive) channel[i].dmaActive = false; + if (channel[i].hdmaActive && !channel[i].terminated) { + // do the hdma + if (channel[i].doTransfer) { + for (int j = 0; j < transferLength[channel[i].mode]; j++) { + snes->RunCycles(8); + if (channel[i].indirect) { + TransferByte(snes, memory, channel[i].size++, channel[i].indBank, + channel[i].bAdr + bAdrOffsets[channel[i].mode][j], + channel[i].fromB); + } else { + TransferByte(snes, memory, channel[i].tableAdr++, channel[i].aBank, + channel[i].bAdr + bAdrOffsets[channel[i].mode][j], + channel[i].fromB); + } } } - - // Update the channel registers after the transfer (e.g., A1Tn, DASn) - // ... } } - MDMAEN = channelMask; // Set the MDMAEN register to the channel mask -} - -void DirectMemoryAccess::EnableHDMATransfers(uint8_t channelMask) { - for (int i = 0; i < 8; ++i) { - if ((channelMask & (1 << i)) != 0) { - Channel& ch = channels[i]; - - // Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn) - // ... - - // Perform the HDMA setup based on the channel parameters - std::cout << "Enabling HDMA transfer for channel " << i << std::endl; - - // Read the HDMA table from memory starting at A1Tn - // ... - - // Update the A2An register based on the HDMA table - // ... - - // Update the NLTRn register based on the HDMA table - // ... + // do all updates + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive && !channel[i].terminated) { + channel[i].repCount--; + channel[i].doTransfer = channel[i].repCount & 0x80; + snes->RunCycles(8); + uint8_t newRepCount = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr); + if ((channel[i].repCount & 0x7f) == 0) { + channel[i].repCount = newRepCount; + channel[i].tableAdr++; + if (channel[i].indirect) { + if (channel[i].repCount == 0 && i == lastActive) { + // if this is the last active channel, only fetch high, and use 0 + // for low + channel[i].size = 0; + } else { + snes->RunCycles(8); + channel[i].size = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++); + } + snes->RunCycles(8); + channel[i].size |= + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8; + } + if (channel[i].repCount == 0) channel[i].terminated = true; + channel[i].doTransfer = true; + } } } - HDMAEN = channelMask; // Set the HDMAEN register to the channel mask + + if (do_sync) snes->SyncCycles(false, cycles); } +void TransferByte(SNES* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank, + uint8_t bAdr, bool fromB) { + // accessing 0x2180 via b-bus while a-bus accesses ram gives open bus + bool validB = + !(bAdr == 0x80 && + (aBank == 0x7e || aBank == 0x7f || + ((aBank < 0x40 || (aBank >= 0x80 && aBank < 0xc0)) && aAdr < 0x2000))); + // accesing b-bus, or dma regs via a-bus gives open bus + bool validA = !((aBank < 0x40 || (aBank >= 0x80 && aBank < 0xc0)) && + (aAdr == 0x420b || aAdr == 0x420c || + (aAdr >= 0x4300 && aAdr < 0x4380) || + (aAdr >= 0x2100 && aAdr < 0x2200))); + if (fromB) { + uint8_t val = validB ? snes->ReadBBus(bAdr) : memory->open_bus(); + if (validA) snes->Write((aBank << 16) | aAdr, val); + } else { + uint8_t val = + validA ? snes->Read((aBank << 16) | aAdr) : memory->open_bus(); + if (validB) snes->WriteBBus(bAdr, val); + } +} + +void StartDma(MemoryImpl* memory, uint8_t val, bool hdma) { + auto channel = memory->dma_channels(); + for (int i = 0; i < 8; i++) { + if (hdma) { + channel[i].hdmaActive = val & (1 << i); + } else { + channel[i].dmaActive = val & (1 << i); + } + } + if (!hdma) { + memory->set_dma_state(val != 0 ? 1 : 0); + } +} + +} // namespace dma } // namespace memory } // namespace emu } // namespace app diff --git a/src/app/emu/memory/dma.h b/src/app/emu/memory/dma.h index e200faaf..38c37238 100644 --- a/src/app/emu/memory/dma.h +++ b/src/app/emu/memory/dma.h @@ -3,56 +3,31 @@ #include +#include "app/emu/memory/memory.h" +#include "app/emu/snes.h" + namespace yaze { namespace app { namespace emu { namespace memory { +namespace dma { -class DirectMemoryAccess { - public: - DirectMemoryAccess() { - // Initialize DMA and HDMA channels - for (int i = 0; i < 8; ++i) { - channels[i].DMAPn = 0; - channels[i].BBADn = 0; - channels[i].UNUSEDn = 0; - channels[i].A1Tn = 0xFFFFFF; - channels[i].DASn = 0xFFFF; - channels[i].A2An = 0xFFFF; - channels[i].NLTRn = 0xFF; - } - } +void HandleDma(SNES* snes, MemoryImpl* memory, int cpu_cycles); - // DMA Transfer Modes - enum class DMA_TRANSFER_TYPE { - OAM, - PPUDATA, - CGDATA, - FILL_VRAM, - CLEAR_VRAM, - RESET_VRAM - }; +void WaitCycle(SNES* snes, MemoryImpl* memory); - // Functions for handling DMA and HDMA transfers - void StartDMATransfer(uint8_t channels); - void EnableHDMATransfers(uint8_t channels); +void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles); +void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles); - // Structure for DMA and HDMA channel registers - struct Channel { - uint8_t DMAPn; // DMA/HDMA parameters - uint8_t BBADn; // B-bus address - uint8_t UNUSEDn; // Unused byte - uint32_t A1Tn; // DMA Current Address / HDMA Table Start Address - uint16_t DASn; // DMA Byte-Counter / HDMA indirect table address - uint16_t A2An; // HDMA Table Current Address - uint8_t NLTRn; // HDMA Line-Counter - }; - Channel channels[8]; +void TransferByte(SNES* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank, + uint8_t bAdr, bool fromB); - uint8_t MDMAEN = 0; // Start DMA transfer - uint8_t HDMAEN = 0; // Enable HDMA transfers -}; +uint8_t Read(MemoryImpl* memory, uint16_t address); +void Write(MemoryImpl* memory, uint16_t address, uint8_t data); +void StartDma(MemoryImpl* memory, uint8_t val, bool hdma); +void DoDma(MemoryImpl* memory, int cycles); +} // namespace dma } // namespace memory } // namespace emu } // namespace app diff --git a/src/app/emu/memory/dma_channel.h b/src/app/emu/memory/dma_channel.h new file mode 100644 index 00000000..028b4fc0 --- /dev/null +++ b/src/app/emu/memory/dma_channel.h @@ -0,0 +1,37 @@ +#ifndef YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H +#define YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H + +#include + +namespace yaze { +namespace app { +namespace emu { +namespace memory { + +typedef struct DmaChannel { + uint8_t bAdr; + uint16_t aAdr; + uint8_t aBank; + uint16_t size; // also indirect hdma adr + uint8_t indBank; // hdma + uint16_t tableAdr; // hdma + uint8_t repCount; // hdma + uint8_t unusedByte; + bool dmaActive; + bool hdmaActive; + uint8_t mode; + bool fixed; + bool decrement; + bool indirect; // hdma + bool fromB; + bool unusedBit; + bool doTransfer; // hdma + bool terminated; // hdma +} DmaChannel; + +} // namespace memory +} // namespace emu +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H \ No newline at end of file