void MacroAssembler::lowLevelDebug(const char *s, Register ra, Register rb, Register rc) { Q_ASSERT(ra.code() < 13 && rb.code() < 13 && rc.code() < 13); int preserved = 0x1fff | lr.bit(); stm(db_w, sp, preserved); add(fp, sp, Operand(12*4)); mrs(r4, CPSR); Label omitString; b(&omitString); int sPtr = intptr_t(buffer_ + pcOffset()); do { db(*s); } while (*(s++)); while (pcOffset() & 3) db('\0'); bind(&omitString); ldr(r3, MemOperand(sp, rc.code()*4)); ldr(r2, MemOperand(sp, rb.code()*4)); ldr(r1, MemOperand(sp, ra.code()*4)); mov(r0, Operand(sPtr)); void (*qDebugPtr)(const char *,...) = &qDebug; mov(ip, Operand(intptr_t(qDebugPtr))); blx(ip); msr(CPSR_f, Operand(r4)); ldm(ia_w, sp, preserved); }
/*! Adds cycles from cpuRecData.additionalCycles to mCycles and sets cpuRecData.additionalCycles to 0. additionalCycles member holds postponed cycles in case of interrupt pending or DMA transfer. */ void NesCpuTranslator::mFetchAdditionalCycles() { __ ldr(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,additionalCycles))); __ add(mCycles, mCycles, Operand(ip), SetCC); __ mov(ip, Operand(0)); __ str(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,additionalCycles))); }
void NesCpuTranslator::mHandleInterrupts() { // code at the bottom does following job: // // if (interrupts & mNmiInterrupt) { // hwInterrupt(NmiVectorAddress); // interrupts &= ~mNmiInterrupt; // } else if (interrupts & mIrqInterrupt) { // if (!(mFlags & mIrqDisable)) { // hwInterrupt(IrqVectorAddress); // } // } // r0 - 6502.PC address of the next instruction Label exitInterruptCheck; Label irqPending; Label doInterrupt; __ ldr(r1, MemOperand(mDataBase, offsetof(NesCpuRecData,interrupts))); __ mov(r1, r1, SetCC); __ b(&exitInterruptCheck, eq); __ tst(r1, Operand(NesCpuBase::NmiInterrupt)); __ b(&irqPending, eq); // NMI is handled here // clear NMI flag __ bic(r1, r1, Operand(NesCpuBase::NmiInterrupt)); __ str(r1, MemOperand(mDataBase, offsetof(NesCpuRecData,interrupts))); __ mov(r1, Operand(NesCpuBase::NmiVectorAddress)); __ b(&doInterrupt); // irqPending: // IRQ is handled here // check if P.I is cleared __ bind(&irqPending); __ tst(mFlags, Operand(NesCpuBase::IrqDisable)); __ b(&exitInterruptCheck, ne); __ mov(r1, Operand(NesCpuBase::IrqVectorAddress)); // doInterrupt: __ bind(&doInterrupt); // interrupt occured - handle it mHwInterrupt(); // exitInterruptCheck: __ bind(&exitInterruptCheck); }
void NesSyncCompiler::mLeaveToCpu() { __ add(r3, pc, Operand(Assembler::kInstrSize)); __ str(r3, MemOperand(m_dataBase, offsetof(NesSyncData,nextPc))); // return to cpu emulation __ ldm(ia_w, sp, m_regList | pc.bit()); // nesSyncRecData.nextPc points here now }
void NesCpuTranslator::mRestoreInternalFlags() { #if !defined(FRAME_POINTER_FOR_GDB) __ msr(CPSR_f, Operand(mInternalFlagsCopy)); #else __ ldr(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,internalFlagsCopy))); __ msr(CPSR_f, Operand(ip)); #endif }
void NesCpuTranslator::mSaveInternalFlags() { #if !defined(FRAME_POINTER_FOR_GDB) __ mrs(mInternalFlagsCopy, CPSR); #else __ mrs(ip, CPSR); __ str(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,internalFlagsCopy))); #endif }
void NesSyncCompiler::mCallCFunction(int offsetInData) { #if defined(CAN_USE_ARMV7_INSTRUCTIONS) __ mov(ip, Operand(*(u32 *)((u8 *)&syncData + offsetInData))); __ blx(ip); #else __ ldr(ip, MemOperand(m_dataBase, offsetInData)); __ blx(ip); #endif }
/*! Compiles a code that loads address pointed by a label at the given \a offset in 6502 address space to the \a dst. The label can be bound to the recompiled code or to mTranslateCaller if translation haven't occured for the given \a offset. */ inline void NesCpuTranslator::mLoadLabelAddress(Register offset, Register dst) { Q_ASSERT(offset != ip && dst != ip); __ mov(dst, Operand(intptr_t(m_codeBuffer))); __ mov(ip, Operand(intptr_t(m_labels))); __ ldr(ip, MemOperand(ip, offset, LSL, 2)); // dst = buffer - pos - 1 __ sub(dst, dst, Operand(ip)); __ bic(dst, dst, Operand(3)); }
inline void NesCpuTranslator::mCheckAlert() { // check if write caused an alert __ ldr(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,alert))); __ add(pc, pc, Operand(ip, LSL, 2)); // some pad needed - write current 6502.PC __ dd(currentPc()); // save state can happen in alert handler - r0 must contain 6502.PC __ mov(r0, Operand(currentPc())); // handle an alert if needed __ bl(&m_alertHandlerLabel); // continue otherwise }
void NesCpuTranslator::mHandleEvent() { Label saveState; Label loadState; Label exit; // arguments: r1 = event // reg list for saving state // r0 contains 6502.PC address // r2 contains 6502.P flags RegList regList = r0.bit() | r2.bit() | mA.bit() | mX.bit() | mY.bit() | mS.bit(); // regs member of NesCpuRecData should be placed at the start of the object Q_ASSERT(offsetof(NesCpuRecData,regs) == 0); __ cmp(r1, Operand(NesCpuBase::SaveStateEvent)); __ b(&saveState, eq); __ cmp(r1, Operand(NesCpuBase::LoadStateEvent)); __ b(&loadState, eq); // exitEvent: mExitPoint(); // loadState: __ bind(&loadState); __ ldm(ia, mDataBase, regList); mUnpackFlags(r2, r1); mSaveInternalFlags(); mLoadLabelAddress(r0, lr); // mCycles is zeroed later __ b(&exit); // saveState: __ bind(&saveState); mRestoreInternalFlags(); mPackFlags(r2); __ stm(ia, mDataBase, regList); // saveState occurs on frame end, ticks() will be used on new frame // so set it to zero __ mov(ip, Operand(0)); __ str(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,currentCycles))); // exit: __ bind(&exit); }
void NesSyncCompiler::mEntryPoint() { // r0 = additionalCpuCycles - the amount of cycles that CPU emulation // executed minus requested cycles to execute __ stm(db_w, sp, m_regList | lr.bit()); #if defined(FRAME_POINTER_FOR_GDB) __ add(fp, sp, Operand(3*4)); // point to lr - omit r9,r10,fp #endif // one step before we passed an amount of cycles that CPU should run, // but it's almost impossible to clock CPU by exact cycles, it will // often run more, and we must track these additional cycles // save additional CPU cycles, they will be used later in mClock __ mov(m_additionalCpuCycles, r0); // jump to the point the frame generation was before __ mov(m_dataBase, Operand(reinterpret_cast<int>(&syncData))); __ ldr(pc, MemOperand(m_dataBase, offsetof(NesSyncData,nextPc))); }
void NesCpuTranslator::mClearAlert() { __ mov(ip, Operand(NesCpuRecData::AlertOff)); __ str(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,alert))); }
void NesCpuTranslator::mSync() { // r0 - 6502.PC address of the next instruction __ bind(&m_syncLabel); Label exitSync; Label handleEvent; mSaveInternalFlags(); // IRQ can be cleared, so we must fetch additional cycles every time mFetchAdditionalCycles(); mHandleInterrupts(); // IRQ may be pending and P.I can be set, in this case we may not call // nesSync if mCycles < 0 __ mov(mCycles, mCycles, SetCC); __ b(&m_checkInterruptsForNextInstruction, mi); __ bind(&m_syncWithoutInterruptHandling); int preserved = r0.bit() | lr.bit(); #if defined(FRAME_POINTER_FOR_GDB) preserved |= fp.bit(); #endif __ stm(db_w, sp, preserved); #if defined(FRAME_POINTER_FOR_GDB) __ add(fp, sp, Operand(2*4)); #endif Label dontSyncWithApuAndClock; __ ldr(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,startCycles))); __ add(r0, mCycles, ip, SetCC); __ b(&dontSyncWithApuAndClock, eq); mCallCFunction(offsetof(NesCpuRecData,apuClock)); if (nesMapper->hasClock()) { __ ldr(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,startCycles))); __ add(r0, mCycles, ip, SetCC); mCallCFunction(offsetof(NesCpuRecData,mapperClock)); } __ bind(&dontSyncWithApuAndClock); __ mov(r0, mCycles); mCallCFunction(offsetof(NesCpuRecData,nesSync)); __ str(r0, MemOperand(mDataBase, offsetof(NesCpuRecData,startCycles))); __ rsb(mCycles, r0, Operand(0), SetCC); __ ldm(ia_w, sp, preserved); // if r0 >= 0 then it means we should handle an event __ b(&handleEvent, pl); // interrupts handling is a little tricky: // - once interrupt occurs save cycles in the memory // - force mSync to be called next time by setting mCycles to zero // - handle interrupt in the new mSync call __ bind(&m_checkInterruptsForNextInstruction); mClearAlert(); __ ldr(ip, MemOperand(mDataBase, offsetof(NesCpuRecData,interrupts))); __ mov(ip, ip, SetCC); __ b(&exitSync, eq); // interrupt is pending here __ str(mCycles, MemOperand(mDataBase, offsetof(NesCpuRecData,additionalCycles))); __ mov(mCycles, Operand(0)); __ b(&exitSync); // handleEvent: __ bind(&handleEvent); __ rsb(r1, mCycles, Operand(0)); __ mov(mCycles, Operand(0)); __ str(mCycles, MemOperand(mDataBase, offsetof(NesCpuRecData,startCycles))); mHandleEvent(); __ b(&m_syncWithoutInterruptHandling); // exitSync: __ bind(&exitSync); mRestoreInternalFlags(); // r0 must be loaded here with 6502.PC address of the next instruction __ mov(pc, lr); }
/*! Loads C function address based on the given \a offset and calls this function. */ inline void NesCpuTranslator::mCallCFunction(int funcOffset) { __ ldr(ip, MemOperand(mDataBase, funcOffset)); __ blx(ip); }
/*! Stores mCycles to currentCycles member of cpuRecData, exact cycles are needed for APU writes. */ inline void NesCpuTranslator::mStoreCurrentCycles() { __ str(mCycles, MemOperand(mDataBase, offsetof(NesCpuRecData,currentCycles))); }
void NesSyncCompiler::mClock(int ppuCycles) { /* Here the compiler will generate following function: syncData.baseCycleCounter += baseCycles; u64 cpuCyclesNow = syncData.baseCycleCounter / nesEmu.clockDividerForCpu(); syncData.cpuCycleCounter += additionalCpuCycles; int newCpuCycles = cpuCyclesNow - syncData.cpuCycleCounter; if (newCpuCycles > 0) { syncData.cpuCycleCounter += newCpuCycles; return newCpuCycles; } return 0; If return value != 0 it will return to the cpu emulation also. */ int baseCycles = ppuCycles * nesEmu.clockDividerForPpu(); Q_ASSERT(baseCycles >= 0); __ Ldrd(r0, r1, MemOperand(m_dataBase, offsetof(NesSyncData,baseCycleCounter))); __ add(r0, r0, Operand(baseCycles), SetCC); __ adc(r1, r1, Operand(0)); __ Strd(r0, r1, MemOperand(m_dataBase, offsetof(NesSyncData,baseCycleCounter))); if (nesEmu.clockDividerForCpu() == 12 #if !defined(CAN_USE_ARMV7_INSTRUCTIONS) || nesEmu.clockDividerForCpu() == 16 #endif ) { __ mov(r2, Operand(nesEmu.clockDividerForCpu())); __ mov(r3, Operand(0)); u8 *uldiv = reinterpret_cast<u8 *>(&__aeabi_uldivmod); __ mov(ip, Operand(reinterpret_cast<u32>(uldiv))); __ blx(ip); #if defined(CAN_USE_ARMV7_INSTRUCTIONS) } else if (nesEmu.clockDividerForCpu() == 16) { __ bfi(r0, r1, 0, 4); __ mov(r0, Operand(r0, ROR, 4)); __ mov(r1, Operand(r1, LSR, 4)); #endif } else { UNREACHABLE(); } __ Ldrd(r2, r3, MemOperand(m_dataBase, offsetof(NesSyncData,cpuCycleCounter))); __ add(r2, r2, m_additionalCpuCycles, SetCC); __ adc(r3, r3, Operand(0)); __ Strd(r2, r3, MemOperand(m_dataBase, offsetof(NesSyncData,cpuCycleCounter))); // clear additionalCpuCycles here because mClock can be executed multiple // times in single synchronization step __ mov(m_additionalCpuCycles, Operand(0)); __ sub(r0, r0, r2); __ cmp(r0, Operand(0)); __ mov(r0, Operand(0), LeaveCC, le); Label holdCpu; __ b(&holdCpu, le); __ add(r2, r2, r0, SetCC); __ adc(r3, r3, Operand(0)); __ Strd(r2, r3, MemOperand(m_dataBase, offsetof(NesSyncData,cpuCycleCounter))); mLeaveToCpu(); __ bind(&holdCpu); }