diff --git a/src/core/dma.cpp b/src/core/dma.cpp index 2cc78e031..cc403c7d6 100644 --- a/src/core/dma.cpp +++ b/src/core/dma.cpp @@ -48,6 +48,18 @@ bool DMA::DoState(StateWrapper& sw) sw.Do(&m_DPCR.bits); sw.Do(&m_DICR.bits); + + if (sw.IsReading()) + { + m_transfer_min_ticks = std::numeric_limits::max(); + for (const ChannelState& cs : m_state) + { + if (cs.transfer_ticks > 0) + m_transfer_min_ticks = std::min(m_transfer_min_ticks, cs.transfer_ticks); + } + m_system->SetDowncount(m_transfer_min_ticks); + } + return !sw.HasError(); } @@ -113,7 +125,7 @@ void DMA::WriteRegister(u32 offset, u32 value) { Log_TracePrintf("DMA channel %u block control <- 0x%08X", channel_index, value); state.block_control.bits = value; - Transfer(); + QueueTransferChannel(static_cast(channel_index)); return; } @@ -122,7 +134,7 @@ void DMA::WriteRegister(u32 offset, u32 value) state.channel_control.bits = (state.channel_control.bits & ~ChannelState::ChannelControl::WRITE_MASK) | (value & ChannelState::ChannelControl::WRITE_MASK); Log_TracePrintf("DMA channel %u channel control <- 0x%08X", channel_index, state.channel_control.bits); - Transfer(); + QueueTransferChannel(static_cast(channel_index)); return; } @@ -138,7 +150,7 @@ void DMA::WriteRegister(u32 offset, u32 value) { Log_TracePrintf("DPCR <- 0x%08X", value); m_DPCR.bits = value; - Transfer(); + QueueTransfer(); return; } @@ -167,7 +179,26 @@ void DMA::SetRequest(Channel channel, bool request) cs.request = request; if (request) - Transfer(); + QueueTransfer(); +} + +TickCount DMA::GetTransferDelay(Channel channel) const +{ + const ChannelState& cs = m_state[static_cast(channel)]; + switch (channel) + { + case Channel::SPU: + { + if (cs.channel_control.sync_mode == SyncMode::Request) + return (cs.block_control.request.GetBlockCount() * (cs.block_control.request.GetBlockSize() / 2)); + else + return 1; + } + break; + + default: + return 1; + } } bool DMA::CanTransferChannel(Channel channel) const @@ -185,6 +216,9 @@ bool DMA::CanTransferChannel(Channel channel) const if (cs.channel_control.sync_mode == SyncMode::Manual && !cs.channel_control.start_trigger) return false; + if (cs.transfer_ticks > 0) + return false; + return true; } @@ -209,33 +243,66 @@ void DMA::UpdateIRQ() } } -void DMA::Transfer() +void DMA::QueueTransferChannel(Channel channel) { - if (m_transfer_in_progress) + ChannelState& cs = m_state[static_cast(channel)]; + if (cs.transfer_ticks > 0 || !CanTransferChannel(channel)) return; - // prevent recursive calls + const TickCount ticks = GetTransferDelay(channel); + if (ticks == 0) + { + // immediate transfer + TransferChannel(channel); + return; + } + + if (!m_transfer_in_progress) + m_system->Synchronize(); + + cs.transfer_ticks = ticks; + m_transfer_min_ticks = std::min(m_transfer_min_ticks, ticks); + m_system->SetDowncount(ticks); +} + +void DMA::QueueTransfer() +{ + for (u32 i = 0; i < NUM_CHANNELS; i++) + QueueTransferChannel(static_cast(i)); +} + +void DMA::Execute(TickCount ticks) +{ + m_transfer_min_ticks -= ticks; + if (m_transfer_min_ticks > 0) + { + m_system->SetDowncount(m_transfer_min_ticks); + return; + } + + DebugAssert(!m_transfer_in_progress); m_transfer_in_progress = true; // keep going until all transfers are done. one channel can start others (e.g. MDEC) - for (;;) + m_transfer_min_ticks = std::numeric_limits::max(); + for (u32 i = 0; i < NUM_CHANNELS; i++) { - bool any_channels_active = false; + const Channel channel = static_cast(i); + if (m_state[i].transfer_ticks <= 0) + continue; - for (u32 i = 0; i < NUM_CHANNELS; i++) + m_state[i].transfer_ticks -= ticks; + if (CanTransferChannel(channel)) { - const Channel channel = static_cast(i); - if (CanTransferChannel(channel)) - { - TransferChannel(channel); - any_channels_active = true; - } + TransferChannel(channel); + } + else + { + m_transfer_min_ticks = std::min(m_transfer_min_ticks, ticks); } - - if (!any_channels_active) - break; } + m_system->SetDowncount(m_transfer_min_ticks); m_transfer_in_progress = false; } @@ -348,6 +415,7 @@ void DMA::TransferChannel(Channel channel) } // start/busy bit is cleared on end of transfer + cs.transfer_ticks = 0; cs.channel_control.enable_busy = false; if (m_DICR.IsIRQEnabled(channel)) { diff --git a/src/core/dma.h b/src/core/dma.h index 6f8a0daba..c7c9f9344 100644 --- a/src/core/dma.h +++ b/src/core/dma.h @@ -48,6 +48,8 @@ public: // changing interfaces void SetGPU(GPU* gpu) { m_gpu = gpu; } + void Execute(TickCount ticks); + private: static constexpr PhysicalMemoryAddress BASE_ADDRESS_MASK = UINT32_C(0x00FFFFFF); static constexpr PhysicalMemoryAddress ADDRESS_MASK = UINT32_C(0x001FFFFC); @@ -61,12 +63,17 @@ private: Reserved = 3 }; + /// Returns the number of ticks for a given channel's transfer. + TickCount GetTransferDelay(Channel channel) const; + // is everything enabled for a channel to operate? bool CanTransferChannel(Channel channel) const; bool CanRunAnyChannels() const; void UpdateIRQ(); - void Transfer(); + void QueueTransferChannel(Channel channel); + void QueueTransfer(); + void TransferChannel(Channel channel); // from device -> memory @@ -84,7 +91,6 @@ private: MDEC* m_mdec = nullptr; std::vector m_transfer_buffer; - bool m_transfer_in_progress = false; struct ChannelState { @@ -124,6 +130,7 @@ private: static constexpr u32 WRITE_MASK = 0b01110001'01110111'00000111'00000011; } channel_control; + TickCount transfer_ticks = 0; bool request = false; }; @@ -199,4 +206,7 @@ private: master_flag = master_enable && ((((bits >> 16) & u32(0b1111111)) & ((bits >> 24) & u32(0b1111111))) != 0); } } m_DICR = {}; + + TickCount m_transfer_min_ticks = 0; + bool m_transfer_in_progress = false; }; diff --git a/src/core/system.cpp b/src/core/system.cpp index 1ba481f4f..c468b07f3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -367,6 +367,9 @@ bool System::SetExpansionROM(const char* filename) void System::Synchronize() { const TickCount pending_ticks = m_cpu->GetPendingTicks(); + if (pending_ticks == 0) + return; + m_cpu->ResetPendingTicks(); m_cpu->ResetDowncount(); @@ -378,6 +381,7 @@ void System::Synchronize() m_pad->Execute(pending_ticks); m_spu->Execute(pending_ticks); m_mdec->Execute(pending_ticks); + m_dma->Execute(pending_ticks); } void System::SetDowncount(TickCount downcount)