mirror of
https://github.com/holub/mame
synced 2025-10-05 16:50:57 +03:00

The attached patch adjusts most conditional logging in MAME to use the idiom "do { if (VERBOSE) logerror x; } while (0)". This has the benefit that the compiler checks the syntax of the logging even in the case it will be eliminated, and in fact a number of cases here needed adjustments to compile because of this.
1486 lines
37 KiB
C
1486 lines
37 KiB
C
/***************************************************************************
|
|
|
|
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
|
|
|
|
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
|
|
|
|
|
|
|
|
/*************************************
|
|
*
|
|
* 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, NULL);
|
|
refresh_timer = timer_alloc(NULL, 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, NULL);
|
|
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, NULL, 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;
|
|
}
|
|
|
|
|
|
|
|
/*************************************
|
|
*
|
|
* Burn/yield CPU cycles until an
|
|
* interrupt
|
|
*
|
|
*************************************/
|
|
|
|
void cpu_spinuntil_int(void)
|
|
{
|
|
VERIFY_EXECUTINGCPU(cpu_spinuntil_int);
|
|
cpu_spinuntil_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)
|
|
{
|
|
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_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;
|
|
}
|
|
|
|
|
|
|
|
#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, NULL, 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(FALSE);
|
|
|
|
/* 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(FALSE);
|
|
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, 9), 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, NULL);
|
|
timer_adjust(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->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, 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->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, 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, NULL);
|
|
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, 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(refresh_timer, attotime_never, 0, attotime_never);
|
|
}
|
|
|