Basic timer implementation

This commit is contained in:
Connor McLaughlin 2019-09-20 23:40:19 +10:00
parent ad652c47ed
commit ad316162f3
14 changed files with 375 additions and 16 deletions

View file

@ -10,6 +10,7 @@
#include "gpu.h"
#include "interrupt_controller.h"
#include "pad.h"
#include "timers.h"
#include <cstdio>
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)

View file

@ -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<u8, 2097152> m_ram{}; // 2MB RAM
std::array<u8, 524288> m_bios{}; // 512K BIOS ROM

View file

@ -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);

View file

@ -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);

View file

@ -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,15 +283,24 @@ 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)
{
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?
if (m_crtc_state.current_scanline >= m_crtc_state.total_scanlines_per_frame)
{

View file

@ -1,5 +1,6 @@
#pragma once
#include "common/bitfield.h"
#include "timers.h"
#include "types.h"
#include <array>
#include <deque>
@ -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
{

View file

@ -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();

View file

@ -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:

View file

@ -50,6 +50,7 @@
<ClCompile Include="pad.cpp" />
<ClCompile Include="pad_device.cpp" />
<ClCompile Include="system.cpp" />
<ClCompile Include="timers.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="bus.h" />
@ -70,6 +71,7 @@
<ClInclude Include="pad_device.h" />
<ClInclude Include="save_state_version.h" />
<ClInclude Include="system.h" />
<ClInclude Include="timers.h" />
<ClInclude Include="types.h" />
</ItemGroup>
<ItemGroup>

View file

@ -16,6 +16,7 @@
<ClCompile Include="pad.cpp" />
<ClCompile Include="pad_device.cpp" />
<ClCompile Include="digital_controller.cpp" />
<ClCompile Include="timers.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -37,6 +38,7 @@
<ClInclude Include="pad.h" />
<ClInclude Include="pad_device.h" />
<ClInclude Include="digital_controller.h" />
<ClInclude Include="timers.h" />
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />

View file

@ -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<CDROM>();
m_pad = std::make_unique<Pad>();
m_timers = std::make_unique<Timers>();
}
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);
}
}

View file

@ -19,6 +19,7 @@ class GPU;
class CDROM;
class Pad;
class PadDevice;
class Timers;
class System
{
@ -60,5 +61,6 @@ private:
std::unique_ptr<GPU> m_gpu;
std::unique_ptr<CDROM> m_cdrom;
std::unique_ptr<Pad> m_pad;
std::unique_ptr<Timers> m_timers;
u32 m_frame_number = 1;
};

203
src/pse/timers.cpp Normal file
View file

@ -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<InterruptController::IRQ>(static_cast<u32>(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;
}

76
src/pse/timers.h Normal file
View file

@ -0,0 +1,76 @@
#pragma once
#include "common/bitfield.h"
#include "types.h"
#include <array>
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<u32, bool, 0, 1> sync_enable;
BitField<u32, SyncMode, 1, 2> sync_mode;
BitField<u32, bool, 3, 1> reset_at_target;
BitField<u32, bool, 4, 1> irq_at_target;
BitField<u32, bool, 5, 1> irq_on_overflow;
BitField<u32, bool, 6, 1> irq_repeat;
BitField<u32, bool, 7, 1> irq_pulse;
BitField<u32, u8, 8, 2> clock_source;
BitField<u32, bool, 10, 1> interrupt_request;
BitField<u32, bool, 11, 1> reached_target;
BitField<u32, bool, 12, 1> 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<CounterState, NUM_TIMERS> m_states{};
};