/*************************************************************************** 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 "profiler.h" #include "eminline.h" #include "debugger.h" /*************************************************************************** DEBUGGING ***************************************************************************/ #define VERBOSE 0 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) /*************************************************************************** CONSTANTS ***************************************************************************/ /* internal trigger IDs */ enum { TRIGGER_INT = -2000, TRIGGER_YIELDTIME = -3000, TRIGGER_SUSPENDTIME = -4000 }; /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ /* internal information about the state of inputs */ typedef struct _cpu_input_data cpu_input_data; struct _cpu_input_data { INT32 vector; /* most recently written vector */ INT32 curvector; /* most recently processed vector */ UINT8 curstate; /* most recently processed state */ INT32 queue[MAX_INPUT_EVENTS]; /* queue of pending events */ int qindex; /* index within the queue */ }; /* internal data hanging off of the classtoken */ typedef struct _cpu_class_data cpu_class_data; struct _cpu_class_data { /* execution lists */ const device_config *device; /* pointer back to our device */ cpu_class_data *next; /* pointer to the next CPU to execute, in order */ cpu_execute_func execute; /* execute function pointer */ /* cycle counting and executing */ int profiler; /* profiler tag */ int * icount; /* pointer to the icount */ int cycles_running; /* number of cycles we are executing */ int cycles_stolen; /* number of cycles we artificially stole */ /* input states and IRQ callbacks */ cpu_irq_callback driver_irq; /* driver-specific IRQ callback */ cpu_input_data input[MAX_INPUT_LINES]; /* data about inputs */ /* suspend states */ 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 inttrigger; /* interrupt trigger index */ /* clock and timing information */ 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 divisor; /* 32-bit attoseconds_per_cycle divisor */ UINT8 divshift; /* right shift amount to fit the divisor into 32 bits */ emu_timer * timedint_timer; /* reference to this CPU's periodic interrupt timer */ UINT32 cycles_per_second; /* cycles per second, adjusted for multipliers */ attoseconds_t attoseconds_per_cycle; /* attoseconds per adjusted clock cycle */ /* internal state reflection */ const cpu_state_table *state; /* pointer to the base table */ const cpu_state_entry *regstate[MAX_REGS];/* pointer to the state entry for each register */ /* these below are hacks to support multiple interrupts per frame */ INT32 iloops; /* number of interrupts remaining this frame */ emu_timer * partial_frame_timer; /* the timer that triggers partial frame interrupts */ attotime partial_frame_period; /* the length of one partial frame for interrupt purposes */ }; /* global data stored in the machine */ /* In mame.h: typedef struct _cpuexec_private cpuexec_private; */ struct _cpuexec_private { const device_config *executingcpu; /* pointer to the currently executing CPU */ cpu_class_data *executelist; /* execution list; suspended CPUs are at the back */ char statebuf[256]; /* string buffer containing state description */ }; /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ static void update_clock_information(const device_config *device); static void compute_perfect_interleave(running_machine *machine); static void on_vblank(const device_config *device, void *param, int vblank_state); static TIMER_CALLBACK( trigger_partial_frame_interrupt ); static TIMER_CALLBACK( trigger_periodic_interrupt ); static TIMER_CALLBACK( triggertime_callback ); static TIMER_CALLBACK( empty_event_queue ); static IRQ_CALLBACK( standard_irq_callback ); static void register_save_states(const device_config *device); static void rebuild_execute_list(running_machine *machine); static UINT64 get_register_value(const device_config *device, void *baseptr, const cpu_state_entry *entry); static void set_register_value(const device_config *device, void *baseptr, const cpu_state_entry *entry, UINT64 value); static void get_register_string_value(const device_config *device, void *baseptr, const cpu_state_entry *entry, char *dest); #ifdef UNUSED_FUNCTION static int get_register_string_max_width(const device_config *device, void *baseptr, const cpu_state_entry *entry); #endif /*************************************************************************** MACROS ***************************************************************************/ /* these are macros to ensure inlining in cpuexec_timeslice */ #define ATTOTIME_LT(a,b) ((a).seconds < (b).seconds || ((a).seconds == (b).seconds && (a).attoseconds < (b).attoseconds)) #define ATTOTIME_NORMALIZE(a) do { if ((a).attoseconds >= ATTOSECONDS_PER_SECOND) { (a).seconds++; (a).attoseconds -= ATTOSECONDS_PER_SECOND; } } while (0) /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ /*------------------------------------------------- get_class_data - return a pointer to the class data -------------------------------------------------*/ INLINE cpu_class_data *get_class_data(const device_config *device) { assert(device != NULL); assert(device->devclass == DEVICE_CLASS_CPU_CHIP); assert(device->token != NULL); return (cpu_class_data *)cpu_get_class_header(device) - 1; } /*------------------------------------------------- get_minimum_quantum - return the minimum quantum required for a given CPU device -------------------------------------------------*/ INLINE attoseconds_t get_minimum_quantum(const device_config *device) { attoseconds_t basetick = 0; /* fetch the base clock from the classdata if present */ if (device->token != NULL) basetick = get_class_data(device)->attoseconds_per_cycle; /* otherwise compute it from the raw data */ if (basetick == 0) { UINT32 baseclock = (UINT64)device->clock * cpu_get_clock_multiplier(device) / cpu_get_clock_divider(device); basetick = HZ_TO_ATTOSECONDS(baseclock); } /* apply the minimum cycle count */ return basetick * cpu_get_min_cycles(device); } /*------------------------------------------------- suspend_until_trigger - suspend execution until the given trigger fires -------------------------------------------------*/ INLINE void suspend_until_trigger(const device_config *device, int trigger, int eatcycles) { cpu_class_data *classdata = get_class_data(device); /* suspend the CPU immediately if it's not already */ cpu_suspend(device, SUSPEND_REASON_TRIGGER, eatcycles); /* set the trigger */ classdata->trigger = trigger; } /*************************************************************************** CORE CPU EXECUTION ***************************************************************************/ /*------------------------------------------------- cpuexec_init - initialize internal states of all CPUs -------------------------------------------------*/ void cpuexec_init(running_machine *machine) { attotime min_quantum; /* allocate global state */ machine->cpuexec_data = auto_alloc_clear(machine, cpuexec_private); /* set the core scheduling quantum */ min_quantum = machine->config->minimum_quantum; if (attotime_compare(min_quantum, attotime_zero) == 0) min_quantum = ATTOTIME_IN_HZ(60); if (machine->config->perfect_cpu_quantum != NULL) { const device_config *cpu = cputag_get_cpu(machine, machine->config->perfect_cpu_quantum); attotime cpu_quantum; if (cpu == NULL) fatalerror("CPU '%s' specified for perfect interleave is not present!", machine->config->perfect_cpu_quantum); cpu_quantum = attotime_make(0, get_minimum_quantum(cpu)); min_quantum = attotime_min(cpu_quantum, min_quantum); } assert(min_quantum.seconds == 0); timer_add_scheduling_quantum(machine, min_quantum.attoseconds, attotime_never); } /*------------------------------------------------- cpuexec_timeslice - execute all CPUs for a single timeslice -------------------------------------------------*/ void cpuexec_timeslice(running_machine *machine) { int call_debugger = ((machine->debug_flags & DEBUG_FLAG_ENABLED) != 0); timer_execution_state *timerexec = timer_get_execution_state(machine); cpuexec_private *global = machine->cpuexec_data; int ran; /* build the execution list if we don't have one yet */ if (global->executelist == NULL) rebuild_execute_list(machine); /* loop until we hit the next timer */ while (ATTOTIME_LT(timerexec->basetime, timerexec->nextfire)) { cpu_class_data *classdata; UINT32 suspendchanged; attotime target; /* by default, assume our target is the end of the next quantum */ target.seconds = timerexec->basetime.seconds; target.attoseconds = timerexec->basetime.attoseconds + timerexec->curquantum; ATTOTIME_NORMALIZE(target); /* however, if the next timer is going to fire before then, override */ assert(attotime_sub(timerexec->nextfire, target).seconds <= 0); if (ATTOTIME_LT(timerexec->nextfire, target)) target = timerexec->nextfire; LOG(("------------------\n")); LOG(("cpu_timeslice: target = %s\n", attotime_string(target, 9))); /* apply pending suspension changes */ suspendchanged = 0; for (classdata = global->executelist; classdata != NULL; classdata = classdata->next) { suspendchanged |= (classdata->suspend ^ classdata->nextsuspend); classdata->suspend = classdata->nextsuspend; classdata->nextsuspend &= ~SUSPEND_REASON_TIMESLICE; classdata->eatcycles = classdata->nexteatcycles; } /* recompute the execute list if any CPUs changed their suspension state */ if (suspendchanged != 0) rebuild_execute_list(machine); /* loop over non-suspended CPUs */ for (classdata = global->executelist; classdata != NULL; classdata = classdata->next) { /* only process if our target is later than the CPU's current time (coarse check) */ if (target.seconds >= classdata->localtime.seconds) { attoseconds_t delta, actualdelta; /* compute how many attoseconds to execute this CPU */ delta = target.attoseconds - classdata->localtime.attoseconds; if (delta < 0 && target.seconds > classdata->localtime.seconds) delta += ATTOSECONDS_PER_SECOND; assert(delta == attotime_to_attoseconds(attotime_sub(target, classdata->localtime))); /* if we have enough for at least 1 cycle, do the math */ if (delta >= classdata->attoseconds_per_cycle) { /* compute how many cycles we want to execute */ ran = classdata->cycles_running = divu_64x32((UINT64)delta >> classdata->divshift, classdata->divisor); LOG((" cpu '%s': %d cycles\n", classdata->device->tag, classdata->cycles_running)); /* if we're not suspended, actually execute */ if (classdata->suspend == 0) { profiler_mark_start(classdata->profiler); /* note that this global variable cycles_stolen can be modified */ /* via the call to cpu_execute */ classdata->cycles_stolen = 0; global->executingcpu = classdata->device; *classdata->icount = classdata->cycles_running; if (!call_debugger) ran = (*classdata->execute)(classdata->device, classdata->cycles_running); else { debugger_start_cpu_hook(classdata->device, target); ran = (*classdata->execute)(classdata->device, classdata->cycles_running); debugger_stop_cpu_hook(classdata->device); } /* adjust for any cycles we took back */ assert(ran >= classdata->cycles_stolen); ran -= classdata->cycles_stolen; profiler_mark_end(); } /* account for these cycles */ classdata->totalcycles += ran; /* update the local time for this CPU */ actualdelta = classdata->attoseconds_per_cycle * ran; classdata->localtime.attoseconds += actualdelta; ATTOTIME_NORMALIZE(classdata->localtime); LOG((" %d ran, %d total, time = %s\n", ran, (INT32)classdata->totalcycles, attotime_string(classdata->localtime, 9))); /* if the new local CPU time is less than our target, move the target up */ if (ATTOTIME_LT(classdata->localtime, target)) { assert(attotime_compare(classdata->localtime, target) < 0); target = classdata->localtime; /* however, if this puts us before the base, clamp to the base as a minimum */ if (ATTOTIME_LT(target, timerexec->basetime)) { assert(attotime_compare(target, timerexec->basetime) < 0); target = timerexec->basetime; } LOG((" (new target)\n")); } } } } global->executingcpu = NULL; /* update the base time */ timerexec->basetime = target; } /* execute timers */ timer_execute_timers(machine); } /*------------------------------------------------- cpuexec_boost_interleave - temporarily boosts the interleave factor -------------------------------------------------*/ void cpuexec_boost_interleave(running_machine *machine, attotime timeslice_time, attotime boost_duration) { /* ignore timeslices > 1 second */ if (timeslice_time.seconds > 0) return; timer_add_scheduling_quantum(machine, timeslice_time.attoseconds, boost_duration); } /*************************************************************************** GLOBAL HELPERS ***************************************************************************/ /*------------------------------------------------- cpuexec_abort_timeslice - abort execution for the current timeslice -------------------------------------------------*/ void cpuexec_abort_timeslice(running_machine *machine) { const device_config *executingcpu = machine->cpuexec_data->executingcpu; if (executingcpu != NULL) cpu_abort_timeslice(executingcpu); } /*------------------------------------------------- cpuexec_describe_context - return a string describing which CPUs are currently executing and their PC -------------------------------------------------*/ const char *cpuexec_describe_context(running_machine *machine) { cpuexec_private *global = machine->cpuexec_data; const device_config *executingcpu = global->executingcpu; /* if we have an executing CPU, output data */ if (executingcpu != NULL) { const address_space *space = cpu_get_address_space(executingcpu, ADDRESS_SPACE_PROGRAM); sprintf(global->statebuf, "'%s' (%s)", executingcpu->tag, core_i64_hex_format(cpu_get_pc(executingcpu), space->logaddrchars)); } else strcpy(global->statebuf, "(no context)"); return global->statebuf; } /*************************************************************************** CPU DEVICE INTERFACE ***************************************************************************/ /*------------------------------------------------- device_start_cpu - device start callback -------------------------------------------------*/ static DEVICE_START( cpu ) { int index = cpu_get_index(device); cpu_class_header *header; cpu_class_data *classdata; const cpu_config *config; cpu_init_func init; int num_regs; int line; /* validate some basic stuff */ assert(device != NULL); assert(device->inline_config != NULL); assert(device->machine != NULL); assert(device->machine->config != NULL); /* get pointers to our data */ config = (const cpu_config *)device->inline_config; header = cpu_get_class_header(device); classdata = get_class_data(device); /* build the header */ header->debug = NULL; header->set_info = (cpu_set_info_func)device_get_info_fct(device, CPUINFO_FCT_SET_INFO); /* fill in the input states and IRQ callback information */ for (line = 0; line < ARRAY_LENGTH(classdata->input); line++) { cpu_input_data *inputline = &classdata->input[line]; /* vector and curvector are initialized later */ inputline->curstate = CLEAR_LINE; inputline->qindex = 0; } /* fill in the suspend states */ classdata->device = device; classdata->execute = (cpu_execute_func)device_get_info_fct(device, CPUINFO_FCT_EXECUTE); classdata->profiler = index + PROFILER_CPU_FIRST; classdata->suspend = SUSPEND_REASON_RESET; classdata->inttrigger = index + TRIGGER_INT; /* fill in the clock and timing information */ classdata->clock = (UINT64)device->clock * cpu_get_clock_multiplier(device) / cpu_get_clock_divider(device); classdata->clockscale = 1.0; /* allocate timers if we need them */ if (config->vblank_interrupts_per_frame > 1) classdata->partial_frame_timer = timer_alloc(device->machine, trigger_partial_frame_interrupt, (void *)device); if (config->timed_interrupt_period != 0) classdata->timedint_timer = timer_alloc(device->machine, trigger_periodic_interrupt, (void *)device); /* initialize this CPU */ num_regs = state_save_get_reg_count(device->machine); init = (cpu_init_func)device_get_info_fct(device, CPUINFO_FCT_INIT); (*init)(device, standard_irq_callback); num_regs = state_save_get_reg_count(device->machine) - num_regs; /* fetch post-initialization data */ classdata->icount = cpu_get_icount_ptr(device); for (line = 0; line < ARRAY_LENGTH(classdata->input); line++) { cpu_input_data *inputline = &classdata->input[line]; inputline->vector = cpu_get_default_irq_vector(device); inputline->curvector = inputline->vector; } update_clock_information(device); /* fetch information about the CPU states */ classdata->state = cpu_get_state_table(device); if (classdata->state != NULL) { int stateindex; /* loop over all states specified, and work with any that apply */ for (stateindex = 0; stateindex < classdata->state->entrycount; stateindex++) { const cpu_state_entry *entry = &classdata->state->entrylist[stateindex]; if (entry->validmask == 0 || (entry->validmask & classdata->state->subtypemask) != 0) { assert(entry->index < MAX_REGS); assert(classdata->regstate[entry->index] == NULL); classdata->regstate[entry->index] = entry; } } } /* if no state registered for saving, we can't save */ if (num_regs == 0) { logerror("CPU '%s' did not register any state to save!\n", device->tag); if (device->machine->gamedrv->flags & GAME_SUPPORTS_SAVE) fatalerror("CPU '%s' did not register any state to save!", device->tag); } /* register some internal states as well */ register_save_states(device); } /*------------------------------------------------- device_reset_cpu - device reset callback -------------------------------------------------*/ static DEVICE_RESET( cpu ) { cpu_class_data *classdata = get_class_data(device); const cpu_config *config = (const cpu_config *)device->inline_config; cpu_reset_func reset; int line; /* enable all CPUs (except for disabled CPUs) */ if (!(config->flags & CPU_DISABLE)) cpu_resume(device, SUSPEND_ANY_REASON); else cpu_suspend(device, SUSPEND_REASON_DISABLE, 1); /* reset the total number of cycles */ classdata->totalcycles = 0; /* then reset the CPU directly */ reset = (cpu_reset_func)device_get_info_fct(device, CPUINFO_FCT_RESET); if (reset != NULL) (*reset)(device); /* reset the interrupt vectors and queues */ for (line = 0; line < ARRAY_LENGTH(classdata->input); line++) { cpu_input_data *inputline = &classdata->input[line]; inputline->vector = cpu_get_default_irq_vector(device); inputline->qindex = 0; } /* reconfingure VBLANK interrupts */ if (config->vblank_interrupts_per_frame > 0 || config->vblank_interrupt_screen != NULL) { const device_config *screen; /* get the screen that will trigger the VBLANK */ /* new style - use screen tag directly */ if (config->vblank_interrupt_screen != NULL) screen = devtag_get_device(device->machine, config->vblank_interrupt_screen); /* old style 'hack' setup - use screen #0 */ else screen = video_screen_first(device->machine->config); assert(screen != NULL); video_screen_register_vblank_callback(screen, on_vblank, NULL); } /* reconfigure periodic interrupts */ if (config->timed_interrupt_period != 0) { attotime timedint_period = UINT64_ATTOTIME_TO_ATTOTIME(config->timed_interrupt_period); assert(classdata->timedint_timer != NULL); timer_adjust_periodic(classdata->timedint_timer, timedint_period, 0, timedint_period); } } /*------------------------------------------------- device_stop_cpu - device stop callback -------------------------------------------------*/ static DEVICE_STOP( cpu ) { cpu_exit_func exit; /* call the CPU's exit function if present */ exit = (cpu_exit_func)device_get_info_fct(device, CPUINFO_FCT_EXIT); if (exit != NULL) (*exit)(device); } /*------------------------------------------------- cpu_set_info - device set info callback -------------------------------------------------*/ void cpu_set_info(const device_config *device, UINT32 state, UINT64 value) { cpu_class_header *header = cpu_get_class_header(device); cpu_set_info_func set_info; cpuinfo cinfo; /* if we are live and have a header, save ourself a call */ if (header != NULL) set_info = header->set_info; else set_info = (cpu_set_info_func)device_get_info_fct(device, CPUINFO_FCT_SET_INFO); switch (state) { /* no parameters to set */ default: /* if we have a state pointer, we can handle some stuff for free */ if (device->token != NULL) { const cpu_class_data *classdata = get_class_data(device); if (classdata->state != NULL) { if (state >= CPUINFO_INT_REGISTER && state <= CPUINFO_INT_REGISTER_LAST) { set_register_value(device, classdata->state->baseptr, classdata->regstate[state - CPUINFO_INT_REGISTER], value); return; } } } /* integer data */ assert(state >= DEVINFO_INT_FIRST && state <= DEVINFO_INT_LAST); if (state >= DEVINFO_INT_FIRST && state <= DEVINFO_INT_LAST) { cinfo.i = value; (*set_info)(device, state, &cinfo); } break; } } /*------------------------------------------------- cpu_get_info - device get info callback -------------------------------------------------*/ DEVICE_GET_INFO( cpu ) { const cpu_config *config = (device != NULL) ? (const cpu_config *)device->inline_config : NULL; cpuinfo cinfo = { 0 }; switch (state) { /* --- the following bits of info are returned as 64-bit signed integers --- */ case DEVINFO_INT_TOKEN_BYTES: cinfo.i = 0; (*config->type)(device, CPUINFO_INT_CONTEXT_SIZE, &cinfo); info->i = cinfo.i + sizeof(cpu_class_data) + sizeof(cpu_class_header); break; case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(cpu_config); break; case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_CPU_CHIP; break; /* --- the following bits of info are returned as pointers to data or functions --- */ case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(cpu); break; case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME(cpu); break; case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(cpu); break; default: /* if we don't have a device pointer, ignore everything else */ if (device == NULL) break; /* if we have a state pointer, we can handle some stuff for free */ if (device->token != NULL) { const cpu_class_data *classdata = get_class_data(device); if (classdata->state != NULL) { if (state >= CPUINFO_INT_REGISTER && state <= CPUINFO_INT_REGISTER_LAST) { info->i = get_register_value(device, classdata->state->baseptr, classdata->regstate[state - CPUINFO_INT_REGISTER]); return; } else if (state >= CPUINFO_STR_REGISTER && state <= CPUINFO_STR_REGISTER_LAST) { cinfo.s = info->s; get_register_string_value(device, classdata->state->baseptr, classdata->regstate[state - CPUINFO_STR_REGISTER], cinfo.s); info->s = cinfo.s; return; } } } /* integer data */ if (state >= DEVINFO_INT_FIRST && state <= DEVINFO_INT_LAST) { cinfo.i = info->i; (*config->type)(device, state, &cinfo); info->i = cinfo.i; } /* pointer data */ else if ((state >= DEVINFO_PTR_FIRST && state <= DEVINFO_PTR_LAST) || (state >= DEVINFO_FCT_FIRST && state <= DEVINFO_FCT_LAST) || (state >= DEVINFO_STR_FIRST && state <= DEVINFO_STR_LAST)) { cinfo.p = info->p; (*config->type)(device, state, &cinfo); info->p = cinfo.p; } break; } } /*************************************************************************** CPU SCHEDULING ***************************************************************************/ /*------------------------------------------------- cpu_suspend - set a suspend reason for the given CPU -------------------------------------------------*/ void cpu_suspend(const device_config *device, int reason, int eatcycles) { cpu_class_data *classdata = get_class_data(device); /* set the suspend reason and eat cycles flag */ classdata->nextsuspend |= reason; classdata->nexteatcycles = eatcycles; /* if we're active, synchronize */ cpu_abort_timeslice(device); } /*------------------------------------------------- cpu_resume - clear a suspend reason for the given CPU -------------------------------------------------*/ void cpu_resume(const device_config *device, int reason) { cpu_class_data *classdata = get_class_data(device); /* clear the suspend reason and eat cycles flag */ classdata->nextsuspend &= ~reason; /* if we're active, synchronize */ cpu_abort_timeslice(device); } /*------------------------------------------------- cpu_is_executing - return TRUE if the given CPU is within its execute function -------------------------------------------------*/ int cpu_is_executing(const device_config *device) { return (device == device->machine->cpuexec_data->executingcpu); } /*------------------------------------------------- cpu_is_suspended - returns TRUE if the given CPU is suspended for any of the given reasons -------------------------------------------------*/ int cpu_is_suspended(const device_config *device, int reason) { cpu_class_data *classdata = get_class_data(device); /* return true if the given reason is indicated */ return ((classdata->nextsuspend & reason) != 0); } /*************************************************************************** CPU CLOCK MANAGEMENT ***************************************************************************/ /*------------------------------------------------- cpu_get_clock - gets the given CPU's clock speed -------------------------------------------------*/ int cpu_get_clock(const device_config *device) { cpu_class_data *classdata; /* if we haven't been started yet, compute it manually */ if (device->token == NULL) return (UINT64)device->clock * cpu_get_clock_multiplier(device) / cpu_get_clock_divider(device); /* return the current clock value */ classdata = get_class_data(device); return classdata->clock; } /*------------------------------------------------- cpu_set_clock - sets the given CPU's clock speed -------------------------------------------------*/ void cpu_set_clock(const device_config *device, int clock) { cpu_class_data *classdata = get_class_data(device); /* set the clock and update the information */ classdata->clock = clock; update_clock_information(device); } /*------------------------------------------------- cpu_get_clockscale - returns the current scaling factor for a CPU's clock speed -------------------------------------------------*/ double cpu_get_clockscale(const device_config *device) { cpu_class_data *classdata = get_class_data(device); /* return the current clock scale factor */ return classdata->clockscale; } /*------------------------------------------------- cpu_set_clockscale - sets the current scaling factor for a CPU's clock speed -------------------------------------------------*/ void cpu_set_clockscale(const device_config *device, double clockscale) { cpu_class_data *classdata = get_class_data(device); /* set the scale factor and update the information */ classdata->clockscale = clockscale; update_clock_information(device); } /*------------------------------------------------- cpu_clocks_to_attotime - converts a number of clock ticks to an attotime -------------------------------------------------*/ attotime cpu_clocks_to_attotime(const device_config *device, UINT64 clocks) { cpu_class_data *classdata = get_class_data(device); if (clocks < classdata->cycles_per_second) return attotime_make(0, clocks * classdata->attoseconds_per_cycle); else { UINT32 remainder; UINT32 quotient = divu_64x32_rem(clocks, classdata->cycles_per_second, &remainder); return attotime_make(quotient, (UINT64)remainder * (UINT64)classdata->attoseconds_per_cycle); } } /*------------------------------------------------- cpu_attotime_to_clocks - converts a duration as attotime to CPU clock ticks -------------------------------------------------*/ UINT64 cpu_attotime_to_clocks(const device_config *device, attotime duration) { cpu_class_data *classdata = get_class_data(device); return mulu_32x32(duration.seconds, classdata->cycles_per_second) + (UINT64)duration.attoseconds / (UINT64)classdata->attoseconds_per_cycle; } /*************************************************************************** CPU TIMING ***************************************************************************/ /*------------------------------------------------- cpu_get_local_time - returns the current local time for a CPU -------------------------------------------------*/ attotime cpu_get_local_time(const device_config *device) { cpu_class_data *classdata = get_class_data(device); attotime result; /* if we're active, add in the time from the current slice */ result = classdata->localtime; if (device == device->machine->cpuexec_data->executingcpu) { int cycles = classdata->cycles_running - *classdata->icount; result = attotime_add(result, cpu_clocks_to_attotime(device, cycles)); } return result; } /*------------------------------------------------- cpuexec_override_local_time - overrides the given time with the executing CPU's local time, if present (this function is private to timer.c) -------------------------------------------------*/ attotime cpuexec_override_local_time(running_machine *machine, attotime default_time) { if (machine->cpuexec_data != NULL && machine->cpuexec_data->executingcpu != NULL) return cpu_get_local_time(machine->cpuexec_data->executingcpu); return default_time; } /*------------------------------------------------- cpu_get_total_cycles - return the total number of CPU cycles executed on the active CPU -------------------------------------------------*/ UINT64 cpu_get_total_cycles(const device_config *device) { cpu_class_data *classdata = get_class_data(device); if (device == device->machine->cpuexec_data->executingcpu) return classdata->totalcycles + classdata->cycles_running - *classdata->icount; else return classdata->totalcycles; } /*------------------------------------------------- cpu_eat_cycles - safely eats cycles so we don't cross a timeslice boundary -------------------------------------------------*/ void cpu_eat_cycles(const device_config *device, int cycles) { cpu_class_data *classdata = get_class_data(device); /* ignore if not the executing CPU */ if (device != device->machine->cpuexec_data->executingcpu) return; if (cycles > *classdata->icount) cycles = *classdata->icount; *classdata->icount -= cycles; } /*------------------------------------------------- cpu_adjust_icount - apply a +/- to the current icount -------------------------------------------------*/ void cpu_adjust_icount(const device_config *device, int delta) { cpu_class_data *classdata = get_class_data(device); /* ignore if not the executing CPU */ if (device != device->machine->cpuexec_data->executingcpu) return; *classdata->icount += delta; } /*------------------------------------------------- cpu_abort_timeslice - abort execution for the current timeslice, allowing other CPUs to run before we run again -------------------------------------------------*/ void cpu_abort_timeslice(const device_config *device) { cpu_class_data *classdata = get_class_data(device); int delta; /* ignore if not the executing CPU */ if (device != device->machine->cpuexec_data->executingcpu) return; /* swallow the remaining cycles */ if (classdata->icount != NULL) { delta = *classdata->icount; classdata->cycles_stolen += delta; classdata->cycles_running -= delta; *classdata->icount -= delta; } } /*************************************************************************** SYNCHRONIZATION HELPERS ***************************************************************************/ /*------------------------------------------------- cpu_yield - yield the given CPU until the end of the current timeslice -------------------------------------------------*/ void cpu_yield(const device_config *device) { /* suspend against the timeslice */ cpu_suspend(device, SUSPEND_REASON_TIMESLICE, FALSE); } /*------------------------------------------------- cpu_spin - burn CPU cycles until our timeslice is up -------------------------------------------------*/ void cpu_spin(const device_config *device) { /* suspend against the timeslice */ cpu_suspend(device, SUSPEND_REASON_TIMESLICE, TRUE); } /*------------------------------------------------- cpu_spinuntil_trigger - burn specified CPU cycles until a timer trigger -------------------------------------------------*/ void cpu_spinuntil_trigger(const device_config *device, int trigger) { /* suspend until the given trigger fires */ suspend_until_trigger(device, trigger, TRUE); } /*------------------------------------------------- cpu_spinuntil_int - burn CPU cycles until the next interrupt -------------------------------------------------*/ void cpu_spinuntil_int(const device_config *device) { cpu_class_data *classdata = get_class_data(device); /* suspend until the given trigger fires */ suspend_until_trigger(device, classdata->inttrigger, TRUE); } /*------------------------------------------------- cpu_spinuntil_time - burn CPU cycles for a specific period of time -------------------------------------------------*/ void cpu_spinuntil_time(const device_config *device, attotime duration) { static int timetrig = 0; /* suspend until the given trigger fires */ suspend_until_trigger(device, TRIGGER_SUSPENDTIME + timetrig, TRUE); /* then set a timer for it */ cpuexec_triggertime(device->machine, TRIGGER_SUSPENDTIME + timetrig, duration); timetrig = (timetrig + 1) % 256; } /*************************************************************************** TRIGGERS ***************************************************************************/ /*------------------------------------------------- cpuexec_trigger - generate a trigger now -------------------------------------------------*/ void cpuexec_trigger(running_machine *machine, int trigger) { const device_config *cpu; /* look for suspended CPUs waiting for this trigger and unsuspend them */ for (cpu = machine->firstcpu; cpu != NULL; cpu = cpu_next(cpu)) { cpu_class_data *classdata = get_class_data(cpu); /* if we're executing, for an immediate abort */ cpu_abort_timeslice(cpu); /* see if this is a matching trigger */ if ((classdata->nextsuspend & SUSPEND_REASON_TRIGGER) != 0 && classdata->trigger == trigger) { cpu_resume(cpu, SUSPEND_REASON_TRIGGER); classdata->trigger = 0; } } } /*------------------------------------------------- cpuexec_triggertime - generate a trigger after a specific period of time -------------------------------------------------*/ void cpuexec_triggertime(running_machine *machine, int trigger, attotime duration) { timer_set(machine, duration, NULL, trigger, triggertime_callback); } /*------------------------------------------------- cpu_triggerint - generate a trigger corresponding to an interrupt on the given CPU -------------------------------------------------*/ void cpu_triggerint(const device_config *device) { cpu_class_data *classdata = get_class_data(device); /* signal this CPU's interrupt trigger */ cpuexec_trigger(device->machine, classdata->inttrigger); } /*************************************************************************** INTERRUPTS ***************************************************************************/ /*------------------------------------------------- cpu_set_input_line - set the logical state (ASSERT_LINE/CLEAR_LINE) of an input line on a CPU -------------------------------------------------*/ void cpu_set_input_line(const device_config *device, int line, int state) { cpu_class_data *classdata = get_class_data(device); int vector = (line >= 0 && line < MAX_INPUT_LINES) ? classdata->input[line].vector : 0xff; cpu_set_input_line_and_vector(device, line, state, vector); } /*------------------------------------------------- cpu_set_input_line_vector - set the vector to be returned during a CPU's interrupt acknowledge cycle -------------------------------------------------*/ void cpu_set_input_line_vector(const device_config *device, int line, int vector) { cpu_class_data *classdata = get_class_data(device); if (line >= 0 && line < MAX_INPUT_LINES) { classdata->input[line].vector = vector; return; } LOG(("cpu_set_input_line_vector CPU '%s' line %d > max input lines\n", device->tag, line)); } /*------------------------------------------------- cpu_set_input_line_and_vector - set the logical state (ASSERT_LINE/CLEAR_LINE) of an input line on a CPU and its associated vector -------------------------------------------------*/ void cpu_set_input_line_and_vector(const device_config *device, int line, int state, int vector) { cpu_class_data *classdata = get_class_data(device); /* catch errors where people use PULSE_LINE for CPUs that don't support it */ if (state == PULSE_LINE && line != INPUT_LINE_NMI && line != INPUT_LINE_RESET) fatalerror("CPU %s: PULSE_LINE can only be used for NMI and RESET lines\n", device->tag); if (line >= 0 && line < MAX_INPUT_LINES) { cpu_input_data *inputline = &classdata->input[line]; INT32 input_event = (state & 0xff) | (vector << 8); int event_index = inputline->qindex++; LOG(("cpu_set_input_line_and_vector('%s',%d,%d,%02x)\n", device->tag, line, state, vector)); /* if we're full of events, flush the queue and log a message */ if (event_index >= ARRAY_LENGTH(inputline->queue)) { inputline->qindex--; empty_event_queue(device->machine, (void *)device, line); event_index = inputline->qindex++; logerror("Exceeded pending input line event queue on CPU '%s'!\n", device->tag); } /* enqueue the event */ if (event_index < ARRAY_LENGTH(inputline->queue)) { inputline->queue[event_index] = input_event; /* if this is the first one, set the timer */ if (event_index == 0) timer_call_after_resynch(device->machine, (void *)device, line, empty_event_queue); } } } /*------------------------------------------------- cpu_set_irq_callback - install a driver- specific callback for IRQ acknowledge -------------------------------------------------*/ void cpu_set_irq_callback(const device_config *device, cpu_irq_callback callback) { cpu_class_data *classdata = get_class_data(device); classdata->driver_irq = callback; } /*************************************************************************** CHEESY FAKE VIDEO TIMING (OBSOLETE) ***************************************************************************/ /*------------------------------------------------- cpu_getiloops - return the cheesy VBLANK interrupt counter (deprecated) -------------------------------------------------*/ int cpu_getiloops(const device_config *device) { cpu_class_data *classdata = get_class_data(device); return classdata->iloops; } /*************************************************************************** INTERNAL FUNCTIONS ***************************************************************************/ /*------------------------------------------------- update_clock_information - recomputes clock information for the specified CPU -------------------------------------------------*/ static void update_clock_information(const device_config *device) { cpu_class_data *classdata = get_class_data(device); INT64 attos; /* recompute cps and spc */ classdata->cycles_per_second = (double)classdata->clock * classdata->clockscale; classdata->attoseconds_per_cycle = HZ_TO_ATTOSECONDS((double)classdata->clock * classdata->clockscale); /* update the CPU's divisor */ attos = classdata->attoseconds_per_cycle; classdata->divshift = 0; while (attos >= (1UL << 31)) { classdata->divshift++; attos >>= 1; } classdata->divisor = attos; /* re-compute the perfect interleave factor */ compute_perfect_interleave(device->machine); } /*------------------------------------------------- compute_perfect_interleave - compute the "perfect" interleave interval -------------------------------------------------*/ static void compute_perfect_interleave(running_machine *machine) { const device_config *firstcpu = machine->firstcpu; if (firstcpu != NULL) { attoseconds_t smallest = get_minimum_quantum(firstcpu); attoseconds_t perfect = ATTOSECONDS_PER_SECOND - 1; const device_config *cpu; /* start with a huge time factor and find the 2nd smallest cycle time */ for (cpu = cpu_next(firstcpu); cpu != NULL; cpu = cpu_next(cpu)) { attoseconds_t curquantum = get_minimum_quantum(cpu); /* find the 2nd smallest cycle interval */ if (curquantum < smallest) { perfect = smallest; smallest = curquantum; } else if (curquantum < perfect) perfect = curquantum; } /* adjust the final value */ timer_set_minimum_quantum(machine, perfect); LOG(("Perfect interleave = %.9f, smallest = %.9f\n", ATTOSECONDS_TO_DOUBLE(perfect), ATTOSECONDS_TO_DOUBLE(smallest))); } } /*------------------------------------------------- on_vblank - calls any external callbacks for this screen -------------------------------------------------*/ static void on_vblank(const device_config *device, void *param, int vblank_state) { /* VBLANK starting */ if (vblank_state) { const device_config *cpu; /* find any CPUs that have this screen as their VBLANK interrupt source */ for (cpu = device->machine->firstcpu; cpu != NULL; cpu = cpu_next(cpu)) { const cpu_config *config = (const cpu_config *)cpu->inline_config; cpu_class_data *classdata = get_class_data(cpu); int cpu_interested; /* start the interrupt counter */ if (!(classdata->suspend & SUSPEND_REASON_DISABLE)) classdata->iloops = 0; else classdata->iloops = -1; /* the hack style VBLANK decleration always uses the first screen */ if (config->vblank_interrupts_per_frame > 1) cpu_interested = TRUE; /* for new style declaration, we need to compare the tags */ else if (config->vblank_interrupt_screen != NULL) cpu_interested = (strcmp(config->vblank_interrupt_screen, device->tag) == 0); /* no VBLANK interrupt, not interested */ else cpu_interested = FALSE; /* if interested, call the interrupt handler */ if (cpu_interested) { if (!(classdata->suspend & (SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE))) (*config->vblank_interrupt)(cpu); /* if we have more than one interrupt per frame, start the timer now to trigger the rest of them */ if (config->vblank_interrupts_per_frame > 1 && !(classdata->suspend & SUSPEND_REASON_DISABLE)) { classdata->partial_frame_period = attotime_div(video_screen_get_frame_period(device->machine->primary_screen), config->vblank_interrupts_per_frame); timer_adjust_oneshot(classdata->partial_frame_timer, classdata->partial_frame_period, 0); } } } } } /*------------------------------------------------- trigger_partial_frame_interrupt - called to trigger a partial frame interrupt -------------------------------------------------*/ static TIMER_CALLBACK( trigger_partial_frame_interrupt ) { const device_config *device = (const device_config *)ptr; const cpu_config *config = (const cpu_config *)device->inline_config; cpu_class_data *classdata = get_class_data(device); if (classdata->iloops == 0) classdata->iloops = config->vblank_interrupts_per_frame; classdata->iloops--; /* call the interrupt handler */ if (!cpu_is_suspended(device, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE)) (*config->vblank_interrupt)(device); /* more? */ if (classdata->iloops > 1) timer_adjust_oneshot(classdata->partial_frame_timer, classdata->partial_frame_period, 0); } /*------------------------------------------------- trigger_periodic_interrupt - timer callback for timed interrupts -------------------------------------------------*/ static TIMER_CALLBACK( trigger_periodic_interrupt ) { const device_config *device = (const device_config *)ptr; const cpu_config *config = (const cpu_config *)device->inline_config; /* bail if there is no routine */ if (config->timed_interrupt != NULL && !cpu_is_suspended(device, SUSPEND_REASON_HALT | SUSPEND_REASON_RESET | SUSPEND_REASON_DISABLE)) (*config->timed_interrupt)(device); } /*------------------------------------------------- triggertime_callback - signal a global trigger -------------------------------------------------*/ static TIMER_CALLBACK( triggertime_callback ) { cpuexec_trigger(machine, param); } /*------------------------------------------------- empty_event_queue - empty a CPU's event queue for a specific input line -------------------------------------------------*/ static TIMER_CALLBACK( empty_event_queue ) { const device_config *device = (const device_config *)ptr; cpu_class_data *classdata = get_class_data(device); cpu_input_data *inputline = &classdata->input[param]; int curevent; /* loop over all events */ for (curevent = 0; curevent < inputline->qindex; curevent++) { INT32 input_event = inputline->queue[curevent]; int state = input_event & 0xff; int vector = input_event >> 8; /* set the input line state and vector */ inputline->curstate = state; inputline->curvector = vector; /* special case: RESET */ if (param == INPUT_LINE_RESET) { /* if we're asserting the line, just halt the CPU */ if (state == ASSERT_LINE) cpu_suspend(device, SUSPEND_REASON_RESET, 1); else { /* if we're clearing the line that was previously asserted, or if we're just */ /* pulsing the line, reset the CPU */ if ((state == CLEAR_LINE && cpu_is_suspended(device, SUSPEND_REASON_RESET)) || state == PULSE_LINE) device_reset(device); /* if we're clearing the line, make sure the CPU is not halted */ cpu_resume(device, SUSPEND_REASON_RESET); } } /* special case: HALT */ else if (param == INPUT_LINE_HALT) { /* if asserting, halt the CPU */ if (state == ASSERT_LINE) cpu_suspend(device, SUSPEND_REASON_HALT, 1); /* if clearing, unhalt the CPU */ else if (state == CLEAR_LINE) cpu_resume(device, SUSPEND_REASON_HALT); } /* all other cases */ else { /* switch off the requested state */ switch (state) { case PULSE_LINE: cpu_set_info(device, CPUINFO_INT_INPUT_STATE + param, ASSERT_LINE); cpu_set_info(device, CPUINFO_INT_INPUT_STATE + param, CLEAR_LINE); break; case HOLD_LINE: case ASSERT_LINE: cpu_set_info(device, CPUINFO_INT_INPUT_STATE + param, ASSERT_LINE); break; case CLEAR_LINE: cpu_set_info(device, CPUINFO_INT_INPUT_STATE + param, CLEAR_LINE); break; default: logerror("empty_event_queue cpu '%s', line %d, unknown state %d\n", device->tag, param, state); break; } /* generate a trigger to unsuspend any CPUs waiting on the interrupt */ if (state != CLEAR_LINE) cpu_triggerint(device); } } /* reset counter */ inputline->qindex = 0; } /*------------------------------------------------- standard_irq_callback - IRQ acknowledge callback; handles HOLD_LINE case and signals to the debugger -------------------------------------------------*/ static IRQ_CALLBACK( standard_irq_callback ) { cpu_class_data *classdata = get_class_data(device); cpu_input_data *inputline = &classdata->input[irqline]; int vector = inputline->curvector; LOG(("standard_irq_callback('%s', %d) $%04x\n", device->tag, irqline, vector)); /* if the IRQ state is HOLD_LINE, clear it */ if (inputline->curstate == HOLD_LINE) { LOG(("->set_irq_line('%s',%d,%d)\n", device->tag, irqline, CLEAR_LINE)); cpu_set_info(device, CPUINFO_INT_INPUT_STATE + irqline, CLEAR_LINE); inputline->curstate = CLEAR_LINE; } /* if there's a driver callback, run it */ if (classdata->driver_irq != NULL) vector = (*classdata->driver_irq)(device, irqline); /* notify the debugger */ debugger_interrupt_hook(device, irqline); /* otherwise, just return the current vector */ return vector; } /*------------------------------------------------- register_save_states - register for CPU- specific save states -------------------------------------------------*/ static void register_save_states(const device_config *device) { cpu_class_data *classdata = get_class_data(device); int line; state_save_register_device_item(device, 0, classdata->suspend); state_save_register_device_item(device, 0, classdata->nextsuspend); state_save_register_device_item(device, 0, classdata->eatcycles); state_save_register_device_item(device, 0, classdata->nexteatcycles); state_save_register_device_item(device, 0, classdata->trigger); state_save_register_device_item(device, 0, classdata->iloops); state_save_register_device_item(device, 0, classdata->totalcycles); state_save_register_device_item(device, 0, classdata->localtime.seconds); state_save_register_device_item(device, 0, classdata->localtime.attoseconds); state_save_register_device_item(device, 0, classdata->clock); state_save_register_device_item(device, 0, classdata->clockscale); for (line = 0; line < ARRAY_LENGTH(classdata->input); line++) { cpu_input_data *inputline = &classdata->input[line]; state_save_register_device_item(device, line, inputline->vector); state_save_register_device_item(device, line, inputline->curvector); state_save_register_device_item(device, line, inputline->curstate); } } /*------------------------------------------------- rebuild_execute_list - rebuild the list of executing CPUs, moving suspended CPUs to the end -------------------------------------------------*/ static void rebuild_execute_list(running_machine *machine) { cpuexec_private *global = machine->cpuexec_data; const device_config *curcpu; cpu_class_data **tailptr; /* start with an empty list */ tailptr = &global->executelist; *tailptr = NULL; /* first iterate over non-suspended CPUs */ for (curcpu = machine->firstcpu; curcpu != NULL; curcpu = cpu_next(curcpu)) { cpu_class_data *classdata = get_class_data(curcpu); if (classdata->suspend == 0) { *tailptr = classdata; tailptr = &classdata->next; classdata->next = NULL; } } /* then add the suspended CPUs */ for (curcpu = machine->firstcpu; curcpu != NULL; curcpu = cpu_next(curcpu)) { cpu_class_data *classdata = get_class_data(curcpu); if (classdata->suspend != 0) { *tailptr = classdata; tailptr = &classdata->next; classdata->next = NULL; } } } /*------------------------------------------------- get_register_value - return a register value of a CPU using the state table -------------------------------------------------*/ static UINT64 get_register_value(const device_config *device, void *baseptr, const cpu_state_entry *entry) { void *dataptr; UINT64 result; /* NULL entry returns 0 */ if (entry == NULL || baseptr == NULL) return 0; /* if we have an exporter, call it now */ if ((entry->flags & CPUSTATE_EXPORT) != 0) { cpu_state_io_func exportcb = (cpu_state_io_func)device_get_info_fct(device, CPUINFO_FCT_EXPORT_STATE); assert(exportcb != NULL); (*exportcb)(device, baseptr, entry); } /* pick up the value */ dataptr = (UINT8 *)baseptr + entry->dataoffs; switch (entry->datasize) { default: case 1: result = *(UINT8 *)dataptr; break; case 2: result = *(UINT16 *)dataptr; break; case 4: result = *(UINT32 *)dataptr; break; case 8: result = *(UINT64 *)dataptr; break; } return result & entry->mask; } /*------------------------------------------------- set_register_value - set the value of a CPU register using the state table -------------------------------------------------*/ static void set_register_value(const device_config *device, void *baseptr, const cpu_state_entry *entry, UINT64 value) { void *dataptr; /* NULL entry is a no-op */ if (entry == NULL || baseptr == NULL) return; /* apply the mask */ value &= entry->mask; /* sign-extend if necessary */ if ((entry->flags & CPUSTATE_IMPORT_SEXT) != 0 && value > (entry->mask >> 1)) value |= ~entry->mask; /* store the value */ dataptr = (UINT8 *)baseptr + entry->dataoffs; switch (entry->datasize) { default: case 1: *(UINT8 *)dataptr = value; break; case 2: *(UINT16 *)dataptr = value; break; case 4: *(UINT32 *)dataptr = value; break; case 8: *(UINT64 *)dataptr = value; break; } /* if we have an importer, call it now */ if ((entry->flags & CPUSTATE_IMPORT) != 0) { cpu_state_io_func importcb = (cpu_state_io_func)device_get_info_fct(device, CPUINFO_FCT_IMPORT_STATE); assert(importcb != NULL); (*importcb)(device, baseptr, entry); } } /*------------------------------------------------- get_register_string_value - return a string representation of a CPU register using the state table -------------------------------------------------*/ static void get_register_string_value(const device_config *device, void *baseptr, const cpu_state_entry *entry, char *dest) { static const UINT64 decdivisor[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, U64(10000000000), U64(100000000000), U64(1000000000000), U64(10000000000000), U64(100000000000000), U64(1000000000000000), U64(10000000000000000), U64(100000000000000000), U64(1000000000000000000), U64(10000000000000000000) }; static const char hexchars[] = "0123456789ABCDEF"; int leadzero = 0, width = 0, percent = 0, explicitsign = 0, hitnonzero = 0, reset; const char *fptr; UINT64 result; /* NULL entry does nothing */ if (entry == NULL || entry->symbol == NULL || entry->format == NULL) return; /* fetch the data */ result = get_register_value(device, baseptr, entry); /* start with the basics */ dest += sprintf(dest, "%s%s:", (entry->flags & CPUSTATE_NOSHOW) ? "~" : "", entry->symbol); /* parse the format */ reset = TRUE; for (fptr = entry->format; *fptr != 0; fptr++) { int digitnum; /* reset any accumulated state */ if (reset) leadzero = width = percent = explicitsign = reset = 0; /* if we're not within a format, then anything other than a % outputs directly */ if (!percent && *fptr != '%') { *dest++ = *fptr; continue; } /* handle each character in turn */ switch (*fptr) { /* % starts a format; %% outputs a single % */ case '%': if (!percent) percent = TRUE; else { *dest++ = *fptr; percent = FALSE; } break; /* 0 means insert leading 0s, unless it follows another width digit */ case '0': if (width == 0) leadzero = TRUE; else width *= 10; break; /* 1-9 accumulate into the width */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': width = width * 10 + (*fptr - '0'); break; /* + means explicit sign */ case '+': explicitsign = TRUE; break; /* X outputs as hexadecimal */ case 'X': if (width == 0) fatalerror("Width required for %%X formats\n"); hitnonzero = FALSE; while (leadzero && width > 16) { *dest++ = ' '; width--; } for (digitnum = 15; digitnum >= 0; digitnum--) { int digit = (result >> (4 * digitnum)) & 0x0f; if (digit != 0) *dest++ = hexchars[digit]; else if (hitnonzero || (leadzero && digitnum < width) || digitnum == 0) *dest++ = '0'; hitnonzero |= digit; } reset = TRUE; break; /* d outputs as signed decimal */ case 'd': if (width == 0) fatalerror("Width required for %%d formats\n"); if ((result & entry->mask) > (entry->mask >> 1)) { result = -result & entry->mask; *dest++ = '-'; width--; } else if (explicitsign) { *dest++ = '+'; width--; } /* fall through to unsigned case */ /* u outputs as unsigned decimal */ case 'u': if (width == 0) fatalerror("Width required for %%u formats\n"); hitnonzero = FALSE; while (leadzero && width > ARRAY_LENGTH(decdivisor)) { *dest++ = ' '; width--; } for (digitnum = ARRAY_LENGTH(decdivisor); digitnum >= 0; digitnum--) { int digit = (result >= decdivisor[digitnum]) ? (result / decdivisor[digitnum]) % 10 : 0; if (digit != 0) *dest++ = '0' + digit; else if (hitnonzero || (leadzero && digitnum < width) || digitnum == 0) *dest++ = '0'; hitnonzero |= digit; } reset = TRUE; break; /* s is a custom format */ case 's': { cpu_string_io_func exportstring = (cpu_string_io_func)device_get_info_fct(device, CPUINFO_FCT_EXPORT_STRING); assert(exportstring != NULL); (*exportstring)(device, baseptr, entry, dest); dest += strlen(dest); break; } /* other formats unknown */ default: fatalerror("Unknown format character '%c'\n", *fptr); break; } } *dest = 0; } /*------------------------------------------------- get_register_string_max_width - return the maximum width of a string described by a format -------------------------------------------------*/ #ifdef UNUSED_FUNCTION static int get_register_string_max_width(const device_config *device, void *baseptr, const cpu_state_entry *entry) { int leadzero = 0, width = 0, percent = 0, explicitsign = 0, reset; int totalwidth = 0; const char *fptr; /* NULL entry does nothing */ if (entry == NULL || entry->symbol == NULL || entry->format == NULL) return 0; /* parse the format */ reset = TRUE; for (fptr = entry->format; *fptr != 0; fptr++) { /* reset any accumulated state */ if (reset) leadzero = width = percent = explicitsign = reset = 0; /* if we're not within a format, then anything other than a % outputs directly */ if (!percent && *fptr != '%') { totalwidth++; continue; } /* handle each character in turn */ switch (*fptr) { /* % starts a format; %% outputs a single % */ case '%': if (!percent) percent = TRUE; else { totalwidth++; percent = FALSE; } break; /* 0 means insert leading 0s, unless it follows another width digit */ case '0': if (width == 0) leadzero = TRUE; else width *= 10; break; /* 1-9 accumulate into the width */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': width = width * 10 + (*fptr - '0'); break; /* + means explicit sign */ case '+': explicitsign = TRUE; break; /* X outputs as hexadecimal */ /* d outputs as signed decimal */ /* u outputs as unsigned decimal */ /* s outputs as custom format */ case 'X': case 'd': case 'u': case 's': totalwidth += width; reset = TRUE; break; if (width == 0) fatalerror("Width required for %%d formats\n"); totalwidth += width; break; /* other formats unknown */ default: fatalerror("Unknown format character '%c'\n", *fptr); break; } } return totalwidth; } #endif