Add DmaChannel, implement DMA functions
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -3,56 +3,31 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#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
|
||||
|
||||
37
src/app/emu/memory/dma_channel.h
Normal file
37
src/app/emu/memory/dma_channel.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H
|
||||
#define YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user