From ad316162f319906d01f3435c41c9757a66c8370b Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 20 Sep 2019 23:40:19 +1000 Subject: [PATCH] Basic timer implementation --- src/pse/bus.cpp | 19 +++- src/pse/bus.h | 10 +- src/pse/bus.inl | 9 ++ src/pse/cdrom.cpp | 11 ++ src/pse/gpu.cpp | 27 +++-- src/pse/gpu.h | 7 +- src/pse/gpu_hw_opengl.cpp | 4 +- src/pse/gpu_hw_opengl.h | 2 +- src/pse/pse.vcxproj | 2 + src/pse/pse.vcxproj.filters | 2 + src/pse/system.cpp | 17 ++- src/pse/system.h | 2 + src/pse/timers.cpp | 203 ++++++++++++++++++++++++++++++++++++ src/pse/timers.h | 76 ++++++++++++++ 14 files changed, 375 insertions(+), 16 deletions(-) create mode 100644 src/pse/timers.cpp create mode 100644 src/pse/timers.h diff --git a/src/pse/bus.cpp b/src/pse/bus.cpp index c14d99ed7..a68d2d19f 100644 --- a/src/pse/bus.cpp +++ b/src/pse/bus.cpp @@ -10,6 +10,7 @@ #include "gpu.h" #include "interrupt_controller.h" #include "pad.h" +#include "timers.h" #include Log_SetChannel(Bus); @@ -25,7 +26,8 @@ Bus::Bus() = default; Bus::~Bus() = default; -bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad) +bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, + Pad* pad, Timers* timers) { if (!LoadBIOS()) return false; @@ -36,6 +38,7 @@ bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_co m_gpu = gpu; m_cdrom = cdrom; m_pad = pad; + m_timers = timers; return true; } @@ -274,6 +277,20 @@ bool Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 valu return true; } +bool Bus::DoReadTimers(MemoryAccessSize size, u32 offset, u32& value) +{ + FixupUnalignedWordAccessW32(offset, value); + value = m_timers->ReadRegister(offset); + return true; +} + +bool Bus::DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value) +{ + FixupUnalignedWordAccessW32(offset, value); + m_timers->WriteRegister(offset, value); + return true; +} + bool Bus::ReadSPU(MemoryAccessSize size, u32 offset, u32& value) { if (offset == 0x1AE) diff --git a/src/pse/bus.h b/src/pse/bus.h index 94dc46c2b..81d576406 100644 --- a/src/pse/bus.h +++ b/src/pse/bus.h @@ -16,6 +16,7 @@ class InterruptController; class GPU; class CDROM; class Pad; +class Timers; class System; class Bus @@ -24,7 +25,7 @@ public: Bus(); ~Bus(); - bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad); + bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad, Timers* timers); void Reset(); bool DoState(StateWrapper& sw); @@ -50,6 +51,9 @@ private: static constexpr u32 DMA_BASE = 0x1F801080; static constexpr u32 DMA_SIZE = 0x80; static constexpr u32 DMA_MASK = DMA_SIZE - 1; + static constexpr u32 TIMERS_BASE = 0x1F801100; + static constexpr u32 TIMERS_SIZE = 0x40; + static constexpr u32 TIMERS_MASK = TIMERS_SIZE - 1; static constexpr u32 CDROM_BASE = 0x1F801800; static constexpr u32 CDROM_SIZE = 0x04; static constexpr u32 CDROM_MASK = CDROM_SIZE - 1; @@ -94,6 +98,9 @@ private: bool DoReadDMA(MemoryAccessSize size, u32 offset, u32& value); bool DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value); + bool DoReadTimers(MemoryAccessSize size, u32 offset, u32& value); + bool DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value); + bool ReadSPU(MemoryAccessSize size, u32 offset, u32& value); bool WriteSPU(MemoryAccessSize size, u32 offset, u32 value); @@ -103,6 +110,7 @@ private: GPU* m_gpu = nullptr; CDROM* m_cdrom = nullptr; Pad* m_pad = nullptr; + Timers* m_timers = nullptr; std::array m_ram{}; // 2MB RAM std::array m_bios{}; // 512K BIOS ROM diff --git a/src/pse/bus.inl b/src/pse/bus.inl index cdcea12c0..9fd682009 100644 --- a/src/pse/bus.inl +++ b/src/pse/bus.inl @@ -108,6 +108,15 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres return (type == MemoryAccessType::Read) ? DoReadDMA(size, bus_address & DMA_MASK, value) : DoWriteDMA(size, bus_address & DMA_MASK, value); } + else if (bus_address < TIMERS_BASE) + { + return DoInvalidAccess(type, size, cpu_address, bus_address, value); + } + else if (bus_address < (TIMERS_BASE + TIMERS_SIZE)) + { + return (type == MemoryAccessType::Read) ? DoReadTimers(size, bus_address & TIMERS_MASK, value) : + DoWriteTimers(size, bus_address & TIMERS_MASK, value); + } else if (bus_address < CDROM_BASE) { return DoInvalidAccess(type, size, cpu_address, bus_address, value); diff --git a/src/pse/cdrom.cpp b/src/pse/cdrom.cpp index f2a46d1aa..c45f733a2 100644 --- a/src/pse/cdrom.cpp +++ b/src/pse/cdrom.cpp @@ -304,6 +304,17 @@ void CDROM::ExecuteTestCommand(u8 subcommand) return; } + case 0x22: + { + Log_DebugPrintf("Get CDROM region ID string"); + static constexpr u8 response[] = {'f', 'o', 'r', ' ', 'U', '/', 'C'}; + m_response_fifo.PushRange(response, countof(response)); + m_param_fifo.Clear(); + SetInterrupt(Interrupt::INT3); + UpdateStatusRegister(); + return; + } + default: { Log_ErrorPrintf("Unknown test command 0x%02X", subcommand); diff --git a/src/pse/gpu.cpp b/src/pse/gpu.cpp index 313c352bc..09e3c6a8f 100644 --- a/src/pse/gpu.cpp +++ b/src/pse/gpu.cpp @@ -5,6 +5,7 @@ #include "interrupt_controller.h" #include "stb_image_write.h" #include "system.h" +#include "timers.h" Log_SetChannel(GPU); bool GPU::DUMP_CPU_TO_VRAM_COPIES = false; @@ -16,11 +17,12 @@ GPU::GPU() = default; GPU::~GPU() = default; -bool GPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller) +bool GPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers) { m_system = system; m_dma = dma; m_interrupt_controller = interrupt_controller; + m_timers = timers; return true; } @@ -254,12 +256,14 @@ void GPU::UpdateCRTCConfig() void GPU::UpdateSliceTicks() { // the next event is at the end of the next scanline - // const TickCount ticks_until_next_event = m_crtc_state.ticks_per_scanline - m_crtc_state.current_tick_in_scanline; - +#if 1 + const TickCount ticks_until_next_event = m_crtc_state.ticks_per_scanline - m_crtc_state.current_tick_in_scanline; +#else // or at vblank. this will depend on the timer config.. const TickCount ticks_until_next_event = ((m_crtc_state.total_scanlines_per_frame - m_crtc_state.current_scanline) * m_crtc_state.ticks_per_scanline) - m_crtc_state.current_tick_in_scanline; +#endif // convert to master clock, rounding up as we want to overshoot not undershoot const TickCount system_ticks = (ticks_until_next_event * 7 + 10) / 11; @@ -279,13 +283,22 @@ void GPU::Execute(TickCount ticks) { m_crtc_state.current_tick_in_scanline -= m_crtc_state.ticks_per_scanline; m_crtc_state.current_scanline++; + if (m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX)) + m_timers->AddTicks(HBLANK_TIMER_INDEX, 1); const bool old_vblank = m_crtc_state.in_vblank; - m_crtc_state.in_vblank = m_crtc_state.current_scanline >= m_crtc_state.visible_vertical_resolution; - if (m_crtc_state.in_vblank && !old_vblank) + const bool new_vblank = m_crtc_state.current_scanline >= m_crtc_state.visible_vertical_resolution; + if (new_vblank != old_vblank) { - Log_DebugPrintf("Now in v-blank"); - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK); + m_crtc_state.in_vblank = new_vblank; + + if (!old_vblank) + { + Log_DebugPrintf("Now in v-blank"); + m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK); + } + + m_timers->SetGate(HBLANK_TIMER_INDEX, new_vblank); } // past the end of vblank? diff --git a/src/pse/gpu.h b/src/pse/gpu.h index d2fd020f2..ae542fe77 100644 --- a/src/pse/gpu.h +++ b/src/pse/gpu.h @@ -1,5 +1,6 @@ #pragma once #include "common/bitfield.h" +#include "timers.h" #include "types.h" #include #include @@ -10,6 +11,7 @@ class StateWrapper; class System; class DMA; class InterruptController; +class Timers; class GPU { @@ -17,7 +19,7 @@ public: GPU(); virtual ~GPU(); - virtual bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller); + virtual bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers); virtual void Reset(); virtual bool DoState(StateWrapper& sw); @@ -39,6 +41,8 @@ protected: static constexpr u32 VRAM_SIZE = VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16); static constexpr u32 TEXTURE_PAGE_WIDTH = 256; static constexpr u32 TEXTURE_PAGE_HEIGHT = 256; + static constexpr u32 DOT_TIMER_INDEX = 0; + static constexpr u32 HBLANK_TIMER_INDEX = 1; static constexpr s32 S11ToS32(u32 value) { @@ -184,6 +188,7 @@ protected: System* m_system = nullptr; DMA* m_dma = nullptr; InterruptController* m_interrupt_controller = nullptr; + Timers* m_timers = nullptr; union GPUSTAT { diff --git a/src/pse/gpu_hw_opengl.cpp b/src/pse/gpu_hw_opengl.cpp index 0d3b20887..b77e60b27 100644 --- a/src/pse/gpu_hw_opengl.cpp +++ b/src/pse/gpu_hw_opengl.cpp @@ -12,9 +12,9 @@ GPU_HW_OpenGL::~GPU_HW_OpenGL() DestroyFramebuffer(); } -bool GPU_HW_OpenGL::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller) +bool GPU_HW_OpenGL::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers) { - if (!GPU_HW::Initialize(system, dma, interrupt_controller)) + if (!GPU_HW::Initialize(system, dma, interrupt_controller, timers)) return false; CreateFramebuffer(); diff --git a/src/pse/gpu_hw_opengl.h b/src/pse/gpu_hw_opengl.h index 503c068ba..8287706f1 100644 --- a/src/pse/gpu_hw_opengl.h +++ b/src/pse/gpu_hw_opengl.h @@ -13,7 +13,7 @@ public: GPU_HW_OpenGL(); ~GPU_HW_OpenGL() override; - bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller) override; + bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers) override; void Reset() override; protected: diff --git a/src/pse/pse.vcxproj b/src/pse/pse.vcxproj index 61ae25e0a..89d27835b 100644 --- a/src/pse/pse.vcxproj +++ b/src/pse/pse.vcxproj @@ -50,6 +50,7 @@ + @@ -70,6 +71,7 @@ + diff --git a/src/pse/pse.vcxproj.filters b/src/pse/pse.vcxproj.filters index 3ab922a7f..d9f6f35f8 100644 --- a/src/pse/pse.vcxproj.filters +++ b/src/pse/pse.vcxproj.filters @@ -16,6 +16,7 @@ + @@ -37,6 +38,7 @@ + diff --git a/src/pse/system.cpp b/src/pse/system.cpp index 9df5a9125..1df20ac31 100644 --- a/src/pse/system.cpp +++ b/src/pse/system.cpp @@ -9,6 +9,7 @@ #include "interrupt_controller.h" #include "pad.h" #include "pad_device.h" +#include "timers.h" System::System(HostInterface* host_interface) : m_host_interface(host_interface) { @@ -20,6 +21,7 @@ System::System(HostInterface* host_interface) : m_host_interface(host_interface) m_gpu = GPU::CreateHardwareOpenGLRenderer(); m_cdrom = std::make_unique(); m_pad = std::make_unique(); + m_timers = std::make_unique(); } System::~System() = default; @@ -30,7 +32,7 @@ bool System::Initialize() return false; if (!m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(), - m_pad.get())) + m_pad.get(), m_timers.get())) { return false; } @@ -41,7 +43,7 @@ bool System::Initialize() if (!m_interrupt_controller->Initialize(m_cpu.get())) return false; - if (!m_gpu->Initialize(this, m_dma.get(), m_interrupt_controller.get())) + if (!m_gpu->Initialize(this, m_dma.get(), m_interrupt_controller.get(), m_timers.get())) return false; if (!m_cdrom->Initialize(m_dma.get(), m_interrupt_controller.get())) @@ -50,6 +52,9 @@ bool System::Initialize() if (!m_pad->Initialize(m_interrupt_controller.get())) return false; + if (!m_timers->Initialize(m_interrupt_controller.get())) + return false; + return true; } @@ -76,6 +81,9 @@ bool System::DoState(StateWrapper& sw) if (!sw.DoMarker("Pad") || !m_pad->DoState(sw)) return false; + if (!sw.DoMarker("Timers") || !m_timers->DoState(sw)) + return false; + return !sw.HasError(); } @@ -90,6 +98,7 @@ void System::Reset() m_gpu->Reset(); m_cdrom->Reset(); m_pad->Reset(); + m_timers->Reset(); m_frame_number = 1; } @@ -113,7 +122,9 @@ void System::RunFrame() const TickCount pending_ticks = m_cpu->Execute(); // run pending ticks from CPU for other components - m_gpu->Execute(pending_ticks); + m_gpu->Execute(pending_ticks * 3); + + m_timers->AddTicks(2, m_timers->IsUsingExternalClock(2) ? (pending_ticks / 8) : pending_ticks); } } diff --git a/src/pse/system.h b/src/pse/system.h index f08ab02fe..14d1c1996 100644 --- a/src/pse/system.h +++ b/src/pse/system.h @@ -19,6 +19,7 @@ class GPU; class CDROM; class Pad; class PadDevice; +class Timers; class System { @@ -60,5 +61,6 @@ private: std::unique_ptr m_gpu; std::unique_ptr m_cdrom; std::unique_ptr m_pad; + std::unique_ptr m_timers; u32 m_frame_number = 1; }; diff --git a/src/pse/timers.cpp b/src/pse/timers.cpp new file mode 100644 index 000000000..21bea1b5e --- /dev/null +++ b/src/pse/timers.cpp @@ -0,0 +1,203 @@ +#include "timers.h" +#include "YBaseLib/Log.h" +#include "common/state_wrapper.h" +#include "interrupt_controller.h" +Log_SetChannel(Timers); + +Timers::Timers() = default; + +Timers::~Timers() = default; + +bool Timers::Initialize(InterruptController* interrupt_controller) +{ + m_interrupt_controller = interrupt_controller; + return true; +} + +void Timers::Reset() +{ + for (CounterState& cs : m_states) + { + cs.mode.bits = 0; + cs.counter = 0; + cs.target = 0; + cs.gate = false; + cs.external_counting_enabled = false; + cs.counting_enabled = true; + } +} + +bool Timers::DoState(StateWrapper& sw) +{ + for (CounterState& cs : m_states) + { + sw.Do(&cs.mode.bits); + sw.Do(&cs.counter); + sw.Do(&cs.target); + sw.Do(&cs.gate); + sw.Do(&cs.external_counting_enabled); + sw.Do(&cs.counting_enabled); + } + + return !sw.HasError(); +} + +void Timers::SetGate(u32 timer, bool state) +{ + CounterState& cs = m_states[timer]; + if (cs.gate == state) + return; + + cs.gate = state; + + if (cs.mode.sync_enable) + { + if (state) + { + switch (cs.mode.sync_mode) + { + case SyncMode::ResetOnGate: + case SyncMode::ResetAndRunOnGate: + cs.counter = 0; + break; + + case SyncMode::FreeRunOnGate: + cs.mode.sync_enable = false; + break; + } + } + + UpdateCountingEnabled(cs); + } +} + +void Timers::AddTicks(u32 timer, u32 count) +{ + CounterState& cs = m_states[timer]; + cs.counter += count; + + const u32 reset_value = cs.mode.reset_at_target ? cs.target : u32(0xFFFF); + if (cs.counter < reset_value) + return; + + const bool old_intr = cs.mode.interrupt_request; + + if (cs.counter >= cs.target) + cs.mode.reached_target = true; + if (cs.counter >= u32(0xFFFF)) + cs.mode.reached_overflow = true; + + // TODO: Non-repeat mode. + const bool target_intr = cs.mode.reached_target & cs.mode.irq_at_target; + const bool overflow_intr = cs.mode.reached_overflow & cs.mode.irq_on_overflow; + const bool new_intr = target_intr | overflow_intr; + if (!old_intr && new_intr) + { + m_interrupt_controller->InterruptRequest( + static_cast(static_cast(InterruptController::IRQ::TMR0) + timer)); + } + + if (reset_value > 0) + cs.counter = cs.counter % reset_value; + else + cs.counter = 0; +} + +u32 Timers::ReadRegister(u32 offset) +{ + const u32 timer_index = (offset >> 4) & u32(0x03); + const u32 port_offset = offset & u32(0x0F); + + CounterState& cs = m_states[timer_index]; + + switch (port_offset) + { + case 0x00: + return cs.counter; + + case 0x04: + { + const u32 bits = cs.mode.bits; + cs.mode.reached_overflow = false; + cs.mode.reached_target = false; + } + break; + + case 0x08: + return cs.target; + + default: + Log_ErrorPrintf("Read unknown register in timer %u (offset 0x%02X)", offset); + return UINT32_C(0xFFFFFFFF); + } +} + +void Timers::WriteRegister(u32 offset, u32 value) +{ + const u32 timer_index = (offset >> 4) & u32(0x03); + const u32 port_offset = offset & u32(0x0F); + + CounterState& cs = m_states[timer_index]; + + switch (port_offset) + { + case 0x00: + Log_DebugPrintf("Timer %u write counter %u", timer_index, value); + cs.counter = value & u32(0xFFFF); + break; + + case 0x04: + { + Log_DebugPrintf("Timer %u write mode register 0x%04X", timer_index, value); + cs.mode.bits = value & u32(0x1FFF); + cs.use_external_clock = (cs.mode.clock_source & (timer_index == 2 ? 2 : 1)) != 0; + cs.counter = 0; + UpdateCountingEnabled(cs); + } + break; + + case 0x08: + Log_DebugPrintf("Timer %u write target 0x%04X", timer_index, ZeroExtend32(Truncate16(value))); + cs.target = value & u32(0xFFFF); + break; + + default: + Log_ErrorPrintf("Write unknown register in timer %u (offset 0x%02X, value 0x%X)", offset, value); + break; + } +} + +void Timers::UpdateCountingEnabled(CounterState& cs) +{ + if (cs.mode.sync_enable) + { + switch (cs.mode.sync_mode) + { + case SyncMode::PauseOnGate: + case SyncMode::FreeRunOnGate: + cs.counting_enabled = !cs.gate; + break; + + case SyncMode::ResetOnGate: + cs.counting_enabled = true; + break; + + case SyncMode::ResetAndRunOnGate: + cs.counting_enabled = cs.gate; + break; + } + } + else + { + cs.counting_enabled = true; + } + + cs.external_counting_enabled = cs.use_external_clock && cs.counting_enabled; +} + +void Timers::UpdateDowncount() {} + +u32 Timers::GetSystemTicksForTimerTicks(u32 timer) const +{ + return 1; +} diff --git a/src/pse/timers.h b/src/pse/timers.h new file mode 100644 index 000000000..ec30b5e44 --- /dev/null +++ b/src/pse/timers.h @@ -0,0 +1,76 @@ +#pragma once +#include "common/bitfield.h" +#include "types.h" +#include + +class StateWrapper; + +class InterruptController; + +class Timers +{ +public: + Timers(); + ~Timers(); + + bool Initialize(InterruptController* interrupt_controller); + void Reset(); + bool DoState(StateWrapper& sw); + + void SetGate(u32 timer, bool state); + + // dot clock/hblank/sysclk div 8 + bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; } + void AddTicks(u32 timer, u32 ticks); + + u32 ReadRegister(u32 offset); + void WriteRegister(u32 offset, u32 value); + +private: + static constexpr u32 NUM_TIMERS = 3; + + enum class SyncMode : u8 + { + PauseOnGate = 0, + ResetOnGate = 1, + ResetAndRunOnGate = 2, + FreeRunOnGate = 3 + }; + + union CounterMode + { + u32 bits; + + BitField sync_enable; + BitField sync_mode; + BitField reset_at_target; + BitField irq_at_target; + BitField irq_on_overflow; + BitField irq_repeat; + BitField irq_pulse; + BitField clock_source; + BitField interrupt_request; + BitField reached_target; + BitField reached_overflow; + }; + + struct CounterState + { + CounterMode mode; + u32 counter; + u32 target; + bool gate; + bool use_external_clock; + bool external_counting_enabled; + bool counting_enabled; + }; + + void UpdateCountingEnabled(CounterState& cs); + + void UpdateDowncount(); + u32 GetSystemTicksForTimerTicks(u32 timer) const; + + InterruptController* m_interrupt_controller = nullptr; + + std::array m_states{}; +};