From 5b4dbdc9a4a5eaf239ec2698afb2b62fd6659b1c Mon Sep 17 00:00:00 2001 From: Riyyi Date: Fri, 2 Sep 2022 22:38:02 +0200 Subject: [PATCH] Emulator: Add interrupt handling --- src/cpu.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cpu.h | 24 ++++++++++++++++++---- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/cpu.cpp b/src/cpu.cpp index 51d12cd..3d14898 100644 --- a/src/cpu.cpp +++ b/src/cpu.cpp @@ -55,8 +55,66 @@ CPU::~CPU() // ----------------------------------------- +void CPU::handleInterrupt(uint32_t interrupt_flag, uint8_t interrupt_source, uint8_t address) +{ + // Clear interrupt + m_ime = 0; + Emu::the().writeMemory(0xff0f, interrupt_flag & (~interrupt_source)); + + // Call + + m_wait_cycles += 20; + + // Push address of the program counter on the stack, such that RET can pop it later + m_sp = (m_sp - 1) & 0xffff; + write(m_sp, m_pc >> 8); // msb(PC) + m_sp = (m_sp - 1) & 0xffff; + write(m_sp, m_pc & 0xff); // lsb(PC) + + // Jump to address + m_pc = address; +} + void CPU::update() { + // ------------------------------------- + // Interrupt Service Routine + + bool effective_ime = m_ime; + + // IME only becomes active after the instruction following EI + if (m_should_enable_ime) { + m_ime = 1; + m_should_enable_ime = 0; + } + + if (effective_ime) { + // Get the 5 lower bits of the IE (interrupt enable) address + uint32_t interrupt_enabled = Emu::the().readMemory(0xffff) & 0x1f; + // Get the 5 lower bits of the IF (interrupt flag) address + uint32_t interrupt_flag = Emu::the().readMemory(0xff0f) & 0x1f; + + uint32_t interrupt = interrupt_enabled & interrupt_flag; + if (interrupt & InterruptRequest::VBlank) { + handleInterrupt(interrupt_flag, InterruptRequest::VBlank, 0x40); + } + else if (interrupt & InterruptRequest::STAT) { + handleInterrupt(interrupt_flag, InterruptRequest::STAT, 0x48); + } + else if (interrupt & InterruptRequest::Timer) { + handleInterrupt(interrupt_flag, InterruptRequest::Timer, 0x50); + } + else if (interrupt & InterruptRequest::Serial) { + handleInterrupt(interrupt_flag, InterruptRequest::Serial, 0x58); + } + else if (interrupt & InterruptRequest::Joypad) { + handleInterrupt(interrupt_flag, InterruptRequest::Joypad, 0x60); + } + } + + // ------------------------------------- + // Run opcodes + m_wait_cycles--; // TODO: convert to early-return if (m_wait_cycles <= 0) { diff --git a/src/cpu.h b/src/cpu.h index d12a42a..146d586 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -13,6 +13,19 @@ #include "processing-unit.h" #include "ruc/format/formatter.h" +#include "ruc/meta/core.h" + +namespace InterruptRequest { + +enum Enum : uint8_t { + VBlank = BIT(0), + STAT = BIT(1), + Timer = BIT(2), + Serial = BIT(3), + Joypad = BIT(4), +}; + +} // namespace InterruptRequest class CPU final : public ProcessingUnit { private: @@ -22,6 +35,7 @@ public: explicit CPU(uint32_t frequency); virtual ~CPU(); + void handleInterrupt(uint32_t interrupt_flag, uint8_t interrupt_source, uint8_t address); void update() override; // ------------------------------------- @@ -147,11 +161,13 @@ private: uint32_t m_sp { 0 }; // Stack Pointer // Flags - uint32_t m_zf { 0 }; // Zero flag - uint32_t m_nf { 0 }; // Subtraction flag (BCD) - uint32_t m_hf { 0 }; // Half Carry flag (BCD) - uint32_t m_cf { 0 }; // Carry flag + uint32_t m_zf { 0 }; // Zero flag + uint32_t m_nf { 0 }; // Subtraction flag (BCD) + uint32_t m_hf { 0 }; // Half Carry flag (BCD) + uint32_t m_cf { 0 }; // Carry flag + uint32_t m_ime { 0 }; // Interrupt Master Enable flag + bool m_should_enable_ime { 0 }; int8_t m_wait_cycles { 0 }; };