The YAKC KC85 Emulator
YAKC means ‘Yet Another KC emulator’. I might come up with a better name some day.
It is written in a simple and low-level C++11 (meaning no exceptions, no RTTI, minimal STL usage), and aims to be fast, simple and portable. The most important target platform is the web browser via emscripten, but it is also possible to build native versions for OSX, Linux, Windows, Android and iOS.
Extra care has been taken to keep the client size small for the web browser platform. The compressed size for the complete emulator with debugging UI is under 400 kBytes, without the debugging UI the compressed size is under 200 kBytes.
The emulator consists of 4 main components:
- the ‘emulator core’: this is the complete Z80 and KC85 emulator as a set of C++ headers with a callback interface to feed the emulator with keyboard input and access to the video and audio output generated by the emulator
- debugging UI: the debugger UI overlay has been implemented with Ocornut’s imgui
- application wrapper: Oryol is used as portable application wrapper
- build system: the fips buildsystem is used to build the whole thing, fips is essentially a layer above cmake to simplify the setup of multi-platform projects
Python is used for code-generation to create the Z80 instruction decoder and dump binary files like ROMs and tape files into C headers.
Oryol depends on a few other 3rd party libs that are used indirectly:
- GLFW: GLFW is used as window system and input wrapper on OSX, Linux and Windows if the OpenGL rendering backend is used
- flextgl: is used as OpenGL extension wrangler
- Unittest++: is used for unit tests (there doesn’t seem to be an official github repo, so here’s only the link to my fipsified version)
The emulator is fairly complete, but not perfect:
- emulates the KC85/2 (with support for M006 BASIC module), KC85/3 and KC85/4 with switchable CAOS operating system ROMs
- fast and nearly complete documented Z80 emulation passing the ZEXDOC conformance test, instructions are emulated with their proper cycle counts
- expansion slot system with support for the following RAM and ROM modules:
- M022 EXPANDER-RAM (16 KByte RAM)
- M011 64 KBYTE RAM
- M006 BASIC (+ HC-901 CAOS, only useful on KC85/2)
- M026 FORTH
- M027 DEVELOPMENT
- …more modules may be added in the future
- internal RAM/ROM bank switching
- properly emulates the CTC and PIO registers and their interaction with the rest of the system (with a few minor exceptions)
- emulates the look of an old-school CRT color or black-and-white TV
- KCC file format loading
- floppy disk device and the CP/M OS
- cassette loading/saving
- hardware quirks like the ‘display snowing/needling’
- the serial keyboard input logic is not emulated, instead the key codes are patched directly into the right operating system locations
- the Z80 emulation doesn’t emulate the following features: non-maskable interrupts, interrupt modes 0 and 1, extra memory wait-states
The Emulator Core
The core classes are under yakc_core.
Low-level hardware components:
- clock: this provides a central system clock and counters
- memory: the memory class maps the 64kByte Z80 address range to host memory in 2 KByte pages. It also implements a simple bank-switching logic to map memory pages in and out of the visible address range.
- z80: the z80 class is the core of the emulator, it provides a very fast and (nearly) complete emulation of the Z80 (enough to run the ZEXDOC tests by Frank D. Cringle), a large part of the z80 class is code-generated via python
- z80ctc: a simple and incomplete emulation of the Z80 CTC chip, currently just enough to run the KC85 emulator
- z80pio: likewise, a simple and incomplete emulation of the Z80 PIO chip, just enough for the KC85
- z80int: this implements the interrupt controller device logic in a ‘daisy chain’, used to prioritize interrupt requests from the CTC and PIO
- z80dbg: this class bundles some low-level debugging functions, for instance a single breakpoint, and a history ring-buffer for the PC register
- kc85_video: the kc85_video class emulates the video memory scan-out logic, mainly a method to decode the KC85 video memory into a linear RGBA8 image (which is then usually copied into an OpenGL texture)
- kc85_audio: this is a simple interface class to the KC85 sound generation hardware (which is just a CTC-driven oscillator connected to an internal speaker), it provides callbacks to control start/stop/frequency and volume of sound
- kc85_exp: emulates the expansion slot system of the KC85
- kc85: this is the emulators main class which embeds, connects and controls all previously described components
How the Z80 emulator works:
The core of the Z80 emulation is the z80::step() method, which executes the next instruction and returns the number of CPU cycles taken. The method’s body is generated by a python script and is essentially a huge, nested switch-case statement on one or more instruction-bytes, where each case-branch implements a single instruction.
Some instructions use lookup tables for the resulting flags, other instructions compute the flags (I tried out different combinations of flag lookup and computation until I ended up with the fastest execution of the ZEXDOC test).
The R register is incremented once per fetched instruction-byte and wraps around at 0x7F.
The increment/decrement-and-repeat instructions (e.g. LDIR, CPIR) implement their loops by resetting the PC to the start of the instruction as long as the instruction isn’t done yet.
The IN and OUT instructions simply invoke externally provided callbacks (these are implemented in the kc85 class).
For interrupt handling, a simple daisy-chain protocol is implemented, which prioritizes interrupts between different interrupt controllers (in the KC85 emulation, only the CTC and PIO act as interrupt controllers).
Non-maskable interrupts, and interrupt modes 0 and 1 are currently not implemented (these are not needed for the KC85).
The Z80 emulation is quite fast. On a mid-2014 MacBookPro with 2.8 GHz i5 CPU, a Z80 running at 1.2 GHz can be emulated, which results in around 150 MIPS (the fastest Z80 instructions take 4 cycles, the slowest 23 cycles).
The emulator is running the ZEXDOC conformance test without errors, which means that the documented CPU flags are computed correctly, and most undocumented instructions have been implemented.
How the KC85 emulator works:
The KC85 emulator wraps around the Z80 emulator components. The most important methods of the kc85 class are poweron(), reset() and onframe().
The poweron() method performs power-on initialization, reset() performs a ‘soft-reset’, and onframe() runs the emulator for a single 16.6ms frame.
What happens during an emulator-frame:
- input is handled once per frame, the keyboard input is not perfectly emulated but uses a shortcut, the real input handler code (which is not called) is described here
- the clock object computes how many CPU cycles to run for this frame (at 60fps and 1.75MHz Z80 speed, this results in around 29.2k CPU cycles)
- the instruction-loop is executed until the computed number of CPU cycles
- the z80::step() function is called, this executes the next instruction and returns the number of cycles taken
- the clock-timers and CTC are updated, this may trigger hardware- lines (like vsync and CTC input pins) and cause interrupt requests to the CPU
- the z80::handle_irq() method is called to handle pending interrupt requests
- if the emulated CPU executes an IN/OUT instruction, the kc85::in_cb() and kc85::out_cb() callbacks are invoked, these callbacks read or write PIO/CTC registers, control expansion modules and update the memory bank mapping
The emulator does not directly interact with the host system, platform-specific things like keyboard input, video and audio output need to be handled outside in the wrapper application.
The Oryol Wrapper Application
The wrapper application code is in the yakc_app directory:
- YakcApp in Main.cc: this is the Oryol application subclass which
initializes the required Oryol modules:
- Gfx: required to render the emulator video output to screen as a fullscreen rectangle
- IO and HTTP: are needed to load KCC tape files from a web server
- Input: is required to get keyboard and mouse input
- Synth: a software audio synthesizer module to generate audio via OpenAL
- Draw: the Draw class creates all the required rendering resources and per-frame decodes the KC85 video memory into a texture, and render a fullscreen rectangle using a CRT-effect shader
- Audio: the Audio class is triggered by callbacks from the kc85_audio class and forwards sound generation to the Oryol Synth module using a simple square wave generator