From 2128a2984bb72f21a72831772d32c74adc8f517a Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 17 Sep 2019 16:26:00 +1000 Subject: [PATCH] Add interrupt controller emulation --- src/common/bitfield.h | 12 +++++ src/pse/bus.cpp | 28 ++++++++++- src/pse/bus.h | 23 +++++++-- src/pse/bus.inl | 10 ++++ src/pse/cdrom.cpp | 1 + src/pse/cpu_core.cpp | 34 +++++++++++-- src/pse/cpu_core.h | 19 +++---- src/pse/interrupt_controller.cpp | 86 ++++++++++++++++++++++++++++++++ src/pse/interrupt_controller.h | 60 ++++++++++++++++++++++ src/pse/pse.vcxproj | 2 + src/pse/pse.vcxproj.filters | 2 + src/pse/system.cpp | 11 +++- src/pse/system.h | 2 + 13 files changed, 271 insertions(+), 19 deletions(-) create mode 100644 src/pse/cdrom.cpp create mode 100644 src/pse/interrupt_controller.cpp create mode 100644 src/pse/interrupt_controller.h diff --git a/src/common/bitfield.h b/src/common/bitfield.h index 83abee13e..b331d38f2 100644 --- a/src/common/bitfield.h +++ b/src/common/bitfield.h @@ -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); diff --git a/src/pse/bus.cpp b/src/pse/bus.cpp index eed10a537..04076193d 100644 --- a/src/pse/bus.cpp +++ b/src/pse/bus.cpp @@ -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 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) diff --git a/src/pse/bus.h b/src/pse/bus.h index 81d7dd86f..0ed9879ca 100644 --- a/src/pse/bus.h +++ b/src/pse/bus.h @@ -5,8 +5,15 @@ #include 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 m_ram{}; // 2MB RAM diff --git a/src/pse/bus.inl b/src/pse/bus.inl index b2ffc92dc..b89190642 100644 --- a/src/pse/bus.inl +++ b/src/pse/bus.inl @@ -80,6 +80,16 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres { return DoRAMAccess(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); diff --git a/src/pse/cdrom.cpp b/src/pse/cdrom.cpp new file mode 100644 index 000000000..37dfada43 --- /dev/null +++ b/src/pse/cdrom.cpp @@ -0,0 +1 @@ +#include "interrupt_controller.h" \ No newline at end of file diff --git a/src/pse/cpu_core.cpp b/src/pse/cpu_core.cpp index e4fddc5ff..e685ecefd 100644 --- a/src/pse/cpu_core.cpp +++ b/src/pse/cpu_core.cpp @@ -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(1u << bit); +} + +void Core::ClearExternalInterrupt(u8 bit) +{ + m_cop0_regs.cause.Ip &= static_cast(~(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; diff --git a/src/pse/cpu_core.h b/src/pse/cpu_core.h index 2b20d4f3e..d0f47ea05 100644 --- a/src/pse/cpu_core.h +++ b/src/pse/cpu_core.h @@ -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 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 m_dcache = {}; }; diff --git a/src/pse/interrupt_controller.cpp b/src/pse/interrupt_controller.cpp new file mode 100644 index 000000000..e40b7443b --- /dev/null +++ b/src/pse/interrupt_controller.cpp @@ -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(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); +} diff --git a/src/pse/interrupt_controller.h b/src/pse/interrupt_controller.h new file mode 100644 index 000000000..1fc52a5a1 --- /dev/null +++ b/src/pse/interrupt_controller.h @@ -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; +}; + diff --git a/src/pse/pse.vcxproj b/src/pse/pse.vcxproj index 3f26b44d3..dfa89cd29 100644 --- a/src/pse/pse.vcxproj +++ b/src/pse/pse.vcxproj @@ -43,6 +43,7 @@ + @@ -55,6 +56,7 @@ + diff --git a/src/pse/pse.vcxproj.filters b/src/pse/pse.vcxproj.filters index c4e20b9b4..1aa24e9b2 100644 --- a/src/pse/pse.vcxproj.filters +++ b/src/pse/pse.vcxproj.filters @@ -10,6 +10,7 @@ + @@ -24,6 +25,7 @@ + diff --git a/src/pse/system.cpp b/src/pse/system.cpp index 1aa9e09a9..9e67f3cde 100644 --- a/src/pse/system.cpp +++ b/src/pse/system.cpp @@ -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(); m_bus = std::make_unique(); m_dma = std::make_unique(); + m_interrupt_controller = std::make_unique(); // m_gpu = std::make_unique(); 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; } diff --git a/src/pse/system.h b/src/pse/system.h index 44f71dd9d..c56e95750 100644 --- a/src/pse/system.h +++ b/src/pse/system.h @@ -13,6 +13,7 @@ class Core; class Bus; class DMA; +class InterruptController; class GPU; class System @@ -45,6 +46,7 @@ private: std::unique_ptr m_cpu; std::unique_ptr m_bus; std::unique_ptr m_dma; + std::unique_ptr m_interrupt_controller; std::unique_ptr m_gpu; u32 m_frame_number = 1; };