/* ========= EMULATE.C ========= Handle emulation functions -- - Emulation loop - Stateflag handling - Interrupt functions */ #define __EMULATE__ //#include "winv9t9.h" #include #include #include #include #include #include #if __MWERKS__ #include #else #include #include #endif #include "v9t9_common.h" #include "v9t9.h" #include "9900.h" #include "grom.h" #include "vdp.h" #include "video.h" #include "sound.h" #include "cru.h" #include "roms.h" #include "keyboard.h" #include "emulate.h" #include "timer.h" #include "debugger.h" #include "memory.h" #include "command.h" #include "system.h" #include "9900st.h" #include "opcode_callbacks.h" #include "speech.h" #define _L LOG_CPU | LOG_INFO u32 features; u32 stateflag; /* * The emulator functions from the perspective of * a 3 MHz clock, which drives the instruction speed of * the processor, and secondarily, the 9901 timer, which * is derived from it. */ /* Variables for controlling a "real time" emulation of the 9900 processor. Each call to execute() sets an estimated cycle count for the instruction and parameters in "instcycles". We waste time in 1/BASE_EMULATOR_HZ second quanta to maintain the appearance of a 3.0 MHz clock. */ int realtime = 0; u32 baseclockhz = 3300000; u32 instcycles; // cycles for each instruction u64 targetcycles; // target # cycles to be executed per second u64 totaltargetcycles; // total # target cycles expected u64 currentcycles = 0; // current cycles per 1/BASE_EMULATOR_HZ second u64 totalcurrentcycles; // total # current cycles executed u32 currenttime; /* time, [0..baseclockhz / BASE_EMULATOR_HZ) */ u64 totalticks; /* total number of BASE_EMULATOR_HZ events passed */ u32 executed; /* # instructions executed in this tick */ u64 totalexecuted; /* total # executed */ /* Old way of controlling time */ unsigned long delaybetweeninstructions = 0; u64 delaycycles = 0; // delay cycles used to maintain time per 1/BASE_EMULATOR_HZ int allow_debug_interrupts = 0; // interrupt processor while debugging /////////// /* char *modulespath; char *romspath; char cpuromfilename[OS_NAMESIZE] = "994arom.bin"; char cpugromfilename[OS_NAMESIZE] = "994agrom.bin"; char modulegrom[OS_NAMESIZE] = ""; char modulerom1[OS_NAMESIZE] = ""; char modulerom2[OS_NAMESIZE] = ""; */ static void inserthacks(void) { if (!realtime && MEMORY_READ_WORD(0x496) == 0x45B) { logger(_L | 0, "inserting keyboard delay hack\n"); MEMORY_WRITE_WORD(0x496, OP_KEYSLOW); } } void execution_pause(bool enable) { if (enable) { stateflag |= ST_PAUSE; } else { stateflag &= ~ST_PAUSE; } system_execution_paused(execution_paused()); } #if 0 static sigjmp_buf bogocalc; static void stop_bogo(int unused) { siglongjmp(bogocalc, 1); } static void calibrate_processor(void) { /* Calibrate processor. */ unsigned char flag = 0; long long cnt; int tmtag = TM_UniqueTag(); int killtimer = 0; int origtime; logger(_L | LOG_USER, "Calibrating processor... "); stateflag = 0; killtimer = TM_Start(); origtime = TM_GetTicks(); while (origtime == TM_GetTicks()); if (sigsetjmp(bogocalc, ~0) == 0) { TM_SetEvent(tmtag, TM_HZ * BASE_EMULATOR_HZ, 0, TM_FUNC, stop_bogo); cnt = ~0; while (--cnt && !stateflag); } TM_ResetEvent(tmtag); if (killtimer) TM_Stop(); bogocycles = (~cnt) / BASE_EMULATOR_HZ; logger(_L | LOG_USER | 0, "%Ld bogocycles.\n\n", bogocycles); } #endif /*************************************************/ static bool calibrated_processor; static vmResult emulate9900_restart(void); static vmResult emulate9900_restop(void); static DECL_SYMBOL_ACTION(emulate_reset_computer) { /* Upon restart, save volatile RAMs and reload all ROMs, in case they were overwritten by a bug or something. ;) */ if (task == csa_WRITE) { memory_volatile_save(); memory_complete_load(); stateflag &= ~ST_INTERRUPT; reset9901(); init9900(); hold9900pin(INTPIN_RESET); contextswitch(0); } return 1; } static DECL_SYMBOL_ACTION(emulate_debugger_enable) { if (task == csa_READ) { if (iter) return 0; command_arg_set_num(SYM_ARG_1st, debugger_enabled()); } else { int val; command_arg_get_num(SYM_ARG_1st, &val); debugger_enable(val); } return 1; } static DECL_SYMBOL_ACTION(emulate_execution_pause) { if (task == csa_READ) { if (iter) return 0; command_arg_set_num(SYM_ARG_1st, execution_paused()); } else { int val; command_arg_get_num(SYM_ARG_1st, &val); execution_pause(val); } return 1; } static DECL_SYMBOL_ACTION(emulate_single_step) { execution_pause(true); execution_pause(false); stateflag |= ST_SINGLESTEP; return 1; } static DECL_SYMBOL_ACTION(emulate_change_clock_speed) { if (task == csa_WRITE) { emulate9900_restart(); } return 1; } static DECL_SYMBOL_ACTION(emulate_set_pc) { int val; if (task == csa_READ) if (!iter) { command_arg_set_num(sym->args, pc); return 1; } else { return 0; } command_arg_get_num(sym->args, &val); if (verifypc(val)) { setandverifypc(val); } else { logger(_L | LOG_ERROR, "Cannot set PC to %04X, ignoring\n", val); } return 1; } static DECL_SYMBOL_ACTION(emulate_set_wp) { int val; if (task == csa_READ) if (!iter) { command_arg_set_num(sym->args, wp); return 1; } else { return 0; } command_arg_get_num(sym->args, &val); if (verifywp(val)) { setandverifywp(val); } else { logger(_L | LOG_ERROR, "Cannot set WP to %04X, ignoring\n", val); } return 1; } static DECL_SYMBOL_ACTION(emulate_set_st) { int val; if (task == csa_READ) if (!iter) { statusto9900(); command_arg_set_num(sym->args, status); return 1; } else { return 0; } command_arg_get_num(sym->args, &val); T9900tostatus(val); change9900intmask(status & 0xf); return 1; } #define HEXNUM(x) (((x)>='0' && (x)<='9') ? (x)-'0' : tolower(x)-'a'+10) #define NUMHEX(x) ((x) < 10 ? (x)+'0' : (x)-10+'A') // Utility function to make a hex string for machine_state routines // If 'hex' is NULL, we xmalloc() space and return a pointer to it, // else the buffer at 'hex' should have size*2+1 bytes available. char *emulate_bin2hex(u8 *mem, char *hex, int size) { char *str; if (!hex) hex = (char *)xmalloc(size*2+1); str = hex; while (size--) { u8 byt = *mem++; *str++ = NUMHEX(byt>>4); *str++ = NUMHEX(byt&0xf); } *str = 0; return hex; } // Utility for converting hex string to binary. // 'size' is the upper limit on the expected size of the // string. If 'hex' is smaller, we zero-fill the memory. void emulate_hex2bin(char *hex, u8 *mem, int size) { while (*hex && *(hex+1) && size) { u8 byt = (HEXNUM(*hex) << 4) + HEXNUM(*(hex+1)); hex += 2; *mem++ = byt; size--; } while (size--) { *mem++ = 0; } } /* * Attempt to RLE compress 'len' bytes of 'data' * into 'compress' but not using more than 'maxlen' bytes. * * Returns length of 'compress' or 0 if compression failed. */ int rle_compress(u8 *data, int len, u8 *compress, int maxlen) { u8 curseglen; u8 curtok; int curlen = 0; while (len && curlen < maxlen) { curtok = *data; // use 0xc0-0xff as repeat counts 1-64 curseglen = 1; while (curseglen < len && data[curseglen] == curtok && curseglen < 64) { curseglen++; } // due to using 0xc0 through 0xff as repeat counts, we must // allow a repeat of one for these tokens themselves. // However, we can only use RLE when there are two bytes // available in the output buffer. if ((curtok >= 0xc0 || curseglen > 2) && curlen <= maxlen - 2) { *compress++ = 0xC0 + curseglen - 1; *compress++ = curtok; curlen += 2; } else { *compress++ = curtok; curlen++; curseglen = 1; } data += curseglen; len -= curseglen; } // whoops, ran out of output space before finishing the input if (len || (len == 0 && curlen == maxlen)) return 0; else return curlen; } /* * Attempt to RLE uncompress 'len' bytes of 'data' * into 'uncompress' using at most 'maxlen' bytes. * * Returns zero if uncompressed data cannot fit, else * the length of that data. */ int rle_uncompress(u8 *data, int len, u8 *uncompress, int maxlen) { u8 curseglen; u8 curtok; int curlen = 0; while (len && curlen < maxlen) { curtok = *data++; len--; // a repeat count and available data? // 0xc0==1, and 0xff==64 if (curtok >= 0xC0 && len > 0) { curseglen = curtok - 0xC0 + 1; curtok = *data++; len--; memset(uncompress, curtok, curseglen > (maxlen - curlen) ? (maxlen - curlen) : curseglen); uncompress += curseglen; curlen += curseglen; } else { *uncompress++ = curtok; curlen++; } } // unused compressed data? if (len) return 0; else return curlen; } static DECL_SYMBOL_ACTION(emulate_set_mem) { char *str, *typ; int addr; char hexstr[256]; char memtyp[4]; mem_domain dmn; u8 vector[64]; u8 rle[64]; int vectorlen, rlelen, idx; if (task == csa_READ) { static int saveaddr; u8 verify[64]; /* Write memory to a hex string, CPU RAM followed by VDP RAM */ if (!iter) saveaddr = 0; addr = saveaddr; // spit out 64 bytes at a time if (addr < 65536) { /* Note: this may not seem right, when we could have a * MemoryEntry for a MEMENT_STORED RAM block covering * this area already, but this preserves the state of * that RAM at the time of the session being saved. */ mrstruct *mrarea; while (!HAS_RAM_ACCESS(md_cpu, addr)) addr = (addr & ~4095) + 4096; dmn = md_cpu; mrarea = THE_AREA(md_cpu, addr); memtyp[0] = 'C'; memtyp[1] = 0; // not compressed memtyp[2] = 0; } else if (addr < 65536 + 16384) { addr -= 65536; memtyp[0] = 'V'; memtyp[1] = 0; // not compressed memtyp[2] = 0; dmn = md_video; } else { return 0; } command_arg_set_num(sym->args->next, addr); /* Read 64 bytes from memory */ vectorlen = 0; do { vector[vectorlen++] = domain_read_byte(dmn, addr); addr++; } while (addr & 63); /* RLE compress the vector */ rlelen = rle_compress(vector, vectorlen, rle, sizeof(rle)); /* Returns zero if it couldn't compress any */ if (rlelen) { memtyp[1] = '*'; // compressed rle_uncompress(rle, rlelen, verify, sizeof(verify)); if (memcmp(vector, verify, vectorlen)) { logger(_L|LOG_FATAL, "Mismatched on RLE compress:\n" "In : %s\n" "Out: %s\n" "Ver: %s\n", emulate_bin2hex(vector, 0L, vectorlen), emulate_bin2hex(rle, 0L, rlelen), emulate_bin2hex(verify, 0L, sizeof(verify))); } } else { rlelen = vectorlen; memcpy(rle, vector, rlelen); } str = hexstr; idx = 0; while (idx < rlelen) { u8 byt; byt = rle[idx++]; *str++ = NUMHEX(byt>>4); *str++ = NUMHEX(byt&0xf); } *str = 0; saveaddr = addr + (dmn == md_video ? 65536 : 0); command_arg_set_string(sym->args, memtyp); command_arg_set_string(sym->args->next->next, hexstr); return 1; } // write memory command_arg_get_string(sym->args, &typ); command_arg_get_num(sym->args->next, &addr); addr &= 0xffff; command_arg_get_string(sym->args->next->next, &str); if (toupper(*typ) == 'C' || *typ == '>') dmn = md_cpu; else if (toupper(*typ) == 'V') dmn = md_video; else if (toupper(*typ) == 'G') dmn = md_graphics; else if (toupper(*typ) == 'S') dmn = md_speech; else { logger(_L | LOG_ERROR | LOG_USER, "Can't access memory type '%s'\n", typ); return 0; } // decode string, assuming it's rle-encoded rlelen = 0; while (*str) { u8 byt = (HEXNUM(*str) << 4) + HEXNUM(*(str+1)); rle[rlelen++] = byt; str+=2; } // second char of memory type indicates compression if (typ[1] && typ[1] == '*') { vectorlen = rle_uncompress(rle, rlelen, vector, sizeof(vector)); } else { memcpy(vector, rle, rlelen); vectorlen = rlelen; } idx = 0; while (idx < vectorlen) { domain_write_byte(dmn, addr, vector[idx++]); addr++; } return 1; } static DECL_SYMBOL_ACTION(do_vdp_mmio_set_addr) { int num; if (task == csa_READ) { if (iter) return 0; command_arg_set_num(SYM_ARG_1st, vdp_mmio_get_addr()); return 1; } else { command_arg_get_num(SYM_ARG_1st, &num); vdp_mmio_set_addr(num); return 1; } } static DECL_SYMBOL_ACTION(do_grom_mmio_set_addr) { int num; if (task == csa_READ) { if (iter) return 0; command_arg_set_num(SYM_ARG_1st, grom_mmio_get_addr()); return 1; } else { command_arg_get_num(SYM_ARG_1st, &num); grom_mmio_set_addr(num); return 1; } } static DECL_SYMBOL_ACTION(do_speech_mmio_set_addr) { int num; if (task == csa_READ) { if (iter) return 0; command_arg_set_num(SYM_ARG_1st, speech_mmio_get_addr()); return 1; } else { command_arg_get_num(SYM_ARG_1st, &num); speech_mmio_set_addr(num); return 1; } } /***************************************/ static int emulate_tag, slowdown_tag; void EmulateEvent(void); static vmResult emulate9900_detect(void) { return vmOk; } static vmResult emulate9900_init(void) { command_symbol_table *internal = command_symbol_table_new("Internal Emulator Commands", "These options affect the mechanics of 99/4A emulation", command_symbol_new ("RealTimeEmulation", "Toggle real-time emulation mode (attempts to operate at the " "same speed of the original 9900)", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_num ("on|off", "on: execute at 9900 speed; " "off: execute with DelayBetweenInstructions", NULL, ARG_NUM(realtime), NULL /* next */ ) , command_symbol_new ("DelayBetweenInstructions", "Sets a constant delay between instructions (when not in real-time mode)", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_num ("cycles", "number of cycles to count", NULL, ARG_NUM(delaybetweeninstructions), NULL /* next */ ) , command_symbol_new ("ResetComputer", "Resets the 99/4A via RESET", c_DONT_SAVE, emulate_reset_computer, NULL /* ret */ , NULL /* args */ , command_symbol_new ("PauseComputer", "Pauses emulation of the 99/4A", c_DONT_SAVE, emulate_execution_pause /* action */ , RET_FIRST_ARG, command_arg_new_num ("on|off", NULL /* help */, NULL /* action */ , NEW_ARG_NUM(bool), NULL /* next */ ) , command_symbol_new ("Debugger", "Enable the debugger/tracer", c_DYNAMIC, emulate_debugger_enable /* action */ , RET_FIRST_ARG, command_arg_new_num ("on|off", NULL /* help */, NULL /* action */ , NEW_ARG_NUM(bool), NULL /* next */ ) , command_symbol_new ("AllowDebuggingInterrupts", "Allow interrupts to occur while debugging", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_num ("on|off", NULL /* help */, NULL /* action */ , ARG_NUM(allow_debug_interrupts), NULL /* next */ ) , command_symbol_new ("SingleStep", "Execute one instruction and stop", c_DONT_SAVE, emulate_single_step /* action */ , RET_FIRST_ARG, NULL /* args */ , command_symbol_new ("BaseClockHZ", "Set HZ speed base clock (usually 3.0 MHz)", c_STATIC, emulate_change_clock_speed, RET_FIRST_ARG, command_arg_new_num ("hertz", "number of times per second", NULL /* action */, ARG_NUM(baseclockhz), NULL /* next */), NULL /* next */ )))))))), NULL /* sub */ , NULL /* next */ ); command_symbol_table *state = command_symbol_table_new("Memory / Debugging Commands", "These options allow you to change the running state of the virtual machine", command_symbol_new("ProgramCounter|PC", "Set the program counter", c_DYNAMIC|c_SESSION_ONLY, emulate_set_pc, RET_FIRST_ARG, command_arg_new_num ("address", "illegal addresses will be ignored", NULL /* action */, NEW_ARG_NUM(u16), NULL /* next */) , command_symbol_new ("WorkspacePointer|WP", "Set the workspace pointer", c_DYNAMIC|c_SESSION_ONLY, emulate_set_wp, RET_FIRST_ARG, command_arg_new_num ("address", "illegal addresses will be ignored", NULL /* action */, NEW_ARG_NUM(u16), NULL /* next */) , command_symbol_new ("StatusRegister|ST", "Set the status register", c_DYNAMIC|c_SESSION_ONLY, emulate_set_st, RET_FIRST_ARG, command_arg_new_num ("address", "illegal addresses will be ignored", NULL /* action */, NEW_ARG_NUM(u16), NULL /* next */) , command_symbol_new ("VDPAddress", "Set the VDP address register", c_DYNAMIC|c_SESSION_ONLY, do_vdp_mmio_set_addr /* action */, RET_FIRST_ARG, command_arg_new_num ("address", "0->3FFF sets read address, " ">4000->7FFF sets write address, " ">8000->87FF sets VDP write register", NULL /* action */, NEW_ARG_NUM(u16), NULL /* next */) , command_symbol_new ("VDPRegister", "Set a VDP register", c_DYNAMIC|c_SESSION_ONLY, vdp_set_register, NULL, command_arg_new_num ("register", "register number, 0-7", NULL /* action */, NEW_ARG_NUM(u8), command_arg_new_num ("value", "value for register", NULL /* action */, NEW_ARG_NUM(u8), NULL /* next */)) , command_symbol_new ("VDPReadAhead", "Set VDP read-ahead value", c_DYNAMIC|c_SESSION_ONLY, vdp_set_read_ahead, NULL, command_arg_new_num ("value", "value for register", NULL /* action */, NEW_ARG_NUM(u8), NULL /* next */) , command_symbol_new ("VDPAddrFlag", "Set VDP address", c_DYNAMIC|c_SESSION_ONLY, vdp_set_addr_flag, NULL, command_arg_new_num ("register", "register number, 0-7", NULL /* action */, NEW_ARG_NUM(u8), command_arg_new_num ("value", "value for register", NULL /* action */, NEW_ARG_NUM(u8), NULL /* next */)) , command_symbol_new ("GROMAddress", "Set the GROM address register", c_DYNAMIC|c_SESSION_ONLY, do_grom_mmio_set_addr /* action */, RET_FIRST_ARG, command_arg_new_num ("address", NULL, NULL /* action */, NEW_ARG_NUM(u16), NULL /* next */) , command_symbol_new ("SpeechState", NULL /* help */, c_DYNAMIC|c_SESSION_ONLY, speech_machine_state, RET_FIRST_ARG, command_arg_new_string("sp", NULL /* help */, NULL /* action */, NEW_ARG_NEW_STRBUF, command_arg_new_string("lpc", NULL /* help */, NULL /* action */, NEW_ARG_NEW_STRBUF, NULL /* next */)), command_symbol_new ("HW9901State", NULL /* help */, c_DYNAMIC|c_SESSION_ONLY, hw9901_machine_state, RET_FIRST_ARG, command_arg_new_string("hw9901", NULL /* help */, NULL /* action */, NEW_ARG_NEW_STRBUF, command_arg_new_string("audiogate", NULL /* help */, NULL /* action */, NEW_ARG_NEW_STRBUF, NULL /* next */)), command_symbol_new ("SetRAM", "Change contents of RAM", c_DYNAMIC|c_SESSION_ONLY, emulate_set_mem, NULL /* ret */, command_arg_new_string ("type", "memory type: C/V/G/S", NULL /* action */, NEW_ARG_STR (16), command_arg_new_num ("address", "illegal addresses will be ignored", NULL /* action */, NEW_ARG_NUM(u16), command_arg_new_string ("string", "hexadecimal string", NULL /* action */, NEW_ARG_NEW_STRBUF, NULL /* next */))) , NULL /* next */))))))))))), NULL /* sub */, NULL /* next */ ); command_symbol_table_add_subtable(universe, internal); command_symbol_table_add_subtable(universe, state); delaybetweeninstructions = 0; #if 0 bogocycles = 0; calibrate_processor(); #endif emulate_tag = TM_UniqueTag(); slowdown_tag = TM_UniqueTag(); /* one shot */ totalticks = 0; totalexecuted = 0; executed = 0; memory_init(); return vmOk; } static vmResult emulate9900_enable(void) { return vmOk; } static vmResult emulate9900_disable(void) { return vmOk; } static vmResult emulate9900_term(void) { return vmOk; } static vmResult emulate9900_restart(void) { targetcycles = baseclockhz / BASE_EMULATOR_HZ; currenttime = 0; totalcurrentcycles = totalticks = totalexecuted = executed = 0; TM_ResetEvent(emulate_tag); TM_SetEvent(emulate_tag, TM_HZ * 100 / BASE_EMULATOR_HZ, 0, TM_FUNC | TM_REPEAT, EmulateEvent); calibrated_processor = false; command_parse_text("LoadMemory\n"); // be sure our register pointer is set up setandverifywp(wp); return vmOk; } static vmResult emulate9900_restop(void) { TM_ResetEvent(emulate_tag); command_parse_text("SaveMemory\n"); return vmOk; } /*********************************************************/ static int keep_going; int handlestateflag(void); static void emulate9900_stop(void) { keep_going = 0; } static long emulate_stopwatch; static int emulate9900_execute(void) { int ret; // if we're already speeding along... if (!stateflag && realtime && currentcycles >= targetcycles) return em_TooFast; emulate_stopwatch = TM_GetTicks(); while (1) { if (debugger_check_breakpoint(pc)) { debugger_enable(1); stateflag |= ST_SINGLESTEP; } if (stateflag && (ret = handlestateflag()) != em_KeepGoing) { break; } // execute() updates this for the current instruction instcycles = 0; execute(fetch()); executed++; /* Check for 9901 timer, which is keyed on baseclockhz / 64 */ if ((currentcycles & ~63) != ((currentcycles + instcycles) & ~63)) { // tick at the baseclockhz/64 rate; 9901.c will handle // correlating this to the clock interrupt if (currenttime < baseclockhz / 64 / BASE_EMULATOR_HZ) { currenttime++; handle9901tick(); } } currentcycles += instcycles; // store new interrupt level, if any // (prevents an interrupt right after RTWP from one) change9900intmask(status & 0xf); if (!realtime) { ret = delaybetweeninstructions; while (ret--) ; } else if (currentcycles >= targetcycles) { ret = em_TooFast; break; } // finally, don't take too long here if (TM_GetTicks() > emulate_stopwatch + TM_HZ/10) { ret = em_Interrupted; break; } } return ret; } /*********************************************/ static void getcommands(void) { stateflag |= ST_INTERACTIVE; system_getcommands(); stateflag &= ~ST_INTERACTIVE; } int handlestateflag(void) { int ret = em_KeepGoing; logger(_L | L_2, "handlestateflag %X\n", stateflag); // set when we want to quit if (stateflag & ST_TERMINATE) return em_Quitting; // set when we want to enter commands while (stateflag & ST_INTERACTIVE) { getcommands(); ret = em_Interrupted; } // set to force emulation to halt if (stateflag & ST_STOP) { stateflag &= ~ST_STOP; ret = em_Interrupted; return ret; } if (stateflag & ST_SINGLESTEP) { stateflag &= ~(ST_SINGLESTEP | ST_STOP); stateflag |= ST_PAUSE; } else if (stateflag & ST_PAUSE) return em_TooFast; // set when we want to monitor execution if (stateflag & ST_DEBUG) { debugger(); } // any sort of interrupt that sets intpins9900 if ((stateflag & ST_INTERRUPT)) { // non-maskable if (intpins9900 & INTPIN_LOAD) { intpins9900 &= ~INTPIN_LOAD; logger(_L | 0, "**** NMI ****"); contextswitch(0xfffc); instcycles += 22; execute(fetch()); } else // non-maskable (?) if (intpins9900 & INTPIN_RESET) { intpins9900 &= ~INTPIN_RESET; logger(_L | 0, "**** RESET ****\n"); contextswitch(0); instcycles += 26; execute(fetch()); } else // maskable if (intpins9900 & INTPIN_INTREQ) { u16 highmask = 1 << (intlevel9900 + 1); // 99/4A console hardcodes all interrupts as level 1, // so if any are available, level 1 is it. if (intlevel9900 && read9901int() && (!(stateflag & ST_DEBUG) || (allow_debug_interrupts))) { intpins9900 &= ~INTPIN_INTREQ; contextswitch(0x4); intlevel9900--; instcycles += 22; execute(fetch()); } } else intpins9900 = 0; // invalid if (!intpins9900) stateflag &= ~ST_INTERRUPT; } return ret; } /* Ensure that a restore of machine state succeeds without a reboot */ void emulate_setup_for_restore(void) { // video module needs a jab TM_Start(); vdpcompleteredraw(); VIDEO(resetfromblank,()); intpins9900 &= ~INTPIN_RESET; stateflag &= ~(ST_REBOOT | ST_INTERRUPT); } /* Always called BASE_EMULATOR_HZ times a second. */ void EmulateEvent(void) { totalcurrentcycles += currentcycles; totaltargetcycles += targetcycles; currentcycles = 0; currenttime = 0; totalexecuted += executed; executed = 0; totalticks++; if (totalticks % (BASE_EMULATOR_HZ * 10) == 0) { report_status(STATUS_CYCLES_SECOND, (long)(totalcurrentcycles / (totalticks / BASE_EMULATOR_HZ)), (long)(totalexecuted / (totalticks / BASE_EMULATOR_HZ))); executed = 0; } } static vmCPUModule myCPUModule = { 2, emulate9900_execute, emulate9900_stop }; vmModule emulate9900CPU = { 3, "9900 emulator", "cpu9900", vmTypeCPU, vmFlagsExclusive, emulate9900_detect, emulate9900_init, emulate9900_term, emulate9900_enable, emulate9900_disable, emulate9900_restart, emulate9900_restop, {(vmGenericModule *) & myCPUModule} }; /////////////// static int round, timeout, slowing; static u8 lastkey = 0xff; static volatile int resume; void emulate_keyslow(void) { u8 curkey; logger(_L | L_2, "emulate_keyslow; slowing=%d, resume=%d\n", slowing, resume); if (pc == 0x496 + 2) { if (slowing && resume) { resume = slowing = 0; pc = register (11); return; } // is the same key pressed? curkey = (u8) memory_read_byte(0x8375); if (curkey != 0xff && curkey == lastkey) { logger(_L | L_2, "got a keypress [%02X]\n", curkey); if (!slowing) { logger(_L | L_2, "slowing down...\n"); round = (round + 1) & 0x7; if (!round) { resume = 0; slowing = 1; TM_SetEvent(slowdown_tag, 1 * 100 / TM_HZ, 0, 0, &resume); } else { timeout = 1000; slowing = 1; resume = 0; } } else if (round) { if (!timeout--) resume = 1; } pc -= 2; } else { if (curkey != 0xff) lastkey = curkey; slowing = 0; pc = register (11); } } }