![]() |
/Develop/Projects/Z80EMUF/ |
k1.spdns.de / Develop / Projects / Z80EMUF / |
This page describes my Z80 emulation soft core and a simple emulated Z80 system which uses it.
Source of the Z80 emulation, the Z80 system and the system's Rom is available for download at the end of this page.
The emulation handles most aspects of the Z80 CPU exactly:
The emulation speed is approx. 1Hz/12Hz (250 MHz on a 3 GHz Intel 64 bit CPU). But will degrade with every complication added. :-) You can add a lot of complications until you hit the clock speed of a real system, though.
The Z80 emulation is written in C++. It can be heavily customized by replacing the provided standard macros with own definitions. The core is plain C and could be back-ported to C easily.
The example system is written in C++ and uses the Qt framework and POSIX calls. It was developed on Mac OSX and should compile with only tiny tweaks on Linux as well. Windows people have to rewrite the POSIX files and threads (and maybe other parts) if they want to start with the example project. Maybe Cygwin can help.
The program in the emulated system's Rom is written in Z80 assembler and C. I used zasm to assemble it. zasm can include C source with the help of the C compiler sdcc. This program is just to show "yee, it works" and does nothing useful.
I reworked the old source of my Z80 emulation to make it usable for starting projects again. Over time it became too fat, because i use it in zxsp, my ZX Spectrum emulator, and every requirement for zxsp made it's way into the Z80 emulation.
build-* build directory, contains the executable
config.h main config file: settings for compilation
Libraries/ some of my library files from here are included
Qt/ Qt specific souce files: mostly Gui
Rom/ Source of target system's Rom
Uni/ universal sources: mostly Machine and Items
Z80/ the Z80 emulation (an Item as well)
Z80EMUF.pro the Qt project (setup for OSX)
Z80EMUF 2015-06-05.zip all files zipped up for download
There is a small Type hierarchy:
IsaObject
Item
Machine
Z80
SystemTimer
Sio
Mmu
IsaObject provides the base for objects which can expose their type. Just a kind of private RTTI. I have reduced this type to the minimum as used in the example system.
Item is the base class for anything which provides a functional unit to the computer. They are linked in a chain, so that every item can find any other by walking the chain and comparing the isa_id.
Machine is the class for the object which contains all the emulated system's parts. It contains the system memory and instantiates all required Items. Machine itself is based on Item, which is actually not required. I just happened to do it this way in the example project. Machine does not contain any Gui stuff. (Model - View paradigma)
Z80 The Z80 CPU of course is an Item as well. The current implementation expects that it is the first Item in the linked list of all Items (at least of those which require update(), input() or output() to be called, this excludes the machine :-) )
SystemTime In the current implementation of the Z80 emulation all interrupts must be generated by Items, because the Z80 searches the list of all Items for an active interrupt. Therefore the system timer interrupt must be generated by an Item and i implemented it as a distinct Item in this project.
Sio The example project provides an emulation of a serial interface. It is not simply an intercepted illegal instruction, to show how things work. It is no real device either, because these are already very complex and hard to understand, making them inappropriate as an example. This Sio just has a data port and a status register where the target code can test for pending interrupts for input and output and a control register where it can enable or disable these interrupts. The emulation provides a real-time emulation of the serial speed. The serial link may be viewed as having hardware handshake without the need to configure it, it can't overflow in any direction. The handling of the Sios in the example Rom is near the bare minimum.
Mmu As an example for support of paged memory i have included a MMU (memory management unit) Item. The Machine contains 128 kB of memory, thereof the first 32 kB are handled as Rom (read only) and the rest as Ram. The Mmu can page in any of these four 32K pages for reading and/or writing for the lower and upper address half. If Rom is paged in for writing, then all writes go to a dummy write page. The example project makes no use of the paged memory except that must setup the MMU after reset, it is just provided as an example.
The emulation itself is run by calling Z80::run(int32 cc_exit, uint options).
This runs the Z80 from the current cpu cycle (Z80::cc) to at least cc_exit. Small overshoots are normal, because Z80 instructions are 4 to 23 cpu cycles and may be extended to an unlimited duration by prepending illegal prefixes…
During Z80::run() some data is held in local variables, e.g. registers A and F, the cpu cycle counter CC and others. You have to take care for this when you want to peek and poke the registers, e.g. either only do that while the CPU is not currently run() or if you do this from a macro, e.g. for INPUT, then you must extend the macro to provide access to the local variable, e.g. if you want to add wait cycles to the cpu cycle counter CC.
The options are not used by the current implementation of the CPU, but will be handy when you implement additional features. E.g. the standard CoreByte is 16 bit, though the Z80 uses 8 bit wide bytes. The additional bits can be used to associate flags with memory locations, e.g. for a debugger or to flag video memory.
In case of video memory you may wish to call a CRTC (cathode ray tube controller) in parallel to the CPU so that it exactly reads what the real device would have read. This requires to call the CRTC before each write to video ram, so that it can read up to the current cpu cycle and may – or may not read the old value at the overwritten address. This is time consuming, so you will do this only for video ram, and there only during the critical time when the video ram is actually read, not in the upper or lower border.
This is where all the bits come together: The bytes in the video ram are flagged with a dedicated bit, the CPU is run with options which indicate whether this run() is within the screen section, and the macro POKE(A,R) is replaced by an own macro which calls the CRTC if and only if the options and the flag bit at the memory address are set, to update the display up to the current cpu cycle. In total you have to take care for a lot of things, but you can start with and modify a running system, which makes it a little bit easier.
File "Z80/macros.h" contains a bunch of macro definitions which hook in for any macro which is not defined. That's your chance! :-)
File "Z80/options.h" can be used to define any customizations which do not ultimately require modifications to the Z80 implementation itself.
Defines the page size for the CPU. Even if you don't use paged memory it must be defined.
If you use paged memory set it to the smallest page size your system supports.
The class Z80 contains some helper methods to page in and out memory. Supplied address and size must always be a multiple of the page size.
Defines the size of data used per byte in the Z80 system. Normally this is 16 bit, so you have 8 bits for flags. If you need more set it to uint32. If you really don't need any set it to uint8, but you have to remove some methods in Z80 as well, which now become redundant. The compiler will tell you which…
That's new! :-) The emulation can be limited to a 8080 CPU which did not have the $ED, $BC and index register prefix opcodes. No real loss, but it also did not have the relative jumps, which was a pity. But this is nice to test your code for CP/M compatibility. ;-)
Increment the cpu cycle counter, R register or a (not existing) instruction counter. There is almost never any need to change these macros.
This macro is used for every standard memory read which is not an M1 cycle and takes 3 cpu clock cycles if no wait states are added.
This macro is used for every standard memory write which takes 3 cpu clock cycles if no wait states are added.
Get the first byte of the next instruction. This is a M1 cycle which normally takes 4 cpu clock cycles.
This is an ideal location to intercept for a debugger.
Read the second byte after a prefix opcode. These are M1 cycles as well.
Read the 4th byte in a IX+CB or IY+CB instruction. I'm not sure whether this couldn't be replaced with a plain GET_N() macro.
Read or skip one byte fetched at current PC position. This is a specialization of PEEK(R,A).
Various variants for adding some cpu cycles used in special instructions, see source. These macros are expected to do a wait test in every cycle. This is required for exact emulation of the ZX Spectrum family, the implementation for other systems may vary. E.g. these wait tests are made while the Z80 does not actually access the bus, which is a little bit stupid. The default implementation just adds the requested number of cpu cycles.
Handle an OUT instruction. The default implementation walks the linked list of all attached Items to find the requested Item and calls Item::output(cc,a,b). You may add code to add wait states or handle OUT to multiple Items.
Handle an IN instruction. The default implementation walks the linked list of all attached Items to find the requested Item and calls Item::input(cc,a,b&). You may add code to add wait states or handle IN from multiple Items. The target byte, which is passed to Item::input(cc,a,b&), should be preset with $FF so that the Items can "merge" their byte as would happen in a real system. Typical code to do so is:
byte &= my_value;
(low bits always won due to TTL driving power and threshold level.)
The following macros provide easy hooks for callbacks on certain special opcodes and occasions:
#define Z80_INFO_CTOR /* nop */
#define Z80_INFO_DTOR /* nop */
#define Z80_INFO_IRPT() /* nop */
#define Z80_INFO_NMI() /* nop */
#define Z80_INFO_RETI /* nop */
#define Z80_INFO_RETN /* nop */
#define Z80_INFO_HALT /* nop */
#define Z80_INFO_ILLEGAL(CC,PC) /* nop */
#define Z80_INFO_POP /* nop */
#define Z80_INFO_RET /* nop */
#define Z80_INFO_EX_HL_xSP /* nop */
#define Z80_INFO_RST00 /* nop */
#define Z80_INFO_RST08 /* nop */
#define Z80_INFO_RST10 /* nop */
#define Z80_INFO_RST18 /* nop */
#define Z80_INFO_RST20 /* nop */
#define Z80_INFO_RST28 /* nop */
#define Z80_INFO_RST30 /* nop */
#define Z80_INFO_RST38 /* nop */
#define Z80_INFO_EI /* nop */
#define Z80_INFO_LD_R_A /* nop */
#define Z80_INFO_LD_I_A /* nop */
The class Item defines an interface:
// Item interface:
virtual void init (/*cc=0*/) {}
virtual void reset (int32) {}
virtual bool input (int32, uint, uint8&) {return no;}
virtual bool output (int32, uint, uint8) {return no;}
virtual void update (int32) {}
virtual void shift_cc (int32, int32) {}
init() Initialize the system as after Power-On.
The Machine first creates all Items which must do all their minimal and one-time setup.
Then all Items a are called their init() method to "power-up". When init() is called they can trust that all other Items exist and that they are linked in an eventually expected order. In the example system the "guaranteed order" is machine -> z80 -> others. When init() is called, the system time (measured in cpu clock cycles 'cc') is 0.
reset(int32 cc) may be called any time and therefore provides the current system time as it's argument. reset() is called for all Items in order and all items should do exactly what the real device would do if the reset button of the system was pressed. E.g. there may be items which do not react to a reset.
input(int32 cc, uint addr, uint8& byte) Items which react to a Z80 IN instruction must register their input address and the bit mask of the decoded bits when calling the Item's constructor in their constructor. Then the Z80 finds them when it searches for the target of an IN instruction and calls it's input() method. The provided default implementation expects that each address is decoded by at most one Item, systems which (may) contain multiple Items which react to the same address must replace the default macro.
The Z80 calls the found Item's input() method with the current cpu cycle, the exact address and a reference where the Item can store it's byte. input() returns a boolean flag which tells the Z80, whether this input() caused changes to it's schedule list: whether this caused the interrupt state of this item to change or whether the Item updated the timestamp for the next update().
The byte provided to this call is preset with $FF. This allows the Item to AND in it's bytes which is required if more than one Item reacts to an address, e.g. if the low 5 bits come from the keyboard and bit 7 from the EAR socket, or whatever.
The provided implementation does not much magic beyond searching the i/o target and then calling it's input() method.
A more sophisticated implementation will replace the INPUT macro to add wait cycles, update video ram in a cycle-precise manner and so on.
Items may do a time-based job while they live. When their input() method is called those Items will have to catch-up to the provided time stamp before they can provide their byte. See the Sio of the example project.
output(int32 cc, uint addr, uint8 byte) Basically the same as for input(), but to handle the OUT instruction.
update(int32 cc) Items may do a job in the background and may require to be called at a certain time, e.g. to assert or remove their interrupt request at a foreseen time. See the Sio as an example. If Items just need a regular callback they should use shift_cc() instead.
shift_cc(int32 cc, int32 dis) The cpu cycle based system time will overflow approx. every 20 minutes at 3.5 MHz. Basically this can be handled by rewriting all compares from
if(A > B)
to
if(A - B > 0)
but it's more than likely that you forget this, and debugging won't be easy, because it starts to happen not before running for 20 minutes…
Therefore the time base of the system is shifted regularly. It is recommended to synchronize this with either the system timer interrupt or the video frame flyback. (which ideally happen to be the same.) Then the current system time, as passed in every callback to the Item interface, is a fixed point within the video frame which simplifies precise emulation of video ram reading.
The example system (which has no video circuitry) calls shift_cc() for all Items once per system timer interrupt and the implementation of the SystemTimer makes use of this.
class Item has a protected creconstruconstructorctorator to be called by all derived classes, e.g. by the Z80 constructor:
Item ( Item* behind, isa_id, // link behind or parent, type
uint o_addr=~0, uint o_mask=0, // no output
uint i_addr=~0, uint i_mask=0 ); // no input
Item* behind defines the Item behind which this Item is to be linked. The first item is the Machine itself which links behind NULL, then the Z80 behind the Machine and then all other Items behind the Z80.
o_addr and o_mask define the address and the decoded address bits for this item, if it reacts to OUT instructions. The default arguments can be used for items without output.
i_addr and i_mask define the address and the decoded address bits for this item, if it reacts to IN instructions. The default arguments can be used for items without input.
The Z80 uses these informations in it's IN and OUT instruction implementations. See Z80::handle_input() and Z80::handle_output() which are called by the INPUT and OUTPUT macro.
EMUF = Einplatinen-Microcomputer für Universelle Festanwendungen – yeah, that's German… :-)
(Single-board microcomputer for universal but fixed applications)
The example Z80 computer comprises of a PCB (let's call it "Machine"), a Z80 CPU, memory (Rom and Ram) which can be paged in by the help of a MMU, a system timer, a Dual ACIA (or two single ACIAs…) an invisible keyboard and a 404 dot matrix display which i happen to actually own.
The Machine is represented by an item of class Machine. It contains all the other items, except those related to the Gui which aren't Items.
The CPU is implemented by an instance of class Z80. It is used 'as is' without any tweaks. You may soon rewrite some of the macros to supply weird unusual features if you use it. The CPU is run on a separate thread by the machine, which is not required and makes things more complicated (you have to lock the machine sometimes so that you can access Item internals, e.g. to call reset() on them)) but this way it can run on a different core so that i can see more reliably how fast it actually is.
The memory consists of 32 kB Rom and 96 kB Ram, provided by a CoreByte array in the Machine.
The MMU (memory management unit) is implemented in class Mmu. It can be programmed by writing to port $FE. 4 groups of 2 bits in the i/o byte configure the two 32K banks of the system for reading and writing. 2 bits can just select one of the 4 pages.
#define bank0_readpage_mask 0x03
#define bank0_writepage_mask 0x0C
#define bank1_readpage_mask 0x30
#define bank1_writepage_mask 0xC0
Two ACIAs are implemented by class Sio. These don't represent real devices, because they would be too complicated. But they emulate the real timing of a serial interface and provide an input and output interrupt. This allows to show an example implementation for some kind of real-time Item. The Sios are set up as follows:
sioA_data equ $00ff ; port address: RxD/TxD data
sioB_data equ $01ff ; port address
sioA_control equ $02ff ; port address: control / status
sioB_control equ $03ff ; port address
; bit in control/status register:
; in: query TxD or RxD interrupt state
: out: enable/disable RxD/TxD interrupt
sio_out_mask equ $01 ; TxD interrupt
sio_in_mask equ $02 ; RxD interrupt
Baud rate is set to 9600 bits/second in the Machine's constructor.
Sio A is connected to the keyboard (that is: the MainWindow get's the key press events and calls machine->sio_A->store_byte()) and the 404 LCD display (that is: the MainWindow regularly reads available transmit data from machine->sio_A and sends it to the Display404), Sio B is not used in this example.
The SystemTimer is set up to fire once per shift_cc() callback. This is organized in the run loop of the Machine: It wait's for 1/100 sec to pass by and then calls the Z80 to run the corresponding number of cpu cycles. Then it calls shift_cc() to shift the system time base and the SystemTimer uses this opportunity to fire an interrupt.
This is a minimalistic rom to show "yee, it works!". It prints some text to the 404 display, reads one keypress back and then displays the system time counter. This shows that all parts in the emulated system work, basically.
I opted to include C code, so i can more easily add this or that. The source is assembled with zasm, which can include C source if the C compiler sdcc (or something very similar) is installed.
The Sio is handled very simple (wait and read or write) but already uses the sio interrupts to speed up things.
The MMU of the system is not used, but needs to be initialized in the boot code.
The Rom and it's source can be found in the folder "Rom" (sic!).
Name | Letzte Änderung | Länge | |||
---|---|---|---|---|---|
![]() |
Libraries/ | 2024-09-12 22:17 | 25 | ||
![]() |
Linux/ | 2021-10-08 17:26 | 4 | ||
![]() |
macos/ | 2019-10-30 13:10 | 5 | ||
![]() |
Rom/ | 2020-08-22 13:05 | 6 | ||
![]() |
Source/ | 2019-11-15 20:44 | 5 | ||
![]() |
Z80/ | 2023-03-03 10:14 | 8 | ||
![]() |
404.png size: 757 × 185 |
2015-06-05 19:10 | 18329 | ||
![]() |
LICENSE | 2019-10-30 13:00 | 1323 | ||
![]() |
README.md | 2019-10-30 16:30 | 2936 | ||
![]() |
README.txt | 2019-10-30 13:01 | 2798 | ||
![]() |
Video Display Idee.txt | 2017-06-29 09:54 | 13995 | ||
![]() |
VT amber.png size: 527 × 548 |
2017-01-10 16:45 | 121063 | ||
![]() |
VT blue.png size: 528 × 546 |
2017-01-10 16:42 | 59970 | ||
![]() |
VT green.png size: 523 × 546 |
2017-01-10 16:43 | 69817 | ||
![]() |
VT transparent with titlebar.png size: 536 × 547 |
2017-01-10 16:47 | 167607 | ||
![]() |
VT transparent.png size: 528 × 526 |
2017-01-10 16:49 | 106009 |
powered by vipsi - your friendly VIP Script Interpreter