2017-02-26 16:00:41 +00:00
# include <stdio.h>
# include <unistd.h>
# include <curses.h>
# include <termios.h>
# include <pthread.h>
# include "applevm.h"
# include "sdl-display.h"
# include "sdl-keyboard.h"
# include "sdl-speaker.h"
# include "sdl-paddles.h"
# include "sdl-filemanager.h"
# include "sdl-printer.h"
# include "globals.h"
# include "timeutil.h"
2017-02-27 13:11:59 +00:00
# define SHOWFPS
2017-02-26 16:00:41 +00:00
//#define SHOWPC
//#define DEBUGCPU
//#define SHOWMEMPAGE
static struct timespec nextInstructionTime , startTime ;
# define NB_ENABLE 1
# define NB_DISABLE 0
int send_rst = 0 ;
pthread_t cpuThreadID ;
2017-12-29 19:08:49 +00:00
char disk1name [ 256 ] = " \0 " ;
char disk2name [ 256 ] = " \0 " ;
2017-12-30 20:20:34 +00:00
volatile bool wantSuspend = false ;
volatile bool wantResume = false ;
2017-02-26 16:00:41 +00:00
void sigint_handler ( int n )
{
send_rst = 1 ;
}
void nonblock ( int state )
{
struct termios ttystate ;
//get the terminal state
tcgetattr ( STDIN_FILENO , & ttystate ) ;
if ( state = = NB_ENABLE )
{
//turn off canonical mode
ttystate . c_lflag & = ~ ICANON ;
//minimum of number input read.
ttystate . c_cc [ VMIN ] = 1 ;
}
else if ( state = = NB_DISABLE )
{
//turn on canonical mode
ttystate . c_lflag | = ICANON ;
}
//set the terminal attributes.
tcsetattr ( STDIN_FILENO , TCSANOW , & ttystate ) ;
}
uint8_t read ( void * arg , uint16_t address )
{
// no action; this is a dummy function until we've finished initializing...
return 0x00 ;
}
void write ( void * arg , uint16_t address , uint8_t v )
{
// no action; this is a dummy function until we've finished initializing...
}
static void * cpu_thread ( void * dummyptr ) {
struct timespec currentTime ;
#if 0
int policy ;
struct sched_param param ;
pthread_getschedparam ( pthread_self ( ) , & policy , & param ) ;
param . sched_priority = sched_get_priority_max ( policy ) ;
pthread_setschedparam ( pthread_self ( ) , policy , & param ) ;
# endif
_init_darwin_shim ( ) ;
do_gettime ( & startTime ) ;
2017-12-31 22:21:34 +00:00
printf ( " Start time: %lu,%lu \n " , startTime . tv_sec , startTime . tv_nsec ) ;
2017-02-26 16:00:41 +00:00
do_gettime ( & nextInstructionTime ) ;
printf ( " free-running \n " ) ;
while ( 1 ) {
2017-12-30 20:20:34 +00:00
if ( wantSuspend ) {
printf ( " CPU halted; suspending VM \n " ) ;
g_vm - > Suspend ( " suspend.vm " ) ;
printf ( " ... done; resuming CPU. \n " ) ;
wantSuspend = false ;
}
if ( wantResume ) {
printf ( " CPU halted; resuming VM \n " ) ;
g_vm - > Resume ( " suspend.vm " ) ;
printf ( " ... done. resuming CPU. \n " ) ;
wantResume = false ;
}
2017-12-31 22:21:34 +00:00
// Would like to do the old nanosleep thing, but the speaker needs
// to run. FIXME: do something more intelligent here - sleep 'til speakertime+1? (Obv. to do this below, not right here)
2017-02-26 16:00:41 +00:00
do_gettime ( & currentTime ) ;
2017-12-31 22:21:34 +00:00
// tsSubtract doesn't return negatives; it bounds at 0.
2017-02-26 16:00:41 +00:00
struct timespec diff = tsSubtract ( nextInstructionTime , currentTime ) ;
2017-12-31 22:21:34 +00:00
// do_gettime(¤tTime);
struct timespec runtime = tsSubtract ( currentTime , startTime ) ;
double speakerCycle = cycles_since_time ( & runtime ) ;
uint8_t executed = 0 ;
if ( diff . tv_sec = = 0 & & diff . tv_nsec = = 0 ) {
// okay to run CPU
// If speakerCycle == 0, we're still starting up
// If speakerCycle > cycles, the CPU is running behind; don't bother with that just yet
// If we're about to run the CPU then we *should* have caught up the speaker - how could it possibly be this far out of skew?
if ( speakerCycle & & speakerCycle < g_cpu - > cycles & & abs ( g_cpu - > cycles - speakerCycle ) > 24 ) {
#if 0
printf ( " Start time: %lu,%lu \n " , startTime . tv_sec , startTime . tv_nsec ) ;
printf ( " runtime: %lu,%lu \n " , runtime . tv_sec , runtime . tv_nsec ) ;
printf ( " Current time: %lu,%lu \n " , currentTime . tv_sec , currentTime . tv_nsec ) ;
printf ( " Next time: %lu,%lu \n " , nextInstructionTime . tv_sec , nextInstructionTime . tv_nsec ) ;
printf ( " Speaker calc / cycle count: %lf / %d [e %d; d %f] \n " , speakerCycle , g_cpu - > cycles , executed , abs ( g_cpu - > cycles - speakerCycle ) ) ;
# endif
// If we're okay to run the CPU, then the speaker should be caught up. Not sure how it wouldn't be.
printf ( " About to run cpu but speaker diff > 24 - how, exactly? \n " ) ;
exit ( 1 ) ;
}
2017-02-26 16:00:41 +00:00
# ifdef DEBUGCPU
2017-12-31 22:21:34 +00:00
uint8_t executed = g_cpu - > Run ( 1 ) ;
2017-02-26 16:00:41 +00:00
# else
2017-12-31 22:21:34 +00:00
executed = g_cpu - > Run ( 24 ) ;
2017-02-26 16:00:41 +00:00
# endif
2017-12-31 22:21:34 +00:00
// calculate the real time that we should be at now, and schedule
// that as our next instruction time
timespec_add_cycles ( & startTime , g_cpu - > cycles , & nextInstructionTime ) ;
2017-02-26 16:00:41 +00:00
2017-12-31 22:21:34 +00:00
// The paddles need to be triggered in real-time on the CPU
// clock. That happens from the VM's CPU maintenance poller.
( ( AppleVM * ) g_vm ) - > cpuMaintenance ( g_cpu - > cycles ) ;
2017-02-26 16:00:41 +00:00
2017-12-31 22:21:34 +00:00
#if 0
do_gettime ( & currentTime ) ;
printf ( " Executed %d cycles; count %d; now %lu,%lu; next runtime at %lu,%lu \n " , executed , g_cpu - > cycles , currentTime . tv_sec , currentTime . tv_nsec , nextInstructionTime . tv_sec , nextInstructionTime . tv_nsec ) ;
# endif
} else {
// printf("delta %lu,%lu\n", diff.tv_sec, diff.tv_nsec);
// printf("Current time: %lu,%lu\n", currentTime.tv_sec, currentTime.tv_nsec);
// printf("Next time: %lu,%lu\n", nextInstructionTime.tv_sec, nextInstructionTime.tv_nsec);
}
// Run the speaker a short bit delayed, based on real time rather
// than the cpu cycle count
2017-02-26 16:00:41 +00:00
2017-12-31 22:21:34 +00:00
#if 0
if ( speakerCycle < g_cpu - > cycles ) {
printf ( " Start time: %lu,%lu \n " , startTime . tv_sec , startTime . tv_nsec ) ;
printf ( " runtime: %lu,%lu \n " , runtime . tv_sec , runtime . tv_nsec ) ;
printf ( " Current time: %lu,%lu \n " , currentTime . tv_sec , currentTime . tv_nsec ) ;
printf ( " Next time: %lu,%lu \n " , nextInstructionTime . tv_sec , nextInstructionTime . tv_nsec ) ;
printf ( " Speaker calc / cycle count: %lf / %d [e %d; d %f] \n " , speakerCycle , g_cpu - > cycles , executed , abs ( g_cpu - > cycles - speakerCycle ) ) ;
}
# endif
int lastdrift = g_cpu - > cycles - speakerCycle ;
if ( speakerCycle & &
speakerCycle < g_cpu - > cycles & &
lastdrift > 64 ) {
printf ( " Cycle -> speakercycle drift > 64 [%f] \n " , abs ( g_cpu - > cycles - speakerCycle ) ) ;
exit ( 1 ) ;
}
if ( speakerCycle = = 0 ) lastdrift = 0 ;
g_speaker - > maintainSpeaker ( speakerCycle - 48 ) ;
/* // recalc what the fuck is happening
do_gettime ( & currentTime ) ;
sdiff = tsSubtract ( currentTime , startTime ) ;
speakerCycle = cycles_since_time ( & sdiff ) ;
if ( lastdrift & & speakerCycle & & speakerCycle < g_cpu - > cycles & & abs ( g_cpu - > cycles - speakerCycle ) > 64 )
{
int newdrift = g_cpu - > cycles - speakerCycle ;
printf ( " WTF: was %d, now %d [sc now %f] \n " , lastdrift , newdrift , speakerCycle ) ;
exit ( 1 ) ;
} */
2017-02-26 16:00:41 +00:00
# ifdef DEBUGCPU
{
uint8_t p = g_cpu - > flags ;
printf ( " OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c \n " ,
g_vm - > getMMU ( ) - > read ( g_cpu - > pc ) ,
g_cpu - > a , g_cpu - > x , g_cpu - > y , g_cpu - > pc , g_cpu - > sp ,
p & ( 1 < < 7 ) ? ' N ' : ' ' ,
p & ( 1 < < 6 ) ? ' V ' : ' ' ,
p & ( 1 < < 4 ) ? ' B ' : ' ' ,
p & ( 1 < < 3 ) ? ' D ' : ' ' ,
p & ( 1 < < 2 ) ? ' I ' : ' ' ,
p & ( 1 < < 1 ) ? ' Z ' : ' ' ,
p & ( 1 < < 0 ) ? ' C ' : ' '
) ;
}
# endif
if ( send_rst ) {
2017-12-30 20:20:34 +00:00
#if 0
printf ( " Scheduling suspend request... \n " ) ;
wantSuspend = true ;
# endif
2017-12-31 22:21:34 +00:00
#if 0
2017-12-30 20:20:34 +00:00
printf ( " Scheduling resume resume request... \n " ) ;
wantResume = true ;
# endif
2017-12-29 19:08:49 +00:00
#if 0
2017-02-26 16:00:41 +00:00
printf ( " Sending reset \n " ) ;
g_cpu - > Reset ( ) ;
// testing startup keyboard presses - perform Apple //e self-test
//g_vm->getKeyboard()->keyDepressed(RA);
//g_vm->Reset();
//g_cpu->Reset();
//((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK");
2017-12-30 20:20:34 +00:00
# endif
#if 0
2017-12-29 19:08:49 +00:00
// Swap disks
if ( disk1name [ 0 ] & & disk2name [ 0 ] ) {
printf ( " Swapping disks \n " ) ;
printf ( " Inserting disk %s in drive 1 \n " , disk2name ) ;
( ( AppleVM * ) g_vm ) - > insertDisk ( 0 , disk2name ) ;
printf ( " Inserting disk %s in drive 2 \n " , disk1name ) ;
( ( AppleVM * ) g_vm ) - > insertDisk ( 1 , disk1name ) ;
}
2017-12-30 20:20:34 +00:00
# endif
#if 0
2017-02-26 16:00:41 +00:00
MMU * mmu = g_vm - > getMMU ( ) ;
printf ( " PC: 0x%X \n " , g_cpu - > pc ) ;
for ( int i = g_cpu - > pc ; i < g_cpu - > pc + 0x100 ; i + + ) {
printf ( " 0x%X " , mmu - > read ( i ) ) ;
}
printf ( " \n " ) ;
printf ( " Dropping to monitor \n " ) ;
// drop directly to monitor.
g_cpu - > pc = 0xff69 ; // "call -151"
mmu - > read ( 0xC054 ) ; // make sure we're in page 1
mmu - > read ( 0xC056 ) ; // and that hires is off
mmu - > read ( 0xC051 ) ; // and text mode is on
mmu - > read ( 0xC08A ) ; // and we have proper rom in place
mmu - > read ( 0xc008 ) ; // main zero-page
mmu - > read ( 0xc006 ) ; // rom from cards
mmu - > write ( 0xc002 + mmu - > read ( 0xc014 ) ? 1 : 0 , 0xff ) ; // make sure aux ram read and write match
mmu - > write ( 0x20 , 0 ) ; // text window
mmu - > write ( 0x21 , 40 ) ;
mmu - > write ( 0x22 , 0 ) ;
mmu - > write ( 0x23 , 24 ) ;
mmu - > write ( 0x33 , ' > ' ) ;
mmu - > write ( 0x48 , 0 ) ; // from 0xfb2f: part of text init
2017-12-29 19:08:49 +00:00
*/
2017-02-26 16:00:41 +00:00
# endif
send_rst = 0 ;
}
}
}
int main ( int argc , char * argv [ ] )
{
2017-12-31 22:21:34 +00:00
#if 0
// Timing consistency check
sleep ( 2 ) ; // kinda random, hopefully sloppy? - to make startTime != 0,0
printf ( " starting time consistency check \n " ) ;
do_gettime ( & startTime ) ;
for ( int i = 0 ; i < 10000000 ; i + + ) {
// Calculate the time delta from startTime to cycle # i
timespec_add_cycles ( & startTime , i , & nextInstructionTime ) ;
// Recalculate the time difference between nextInstructionTime and startTime
struct timespec runtime = tsSubtract ( nextInstructionTime , startTime ) ;
// See if it's the same as cycles_since_time
double guesstimate = cycles_since_time ( & runtime ) ;
printf ( " cycle %d guesstimate %f \n " , i , guesstimate ) ;
if ( guesstimate ! = i ) {
printf ( " FAILED: cycle %d has guesstimate %f \n " , i , guesstimate ) ;
exit ( 1 ) ;
}
}
printf ( " All ok \n " ) ;
exit ( 1 ) ;
# endif
2017-02-26 16:00:41 +00:00
SDL_Init ( SDL_INIT_EVERYTHING ) ;
g_speaker = new SDLSpeaker ( ) ;
g_printer = new SDLPrinter ( ) ;
// create the filemanager - the interface to the host file system.
g_filemanager = new SDLFileManager ( ) ;
g_display = new SDLDisplay ( ) ;
2017-12-30 01:24:21 +00:00
// g_displayType = m_blackAndWhite;
2017-02-26 16:00:41 +00:00
// paddles have to be created after g_display created the window
g_paddles = new SDLPaddles ( ) ;
// Next create the virtual CPU. This needs the VM's MMU in order to run, but we don't have that yet.
g_cpu = new Cpu ( ) ;
// Create the virtual machine. This may read from g_filemanager to get ROMs if necessary.
// (The actual Apple VM we've built has them compiled in, though.) It will create its virutal
// hardware (MMU, video driver, floppy, paddles, whatever).
g_vm = new AppleVM ( ) ;
g_keyboard = new SDLKeyboard ( g_vm - > getKeyboard ( ) ) ;
// Now that the VM exists and it has created an MMU, we tell the CPU how to access memory through the MMU.
g_cpu - > SetMMU ( g_vm - > getMMU ( ) ) ;
// Now that all the virtual hardware is glued together, reset the VM
g_vm - > Reset ( ) ;
g_cpu - > rst ( ) ;
// g_display->blit();
g_display - > redraw ( ) ;
if ( argc > = 2 ) {
printf ( " Inserting disk %s \n " , argv [ 1 ] ) ;
( ( AppleVM * ) g_vm ) - > insertDisk ( 0 , argv [ 1 ] ) ;
2017-12-29 19:08:49 +00:00
strcpy ( disk1name , argv [ 1 ] ) ;
2017-02-26 16:00:41 +00:00
}
if ( argc = = 3 ) {
printf ( " Inserting disk %s \n " , argv [ 2 ] ) ;
( ( AppleVM * ) g_vm ) - > insertDisk ( 1 , argv [ 2 ] ) ;
2017-12-29 19:08:49 +00:00
strcpy ( disk2name , argv [ 2 ] ) ;
2017-02-26 16:00:41 +00:00
}
2017-12-29 19:08:49 +00:00
// FIXME: fixed test disk...
2017-12-29 20:35:47 +00:00
// ((AppleVM *)g_vm)->insertHD(0, "hd32.img");
2017-02-26 16:00:41 +00:00
nonblock ( NB_ENABLE ) ;
signal ( SIGINT , sigint_handler ) ;
printf ( " creating CPU thread \n " ) ;
if ( ! pthread_create ( & cpuThreadID , NULL , & cpu_thread , ( void * ) NULL ) ) {
printf ( " thread created \n " ) ;
// pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY);
}
while ( 1 ) {
2017-02-27 14:35:49 +00:00
static uint32_t usleepcycles = 16384 ; // step-down for display drawing. FIXME: this constant works well for *my* machine. Dynamically generate?
2017-12-31 22:21:34 +00:00
// static uint32_t ctr = 0;
// if (++ctr == 0) {
// printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount));
// }
2017-02-26 16:00:41 +00:00
2017-02-27 00:59:51 +00:00
// fill disk buffer when needed
( ( AppleVM * ) g_vm ) - > disk6 - > fillDiskBuffer ( ) ;
2017-02-26 16:00:41 +00:00
// Make this a little friendlier, and the expense of some framerate?
// usleep(10000);
if ( g_vm - > vmdisplay - > needsRedraw ( ) ) {
AiieRect what = g_vm - > vmdisplay - > getDirtyRect ( ) ;
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm - > vmdisplay - > didRedraw ( ) ;
g_display - > blit ( what ) ;
}
g_printer - > update ( ) ;
g_keyboard - > maintainKeyboard ( ) ;
g_display - > drawBatteryStatus ( 100 ) ;
2017-02-27 14:35:49 +00:00
// calculate FPS & dynamically step up/down as necessary
2017-02-26 16:00:41 +00:00
static time_t startAt = time ( NULL ) ;
static uint32_t loopCount = 0 ;
loopCount + + ;
2017-02-27 14:35:49 +00:00
uint32_t lenSecs = time ( NULL ) - startAt ;
if ( lenSecs > = 5 ) {
float fps = loopCount / lenSecs ;
2017-02-28 13:15:11 +00:00
2017-02-27 14:35:49 +00:00
# ifdef SHOWFPS
2017-02-26 16:00:41 +00:00
char buf [ 25 ] ;
2017-02-28 13:15:11 +00:00
sprintf ( buf , " %f FPS [delay %u] " , fps , usleepcycles ) ;
2017-02-26 16:00:41 +00:00
g_display - > debugMsg ( buf ) ;
2017-02-27 14:35:49 +00:00
# endif
2017-02-28 13:15:11 +00:00
2017-02-27 14:35:49 +00:00
if ( fps > 60 ) {
usleepcycles * = 2 ;
} else if ( fps < 40 ) {
usleepcycles / = 2 ;
}
// reset the counter & we'll adjust again in 5 seconds
2017-02-27 13:11:59 +00:00
loopCount = 0 ;
2017-02-27 14:35:49 +00:00
startAt = time ( NULL ) ;
2017-02-26 16:00:41 +00:00
}
2017-02-27 14:35:49 +00:00
if ( usleepcycles > = 2 ) {
usleep ( usleepcycles ) ;
}
2017-02-26 16:00:41 +00:00
# ifdef SHOWPC
{
char buf [ 25 ] ;
sprintf ( buf , " %X " , g_cpu - > pc ) ;
g_display - > debugMsg ( buf ) ;
}
# endif
# ifdef SHOWMEMPAGE
{
char buf [ 40 ] ;
sprintf ( buf , " AUX %c/%c BNK %d BSR %c/%c ZP %c 80 %c INT %c " ,
g_vm - > auxRamRead ? ' R ' : ' _ ' ,
g_vm - > auxRamWrite ? ' W ' : ' _ ' ,
g_vm - > bank1 ,
g_vm - > readbsr ? ' R ' : ' _ ' ,
g_vm - > writebsr ? ' W ' : ' _ ' ,
g_vm - > altzp ? ' Y ' : ' _ ' ,
g_vm - > _80store ? ' Y ' : ' _ ' ,
g_vm - > intcxrom ? ' Y ' : ' _ ' ) ;
g_display - > debugMsg ( buf ) ;
}
# endif
}
}