A portable Motorola M680x0 processor emulation engine.
Musashi is a Motorola 68000, 68010, 68EC020, and 68020 emulator written in C. This emulator was written with two goals in mind: portability and speed.
The emulator is written to ANSI C89 specifications. It also uses inline functions, which are C9X compliant.
It has been successfully running in the MAME project for years and so has had time to mature.
Musashi is available under the terms of the MIT license.
The basic configuration will give you a standard 68000 that has sufficient functionality to work in a primitive environment.
This setup assumes that you only have 1 device interrupting it, that the device will always request an autovectored interrupt, and it will always clear the interrupt before the interrupt service routine finishes (but could possibly re-assert the interrupt). You will have only one address space, no tracing, and no instruction prefetch.
To implement the basic configuration:
-
Open
m68kconf.hand verify that the settings forINLINEwill work with your compiler. (Currently set tostatic __inline__, which works in gcc 2.9. For C9X compliance, it should beinline) -
In your host program, implement the following functions:
unsigned int m68k_read_memory_8(unsigned int address);unsigned int m68k_read_memory_16(unsigned int address);unsigned int m68k_read_memory_32(unsigned int address);void m68k_write_memory_8(unsigned int address, unsigned int value);void m68k_write_memory_16(unsigned int address, unsigned int value);void m68k_write_memory_32(unsigned int address, unsigned int value); -
In your host program, be sure to call
m68k_pulse_reset()once before calling any of the other functions as this initializes the core. -
Use
m68k_execute()to execute instructions andm68k_set_irq()to cause an interrupt.
The interrupt handling in the basic configuration doesn't emulate the interrupt acknowledge phase of the CPU and automatically clears an interrupt request during interrupt processing. While this works for most systems, you may need more accurate interrupt handling.
To add proper interrupt handling:
-
In
m68kconf.h, setM68K_EMULATE_INT_ACKtoOPT_SPECIFY_HANDLER -
In
m68kconf.h, setM68K_INT_ACK_CALLBACK(A)to your interrupt acknowledge routine -
Your interrupt acknowledge routine must return an interrupt vector,
M68K_INT_ACK_AUTOVECTOR, orM68K_INT_ACK_SPURIOUS. Most m68k implementations just use autovectored interrupts. -
When the interrupting device is satisfied, you must call
m68k_set_irq(0)to remove the interrupt request.
The above system will work if you have only one device interrupting the CPU, but if you have more than one device, you must do a bit more.
To add multiple interrupts:
-
You must make an interrupt arbitration device that will take the highest priority interrupt and encode it onto the IRQ pins on the CPU.
-
The interrupt arbitration device should use
m68k_set_irq()to set the highest pending interrupt, or 0 for no interrupts pending.
You can write faster memory access functions if you know whether you are fetching from ROM or RAM. Immediate reads are always from the program space (Always in ROM unless it is running self-modifying code).
To enable separate immediate reads:
-
In
m68kconf.h, turn onM68K_SEPARATE_READ_IMM. -
In your host program, implement the following functions:
unsigned int m68k_read_immediate_16(unsigned int address);unsigned int m68k_read_immediate_32(unsigned int address); -
If you need to know the current PC (for banking and such), set
M68K_MONITOR_PCtoOPT_SPECIFY_HANDLER, and setM68K_SET_PC_CALLBACK(A)to your routine.
Most systems will only implement one address space, placing ROM at the lower addresses and RAM at the higher. However, there is the possibility that a system will implement ROM and RAM in the same address range, but in different address spaces.
In this case, you might get away with assuming that immediate reads are in the program space and all other reads are in the data space, if it weren't for the fact that the exception vectors are fetched from the data space. As a result, anyone implementing this kind of system will have to copy the vector table from ROM to RAM using PC-relative instructions.
This makes things bad for emulation, because this means that a non-immediate read is not necessarily in the data space. The m68k deals with this by encoding the requested address space on the function code pins:
FC
Address Space 210
------------------ ---
USER DATA 001
USER PROGRAM 010
SUPERVISOR DATA 101
SUPERVISOR PROGRAM 110
CPU SPACE 111 <-- not emulated in this core since we emulate
interrupt acknowledge in another way.
To emulate the function code pins:
-
In
m68kconf.h, setM68K_EMULATE_FCtoOPT_SPECIFY_HANDLERand setM68K_SET_FC_CALLBACK(A)to your function code handler function. -
Your function code handler should select the proper address space for subsequent calls to
m68k_read_xx(andm68k_write_xxfor 68010+).
Note: immediate reads are always done from program space, so technically you don't need to implement the separate immediate reads, although you could gain more speed improvements leaving them in and doing some clever programming.
The default is to enable only the 68000 CPU type. To change this, change the
settings for M68K_EMULATE_010 etc in m68kconf.h.
To set the CPU type you want to use:
-
Make sure it is enabled in
m68kconf.h. Current switches are:M68K_EMULATE_010M68K_EMULATE_EC020M68K_EMULATE_020 -
In your host program, call
m68k_set_cpu_type()and then callm68k_pulse_reset(). Valid CPU types are:M68K_CPU_TYPE_68000,M68K_CPU_TYPE_68010,M68K_CPU_TYPE_68EC020,M68K_CPU_TYPE_68020
In order to emulate the correct clock frequency, you will have to calculate
how long it takes the emulation to execute a certain number of "cycles" and
vary your calls to m68k_execute() accordingly.
It is a good idea to take away the CPU's timeslice when it writes to a memory-mapped port in order to give the device it wrote to a chance to react.
You can use the functions m68k_cycles_run(), m68k_cycles_remaining(),
m68k_modify_timeslice(), and m68k_end_timeslice() to do this.
Try to use large cycle values in your calls to m68k_execute() since it will
increase throughput. You can always take away the timeslice later.
You may need to enable these in order to properly emulate some of the more obscure functions of the m68k:
M68K_EMULATE_BKPT_ACKcauses the CPU to call a breakpoint handler on aBKPTinstructionM68K_EMULATE_TRACEcauses the CPU to generate trace exceptions when the trace bits are setM68K_EMULATE_RESETcauses the CPU to call a reset handler on a RESET instruction.M68K_EMULATE_PREFETCHemulates the 4-word instruction prefetch that is part of the 68000/68010 (needed for Amiga emulation). NOTE: if the CPU fetches a word or long word at an odd address when this option is on, it will yield unpredictable results, which is why a real 68000 will generate an address error exception.M68K_EMULATE_ADDRESS_ERRORwill cause the CPU to generate address error exceptions if it attempts to read a word or long word at an odd address.- call
m68k_pulse_halt()to emulate theHALTpin.
These are in here for programmer convenience:
M68K_INSTRUCTION_HOOKlets you call a handler before each instruction.M68K_LOG_ENABLEandM68K_LOG_1010_1111lets you log illegal and A/F-line instructions.
The default is to use only one CPU. To use more than one CPU in this core, there are some things to keep in mind:
- To have different CPUs call different functions, use
OPT_ONinstead ofOPT_SPECIFY_HANDLER, and use them68k_set_xxx_callback()functions to set your callback handlers on a per-CPU basis. - Be sure to call
set_cpu_type()for each CPU you use. - Use
m68k_set_context()andm68k_get_context()to switch to another CPU.
You can use the m68k_load_context() and m68k_save_context() functions to load
and save the CPU state to disk.
You can use m68k_get_reg() and m68k_set_reg() to gain access to the internals
of the CPU.
The subdir example contains a full example (currently DOS only).
rm -rf `pwd`/_build/ `pwd`/_install/
cmake -H`pwd` -B`pwd`/_build/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=`pwd`/_install/
cmake --build `pwd`/_build/ --target all --config Debug -- -j 1