diff options
Diffstat (limited to 'src/emu/cpuexec.c')
-rw-r--r-- | src/emu/cpuexec.c | 1511 |
1 files changed, 1511 insertions, 0 deletions
diff --git a/src/emu/cpuexec.c b/src/emu/cpuexec.c new file mode 100644 index 00000000000..8c7556ef092 --- /dev/null +++ b/src/emu/cpuexec.c @@ -0,0 +1,1511 @@ +/*************************************************************************** + + cpuexec.c + + Core multi-CPU execution engine. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include <math.h> +#include "driver.h" +#include "cheat.h" +#include "profiler.h" +#include "debugger.h" + +#ifdef MAME_DEBUG +#include "debug/debugcpu.h" +#endif + + + +/************************************* + * + * Debug logging + * + *************************************/ + +#define VERBOSE 0 + +#if VERBOSE +#define LOG(x) logerror x +#else +#define LOG(x) +#endif + + + +/************************************* + * + * Macros to help verify active CPU + * + *************************************/ + +#define VERIFY_ACTIVECPU(name) \ + int activecpu = cpu_getactivecpu(); \ + assert_always(activecpu >= 0, #name "() called with no active cpu!") + +#define VERIFY_EXECUTINGCPU(name) \ + int activecpu = cpu_getexecutingcpu(); \ + assert_always(activecpu >= 0, #name "() called with no executing cpu!") + +#define VERIFY_CPUNUM(name) \ + assert_always(cpunum >= 0 && cpunum < cpu_gettotalcpu(), #name "() called for invalid cpu num!") + + + +/************************************* + * + * Triggers for the timer system + * + *************************************/ + +enum +{ + TRIGGER_TIMESLICE = -1000, + TRIGGER_INT = -2000, + TRIGGER_YIELDTIME = -3000, + TRIGGER_SUSPENDTIME = -4000 +}; + + + +/************************************* + * + * Internal CPU info structure + * + *************************************/ + +typedef struct _cpuexec_data cpuexec_data; +struct _cpuexec_data +{ + UINT8 saveable; /* true if saveable */ + + UINT8 suspend; /* suspend reason mask (0 = not suspended) */ + UINT8 nextsuspend; /* pending suspend reason mask */ + UINT8 eatcycles; /* true if we eat cycles while suspended */ + UINT8 nexteatcycles; /* pending value */ + INT32 trigger; /* pending trigger to release a trigger suspension */ + + INT32 iloops; /* number of interrupts remaining this frame */ + + UINT64 totalcycles; /* total CPU cycles executed */ + attotime localtime; /* local time, relative to the timer system's global time */ + INT32 clock; /* current active clock */ + double clockscale; /* current active clock scale factor */ + + INT32 vblankint_countdown; /* number of vblank callbacks left until we interrupt */ + INT32 vblankint_multiplier; /* number of vblank callbacks per interrupt */ + void * vblankint_timer; /* reference to elapsed time counter */ + + void * timedint_timer; /* reference to this CPU's timer */ + attotime timedint_period; /* timing period of the timed interrupt */ +}; + + + +/************************************* + * + * General CPU variables + * + *************************************/ + +static cpuexec_data cpu[MAX_CPU]; + +static UINT8 vblank; +static UINT32 current_frame; +static INT32 watchdog_counter; + +static int cycles_running; +static int cycles_stolen; + + + +/************************************* + * + * Timer variables + * + *************************************/ + +static emu_timer *vblank_timer; +static INT32 vblank_countdown; +static INT32 vblank_multiplier; +static attotime vblank_period; + +static emu_timer *update_timer; + +emu_timer *refresh_timer; /* temporarily made non-static (for ccpu) */ +static attotime refresh_period; + +static emu_timer *timeslice_timer; +static attotime timeslice_period; + +static emu_timer *interleave_boost_timer; +static emu_timer *interleave_boost_timer_end; +static attotime perfect_interleave; + +static emu_timer *watchdog_timer; + + + +/************************************* + * + * Static prototypes + * + *************************************/ + +static void cpuexec_exit(running_machine *machine); +static void cpuexec_reset(running_machine *machine); +static void cpu_inittimers(running_machine *machine); +static void cpu_vblankreset(void); +static TIMER_CALLBACK( cpu_vblankcallback ); +static TIMER_CALLBACK( cpu_updatecallback ); +static TIMER_CALLBACK( end_interleave_boost ); +static void compute_perfect_interleave(void); +static void watchdog_setup(int alloc_new); + + + +/************************************* + * + * Watchdog Flags + * + *************************************/ + +#define WATCHDOG_IS_STARTED_DISABLED -1 +#define WATCHDOG_IS_DISABLED -2 +#define WATCHDOG_IS_TIMER_BASED -3 +#define WATCHDOG_IS_INVALID -4 +#define WATCHDOG_IS_BEING_STARTED -5 + + + +#if 0 +#pragma mark CORE CPU +#endif + +/************************************* + * + * Initialize all the CPUs + * + *************************************/ + +void cpuexec_init(running_machine *machine) +{ + int cpunum; + + /* if there has been no VBLANK time specified in the MACHINE_DRIVER, compute it now + from the visible area */ + if (machine->screen[0].vblank == 0 && !machine->screen[0].oldstyle_vblank_supplied) + machine->screen[0].vblank = (machine->screen[0].refresh / machine->screen[0].height) * (machine->screen[0].height - (machine->screen[0].visarea.max_y + 1 - machine->screen[0].visarea.min_y)); + + /* allocate vblank and refresh timers, and compute the initial timing */ + vblank_timer = timer_alloc(cpu_vblankcallback); + refresh_timer = timer_alloc(NULL); + cpu_compute_vblank_timing(); + + /* loop over all our CPUs */ + for (cpunum = 0; cpunum < MAX_CPU; cpunum++) + { + cpu_type cputype = machine->drv->cpu[cpunum].type; + int num_regs; + + /* if this is a dummy, stop looking */ + if (cputype == CPU_DUMMY) + break; + + /* initialize the cpuinfo struct */ + memset(&cpu[cpunum], 0, sizeof(cpu[cpunum])); + cpu[cpunum].suspend = SUSPEND_REASON_RESET; + cpu[cpunum].clock = machine->drv->cpu[cpunum].clock; + cpu[cpunum].clockscale = 1.0; + cpu[cpunum].localtime = attotime_zero; + + /* compute the cycle times */ + cycles_per_second[cpunum] = cpu[cpunum].clockscale * cpu[cpunum].clock; + attoseconds_per_cycle[cpunum] = ATTOSECONDS_PER_SECOND / (cpu[cpunum].clockscale * cpu[cpunum].clock); + + /* register some of our variables for later */ + state_save_register_item("cpu", cpunum, cpu[cpunum].suspend); + state_save_register_item("cpu", cpunum, cpu[cpunum].nextsuspend); + state_save_register_item("cpu", cpunum, cpu[cpunum].eatcycles); + state_save_register_item("cpu", cpunum, cpu[cpunum].nexteatcycles); + state_save_register_item("cpu", cpunum, cpu[cpunum].trigger); + + state_save_register_item("cpu", cpunum, cpu[cpunum].iloops); + + state_save_register_item("cpu", cpunum, cpu[cpunum].totalcycles); + state_save_register_item("cpu", cpunum, cpu[cpunum].localtime.seconds); + state_save_register_item("cpu", cpunum, cpu[cpunum].localtime.attoseconds); + state_save_register_item("cpu", cpunum, cpu[cpunum].clock); + state_save_register_item("cpu", cpunum, cpu[cpunum].clockscale); + + state_save_register_item("cpu", cpunum, cpu[cpunum].vblankint_countdown); + + /* initialize this CPU */ + state_save_push_tag(cpunum + 1); + num_regs = state_save_get_reg_count(); + if (cpuintrf_init_cpu(cpunum, cputype, cpu[cpunum].clock, machine->drv->cpu[cpunum].reset_param, cpu_irq_callbacks[cpunum])) + fatalerror("Unable to initialize CPU #%d (%s)", cpunum, cputype_name(cputype)); + num_regs = state_save_get_reg_count() - num_regs; + state_save_pop_tag(); + + /* if no state registered for saving, we can't save */ + if (num_regs == 0) + { + logerror("CPU #%d (%s) did not register any state to save!\n", cpunum, cputype_name(cputype)); + if (machine->gamedrv->flags & GAME_SUPPORTS_SAVE) + fatalerror("CPU #%d (%s) did not register any state to save!", cpunum, cputype_name(cputype)); + } + } + add_reset_callback(machine, cpuexec_reset); + add_exit_callback(machine, cpuexec_exit); + + /* compute the perfect interleave factor */ + compute_perfect_interleave(); + + /* save some stuff in the default tag */ + state_save_push_tag(0); + state_save_register_item("cpu", 0, vblank); + state_save_register_item("cpu", 0, current_frame); + state_save_register_item("cpu", 0, watchdog_counter); + state_save_register_item("cpu", 0, vblank_countdown); + state_save_pop_tag(); +} + + + +/************************************* + * + * Prepare the system for execution + * + *************************************/ + +static void cpuexec_reset(running_machine *machine) +{ + int cpunum; + + /* initialize the various timers (suspends all CPUs at startup) */ + cpu_inittimers(machine); + watchdog_counter = WATCHDOG_IS_INVALID; + watchdog_setup(TRUE); + + /* first pass over CPUs */ + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + /* enable all CPUs (except for disabled CPUs) */ + if (!(machine->drv->cpu[cpunum].flags & CPU_DISABLE)) + cpunum_resume(cpunum, SUSPEND_ANY_REASON); + else + cpunum_suspend(cpunum, SUSPEND_REASON_DISABLE, 1); + + /* reset the total number of cycles */ + cpu[cpunum].totalcycles = 0; + + /* then reset the CPU directly */ + cpunum_reset(cpunum); + } + + /* reset the globals */ + cpu_vblankreset(); + vblank = 0; + current_frame = 0; +} + + + +/************************************* + * + * Deinitialize all the CPUs + * + *************************************/ + +static void cpuexec_exit(running_machine *machine) +{ + int cpunum; + + /* shut down the CPU cores */ + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + cpuintrf_exit_cpu(cpunum); +} + + + + +#if 0 +#pragma mark - +#pragma mark WATCHDOG +#endif + +/************************************* + * + * Watchdog timer callback + * + *************************************/ + +static TIMER_CALLBACK( watchdog_callback ) +{ + logerror("reset caused by the (time) watchdog\n"); + mame_schedule_soft_reset(machine); +} + + + +/************************************* + * + * Watchdog setup routine + * + *************************************/ + +static void watchdog_setup(int alloc_new) +{ + if (watchdog_counter != WATCHDOG_IS_DISABLED) + { + if (Machine->drv->watchdog_vblank_count) + { + /* Start a vblank based watchdog. */ + watchdog_counter = Machine->drv->watchdog_vblank_count; + } + else if (attotime_compare(Machine->drv->watchdog_time, attotime_zero) != 0) + { + /* Start a time based watchdog. */ + if (alloc_new) + watchdog_timer = timer_alloc(watchdog_callback); + timer_adjust(watchdog_timer, Machine->drv->watchdog_time, 0, attotime_zero); + watchdog_counter = WATCHDOG_IS_TIMER_BASED; + } + else if (watchdog_counter == WATCHDOG_IS_INVALID) + { + /* The watchdog was not initialized in the MACHINE_DRIVER, + * so we will start with it disabled. + */ + watchdog_counter = WATCHDOG_IS_STARTED_DISABLED; + } + else + { + /* The watchdog was not initialized in the MACHINE_DRIVER. + * But it has been manually started, so we will default to + * using a vblank watchdog. We will set up a default time + * of 3 times the refresh rate. Which is 3 seconds @ 60Hz + * refresh. + + * The 3 seconds delay is targeted at qzshowby, which otherwise + * would reset at the start of a game. + */ + watchdog_counter = 3 * ATTOSECONDS_TO_HZ(Machine->screen[0].refresh); + } + } +} + + + +/************************************* + * + * Watchdog reset + * + *************************************/ + +void watchdog_reset(void) +{ + if (watchdog_counter == WATCHDOG_IS_TIMER_BASED) + { + timer_reset(watchdog_timer, Machine->drv->watchdog_time); + } + else + { + if (watchdog_counter == WATCHDOG_IS_STARTED_DISABLED) + { + watchdog_counter = WATCHDOG_IS_BEING_STARTED; + logerror("(vblank) watchdog armed by reset\n"); + } + + watchdog_setup(FALSE); + } +} + + + +/************************************* + * + * Watchdog enable/disable + * + *************************************/ + +void watchdog_enable(int enable) +{ + if (!enable) + { + // Disable all timers + watchdog_counter = WATCHDOG_IS_DISABLED; + } + else + // Setup only on change from disable to enable. + // Do not setup if watchdog is disabled from machine init. + if (watchdog_counter == WATCHDOG_IS_DISABLED) + { + watchdog_counter = WATCHDOG_IS_BEING_STARTED; + watchdog_setup(FALSE); + } +} + + + +#if 0 +#pragma mark - +#pragma mark CPU SCHEDULING +#endif + +/************************************* + * + * Execute all the CPUs for one + * timeslice + * + *************************************/ + +void cpuexec_timeslice(void) +{ + attotime target = timer_next_fire_time(); + attotime base = timer_get_time(); + int cpunum, ran; + + LOG(("------------------\n")); + LOG(("cpu_timeslice: target = %s\n", attotime_string(target, 9))); + + /* process any pending suspends */ + for (cpunum = 0; Machine->drv->cpu[cpunum].type != CPU_DUMMY; cpunum++) + { + if (cpu[cpunum].suspend != cpu[cpunum].nextsuspend) + LOG(("--> updated CPU%d suspend from %X to %X\n", cpunum, cpu[cpunum].suspend, cpu[cpunum].nextsuspend)); + cpu[cpunum].suspend = cpu[cpunum].nextsuspend; + cpu[cpunum].eatcycles = cpu[cpunum].nexteatcycles; + } + + /* loop over CPUs */ + for (cpunum = 0; Machine->drv->cpu[cpunum].type != CPU_DUMMY; cpunum++) + { + /* only process if we're not suspended */ + if (!cpu[cpunum].suspend) + { + /* compute how long to run */ + cycles_running = ATTOTIME_TO_CYCLES(cpunum, attotime_sub(target, cpu[cpunum].localtime)); + LOG((" cpu %d: %d cycles\n", cpunum, cycles_running)); + + /* run for the requested number of cycles */ + if (cycles_running > 0) + { + profiler_mark(PROFILER_CPU1 + cpunum); + + /* note that this global variable cycles_stolen can be modified */ + /* via the call to the cpunum_execute */ + cycles_stolen = 0; + ran = cpunum_execute(cpunum, cycles_running); + +#ifdef MAME_DEBUG + if (ran < cycles_stolen) + fatalerror("Negative CPU cycle count!"); +#endif /* MAME_DEBUG */ + + ran -= cycles_stolen; + profiler_mark(PROFILER_END); + + /* account for these cycles */ + cpu[cpunum].totalcycles += ran; + cpu[cpunum].localtime = attotime_add(cpu[cpunum].localtime, ATTOTIME_IN_CYCLES(ran, cpunum)); + LOG((" %d ran, %d total, time = %s\n", ran, (INT32)cpu[cpunum].totalcycles, attotime_string(cpu[cpunum].localtime, 9))); + + /* if the new local CPU time is less than our target, move the target up */ + if (attotime_compare(cpu[cpunum].localtime, target) < 0) + { + if (attotime_compare(cpu[cpunum].localtime, base) > 0) + target = cpu[cpunum].localtime; + else + target = base; + LOG((" (new target)\n")); + } + } + } + } + + /* update the local times of all CPUs */ + for (cpunum = 0; Machine->drv->cpu[cpunum].type != CPU_DUMMY; cpunum++) + { + /* if we're suspended and counting, process */ + if (cpu[cpunum].suspend && cpu[cpunum].eatcycles && attotime_compare(cpu[cpunum].localtime, target) < 0) + { + /* compute how long to run */ + cycles_running = ATTOTIME_TO_CYCLES(cpunum, attotime_sub(target, cpu[cpunum].localtime)); + LOG((" cpu %d: %d cycles (suspended)\n", cpunum, cycles_running)); + + cpu[cpunum].totalcycles += cycles_running; + cpu[cpunum].localtime = attotime_add(cpu[cpunum].localtime, ATTOTIME_IN_CYCLES(cycles_running, cpunum)); + LOG((" %d skipped, %d total, time = %s\n", cycles_running, (INT32)cpu[cpunum].totalcycles, attotime_string(cpu[cpunum].localtime, 9))); + } + + /* update the suspend state */ + if (cpu[cpunum].suspend != cpu[cpunum].nextsuspend) + LOG(("--> updated CPU%d suspend from %X to %X\n", cpunum, cpu[cpunum].suspend, cpu[cpunum].nextsuspend)); + cpu[cpunum].suspend = cpu[cpunum].nextsuspend; + cpu[cpunum].eatcycles = cpu[cpunum].nexteatcycles; + } + + /* update the global time */ + timer_set_global_time(target); +} + + + +/************************************* + * + * Abort the timeslice for the + * active CPU + * + *************************************/ + +void activecpu_abort_timeslice(void) +{ + int current_icount; + + VERIFY_EXECUTINGCPU(activecpu_abort_timeslice); + LOG(("activecpu_abort_timeslice (CPU=%d, cycles_left=%d)\n", cpu_getexecutingcpu(), activecpu_get_icount() + 1)); + + /* swallow the remaining cycles */ + current_icount = activecpu_get_icount() + 1; + cycles_stolen += current_icount; + cycles_running -= current_icount; + activecpu_adjust_icount(-current_icount); +} + + + +/************************************* + * + * Return the current local time for + * a CPU, relative to the current + * timeslice + * + *************************************/ + +attotime cpunum_get_localtime(int cpunum) +{ + attotime result; + + VERIFY_CPUNUM(cpunum_get_localtime); + + /* if we're active, add in the time from the current slice */ + result = cpu[cpunum].localtime; + if (cpunum == cpu_getexecutingcpu()) + { + int cycles = cycles_currently_ran(); + result = attotime_add(result, ATTOTIME_IN_CYCLES(cycles, cpunum)); + } + return result; +} + + + +/************************************* + * + * Set a suspend reason for the + * given CPU + * + *************************************/ + +void cpunum_suspend(int cpunum, int reason, int eatcycles) +{ + VERIFY_CPUNUM(cpunum_suspend); + LOG(("cpunum_suspend (CPU=%d, r=%X, eat=%d)\n", cpunum, reason, eatcycles)); + + /* set the pending suspend bits, and force a resync */ + cpu[cpunum].nextsuspend |= reason; + cpu[cpunum].nexteatcycles = eatcycles; + if (cpu_getexecutingcpu() >= 0) + activecpu_abort_timeslice(); +} + + + +/************************************* + * + * Clear a suspend reason for a + * given CPU + * + *************************************/ + +void cpunum_resume(int cpunum, int reason) +{ + VERIFY_CPUNUM(cpunum_resume); + LOG(("cpunum_resume (CPU=%d, r=%X)\n", cpunum, reason)); + + /* clear the pending suspend bits, and force a resync */ + cpu[cpunum].nextsuspend &= ~reason; + if (cpu_getexecutingcpu() >= 0) + activecpu_abort_timeslice(); +} + + + +/************************************* + * + * Return true if a given CPU is + * suspended + * + *************************************/ + +int cpunum_is_suspended(int cpunum, int reason) +{ + VERIFY_CPUNUM(cpunum_suspend); + return ((cpu[cpunum].nextsuspend & reason) != 0); +} + + + +/************************************* + * + * Gets the current CPU's clock speed + * + *************************************/ + +int cpunum_get_clock(int cpunum) +{ + VERIFY_CPUNUM(cpunum_get_clock); + return cpu[cpunum].clock; +} + + + +/************************************* + * + * Sets the current CPU's clock speed + * + *************************************/ + +void cpunum_set_clock(int cpunum, int clock) +{ + VERIFY_CPUNUM(cpunum_set_clock); + + cpu[cpunum].clock = clock; + cycles_per_second[cpunum] = (double)clock * cpu[cpunum].clockscale; + attoseconds_per_cycle[cpunum] = ATTOSECONDS_PER_SECOND / ((double)clock * cpu[cpunum].clockscale); + + /* re-compute the perfect interleave factor */ + compute_perfect_interleave(); +} + + + +void cpunum_set_clock_period(int cpunum, attoseconds_t clock_period) +{ + VERIFY_CPUNUM(cpunum_set_clock); + + cpu[cpunum].clock = ATTOSECONDS_PER_SECOND / clock_period; + cycles_per_second[cpunum] = (double) (ATTOSECONDS_PER_SECOND / clock_period) * cpu[cpunum].clockscale; + attoseconds_per_cycle[cpunum] = clock_period; + + /* re-compute the perfect interleave factor */ + compute_perfect_interleave(); +} + + + +/************************************* + * + * Returns the current scaling factor + * for a CPU's clock speed + * + *************************************/ + +double cpunum_get_clockscale(int cpunum) +{ + VERIFY_CPUNUM(cpunum_get_clockscale); + return cpu[cpunum].clockscale; +} + + + +/************************************* + * + * Sets the current scaling factor + * for a CPU's clock speed + * + *************************************/ + +void cpunum_set_clockscale(int cpunum, double clockscale) +{ + VERIFY_CPUNUM(cpunum_set_clockscale); + + cpu[cpunum].clockscale = clockscale; + cycles_per_second[cpunum] = (double)cpu[cpunum].clock * clockscale; + attoseconds_per_cycle[cpunum] = ATTOSECONDS_PER_SECOND / ((double)cpu[cpunum].clock * clockscale); + + /* re-compute the perfect interleave factor */ + compute_perfect_interleave(); +} + + + +/************************************* + * + * Temporarily boosts the interleave + * factor + * + *************************************/ + +void cpu_boost_interleave(attotime timeslice_time, attotime boost_duration) +{ + /* if you pass 0 for the timeslice_time, it means pick something reasonable */ + if (attotime_compare(timeslice_time, perfect_interleave) < 0) + timeslice_time = perfect_interleave; + + LOG(("cpu_boost_interleave(%s, %s)\n", attotime_string(timeslice_time, 9), attotime_string(boost_duration, 9))); + + /* adjust the interleave timer */ + timer_adjust(interleave_boost_timer, timeslice_time, 0, timeslice_time); + + /* adjust the end timer, but only if we are going to extend it */ + if (!timer_enabled(interleave_boost_timer_end) || attotime_compare(timer_timeleft(interleave_boost_timer_end), boost_duration) < 0) + timer_adjust(interleave_boost_timer_end, boost_duration, 0, attotime_never); +} + + + +#if 0 +#pragma mark - +#pragma mark TIMING HELPERS +#endif + +/************************************* + * + * Return cycles ran this iteration + * + *************************************/ + +int cycles_currently_ran(void) +{ + VERIFY_EXECUTINGCPU(cycles_currently_ran); + return cycles_running - activecpu_get_icount(); +} + + + +/************************************* + * + * Return total number of CPU cycles + * for the active CPU or for a given CPU. + * + *************************************/ + +/*-------------------------------------------------------------- + + IMPORTANT: this value wraps around in a relatively short + time. For example, for a 6MHz CPU, it will wrap around in + 2^32/6000000 = 716 seconds = 12 minutes. + + Make sure you don't do comparisons between values returned + by this function, but only use the difference (which will + be correct regardless of wraparound). + + Alternatively, use the new 64-bit variants instead. + +--------------------------------------------------------------*/ + +UINT32 activecpu_gettotalcycles(void) +{ + VERIFY_ACTIVECPU(activecpu_gettotalcycles); + if (activecpu == cpu_getexecutingcpu()) + return cpu[activecpu].totalcycles + cycles_currently_ran(); + else + return cpu[activecpu].totalcycles; +} + +UINT32 cpunum_gettotalcycles(int cpunum) +{ + VERIFY_CPUNUM(cpunum_gettotalcycles); + if (cpunum == cpu_getexecutingcpu()) + return cpu[cpunum].totalcycles + cycles_currently_ran(); + else + return cpu[cpunum].totalcycles; +} + + +UINT64 activecpu_gettotalcycles64(void) +{ + VERIFY_ACTIVECPU(activecpu_gettotalcycles64); + if (activecpu == cpu_getexecutingcpu()) + return cpu[activecpu].totalcycles + cycles_currently_ran(); + else + return cpu[activecpu].totalcycles; +} + +UINT64 cpunum_gettotalcycles64(int cpunum) +{ + VERIFY_CPUNUM(cpunum_gettotalcycles64); + if (cpunum == cpu_getexecutingcpu()) + return cpu[cpunum].totalcycles + cycles_currently_ran(); + else + return cpu[cpunum].totalcycles; +} + + + +/************************************* + * + * Safely eats cycles so we don't + * cross a timeslice boundary + * + *************************************/ + +void activecpu_eat_cycles(int cycles) +{ + int cyclesleft = activecpu_get_icount(); + if (cycles > cyclesleft) + cycles = cyclesleft; + activecpu_adjust_icount(-cycles); +} + + + +/************************************* + * + * Scales a given value by the fraction + * of time elapsed between refreshes + * + *************************************/ + +int cpu_scalebyfcount(int value) +{ + attotime refresh_elapsed = timer_timeelapsed(refresh_timer); + int result; + + /* shift off some bits to ensure no overflow */ + if (value < 65536) + result = value * (refresh_elapsed.attoseconds >> 16) / (refresh_period.attoseconds >> 16); + else + result = value * (refresh_elapsed.attoseconds >> 32) / (refresh_period.attoseconds >> 32); + if (value >= 0) + return (result < value) ? result : value; + else + return (result > value) ? result : value; +} + + + +#if 0 +#pragma mark - +#pragma mark VIDEO TIMING +#endif + +/************************************* + * + * Computes the VBLANK timing + * + *************************************/ + +void cpu_compute_vblank_timing(void) +{ + refresh_period = attotime_make(0, Machine->screen[0].refresh); + + /* recompute the vblank period */ + vblank_period = attotime_make(0, Machine->screen[0].refresh / (vblank_multiplier ? vblank_multiplier : 1)); + if (vblank_timer != NULL && timer_enable(vblank_timer, FALSE)) + { + attotime remaining = timer_timeleft(vblank_timer); + if (remaining.seconds == 0 && remaining.attoseconds == 0) + remaining = vblank_period; + timer_adjust(vblank_timer, remaining, 0, vblank_period); + } + + LOG(("cpu_compute_vblank_timing: refresh=%s vblank=%s\n", attotime_string(refresh_period, 9), attotime_string(vblank_period, 9))); +} + + + +/************************************* + * + * Returns the VBLANK state + * + *************************************/ + +int cpu_getvblank(void) +{ + return vblank; +} + + + +/************************************* + * + * Returns the current frame count + * + *************************************/ + +int cpu_getcurrentframe(void) +{ + return current_frame; +} + + + +#if 0 +#pragma mark - +#pragma mark SYNCHRONIZATION +#endif + +/************************************* + * + * Generate a specific trigger + * + *************************************/ + +void cpu_trigger(int trigger) +{ + int cpunum; + + /* cause an immediate resynchronization */ + if (cpu_getexecutingcpu() >= 0) + activecpu_abort_timeslice(); + + /* look for suspended CPUs waiting for this trigger and unsuspend them */ + for (cpunum = 0; cpunum < MAX_CPU; cpunum++) + { + /* if this is a dummy, stop looking */ + if (Machine->drv->cpu[cpunum].type == CPU_DUMMY) + break; + + /* see if this is a matching trigger */ + if (cpu[cpunum].suspend && cpu[cpunum].trigger == trigger) + { + cpunum_resume(cpunum, SUSPEND_REASON_TRIGGER); + cpu[cpunum].trigger = 0; + } + } +} + + + +/************************************* + * + * Generate a trigger in the future + * + *************************************/ + +static TIMER_CALLBACK( cpu_triggertime_callback ) +{ + cpu_trigger(param); +} + + +void cpu_triggertime(attotime duration, int trigger) +{ + timer_set(duration, trigger, cpu_triggertime_callback); +} + + + +/************************************* + * + * Generate a trigger for an int + * + *************************************/ + +void cpu_triggerint(int cpunum) +{ + cpu_trigger(TRIGGER_INT + cpunum); +} + + + +/************************************* + * + * Burn/yield CPU cycles until a trigger + * + *************************************/ + +void cpu_spinuntil_trigger(int trigger) +{ + int cpunum = cpu_getexecutingcpu(); + + VERIFY_EXECUTINGCPU(cpu_spinuntil_trigger); + + /* suspend the CPU immediately if it's not already */ + cpunum_suspend(cpunum, SUSPEND_REASON_TRIGGER, 1); + + /* set the trigger */ + cpu[cpunum].trigger = trigger; +} + +void cpunum_spinuntil_trigger( int cpunum, int trigger ) +{ + VERIFY_CPUNUM(cpunum_spinuntil_trigger); + + /* suspend the CPU immediately if it's not already */ + cpunum_suspend(cpunum, SUSPEND_REASON_TRIGGER, 1); + + /* set the trigger */ + cpu[cpunum].trigger = trigger; +} + +void cpu_yielduntil_trigger(int trigger) +{ + int cpunum = cpu_getexecutingcpu(); + + VERIFY_EXECUTINGCPU(cpu_yielduntil_trigger); + + /* suspend the CPU immediately if it's not already */ + cpunum_suspend(cpunum, SUSPEND_REASON_TRIGGER, 0); + + /* set the trigger */ + cpu[cpunum].trigger = trigger; +} + + + +/************************************* + * + * Burn/yield CPU cycles until an + * interrupt + * + *************************************/ + +void cpu_spinuntil_int(void) +{ + VERIFY_EXECUTINGCPU(cpu_spinuntil_int); + cpu_spinuntil_trigger(TRIGGER_INT + activecpu); +} + + +void cpu_yielduntil_int(void) +{ + VERIFY_EXECUTINGCPU(cpu_yielduntil_int); + cpu_yielduntil_trigger(TRIGGER_INT + activecpu); +} + + + +/************************************* + * + * Burn/yield CPU cycles until the + * end of the current timeslice + * + *************************************/ + +void cpu_spin(void) +{ + cpu_spinuntil_trigger(TRIGGER_TIMESLICE); +} + + +void cpu_yield(void) +{ + cpu_yielduntil_trigger(TRIGGER_TIMESLICE); +} + + + +/************************************* + * + * Burn/yield CPU cycles for a + * specific period of time + * + *************************************/ + +void cpu_spinuntil_time(attotime duration) +{ + static int timetrig = 0; + + cpu_spinuntil_trigger(TRIGGER_SUSPENDTIME + timetrig); + cpu_triggertime(duration, TRIGGER_SUSPENDTIME + timetrig); + timetrig = (timetrig + 1) & 255; +} + + +void cpu_yielduntil_time(attotime duration) +{ + static int timetrig = 0; + + cpu_yielduntil_trigger(TRIGGER_YIELDTIME + timetrig); + cpu_triggertime(duration, TRIGGER_YIELDTIME + timetrig); + timetrig = (timetrig + 1) & 255; +} + + + +#if 0 +#pragma mark - +#pragma mark CORE TIMING +#endif + +/************************************* + * + * Returns the number of times the + * interrupt handler will be called + * before the end of the current + * video frame. + * + *************************************/ + +/*-------------------------------------------------------------- + + This can be useful to interrupt handlers to synchronize + their operation. If you call this from outside an interrupt + handler, add 1 to the result, i.e. if it returns 0, it means + that the interrupt handler will be called once. + +--------------------------------------------------------------*/ + +int cpu_getiloops(void) +{ + VERIFY_ACTIVECPU(cpu_getiloops); + return cpu[activecpu].iloops; +} + + + +/************************************* + * + * Hook for updating things on the + * real VBLANK (once per frame) + * + *************************************/ + +static void cpu_vblankreset(void) +{ + int cpunum; + + /* notify the video system of a VBLANK start */ + video_vblank_start(Machine); + + /* read keyboard & update the status of the input ports */ + input_port_vblank_start(); + + /* check the watchdog */ + if (watchdog_counter > 0) + { + if (--watchdog_counter == 0) + { + logerror("reset caused by the (vblank) watchdog\n"); + mame_schedule_soft_reset(Machine); + } + } + + /* reset the cycle counters */ + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + if (!(cpu[cpunum].suspend & SUSPEND_REASON_DISABLE)) + cpu[cpunum].iloops = Machine->drv->cpu[cpunum].vblank_interrupts_per_frame - 1; + else + cpu[cpunum].iloops = -1; + } +} + + + +/************************************* + * + * First-run callback for VBLANKs + * + *************************************/ + +static TIMER_CALLBACK( cpu_firstvblankcallback ) +{ + /* now that we're synced up, pulse from here on out */ + timer_adjust(vblank_timer, vblank_period, param, vblank_period); + + /* but we need to call the standard routine as well */ + cpu_vblankcallback(machine, param); +} + + + +/************************************* + * + * VBLANK core handler + * + *************************************/ + +static TIMER_CALLBACK( cpu_vblankcallback ) +{ + int cpunum; + + if (vblank_countdown == 1) + vblank = 1; + + /* loop over CPUs */ + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + /* if the interrupt multiplier is valid */ + if (cpu[cpunum].vblankint_multiplier != -1) + { + /* decrement; if we hit zero, generate the interrupt and reset the countdown */ + if (!--cpu[cpunum].vblankint_countdown) + { + /* a param of -1 means don't call any callbacks */ + if (param != -1) + { + /* if the CPU has a VBLANK handler, call it */ + if (machine->drv->cpu[cpunum].vblank_interrupt && !cpunum_is_suspended(cpunum, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE)) + { + cpuintrf_push_context(cpunum); + (*machine->drv->cpu[cpunum].vblank_interrupt)(); + cpuintrf_pop_context(); + } + + /* update the counters */ + cpu[cpunum].iloops--; + } + + /* reset the countdown and timer */ + cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier; + timer_adjust(cpu[cpunum].vblankint_timer, attotime_never, 0, attotime_never); + } + } + + /* else reset the VBLANK timer if this is going to be a real VBLANK */ + else if (vblank_countdown == 1) + timer_adjust(cpu[cpunum].vblankint_timer, attotime_never, 0, attotime_never); + } + + /* is it a real VBLANK? */ + if (!--vblank_countdown) + { + /* do we update the screen now? */ + if (!(machine->drv->video_attributes & VIDEO_UPDATE_AFTER_VBLANK)) + video_frame_update(); + + /* Set the timer to update the screen */ + timer_adjust(update_timer, attotime_make(0, machine->screen[0].vblank), 0, attotime_zero); + + /* reset the globals */ + cpu_vblankreset(); + + /* reset the counter */ + vblank_countdown = vblank_multiplier; + +#ifdef MAME_DEBUG + /* notify the debugger */ + debug_vblank_hook(); +#endif + } +} + + + +/************************************* + * + * End-of-VBLANK callback + * + *************************************/ + +static TIMER_CALLBACK( cpu_updatecallback ) +{ + /* update the screen if we didn't before */ + if (machine->drv->video_attributes & VIDEO_UPDATE_AFTER_VBLANK) + video_frame_update(); + vblank = 0; + + /* update IPT_VBLANK input ports */ + input_port_vblank_end(); + + /* track total frames */ + current_frame++; + + /* reset the refresh timer */ + timer_adjust(refresh_timer, attotime_never, 0, attotime_never); +} + + + +/************************************* + * + * Callback for timed interrupts + * (not tied to a VBLANK) + * + *************************************/ + +static TIMER_CALLBACK( cpu_timedintcallback ) +{ + /* bail if there is no routine */ + if (machine->drv->cpu[param].timed_interrupt && !cpunum_is_suspended(param, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE)) + { + cpuintrf_push_context(param); + (*machine->drv->cpu[param].timed_interrupt)(); + cpuintrf_pop_context(); + } +} + + + +/************************************* + * + * Callback to force a timeslice + * + *************************************/ + +static TIMER_CALLBACK( cpu_timeslicecallback ) +{ + cpu_trigger(TRIGGER_TIMESLICE); +} + + + +/************************************* + * + * Callback to end a temporary + * interleave boost + * + *************************************/ + +static TIMER_CALLBACK( end_interleave_boost ) +{ + timer_adjust(interleave_boost_timer, attotime_never, 0, attotime_never); + LOG(("end_interleave_boost\n")); +} + + + +/************************************* + * + * Compute the "perfect" interleave + * interval + * + *************************************/ + +static void compute_perfect_interleave(void) +{ + attoseconds_t smallest = attoseconds_per_cycle[0]; + int cpunum; + + /* start with a huge time factor and find the 2nd smallest cycle time */ + perfect_interleave = attotime_zero; + perfect_interleave.attoseconds = ATTOSECONDS_PER_SECOND - 1; + for (cpunum = 1; Machine->drv->cpu[cpunum].type != CPU_DUMMY; cpunum++) + { + /* find the 2nd smallest cycle interval */ + if (attoseconds_per_cycle[cpunum] < smallest) + { + perfect_interleave.attoseconds = smallest; + smallest = attoseconds_per_cycle[cpunum]; + } + else if (attoseconds_per_cycle[cpunum] < perfect_interleave.attoseconds) + perfect_interleave.attoseconds = attoseconds_per_cycle[cpunum]; + } + + /* adjust the final value */ + if (perfect_interleave.attoseconds == ATTOSECONDS_PER_SECOND - 1) + perfect_interleave.attoseconds = attoseconds_per_cycle[0]; + + LOG(("Perfect interleave = %s, smallest = %.9f\n", attotime_string(perfect_interleave), ATTOSECONDS_TO_DOUBLE(smallest))); +} + + + +/************************************* + * + * Setup all the core timers + * + *************************************/ + +static void cpu_inittimers(running_machine *machine) +{ + attotime first_time; + int cpunum, max, ipf; + + /* allocate a dummy timer at the minimum frequency to break things up */ + ipf = machine->drv->cpu_slices_per_frame; + if (ipf <= 0) + ipf = 1; + timeslice_period = attotime_make(0, machine->screen[0].refresh / ipf); + timeslice_timer = timer_alloc(cpu_timeslicecallback); + timer_adjust(timeslice_timer, timeslice_period, 0, timeslice_period); + + /* allocate timers to handle interleave boosts */ + interleave_boost_timer = timer_alloc(NULL); + interleave_boost_timer_end = timer_alloc(end_interleave_boost); + + /* + * The following code finds all the CPUs that are interrupting in sync with the VBLANK + * and sets up the VBLANK timer to run at the minimum number of cycles per frame in + * order to service all the synced interrupts + */ + + /* find the CPU with the maximum interrupts per frame */ + max = 1; + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + ipf = machine->drv->cpu[cpunum].vblank_interrupts_per_frame; + if (ipf > max) + max = ipf; + } + + /* now find the LCD with the rest of the CPUs (brute force - these numbers aren't huge) */ + vblank_multiplier = max; + while (1) + { + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + ipf = machine->drv->cpu[cpunum].vblank_interrupts_per_frame; + if (ipf > 0 && (vblank_multiplier % ipf) != 0) + break; + } + if (cpunum == cpu_gettotalcpu()) + break; + vblank_multiplier += max; + } + + /* initialize the countdown timers and intervals */ + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + ipf = machine->drv->cpu[cpunum].vblank_interrupts_per_frame; + if (ipf > 0) + cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier = vblank_multiplier / ipf; + else + cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier = -1; + } + + /* allocate a vblank timer at the frame rate * the LCD number of interrupts per frame */ + vblank_period = attotime_make(0, machine->screen[0].refresh / vblank_multiplier); + vblank_countdown = vblank_multiplier; + + /* allocate an update timer that will be used to time the actual screen updates */ + update_timer = timer_alloc(cpu_updatecallback); + + /* + * The following code creates individual timers for each CPU whose interrupts are not + * synced to the VBLANK, and computes the typical number of cycles per interrupt + */ + + /* start the CPU interrupt timers */ + for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++) + { + ipf = machine->drv->cpu[cpunum].vblank_interrupts_per_frame; + + /* compute the average number of cycles per interrupt */ + if (ipf <= 0) + ipf = 1; + cpu[cpunum].vblankint_timer = timer_alloc(NULL); + + /* see if we need to allocate a CPU timer */ + if (machine->drv->cpu[cpunum].timed_interrupt_period != 0) + { + cpu[cpunum].timedint_period = attotime_make(0, machine->drv->cpu[cpunum].timed_interrupt_period); + cpu[cpunum].timedint_timer = timer_alloc(cpu_timedintcallback); + timer_adjust(cpu[cpunum].timedint_timer, cpu[cpunum].timedint_period, cpunum, cpu[cpunum].timedint_period); + } + } + + /* note that since we start the first frame on the refresh, we can't pulse starting + immediately; instead, we back up one VBLANK period, and inch forward until we hit + positive time. That time will be the time of the first VBLANK timer callback */ + first_time = attotime_sub_attoseconds(vblank_period, machine->screen[0].vblank); + while (attotime_compare(first_time, attotime_zero) < 0) + { + cpu_vblankcallback(machine, -1); + first_time = attotime_add(first_time, vblank_period); + } + timer_set(first_time, 0, cpu_firstvblankcallback); + + /* reset the refresh timer to get ourself back in sync */ + timer_adjust(refresh_timer, attotime_never, 0, attotime_never); +} + |