kolibrios-gitea/programs/emulator/dgen-sdl-1.33/star/stardoc.txt
turbocat ea1a60faa3 Upload DGEN port source
git-svn-id: svn://kolibrios.org@9837 a494cfbc-eb01-0410-851d-a64ba20cac60
2022-06-15 18:25:17 +00:00

1068 lines
40 KiB
Plaintext

-----------------------------------------------------------------------------
Starscream 680x0 emulation library version 0.26d
Copyright 1997, 1998, 1999 Neill Corlett
Modified by Stéphane Dallongeville
-----------------------------------------------------------------------------
"Pathetic flesh creatures... can't you see we're INVINCIBLE? Come,
Decepticons, follow me to victory!"
How to contact Neill Corlett:
email: corlett@elwha.nrrc.ncsu.edu
www: http://www4.ncsu.edu/~nscorlet/
Contents
--------
0. Terms of Use
1. What's New
2. Getting Started
2.1. What You Will Need
2.2. How to Compile
3. The Starscream Interface
3.1. Function Reference
3.2. Contexts
3.3. Address Spaces
3.4. Executing Code
3.5. Read and Write Handlers
3.6. The Odometer
3.7. Interrupts
3.8. Emulating More Than One CPU
3.9. RESET and BKPT Instructions
3.10. Using the Interactive Debugger
3.11. Emulating the 68010
4. Tricks of the Trade
4.1. Inter-CPU Communication
4.2. Other Helpful Tips
4.3. Pitfalls
4.4. Ways to Abuse Starscream
4.5. Emulating Other 68K-Series CPUs
5. Known Bugs
6. Credits
-----------------------------------------------------------------------------
0. Terms of Use
-----------------------------------------------------------------------------
"Starscream" refers to the following files:
* STAR.C
* STARCPU.H
* CPUDEBUG.C
* CPUDEBUG.H
* STARDOC.TXT
* any object file or executable compiled from the above
* any source code generated from STAR.C, or object file assembled from such
code
Starscream may be distributed freely in unmodified form, as long as this
documentation is included.
No money, goods, or services may be charged or solicited for Starscream, or
any emulator or other program which includes Starscream, in whole or in part.
Using Starscream in a shareware or commercial application is forbidden.
Contact Neill Corlett (corlett@elwha.nrrc.ncsu.edu) if you'd like to license
Starscream for commercial use.
Any program which uses Starscream must include the following credit text, in
its documentation or in the program itself:
"Starscream 680x0 emulation library by Neill Corlett
(corlett@elwha.nrrc.ncsu.edu)"
-----------------------------------------------------------------------------
1. What's New
-----------------------------------------------------------------------------
Version 0.26d:
* CHK generation fixed.
* IDBcc generation fixed (IDBtr added).
Version 0.26c:
* Shift bits instructions fixed.
Version 0.26b:
* IO DWord Write bug fixed.
* Interrupt processing bug fixed.
(all lines added are followed by "// Stef Fix (Gens)" )
Version 0.26a:
* Egregious GetContext/SetContext bug fixed.
* Minor tweaking was done to the alignment rules.
Version 0.26:
* First publicly redistributable version.
* Added the following exceptions:
Trace
Format Error (68010+)
* Added a Double Fault mechanism. Currently the only thing that can cause
this is s68000reset().
* The memory map interface has changed to support different function codes /
address spaces (see section 3.3).
* Many improvements have been made to interrupt handling (see section 3.7).
* The unused bits of PC are now handled and preserved properly.
* Added fetch region caching. Jumps within the same program region are
now faster.
* Added a user-definable BKPT instruction handler (68010+).
* All Illegal Instruction exceptions are now emulated. The -illegal and
-noillegal options have been removed.
* Some features were added to the interactive debugger (see section 3.10).
* A few experimental 68020-related functions and variables have been added.
Ignore them for now.
Version 0.25 and earlier:
Ask if interested.
-----------------------------------------------------------------------------
2. Getting Started
-----------------------------------------------------------------------------
2.1. What You Will Need
------------------------
* A 32-bit C or C++ compiler. So far, the following compilers have been
proven to work:
- DJGPP 2.01
- Watcom 10.6
- GCC, running under Linux
- Microsoft Visual C++
* Netwide Assembler (NASM) version 0.95 or higher. NASM is freeware, with
publicly available source code, and available on a multitude of x86-based
platforms. It's available from Programmer's Heaven:
http://www.programmersheaven.com/
* Basic knowledge about the 680x0. You don't (theoretically) need to know
any 680x0 assembly, but it helps. A lot.
You can get a copy of the M68000 Family Programmer's Reference Manual (in
Adobe Acrobat .PDF format) from this web page:
http://www.mot.com/SPS/HPESD/prod/0X0/frames/68K.html
2.2. How to Compile
--------------------
Step 1
Generate the Starscream "code builder" executable by compiling STAR.C.
(for example: gcc star.c -o star)
Step 2
Generate the CPU source code. Run the code builder with the following
command line:
star source.asm [options]
Replace "source.asm" with the source code filename of your choice.
Options that you might need:
-regcall Use register calling conventions.
-stackcall Use stack-based calling conventions (default).
-hog Enable Hog mode. This inlines the fetch-decode-
execute loop, resulting in a decent speed increase,
at the cost of about 130K of executable size.
-nohog Disable Hog mode (default).
-cputype <type> Specify the CPU type, 68000 or 68010 (default=68000).
Options that you should never need (but they're here anyway):
-addressbits n Use n-bit addresses. The default value depends on
the CPU type (currently it's just 24).
"-addressbits 32" might allow some 68020/68070 code
to work. No guarantees, though.
-name <name> Appends the string <name> to all identifiers. This
overrides the default of s68000 or s68010 (depending
on the CPU type).
Step 3
Assemble the CPU source code. Refer to the NASM documentation for how to
do this.
If you run out of memory, try using NASMW.EXE instead of NASM.EXE (note,
however, that the NASMW.EXE executable from v0.97 is somewhat unstable).
Better yet, just recompile NASM yourself. Worked for me.
To generate a COFF object for DJGPP or GNUWin32, try this:
nasm -f coff source.asm
or to generate a Win32 object for Watcom or Visual C++:
nasm -f win32 source.asm
Step 4
Strip your executable when you're done! Starscream spews out symbols all
over the place (over 7000 of them), and you can save a ton of space by
stripping them once you're finished debugging.
For GCC, this means use the -s option in the linking phase.
-----------------------------------------------------------------------------
3. The Starscream Interface
-----------------------------------------------------------------------------
3.1. Function Reference
------------------------
The following is a brief summary of the functions declared in STARCPU.H. For
more information on a particular function, read the associated section.
int s68000init(void); (section 3.4)
Initializes Starscream. Always returns 0.
int s68000reset(void); (section 3.4)
Resets the current CPU.
Return value:
0 Success
1 Failure: Reset vector couldn't be fetched
-1 Failure: Double fault
unsigned s68000exec(int n); (section 3.4)
Executes n cycles' worth of instructions on the current CPU.
Return value:
= 0x80000000 Success
= 0x80000001 Out of bounds
= 0x80000002 Unsupported stack frame (68010+)
= 0xFFFFFFFF Double fault
< 0x80000000 Invalid instruction; the value returned is the address
of the instruction
int s68000interrupt(int level, int vector); (section 3.7)
Generates a hardware interrupt on the current CPU, with a given priority
level and vector number. The interrupt is queued for processing as soon
as possible.
Valid levels: 1-7
Valid vectors:
0-255 Deliberately vectored interrupt
-1 Auto-vectored interrupt
-2 Spurious interrupt
Return value:
0 The interrupt was queued successfully.
1 The interrupt was not queued, because a previously queued
interrupt exists at the same level.
2 The interrupt was not queued, because either "level" or "vector"
were invalid.
void s68000flushInterrupts(void); (section 3.7)
Check if there are any unmasked interrupts pending, and if so, process
them.
int s68000GetContextSize(void);
Returns the size of the context structure. Useful for verifying that
the context struct is packed correctly, i.e.:
ASSERT(s68000GetContextSize() == sizeof(struct S68000CONTEXT));
void s68000GetContext(void *context); (section 3.2)
Copies the current context into another context structure.
void s68000SetContext(void *context); (section 3.2)
Copies a context structure into the current context.
int s68000fetch(unsigned address);
Fetches the word at the specified address, using the memoryfetch array.
Returns -1 if the address is out of bounds.
unsigned s68000readOdometer(void); (section 3.6)
Returns the value of the odometer for the current CPU. Works anywhere,
even from within memory read/write, RESET, or BKPT handlers.
unsigned s68000tripOdometer(void); (section 3.6)
Returns the value of the odometer for the current CPU, and resets it to
zero. Works anywhere.
unsigned s68000controlOdometer(int n); (section 3.6)
Returns the value of the odometer for the current CPU. If n!=0, it also
resets the odometer to zero. Works anywhere.
void s68000releaseTimeslice(void); (section 3.5)
When called from inside a memory read/write, RESET, or BKPT handler,
this causes s68000exec to end prematurely. The early exit is reflected
in the odometer.
unsigned s68000readPC(void);
Returns the current program counter. Works anywhere.
3.2. Contexts
--------------
A "context" is a memory structure which holds all the information needed to
emulate a single CPU. Starscream defines a 68000 context as follows:
struct S68000CONTEXT {
struct STARSCREAM_PROGRAMREGION *fetch;
struct STARSCREAM_DATAREGION *readbyte;
struct STARSCREAM_DATAREGION *readword;
struct STARSCREAM_DATAREGION *writebyte;
struct STARSCREAM_DATAREGION *writeword;
struct STARSCREAM_PROGRAMREGION *s_fetch;
struct STARSCREAM_DATAREGION *s_readbyte;
struct STARSCREAM_DATAREGION *s_readword;
struct STARSCREAM_DATAREGION *s_writebyte;
struct STARSCREAM_DATAREGION *s_writeword;
struct STARSCREAM_PROGRAMREGION *u_fetch;
struct STARSCREAM_DATAREGION *u_readbyte;
struct STARSCREAM_DATAREGION *u_readword;
struct STARSCREAM_DATAREGION *u_writebyte;
struct STARSCREAM_DATAREGION *u_writeword;
void (*resethandler)(void);
unsigned dreg[8];
unsigned areg[8];
unsigned asp;
unsigned pc;
unsigned odometer;
unsigned char interrupts[8];
unsigned short sr;
};
There is a built-in context, called s68000context, which contains information
about the current CPU. If you're only emulating one 68000, then
s68000context is the only context you need. On the other hand, if you're
emulating more than one 68000, you'll need to define your own context for
each one.
Here is a detailed description of the contents of a S68000CONTEXT structure:
* "s_fetch", "s_readbyte", "s_readword", "s_writebyte", and "s_writeword"
are used to define the supervisor address space. "u_fetch", etc. are
used to define the user address space. (see section 3.3)
* "resethandler" points to your reset handler routine. (see section 3.9)
* "pc" is the program counter.
* "dreg" holds the eight data registers, d0-d7, in order.
* "areg" holds the eight address registers, a0-a7, in order. In supervisor
mode, areg[7] is the interrupt stack pointer. Otherwise, it's the user
stack pointer.
* "asp" is whatever stack register areg[7] isn't. In supervisor mode, asp
is the user stack pointer. Othewise, it's the supervisor stack pointer.
* "odometer" holds the number of cycles executed so far. It's incremented
as necessary on calls to s68000exec or s68000flushInterrupts.
* "interrupts" is an array containing information about pending interrupts.
(see section 3.7)
* "sr" is the status register. The lower 8 bits are the condition code
register.
3.3. Address Spaces
--------------------
The 68000 communicates with other devices via a number of address spaces.
There are different address spaces for program code and data, as well as
different address spaces for Supervisor and User modes. Most 68000-based
architectures use the same address space for all these, but the distinction
is there nonetheless.
Starscream emulates program address spaces by translating the 68K address
into a native host address, and fetching data directly from host memory (from
an array of words with native byte order). A program address space is
defined using an array of STARSCREAM_PROGRAMREGION structures:
struct STARSCREAM_PROGRAMREGION {
unsigned lowaddr;
unsigned highaddr;
unsigned offset;
};
Each structure contains the range of addresses in a program region, and an
offset which is added to the 68K address to obtain the equivalent host
address.
IMPORTANT NOTE: Program regions must be stored as an array of words with
native byte order. Since Intel's byte order is different than Motorola's,
this means every other byte must be swapped.
The s68000context fields "s_fetch" and "u_fetch" should be set to point to
arrays of STARSCREAM_PROGRAMREGION structures in order to define the
Supervisor Program and User Program address spaces, respectively. Each array
may contain any number of entries, and is terminated by an entry containing
-1 for both lowaddr and highaddr, and NULL for the offset.
Starscream emulates data address spaces in a completely different way. Since
the 68000 has no dedicated I/O bus, device interfaces (video/sound chips,
input devices, etc.) are often mapped directly into a data address space.
This raises some complex issues; for example, writing to an emulated serial
port address might require that an extra routine be called, in order to pass
the data through a real serial port, append it to a log file, etc. To this
end, Starscream defines data address spaces using an array of
STARSCREAM_DATAREGION structures:
struct STARSCREAM_DATAREGION {
unsigned lowaddr;
unsigned highaddr;
void *memorycall;
void *userdata;
};
Each structure contains the low and high addresses in a region, as before.
Rather than simply adding a constant offset to translate a 68K address into
a native one, Starscream offers two different ways to handle accesses to a
data region:
1. Call a user-defined handler routine whenever this region is accessed.
unsigned read_handler(unsigned address);
void write_handler(unsigned address, unsigned data);
memorycall = pointer to user handler
userdata = unused
(For some important information about read and write handlers, refer
to section 3.5.)
2. Handle accesses internally by reading/writing to an area of native
memory. Don't trap anything. This is ideal for a simple RAM or ROM
area as it requires the least processing.
memorycall = NULL
userdata = pointer to native memory area where reads or writes
will occur
NOTE: The native memory area must be byte swapped, same as program
regions are.
The s68000context fields "s_readbyte", "u_readbyte", "s_writebyte", etc.
should be set to point to arrays of STARSCREAM_DATAREGION structures in order
to define the Supervisor Data and User Data address spaces. Each array may
contain any number of entries, and is terminated by an entry containing -1
for lowaddr and highaddr, and NULL for memorycall and userdata.
And now, for an example of address space definition. Consider the following
make-believe memory map:
000000-03FFFF: Program ROM
300000-307FFF: Main RAM
400000-407FFF: Extra RAM
800000-81FFFF: Video RAM
A00000-A00007: Sound chip
Assume that:
* Program ROM is in an array called program_rom
* Main RAM is in an array called main_ram
* Extra RAM is in an array called extra_ram
* Video RAM is in an array called video_ram
* You've set up two routines to write to the sound chip:
void soundchip_writebyte(unsigned address, unsigned data);
void soundchip_writeword(unsigned address, unsigned data);
The first step is to build the program address space. Program code is
probably only going to be fetched from Program ROM, Main RAM, or Extra RAM.
The address space would thus be defined:
struct STARSCREAM_PROGRAMREGION pretend_programfetch[] = {
{0x000000, 0x03FFFF, (unsigned)program_rom - 0x000000},
{0x300000, 0x307FFF, (unsigned)main_ram - 0x300000},
{0x400000, 0x407FFF, (unsigned)extra_ram - 0x400000},
{-1, -1, NULL}
};
The last entry must be {-1, -1, NULL}.
The next step is to build the data address space. This is accomplished with
four arrays of STARSCREAM_DATAREGION structures: one for byte reads, word
reads, byte writes, and word writes. For example:
struct STARSCREAM_DATAREGION pretend_readbyte[] = {
{0x000000, 0x03FFFF, NULL, program_rom},
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{-1, -1, NULL, NULL}
};
struct STARSCREAM_DATAREGION pretend_readword[] = {
{0x000000, 0x03FFFF, NULL, program_rom},
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{-1, -1, NULL, NULL}
};
struct STARSCREAM_DATAREGION pretend_writebyte[] = {
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{0xA00000, 0xA00007, soundchip_writebyte, NULL},
{-1, -1, NULL, NULL}
};
struct STARSCREAM_DATAREGION pretend_writeword[] = {
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{0xA00000, 0xA00007, soundchip_writeword, NULL},
{-1, -1, NULL, NULL}
};
The last entry of each array must be {-1, -1, NULL, NULL}.
The above structures take advantage of Starscream's internal handling for the
Program ROM, Main RAM, Extra RAM, and Video RAM, and use the custom handler
when writing to the sound chip.
Now that you've defined your address spaces, you need to tie it in with the
context (since each CPU can have different address spaces). This is done by
setting the address space pointers (s_fetch, u_fetch, etc.) to point to your
addres space definitions.
s68000context.s_fetch = pretend_programfetch;
s68000context.u_fetch = pretend_programfetch;
s68000context.s_readbyte = pretend_readbyte;
s68000context.u_readbyte = pretend_readbyte;
s68000context.s_readword = pretend_readword;
s68000context.u_readword = pretend_readword;
s68000context.s_writebyte = pretend_writebyte;
s68000context.u_writebyte = pretend_writebyte;
s68000context.s_writeword = pretend_writeword;
s68000context.u_writeword = pretend_writeword;
In this example, the Supervisor and User address spaces are set up to point
to the same definitions. For most 68000-based architectures, this is
correct.
If you are dynamically allocating memory for your emulated RAM or ROM areas
(using malloc() or a similar function), you will have to set up the address
space definitions at run time. This is left as a trivial exercise for the
reader.
Veteran users of the UAE 680x0 core might wonder why there is no "readlong"
or "writelong". This is because the real 68000 has a 16-bit data bus, and
therefore breaks up each 32-bit access into two 16-bit accesses. Using
separate handlers for 32-bit accesses is cumbersome and unnecessary. For
speed considerations, Starscream's internal memory read/write handlers access
32 bits at a time wherever possible (using the readword/writeword map).
3.4. Executing Code
--------------------
Before you execute any code, you must call s68000init(). It only needs to
be called one time.
Once that's done, and you've set up the memory map, call s68000reset() to
reset the CPU. If everything works, it should read the initial stack pointer
from address 0x000000, and the initial program counter from 0x000004, using
the Supervisor Program address space. (You can verify this by examining
s68000context.areg[7] and s68000context.pc.)
At this point, you can call the interactive debugger to try disassembling or
stepping through code, and to make sure everything is in the right place.
(see section 3.10 for details)
To execute 68000 code, call s68000exec(n), where n is the number of cycles.
s68000exec will return one of the following:
= 0x80000000: Success.
= 0x80000001: Out of range - the program "went off into the weeds".
More specifically, the program jumped to an address which was
not defined in the s_fetch or u_fetch array.
= 0x80000002: Unsupported stack frame (68010+). This happens when an RTE
instruction encounters a stack frame format that hasn't been
implemented (see section 5).
= 0xFFFFFFFF: Double fault. The CPU is dead and will stay dead until it
gets reset again.
< 0x80000000: Invalid instruction. The value returned is the address of
the instruction. For 32-bit addresses, the highest bit is
cut off (s68000context.pc contains the complete address,
however).
If you get a return code of 0x80000001, 0x80000002, or 0xFFFFFFFF, then the
68000 CPU will be in an unstable state and should be reset.
The real 68000 generates an exception for illegal instructions. As of v0.26,
this exception is always emulated. You will only get a return code less than
0x80000000 in an unexpected circumstance; upon encountering an invalid
instruction that isn't supposed to be. For example, a RESET instruction will
cause this if the user-defined RESET handler is null (see section 3.9 for
details on RESET handlers).
Whenever you call s68000exec, it increments the odometer (see section 3.6).
3.5. Read and Write Handlers
-----------------------------
Memory read and write handlers (as described in section 3.3) are a special
case, as they involve Starscream calling your code instead of the other way
around. Since Starscream is not re-entrant, there are some rules which all
read and write handlers must follow.
1. They must not call any of the following routines:
s68000reset s68000exec
s68000SetContext s68000GetContext
2. They must not attempt to read or modify s68000context directly. All
data in s68000context is undefined.
The primary purpose of a read handler is to return data, and the primary
purpose of a write handler is to take data and store it somewhere. However,
read and write handlers do have some control over the emulation process,
beyond the data that they send or receive:
* You can call s68000interrupt() as much as you like (see section 3.7).
* You can cause s68000exec to quit early, by calling the
s68000releaseTimeslice() function. This is useful if you need to transfer
control over to another emulated CPU.
* You can read and/or clear the odometer, with some specialized functions
(see section 3.6).
* You can read the program counter with s68000readPC(), but it probably
won't be located exactly at the beginning of the instruction.
3.6. The Odometer
------------------
Every context contains an integer field called "odometer". Whenever you
call s68000exec, the number of elapsed clock cycles is added to
s68000context.odometer.
Note: When calling s68000exec, the number of elapsed cycles is not
necessarily the same as the number of cycles you specify. Imagine this
scenario: You call s68000exec(100). The next instruction in the code stream
is...
movem.l ($100000).L,d0-d7/a0-a7
s68000exec would quit after that one instruction, returning the success code,
80000000h (assuming nothing else went wrong), and s68000context.odometer
would be incremented by 148.
You can freely change the odometer between calls to s68000exec, but not from
within read/write handlers (as it's part of the context). If you need to use
the odometer from a read or write handler, there are some specialized
functions for that:
s68000readOdometer() - Returns the value of the odometer.
s68000tripOdometer() - Returns the value of the odometer, and sets it to
zero in the process.
s68000controlOdometer(n) - If n==0, it works just like s68000readOdometer.
If n!=0, it works just like s68000tripOdometer.
(We have Neil Bradley to thank for this gem...)
3.7. Interrupts
----------------
Hardware interrupts can be generated at any time, including within read/write
handlers, by calling s68000interrupt(l, v) where l is the interrupt priority
level, and v is the vector number. Levels 1-7 are valid; vector numbers
0-255 are valid. Vector number -1 means "auto-vectored". -2 will generate a
Spurious Interrupt.
The interrupt in question will be made pending until the Processor Priority
Level allows it to be processed. You can tell which interrupt levels are
pending, if any, by examining s68000context.interrupts[0]. Bits 1-7
correspond to interrupt level 1-7; bit 0 is set if the CPU is in a stopped
state (waiting for an interrupt). The vector numbers are stored in
s68000context.interrupts[n] where n is the interrupt level.
s68000interrupt returns one of the following values:
0 The interrupt was added successfully.
1 The interrupt was not added, because there was already a pending
interrupt at the same level.
2 The interrupt was not added, because the parameters were invalid.
Pending interrupts are checked at the following times:
* s68000exec checks for pending interrupts before executing any code. The
cycles taken by interrupt processing are subtracted from the total "cycle
budget", though s68000exec is still guaranteed to execute at least one
instruction.
* Interrupts generated from within a read/write, RESET, or BKPT handler will
be checked when the current instruction is finished.
* You can force a pending interrupt check by calling s68000flushInterrupts.
This has no effect if called from within a read/write, RESET, or BKPT
handler.
Starscream can call a user-defined function when handling interrupts.
The handler must take no arguments, and return void.
s68000context.inthandler = my_int_handler;
3.8. Emulating More Than One CPU
---------------------------------
If you want to emulate multiple 68000s, you'll need to create a context and a
memory map for each one (see sections 3.2 and 3.3). For example:
struct S68000CONTEXT myContext[number_of_68000s];
When you use your own contexts, it's a good idea to initialize all the bytes
in the new contexts to zero:
memset (myContext, 0, sizeof(myContext));
To use one of your own contexts, you must follow these three steps:
1. Copy your context into s68000context:
s68000SetContext(myContext);
2. Use a function which reads or modifies the context - s68000exec,
s68000reset, s68000interrupt, etc.
3. Copy s68000context back into your context:
s68000GetContext(myContext);
Expanding on the example in section 3.2 - say you're emulating two 68000s,
and you've set up two contexts for them:
struct S68000CONTEXT myContext[2];
To reset both CPUs, you'd do this:
for(i = 0; i < 2; i++) {
s68000SetContext(&myContext[i]);
s68000reset();
s68000GetContext(&myContext[i]);
}
An emulation loop involving both CPUs might look something like this. (Note:
This is a CRUDE example.)
while(!done) {
/* Emulate primary 68000 */
s68000SetContext(&myContext[0]);
s68000exec(100000);
s68000GetContext(&myContext[0]);
/* Emulate secondary 68000 */
s68000SetContext(&myContext[1]);
s68000exec(100000);
s68000GetContext(&myContext[1]);
}
The overhead of copying CPU contexts is compensated by the fact that you can
emulate the CPUs in large timeslices.
3.9. RESET and BKPT Instructions
---------------------------------
Starscream can call a user-defined function to emulate the RESET instruction,
and to notify when a BKPT instruction is executed. The handlers must take no
arguments, and return void. For example:
void my_reset_handler(void) {
/* Reset some hardware */
}
Note: RESET and BKPT handlers must follow the same rules as memory
read/write handlers (see section 3.5).
To register a handler function, set the "resethandler" and/or "bkpthandler"
field of the appropriate context to point to the function, i.e.:
s68000context.resethandler = my_reset_handler;
You can also set "resethandler" or "bkpthandler" to NULL, in which case:
* A RESET instruction will cause an Invalid Instruction case. s68000exec
will halt and return its address.
* A BKPT instruction will behave normally, but without notification.
Calling s68000readPC() during a RESET or BKPT handler will return the address
directly after the RESET or BKPT instruction. So, to determine the exact
BKPT opcode, you can do this:
opcode = s68000fetch(s68000readPC() - 2);
3.10. Using the Interactive Debugger
-------------------------------------
Included in the Starscream package is an interactive 68000 cross-debugger and
built in disassembler, with an interface similar to the MS-DOS DEBUG utility.
CPUDEBUG.C contains everything needed for the debugger. If you don't need
it, you can leave out CPUDEBUG.C and CPUDEBUG.H altogether.
Before using the debugger, make sure to set up your memory map, and call
s68000init() and s68000reset().
To start the debugger, call the cpudebug_interactive routine, which is
defined in CPUDEBUG.H as:
int cpudebug_interactive(
int cpun,
void (*put)(const char*),
void (*get)(char*, int),
void (*execstep)(void),
void (*dump)(void)
);
"put", "get", "execstep", and "dump" are all optional - you can pass NULL
values for any or all of them. Their purpose is explained below.
If you're only emulating one CPU, use a value of 1 for "cpun", and keep
calling cpudebug_interactive until the return value is -1.
If you're debugging more than one CPU, assign an ID number to each (starting
at 1), including any non-68000 CPUs. Switch to the context of the CPU you
want to debug, and then call cpudebug_interactive with the appropriate ID
number in "cpun". Then, take one of the following actions depending on the
return value:
-1 Quit
0 Switch to the next CPU in your list (ID + 1), wrap around if necessary,
and call cpudebug_interactive again. Or, if the CPU in question is not
a 680x0, then use whatever debugger is appropriate.
N Switch to CPU #N. If N is invalid, don't switch. In either case, call
the appropriate debugger.
While you're at the debug prompt, enter "?" for a list of commands.
Normally, the interactive debugger uses stdin and stdout for its input and
output. If you need to use another source of input or output, you can write
custom replacement functions for puts() and gets(), and pass their addresses
via "put" and "get".
Normally, the debugger calls s68000exec() directly to step through each
instruction. If your emulator manages its own timing, you can write a custom
replacement single-step function, and pass its address via "execstep".
The debugger command 'h' (Hardware dump) will call the function specified by
"dump", if it exists.
3.11. Emulating the 68010
--------------------------
To generate a 68010 emulator, use the "-cputype 68010" option when building
your source code file. The names of all identifiers will begin with s68010
instead of s68000 (unless overridden by the -name option).
When emulating the 68010, use the S68010CONTEXT structure instead of
S68000CONTEXT.
S68010CONTEXT has six new fields:
unsigned char sfc;
unsigned char dfc;
unsigned vbr;
void (*bkpthandler)(void);
unsigned char loopmode;
unsigned char contextfiller10[3];
"vbr", "sfc", and "dfc" are control registers.
"bkpthandler" is a user-defined routine for BKPT notification (see section
3.8).
"loopmode" is used internally for loop mode timing. (Did I mention the loop
mode timing is insanely accurate?)
"contextfiller10" is there just to maintain alignment.
An invalid MOVEC register code will cause s68010exec to return with an
invalid instruction error, pointing to the MOVEC instruction (not the code
itself).
NOTE: The interactive debugger currently does not support the 68010.
-----------------------------------------------------------------------------
4. Tricks of the Trade
-----------------------------------------------------------------------------
4.1. Inter-CPU Communication
-----------------------------
Many arcade and console game systems which use multiple CPUs use a sort of
"mailbox" technique to allow the main CPU to send messages to the sub CPU
(sound effect numbers, etc.)
When one of your 68000s writes to another CPU's mailbox, it's generally a
good idea to transfer control over to the other CPU temporarily. This can be
achieved by calling s68000releaseTimeslice() from your I/O handler. This
will cause s68000exec to exit, even if it's not finished. (Always check the
odometer to see how many cycles were really executed!)
4.2. Other Helpful Tips
------------------------
When defining your memory map(s):
* Take advantage of the built-in RAM and ROM handling as much as possible.
They involve much less "red tape" than using your own handlers.
* The addresses don't have to be in order. You can rearrange them so that
the most commonly-accessed areas are at the beginning. This will shave a
few cycles off each access.
* If you have two areas of fetchable RAM or ROM that are right next to each
other, consider coalescing them into one area. This will make the memory
map simpler and facilitate fetch region caching. It will also allow the
680x0 code to wander from one area into the next. Starscream only checks
boundaries after JMP, JSR, RTS, RTR, RTD, RTE, and exceptions.
When executing code:
* Instead of calling s68000exec with a fixed number of cycles, keep track of
how many cycles overflowed from the last call to s68000exec, and subtract
them:
#define TIMESLICE 100000
s68000context.odometer = 0;
while(!done) {
if (s68000context.odometer < TIMESLICE) {
s68000exec(TIMESLICE - s68000context.odometer);
}
s68000context.odometer -= TIMESLICE;
}
* Use big timeslices. The fewer calls you make to s68000exec, the better.
4.3. Pitfalls
--------------
* Make sure to include "starcpu.h" in any of your C modules that use
Starscream. Also remember that starcpu.h is subject to change in new
versions. (Hint hint.)
* Remember to call s68000init before executing any code. s68000init doesn't
automatically call s68000reset, so you must call s68000reset also.
* Remember to set up your memory map before calling s68000reset. The memory
map is required in order to read the initial SSP/PC from the vector table.
* When you store a context via s68000SetContext, remember to read it back
via s68000GetContext.
* The interactive debugger can only read memory areas that are defined in
your STARSCREAM_PROGRAMREGION array. Everything else will appear as FFFF.
The upshot of this is that a memory dump is guaranteed not to trigger any
I/O hardware.
* Simplify your STARSCREAM_PROGRAMREGION map as much as possible, in case
the emulated code decides to wander from one area to another.
* Remember the rules for read/write handlers (section 3.5), and remember
that they also apply to RESET and BKPT handlers (section 3.9).
* When you use your own contexts, make sure to explicitly set "resethandler"
and "bkpthandler" to NULL if you're not using them. (This is why it's a
good idea to initialize every byte of a new context to zero.)
4.4. Ways to Abuse Starscream
------------------------------
For the adventurous...
* Upon encountering an unrecognized opcode, s68000exec will return the
address of the opcode, instead of the success code, 0x80000000. If you
need to emulate an instruction that's not implemented yet, you can take
advantage of this behavior and implement it in your own code.
Outside of s68000exec, the context can be modified freely. You can use
the s68000fetch routine to fetch the unrecognized opcode and anything
after it (see section 3.1 for the declaration of s68000fetch).
* Using 32-bit addresses (-addressbits 32) saves one cycle for every memory
access, and simplifies the PC re-basing process. This is safe only for
software that is "32-bit clean", however.
* Make a stand-alone 68000 disassembler out of CPUDEBUG.C. ;)
4.5. Emulating Other 68K-Series CPUs
-------------------------------------
Note: None of these are officially supported.
68008
Try "-cputype 68000 -addressbits 20" or "-cputype 68000 -addressbits 22",
depending on the chip package. You'll have to lower the clock speed to get
more accurate timing (try dividing it in half), and you might have to
compensate for the 8-bit data bus.
68EC000
"-cputype 68000" should work. If it's running in 8-bit mode, try dividing
the clock speed in half.
SCC68070
Try "-cputype 68000 -addressbits 32". Not sure about timing issues.
6833X (CPU32)
Try "-cputype 68010". Timing might be a little slow. A few instructions
aren't supported.
68020 and higher
Try "-cputype 68010 -addressbits 32". This is a bit of a stretch, though,
since the 68020 supports many new address modes and instructions. Failing
that, wait until official 68020 support is added. ;)
-----------------------------------------------------------------------------
5. Known Bugs
-----------------------------------------------------------------------------
* Address and Bus Errors are not implemented. For arcade game emulation,
this is probably OK, but if you're emulating a home computer system, this
could be cause for concern.
* The MOVES instruction (68010+) is not implemented.
* Stack frame format 8 (68010+) is not supported. Starscream will never
generate such a stack frame itself; however, if this format is
encountered during a RTE, s68010exec() will return with an "Unsupported
stack frame" error.
* There are a few timing inaccuracies:
68000: DIVS and DIVU: +/- 5%
68010: MULS and MULU are inaccurate, but I'm not sure by how much.
* Flags which Motorola documents as "Undefined" are, in fact, undefined.
I could probably look up how they're calculated, and implement them, but
the need has not arisen (yet).
-----------------------------------------------------------------------------
6. Credits
-----------------------------------------------------------------------------
Thanks to:
* Heinz Seltmann, for letting me borrow his M68000 Microprocessor User's
Manual (Ninth Edition) for way too long.
* Neil Bradley, Mike Cuddy, James Boulton, Richard Bush, and Dave (of DTMNT/
DGen fame) for various tips and ideas.
* Thierry Lescot, who gave me the idea to distribute Starscream in the first
place.