/** * Find next interrupt to occur, and store to global variables for decrement * in instruction decode loop. * Note: Although InterruptHandlers.Cycles and LowestCycleCount are 64 bit * variables to get all the cycle counters right (e.g. the DMA sound counter * can get very high), PendingInterruptCount is still a 32 bit variable for * performance reasons (it's decremented after each CPU instruction). * So we have to initialize LowestCycleCount with INT_MAX, not with INT64_MAX! * Since there is always a VBL or HBL counter pending which fits fine into the * 32 bit variable, we can be sure that we don't run into problems here. */ static void CycInt_SetNewInterrupt(void) { Sint64 LowestCycleCount = INT_MAX; interrupt_id LowestInterrupt = INTERRUPT_NULL, i; LOG_TRACE(TRACE_INT, "int set new in video_cyc=%d active_int=%d pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), ActiveInterrupt, PendingInterruptCount); /* Find next interrupt to go off */ for (i = INTERRUPT_NULL+1; i < MAX_INTERRUPTS; i++) { /* Is interrupt pending? */ if (InterruptHandlers[i].bUsed) { if (InterruptHandlers[i].Cycles < LowestCycleCount) { LowestCycleCount = InterruptHandlers[i].Cycles; LowestInterrupt = i; } } } /* Set new counts, active interrupt */ PendingInterruptCount = InterruptHandlers[LowestInterrupt].Cycles; PendingInterruptFunction = InterruptHandlers[LowestInterrupt].pFunction; ActiveInterrupt = LowestInterrupt; LOG_TRACE(TRACE_INT, "int set new out video_cyc=%d active_int=%d pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), ActiveInterrupt, PendingInterruptCount ); }
/** * Read a counter on CPU memory write access by taking care of the instruction * type (add the needed amount of additional cycles). */ int Cycles_GetCounterOnWriteAccess(int nId) { int AddCycles; AddCycles = Cycles_GetInternalCycleOnWriteAccess(); return Cycles_GetCounter(nId) + AddCycles; }
/** * Initialize CPU profiling when necessary. Return true if profiling. */ bool Profile_CpuStart(void) { int size; Profile_FreeCallinfo(&(cpu_callinfo)); if (cpu_profile.sort_arr) { /* remove previous results */ free(cpu_profile.sort_arr); free(cpu_profile.data); cpu_profile.sort_arr = NULL; cpu_profile.data = NULL; printf("Freed previous CPU profile buffers.\n"); } if (!cpu_profile.enabled) { return false; } /* zero everything */ memset(&cpu_profile, 0, sizeof(cpu_profile)); /* Shouldn't change within same debug session */ size = (STRamEnd + 0x20000 + TosSize) / 2; /* Add one entry for catching invalid PC values */ cpu_profile.data = calloc(size + 1, sizeof(*cpu_profile.data)); if (!cpu_profile.data) { perror("ERROR, new CPU profile buffer alloc failed"); return false; } printf("Allocated CPU profile buffer (%d MB).\n", (int)sizeof(*cpu_profile.data)*size/(1024*1024)); cpu_profile.size = size; Profile_AllocCallinfo(&(cpu_callinfo), Symbols_CpuCount(), "CPU"); /* special hack for EmuTOS */ etos_switcher = PC_UNDEFINED; if (cpu_callinfo.sites && bIsEmuTOS && (!Symbols_GetCpuAddress(SYMTYPE_TEXT, "_switchto", &etos_switcher) || etos_switcher < TosAddress)) { etos_switcher = PC_UNDEFINED; } cpu_profile.prev_cycles = Cycles_GetCounter(CYCLES_COUNTER_CPU); cpu_profile.prev_family = OpcodeFamily; cpu_profile.prev_pc = M68000_GetPC() & 0xffffff; cpu_profile.loop_start = PC_UNDEFINED; cpu_profile.loop_end = PC_UNDEFINED; cpu_profile.loop_count = 0; Profile_LoopReset(); cpu_profile.disasm_addr = 0; cpu_profile.processed = false; cpu_profile.enabled = true; return cpu_profile.enabled; }
/** * Return cycles passed for an interrupt handler */ int CycInt_FindCyclesPassed(interrupt_id Handler, int CycleType) { Sint64 CyclesPassed, CyclesFromLastInterrupt; CyclesFromLastInterrupt = InterruptHandlers[ActiveInterrupt].Cycles - PendingInterruptCount; CyclesPassed = InterruptHandlers[Handler].Cycles - CyclesFromLastInterrupt; LOG_TRACE(TRACE_INT, "int find passed cyc video_cyc=%d handler=%d last_cyc=%lld passed_cyc=%lld\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), Handler, (long long)CyclesFromLastInterrupt, (long long)CyclesPassed); return INT_CONVERT_FROM_INTERNAL ( CyclesPassed , CycleType ) ; }
/** * Resume a stopped interrupt from its current cycle count (for MFP timers) */ void CycInt_ResumeStoppedInterrupt(interrupt_id Handler) { /* Restart interrupt */ InterruptHandlers[Handler].bUsed = true; /* Update list cycle counts */ CycInt_UpdateInterrupt(); /* Set new */ CycInt_SetNewInterrupt(); LOG_TRACE(TRACE_INT, "int resume stopped video_cyc=%d handler=%d handler_cyc=%lld pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), Handler, (long long)InterruptHandlers[Handler].Cycles, PendingInterruptCount); }
/** * Adjust all interrupt timings as 'ActiveInterrupt' has occured, and * remove from active list. */ void CycInt_AcknowledgeInterrupt(void) { /* Update list cycle counts */ CycInt_UpdateInterrupt(); /* Disable interrupt entry which has just occured */ InterruptHandlers[ActiveInterrupt].bUsed = false; /* Set new */ CycInt_SetNewInterrupt(); LOG_TRACE(TRACE_INT, "int ack video_cyc=%d active_int=%d active_cyc=%d pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), ActiveInterrupt, (int)InterruptHandlers[ActiveInterrupt].Cycles, PendingInterruptCount ); }
/** * Remove a pending interrupt from our table */ void CycInt_RemovePendingInterrupt(interrupt_id Handler) { /* Update list cycle counts, including the handler we want to remove */ /* to be able to resume it later (for MFP timers) */ CycInt_UpdateInterrupt(); /* Stop interrupt after CycInt_UpdateInterrupt, for CycInt_ResumeStoppedInterrupt */ InterruptHandlers[Handler].bUsed = false; /* Set new */ CycInt_SetNewInterrupt(); LOG_TRACE(TRACE_INT, "int remove pending video_cyc=%d handler=%d handler_cyc=%lld pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), Handler, (long long)InterruptHandlers[Handler].Cycles, PendingInterruptCount); }
void CycInt_AddRelativeInterruptNoOffset(int CycleTime, int CycleType, interrupt_id Handler) { /* Update list cycle counts before adding a new one, */ /* since CycInt_SetNewInterrupt can change the active int / PendingInterruptCount */ if ( ( ActiveInterrupt > 0 ) && ( PendingInterruptCount > 0 ) ) CycInt_UpdateInterrupt(); // nCyclesOver = 0; InterruptHandlers[Handler].bUsed = true; InterruptHandlers[Handler].Cycles = INT_CONVERT_TO_INTERNAL((Sint64)CycleTime , CycleType) + PendingInterruptCount; /* Set new */ CycInt_SetNewInterrupt(); LOG_TRACE(TRACE_INT, "int add rel no_off video_cyc=%d handler=%d handler_cyc=%lld pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), Handler, InterruptHandlers[Handler].Cycles, PendingInterruptCount ); }
/** * Add interrupt to occur after CycleTime/CycleType + CycleOffset. * CycleOffset can be used to add another delay to the resulting * number of internal cycles (should be 0 most of the time, except in * the MFP emulation to start timers precisely based on the number of * cycles of the current instruction). * This allows to restart an MFP timer just after it expired. */ void CycInt_AddRelativeInterruptWithOffset(int CycleTime, int CycleType, interrupt_id Handler, int CycleOffset) { assert(CycleTime >= 0); /* Update list cycle counts with current PendingInterruptCount before adding a new int, */ /* because CycInt_SetNewInterrupt can change the active int / PendingInterruptCount */ if ( ActiveInterrupt > 0 ) CycInt_UpdateInterrupt(); InterruptHandlers[Handler].bUsed = true; InterruptHandlers[Handler].Cycles = INT_CONVERT_TO_INTERNAL((Sint64)CycleTime , CycleType) + CycleOffset; /* Set new active int and compute a new value for PendingInterruptCount*/ CycInt_SetNewInterrupt(); LOG_TRACE(TRACE_INT, "int add rel offset video_cyc=%d handler=%d handler_cyc=%lld offset_cyc=%d pending_count=%d\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), Handler, (long long)InterruptHandlers[Handler].Cycles, CycleOffset, PendingInterruptCount); }
/** * Adjust all interrupt timings, MUST call CycInt_SetNewInterrupt after this. */ static void CycInt_UpdateInterrupt(void) { Sint64 CycleSubtract; int i; /* Find out how many cycles we went over (<=0) */ nCyclesOver = PendingInterruptCount; /* Calculate how many cycles have passed, included time we went over */ CycleSubtract = InterruptHandlers[ActiveInterrupt].Cycles - nCyclesOver; /* Adjust table */ for (i = 0; i < MAX_INTERRUPTS; i++) { if (InterruptHandlers[i].bUsed) InterruptHandlers[i].Cycles -= CycleSubtract; } LOG_TRACE(TRACE_INT, "int upd video_cyc=%d cycle_over=%d cycle_sub=%lld\n", Cycles_GetCounter(CYCLES_COUNTER_VIDEO), nCyclesOver, (long long)CycleSubtract); }
/** * Update CPU cycle and count statistics for PC address. * * This gets called after instruction has executed and PC * has advanced to next instruction. */ void Profile_CpuUpdate(void) { counters_t *counters = &(cpu_profile.all); Uint32 pc, prev_pc, idx, cycles, misses; cpu_profile_item_t *prev; prev_pc = cpu_profile.prev_pc; /* PC may have extra bits, they need to be masked away as * emulation itself does that too when PC value is used */ cpu_profile.prev_pc = pc = M68000_GetPC() & 0xffffff; if (unlikely(profile_loop.fp)) { if (pc < prev_pc) { if (pc == cpu_profile.loop_start && prev_pc == cpu_profile.loop_end) { cpu_profile.loop_count++; } else { cpu_profile.loop_start = pc; cpu_profile.loop_end = prev_pc; cpu_profile.loop_count = 1; } } else { if (pc > cpu_profile.loop_end) { log_last_loop(); cpu_profile.loop_end = 0xffffffff; cpu_profile.loop_count = 0; } } } idx = address2index(prev_pc); assert(idx <= cpu_profile.size); prev = cpu_profile.data + idx; if (likely(prev->count < MAX_CPU_PROFILE_VALUE)) { prev->count++; } #if USE_CYCLES_COUNTER /* Confusingly, with DSP enabled, cycle counter is for this instruction, * without DSP enabled, it's a monotonically increasing counter. */ if (bDspEnabled) { cycles = Cycles_GetCounter(CYCLES_COUNTER_CPU); } else { Uint32 newcycles = Cycles_GetCounter(CYCLES_COUNTER_CPU); cycles = newcycles - cpu_profile.prev_cycles; cpu_profile.prev_cycles = newcycles; } #else cycles = CurrentInstrCycles + nWaitStateCycles; #endif /* cycles are based on 8Mhz clock, change them to correct one */ cycles <<= nCpuFreqShift; if (likely(prev->cycles < MAX_CPU_PROFILE_VALUE - cycles)) { prev->cycles += cycles; } else { prev->cycles = MAX_CPU_PROFILE_VALUE; } #if ENABLE_WINUAE_CPU misses = CpuInstruction.iCacheMisses; assert(misses < MAX_MISS); cpu_profile.miss_counts[misses]++; if (likely(prev->misses < MAX_CPU_PROFILE_VALUE - misses)) { prev->misses += misses; } else { prev->misses = MAX_CPU_PROFILE_VALUE; } #else misses = 0; #endif if (cpu_callinfo.sites) { collect_calls(prev_pc, counters); } /* counters are increased after caller info is processed, * otherwise cost for the instruction calling the callee * doesn't get accounted to caller (but callee). */ counters->misses += misses; counters->cycles += cycles; counters->count++; #if DEBUG if (unlikely(OpcodeFamily == 0)) { Uint32 nextpc; fputs("WARNING: instruction opcode family is zero (=i_ILLG) for instruction:\n", stderr); Disasm(stderr, prev_pc, &nextpc, 1); } /* catch too large (and negative) cycles for other than STOP instruction */ if (unlikely(cycles > 512 && OpcodeFamily != i_STOP)) { Uint32 nextpc; fprintf(stderr, "WARNING: cycles %d > 512:\n", cycles); Disasm(stderr, prev_pc, &nextpc, 1); } if (unlikely(cycles == 0)) { Uint32 nextpc; fputs("WARNING: Zero cycles for an opcode:\n", stderr); Disasm(stderr, prev_pc, &nextpc, 1); } #endif }