forked from KolibriOS/kolibrios
ea1a60faa3
git-svn-id: svn://kolibrios.org@9837 a494cfbc-eb01-0410-851d-a64ba20cac60
810 lines
30 KiB
Plaintext
810 lines
30 KiB
Plaintext
Multi-Z80 32 Bit emulator
|
|
Copyright 1996, 1997, 1998, 1999, 2000 - Neil Bradley, All rights reserved
|
|
|
|
MZ80 License agreement
|
|
-----------------------
|
|
|
|
(MZ80 Refers to both the assembly code emitted by makez80.c and makez80.c
|
|
itself)
|
|
|
|
MZ80 May be distributed in unmodified form to any medium.
|
|
|
|
MZ80 May not be sold, or sold as a part of a commercial package without
|
|
the express written permission of Neil Bradley (neil@synthcom.com). This
|
|
includes shareware.
|
|
|
|
Modified versions of MZ80 may not be publicly redistributed without author
|
|
approval (neil@synthcom.com). This includes distributing via a publicly
|
|
accessible LAN. You may make your own source modifications and distribute
|
|
MZ80 in source or object form, but if you make modifications to MZ80
|
|
then it should be noted in the top as a comment in makez80.c.
|
|
|
|
MZ80 Licensing for commercial applications is available. Please email
|
|
neil@synthcom.com for details.
|
|
|
|
Synthcom Systems, Inc, and Neil Bradley will not be held responsible for
|
|
any damage done by the use of MZ80. It is purely "as-is".
|
|
|
|
If you use MZ80 in a freeware application, credit in the following text:
|
|
|
|
"Multi-Z80 CPU emulator by Neil Bradley (neil@synthcom.com)"
|
|
|
|
must accompany the freeware application within the application itself or
|
|
in the documentation.
|
|
|
|
Legal stuff aside:
|
|
|
|
If you find problems with MZ80, please email the author so they can get
|
|
resolved. If you find a bug and fix it, please also email the author so
|
|
that those bug fixes can be propogated to the installed base of MZ80
|
|
users. If you find performance improvements or problems with MZ80, please
|
|
email the author with your changes/suggestions and they will be rolled in
|
|
with subsequent releases of MZ80.
|
|
|
|
The whole idea of this emulator is to have the fastest available 32 bit
|
|
Multi-Z80 emulator for the x86, giving maximum performance.
|
|
|
|
MZ80 Contact information
|
|
-------------------------
|
|
|
|
Author : Neil Bradley (neil@synthcom.com)
|
|
Distribution: ftp://ftp.synthcom.com/pub/emulators/cpu/makez80.zip (latest)
|
|
|
|
You can join the cpuemu mailing list on Synthcom for discussion of Neil
|
|
Bradley's Z80 (and other) CPU emulators. Send a message to
|
|
"cpuemu-request@synthcom.com" with "subscribe" in the message body. The
|
|
traffic is fairly low, and is used as a general discussion and announcement
|
|
for aforementioned emulators.
|
|
|
|
|
|
MZ80 Documentation
|
|
-------------------
|
|
|
|
MZ80 Is a full featured Z80 emulator coded in 32 bit assembly. It runs well
|
|
over a hundred games, in addition to it supporting many undocumented Z80
|
|
instructions required to run some of the Midway MCR games, Galaga, and
|
|
countless other wonderful Z80 based arcade games.
|
|
|
|
MZ80 Contains a makez80.c program that must be compiled. It is the program
|
|
that emits the assembly code that NASM will compile. This minimizes the
|
|
possibility of bugs creeping in to MZ80 for the different addressing modes
|
|
for each instruction. It requires NASM 0.97 or greater.
|
|
|
|
The goal of MZ80 is to have a high performance Z80 emulator that is capable
|
|
of running multiple emulations concurrently at full speed, even on lower-end
|
|
machines (486/33). MZ80 Harnesses the striking similarities of both the Z80
|
|
and the x86 instruction sets to take advantage of flag handling which greatly
|
|
reduces the time required to emulate a processor, so no extra time is spent
|
|
computing things that are already available in the native x86 processor,
|
|
allowing it to perform leaps and bounds over comparable C based Z80 emulators
|
|
on the same platform.
|
|
|
|
MZ80 Is designed exclusively for use with NASM, the Netwide Assembler. This
|
|
gives the ultimate in flexibility, as NASM can emit object files that work
|
|
with Watcom, Microsoft Visual C++ (4.0-current), DJGPP, Borland C++, and
|
|
gcc under FreeBSD or Linux. MZ80 Has been tested with each one of these
|
|
compilers and is known to work properly on each.
|
|
|
|
|
|
What's in the package
|
|
---------------------
|
|
|
|
MZ80.TXT - This text file
|
|
|
|
MAKEZ80.C - Multi Z80 32 Bit emulator emitter program
|
|
|
|
MZ80.H - C Header file for MZ80 functions
|
|
|
|
|
|
What's new in this release
|
|
--------------------------
|
|
|
|
Revision 3.4:
|
|
|
|
* Fixed the overflow flag not getting cleared in the SetOverflow()
|
|
routine. It caused strange problems with a handful of Genesis games
|
|
* Removed invalid instruction in the C version so that more
|
|
instructions will execute
|
|
|
|
Revision 3.3:
|
|
|
|
* Undocumented opcodes added to the C emitter
|
|
* Bug fix to the C emission that properly handles shared RAM regions
|
|
(I.E. with handlers that are NULL)
|
|
* Now using 32 bit registers to do register/memory access. Slight
|
|
speed increase (assembly version only)
|
|
|
|
Revision 3.2:
|
|
|
|
* R Register emulation now accurate with a real Z80
|
|
* mz80int() Called when interrupts are disabled causes the
|
|
z80intPending flag to be set, and an interrupt will be caused after
|
|
the execution of EI and the next instruction. See "IMPORTANT NOTE
|
|
ABOUT INTERRUPTS" below
|
|
* The instruction after EI executes fully before interrupt status is
|
|
checked. (as does a real Z80)
|
|
|
|
|
|
Revision 3.1:
|
|
|
|
* Fixed bug in memory dereference when handler was set to NULL (keeps
|
|
system from crashing or faulting)
|
|
* Removed the only stricmp() from the entire file and replaced it
|
|
with strcmp() so that stdlibs without it will compile
|
|
* Changed cyclesRemaining > 0 to cyclesRemaining >= 0 to be compatible
|
|
with the ASM core
|
|
* Removed additional sub [dwCyclesRemaining], 5 at the beginning of
|
|
mz80exec() (ASM Core only). Increases timing accuracy.
|
|
* NMIs And INTs add additional time to dwElapsedTicks as it should
|
|
* mz80ReleaseTimeslice() Sets remaining clocks to 0 instead of 1
|
|
|
|
|
|
Revision 3.0:
|
|
|
|
* All instructions validated against a real Z80. Used an ISA card
|
|
with a Z80 on it to validate flag handling, instruction handling,
|
|
timing, and other goodies. The only thing not implemented/emulated
|
|
is flag bit 3 & 5 emulation. Believed to be 100% bug free!
|
|
* 80% Speed improvement over version 2.7 of mz80
|
|
* z80stb.c Removed. Use -c to emit a C version of mz80! API compatible!
|
|
Note that this is mostly, but not fully, debugged, so consider the
|
|
C version a beta! It's at least healthier than z80stb.c was. The C
|
|
version does not include the undocumented Z80 instructions.
|
|
* mz80nmi() No longer trashes registers it uses when using -cs
|
|
* IN/OUT Instructions work properly when using -16
|
|
* IN A, (xxh) uses A as high 8 bits of I/O fetch address when using -16
|
|
* IM 0/IM 1 Description in documentation fixed
|
|
* Sizes of all context registers increased to 32 bits - for speed!
|
|
* IFF1/IFF2 Now properly emulated
|
|
* JR Instruction offset can fetch from $ffff and properly wrap
|
|
* LDIR/LDDR Instruction now won't go to completion - instead it will
|
|
run until BC=0 or the # of cycles to execute have expired. These
|
|
instructions used to run to completion - even beyond the # of cycles
|
|
left to execute
|
|
* INI/IND/INIR/INDR countdown bug fixed - it was decrementing B twice
|
|
for each IN! Whoops!
|
|
* If you specify NULL as a handler address to a memory region, mz80 will
|
|
use vpData as a pointer to where that block of data resides. Quite
|
|
useful for multiprocessor emulations that share the same memory.
|
|
* EDI Now keeps track of cycle counting for faster execution
|
|
* Modified memory region scanning code to use 32 bit registers instead
|
|
of their 16 bit counterparts
|
|
* Get/SetContext() uses rep movsd/movsb. Insignificant overall, but
|
|
why waste the time?
|
|
* Debugging routines added. See the "DEBUGGING" section below for more
|
|
information. NOTE: The debugging routines are not yet available in
|
|
the C emission.
|
|
* Timing done slightly differently now. Mz80 now executes one
|
|
instruction past the timing given on input. For example, mz80exec(0)
|
|
will cause a single instruction to be executed (thusly -ss was
|
|
removed).
|
|
|
|
Revision 2.7:
|
|
|
|
* Fixed OTIR/OTDR/INIR/INDR instructions so their 16 bit counterparts
|
|
work properly
|
|
* Emulation core 30-70% faster overall than 2.6 due to optimization to
|
|
the timing routines
|
|
* Replaced word reads/writes with a special word write routine rather
|
|
than the standard calling to read/write byte functions
|
|
* z80stb.c (the C equivalent of mz80) compiles properly now
|
|
* Fixed OS/2 text/segment issue
|
|
* Fixed bug in set/getCPU context that ensures that ES=DS and avoids
|
|
crashes. Caused crashes under OS/2 and other OS's
|
|
|
|
Revision 2.6:
|
|
|
|
* Emulator core 5-30% faster overall. Some 16 and 8 bit instructions
|
|
sped up when using their 32 bit equivalents.
|
|
* Fix to -l so that proper labels without leading and trailing
|
|
underscores so Linux/FreeBSD compiles will work properly
|
|
* Single step now executes the # of instructions passed in to z80exec()
|
|
instead of just 1 as it had in prior releases. This is only active
|
|
when the -ss option is used.
|
|
* The -nt option was added. This will cause the timing information to
|
|
not be added in, speeding up execution. Warning: Only do this if your
|
|
emulated target does not require instruction timing!
|
|
* Updated documentation errors
|
|
* C Version of mz80 (mz80.c) that is API compliant is distributed with
|
|
the archive (With kind permission of Edward Massey).
|
|
|
|
Revision 2.5:
|
|
|
|
* Fixed an unconditional flag being cleared in the ddcbxx instructions.
|
|
It caused Donkey Kong's barrels to not roll.
|
|
|
|
Revision 2.4:
|
|
|
|
* Fixed improper HALT handling (didn't advance the PTR when it should)
|
|
* Fixed SRL (IX+$xx) instruction so that carry wasn't trashed
|
|
* Fixed single stepping problems with it giving too much time to
|
|
any given instruction
|
|
* Fixed half carry flag handling with 16 bit SBC and ADD instructions
|
|
* Fixed DAA emulation so that parity flags weren't getting trashed
|
|
|
|
Revision 2.3:
|
|
|
|
* Fixed many stack handling bugs
|
|
* Timing problems fixed. The prior version was causing massive
|
|
overruns on maximum timeslices with some insutructions.
|
|
|
|
Revision 2.2:
|
|
|
|
* Fixed a bug in CPI/CPD/CPIR/CPDR that mishandled flags
|
|
* All known bugs are out of mz80 now
|
|
* Added the -cs option to route all stack operations through the
|
|
handlers (required for games like Galaga)
|
|
|
|
Revision 2.1:
|
|
|
|
* Fixed a bug in CPI/CPD/CPIR/CPDR that caused intermittent lockups.
|
|
Also fixed a bug that caused erratic behavior in several video games.
|
|
* Added INI/IND/INIR/INDR instruction group
|
|
* Added OUTI/OUTD/OTIR/OTDR instruction group
|
|
|
|
Revision 1.0:
|
|
|
|
* First release! The whole thing is new!
|
|
|
|
|
|
ASSEMBLING FOR USE WITH WATCOM C/C++
|
|
------------------------------------
|
|
|
|
Watcom, by default, uses register calling conventions, as does MZ80. To
|
|
create a proper emulator for Watcom:
|
|
|
|
makez80 MZ80.asm -x86
|
|
|
|
From here:
|
|
|
|
nasm -f win32 MZ80.asm
|
|
|
|
Link the MZ80.obj with your Watcom linker.
|
|
|
|
|
|
ASSEMBLING FOR USE WITH MICROSOFT VISUAL C++ AND BORLAND C++
|
|
--------------------------------------------------------------------
|
|
|
|
Visual C++ and Borland C++ use stack calling conventions by default. To
|
|
create a proper emulator for these compilers:
|
|
|
|
makez80 MZ80.asm -s -x86
|
|
|
|
For Visual C++ or Borland C++:
|
|
|
|
nasm -f win32 MZ80.asm
|
|
|
|
Link with your standard Visual C++ or Borland C++.
|
|
|
|
|
|
ASSEMBLING FOR USE WITH DJGPP, GCC/FREEBSD, OR GCC/LINUX
|
|
--------------------------------------------------------------------
|
|
|
|
DJGPP Uses stack calling conventions:
|
|
|
|
makez80 MZ80.asm -s -x86
|
|
|
|
To assemble:
|
|
|
|
nasm -f coff MZ80.asm
|
|
|
|
Link with your standard DJGPP linker. The same holds true for GCC under
|
|
FreeBSD or Linux. If you're using GCC, use the -l option to generate "plain"
|
|
labels so that gcc's linker will properly link things.
|
|
|
|
|
|
MAKEZ80 COMMAND LINE OPTIONS
|
|
----------------------------
|
|
|
|
-s - Use stack calling conventions (DJGPP, MSVC, Borland, etc...)
|
|
|
|
-cs - Force all stack operations to go through the Read/Write memory handlers.
|
|
This slows things down, but is useful when needed.
|
|
|
|
-16 - Treat all I/O input and output as 16 bit (BC)
|
|
|
|
-l - Create 'plain' labels - ones without leading and trailing underscores
|
|
|
|
-nt - Do not generate timing code - this speeds the emulator up, but the
|
|
downside is that no timing info is available.
|
|
|
|
-c - Emit a C mz80 emulator (API Compatible with the assembly version -
|
|
handy for porters!)
|
|
|
|
-x86 - Emit an assembly (x86) mz80 emulator
|
|
|
|
-os2 - Generate OS/2 compatible segmentation
|
|
|
|
|
|
IMPORTANT NOTE ABOUT INTERRUPTS
|
|
-------------------------------
|
|
|
|
A minor change was made between the 3.1 and 3.2 versions of makez80 in the
|
|
way that interrupts were handled.
|
|
|
|
On a real Z80, the !INT line is a level triggered interrupt, meaning that if
|
|
the interrupt line is held low, the Z80 will continue to take interrupts
|
|
immediately after the instruction after the EI instruction is executed until
|
|
the interrupt line is high again.
|
|
|
|
In 3.1, if an interrupt came in and interrupts were disabled, the interrupt
|
|
would never be "latched" for later execution. The Z80 does not have any
|
|
internal latching capabilities, however external hardware often does hold
|
|
the interrupt line low until the interrupt is executed, in effect, a latch.
|
|
|
|
I've only found one video game so far that requires the "raising/lowering"
|
|
of the interrupt line (Ataxx). In the games that I've tried, it has improved
|
|
performance, in some cases drastically, and in others not at all. This can
|
|
be accounted for by interrupts being taken now, where they were being dropped
|
|
in prior mz80 releases.
|
|
|
|
mz80 Emulates the most commonly used scenario. Now when mz80int() is executed
|
|
and a nonzero value is returned (indicating interrupts were disabled), it
|
|
will set z80intPending, and the interrupt will be taken after execution of
|
|
one instruction beyond the EI instruction.
|
|
|
|
So now, if mz80int() returns a nonzero value, that means an interrupt is
|
|
latched. If clearing this latch is desired or the old behavior of 3.1 is
|
|
desired, make a call to the mz80ClearPendingInterrupt() call. It's a 2
|
|
instruction call that has extremely small overhead and will not affect
|
|
performance in any measurable way.
|
|
|
|
In any case, MZ80 will now execute one instruction after EI regardless of
|
|
how much time is available to avoid the possibility of an interrupt request
|
|
coming in directly after the EI instruction.
|
|
|
|
|
|
STEPS TO EMULATION
|
|
------------------
|
|
|
|
NOTE: -16 Is a command line option that will treat all I/O as 16 bit. That
|
|
is, in an instruction like "IN AL, (C)", the addressed passed to the I/O
|
|
handler will be BC instead of just C. Bear this in mind when considering your
|
|
emulated platform.
|
|
|
|
There are a few steps you want to go through to get proper emulation, and a
|
|
few guidelines must be followed.
|
|
|
|
1) Create a MZ80CONTEXT
|
|
|
|
2) Create your virtual 64K memory space using whatever means of obtaining
|
|
memory you need to do.
|
|
|
|
3) Set mz80Base in your context to be the base of your 64K memory space
|
|
|
|
4) Load up your image to be emulated within that 64K address space.
|
|
|
|
5) Set z80IoRead and z80IoWrite to their appropriate structure arrays. Here's
|
|
an example:
|
|
|
|
struct z80PortRead ReadPorts[] =
|
|
{
|
|
{0x10, 0x1f, SoundChip1Read},
|
|
{0x20, 0x2f, SoundChip2Read}
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
};
|
|
|
|
When an IN instruction occurs, mz80 will probe this table looking for a
|
|
handler to the address of the "IN" instruction. If it is found in the list,
|
|
it's up to the handler to return the proper value. Otherwise, a value of
|
|
0ffh is returned internally if no handler for that I/O address is found. In
|
|
the case above, SoundChip1Read is called when the I/O address is between 0x10-
|
|
0x1f. A similar structure is used for I/O writes as well (OUT):
|
|
|
|
struct z80PortWrite WritePorts[] =
|
|
{
|
|
{0x20, 0x2f, SoundChip2Write},
|
|
{0x30, 0x36, VideoCtrlWrite},
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
}
|
|
|
|
Of course, this does the opposite that the z80PortRead struct, and instead
|
|
looks for a handler to hand some data to. If it doesn't find an appropriate
|
|
handler, nothing happens.
|
|
|
|
6) Set mz80MemoryRead & mz80MemoryWrite to their appropriate structure
|
|
arrays. Here is an example:
|
|
|
|
struct MemoryWriteByte GameWrite[] =
|
|
{
|
|
{0x3000, 0x3fff, VideoWrite},
|
|
{0x4000, 0x4fff, SpriteWrite},
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
};
|
|
|
|
The above example says that any time a write occurs in the 0x3000-0x3fff
|
|
range, call the VideoWrite routine. The same holds true for the SpriteWrite
|
|
region as well.
|
|
|
|
NOTE: When your write handler is called, it is passed the address of the
|
|
write and the data that is to be written to it. If your handler doesn't
|
|
write the data to the virtual image, the mz80 internal code will not.
|
|
|
|
NOTE: These routines will *NOT* be called when execution asks for these
|
|
addresses. It will only call them when a particular instruction uses the
|
|
memory at these locations.
|
|
|
|
If you wish for a region to be RAM, just leave it out of your memory region
|
|
exception list. The WriteMemoryByte routine will treat it as read/write
|
|
RAM and will write to mz80Base + addr directly.
|
|
|
|
If you wish to protect ROM regions (not often necessary), create a range that
|
|
encompasses the ROM image, and have it call a routine that does nothing. This
|
|
will prevent data from being written back onto the ROM image.
|
|
|
|
Leave your last entry in the table as shown above, with a null handler and
|
|
0xffffffff-0xffffffff as your read address. Even though the Z80 only
|
|
addresses 64K of space, the read/write handlers are defined as 32 bit so
|
|
the compiler won't pass junk in the upper 16 bits of the address lines. Not
|
|
only that, it allows orthoganality for future CPU emulators that may use
|
|
these upper bits.
|
|
|
|
You can do a mz80GetContext() if you'd like to read the current context of
|
|
the registers. Note that by the time your handler gets called, the program
|
|
counter will be pointing to the *NEXT* instruction.
|
|
|
|
struct MemoryReadByte GameRead[] =
|
|
{
|
|
{0x2000, 0x200f, ReadHandler},
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
};
|
|
|
|
Same story here. If you have a special handler for an attempted read at a
|
|
particular address, place its range in this table and create a handler
|
|
routine for it.
|
|
|
|
If you don't define a handler for a particular region, then the ReadMemoryByte
|
|
in mz80.ASM will actually read the value out of mz80Base + the offset
|
|
required to complete the instruction.
|
|
|
|
7) Set the intAddr and nmiAddr to the addresses where you want mz80 to start
|
|
executing when an interrupt or NMI happens. Take a look at the section
|
|
entitled "INTERRUPTS" below for more information on this.
|
|
|
|
8) Call mz80SetContext() on your Z80 context
|
|
|
|
9) Call mz80Reset(). This will prime the program counter and cause a virtual
|
|
CPU-wide reset.
|
|
|
|
10) Once you have those defined, you're ready to begin emulation. There's some
|
|
sort of main loop that you'll want. Maybe something like:
|
|
|
|
while (hit == 0)
|
|
{
|
|
if (lastSec != (UINT32) time(0))
|
|
{
|
|
diff = (mz80clockticks - prior) / 3000000;
|
|
printf("%ld Clockticks, %ld frames, %ld Times original speed\n", MZ80clockticks - prior, frames, diff);
|
|
frames = 0;
|
|
prior = mz80clockticks;
|
|
lastSec = time(0);
|
|
if (kbhit())
|
|
{
|
|
getch();
|
|
hit = 1;
|
|
}
|
|
}
|
|
|
|
/* 9000 Cycles per NMI (~3 milliseconds @ 3MHZ) */
|
|
|
|
dwResult = mz80exec(9000);
|
|
mz80clockticks += mz80GetElapsedTicks(TRUE);
|
|
mz80nmi();
|
|
|
|
/* If the result is not 0x80000000, it's an address where
|
|
an invalid instruction was hit. */
|
|
|
|
if (0x80000000 != dwResult)
|
|
{
|
|
mz80GetContext(&sCpu1);
|
|
printf("Invalid instruction at %.2x\n", sCpu1.MZ80pc);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
Call mz80exec() With the # of virtual CPU cycles you'd like mz80 to
|
|
execute. Be sure to use the mz80GetElapsedTicks() call *AFTER* execution to
|
|
see how many virtual CPU cycles it actually executed. For example, if you tell
|
|
mz80 to execute 500 virtual CPU cycles, it will execute slightly more. Anything
|
|
from 500 to 524 (24 cycles being the longest any 1 instruction takes in the
|
|
Z80).
|
|
|
|
Use the mz80GetElapsedTicks() call for more accurate cycle counting. Of course,
|
|
this is only if you have *NOT* included the -nt option.
|
|
|
|
If you pass FALSE to the mz80GetElapsedTicks() function, the internal CPU
|
|
elapsed tick clock will not be reset. The elapsed tick counter is something
|
|
that continues to increase every emulated instruction, and like an odometer,
|
|
will keep counting unless you pass TRUE to mz80GetElapsedTicks(), of which
|
|
case it will return you the current value of the elapsed ticks and set it to
|
|
0 when complete.
|
|
|
|
NOTE: The bigger value you pass to mz80exec, the greater benefit you get out
|
|
of the virtual registers persisting within the emulator, and it will run
|
|
faster. Pass in a value that is large enough to take advantage of it, but
|
|
not so often that you can't handle nmi or int's properly.
|
|
|
|
If you wish to create a virtual NMI, call mz80nmi(), and it will be taken
|
|
the next time you call mz80exec, or alternately if you have a handler call
|
|
mz80nmi/mz80int(), the interrupt will be taken upon return. Note that
|
|
mz80nmi() doesn't actually execute any code - it only primes the emulator to
|
|
begin executing NMI/INT code.
|
|
|
|
NOTE: mz80int() is defined with a UINT32 as a formal parameter. Depending
|
|
upon what interrupt mode you're executing in (described later), it may or may
|
|
not take a value.
|
|
|
|
NMI's can interrupt interrupts, but not the other way around - just like a
|
|
real Z80. If your program is already in an interrupt, another one will not be
|
|
taken. The same holds true for an NMI - Just like a real Z80!
|
|
|
|
|
|
MUTLI-PROCESSOR NOTES
|
|
---------------------
|
|
|
|
Doing multi processor support is a bit trickier, but is still fairly straight-
|
|
forward.
|
|
|
|
For each processor to be emulated, go through steps 1-7 above - giving each
|
|
CPU its own memory space, register storage, and read/write handlers.
|
|
|
|
|
|
EXECUTION OF MULTI-CPUS:
|
|
-------------------------
|
|
|
|
When you're ready to execute a given CPU, do the following:
|
|
|
|
mz80SetContext(contextPointer);
|
|
|
|
This will load up all information saved before into the emulator and ready it
|
|
for execution. Then execute step 7 above to do your virtual NMI's, interrupts,
|
|
etc... All CPU state information is saved within a context.
|
|
|
|
When the execution cycle is complete, do the following to save the updated
|
|
context away for later:
|
|
|
|
mz80GetContext(contextPointer);
|
|
|
|
Give each virtual processor a slice of time to execute. Don't make the values
|
|
too small or it will spend its time swapping contexts. While this in itself
|
|
isn't particularly CPU expensive, the more time you spend executing the better.
|
|
mz80 Keeps all of the Z80 register in native x86 register (including most
|
|
of the flags, HL, BC, and A). If no context swap is needed, then you get the
|
|
added advantage of the register storage. For example, let's say you were
|
|
running two Z80s - one at 2.0MHZ and one at 3.0MHZ. An example like this
|
|
might be desirable:
|
|
|
|
mz80SetContext(cpu1Context); // Set CPU #1's information
|
|
mz80exec(2000); // 2000 Instructions for 2.0MHZ CPU
|
|
mz80GetContext(cpu1Context); // Get CPU #1's state info
|
|
|
|
mz80SetContext(cpu2Context); // Set CPU #2's state information
|
|
mz80exec(3000); // 3000 Instructions for 3.0MHZ CPU
|
|
mz80GetContext(cpu2Context); // Get CPU #2's state information
|
|
|
|
This isn't entirely realistic, but if you keep the instruction or timing
|
|
ratios between the emulated CPUs even, then timing is a bit more accurate.
|
|
|
|
NOTE: If you need to make a particular CPU give up its own time cycle because
|
|
of a memory read/write, simply trap a particular address (say, a write to a
|
|
slave processor) and call mz80ReleaseTimeslice(). It will not execute any
|
|
further instructions, and will give up its timeslice. Put this in your
|
|
read/write memory trap.
|
|
|
|
NOTE: You are responsible for "holding back" the processor emulator from
|
|
running too fast.
|
|
|
|
|
|
INTERRUPTS
|
|
----------
|
|
|
|
The Z80 has three interrupt modes: IM 0 - IM 2. Each act differently. Here's
|
|
a description of each:
|
|
|
|
IM 0
|
|
|
|
This mode will cause the Z80 to be able to pull a "single byte instruction"
|
|
off the bus when an interrupt occurs. Since we're not doing bus cycle
|
|
emulation, it acts identically to mode 1 (described below). The formal
|
|
parameter to mz80int() is ignored. There is really no point in actually
|
|
emulating the instruction execution since any instruction that would be
|
|
executed would be a branch instruction!
|
|
|
|
IM 1
|
|
|
|
This mode is the "default" mode that the Z80 (and mz80 for that matter) comes
|
|
up in. When you call mz80reset(), the interrupt address is set to 38h and
|
|
the NMI address is set to 66h. So when you're in IM 1 and mz80int() is
|
|
called, the formal parameter is ignored and the z80intAddr/z80nmiAddr values
|
|
are appropriately loaded into the program counter.
|
|
|
|
IM 2
|
|
|
|
This mode causes the Z80 to read the upper 8 bits from the current value
|
|
of the "I" register, and the lower 8 bits from the value passed into mz80int().
|
|
So, if I contained 35h, and you did an mz80int(0x64), then an interrupt at
|
|
address 3564h would be taken. Simple!
|
|
|
|
|
|
OTHER GOODIES
|
|
-------------
|
|
|
|
MZ80 Has a nice feature for allowing the same handler to handle different
|
|
data regions on a single handler. Here's an example:
|
|
|
|
struct PokeyDataStruct Pokey1;
|
|
struct PokeyDataStruct Pokey2;
|
|
|
|
struct MemoryWriteByte GameWrite[] =
|
|
{
|
|
{0x1000, 0x100f, PokeyHandler, Pokey1},
|
|
{0x1010, 0x101f, PokeyHandler, Pokey2},
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
};
|
|
|
|
void PokeyHandler(UINT32 dwAddr, UINT8 bData, struct sMemoryWriteByte *psMem)
|
|
{
|
|
struct PokeyDataStruct *psPokey = psMem->pUserArea;
|
|
|
|
// Do stuff with psPokey here....
|
|
}
|
|
|
|
This passes in the pointer to the sMemoryWriteByte structure that caused
|
|
the handler to be called. The pUserArea is a user defined address that can
|
|
be anything. It is not necessary to fill it in with anything or even
|
|
initialize it if the handler doesn't actually use it.
|
|
|
|
This allows a single handler to handle multiple data references. This is
|
|
particularly useful when handling sound chip emulation, where there might
|
|
be more than one of a given device. Sure beats having multiple unique
|
|
handlers that are identical with the exception of the data area where it
|
|
writes! This allows a good deal of flexibility.
|
|
|
|
The same construct holds for MemoryReadByte, z80PortRead, and z80PortWrite,
|
|
so all can take advantage of this feature.
|
|
|
|
|
|
SHARED MEMORY FEATURES
|
|
----------------------
|
|
|
|
MZ80 Also has another useful feature for dealing with shared memory regions:
|
|
|
|
UINT8 bSharedRAM[0x100];
|
|
|
|
struct MemoryWriteByte Processor1[] =
|
|
{
|
|
{0x1000, 0x10ff, NULL, bSharedRAM},
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
};
|
|
|
|
struct MemoryWriteByte Processor2[] =
|
|
{
|
|
{0x1000, 0x10ff, NULL, bSharedRAM},
|
|
{(UINT32) -1, (UINT32) -1, NULL}
|
|
};
|
|
|
|
If the handler address is NULL, mz80 will look at the pUserArea field as a
|
|
pointer to RAM to read from/write to. This comes in extremely handy when you
|
|
have an emulation that requires two or more processors writing to the same
|
|
memory block. And it's lots faster than creating a handler that writes to
|
|
a common area as well.
|
|
|
|
|
|
DEBUGGING
|
|
---------
|
|
|
|
Several new functions have been added to mz80 that assist the emulator
|
|
author by providing a standard set of functions for register access:
|
|
|
|
UINT8 mz80SetRegisterValue(void *pContext, UINT32 dwRegister, UINT32 dwValue)
|
|
|
|
This allows setting of any register within the Z80. The register field can be
|
|
one of the following values (defined in mz80.h):
|
|
|
|
CPUREG_PC
|
|
CPUREG_Z80_AF
|
|
CPUREG_Z80_BC
|
|
CPUREG_Z80_DE
|
|
CPUREG_Z80_HL
|
|
CPUREG_Z80_AFPRIME
|
|
CPUREG_Z80_BCPRIME
|
|
CPUREG_Z80_DEPRIME
|
|
CPUREG_Z80_HLPRIME
|
|
CPUREG_Z80_IX
|
|
CPUREG_Z80_IY
|
|
CPUREG_Z80_SP
|
|
CPUREG_Z80_I
|
|
CPUREG_Z80_R
|
|
CPUREG_Z80_A
|
|
CPUREG_Z80_B
|
|
CPUREG_Z80_C
|
|
CPUREG_Z80_D
|
|
CPUREG_Z80_E
|
|
CPUREG_Z80_H
|
|
CPUREG_Z80_L
|
|
CPUREG_Z80_F
|
|
CPUREG_Z80_CARRY
|
|
CPUREG_Z80_NEGATIVE
|
|
CPUREG_Z80_PARITY
|
|
CPUREG_Z80_OVERFLOW
|
|
CPUREG_Z80_HALFCARRY
|
|
CPUREG_Z80_ZERO
|
|
CPUREG_Z80_SIGN
|
|
CPUREG_Z80_IFF1
|
|
CPUREG_Z80_IFF2
|
|
|
|
Each individual register's value can be set, including the flags at the end.
|
|
The only valid values for the flags are 1 and 0. Setting these will
|
|
automatically adjust the "F" register.
|
|
|
|
If pContext is NULL, then the registers in the currently active context are
|
|
changed. If pContext points to a non-NULL area, that area is assumed to be
|
|
a CONTEXTMZ80 structure where the new register value will be written.
|
|
|
|
If mz80SetRegisterValue() returns a nonzero value, either the register value
|
|
or register is out of range or invalid.
|
|
|
|
|
|
UINT32 mz80GetRegisterValue(void *pContext, UINT32 dwRegister)
|
|
|
|
This returns the value of the register given on input (listed above as
|
|
CPUREG_Z80_xxxxx). Flag values will be 1 or 0.
|
|
|
|
If pContext is NULL, then the registers in the currently active context are
|
|
read. If pContext points to a non-NULL area, that area is assumed to be
|
|
a CONTEXTMZ80 structure from which register values are pulled.
|
|
|
|
|
|
UINT32 mz80GetRegisterTextValue(void *pContext, UINT32 dwRegister,
|
|
UINT8 *pbTextArea)
|
|
|
|
This returns the textual representation of the value of a given register.
|
|
It is a text printable string that can be used in sprintf() statements and
|
|
the like. This function is useful because different representations for
|
|
registers (like flags) can be a group of 8 flag bytes instead of a single
|
|
value.
|
|
|
|
On entry, pContext being set to NULL indicates that mz80 should get the
|
|
register value from the currently active context. Otherwise, it is assumed
|
|
to be pointing to a CONTEXTMZ80 structure, which contains the value of the
|
|
registers to be read.
|
|
|
|
pbTextArea points to a buffer where the value text can be written. This points
|
|
to a user supplied buffer.
|
|
|
|
On exit, if any nonzero value is encountered, either the register # is out
|
|
of range or pbTextArea is NULL.
|
|
|
|
|
|
UINT8 *mz80GetRegisterName(UINT32 dwRegister)
|
|
|
|
This returns a pointer to the textual name of the register passed in. NULL
|
|
Is returned if the register index (CPUREG_Z80_xxxx table described above) is
|
|
out of range. DO NOT MODIFY THE TEXT! It is static data.
|
|
|
|
|
|
FINAL NOTES
|
|
-----------
|
|
|
|
I have debugged MZ80.ASM to the best of my abilities. There might still be
|
|
a few bugs floating around in it, but I'm not aware of any. I've validated
|
|
all instructions (That I could) against a custom built Z80 on an ISA card
|
|
(that fits in a PC) so I'm quite confident that it works just like a real
|
|
Z80.
|
|
|
|
If you see any problems, please point them out to me, as I am eager to make
|
|
mz80 the best emulator that I can.
|
|
|
|
If you have questions, comments, etc... about mz80, please don't hesitate
|
|
to send me an email. And if you use mz80 in your emulator, I'd love to take
|
|
a look at your work. If you have special needs, or need implementation
|
|
specific hints, feel free to email me, Neil Bradley (neil@synthcom.com). I
|
|
will do my best to help you.
|
|
|
|
Enjoy!
|
|
|
|
Neil Bradley
|
|
neil@synthcom.com
|
|
|
|
|