mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 22:05:38 +00:00
.. | ||
Make68K.c | ||
README.TXT | ||
Turbo68K.h |
###### ## ## ###### ###### ##### #### ##### ### ## # ## # ## ## ## ## ## ### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##### ##### ## ## ###### ##### #### ## ## ## #### ## ## ## ## ## ## ## ## #### ## ## ## ## ## ## ### ## ## ## ## ## ## ## ## #### #### ### ## ###### ##### ##### ##### ### ## [ Turbo68K Readme ] Copyright 2000-2002 Bart Trzynadlowski This is the documentation for Turbo68K Version 0.6, please read it. Terms of use are explained a little further below, it is required that you read and abide by them in order to use this software. Turbo68K is a Motorola 680X0 emulator. It currently emulates the 68000 and 68010 microprocessors. It was written for Intel X86-based (Pentium or better) systems. Originally designed for Genital it is now available for public use. Contact Bart Trzynadlowski: Email: trzy@mailandnews.com WWW: http://trzy.overclocked.org http://trzy.overclocked.org/turbo68k ------------------- 0. Terms of Use ------------------- Turbo68K is Copyright 2000, 2001, 2002 Bart Trzynadlowski. "Turbo68K" refers to: MAKE68K.C, TURBO68K.H, README.TXT, any binaries compiled from those files, any source code emitted from MAKE68K.C, and any object file created from such code. To use Turbo68K, you must agree to the following: - Turbo68K shall be distributed freely, unmodified, and intact. - Charging money, goods, or services for Turbo68K is forbidden. - Claiming ownership or authorship of Turbo68K is forbidden. - Turbo68K may not be distributed in any medium for which money, goods, or services are solicited. - The author, Bart Trzynadlowski, will not be held liable for damages. - Credit must be given to the author either in the program itself (where the user can see it) or in its documentation. You are encouraged to contact Bart Trzynadlowski if you wish to use Turbo68K in a commercial product (to negotiate licensing.) Bart Trzynadlowski reserves the right to use Turbo68K in any way he sees fit. If you do not agree with all of these terms, please remove Turbo68K from your possession. ------------------------ 1. Table of Contents ------------------------ 0. Terms of Use 1. Table of Contents 2. Making Turbo68K 3. Command Line Options 4. Using Turbo68K 4.1 Basic Data Types 4.2 Definitions 4.3 Context 4.4 Instruction Fetching 4.5 Data Access 4.6 PC-Relative Reads 4.7 Interrupts 4.8 Byte Swapped Memory 4.9 The RESET and BKPT Instructions 4.10 Initializing and Resetting Turbo68K 4.11 Running the Emulator 5. Function Reference 6. Hints and Tips 6.1 Alternative Identifiers 6.2 Multiple Processors 6.3 Maximizing Speed and Minimizing Size 6.4 Sega Genesis TAS Bug 7. Additional Notes 7.1 Change History 7.2 Un-emulated Features 7.3 Errata 7.4 Bibliography 8. Special Thanks ---------------------- 2. Making Turbo68K ---------------------- The Turbo68K 680X0 emulator is usually distributed in a compressed archive, simply extract it and you should have MAKE68K.C, TURBO68K.H, and this README.TXT file. The first step is to get the source code emitter, Make68K, up and running. It should compile with any 32-bit C compiler (compilers other than gcc will often produce warnings, these can be ignored.) The following have been confirmed: - DJGPP (gcc v2.8.1) [ MS-DOS ] - gcc v2.7.2.1 [ FreeBSD ] - Microsoft VisualC++ 6.0 Professional Edition [ Windows 95 ] - WATCOM C/C++32 Version 10.6 [ MS-DOS ] If you get link errors concerning pow(), make sure to include the appropriate math library. Try "-lm" if you are using GNU tools. Once Make68K has been built, run it with the name of the file you wish to emit on the command line. For example: make68k turbo68k.asm Make68K can take a number of options. Try "make68k -h" to see a list. Section 3 of this text describes them in more detail. Assemble the emitted file with NASM. Version 0.97 or better should work, 0.98 is known to work. Turbo68K contains identifiers with leading and trailing underscores as well as without any, so all the object formats supported by NASM should work. Remember to include TURBO68K.H to use Turbo68K. --------------------------- 3. Command Line Options --------------------------- Turbo68K can be tailored in a variety of ways to suit your needs through the use of the following command line options: -mpu <type> Processor type. Valid types are 68000 and 68010. [Default=68000] -addr <bits> Address bus width. This simulates the specified number of address lines by cutting off any unsupported address bits when accessing memory or updating the PC. The 68000 and 68010 have a 24-bit address bus. [Default=24] -illegal Emulates illegal instruction traps. Line 1010 and Line 1111 emulator exceptions are processed as is the normal illegal instruction exception. [Default] -noillegal Does not emulate illegal instruction traps but instead returns an error from Turbo68KRun(). The ILLEGAL opcode still generates an illegal instruction trap regardless of whether -illegal or -noillegal has been specified. -dummyread Emulates dummy reads for the CLR, Scc, and unprivileged "MOVE from SR" instructions. These instructions perform a read (and do not make use of the data) before writing. Some systems may require this for accurate emulation (for example: some systems will lock up when write-only locations are read.) This is only available if the processor type is 68000. [Default] -nodummyread Does not emulate dummy reads and instead performs a write immediately. -skip Skips over idle DBRA loops by setting the counter register to 0xFFFF, adjusting the timer appropriately, and moving to the next instruction. -noskip Does not skip over idle loops. This eliminates problems with compatibility and is recommended. [Default] -brafetch Uses the fetch memory map array to update the PC on Bcc, BRA, BSR, and DBcc instructions. -nobrafetch Bcc, BRA, BSR, and DBcc branches are handled by adding the displacement to the internal normalized PC. This is less safe, in theory, but you should not normally have any problems. It is also faster and generates less code than -brafetch. [Default] -pcfetch PC-relative reads are handled through a special fetch map. Please read section 4.6 for more information. -nopcfetch PC-relative reads are treated as normal data reads. [Default] -multiaddr Separate address spaces for supervisor and user. This has not been extensively tested, so let me know how it works. [Default] -singleaddr Single address space for supervisor and user. -defmap Definable memory map arrays are used to handle reading and writing to the address space. [Default] -handler Uses handlers for reading/writing bytes, words, and long- words. The fetch memory map array must still be set up. -stackcall Use stack-based calling conventions. [Default] -regcall Use register-based calling conventions. EAX contains the first argument, the second is in EDX. -id <string> Adds the specified string to the beginning of all identifiers. Up to 16 characters are allowed. See section 6.1 for more information on this. Please read further to learn more about the details of some of these options. --------------------- 4. Using Turbo68K --------------------- A general overview of Turbo68K is provided here. More information on certain topics is provided throughout the document. 4.1 Basic Data Types The following data types are provided by Turbo68K: TURBO68K_UINT32 Unsigned 32-bit TURBO68K_INT32 Signed 32-bit TURBO68K_UINT16 Unsigned 16-bit TURBO68K_INT16 Signed 16-bit TURBO68K_UINT8 Unsigned 8-bit TURBO68K_INT8 Signed 8-bit 4.2 Definitions Turbo68K functions return and accept defined arguments. Use these instead of hard-coded values. Different functions return and accept different values, so make sure to check with the function reference first. TURBO68K_NULL 0 TURBO68K_OKAY Success TURBO68K_SUPERVISOR Supervisor address space TURBO68K_USER User address space TURBO68K_ERROR_FETCH Fetch error (PC out of bounds) TURBO68K_ERROR_INVINST Invalid instruction TURBO68K_ERROR_INTLEVEL Invalid interrupt level was specified TURBO68K_ERROR_INTVECTOR Invalid interrupt vector was specified TURBO68K_ERROR_INTPENDING Specified interrupt level already pending TURBO68K_UNINITIALIZED Uninitialized interrupt vector TURBO68K_SPURIOUS Spurious interrupt vector TURBO68K_AUTOVECTOR Autovectored interrupt 4.3 Context Processor contexts are special structures which contain all the data necessary to manage the emulated 680X0. The procedure for emulating a 680X0 processor with Turbo68K involves setting up a context, mapping it in, emulating some code, and perhaps mapping the context out. Context Definitions: -------------------- struct TURBO68K_CONTEXT_68000 { void *fetch; void *read_byte, *read_word, *read_long; void *write_byte, *write_word, *write_long; void *super_fetch; void *super_read_byte, *super_read_word, *super_read_long; void *super_write_byte, *super_write_word, *super_write_long; void *user_fetch; void *user_read_byte, *user_read_word, *user_read_long; void *user_write_byte, *user_write_word, *user_write_long; TURBO68K_UINT32 intr[8], cycles, remaining; TURBO68K_UINT32 d[8], a[8], sp, sr, pc, status; void *InterruptAcknowledge; void *Reset; }; struct TURBO68K_CONTEXT_68010 { void *fetch, *pcfetch; void *read_byte, *read_word, *read_long; void *write_byte, *write_word, *write_long; void *super_fetch, *super_pcfetch; void *super_read_byte, *super_read_word, *super_read_long; void *super_write_byte, *super_write_word, *super_write_long; void *user_fetch, *user_pcfetch; void *user_read_byte, *user_read_word, *user_read_long; void *user_write_byte, *user_write_word, *user_write_long; TURBO68K_UINT32 intr[8], cycles, remaining; TURBO68K_UINT32 d[8], a[8], sp, sr, pc, fc, vbr, status; void *InterruptAcknowledge; void *Reset, *Bkpt; }; If you are emulating different address spaces for supervisor and user modes, the super_ and user_ pointers must be set to point at the respective memory maps. The other memory map pointers (fetch, read_byte, write_byte, etc.) will be managed internally by Turbo68K. If you are only emulating a single address space for both privilege modes, you do not have to set up the super_ and user_ pointers but must set up the others. The "intr" array holds pending interrupt information. Entries 0-6 are for interrupt levels 1-7, they contain the vector of the pending interrupt, or 0 if no interrupt at the given level is pending. Entry 7 contains the number of pending interrupts. You should not modify this yourself, use the interrupt functions. The "cycles" and "remaining" members are used to control execution time. The total amount of request cycles is held in "cycles" and the number left is held in "remaining." Data and address registers are kept in the "d" and "a" array, the PC is kept in "pc", and the SR is kept in "sr" (the low word is used, the upper word should be kept 0.) Whichever stack pointer is not in A7 is stored in "sp." Thus, if the processor is in Supervisor mode, A7 is the SSP and "sp" contains the USP. Turbo68K will only swap these when it has to switch privilege modes internally, such as with a MOVE SR instruction, exception processing, etc. NOTE: If you absolutely must modify the SR outside of Turbo68K (for example, if you are writing a debugger), you have to swap A7 and "sp" manually if the privilege mode changed. For the 68010, "fc" is both SFC (first 3 bits of byte 0) and DFC (first 3 bits of byte 1.) The 68010's VBR is kept in "vbr." Additional information on the processor status is reflected in the "status" member. Its format is: Bit 0: R (Running) flag -- 0 = not running, 1 = running Bit 1: S (Stopped) flag -- 0 = not stopped, 1 = stopped via the STOP instruction Bit 2: I (Interrupt) flag -- 0 = not processing interrupts, 1 = process- ing interrupts (Turbo68KProcessInterrupts()) The "InterruptAcknowledge" pointer points to an interrupt acknowledge handler which is called when interrupts are being processed. If the pointer is set to TURBO68K_NULL, it will not be called. See section 4.7. Finally, the "Reset" pointer points to a Reset handler. If the RESET instruction is executed, this handler will be called. If the pointer is set to TURBO68K_NULL, it will not be called. The "Bkpt" pointer is for the 68010's BKPT instruction. Contexts can be mapped in and out of Turbo68K's internal data space by using Turbo68KSetContext() and Turbo68KGetContext(). Once they are mapped in, no changes will be reflected in your context structures until you map them out. NOTE: You CANNOT access context members while the context is mapped in unless there are functions provided for that purpose (such as Turbo68KSetFetch(), Turbo68KFreeTimeSlice(), Turbo68KReadPC(), etc.) If there is no function for the operation you want to perform on the context, you simply cannot do it until the context is mapped out. You may NOT map contexts in and out from memory handlers while Turbo68K is running. Please consult the function reference for more information. Turbo68K provides an internal context called "turbo68kcontext_68000" if emulating a 68000, or "turbo68kcontext_68010" if emulating a 68010. These are where you copy your context when you use Turbo68KSetContext(). They should only be used if you are emulating one processor. When setting up memory maps, you can modify context pointers only if the context was defined by you. If you wish to use the internal context, you will have to use special functions to perform the same tasks. This is because Turbo68K expects pointers in the internal context to be mangled (for performance reasons.) Turbo68KSetContext()/Turbo68KGetContext() handle this task automatically. Here is an example of defining a context and mapping it in and out: struct TURBO68K_CONTEXT_68000 mycontext; /* ... Set up everything ... */ Turbo68KSetContext(&mycontext); /* ... Run some code ... */ Turbo68KGetContext(&mycontext); 4.4 Instruction Fetching Turbo68K needs to know how to fetch instruction from 680X0 memory space. An array of fetch regions must be set up describing where valid program areas are located. Fetch Region Definition: ------------------------ struct TURBO68K_FETCHREGION { TURBO68K_UINT32 base; TURBO68K_UINT32 limit; TURBO68K_UINT32 ptr; }; The "base" is the start address of the fetch region. The "limit" is the end address. The "ptr" must be set to a host machine pointer value with the base subtracted from it. NOTE: Memory must be byte swapped. See section 4.8 for more information. Here is an example of how a hypothetical system's fetch map might look: struct TURBO68K_FETCHREGION my_fetch[] = { { 0x000000, 0x3fffff, rom - 0x000000 }, { 0xff0000, 0xffffff, ram - 0xff0000 }, { -1, -1, TURBO68K_NULL } } The last element is required to let Turbo68K know where the array ends. To hook the fetch map up to your context, you would code: mycontext.super_fetch = my_fetch; mycontext.user_fetch = my_fetch; If you are only using one unified address space for both supervisor and user modes, all you have to do is: mycontext.fetch = my_fetch; For the internal context, you must use the appropriate functions: Turbo68KSetFetch(my_fetch, TURBO68K_SUPERVISOR); Turbo68KSetFetch(my_fetch, TURBO68K_USER); Or, if one address space is being used: Turbo68KSetFetch(my_fetch, TURBO68K_NULL); /* second arg. ignored */ 4.5 Data Access Data accessing (byte, word, and long word read/writes) can be handled in a number of ways. The first method is handled through definable memory map arrays, somewhat similar to instruction fetching. This is the default method (-defmap.) Data Region Definition: ----------------------- struct TURBO68K_DATAREGION { TURBO68K_UINT32 base; TURBO68K_UINT32 limit; TURBO68K_UINT32 ptr; void *handler; }; You may either set up a pointer, exactly the same as with instruction fetch regions. But, if the pointer is not set up (TURBO68K_NULL), Turbo68K will assume you want to use a handler. Simply set "handler" to the address of the function you want to use, nothing has to be mangled. Handler functions vary for different sizes of data: /* * Read byte */ TURBO68K_UINT8 ReadByte(TURBO68K_UINT32 addr) { /* ... */ } /* * Read word */ TURBO68K_UINT16 ReadWord(TURBO68K_UINT32 addr) { /* ... */ } /* * Read long word */ TURBO68K_UINT32 ReadLong(TURBO68K_UINT32 addr) { /* ... */ } /* * Write byte */ void WriteByte(TURBO68K_UINT32 addr, TURBO68K_UINT8 data) { /* ... */ } /* * Write word */ void WriteWord(TURBO68K_UINT32 addr, TURBO68K_UINT16 data) { /* ... */ } /* * Write long word */ void WriteLong(TURBO68K_UINT32 addr, TURBO68K_UINT32 data) { /* ... */ } All handlers are passed addresses that are clipped to whichever value you chose when emitting the emulator. By default, the address space is 24-bit. NOTE: It is not a good idea to try to read the CCR from within a handler. Some flag calculations are not completed until after data has been read/written. In fact, do not read any registers besides PC (with the Turbo68KReadPC() function.) The 68000 and 68010 break down long word accesses into 2 consecutive word word accesses, yet Turbo68K still uses long word handlers to provide opportunities for optimization. NOTE: Memory must be byte swapped. See section 4.8 for more information. To hook up the memory maps, you would code: mycontext.super_read_byte = my_read_byte; mycontext.super_read_word = my_read_word; mycontext.super_read_long = my_read_long; mycontext.super_write_byte = my_write_byte; mycontext.super_write_word = my_write_word; mycontext.super_write_long = my_write_long; mycontext.user_read_byte = my_read_byte; mycontext.user_read_word = my_read_word; mycontext.user_read_long = my_read_long; mycontext.user_write_byte = my_write_byte; mycontext.user_write_word = my_write_word; mycontext.user_write_long = my_write_long; If only one address space is being emulated, try: mycontext.read_byte = my_read_byte; mycontext.read_word = my_read_word; /* ... */ When using an internal context, use the appropriate functions: Turbo68KSetReadByte(my_read_byte, TURBO68K_SUPERVISOR); Turbo68KSetReadWord(my_read_word, TURBO68K_SUPERVISOR); /* ... */ The second method of handling data is to use functions exclusively. Use the -handler option when emitting Turbo68K. Six functions (as shown above) must be provided and directly hooked up to the context just as with memory maps. Now, whenever the address space is read or written, your function will be called and it is up to you to handle everything. 4.6 PC-Relative Reads In some rare situations, PC-relative address reading cannot function properly through the normal data access regions/handlers. An example of when this can occur is when the emulated system uses partially encrypted ROMs (CPS-2.) Turbo68K must be emitted with the -pcfetch option and a special fetch region for PC-relative reads must be set up. Writes are still handled using the data regions/handlers. The region is a TURBO68K_FETCHREGION and thus no handlers can be used. Memory pointers must be provided in the exact same way as you would for a normal fetch region. The array of fetch regions must be attached to the "pcfetch" pointers of the context: mycontext.pcfetch = pcfetch; mycontext.super_pcfetch = pcfetch; mycontext.user_pcfetch = pcfetch; When using an internal context, use Turbo68KSetPCFetch() and Turbo68KGetPCFetch(). This is a pretty poor hack, but it should do the job in most cases. This should not be needed for most systems. 4.7 Interrupts Interrupts may be triggered with the Turbo68KInterrupt() function and when they are being processed, an option call-back function may be called. Turbo68KInterrupt() takes 2 arguments, the first is the level number, and the second is the vector. For the vector, you may use TURBO68K_AUTOVECTOR for an auto-vectored interrupt, TURBO68K_SPURIOUS for a spurious interrupt, and TURBO68K_UNINITIALIZED for an uninitialized interrupt. Interrupts will be checked for and processed at the start of Turbo68KRun() or when Turbo68KProcessInterrupts() is called. If the "InterruptAcknowledge" member of the context is null, the function it points to will not be called, otherwise, the call-back can be used to make certain that interrupts have been processed, if the hardware being emulated requires this (for example, on the Sega Genesis, the call-back can be used to update IRQ-related bit settings in the VDP status register.) The call-back takes a single argument: The interrupt vector number passed to Turbo68KInterrupt() (a TURBO68K_UINT32), and returns nothing. If TURBO68K_AUTOVECTOR was passed to Turbo68KInterrupt(), the call-back will get the proper vector for the level being processed. If non-autovectored interrupts are triggered, there is no way to determine which level is being processed at the time the call-back is invoked. The InterruptAcknowledge pointer can be directly accessed regardless of whether you use your own context, or Turbo68K's internal context: turbo68kcontext_68000.InterruptAcknowledge = &MyIRQAckHandler; Make sure to set it to TURBO68K_NULL if you do not wish to make use of the call-back feature. It is possible to access this member at any time. 4.8 Byte Swapped Memory Host memory which Turbo68K can access directly must be byte swapped! The MSB (most significant byte) and LSB (least significant byte) of each 16-bit aligned word must be reversed. Sample Byte Swapping Function: ------------------------------ /* * void SwapMemory(TURBO68K_UINT8 *mem, TURBO68K_INT32 size); * * Byte swaps a given region of memory to work with Turbo68K's native * access capabilities. */ void SwapMemory(TURBO68K_UINT8 *mem, TURBO68K_INT32 size) { TURBO68K_UINT32 i, j; /* * Swap bytes in each word */ for (i = 0; i < length; i += 2) { j = buffer[i]; buffer[i] = buffer[i + 1]; buffer[i + 1] = j; } } Byte swapping is an optimization which greatly benefits Turbo68K in terms of speed. It relies on the fact that the 68000 and 68010 use a 16-bit data bus. To access individual bytes in byte swapped memory, XOR the address with 1. Words can be read or written normally. Long words are best accessed as 2 consecutive words. 4.9 The RESET and BKPT Instructions When the RESET or BKPT instruction is executed, the respective handler is called. If the handler is found to be TURBO68K_NULL, no action is taken. This is useful for emulating the functionality of the RESET (some systems need to reset hardware when it is executed) and BKPT (some systems might need to react to the Breakpoint Acknowledge Cycle) instructions. The BKPT handler is called before the 68010 processes the breakpoint exception. You can also perform high level emulation (HLE) by strategically inserting RESET (or BKPT, if dealing with a 68010) instructions into the emulated code. Use Turbo68KReadPC() to make sure you know which RESET/BKPT was executed. Turbo68KReadPC() will point to the next instruction. When setting up the handlers, no special functions are needed, even if the internal context is being manipulated. mycontext.Reset = &MyRESETHandler; turbo68kcontext_68000.Reset = &MyRESETHandler; The RESET handler takes no arguments and returns nothing. The BKPT handler is passed the breakpoint vector number (0-7.) /* * Sample RESET handler */ void MyRESETHandler() { /* ... Reset some hardware ... */ } /* * Sample BKPT handler */ void MyBKPTHandler(TURBO68K_INT32 num) { /* ... */ } 4.10 Initializing and Resetting Turbo68K Prior to doing anything, call Turbo68KInit() to initialize Turbo68K. This only needs to be done once per program session. Next, set up your memory maps. Then, reset the 680X0 processor by calling the Turbo68KReset() function. Turbo68K fetches the initial PC and SP from the supervisor fetch memory map, not through the long word read map. Turbo68K will return an error if it could not reset. This means you have set up your memory map incorrectly. /* * Initialize Turbo68K (VERY important) */ Turbo68KInit(); /* ... set up memory maps and interrupt call-back (optional) ... */ /* * Reset the processor */ if (Turbo68KReset() != TURBO68K_OKAY) Error(); /* ... begin emulating ... */ 4.11 Running the Emulator To execute code, use Turbo68KRun(). Pass it the number of cycles you wish to run for. Turbo68K will execute at least as many cycles as you wanted, but often executes a bit more. You can use Turbo68KGetElapsedCycles() to find out exactly how many were emulated. See the function reference for more information. Turbo68KInterrupt() can be used to trigger an interrupt. Please see section 4.7 for more information on interrupts. /* * Run Turbo68K for 1000 cycles */ Turbo68KRun(1000); /* * Level 6 auto-vectored interrupt */ Turbo68KInterrupt(6, TURBO68K_AUTOVECTOR); Both Turbo68KRun() and Turbo68KInterrupt() can return error information. See the function reference for more information. ------------------------- 5. Function Reference ------------------------- All of Turbo68K's functions are described here. Functions which are said not to work under given circumstances will have undefined results if used under those conditions. Usually, this means the context will be trashed, so be very careful! When Turbo68K is running, any handlers you may have set up are considered part of Turbo68K. Thus, when it is said a function cannot be called while Turbo68K is running, this means it cannot be called from a handler. TURBO68K_INT32 Turbo68KInit(); Initializes the emulator. This must be called before anything else. Input: Nothing. Returns: TURBO68K_OKAY Restrictions: For future compatibility, call this only once per program session. TURBO68K_INT32 Turbo68KReset(); Resets the 680X0. It uses the supervisor instruction fetch region to obtain the start-up vectors. Make sure everything is set up. A fetch error indicates either that the reset vectors could not be fetched or the PC reset vector points to an unhandled region of memory. Input: Nothing. Returns: TURBO68K_OKAY TURBO68K_ERROR_FETCH (PC is out of bounds) Restrictions: Do not use while Turbo68K is running. TURBO68K_INT32 Turbo68KRun(TURBO68K_INT32 cycles); Executes at least the specified number of cycles. To get the actual number of cycles that were emulated, use Turbo68KGetElapsedCycles(). Input: Number of cycles. Returns: TURBO68K_OKAY TURBO68K_ERROR_FETCH (PC is out of bounds) TURBO68K_ERROR_INVINST (invalid instruction, if -noillegal was specified) TURBO68K_ERROR_STACKFRAME (68010 only, unsupported/invalid stack frame, see section 7.2) Restrictions: Do not use while Turbo68K is running. TURBO68K_INT32 Turbo68KProcessInterrupts(); Processes any pending interrupts (unless the priority level does not agree with the SR priority bits.) Input: Nothing. Returns: TURBO68K_OKAY TURBO68K_ERROR_FETCH Restrictions: None. (If called while Turbo68K is running, it has no effect.) TURBO68K_INT32 Turbo68KInterrupt(TURBO68K_INT32 level, TURBO68K_UINT32 vector); Generates an interrupt with the specified level and vector. The level must be 1-7 and the vector cannot be less than 2. For the vector, you may specify the vector itself, TURBO68K_AUTOVECTOR for an autovectored interrupt, TURBO68K_SPURIOUS for a spurious interrupt, or TURBO68K_UNINITIALIZED for an uninitialized interrupt. If the "InterruptAcknowledge" member of the context is not null, the function it points to will be called as each interrupt is being taken. Input: Level. Vector. Returns: TURBO68K_OKAY TURBO68K_INTLEVEL (invalid level) TURBO68K_INTVECTOR (invalid vector) TURBO68K_INTPENDING (interrupt already pending at this level; this is not an error and will frequently occur, it is best not to check for this) Restrictions: None. TURBO68K_INT32 Turbo68KCancelInterrupt(TURBO68K_INT32 level); Cancels the pending interrupt at the specified level. Input: Level. Returns: TURBO68K_OKAY TURBO68K_INTLEVEL (invalid level) Restrictions: None. TURBO68K_UINT32 Turbo68KReadPC(); Returns the current PC. If this is called from within a handler, the PC may point at the next instruction or to one of the operand words. Input: Nothing. Returns: PC Restrictions: None. void Turbo68KSetFetch(void *fetch, TURBO68K_INT32 type); void Turbo68KSetPCFetch(void *pcfetch, TURBO68K_INT32 type); void Turbo68KSetReadByte(void *read_byte, TURBO68K_INT32 type); void Turbo68KSetReadWord(void *read_word, TURBO68K_INT32 type); void Turbo68KSetReadLong(void *read_long, TURBO68K_INT32 type); void Turbo68KSetWriteByte(void *write_byte, TURBO68K_INT32 type); void Turbo68KSetWriteWord(void *write_word, TURBO68K_INT32 type); void Turbo68KSetWriteLong(void *write_long, TURBO68K_INT32 type); These functions will attach memory maps to the internal context. If you want to attach memory maps to a context you have created (which is not mapped in), you must assign them manually. Input: Memory map array or function pointer (depending on whether Turbo68K was configured for memory map arrays or handlers) Type: TURBO68K_SUPERVISOR or TURBO68K_USER. If you are not emulating the distinction, this argument can be set to anything and will be ignored. Returns: Nothing. Restrictions: If different address spaces for supervisor and user modes are being emulated and the "type" argument is unrecognized, the behavior is undefined. void *Turbo68KGetFetch(TURBO68K_INT32 type); void *Turbo68KGetPCFetch(TURBO68K_INT32 type); void *Turbo68KGetReadByte(TURBO68K_INT32 type); void *Turbo68KGetReadWord(TURBO68K_INT32 type); void *Turbo68KGetReadLong(TURBO68K_INT32 type); void *Turbo68KGetWriteByte(TURBO68K_INT32 type); void *Turbo68KGetWriteWord(TURBO68K_INT32 type); void *Turbo68KGetWriteLong(TURBO68K_INT32 type); Will return the memory map or function pointers that are associated with each type of access. Input: Type: TURBO68K_SUPERVISOR or TURBO68K_USER. If you are not emulating the distinction, this argument can be set to anything and will be ignored. Returns: Memory map array or function pointer. Restrictions: If different address spaces for supervisor and user modes are being emulated and the "type" argument is unrecognized, the behavior is undefined. TURBO68K_UINT8 *Turbo68KFetchPtr(TURBO68K_UINT32 addr); Return a host pointer to the specified address in the current address space. Turbo68K scans the fetch region and if the address is not found, it will return 0. This is useful for disassemblers. Input: Address. Returns: Host address. Restrictions: Only the address space currently being used will be scanned. In order to select the supervisor or user address space, you will have to modify the privilege mode bit in SR and restore it to its original value when finished. TURBO68K_UINT8 Turbo68KReadByte(TURBO68K_UINT32 addr); TURBO68K_UINT16 Turbo68KReadWord(TURBO68K_UINT32 addr); TURBO68K_UINT32 Turbo68KReadLong(TURBO68K_UINT32 addr); void Turbo68KWriteByte(TURBO68K_UINT32 addr, TURBO68K_UINT8 data); void Turbo68KWriteWord(TURBO68K_UINT32 addr, TURBO68K_UINT16 data); void Turbo68KWriteLong(TURBO68K_UINT32 addr, TURBO68K_UINT32 data); These functions read/write data from the current Turbo68K address space. Thus, they can call handlers you have set up. These are particularly useful for debuggers. Be careful, the PC (and possibly other context data) will be invalid. Input: Address (and data if writing.) Returns: Data (or nothing if writing.) Restrictions: Do not use while Turbo68K is running. void Turbo68KSetContext(struct TURBO68K_CONTEXT *context); void Turbo68KGetContext(struct TURBO68K_CONTEXT *context); Map contexts in and out of the internal context using this pair of functions. Turbo68K operates only on its internal context, so it is necessary to copy externally-defined contexts into turbo68kcontext_* when you want to emulate the processor they define. These functions also make adjustments to the addresses contained in fetch, read_byte, etc. in order to speed things up -- therefore you cannot access these members, or any other context member, while it is mapped in. Input: Address of context to map in/out. Returns: Nothing. Restrictions: Do not use while Turbo68K is running. TURBO68K_UINT32 Turbo68KGetContextSize(); Returns the size, in bytes, of a Turbo68K context. This is useful for asserting that your compiler is packing the contexts correctly. Input: Nothing. Returns: Size of context in bytes. Restrictions: None. void Turbo68KClearCycles(); Clears both "cycles" and "remaining" causing Turbo68K to exit early. Be careful: a call to Turbo68KGetElapsedCycles() after this will return 0 or a small number which does not reflect the total amount of cycles used since Turbo68K was last called to run. Input: Nothing. Returns: Nothing. Restrictions: None. void Turbo68KFreeTimeSlice(); Causes Turbo68K to stop running early by working some magic on both "cycles" and "remaining." Unlike Turbo68KClearCycles(), you can get the number of cycles executed with Turbo68KGetElapsedCycles(). Input: Nothing. Returns: Nothing. Restrictions: If called from a handler, Turbo68K does not actually stop running until the current instruction finishes. TURBO68K_INT32 Turbo68KGetElapsedCycles(); Returns the amount of cycles run. Input: Nothing. Returns: Cycles run. Restrictions: None. --------------------- 6. Hints and Tips --------------------- For those of you who want to get the most out of Turbo68K, this section aims to provide you with some neat little tricks to try. 6.1 Alternative Identifiers The default identifier names have nothing preceding them (Turbo68KInit(), turbo68kcontext_68000, Turbo68KReset(), etc.) However, if the need arises, you can change the identifier names by adding a string (up to 16 characters) to the beginning of them. Use the -id option to emit Turbo68K with different identifier names. The data types and macros will not be changed, TURBO68K_CONTEXT_68000 will remain as it is, so will TURBO68K_OKAY, etc. You must also use the TURBO68K_ID() macro defined in TURBO68K.H to set the new identifier names immediately after the header file has been included. For example, if you want identifiers that start with "foo" you would write: #include "turbo68k.h" TURBO68K_ID(foo); You can do this multiple times. If you have linked in 2 copies of Turbo68K, one with identifiers that start with "foo", and another with identifiers starting with "bar", you would write: #include "turbo68k.h" TURBO68K_ID(foo); TURBO68K_ID(bar); With the above example, you would have functions like: fooTurbo68KInit(), barTurbo68KInit(), and (if you linked in another copy of Turbo68K without modified identifiers) Turbo68KInit(). 6.2 Multiple Processors Emulating multiple processors is fairly simple (unless extremely accurate timing is required.) You have to set up a context for each processor. You may not use the internal context as one of them, since it will be overwritten if you map in the other processor. Simplistic Example of Running 2 68000s: --------------------------------------- struct TURBO68K_CONTEXT_68000 context[2]; /* ... */ for (int i = 0; i < 2; i++) { Turbo68KSetContext(&context[i]); Turbo68KRun(500); Turbo68KGetContext(&context[i]); } Since Turbo68K is non-reentrant, you cannot multi-thread 2 processors unless you link in 2 separate builds of Turbo68K (with different identifier names, you can use the -id option for this.) 6.3 Maximizing Speed and Minimizing Size What better reasons to write a CPU emulator in assembly language than for speed and size? To get the most speed and least size (the 2 don't always co- exist) out of Turbo68K, here are a few things you can try: 1. If you are using memory map arrays, set them up wisely. If you have memory regions bordering each other, try turning them into a single large region. This not only cuts down the time it takes to scan through the map, but lets code flow between the regions. Put the most commonly accessed regions at the beginning of the array, and the least commonly accessed at the end. 2. Consider using handlers and not memory map arrays (-handler option.) If you can write a fast and clean set of handlers, you can speed things up by taking a load of work off of Turbo68K's back. Optimize your long- word handlers as well. It is perfectly okay to have a long-word handler call your word handlers twice and return the combined data, but it is faster to write the handlers specifically for long-word accesses. 3. Execute code in large time slices. The overhead of Turbo68KRun() is significant but it can be compensated by spending more time emulating. Throw Turbo68KSetContext()/Turbo68KGetContext() in the mix and the gains are even greater. 4. Try using -nodummyread if you can get away with it. For most systems, it is unnecessary to emulate this tiny detail. 5. If possible, use -nobrafetch (the default) instead of -brafetch. The branch instructions are used a lot. If -skip does not impact compatib- ility, consider using it, too. 6. If you can get away with sneaking in a 32-bit address bus, do it. The 68000 and 68010 actuall use a 24-bit address bus, which means that Turbo68K has to mask off the unused bits when fetching data. The overhead of doing this is negligible, but if you can get away with using the full 32-bits, go for it. 7. Don't allow separate address spaces for supervisor and user if you can get away with it (with most systems, it isn't needed.) This reduces some overhead in some of the instruction handlers and functions. 8. Link the Turbo68K object file close to the object files which call it most frequently. The theory behind this is that the processor will be able to easily cache in code areas that use each other often. It might not always yield any speed increase, but I have observed slightly quicker performance when optimizing the link order in Genital. 9. Strip the program you compile, this will reduce the executable tremend- ously. Turbo68K leaves behind a lot of symbols and junk. There is a lot to do on my part. Turbo68K features some really poor code, but usually in uncommon instructions. When/if I have time, I will optimize what I can, but not all at once. 6.4 Sega Genesis TAS Bug The TAS instruction is usually used in multi-processor systems to synchronize execution between multiple 68Ks because it takes the bus. But on the Sega Genesis, the VDP will not release the bus and TAS instructions consequently do not write to memory. In order to emulate this, comment out the call to WriteByte() in the TAS() function in make68k.c. This will fix Gargoyles and possible some other games which use the TAS instruction. The Genesis 3 apparently fixes this little bug and games like Gargoyles no longer work. ----------------------- 7. Additional Notes ----------------------- This is a collection of miscellaneous notes, points of interest, and errata. 7.1 Change History January 19, 2002: Version 0.6 - CMPI and CMP no longer write back to memory - Turbo68KCancelInterrut() fixed (uses EAX*4 as an index into the interrupt array, so it now actually cancels interrupts) - Optmized Turbo68KCancelInterrupt() slightly - Added the "InterruptAcknowledge" call-back to facilitate more accurate interrupt handling on some systems (such as the Sega Genesis) - API functions now use the stack to save registers. Turbo68KInterrupt() can now be called from within a memory handler - Turbo68KGetXXX() functions now properly retrieve arguments passed to them - Some instruction handlers now use the stack to save registers ( Thanks to Charles MacDonald for submitting most of these fixes and additions! ) June 29, 2001: Version 0.5 - Changed the name to "Turbo68K" - Undocumented flags emulated (verified against real 68000 processors) - N flag calculation for ABCD, SBCD, and NBCD instructions is now truly accurate - Dummy read emulation added for CLR, Scc, and the unprivileged version of "MOVE from SR." It is on by default and works only when the processor type is 68000 - CHK comparisons are now signed as they should be - Fixed internal calls to API functions to work with register calling conventions June 15, 2001: Version 0.4 - Now uses proper identifier names when internally calling Genital68KUpdateFetchPtr() - N flag is now calculated for SBCD (thanks to ElSemi for this!) - Optimized C flag calculation for SBCD -(Ax),-(Ay) and ABCD -(Ax),-(Ay) - N flag for ABCD is calculated the same way as for SBCD (not sure if this is correct, I'll verify it once my Saturn 68K debugger is complete) May 7, 2001: Version 0.35 - Output file is now properly closed on exit - RTD is no longer emitted as a 68000 instruction (68010 only) - Initial PC and SP are now read from the instruction fetch memory maps - Instructions are emitted in a more optimal order, cache performance might be better April 29, 2001: Version 0.3 - Fixed a MOVEM bug (example: MOVEM.L (A7)+,A7) - Added rudimentary 68010 support - "MOVE from SR" is no longer privileged for 68000 processors - Rewrote documentation - Possibly other minor changes I forgot to list February 11, 2001: Version 0.22 - Added a PC-relative read mode (useful in very special circumstances) - Removed some unused data - Removed sign-extended byte read functions (they were not being used) January 13, 2001: Version 0.21 - Fixed Genital68KReadXXX() and Genital68KWriteXXX() functions, they no longer trash the PC January 7, 2001: Version 0.2 - Slightly modified Genital68KReadXXX() and Genital68KWriteXXX() functions - Registers are now saved when Genital68K functions are called - Minor optimizations here and there December 8, 2000: Version 0.12 - Fixed a bug with register calling conventions and read/write handlers December 4, 2000: Version 0.11 - Fixed a bug in Genital68KGetContext() and Genital68KSetContext() which corrupted user and supervisor address spaces - Corrected a misleading comment in MAKE68K.C and updated the macro refer- ence in this document December 3, 2000: Version 0.1 - Added user/supervisor address space distinction (optional) - Changed address space functions - Added a GENITAL68K_NULL macro - Fixed some mistakes in this document November 23, 2000: Version 0.0 - Initial release 7.2 Un-emulated Features The following are not emulated: Tracing Turbo68K does not emulate trace exceptions. Address Errors Turbo68K does not emulate address error exceptions. The X86 allows for misaligned accesses (unless alignment checking is on), and consequently, so does Turbo68K. Bus Errors Bus errors are not emulated. Undefined Bits Undefined bits in the SR are usually kept 0. FC Output Lines The real 68000 and 68010 classify address space accesses as either "User Data", "User Program", "Supervisor Data", "Supervisor Program", or "CPU Space." Turbo68K simplifies this by supporting a user address space and a supervisor address space. There is also an option to disable this distinction and use one address space. MOVES The effects of the alternate function code registers (SFC and DFC) are not emulated. This includes the 68010 MOVES instruction. Loop Mode Loop mode (68010) is not emulated. Perhaps if someone supplies me with very detailed information on how it works, I might consider adding it. Stack Frames Only stack frame 0 is supported. Stack frame 8 (associated with group 0 exceptions: bus and address errors) is never used and if encountered, Turbo68KRun() returns a stack frame error. When such an error occurs, the processor state will look as if the problematic RTE instruction had never been executed. 68010 Timing For instructions common to both the 68000 and 68010 (most of them), 68000 timing is used. 7.3 Errata Although I have attempted to fix every bug I came across, there is bound to be something obscure, or possibly major, left undiscovered. There are a few unimportant inaccuracies I do know about, but have not bothered to fix yet: DIVS, DIVU Timing is off by up to 10% in worst-case scenarios. MULS, MULU Timing is off by an unknown amount. The multiplication algorithm used by the 68000 and 68010 affects timing in pretty odd ways. Timing Some of the instructions have slightly inacurrate timing, usually because of human error. Timing for exceptions and interrupts is often wrong (but not TOO far off.) Exceptions Priority between interrupts and other exceptions (such as privilege violation, division by 0, TRAP, TRAPV, etc.) is not fully emulated. Priority among interrupts themselves seems to be properly handled, and that is most important. I have not seen any situations where interrupts and other exceptions affect each other. More information on the priority of spurious and uninitialized interrupts as well as level 7 would be appreciated. I am going off what little the Motorola manuals say. 7.4 Bibliography Most of what I know about 680X0 processors comes from the 2 free manuals I ordered from Motorola. The rest comes from individuals who kindly helped me and are mentioned in section 8. Documents Used: --------------- - "Programmer's Reference Manual" by Motorola (M68000PM/AD REV 1) - "M68000 8-/16-/32-Bit Microprocessor User's Manual Ninth Edition" by Motorola (M68000UM/AD REV 8) --------------------- 8. Special Thanks --------------------- Many thanks go out to those who helped me out with Turbo68K and contributed to the project in any form. - Neil Bradley - Victor Moya - Dynarec Mailing List: M.I.K.e, Neil Griffiths, Gwenole, etc. - Quintesson - Steve Snake - Tim Meekins - Kuwanger - Charles MacDonald - Stephane Dallongeville - Eli Dayan - Neill Corlett - Pete Dabbs - ElSemi And, of course, thank YOU! Remember, comments, suggestions, and contributions are more than welcome, so get in touch with me. See you next time!