/* Copyright (C) 2001-2007 StrmnNrmn This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // Stuff to handle Processor #include "Base/Types.h" #include "Core/CPU.h" #include #include #include #include #include #include "Interface/ConfigOptions.h" #include "Interface/Cheats.h" #include "Core/Dynamo.h" #include "Core/Interpret.h" #include "Core/Interrupt.h" #include "Core/Memory.h" #include "Core/R4300.h" #include "Debug/Registers.h" // For REG_?? defines #include "Core/ROM.h" #include "RomFile/ROMBuffer.h" #include "Core/RSP_HLE.h" #include "Core/Save.h" #include "Interface/SaveState.h" #include "Debug/DBGConsole.h" #include "Debug/DebugLog.h" #include "Ultra/ultra_R4300.h" #include "System/SystemInit.h" #include "System/AtomicPrimitives.h" #include "Utility/FramerateLimiter.h" #include "Utility/Hash.h" #include "Base/Macros.h" #include "Debug/PrintOpCode.h" #include "Debug/Synchroniser.h" #include "System/Thread.h" extern void R4300_Init(); // // New dynarec engine // #ifdef DAEDALUS_PROFILE_EXECUTION u64 gTotalInstructionsExecuted = 0; u64 gTotalInstructionsEmulated = 0; #endif #ifdef DAEDALUS_BREAKPOINTS_ENABLED std::vector< DBG_BreakPoint > g_BreakPoints; #endif volatile u32 eventQueueLocked = 0; static bool gCPURunning = false; // CPU is actively running u8 * gLastAddress = nullptr; std::string gSaveStateFilename = ""; static bool gCPUStopOnSimpleState = false; // When stopping, try to stop in a 'simple' state (i.e. no RSP running and not in a branch delay slot) static std::mutex gSaveStateMutex; enum ESaveStateOperation { SSO_NONE, SSO_SAVE, SSO_LOAD, }; static ESaveStateOperation gSaveStateOperation(SSO_NONE); const u32 kInitialVIInterruptCycles = 62500; static u32 gVerticalInterrupts = 0; static u32 VI_INTR_CYCLES(kInitialVIInterruptCycles); #ifdef USE_SCRATCH_PAD SCPUState *gPtrCPUState = (SCPUState*)0x10000; #else alignas(CACHE_ALIGN) SCPUState gCPUState; #endif static bool CPU_IsStateSimple(); void (* g_pCPUCore)(); using VblCallbackFn = void (*)(void * arg); struct VblCallback { VblCallbackFn Fn; void * Arg; }; static std::vector gVblCallbacks; void CPU_RegisterVblCallback(VblCallbackFn fn, void * arg) { VblCallback callback = { fn, arg }; gVblCallbacks.push_back(callback); } void CPU_UnregisterVblCallback(VblCallbackFn fn, void * arg) { for (auto it = gVblCallbacks.begin(); it != gVblCallbacks.end(); ++it) { if (it->Fn == fn && it->Arg == arg) { gVblCallbacks.erase(it); break; } } } void CPU_SkipToNextEvent() { LOCK_EVENT_QUEUE(); #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( gCPUState.NumEvents > 0, "There are no events" ); #endif gCPUState.CPUControl[C0_COUNT]._u32 += (gCPUState.Events[ 0 ].mCount - 1); gCPUState.Events[ 0 ].mCount = 1; } static void CPU_ResetEventList() { gCPUState.Events[ 0 ].mCount = kInitialVIInterruptCycles; gCPUState.Events[ 0 ].mEventType = CPU_EVENT_VBL; gCPUState.NumEvents = 1; RESET_EVENT_QUEUE_LOCK(); } void CPU_AddEvent( s32 count, ECPUEventType event_type ) { LOCK_EVENT_QUEUE(); #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( count > 0, "Count is invalid" ); DAEDALUS_ASSERT( gCPUState.NumEvents < MAX_CPU_EVENTS, "Too many events" ); #endif u32 event_idx = 0; for( event_idx = 0; event_idx < gCPUState.NumEvents; ++event_idx ) { CPUEvent & event = gCPUState.Events[ event_idx ]; if( count <= event.mCount ) { // // This event belongs before the subsequent one so insert a space for it here and break out // Don't forget to decrement the counter for the subsequent event // event.mCount -= count; u32 num_to_copy = gCPUState.NumEvents - event_idx; if( num_to_copy > 0 ) { memmove( &gCPUState.Events[ event_idx+1 ], &gCPUState.Events[ event_idx ], num_to_copy * sizeof( CPUEvent ) ); } break; } // // Decrease counter by that for this event // count -= event.mCount; } #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( event_idx <= gCPUState.NumEvents, "Invalid idx" ); #endif gCPUState.Events[ event_idx ].mCount = count; gCPUState.Events[ event_idx ].mEventType = event_type; gCPUState.NumEvents++; } static void CPU_SetCompareEvent( s32 count ) { { LOCK_EVENT_QUEUE(); #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( count > 0, "Count is invalid" ); #endif // // Remove any existing compare events. Need to adjust any subsequent timer's count. // for( u32 i = 0; i < gCPUState.NumEvents; ++i ) { if( gCPUState.Events[ i ].mEventType == CPU_EVENT_COMPARE ) { // // Check for a following event, and remove // if( i+1 < gCPUState.NumEvents ) { gCPUState.Events[ i+1 ].mCount += gCPUState.Events[ i ].mCount; u32 num_to_copy = gCPUState.NumEvents - (i+1); memmove( &gCPUState.Events[ i ], &gCPUState.Events[ i+1 ], num_to_copy * sizeof( CPUEvent ) ); } gCPUState.NumEvents--; break; } } } CPU_AddEvent( count, CPU_EVENT_COMPARE ); } static ECPUEventType CPU_PopEvent() { LOCK_EVENT_QUEUE(); #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( gCPUState.NumEvents > 0, "Event queue empty" ); DAEDALUS_ASSERT( gCPUState.Events[ 0 ].mCount <= 0, "Popping event when cycles remain" ); //DAEDALUS_ASSERT( gCPUState.Events[ 0 ].mCount == 0, "Popping event with a bit of underflow" ); #endif ECPUEventType event_type = gCPUState.Events[ 0 ].mEventType; u32 num_to_copy = gCPUState.NumEvents - 1; if( num_to_copy > 0 ) { memmove( &gCPUState.Events[ 0 ], &gCPUState.Events[ 1 ], num_to_copy * sizeof( CPUEvent ) ); } gCPUState.NumEvents--; return event_type; } // XXXX This is for savestate. Looks very suspicious to me u32 CPU_GetVideoInterruptEventCount() { for( u32 i = 0; i < gCPUState.NumEvents; ++i ) { if(gCPUState.Events[ i ].mEventType == CPU_EVENT_VBL) { return gCPUState.Events[ i ].mCount; } } return 0; } // XXXX This is for savestate. Looks very suspicious to me void CPU_SetVideoInterruptEventCount( u32 count ) { for( u32 i = 0; i < gCPUState.NumEvents; ++i ) { if(gCPUState.Events[ i ].mEventType == CPU_EVENT_VBL) { gCPUState.Events[ i ].mCount = count; return; } } } void SCPUState::ClearStuffToDo() { StuffToDo = 0; Dynarec_ClearedCPUStuffToDo(); } void SCPUState::AddJob( u32 job ) { u32 stuff = AtomicBitSet( &StuffToDo, 0xffffffff, job ); if( stuff != 0 ) { Dynarec_SetCPUStuffToDo(); } } void SCPUState::ClearJob( u32 job ) { u32 stuff( AtomicBitSet( &StuffToDo, ~job, 0x00000000 ) ); if( stuff == 0 ) { Dynarec_ClearedCPUStuffToDo(); } } #ifdef DAEDALUS_ENABLE_SYNCHRONISATION static const char * const kRegisterNames[] = { "zr", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra" }; DAEDALUS_STATIC_ASSERT(std::size(kRegisterNames) == 32); void SCPUState::Dump() { DBGConsole_Msg(0, "Emulation CPU State:"); { for(int i = 0; i < 32; i += 4) { DBGConsole_Msg(0, "%s:%08X %s:%08X %s:%08X %s:%08X", kRegisterNames[i+0], gCPUState.CPU[i+0]._u32_0, kRegisterNames[i+1], gCPUState.CPU[i+1]._u32_0, kRegisterNames[i+2], gCPUState.CPU[i+2]._u32_0, kRegisterNames[i+3], gCPUState.CPU[i+3]._u32_0); } DBGConsole_Msg(0, "TargetPC: %08x", gCPUState.TargetPC); DBGConsole_Msg(0, "CurrentPC: %08x", gCPUState.CurrentPC); DBGConsole_Msg(0, "Delay: %08x", gCPUState.Delay); } } #endif bool CPU_RomOpen() { #ifdef DAEDALUS_DEBUG_CONSOLE DBGConsole_Msg(0, "Resetting CPU"); #endif gLastAddress = nullptr; gCPURunning = false; gCPUStopOnSimpleState = false; RESET_EVENT_QUEUE_LOCK(); memset(&gCPUState, 0, sizeof(gCPUState)); CPU_SetPC( 0xbfc00000 ); gCPUState.MultHi._u64 = 0; gCPUState.MultLo._u64 = 0; for(u32 i = 0; i < 32; i++) { gCPUState.CPU[i]._u64 = 0; gCPUState.CPUControl[i]._u32 = 0; gCPUState.FPU[i]._u32 = 0; gCPUState.FPUControl[i]._u32 = 0; } // Init TLBs: for (u32 i = 0; i < 32; i++) { g_TLBs[i].Reset(); } // From R4300 manual gCPUState.CPUControl[C0_RAND]._u32 = 32-1; // TLBENTRIES-1 //gCPUState.CPUControl[C0_SR]._u32 = 0x70400004; //*SR_FR |*/ SR_ERL | SR_CU2|SR_CU1|SR_CU0; R4300_SetSR(0x70400004); gCPUState.CPUControl[C0_PRID]._u32 = 0x00000b10; // Was 0xb00 - test rom reports 0xb10!! gCPUState.CPUControl[C0_CONFIG]._u32 = 0x0006E463; // 0x00066463; gCPUState.CPUControl[C0_WIRED]._u32 = 0x0; gCPUState.FPUControl[0]._u32 = 0x00000511; Memory_MI_SetRegister(MI_VERSION_REG, 0x02020102); ((u32 *)g_pMemoryBuffers[MEM_RI_REG])[3] = 1; // RI_CONFIG_REG Skips most of init R4300_Init(); gCPUState.Delay = NO_DELAY; gCPUState.ClearStuffToDo(); gVerticalInterrupts = 0; // Clear event list: CPU_ResetEventList(); #ifdef DAEDALUS_BREAKPOINTS_ENABLED g_BreakPoints.clear(); #endif Dynamo_Reset(); CPU_SelectCore(); return true; } void CPU_RomClose() { #ifdef DAEDALUS_ENABLE_DYNAREC #ifdef DAEDALUS_DEBUG_CONSOLE_DYNAREC //This will dump the fragment cache on exit to ROMs menu //CPU_DumpFragmentCache(); #endif #endif } static bool CPU_IsStateSimple() { bool rsp_halted = !RSP_IsRunning(); return rsp_halted && (gCPUState.Delay == NO_DELAY); } void CPU_SelectCore() { #ifdef DAEDALUS_ENABLE_DYNAREC if (gDynarecEnabled) Dynamo_SelectCore(); else #endif Inter_SelectCore(); if( gCPUStopOnSimpleState && CPU_IsStateSimple() ) { gCPUState.AddJob( CPU_STOP_RUNNING ); } else { gCPUState.AddJob( CPU_CHANGE_CORE ); } } bool CPU_RequestSaveState( const std::filesystem::path &filename ) { // Call SaveState_SaveToFile directly if the CPU is not running. DAEDALUS_ASSERT(gCPURunning, "Expecting the CPU to be running at this point"); std::scoped_lock lock(gSaveStateMutex); // Abort if already in the process of loading/saving if( gSaveStateOperation != SSO_NONE ) { return false; } gSaveStateOperation = SSO_SAVE; gSaveStateFilename = filename.string(); gCPUState.AddJob(CPU_CHANGE_CORE); return true; } bool CPU_RequestLoadState( const std::filesystem::path &filename ) { // Call SaveState_SaveToFile directly if the CPU is not running. DAEDALUS_ASSERT(gCPURunning, "Expecting the CPU to be running at this point"); std::scoped_lock lock(gSaveStateMutex); // Abort if already in the process of loading/saving if( gSaveStateOperation != SSO_NONE ) { return false; } gSaveStateOperation = SSO_LOAD; gSaveStateFilename = filename.string(); gCPUState.AddJob(CPU_CHANGE_CORE); return true; // XXXX could fail } static void HandleSaveStateOperationOnVerticalBlank() { DAEDALUS_ASSERT(gCPURunning, "Expecting the CPU to be running at this point"); if( gSaveStateOperation == SSO_NONE ) return; std::scoped_lock lock(gSaveStateMutex); // // Handle the save state // switch( gSaveStateOperation ) { case SSO_NONE: DAEDALUS_ERROR( "Unreachable" ); break; case SSO_SAVE: DBGConsole_Msg(0, "Saving '%s'\n", gSaveStateFilename.c_str()); SaveState_SaveToFile( gSaveStateFilename ); gSaveStateOperation = SSO_NONE; break; case SSO_LOAD: DBGConsole_Msg(0, "Loading '%s'\n", gSaveStateFilename.c_str()); // Try to load the savestate immediately. If this fails, it // usually means that we're running the correct rom (we should have a // separate return code to check this case). In that case we // stop the cpu and handle the load in // HandleSaveStateOperationOnCPUStopRunning. if (SaveState_LoadFromFile( gSaveStateFilename )) { CPU_ResetFragmentCache(); gSaveStateOperation = SSO_NONE; } else { // Halt the CPU so that we can swap the rom safely and load the savesate. CPU_Halt("Load SaveSate"); // NB: return without clearing gSaveStateOperation } break; } } // Returns true if we handled a load request and should keep running. static bool HandleSaveStateOperationOnCPUStopRunning() { if (gSaveStateOperation != SSO_LOAD) return false; std::scoped_lock lock(gSaveStateMutex); gSaveStateOperation = SSO_NONE; std::string rom_filename = SaveState_GetRom(gSaveStateFilename); if (!rom_filename.empty()) { System_Close(); System_Open(rom_filename); SaveState_LoadFromFile(gSaveStateFilename); } else { DBGConsole_Msg(0, "Couldn't find matching rom for %s\n", gSaveStateFilename.c_str()); // Keep running with the current rom. } return true; } bool CPU_Run() { if (!RomBuffer::IsRomLoaded()) return false; while (1) { gCPURunning = true; gCPUStopOnSimpleState = false; DAEDALUS_ASSERT(gSaveStateOperation == SSO_NONE, "Shouldn't have a save state operation queued."); RESET_EVENT_QUEUE_LOCK(); while (gCPURunning) { g_pCPUCore(); } if (!HandleSaveStateOperationOnCPUStopRunning()) break; } DAEDALUS_ASSERT(!gCPURunning, "gCPURunning should be false by now."); return true; } void CPU_Halt( const char * reason ) { DBGConsole_Msg( 0, "CPU Halting: %s", reason ); gCPUStopOnSimpleState = true; gCPUState.AddJob( CPU_STOP_RUNNING ); } #ifdef DAEDALUS_BREAKPOINTS_ENABLED void CPU_AddBreakPoint( u32 address ) { OpCode * pdwOp; // Force 4 byte alignment address &= 0xFFFFFFFC; if (!Memory_GetInternalReadAddress(address, (void**)&pdwOp)) { DBGConsole_Msg(0, "Invalid Address for BreakPoint: 0x%08x", address); } else { DBG_BreakPoint bpt; DBGConsole_Msg(0, "[YInserting BreakPoint at 0x%08x]", address); bpt.mOriginalOp = *pdwOp; bpt.mEnabled = true; bpt.mTemporaryDisable = false; g_BreakPoints.push_back(bpt); pdwOp->op = OP_DBG_BKPT; pdwOp->bp_index = (g_BreakPoints.size() - 1); } } #endif #ifdef DAEDALUS_BREAKPOINTS_ENABLED void CPU_EnableBreakPoint( u32 address, bool enable ) { OpCode * pdwOp; // Force 4 byte alignment address &= 0xFFFFFFFC; if (!Memory_GetInternalReadAddress(address, (void**)&pdwOp)) { DBGConsole_Msg(0, "Invalid Address for BreakPoint: 0x%08x", address); } else { OpCode op_code = *pdwOp; if (op_code.op != OP_DBG_BKPT) { DBGConsole_Msg(0, "[YNo breakpoint is set at 0x%08x]", address); return; } // Entry is in lower 26 bits... u32 breakpoint_idx = op_code.bp_index; if (breakpoint_idx < g_BreakPoints.size()) { g_BreakPoints[breakpoint_idx].mEnabled = enable; // Alwyas disable g_BreakPoints[breakpoint_idx].mTemporaryDisable = false; } } } #endif extern u32 gVISyncRate ; extern "C" { void CPU_HANDLE_COUNT_INTERRUPT() { #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( gCPUState.NumEvents > 0, "Should always have at least one event queued up" ); #endif switch (CPU_PopEvent()) { case CPU_EVENT_VBL: { //Todo: Work on VI_INTR_CYCLES should be 62500 * (60/Real game FPS) u32 vertical_sync_reg = Memory_VI_GetRegister( VI_V_SYNC_REG ); if (vertical_sync_reg == 0) { VI_INTR_CYCLES = 62500; } else { VI_INTR_CYCLES = (vertical_sync_reg+1) * ( gVideoRateMatch ? gVISyncRate : 1500 ); } // Apply cheatcodes, if enabled if( gCheatsEnabled ) { CheatCodes_Activate( IN_GAME ); } // Add another Interrupt at the next time: CPU_AddEvent(VI_INTR_CYCLES, CPU_EVENT_VBL); gVerticalInterrupts++; FramerateLimiter_Limit(); Memory_MI_SetRegisterBits(MI_INTR_REG, MI_INTR_VI); R4300_Interrupt_UpdateCause3(); // ToDo: Has to be a better way than this??? // Maybe After each X frames instead of each 60 VI? // (strmnnrmn): I don't see what's so bad about checking these on a vbl, // because it means we can remain responsive even if the game is not rendering frames // (e.g. if it's slow starting up) // Alternatively, we could add a special-purpose CPU even that triggers every // N cycles, but that would have a small impact on framerate (it would // interrupt the dynamo tracer for instance) // TODO(strmnnrmn): should register this with CPU_RegisterVblCallback. if ((gVerticalInterrupts & 0x3F) == 0) { // once every 60 VBLs Save_Flush(); for (size_t i = 0; i < gVblCallbacks.size(); ++i) { VblCallback & callback = gVblCallbacks[i]; callback.Fn(callback.Arg); } } HandleSaveStateOperationOnVerticalBlank(); } break; case CPU_EVENT_COMPARE: { gCPUState.CPUControl[C0_CAUSE]._u32 |= CAUSE_IP8; gCPUState.AddJob( CPU_CHECK_INTERRUPTS ); } break; case CPU_EVENT_AUDIO: { u32 status = Memory_SP_SetRegisterBits(SP_STATUS_REG, SP_STATUS_TASKDONE|SP_STATUS_YIELDED|SP_STATUS_BROKE|SP_STATUS_HALT); if( status & SP_STATUS_INTR_BREAK ) CPU_AddEvent(4000, CPU_EVENT_SPINT); } break; case CPU_EVENT_SPINT: Memory_MI_SetRegisterBits(MI_INTR_REG, MI_INTR_SP); R4300_Interrupt_UpdateCause3(); break; } #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( gCPUState.NumEvents > 0, "Should always have at least one event queued up" ); #endif } } void CPU_SetCompare(u32 value) { gCPUState.CPUControl[C0_CAUSE]._u32 &= ~CAUSE_IP8; #ifdef DAEDALUS_PROFILE DPF( DEBUG_REGS, "COMPARE set to 0x%08x.", value ); //DBGConsole_Msg(0, "COMPARE set to 0x%08x Count is 0x%08x.", value, gCPUState.CPUControl[C0_COUNT]._u32); #endif // Add an event for this compare: if (value == gCPUState.CPUControl[C0_COMPARE]._u32) { //DBGConsole_Msg(0, "Clear"); } else { if (value != 0) { // NB, value can be less than COUNT here, which indicates that the counter is close to wrapping. // Don't do anything special to handle this - just treat delta as an unsigned value. u32 delta = value - gCPUState.CPUControl[C0_COUNT]._u32; // This fires a lot for Zelda OoT. It's benign. // If seems to keep setting a delta of 140624981 when the counter is close to wrapping. // if (value < gCPUState.CPUControl[C0_COUNT]._u32) // { // DBGConsole_Msg(0, "SetCompare wrapping: %d -> %d = %d", gCPUState.CPUControl[C0_COUNT]._u32, value, delta); // } CPU_SetCompareEvent( delta ); } #ifdef DAEDALUS_DEBUG_CONSOLE else { //DBGConsole_Msg(0, "[RIgnoring SetCompare 0] - is this right?"); } #endif gCPUState.CPUControl[C0_COMPARE]._u32 = value; } } #ifdef DAEDALUS_ENABLE_SYNCHRONISATION u32 CPU_ProduceRegisterHash() { u32 hash = 0; if ( DAED_SYNC_MASK & DAED_SYNC_REG_GPR ) { hash = murmur2_hash( (u8*)&(gCPUState.CPU[0]), sizeof( gCPUState.CPU ), hash ); } if ( DAED_SYNC_MASK & DAED_SYNC_REG_CCR0 ) { hash = murmur2_hash( (u8*)&(gCPUState.CPUControl[0]), sizeof( gCPUState.CPUControl ), hash ); } if ( DAED_SYNC_MASK & DAED_SYNC_REG_CPU1 ) { hash = murmur2_hash( (u8*)&(gCPUState.FPU[0]), sizeof( gCPUState.FPU ), hash ); } if ( DAED_SYNC_MASK & DAED_SYNC_REG_CCR1 ) { hash = murmur2_hash( (u8*)&(gCPUState.FPUControl[0]), sizeof( gCPUState.FPUControl ), hash ); } return hash; } #endif #ifdef FRAGMENT_SIMULATE_EXECUTION // Execute the specified opcode void CPU_ExecuteOpRaw( u32 count, u32 address, OpCode op_code, CPU_Instruction p_instruction, bool * p_branch_taken ) { gCPUState.CurrentPC = address; SYNCH_POINT( DAED_SYNC_REG_PC, gCPUState.CurrentPC, "Program Counter doesn't match" ); SYNCH_POINT( DAED_SYNC_REG_PC, count, "Count doesn't match" ); p_instruction( op_code._u32 ); SYNCH_POINT( DAED_SYNC_REGS, CPU_ProduceRegisterHash(), "Registers don't match" ); *p_branch_taken = gCPUState.Delay == DO_DELAY; } #endif extern "C" { void CPU_UpdateCounter( u32 ops_executed ) { #ifdef DAEDALUS_ENABLE_ASSERTS DAEDALUS_ASSERT( ops_executed > 0, "Expecting at least one op" ); #endif //SYNCH_POINT( DAED_SYNC_FRAGMENT_PC, ops_executed, "Number of executed ops doesn't match" ); #ifdef DAEDALUS_PROFILE_EXECUTION gTotalInstructionsExecuted += ops_executed; #endif const u32 cycles = ops_executed * COUNTER_INCREMENT_PER_OP; // Increment count register gCPUState.CPUControl[C0_COUNT]._u32 += cycles; if( CPU_ProcessEventCycles( cycles ) ) { CPU_HANDLE_COUNT_INTERRUPT(); } } #ifdef UPDATE_COUNTER_ON_EXCEPTION // As above, but no interrupts are fired void CPU_UpdateCounterNoInterrupt( u32 ops_executed ) { //SYNCH_POINT( DAED_SYNC_FRAGMENT_PC, ops_executed, "Number of executed ops doesn't match" ); if( ops_executed > 0 ) { const u32 cycles =ops_executed * COUNTER_INCREMENT_PER_OP}; #ifdef DAEDALUS_PROFILE_EXECUTION gTotalInstructionsExecuted += ops_executed; #endif // Increment count register gCPUState.CPUControl[C0_COUNT]._u32 += cycles; #ifdef DAEDALUS_ENABLE_ASSERTS bool ready = CPU_ProcessEventCycles( cycles ); use( ready ); DAEDALUS_ASSERT(!ready, "Ignoring Count interrupt"); // Just a test - remove eventually (needs to handle this) #endif } } #endif } // Return true if change the core bool CPU_CheckStuffToDo() { // Get jobs to do u32 stuff_to_do = gCPUState.GetStuffToDo(); // Early return if nothing to do if (stuff_to_do == 0) { return false; } // Process Interrupts/Exceptions on a priority basis using a switch statement if( gCPUState.GetStuffToDo() & CPU_CHECK_INTERRUPTS ) { R4300_Handle_Interrupt(); gCPUState.ClearJob( CPU_CHECK_INTERRUPTS ); } else if( gCPUState.GetStuffToDo() & CPU_CHECK_EXCEPTIONS ) { R4300_Handle_Exception(); gCPUState.ClearJob( CPU_CHECK_EXCEPTIONS ); } else if( gCPUState.GetStuffToDo() & CPU_CHANGE_CORE ) { gCPUState.ClearJob( CPU_CHANGE_CORE ); return true; } else if( gCPUState.GetStuffToDo() & CPU_STOP_RUNNING ) { gCPUState.ClearJob( CPU_STOP_RUNNING ); gCPURunning = false; return true; } // Clear stuff_to_do? return false; } // FIX ME: This gets called alot // Return true if the CPU is running bool CPU_IsRunning() { return gCPURunning; }