apple2ix/src/timing.c

183 lines
4.7 KiB
C
Raw Normal View History

/*
2013-06-28 06:36:25 +00:00
* Apple // emulator for Linux
*
* CPU Timing Support.
*
* Mostly this adds support for specifically throttling the emulator speed to
* match a 1.02MHz Apple //e.
*
* Added 2013 by Aaron Culliney
*
2013-06-28 06:36:25 +00:00
*/
#include <stdlib.h>
#include <stdio.h>
2013-07-23 07:33:29 +00:00
#include <stdint.h>
2013-06-28 06:36:25 +00:00
#include <time.h>
#include <pthread.h>
#include <limits.h>
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-06-28 06:36:25 +00:00
2013-07-23 07:33:29 +00:00
#define CALIBRATE_HZ 100
2013-07-23 07:33:29 +00:00
static unsigned long CPU_TARGET_HZ = APPLE2_HZ; // target clock speed
static unsigned long CALIBRATE_INTERVAL = NANOSECONDS / CALIBRATE_HZ; // calibration interval for drifting
static float CYCLE_NSECS = NANOSECONDS / (float)APPLE2_HZ; // nanosecs per cycle
2013-07-23 07:33:29 +00:00
static struct timespec ti;
2013-06-28 06:36:25 +00:00
2013-07-23 07:33:29 +00:00
static int spinloop_count=0; // spin loop counter
2013-06-28 06:36:25 +00:00
// -----------------------------------------------------------------------------
// assuming end > start, returns end - start
static inline struct timespec timespec_diff(struct timespec start, struct timespec end) {
2013-06-28 06:36:25 +00:00
struct timespec t;
// assuming time_t is signed ...
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;
t.tv_nsec = NANOSECONDS + end.tv_nsec - start.tv_nsec;
}
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;
}
static inline long timespec_nsecs(struct timespec t) {
2013-06-28 06:36:25 +00:00
return t.tv_sec*NANOSECONDS + t.tv_nsec;
}
// spin loop to throttle to target CPU Hz
static inline void _spin_loop(unsigned long c)
{
static volatile unsigned int spinney=0; // volatile to prevent being optimized away
for (unsigned long i=0; i<c; i++)
{
++spinney;
}
}
static void _determine_initial_spinloop_counter()
{
2013-07-23 07:33:29 +00:00
struct timespec s0, s1, deltat;
// time the spinloop to determine a good starting value for the spin counter
unsigned long avg_spin_nsecs = 0;
unsigned int const samples = 5;
unsigned int i=0;
spinloop_count = 500000000;
do
{
clock_gettime(CLOCK_MONOTONIC, &s0);
_spin_loop(spinloop_count);
clock_gettime(CLOCK_MONOTONIC, &s1);
deltat = timespec_diff(s0, s1);
if (deltat.tv_sec > 0)
{
printf("oops long wait (>= %lu sec) adjusting loop count (%d -> %d)\n", deltat.tv_sec, spinloop_count, spinloop_count>>1);
spinloop_count >>= 1;
i = 0;
avg_spin_nsecs = 0;
continue;
}
printf("spinloop = %lu nsec\n", deltat.tv_nsec);
avg_spin_nsecs += deltat.tv_nsec;
++i;
} while (i<samples);
avg_spin_nsecs = (avg_spin_nsecs / samples);
printf("average = %lu nsec\n", avg_spin_nsecs);
2013-07-23 07:33:29 +00:00
spinloop_count = CYCLE_NSECS * spinloop_count / avg_spin_nsecs;
2013-07-23 07:33:29 +00:00
printf("counter for a single %fns cycle = %d\n", CYCLE_NSECS, spinloop_count);
}
void timing_initialize() {
// should do this only on startup
_determine_initial_spinloop_counter();
2013-07-23 07:33:29 +00:00
clock_gettime(CLOCK_MONOTONIC, &ti);
2013-06-28 06:36:25 +00:00
}
void timing_set_cpu_scale(unsigned int scale)
{
// ...
2013-06-28 06:36:25 +00:00
}
/*
2013-07-23 07:33:29 +00:00
* Calibrate emulator clock to real clock ...
*
2013-07-23 07:33:29 +00:00
* NOTE: these calculations could overflow if emulator speed is severely dampened back...
2013-06-28 06:36:25 +00:00
*/
2013-07-23 07:33:29 +00:00
static int _calibrate_clock (long drift_interval_nsecs)
{
2013-07-23 07:33:29 +00:00
return 0;
// HACK FIXME : this is broken, plz debug, kthxbye!
2013-06-28 06:36:25 +00:00
2013-07-23 07:33:29 +00:00
struct timespec tj, deltat;
clock_gettime(CLOCK_MONOTONIC, &tj);
deltat = timespec_diff(ti, tj);
ti=tj;
2013-06-28 06:36:25 +00:00
2013-07-23 07:33:29 +00:00
long real_nsecs = NANOSECONDS * deltat.tv_sec + deltat.tv_nsec;
int drift_nsecs = (int)(drift_interval_nsecs - real_nsecs); // +/- nsec drift
int drift_count = (int)(drift_nsecs * (spinloop_count / CYCLE_NSECS) ); // +/- count drift
// adjust spinloop_count ...
spinloop_count += drift_count / CALIBRATE_INTERVAL;
2013-06-28 06:36:25 +00:00
2013-07-23 07:33:29 +00:00
// ideally we should return a sub-interval here ...
return 0;
}
2013-06-28 06:36:25 +00:00
2013-07-23 07:33:29 +00:00
/*
* Throttles 6502 CPU down to the target CPU frequency (default is speed of original Apple //e).
*
* This uses an adaptive spin loop to stay closer to the target CPU frequency.
*/
void timing_throttle()
{
static float drift_interval=0.0;
static int spinloop_adjust=0;
2013-07-23 07:33:29 +00:00
uint8_t opcycles = cpu65__opcycles[cpu65_debug.opcode] + cpu65_debug.opcycles;
uint8_t c=0;
if (spinloop_adjust < 0)
{
2013-07-23 07:33:29 +00:00
c=-1;
++spinloop_adjust;
}
2013-07-23 07:33:29 +00:00
else if (spinloop_adjust > 0)
{
2013-07-23 07:33:29 +00:00
c=1;
--spinloop_adjust;
}
2013-07-23 07:33:29 +00:00
// spin for the desired/estimated number of nsecs
_spin_loop(opcycles * (spinloop_count+c));
drift_interval += opcycles*CYCLE_NSECS;
if (drift_interval > CALIBRATE_INTERVAL)
{
2013-07-23 07:33:29 +00:00
// perform calibration
spinloop_adjust = _calibrate_clock((long)drift_interval);
drift_interval = 0.0;
2013-06-28 06:36:25 +00:00
}
}