Package: z80
z80Fast and flexible Z80/i8080 emulator.Quick factsImplements accurate machine cycle-level emulation.Supports undocumented instructions, flags and registers.Passes the well-knowncputest,8080pre,8080exer,8080exm,prelimandzexalltests.Follows a modular event-driven design for flexible interfacing.Employs compile-time polymorphism for zero performance
overhead.Cache-friendly implementation without large code switches and
data tables.Offers default modules for the breakpoint support and generic
memory.Supports multiple independently customized emulator instances.Written in strict C++11.Does not rely on implementation-defined or unspecified
behavior.Single-header implementation.Provides a generic Python 3 API and instruments to create
custom bindings.MIT license.ContentsHello worldAdding memoryInput and outputAccessing processor's stateModulesThe root moduleState modulesFeedbackHello world#include"z80.h"classmy_emulator:publicz80::z80_cpu<my_emulator>{public:typedefz80::z80_cpu<my_emulator>base;my_emulator(){}voidon_set_pc(z80::fast_u16pc){std::printf("pc = 0x%04x\n",static_cast<unsigned>(pc));base::on_set_pc(pc);}};intmain(){my_emulatore;e.on_step();e.on_step();e.on_step();}hello.cppBuilding:$gitclonegit@github.com:kosarev/z80.git
$cmakez80
$make
$maketest$makehello# Or 'make examples' to build all examples at once.Running:$ ./examples/hello
pc = 0x0000
pc = 0x0001
pc = 0x0002In this example we derive our custom emulator class,my_emulator, from amix-inthat implements the
logic and default interfaces necessary to emulate the Zilog Z80
processor.
As you may guess, replacingz80_cpuwithi8080_cpuwould give
us a similar Intel 8080 emulator.Theon_set_pc()method overrides its default counterpart to
print the current value of thePCregister before changing it.
For this compile-time polymorphism to be able to do its job, we
pass the type of the custom emulator to the processor mix-in as a
parameter.Themain()function creates an instance of the emulator and
asks it to execute a few instructions, thus triggering the custom
version ofon_set_pc().
The following section reveals what are those instructions and
where the emulator gets them from.Adding memoryEvery time the CPU emulator needs to access memory, it callson_read()andon_write()methods.
Their default implementations do not really access any memory;on_read()simply returns0x00, meaning the emulator in the
example above actually executes a series ofnops, andon_write()does literally nothing.Since both the reading and writing functions are considered by
thez80::z80_cpuclass to be handlers, which we know because
they have theonpreposition in their names, we can use the
same technique as withon_set_pc()above to override the
default handlers to actually read and write something.classmy_emulator:publicz80::z80_cpu<my_emulator>{public:...fast_u8on_read(fast_u16addr){assert(addr<z80::address_space_size);fast_u8n=memory[addr];std::printf("read 0x%02x at 0x%04x\n",static_cast<unsigned>(n),static_cast<unsigned>(addr));returnn;}voidon_write(fast_u16addr,fast_u8n){assert(addr<z80::address_space_size);std::printf("write 0x%02x at 0x%04x\n",static_cast<unsigned>(n),static_cast<unsigned>(addr));memory[addr]=static_cast<least_u8>(n);}private:least_u8memory[z80::address_space_size]={0x21,0x34,0x12,// ld hl, 0x12340x3e,0x07,// ld a, 70x77,// ld (hl), a};};adding_memory.cppOutput:read 0x21 at 0x0000
pc = 0x0001
read 0x34 at 0x0001
read 0x12 at 0x0002
pc = 0x0003
read 0x3e at 0x0003
pc = 0x0004
read 0x07 at 0x0004
pc = 0x0005
read 0x77 at 0x0005
pc = 0x0006
write 0x07 at 0x1234Input and outputAside of memory, another major way the processors use to
communicate with the outside world is via input and output ports.
If you read the previous sections, it's now easy to guess that
there is a couple of handlers that do that.
These areon_input()andon_output().Note that the handlers have different types of parameters that
store the port address, because i8080 only supports 256 ports
while Z80 extends that number to 64K.// i8080_cpufast_u8on_input(fast_u8port)voidon_output(fast_u8port,fast_u8n)// z80_cpufast_u8on_input(fast_u16port)voidon_output(fast_u16port,fast_u8n)The example:classmy_emulator:publicz80::z80_cpu<my_emulator>{public:...fast_u8on_input(fast_u16port){fast_u8n=0xfe;std::printf("input 0x%02x from 0x%04x\n",static_cast<unsigned>(n),static_cast<unsigned>(port));returnn;}voidon_output(fast_u16port,fast_u8n){std::printf("output 0x%02x to 0x%04x\n",static_cast<unsigned>(n),static_cast<unsigned>(port));}private:least_u8memory[z80::address_space_size]={0xdb,// in a, (0xfe)0xee,0x07,// xor 70xd3,// out (0xfe), a};};input_and_output.cppAccessing processor's stateSometimes it's necessary to examine and/or alter the current
state of the CPU emulator and do that in a way that is
transparent to the custom code in overridden handlers.
For this purpose the default state interface implemented in thei8080_state<>andz80_state<>classes provdes a number of
getters and setters for registers, register pairs, interrupt
flip-flops and other fields constituting the internal state of
the emulator.
By convention, calling such functions does not fire up any
handlers. The example below demonstrates a typical usage.Note that there are no such accessors for memory as it is
external to the processor emulators and they themselves have to
use handlers, namely, theon_read()andon_write()ones, to
deal with memory.classmy_emulator:publicz80::z80_cpu<my_emulator>{public:...voidon_step(){std::printf("hl = %04x\n",static_cast<unsigned>(get_hl()));base::on_step();// Start over on every new instruction.set_pc(0x0000);}accessing_state.cppModulesBy overriding handlers we can extend and otherwise alter the
default behavior of CPU emulators.
That's good, but what do we do if it's not enough?
For example, what if the default representation of the
processor's internal state doesn't fit the needs of your
application?
Say, you might be forced to follow a particular order of
registers or you just want to control the way they are packed in
a structure because there's some external binary API to be
compatible with.
Or, what if you don't need to emulate the whole processor's
logic, and just want to check if a given sequence of bytes forms
a specific instruction?That's where modules come into play.
To understand what they are and how to use them, let's take a
look at the definitions of the emulator classes and see what's
under the hood.template<typenameD>classi8080_cpu:publici8080_executor<i8080_decoder<i8080_state<root<D>>>>{};template<typenameD>classz80_cpu:publicz80_executor<z80_decoder<z80_state<root<D>>>>{};Each of these classes is no more than a stack of a few other
mix-ins.
Theroot<>template provides helpers that make it possible to
call handlers of the most derived class in the heirarchy,D,
which is why it takes that class as its type parameter.
It also contains dummy implementations of the standard handlers,
such ason_output(), so you don't have to define them when you
don't need them.i8080_state<>andz80_state<>have been mentioned in the
previous section as classes that define transparent accessors to
the processor state, e.g.,set_hl().
They also define corresponding handlers, likeon_set_hl(), that
other modules use to inspect and modify the state.i8080_decoder<>andz80_decoder<>modules analyze op-codes
and fire up handlers for specific instructions, e.g,on_halt().Finally, the job ofi8080_executor<>andz80_executor<>is to
implement handlers likeon_halt()to actually execute
corresponding instructions.The convention is that modules shall communicate with each other
only via handlers.
Indeed, if they would call the transparent accessors or refer to
data fields directly, then those accessors wouldn't be
transparent anymore and handlers would never be called.
This also means that modules are free to define transparent
accessors in a way that seems best for their purpose or even not
define them at all.All and any of the standard modules can be used and customized
independently of each other.
Moreover, all and any of the modules can be replaced with custom
implementations.
New modules can be developed and used separately or together with
the standard ones.
In all cases the only requirement is to implement handlers other
modules rely on.The root moduletemplate<typenameD>classroot{public:typedefDderived;...fast_u8on_read(fast_u16addr){unused(addr);return0x00;}voidon_write(fast_u16addr,fast_u8n){unused(addr,n);}...protected:constderived&self()const{returnstatic_cast<constderived&>(*this);}derived&self(){returnstatic_cast<derived&>(*this);}};The main function of the root module is to define theself()method that other modules can use to call handlers. For example,
a decoder could doself().on_ret()whenever it runs into aretinstruction.Aside of that, the module contains dummy implementations of the
standard handlers that do nothing or, if they have to return
something, return some default values.State modulestemplate<typenameB>classi8080_state:publicinternals::cpu_state_base<B>{public:...boolget_iff()const{...}voidset_iff(boolf){...}...};template<typenameB>classz80_state:publicinternals::cpu_state_base<z80_decoder_state<B>>{public:...voidexx_regs(){...}voidon_exx_regs(){exx_regs();}...};The purpose of state modules is to provide handlers to access the
internal state of the emulated CPU.
They also usually store the fields of the state, thus defining
its layout in memory.Regardless of the way the fields are represented and stored, the
default getting and setting handlers for register pairs use
access handlers for the corresponding 8-bit registers to obtain
or set the 16-bit values.
Furthermore, the low half of the register pair is always
retrieved and set before the high half.
This means that by default handlers for 8-bit registers are
getting called even if originally a value of a register pair they
are part of has been queried.
Custom implementations of processor states, however, are not
required to do so.fast_u16on_get_bc(){// Always get the low byte first.fast_u8l=self().on_get_c();fast_u8h=self().on_get_b();returnmake16(h,l);voidon_set_bc(fast_u16n){// Always set the low byte first.self().on_set_c(get_low8(n));self().on_set_b(get_high8(n));}Aside of the usual getters and setters for the registers and
flip-flops, both the i8080 and Z80 states have to provide anon_ex_de_hl_regs()handler that exchangeshlandderegisters the same way thexchgandex de, hldo.
And the Z80 state additionally has to have anon_exx_regs()that swaps register pairs just as theexxinstruction does.
The default swapping handlers do their work by accessing
registers directly, without relying on the getting and setting
handlers, similarly to how silicon implementations of the
processors toggle internal flip-flops demux'ing access to
register cells without actually transferring their values.Because the CPUs have a lot of similarities, processor-specific
variants of modules usually share some common code in helper base
classes that in turn are defined in theinternalclass.
That class defines entities that are internal to the
implementation of the library.
The client code is therefore supposed to be written as if the
module classes are derived directly from their type parameters,B.Note thatz80_statehas an additional mix-in in its inheritance
chain,z80_decoder_state<>, whereasi8080_stateis derived
directly from the generic base.
This is because Z80 decoders are generally not stateless objects;
they have to track which of theIX,IYorHLregisters has
to be used as the index register for the current instruction.
The decoder state class stores and provides access to that
information.template<typenameB>classz80_decoder_state:publicB{public:...iregpget_iregp_kind()const{...}voidset_iregp_kind(iregpr){...}iregpon_get_iregp_kind()const{returnget_iregp_kind();}voidon_set_iregp_kind(iregpr){set_iregp_kind(r);}...};In its simplest form, a custom state module can be a structure defining the
necessary state fields together with corresponding access handlers.template<typenameB>structmy_state:publicB{fast_u16pc;...fast_u16on_get_pc()const{returnpc;}voidon_set_pc(fast_u16n){pc=n;}...// These always have to be explicitly defined.voidon_ex_de_hl_regs(){}voidon_ex_af_alt_af_regs(){}voidon_exx_regs(){}};custom_state.cppFeedbackAny notes on overall design, improving performance and testing
approaches are highly appreciated.
Please file an issue or use the email given athttps://github.com/kosarev.
Thanks!