Add interrupt controller emulation

This commit is contained in:
Connor McLaughlin 2019-09-17 16:26:00 +10:00
parent c615e007c0
commit 2128a2984b
13 changed files with 271 additions and 19 deletions

View file

@ -80,6 +80,18 @@ struct BitField
return *this;
}
BitField& operator&=(DataType rhs)
{
SetValue(GetValue() & rhs);
return *this;
}
BitField& operator|=(DataType rhs)
{
SetValue(GetValue() & rhs);
return *this;
}
BitField& operator^=(DataType rhs)
{
SetValue(GetValue() ^ rhs);

View file

@ -3,22 +3,34 @@
#include "YBaseLib/Log.h"
#include "YBaseLib/String.h"
#include "common/state_wrapper.h"
#include "cpu_core.h"
#include "cpu_disasm.h"
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include <cstdio>
Log_SetChannel(Bus);
// Offset and value remapping for (w32) registers from nocash docs.
void FixupUnalignedWordAccessW32(u32& offset, u32& value)
{
const u32 byte_offset = offset & u32(3);
offset &= ~u32(3);
value <<= byte_offset * 8;
}
Bus::Bus() = default;
Bus::~Bus() = default;
bool Bus::Initialize(System* system, DMA* dma, GPU* gpu)
bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu)
{
if (!LoadBIOS())
return false;
m_cpu = cpu;
m_dma = dma;
m_interrupt_controller = interrupt_controller;
m_gpu = gpu;
return true;
}
@ -216,6 +228,20 @@ bool Bus::DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value)
return true;
}
bool Bus::DoReadInterruptController(MemoryAccessSize size, u32 offset, u32& value)
{
FixupUnalignedWordAccessW32(offset, value);
value = m_interrupt_controller->ReadRegister(offset);
return true;
}
bool Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value)
{
FixupUnalignedWordAccessW32(offset, value);
m_interrupt_controller->WriteRegister(offset, value);
return true;
}
bool Bus::ReadSPU(MemoryAccessSize size, u32 offset, u32& value)
{
if (offset == 0x1AE)

View file

@ -5,8 +5,15 @@
#include <array>
class StateWrapper;
namespace CPU
{
class Core;
}
class DMA;
class GPU;
class InterruptController;
class System;
class Bus
@ -15,7 +22,7 @@ public:
Bus();
~Bus();
bool Initialize(System* system, DMA* dma, GPU* gpu);
bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu);
void Reset();
bool DoState(StateWrapper& sw);
@ -32,12 +39,15 @@ public:
void PatchBIOS(u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));
private:
static constexpr u32 DMA_BASE = 0x1F801080;
static constexpr u32 DMA_SIZE = 0x80;
static constexpr u32 DMA_MASK = DMA_SIZE - 1;
static constexpr u32 GPU_BASE = 0x1F801810;
static constexpr u32 GPU_SIZE = 0x10;
static constexpr u32 GPU_MASK = GPU_SIZE - 1;
static constexpr u32 INTERRUPT_CONTROLLER_BASE = 0x1F801070;
static constexpr u32 INTERRUPT_CONTROLLER_SIZE = 0x08;
static constexpr u32 INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1;
static constexpr u32 DMA_BASE = 0x1F801080;
static constexpr u32 DMA_SIZE = 0x80;
static constexpr u32 DMA_MASK = DMA_SIZE - 1;
static constexpr u32 SPU_BASE = 0x1F801C00;
static constexpr u32 SPU_SIZE = 0x300;
static constexpr u32 SPU_MASK = 0x3FF;
@ -64,13 +74,18 @@ private:
bool DoReadGPU(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadInterruptController(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadDMA(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value);
bool ReadSPU(MemoryAccessSize size, u32 offset, u32& value);
bool WriteSPU(MemoryAccessSize size, u32 offset, u32 value);
CPU::Core* m_cpu = nullptr;
DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr;
GPU* m_gpu = nullptr;
std::array<u8, 2097152> m_ram{}; // 2MB RAM

View file

@ -80,6 +80,16 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres
{
return DoRAMAccess<type, size>(bus_address, value);
}
else if (bus_address < INTERRUPT_CONTROLLER_BASE)
{
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
}
else if (bus_address < (INTERRUPT_CONTROLLER_BASE + INTERRUPT_CONTROLLER_SIZE))
{
return (type == MemoryAccessType::Read) ?
DoReadInterruptController(size, bus_address & INTERRUPT_CONTROLLER_MASK, value) :
DoWriteInterruptController(size, bus_address & INTERRUPT_CONTROLLER_MASK, value);
}
else if (bus_address < DMA_BASE)
{
return DoInvalidAccess(type, size, cpu_address, bus_address, value);

1
src/pse/cdrom.cpp Normal file
View file

@ -0,0 +1 @@
#include "interrupt_controller.h"

View file

@ -36,6 +36,7 @@ void Core::Reset()
m_cop0_regs.BPCM = 0;
m_cop0_regs.EPC = 0;
m_cop0_regs.sr.bits = 0;
m_cop0_regs.cause.bits = 0;
SetPC(RESET_VECTOR);
}
@ -60,10 +61,13 @@ bool Core::DoState(StateWrapper& sw)
sw.Do(&m_cop0_regs.cause.bits);
sw.Do(&m_cop0_regs.dcic.bits);
sw.Do(&m_next_instruction.bits);
sw.Do(&m_in_branch_delay_slot);
sw.Do(&m_branched);
sw.Do(&m_current_instruction_pc);
sw.Do(&m_load_delay_reg);
sw.Do(&m_load_delay_old_value);
sw.Do(&m_next_load_delay_reg);
sw.Do(&m_next_load_delay_old_value);
sw.Do(&m_in_branch_delay_slot);
sw.Do(&m_branched);
sw.Do(&m_cache_control);
sw.DoBytes(m_dcache.data(), m_dcache.size());
return !sw.HasError();
@ -197,6 +201,28 @@ void Core::RaiseException(Exception excode, u8 coprocessor /* = 0 */)
FlushPipeline();
}
void Core::SetExternalInterrupt(u8 bit)
{
m_cop0_regs.cause.Ip |= static_cast<u8>(1u << bit);
}
void Core::ClearExternalInterrupt(u8 bit)
{
m_cop0_regs.cause.Ip &= static_cast<u8>(~(1u << bit));
}
bool Core::DispatchInterrupts()
{
// const bool do_interrupt = m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0);
const bool do_interrupt =
m_cop0_regs.sr.IEc && (((m_cop0_regs.cause.bits & m_cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0);
if (!do_interrupt)
return false;
RaiseException(Exception::INT);
return true;
}
void Core::FlushLoadDelay()
{
m_load_delay_reg = Reg::count;
@ -284,7 +310,7 @@ TickCount Core::Execute()
m_current_instruction_pc = m_regs.pc;
// fetch the next instruction
if (!FetchInstruction())
if (DispatchInterrupts() || !FetchInstruction())
continue;
// handle branch delays - we are now in a delay slot if we just branched
@ -300,7 +326,7 @@ TickCount Core::Execute()
m_load_delay_old_value = m_next_load_delay_old_value;
m_next_load_delay_old_value = 0;
}
// reset slice ticks, it'll be updated when the components execute
m_slice_ticks = MAX_CPU_SLICE_SIZE;
return executed_ticks;

View file

@ -28,14 +28,11 @@ public:
TickCount Execute();
void SetSliceTicks(TickCount downcount)
{
m_slice_ticks = (downcount < m_slice_ticks ? downcount : m_slice_ticks);
}
const Registers& GetRegs() const { return m_regs; }
Registers& GetRegs() { return m_regs; }
void SetSliceTicks(TickCount downcount) { m_slice_ticks = (downcount < m_slice_ticks ? downcount : m_slice_ticks); }
// Sets the PC and flushes the pipeline.
void SetPC(u32 new_pc);
@ -46,6 +43,10 @@ public:
bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
// External IRQs
void SetExternalInterrupt(u8 bit);
void ClearExternalInterrupt(u8 bit);
private:
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch, bool raise_exceptions>
bool DoMemoryAccess(VirtualMemoryAddress address, u32& value);
@ -78,6 +79,7 @@ private:
// exceptions
u32 GetExceptionVector(Exception excode) const;
void RaiseException(Exception excode, u8 coprocessor = 0);
bool DispatchInterrupts();
// flushes any load delays if present
void FlushLoadDelay();
@ -101,9 +103,8 @@ private:
TickCount m_slice_ticks = 0;
Registers m_regs = {};
Cop0Registers m_cop0_regs = {};
Instruction m_next_instruction = {};
bool m_in_branch_delay_slot = false;
bool m_branched = false;
// address of the instruction currently being executed
u32 m_current_instruction_pc = 0;
@ -113,11 +114,11 @@ private:
u32 m_load_delay_old_value = 0;
Reg m_next_load_delay_reg = Reg::count;
u32 m_next_load_delay_old_value = 0;
bool m_in_branch_delay_slot = false;
bool m_branched = false;
u32 m_cache_control = 0;
Cop0Registers m_cop0_regs = {};
// data cache (used as scratchpad)
std::array<u8, DCACHE_SIZE> m_dcache = {};
};

View file

@ -0,0 +1,86 @@
#include "interrupt_controller.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "cpu_core.h"
Log_SetChannel(InterruptController);
InterruptController::InterruptController() = default;
InterruptController::~InterruptController() = default;
bool InterruptController::Initialize(CPU::Core* cpu)
{
m_cpu = cpu;
return true;
}
void InterruptController::Reset()
{
m_interrupt_status_register = 0;
m_interrupt_mask_register = DEFAULT_INTERRUPT_MASK;
}
bool InterruptController::DoState(StateWrapper& sw)
{
sw.Do(&m_interrupt_status_register);
sw.Do(&m_interrupt_mask_register);
return !sw.HasError();
}
void InterruptController::InterruptRequest(IRQ irq)
{
const u32 bit = (u32(1) << static_cast<u32>(irq));
m_interrupt_status_register |= (bit & m_interrupt_mask_register);
UpdateCPUInterruptRequest();
}
u32 InterruptController::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x00: // I_STATUS
return m_interrupt_status_register;
case 0x04: // I_MASK
return m_interrupt_mask_register;
default:
Log_ErrorPrintf("Invalid read at offset 0x%08X", offset);
return UINT32_C(0xFFFFFFFF);
}
}
void InterruptController::WriteRegister(u32 offset, u32 value)
{
switch (offset)
{
case 0x00: // I_STATUS
{
Log_DebugPrintf("Clearing bits 0x%08X", value);
m_interrupt_status_register = m_interrupt_status_register & (~(value & REGISTER_WRITE_MASK));
UpdateCPUInterruptRequest();
}
break;
case 0x04: // I_MASK
{
Log_DebugPrintf("Interrupt mask <- 0x%08X", value);
m_interrupt_mask_register = value & REGISTER_WRITE_MASK;
}
break;
default:
Log_ErrorPrintf("Invalid write at offset 0x%08X", offset);
break;
}
}
void InterruptController::UpdateCPUInterruptRequest()
{
// external interrupts set bit 10 only?
if (m_interrupt_status_register != 0)
m_cpu->SetExternalInterrupt(3);
else
m_cpu->ClearExternalInterrupt(3);
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "types.h"
class StateWrapper;
namespace CPU
{
class Core;
}
class InterruptController
{
public:
static constexpr u32 NUM_IRQS = 11;
enum class IRQ : u32
{
VBLANK = 0, // IRQ0 - VBLANK
GPU = 1, // IRQ1 - GPU via GP0(1Fh)
CDROM = 2, // IRQ2 - CDROM
DMA = 3, // IRQ3 - DMA
TMR0 = 4, // IRQ4 - TMR0 - Sysclk or Dotclk
TMR1 = 5, // IRQ5 - TMR1 - Sysclk Hblank
TMR2 = 6, // IRQ6 - TMR2 - Sysclk or Sysclk / 8
IRQ7 = 7, // IRQ7 - Controller and Memory Card Byte Received
SIO = 8, // IRQ8 - SIO
SPU = 9, // IRQ9 - SPU
IRQ10 = 10 // IRQ10 - Lightpen interrupt, PIO
};
InterruptController();
~InterruptController();
bool Initialize(CPU::Core* cpu);
void Reset();
bool DoState(StateWrapper& sw);
// Should mirror CPU state.
bool GetIRQLineState() const { return (m_interrupt_status_register != 0); }
// Interupts are edge-triggered, so if it is masked when TriggerInterrupt() is called, it will be lost.
void InterruptRequest(IRQ irq);
// I/O
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
private:
static constexpr u32 REGISTER_WRITE_MASK = (u32(1) << NUM_IRQS) - 1;
static constexpr u32 DEFAULT_INTERRUPT_MASK = (u32(1) << NUM_IRQS) - 1;
void UpdateCPUInterruptRequest();
CPU::Core* m_cpu;
u32 m_interrupt_status_register = 0;
u32 m_interrupt_mask_register = DEFAULT_INTERRUPT_MASK;
};

View file

@ -43,6 +43,7 @@
<ClCompile Include="gpu_hw.cpp" />
<ClCompile Include="gpu_hw_opengl.cpp" />
<ClCompile Include="host_interface.cpp" />
<ClCompile Include="interrupt_controller.cpp" />
<ClCompile Include="system.cpp" />
</ItemGroup>
<ItemGroup>
@ -55,6 +56,7 @@
<ClInclude Include="gpu_hw.h" />
<ClInclude Include="gpu_hw_opengl.h" />
<ClInclude Include="host_interface.h" />
<ClInclude Include="interrupt_controller.h" />
<ClInclude Include="save_state_version.h" />
<ClInclude Include="system.h" />
<ClInclude Include="types.h" />

View file

@ -10,6 +10,7 @@
<ClCompile Include="gpu_hw_opengl.cpp" />
<ClCompile Include="gpu_hw.cpp" />
<ClCompile Include="host_interface.cpp" />
<ClCompile Include="interrupt_controller.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -24,6 +25,7 @@
<ClInclude Include="gpu_hw_opengl.h" />
<ClInclude Include="gpu_hw.h" />
<ClInclude Include="host_interface.h" />
<ClInclude Include="interrupt_controller.h" />
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />

View file

@ -5,12 +5,14 @@
#include "cpu_core.h"
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
System::System(HostInterface* host_interface) : m_host_interface(host_interface)
{
m_cpu = std::make_unique<CPU::Core>();
m_bus = std::make_unique<Bus>();
m_dma = std::make_unique<DMA>();
m_interrupt_controller = std::make_unique<InterruptController>();
// m_gpu = std::make_unique<GPU>();
m_gpu = GPU::CreateHardwareOpenGLRenderer();
}
@ -22,12 +24,15 @@ bool System::Initialize()
if (!m_cpu->Initialize(m_bus.get()))
return false;
if (!m_bus->Initialize(this, m_dma.get(), m_gpu.get()))
if (!m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get()))
return false;
if (!m_dma->Initialize(m_bus.get(), m_gpu.get()))
return false;
if (!m_interrupt_controller->Initialize(m_cpu.get()))
return false;
if (!m_gpu->Initialize(this, m_bus.get(), m_dma.get()))
return false;
@ -45,6 +50,9 @@ bool System::DoState(StateWrapper& sw)
if (!sw.DoMarker("DMA") || !m_dma->DoState(sw))
return false;
if (!sw.DoMarker("InterruptController") || !m_interrupt_controller->DoState(sw))
return false;
if (!sw.DoMarker("GPU") || !m_gpu->DoState(sw))
return false;
@ -58,6 +66,7 @@ void System::Reset()
m_cpu->Reset();
m_bus->Reset();
m_dma->Reset();
m_interrupt_controller->Reset();
m_gpu->Reset();
m_frame_number = 1;
}

View file

@ -13,6 +13,7 @@ class Core;
class Bus;
class DMA;
class InterruptController;
class GPU;
class System
@ -45,6 +46,7 @@ private:
std::unique_ptr<CPU::Core> m_cpu;
std::unique_ptr<Bus> m_bus;
std::unique_ptr<DMA> m_dma;
std::unique_ptr<InterruptController> m_interrupt_controller;
std::unique_ptr<GPU> m_gpu;
u32 m_frame_number = 1;
};