/***************************************************************************
cpuexec.c
Core multi-CPU execution engine.
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include "driver.h"
#include "deprecat.h"
#include "cheat.h"
#include "profiler.h"
#include "debugger.h"
#ifdef ENABLE_DEBUGGER
#include "debug/debugcpu.h"
#endif
/***************************************************************************
DEBUGGING
***************************************************************************/
#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
/***************************************************************************
MACROS
***************************************************************************/
#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!")
/***************************************************************************
CONSTANTS
***************************************************************************/
/* internal trigger IDs */
enum
{
TRIGGER_TIMESLICE = -1000,
TRIGGER_INT = -2000,
TRIGGER_YIELDTIME = -3000,
TRIGGER_SUSPENDTIME = -4000
};
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
/* 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 */
emu_timer * vblankint_timer; /* reference to elapsed time counter */
emu_timer * timedint_timer; /* reference to this CPU's timer */
attotime timedint_period; /* timing period of the timed interrupt */
};
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
/* general CPU variables */
static cpuexec_data cpu[MAX_CPU];
static UINT8 vblank;
static UINT32 current_frame;
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;
/* watchdog state */
static UINT8 watchdog_enabled;
static INT32 watchdog_counter;
static emu_timer *watchdog_timer;
/***************************************************************************
FUNCTION 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(running_machine *machine);
static TIMER_CALLBACK( cpu_vblankcallback );
static TIMER_CALLBACK( cpu_updatecallback );
static TIMER_CALLBACK( end_interleave_boost );
static void compute_perfect_interleave(running_machine *machine);
void cpu_compute_vblank_timing(running_machine *machine);
/***************************************************************************
CORE CPU EXECUTION
***************************************************************************/
/*-------------------------------------------------
cpuexec_init - initialize internal states of
all CPUs
-------------------------------------------------*/
void cpuexec_init(running_machine *machine)
{
int numscreens = video_screen_count(machine->config);
int cpunum;
/* if there has been no VBLANK time specified in the MACHINE_DRIVER, compute it now
from the visible area */
if (numscreens > 0 && 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, NULL);
refresh_timer = timer_alloc(NULL, NULL);
cpu_compute_vblank_timing(machine);
/* loop over all our CPUs */
for (cpunum = 0; cpunum < MAX_CPU; cpunum++)
{
cpu_type cputype = machine->config->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 = (UINT64)machine->config->cpu[cpunum].clock * cputype_clock_multiplier(cputype) / cputype_clock_divider(cputype);
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->config->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(machine);
/* 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_enabled);
state_save_register_item("cpu", 0, watchdog_counter);
state_save_register_item("cpu", 0, vblank_countdown);
state_save_pop_tag();
}
/*-------------------------------------------------
cpuexec_reset - reset CPU states on a soft
reset
-------------------------------------------------*/
static void cpuexec_reset(running_machine *machine)
{
int cpunum;
/* initialize the various timers (suspends all CPUs at startup) */
cpu_inittimers(machine);
/* set up the watchdog timer; only start off enabled if explicitly configured */
watchdog_enabled = (machine->config->watchdog_vblank_count != 0 || attotime_compare(machine->config->watchdog_time, attotime_zero) != 0);
watchdog_reset(machine);
watchdog_enabled = TRUE;
/* first pass over CPUs */
for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++)
{
/* enable all CPUs (except for disabled CPUs) */
if (!(machine->config->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(machine);
vblank = 0;
current_frame = 0;
}
/*-------------------------------------------------
cpuexec_exit - cleanup all CPUs on exit
-------------------------------------------------*/
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);
}
/*-------------------------------------------------
cpuexec_timeslice - execute all CPUs for a
single timeslice
-------------------------------------------------*/
void cpuexec_timeslice(running_machine *machine)
{
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->config->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->config->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->config->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);
}
/***************************************************************************
CPU SCHEDULING
***************************************************************************/
/*-------------------------------------------------
cpu_boost_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_periodic(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_oneshot(interleave_boost_timer_end, boost_duration, 0);
}
/*-------------------------------------------------
activecpu_abort_timeslice - abort execution
for the current timeslice, allowing other
CPUs to run before we run again
-------------------------------------------------*/
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);
}
/*-------------------------------------------------
cpunum_suspend - 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();
}
/*-------------------------------------------------
cpunum_resume - clear a suspend reason for the
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();
}
/*-------------------------------------------------
cpunum_is_suspended - returns true if the
given CPU is suspended for any of the given
reasons
-------------------------------------------------*/
int cpunum_is_suspended(int cpunum, int reason)
{
VERIFY_CPUNUM(cpunum_suspend);
return ((cpu[cpunum].nextsuspend & reason) != 0);
}
/***************************************************************************
CPU CLOCK MANAGEMENT
***************************************************************************/
/*-------------------------------------------------
update_clock_information - recomputes clock
information for the specified CPU
-------------------------------------------------*/
static void update_clock_information(running_machine *machine, int cpunum)
{
/* recompute cps and spc */
cycles_per_second[cpunum] = (double)cpu[cpunum].clock * cpu[cpunum].clockscale;
attoseconds_per_cycle[cpunum] = ATTOSECONDS_PER_SECOND / ((double)cpu[cpunum].clock * cpu[cpunum].clockscale);
/* re-compute the perfect interleave factor */
compute_perfect_interleave(machine);
}
/*-------------------------------------------------
cpunum_get_clock - gets the given CPU's
clock speed
-------------------------------------------------*/
int cpunum_get_clock(int cpunum)
{
VERIFY_CPUNUM(cpunum_get_clock);
return cpu[cpunum].clock;
}
/*-------------------------------------------------
cpunum_set_clock - sets the given CPU's
clock speed
-------------------------------------------------*/
void cpunum_set_clock(running_machine *machine, int cpunum, int clock)
{
VERIFY_CPUNUM(cpunum_set_clock);
cpu[cpunum].clock = clock;
update_clock_information(machine, cpunum);
}
/*-------------------------------------------------
cpunum_get_clockscale - 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;
}
/*-------------------------------------------------
cpunum_set_clockscale - sets the current
scaling factor for a CPU's clock speed
-------------------------------------------------*/
void cpunum_set_clockscale(running_machine *machine, int cpunum, double clockscale)
{
VERIFY_CPUNUM(cpunum_set_clockscale);
cpu[cpunum].clockscale = clockscale;
update_clock_information(machine, cpunum);
}
/***************************************************************************
CPU TIMING
***************************************************************************/
/*-------------------------------------------------
cpunum_get_localtime - returns the current
local time for a CPU
-------------------------------------------------*/
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_running - activecpu_get_icount();
result = attotime_add(result, ATTOTIME_IN_CYCLES(cycles, cpunum));
}
return result;
}
/*-------------------------------------------------
activecpu_gettotalcycles - return the total
number of CPU cycles executed on the active
CPU
-------------------------------------------------*/
UINT64 activecpu_gettotalcycles(void)
{
VERIFY_ACTIVECPU(activecpu_gettotalcycles);
if (activecpu == cpu_getexecutingcpu())
return cpu[activecpu].totalcycles + cycles_running - activecpu_get_icount();
else
return cpu[activecpu].totalcycles;
}
/*-------------------------------------------------
cpunum_gettotalcycles - return the total
number of CPU cycles executed on the
specified CPU
-------------------------------------------------*/
UINT64 cpunum_gettotalcycles(int cpunum)
{
VERIFY_CPUNUM(cpunum_gettotalcycles);
if (cpunum == cpu_getexecutingcpu())
return cpu[cpunum].totalcycles + cycles_running - activecpu_get_icount();
else
return cpu[cpunum].totalcycles;
}
/*-------------------------------------------------
activecpu_eat_cycles - 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);
}
/***************************************************************************
SYNCHRONIZATION HELPERS
***************************************************************************/
/*-------------------------------------------------
cpu_suspend_until_trigger - suspend execution
until the given trigger fires
-------------------------------------------------*/
static void cpunum_suspend_until_trigger(int cpunum, int trigger, int eatcycles)
{
/* suspend the CPU immediately if it's not already */
cpunum_suspend(cpunum, SUSPEND_REASON_TRIGGER, eatcycles);
/* set the trigger */
cpu[cpunum].trigger = trigger;
}
/*-------------------------------------------------
cpu_yield - yield our current timeslice
-------------------------------------------------*/
void cpu_yield(void)
{
int cpunum = cpu_getexecutingcpu();
VERIFY_EXECUTINGCPU(cpu_yielduntil_trigger);
cpunum_suspend_until_trigger(cpunum, TRIGGER_TIMESLICE, FALSE);
}
/*-------------------------------------------------
cpu_spin - burn CPU cycles until our timeslice
is up
-------------------------------------------------*/
void cpu_spin(void)
{
int cpunum = cpu_getexecutingcpu();
VERIFY_EXECUTINGCPU(cpu_yielduntil_trigger);
cpunum_suspend_until_trigger(cpunum, TRIGGER_TIMESLICE, TRUE);
}
/*-------------------------------------------------
cpu_spinuntil_trigger - burn CPU cycles until
a timer trigger
-------------------------------------------------*/
void cpu_spinuntil_trigger(int trigger)
{
int cpunum = cpu_getexecutingcpu();
VERIFY_EXECUTINGCPU(cpu_yielduntil_trigger);
cpunum_suspend_until_trigger(cpunum, trigger, TRUE);
}
/*-------------------------------------------------
cpunum_spinuntil_trigger - burn specified CPU
cycles until a timer trigger
-------------------------------------------------*/
void cpunum_spinuntil_trigger(int cpunum, int trigger)
{
VERIFY_CPUNUM(cpunum_spinuntil_trigger);
cpunum_suspend_until_trigger(cpunum, trigger, TRUE);
}
/*-------------------------------------------------
cpu_spinuntil_int - burn CPU cycles until the
next interrupt
-------------------------------------------------*/
void cpu_spinuntil_int(void)
{
int cpunum = cpu_getexecutingcpu();
VERIFY_EXECUTINGCPU(cpu_spinuntil_int);
cpunum_suspend_until_trigger(cpunum, TRIGGER_INT + cpunum, TRUE);
}
/*-------------------------------------------------
cpu_spinuntil_time - burn CPU cycles for a
specific period of time
-------------------------------------------------*/
void cpu_spinuntil_time(attotime duration)
{
static int timetrig = 0;
int cpunum = cpu_getexecutingcpu();
VERIFY_EXECUTINGCPU(cpu_spinuntil_time);
cpunum_suspend_until_trigger(cpunum, TRIGGER_SUSPENDTIME + timetrig, TRUE);
cpu_triggertime(duration, TRIGGER_SUSPENDTIME + timetrig);
timetrig = (timetrig + 1) % 256;
}
/***************************************************************************
TRIGGERS
***************************************************************************/
/*-------------------------------------------------
cpu_trigger - generate a trigger now
-------------------------------------------------*/
void cpu_trigger(running_machine *machine, 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->config->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;
}
}
}
/*-------------------------------------------------
cpu_triggertime - generate a trigger after a
specific period of time
-------------------------------------------------*/
static TIMER_CALLBACK( cpu_triggertime_callback )
{
cpu_trigger(machine, param);
}
void cpu_triggertime(attotime duration, int trigger)
{
timer_set(duration, NULL, trigger, cpu_triggertime_callback);
}
/*-------------------------------------------------
cpu_triggerint - generate a trigger
corresponding to an interrupt on the given CPU
-------------------------------------------------*/
void cpu_triggerint(running_machine *machine, int cpunum)
{
cpu_trigger(machine, TRIGGER_INT + cpunum);
}
/***************************************************************************
WATCHDOG TIMERS
***************************************************************************/
/*-------------------------------------------------
watchdog_callback - watchdog timer callback
-------------------------------------------------*/
static TIMER_CALLBACK( watchdog_callback )
{
logerror("reset caused by the watchdog\n");
mame_schedule_soft_reset(machine);
}
/*-------------------------------------------------
watchdog_reset - reset the watchdog timer
-------------------------------------------------*/
void watchdog_reset(running_machine *machine)
{
/* if we're not enabled, skip it */
if (!watchdog_enabled)
timer_adjust_oneshot(watchdog_timer, attotime_never, 0);
/* VBLANK-based watchdog? */
else if (machine->config->watchdog_vblank_count != 0)
watchdog_counter = machine->config->watchdog_vblank_count;
/* timer-based watchdog? */
else if (attotime_compare(machine->config->watchdog_time, attotime_zero) != 0)
timer_adjust_oneshot(watchdog_timer, machine->config->watchdog_time, 0);
/* default to an obscene amount of time (3 seconds) */
else
timer_adjust_oneshot(watchdog_timer, ATTOTIME_IN_SEC(3), 0);
}
/*-------------------------------------------------
watchdog_enable - reset the watchdog timer
-------------------------------------------------*/
void watchdog_enable(running_machine *machine, int enable)
{
/* when re-enabled, we reset our state */
if (watchdog_enabled != enable)
{
watchdog_enabled = enable;
watchdog_reset(machine);
}
}
/***************************************************************************
CHEESY FAKE VIDEO TIMING
***************************************************************************/
/*-------------------------------------------------
cpu_compute_vblank_timing - recompute cheesy
VBLANK timing after a video change
-------------------------------------------------*/
void cpu_compute_vblank_timing(running_machine *machine)
{
int numscreens = video_screen_count(machine->config);
attoseconds_t refresh_attosecs;
refresh_attosecs = (numscreens == 0) ? HZ_TO_ATTOSECONDS(60) : machine->screen[0].refresh;
refresh_period = attotime_make(0, refresh_attosecs);
/* recompute the vblank period */
vblank_period = attotime_make(0, refresh_attosecs / (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_periodic(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)));
}
/*-------------------------------------------------
cpu_scalebyfcount - scale by time between
refresh timers
-------------------------------------------------*/
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;
}
/*-------------------------------------------------
cpu_getiloops - return the cheesy VBLANK
interrupt counter (deprecated)
-------------------------------------------------*/
int cpu_getiloops(void)
{
VERIFY_ACTIVECPU(cpu_getiloops);
return cpu[activecpu].iloops;
}
/*-------------------------------------------------
cpu_getvblank - return the cheesy VBLANK
state (deprecated)
-------------------------------------------------*/
int cpu_getvblank(void)
{
return vblank;
}
/*-------------------------------------------------
cpu_getcurrentframe - return the current
frame count (deprecated)
-------------------------------------------------*/
int cpu_getcurrentframe(void)
{
return current_frame;
}
/***************************************************************************
INTERNAL TIMING
***************************************************************************/
/*-------------------------------------------------
cpu_vblankreset - hook for updating things on
cheesy fake VBLANK (once per frame)
-------------------------------------------------*/
static void cpu_vblankreset(running_machine *machine)
{
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 (machine->config->watchdog_vblank_count != 0 && --watchdog_counter == 0)
watchdog_callback(machine, NULL, 0);
/* reset the cycle counters */
for (cpunum = 0; cpunum < cpu_gettotalcpu(); cpunum++)
{
if (!(cpu[cpunum].suspend & SUSPEND_REASON_DISABLE))
cpu[cpunum].iloops = machine->config->cpu[cpunum].vblank_interrupts_per_frame - 1;
else
cpu[cpunum].iloops = -1;
}
}
/*-------------------------------------------------
cpu_firstvblankcallback - timer callback for
the first fake VBLANK
-------------------------------------------------*/
static TIMER_CALLBACK( cpu_firstvblankcallback )
{
/* now that we're synced up, pulse from here on out */
timer_adjust_periodic(vblank_timer, vblank_period, param, vblank_period);
/* but we need to call the standard routine as well */
cpu_vblankcallback(machine, NULL, param);
}
/*-------------------------------------------------
cpu_vblankcallback - timer callback for
subsequent fake VBLANKs
-------------------------------------------------*/
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->config->cpu[cpunum].vblank_interrupt && !cpunum_is_suspended(cpunum, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE))
{
cpuintrf_push_context(cpunum);
(*machine->config->cpu[cpunum].vblank_interrupt)(machine, cpunum);
cpuintrf_pop_context();
}
/* update the counters */
cpu[cpunum].iloops--;
}
/* reset the countdown and timer */
cpu[cpunum].vblankint_countdown = cpu[cpunum].vblankint_multiplier;
timer_adjust_oneshot(cpu[cpunum].vblankint_timer, attotime_never, 0);
}
}
/* else reset the VBLANK timer if this is going to be a real VBLANK */
else if (vblank_countdown == 1)
timer_adjust_oneshot(cpu[cpunum].vblankint_timer, attotime_never, 0);
}
/* is it a real VBLANK? */
if (!--vblank_countdown)
{
/* do we update the screen now? */
if (!(machine->config->video_attributes & VIDEO_UPDATE_AFTER_VBLANK))
video_frame_update(machine, FALSE);
/* Set the timer to update the screen */
timer_adjust_oneshot(update_timer, attotime_make(0, machine->screen[0].vblank), 0);
/* reset the globals */
cpu_vblankreset(machine);
/* reset the counter */
vblank_countdown = vblank_multiplier;
#ifdef ENABLE_DEBUGGER
/* notify the debugger */
debug_vblank_hook();
#endif
}
}
/*-------------------------------------------------
cpu_updatecallback - timer callback for
the fake end of VBLANK
-------------------------------------------------*/
static TIMER_CALLBACK( cpu_updatecallback )
{
/* update the screen if we didn't before */
if (machine->config->video_attributes & VIDEO_UPDATE_AFTER_VBLANK)
video_frame_update(machine, FALSE);
vblank = 0;
/* update IPT_VBLANK input ports */
input_port_vblank_end();
/* track total frames */
current_frame++;
/* reset the refresh timer */
timer_adjust_oneshot(refresh_timer, attotime_never, 0);
}
/*-------------------------------------------------
cpu_timedintcallback - timer callback for
timed interrupts
-------------------------------------------------*/
static TIMER_CALLBACK( cpu_timedintcallback )
{
/* bail if there is no routine */
if (machine->config->cpu[param].timed_interrupt != NULL && !cpunum_is_suspended(param, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE))
{
cpuintrf_push_context(param);
(*machine->config->cpu[param].timed_interrupt)(machine, param);
cpuintrf_pop_context();
}
}
/*-------------------------------------------------
cpu_timeslicecallback - timer callback for
timeslicing
-------------------------------------------------*/
static TIMER_CALLBACK( cpu_timeslicecallback )
{
cpu_trigger(machine, TRIGGER_TIMESLICE);
}
/*-------------------------------------------------
end_interleave_boost - timer callback to end
temporary interleave boost
-------------------------------------------------*/
static TIMER_CALLBACK( end_interleave_boost )
{
timer_adjust_oneshot(interleave_boost_timer, attotime_never, 0);
LOG(("end_interleave_boost\n"));
}
/*-------------------------------------------------
compute_perfect_interleave - compute the
"perfect" interleave interval
-------------------------------------------------*/
static void compute_perfect_interleave(running_machine *machine)
{
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->config->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, 9), ATTOSECONDS_TO_DOUBLE(smallest)));
}
/*-------------------------------------------------
cpu_inittimers - set up all the core timers
-------------------------------------------------*/
static void cpu_inittimers(running_machine *machine)
{
int numscreens = video_screen_count(machine->config);
attoseconds_t refresh_attosecs;
attotime first_time;
int cpunum, max, ipf;
/* allocate a timer for the watchdog */
watchdog_timer = timer_alloc(watchdog_callback, NULL);
/* allocate a dummy timer at the minimum frequency to break things up */
ipf = machine->config->cpu_slices_per_frame;
if (ipf <= 0)
ipf = 1;
refresh_attosecs = (numscreens == 0) ? HZ_TO_ATTOSECONDS(60) : machine->screen[0].refresh;
timeslice_period = attotime_make(0, refresh_attosecs / ipf);
timeslice_timer = timer_alloc(cpu_timeslicecallback, NULL);
timer_adjust_periodic(timeslice_timer, timeslice_period, 0, timeslice_period);
/* allocate timers to handle interleave boosts */
interleave_boost_timer = timer_alloc(NULL, NULL);
interleave_boost_timer_end = timer_alloc(end_interleave_boost, NULL);
/*
* 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->config->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->config->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->config->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, refresh_attosecs / 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, NULL);
/*
* 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->config->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, NULL);
/* see if we need to allocate a CPU timer */
if (machine->config->cpu[cpunum].timed_interrupt_period != 0)
{
cpu[cpunum].timedint_period = attotime_make(0, machine->config->cpu[cpunum].timed_interrupt_period);
cpu[cpunum].timedint_timer = timer_alloc(cpu_timedintcallback, NULL);
timer_adjust_periodic(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, NULL, -1);
first_time = attotime_add(first_time, vblank_period);
}
timer_set(first_time, NULL, 0, cpu_firstvblankcallback);
/* reset the refresh timer to get ourself back in sync */
timer_adjust_oneshot(refresh_timer, attotime_never, 0);
}