You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
8.3 KiB
257 lines
8.3 KiB
/* SharpLr35902.cpp |
|
* |
|
* An implementation of the Sharp LR35902 (GameBoy) CPU. |
|
*/ |
|
|
|
#include <sstream> |
|
|
|
#include "../../PlipEmulationException.h" |
|
#include "../../PlipUtility.h" |
|
|
|
#include "SharpLr35902.h" |
|
#include "SharpLr35902.Macros.h" |
|
|
|
namespace Plip::Cpu { |
|
SharpLr35902::SharpLr35902(long hz, PlipMemoryMap *memoryMap) |
|
: PlipCpu(hz, memoryMap) { |
|
PerformReset(); |
|
} |
|
|
|
void SharpLr35902::Cycle() { |
|
// Decode an instruction if there's one in the cache and the CPU isn't |
|
// halted. |
|
if(!m_instr.empty() && !m_halt) |
|
Decode(); |
|
|
|
// Handle interrupts here if (1) we're ready to fetch, (2) interrupts |
|
// are enabled, and (3) an interrupt request has been made. |
|
auto iFlag = m_memory->GetByte(MemInterruptFlag) & 0b00011111; |
|
if(m_allowFetch && m_ime == ScheduledState::Enabled && iFlag && !m_isr) { |
|
auto ie = m_memory->GetByte(MemInterruptEnabled); |
|
|
|
// Check for an enabled interrupt. |
|
m_isr = false; |
|
for(m_isrIdx = 0; m_isrIdx <= 4; m_isrIdx++) { |
|
if(ie & (iFlag & (1 << m_isrIdx))) { |
|
m_isr = true; |
|
break; |
|
} |
|
} |
|
|
|
if(m_isr) { |
|
// Set up the CPU to jump to the interrupt handler. |
|
m_allowFetch = false; |
|
m_ime = ScheduledState::Disabled; |
|
m_isr = true; |
|
m_mcycle = 1; |
|
} |
|
} |
|
|
|
if(m_isr) { |
|
CYCLE(3) { |
|
// Do one last check on IE. If this interrupt is no longer |
|
// enabled, cancel the interrupt request. |
|
auto ie = m_memory->GetByte(MemInterruptEnabled); |
|
m_cancelInterrupt = !(ie & (1 << m_isrIdx)); |
|
STACK_PUSH(m_reg.pc >> 8); |
|
} |
|
CYCLE(4) { |
|
STACK_PUSH(m_reg.pc & 0xFF); |
|
} |
|
CYCLE(5) { |
|
// If the interrupt was cancelled, set PC to 0x0000 instead |
|
// of the interrupt vector and skip updating IF. |
|
if(m_cancelInterrupt) { |
|
m_reg.pc = 0x40 + (m_isrIdx * 0x8); |
|
m_memory->SetByte(MemInterruptFlag, iFlag ^ (1 << m_isrIdx)); |
|
} else { |
|
m_reg.pc = 0; |
|
} |
|
|
|
m_isr = false; |
|
} |
|
NUM_MCYCLES(5); |
|
} |
|
|
|
// The LR35902 allows the next instruction to be fetched when the |
|
// previous instruction finishes its last execute stage. |
|
if(m_allowFetch && !m_halt && !m_isr) { |
|
FETCH; |
|
BEGIN_EXECUTE; |
|
} |
|
|
|
// Enable interrupts if they are scheduled. |
|
if(m_ime == ScheduledState::Scheduled) m_ime = ScheduledState::Enabled; |
|
} |
|
|
|
std::string SharpLr35902::DumpRegisters() const { |
|
using util = PlipUtility; |
|
std::stringstream dump; |
|
|
|
dump << util::DumpValue(" A", m_reg.a, 2) << '\n' |
|
<< util::DumpValue(" F", m_reg.f, 2) << '\n' |
|
<< util::DumpValue(" B", m_reg.b, 2) << '\n' |
|
<< util::DumpValue(" C", m_reg.c, 2) << '\n' |
|
<< util::DumpValue(" D", m_reg.d, 2) << '\n' |
|
<< util::DumpValue(" E", m_reg.e, 2) << '\n' |
|
<< util::DumpValue(" H", m_reg.h, 2) << '\n' |
|
<< util::DumpValue(" L", m_reg.l, 2) << "\n\n" |
|
<< util::DumpValue("PC", m_reg.pc, 4) << '\n' |
|
<< util::DumpValue("SP", m_reg.sp, 4) << "\n" |
|
<< "IME: " << (m_ime == ScheduledState::Enabled ? '1' : '0') << "\n\n" |
|
<< "Instruction Cache:"; |
|
|
|
if(m_instr.empty()) { |
|
dump << " [[empty]]"; |
|
} else { |
|
for(auto i : m_instr) |
|
dump << " " << util::FormatHex(i, 2); |
|
} |
|
dump << "\nMCycle: " << std::to_string(m_mcycle); |
|
|
|
return dump.str(); |
|
} |
|
|
|
uint16_t SharpLr35902::GetRegister16Pointer(uint8_t idx) { |
|
uint16_t val; |
|
|
|
switch(idx) { |
|
case ADDR_BC: return COMBINE16LE(m_reg.b, m_reg.c); |
|
case ADDR_DE: return COMBINE16LE(m_reg.d, m_reg.e); |
|
|
|
case ADDR_HLP: |
|
val = COMBINE16LE(m_reg.h, m_reg.l); |
|
IncPair(&(m_reg.h), &(m_reg.l)); |
|
return val; |
|
|
|
case ADDR_HLM: |
|
val = COMBINE16LE(m_reg.h, m_reg.l); |
|
DecPair(&(m_reg.h), &(m_reg.l)); |
|
return val; |
|
|
|
default: |
|
std::stringstream ex; |
|
ex << "invalid address register pair index: " |
|
<< PlipUtility::FormatHex(idx, 2) |
|
<< "\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
} |
|
|
|
uint16_t SharpLr35902::GetRegister16Value(uint8_t idx) { |
|
switch(idx) { |
|
case IDX_16_BC: |
|
return REG_BC; |
|
case IDX_16_DE: |
|
return REG_DE; |
|
case IDX_16_HL: |
|
return REG_HL; |
|
case IDX_16_SP: |
|
return m_reg.sp; |
|
default: |
|
std::stringstream ex; |
|
ex << "invalid 16-bit register pair index: " |
|
<< PlipUtility::FormatHex(idx, 2) |
|
<< "\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
} |
|
|
|
uint8_t* SharpLr35902::GetRegister8(uint8_t idx) { |
|
switch(idx) { |
|
case IDX_A: return &(m_reg.a); |
|
case IDX_B: return &(m_reg.b); |
|
case IDX_C: return &(m_reg.c); |
|
case IDX_D: return &(m_reg.d); |
|
case IDX_E: return &(m_reg.e); |
|
case IDX_H: return &(m_reg.h); |
|
case IDX_L: return &(m_reg.l); |
|
default: |
|
std::stringstream ex; |
|
ex << "invalid 8-bit register index: " |
|
<< PlipUtility::FormatHex(idx, 2) |
|
<< "\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
} |
|
|
|
std::tuple<uint8_t*, uint8_t*> SharpLr35902::GetRegisterPair(uint8_t idx) { |
|
uint8_t *high, *low; |
|
switch(idx) { |
|
case IDX_16_BC: |
|
high = &(m_reg.b); |
|
low = &(m_reg.c); |
|
break; |
|
case IDX_16_DE: |
|
high = &(m_reg.d); |
|
low = &(m_reg.e); |
|
break; |
|
case IDX_16_HL: |
|
high = &(m_reg.h); |
|
low = &(m_reg.l); |
|
break; |
|
case IDX_16_SP: |
|
low = (uint8_t*)(&(m_reg.sp)); |
|
high = low + 1; // :) |
|
break; |
|
default: |
|
std::stringstream ex; |
|
ex << "invalid 16-bit register pair index: " |
|
<< PlipUtility::FormatHex(idx, 2) |
|
<< "\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
return std::make_tuple(high, low); |
|
} |
|
|
|
void SharpLr35902::Interrupt(uint8_t irq) { |
|
auto iFlag = m_memory->GetByte(MemInterruptFlag); |
|
iFlag |= irq; |
|
m_memory->SetByte(MemInterruptFlag, iFlag); |
|
|
|
// Take the CPU out of halt mode if this particular interrupt is |
|
// enabled, even if the IME is disabled. |
|
auto iEnabled = m_memory->GetByte(MemInterruptEnabled); |
|
if(iEnabled & iFlag) |
|
m_halt = false; |
|
} |
|
|
|
void SharpLr35902::PerformReset() { |
|
m_reg.a = 0; |
|
m_reg.f = 0; |
|
m_reg.b = 0; |
|
m_reg.c = 0; |
|
m_reg.d = 0; |
|
m_reg.e = 0; |
|
m_reg.h = 0; |
|
m_reg.l = 0; |
|
|
|
m_reg.sp = 0; |
|
m_reg.pc = 0; |
|
|
|
m_ime = ScheduledState::Disabled; |
|
|
|
m_allowFetch = true; |
|
m_instr.clear(); |
|
m_mcycle = 0; |
|
} |
|
|
|
void SharpLr35902::Reset() { |
|
PerformReset(); |
|
} |
|
|
|
bool SharpLr35902::TestConditional(uint8_t idx) const { |
|
switch(idx) { |
|
case COND_NZ: return !FLAG_TEST(ZERO); |
|
case COND_Z: return FLAG_TEST(ZERO); |
|
case COND_NC: return !FLAG_TEST(CARRY); |
|
case COND_C: return FLAG_TEST(CARRY); |
|
default: |
|
std::stringstream ex; |
|
ex << "invalid condition: " |
|
<< PlipUtility::FormatHex(idx, 2) |
|
<< "\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
} |
|
}
|
|
|