2013-07-06 04:37:13 +00:00
|
|
|
/*
|
2013-10-06 06:22:08 +00:00
|
|
|
* Apple // emulator for *nix
|
2013-06-28 06:36:25 +00:00
|
|
|
*
|
2013-10-06 06:22:08 +00:00
|
|
|
* This software package is subject to the GNU General Public License
|
|
|
|
* version 2 or later (your choice) as published by the Free Software
|
|
|
|
* Foundation.
|
2013-06-28 06:36:25 +00:00
|
|
|
*
|
2013-10-06 06:22:08 +00:00
|
|
|
* THERE ARE NO WARRANTIES WHATSOEVER.
|
2013-07-06 04:37:13 +00:00
|
|
|
*
|
2013-06-28 06:36:25 +00:00
|
|
|
*/
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
/*
|
|
|
|
* 65c02 CPU Timing Support. Some source derived from AppleWin.
|
|
|
|
*
|
|
|
|
* Copyleft 2013 Aaron Culliney
|
|
|
|
*
|
|
|
|
*/
|
2013-06-28 06:36:25 +00:00
|
|
|
|
2013-07-23 07:33:29 +00:00
|
|
|
#include "timing.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "cpu.h"
|
2013-10-06 06:22:08 +00:00
|
|
|
#include "speaker.h"
|
|
|
|
#include "keys.h"
|
2013-11-06 06:11:27 +00:00
|
|
|
#include "mockingboard.h"
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
#define EXECUTION_PERIOD_NSECS 1000000 // AppleWin: nExecutionPeriodUsec
|
2013-06-28 06:36:25 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
double g_fCurrentCLK6502 = CLK_6502;
|
|
|
|
bool g_bFullSpeed = false; // HACK TODO FIXME : prolly shouldn't be global anymore -- don't think it's necessary for speaker/soundcore/etc anymore ...
|
|
|
|
uint64_t g_nCumulativeCycles = 0; // cumulative cycles since emulator (re)start
|
|
|
|
int g_nCpuCyclesFeedback = 0;
|
2013-10-06 08:31:58 +00:00
|
|
|
|
|
|
|
static bool alt_speed_enabled = false;
|
2013-10-06 06:22:08 +00:00
|
|
|
double cpu_scale_factor = 1.0;
|
2013-10-06 08:31:58 +00:00
|
|
|
double cpu_altscale_factor = 1.0;
|
2013-07-22 00:20:03 +00:00
|
|
|
|
2013-12-21 21:55:40 +00:00
|
|
|
int gc_cycles_timer_0 = 0;
|
|
|
|
int gc_cycles_timer_1 = 0;
|
|
|
|
|
2013-11-06 06:11:27 +00:00
|
|
|
uint8_t emul_reinitialize;
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
static unsigned int g_nCyclesExecuted; // # of cycles executed up to last IO access
|
2013-07-02 08:10:57 +00:00
|
|
|
|
2013-06-28 06:36:25 +00:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative) {
|
2013-06-28 06:36:25 +00:00
|
|
|
struct timespec t;
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
if (*negative)
|
|
|
|
{
|
|
|
|
*negative = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if start > end, swizzle...
|
|
|
|
if ( (start.tv_sec > end.tv_sec) || ((start.tv_sec == end.tv_sec) && (start.tv_nsec > end.tv_nsec)) )
|
|
|
|
{
|
|
|
|
t=start;
|
|
|
|
start=end;
|
|
|
|
end=t;
|
|
|
|
if (negative)
|
|
|
|
{
|
|
|
|
*negative = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-28 06:36:25 +00:00
|
|
|
// assuming time_t is signed ...
|
2013-07-06 04:37:13 +00:00
|
|
|
if (end.tv_nsec < start.tv_nsec)
|
|
|
|
{
|
2013-06-28 06:36:25 +00:00
|
|
|
t.tv_sec = end.tv_sec - start.tv_sec - 1;
|
2013-10-06 06:22:08 +00:00
|
|
|
t.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
|
2013-07-06 04:37:13 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-06-28 06:36:25 +00:00
|
|
|
t.tv_sec = end.tv_sec - start.tv_sec;
|
|
|
|
t.tv_nsec = end.tv_nsec - start.tv_nsec;
|
|
|
|
}
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
static inline struct timespec timespec_add(struct timespec start, unsigned long nsecs) {
|
|
|
|
|
|
|
|
start.tv_nsec += nsecs;
|
|
|
|
if (start.tv_nsec > NANOSECONDS)
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2013-10-06 06:22:08 +00:00
|
|
|
start.tv_sec += (start.tv_nsec / NANOSECONDS);
|
|
|
|
start.tv_nsec %= NANOSECONDS;
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
return start;
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 08:31:58 +00:00
|
|
|
static void _timing_initialize(double scale)
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2013-10-06 08:31:58 +00:00
|
|
|
if (g_bFullSpeed)
|
|
|
|
{
|
|
|
|
LOG("timing_initialize() emulation at fullspeed ...");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_fCurrentCLK6502 = CLK_6502 * scale;
|
|
|
|
// this is extracted out of SetClksPerSpkrSample -- speaker.c
|
|
|
|
g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE);
|
|
|
|
SpkrReinitialize();
|
|
|
|
|
|
|
|
LOG("timing_initialize() ... ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", g_fCurrentCLK6502, g_fClksPerSpkrSample);
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 08:31:58 +00:00
|
|
|
static void _switch_to_fullspeed(double scale)
|
2013-10-06 06:22:08 +00:00
|
|
|
{
|
|
|
|
if (!g_bFullSpeed)
|
|
|
|
{
|
|
|
|
g_bFullSpeed = true;
|
|
|
|
c_disable_sound_hooks();
|
|
|
|
}
|
2013-06-28 06:36:25 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 08:31:58 +00:00
|
|
|
static void _switch_to_regular_speed(double scale)
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2013-10-06 06:22:08 +00:00
|
|
|
if (g_bFullSpeed)
|
|
|
|
{
|
|
|
|
g_bFullSpeed = false;
|
|
|
|
c_initialize_sound_hooks();
|
|
|
|
}
|
2013-10-06 08:31:58 +00:00
|
|
|
_timing_initialize(scale);
|
2013-06-28 06:36:25 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 08:31:58 +00:00
|
|
|
void timing_toggle_cpu_speed()
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2013-10-06 08:31:58 +00:00
|
|
|
alt_speed_enabled = !alt_speed_enabled;
|
2013-06-28 06:36:25 +00:00
|
|
|
|
2013-10-06 08:31:58 +00:00
|
|
|
if (alt_speed_enabled)
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2013-10-06 08:31:58 +00:00
|
|
|
if (cpu_altscale_factor >= CPU_SCALE_FASTEST)
|
|
|
|
{
|
|
|
|
_switch_to_fullspeed(cpu_altscale_factor);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_switch_to_regular_speed(cpu_altscale_factor);
|
|
|
|
}
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
2013-10-06 08:31:58 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (cpu_scale_factor >= CPU_SCALE_FASTEST)
|
|
|
|
{
|
|
|
|
_switch_to_fullspeed(cpu_scale_factor);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_switch_to_regular_speed(cpu_scale_factor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-07-26 05:58:31 +00:00
|
|
|
|
2013-10-06 08:31:58 +00:00
|
|
|
void timing_initialize()
|
|
|
|
{
|
|
|
|
_timing_initialize(cpu_scale_factor);
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void cpu_thread(void *dummyptr) {
|
|
|
|
struct timespec deltat;
|
|
|
|
struct timespec t0; // the target timer
|
|
|
|
struct timespec ti, tj; // actual time samples
|
|
|
|
bool negative = false;
|
|
|
|
long drift_adj_nsecs = 0; // generic drift adjustment between target and actual
|
2013-07-23 07:33:29 +00:00
|
|
|
|
2013-11-17 23:01:23 +00:00
|
|
|
#ifndef NDEBUG
|
2013-10-06 06:22:08 +00:00
|
|
|
unsigned long dbg_ticks = 0;
|
2013-11-17 23:01:23 +00:00
|
|
|
int speaker_neg_feedback = 0;
|
|
|
|
int speaker_pos_feedback = 0;
|
|
|
|
#endif
|
2013-07-23 07:33:29 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
do
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2013-10-06 06:22:08 +00:00
|
|
|
g_nCumulativeCycles = 0;
|
|
|
|
static int16_t cycles_adjust = 0;
|
2013-10-07 04:01:00 +00:00
|
|
|
LOG("cpu_thread : begin main loop ...");
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
2013-11-06 06:11:27 +00:00
|
|
|
|
|
|
|
emul_reinitialize = 1;
|
2013-10-06 06:22:08 +00:00
|
|
|
do {
|
|
|
|
// -LOCK----------------------------------------------------------------------------------------- SAMPLE ti
|
|
|
|
pthread_mutex_lock(&interface_mutex);
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ti);
|
|
|
|
|
|
|
|
deltat = timespec_diff(t0, ti, &negative);
|
|
|
|
if (deltat.tv_sec)
|
|
|
|
{
|
|
|
|
// TODO FIXME : this is innocuous when coming out of interface menus, but are there any other edge cases?
|
|
|
|
LOG("NOTE : serious divergence from target time ...");
|
|
|
|
t0 = ti;
|
|
|
|
deltat = timespec_diff(t0, ti, &negative);
|
|
|
|
}
|
|
|
|
t0 = timespec_add(t0, EXECUTION_PERIOD_NSECS); // expected interval
|
|
|
|
drift_adj_nsecs = negative ? ~deltat.tv_nsec : deltat.tv_nsec;
|
|
|
|
|
|
|
|
// set up increment & decrement counters
|
|
|
|
cpu65_cycles_to_execute = (g_fCurrentCLK6502 / 1000); // g_fCurrentCLK6502 * EXECUTION_PERIOD_NSECS / NANOSECONDS
|
|
|
|
cpu65_cycles_to_execute += g_nCpuCyclesFeedback;
|
|
|
|
cpu65_cycles_to_execute -= cycles_adjust;
|
|
|
|
if (cpu65_cycles_to_execute < 0)
|
|
|
|
{
|
|
|
|
cpu65_cycles_to_execute = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
cpu65_cycle_count = 0;
|
|
|
|
g_nCyclesExecuted = 0;
|
2013-11-06 06:11:27 +00:00
|
|
|
|
|
|
|
MB_StartOfCpuExecute();
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
cpu65_run(); // run emulation for cpu65_cycles_to_execute cycles ...
|
2013-11-06 06:11:27 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
cycles_adjust = cpu65_cycles_to_execute; // counter is decremented in cpu65_run()
|
|
|
|
if (cycles_adjust < 0)
|
|
|
|
{
|
|
|
|
cycles_adjust = ~cycles_adjust +1; // cycles_adjust *= -1
|
|
|
|
}
|
|
|
|
unsigned int uExecutedCycles = cpu65_cycle_count;
|
|
|
|
|
2013-11-06 06:11:27 +00:00
|
|
|
MB_UpdateCycles(uExecutedCycles); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below)
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
// N.B.: IO calls that depend on accurate timing will update g_nCyclesExecuted
|
|
|
|
const unsigned int nRemainingCycles = uExecutedCycles - g_nCyclesExecuted;
|
|
|
|
g_nCumulativeCycles += nRemainingCycles;
|
|
|
|
|
|
|
|
if (!g_bFullSpeed)
|
|
|
|
{
|
|
|
|
SpkrUpdate(uExecutedCycles); // play audio
|
|
|
|
}
|
|
|
|
|
2013-11-06 06:11:27 +00:00
|
|
|
// N.B.: technically this is not the end of the video frame...
|
|
|
|
MB_EndOfVideoFrame();
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &tj);
|
|
|
|
pthread_mutex_unlock(&interface_mutex);
|
|
|
|
// -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj
|
|
|
|
|
|
|
|
deltat = timespec_diff(ti, tj, &negative);
|
|
|
|
assert(!negative);
|
|
|
|
long sleepfor = 0;
|
|
|
|
if (!deltat.tv_sec && !g_bFullSpeed)
|
|
|
|
{
|
|
|
|
sleepfor = EXECUTION_PERIOD_NSECS - drift_adj_nsecs - deltat.tv_nsec;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sleepfor <= 0)
|
|
|
|
{
|
|
|
|
// lagging ...
|
|
|
|
static time_t throttle_warning = 0;
|
|
|
|
if (t0.tv_sec - throttle_warning > 0)
|
|
|
|
{
|
|
|
|
LOG("lagging... %ld . %ld", deltat.tv_sec, deltat.tv_nsec);
|
|
|
|
throttle_warning = t0.tv_sec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
deltat.tv_sec = 0;
|
|
|
|
deltat.tv_nsec = sleepfor;
|
|
|
|
nanosleep(&deltat, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
2013-11-17 23:01:23 +00:00
|
|
|
// collect timing statistics
|
|
|
|
|
|
|
|
if (speaker_neg_feedback > g_nCpuCyclesFeedback)
|
|
|
|
{
|
|
|
|
speaker_neg_feedback = g_nCpuCyclesFeedback;
|
|
|
|
}
|
|
|
|
if (speaker_pos_feedback < g_nCpuCyclesFeedback)
|
|
|
|
{
|
|
|
|
speaker_pos_feedback = g_nCpuCyclesFeedback;
|
|
|
|
}
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
dbg_ticks += EXECUTION_PERIOD_NSECS;
|
|
|
|
if ((dbg_ticks % NANOSECONDS) == 0)
|
|
|
|
{
|
2013-11-17 23:01:23 +00:00
|
|
|
LOG("tick:(%ld.%ld) real:(%ld.%ld) ... speaker cycles feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, speaker_neg_feedback, speaker_pos_feedback);
|
2013-10-06 06:22:08 +00:00
|
|
|
dbg_ticks = 0;
|
2013-11-17 23:01:23 +00:00
|
|
|
speaker_neg_feedback = 0;
|
|
|
|
speaker_pos_feedback = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
#endif
|
2013-11-06 06:11:27 +00:00
|
|
|
} while (!emul_reinitialize);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
reinitialize();
|
|
|
|
} while (1);
|
|
|
|
}
|
2013-07-26 05:58:31 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
// From AppleWin...
|
|
|
|
// Called when an IO-reg is accessed & accurate cycle info is needed
|
|
|
|
void CpuCalcCycles(const unsigned long nExecutedCycles)
|
|
|
|
{
|
|
|
|
// Calc # of cycles executed since this func was last called
|
|
|
|
const long nCycles = nExecutedCycles - g_nCyclesExecuted;
|
|
|
|
assert(nCycles >= 0);
|
|
|
|
g_nCumulativeCycles += nCycles;
|
2013-07-26 05:58:31 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
g_nCyclesExecuted = nExecutedCycles;
|
2013-06-28 06:36:25 +00:00
|
|
|
}
|
2013-07-22 00:20:03 +00:00
|
|
|
|