Z80 Peripheral Emulation Guide for the PSoC

A project log for 3-Chip Z80 Design

Combining a Z80 retro design with a modern PSoC CPU. 10/15/2019 at 17:550 Comments

On this board, Z80 peripherals are emulated by the PSoC. This is done using a combination of hardware and software in the PSoC. The PSoC connects to all of the Z80 address and data lines as well as the Z80 control lines and in the process acts as an I/O Space mapped peripheral part to the Z80.

The peripherals are typically done from PSoC elements. For instance, the SIO (Serial Input/Output) device is emulated and mapped to the USB interface inside the PSoC. When you insert a USB interface onto the schematic in PSOC Creator, the PSoC automatically generates the API routines to talk to its own hardware. The job of emulating peripherals then falls to the software running on the PSoC. The software takes the read/write requests from the Z80 and maps them to the PSoC peripheral. This can be setting control/status registers and/or reading/writing data. Thus, the PSoC hardware takes care of the low level communications.

This log will show the steps to create a peripheral for the PSoC. This was demonstrated in part for the USB-Serial in previous logs. This log will demonstrate the steps to use an MCP23017 external to the PSoC as an equivalent to the Z80 Parallel I/O (PIO). Some of the PIO functions can be emulated and others can't due to the hardware limitations of the MCP23017.

The Datasheet

The first step is to read through the datasheet for the Z80 peripheral you want to emulate. This will indicate how many control and status registers there are. It will also suggest the functions needed by the emulation code.

Map to Internal PSoC or External Hardware

The next step is to map the functions that the Z80 peripheral chip provides to the internal (to the PSoC) or external (connected to the PSoC through some pins. There aren't enough pins on the PSoC to do the PIO function so an external part is used. For the PIO this looks like:

Z80 PIO                 MCP23017
2 of 8-bit I/O ports    2 of 8-bit I/O ports
Pins set to input       Pins set to input
Pins set to output      Pins set to output
Pins set to bidirec     Emulate bidirec pins under control
Two control lines/port  No equivalent
Parallel interface      Parallel interface to PSoC
                        I2C interface to MCP23017
Speedy (Z80 IO speed)   I2C speed (slower)

 So, it looks like the MPC23017 can do most of the basic functions minus handshakes and could possibly do the handshakes under the right set of controls (taking up some of the bits used by the 2nd parallel port, for instance).

Whether or not this is sufficient is application dependent. In the case of the PSoC emulating SIO over USB the cost (in time) is relatively small and the gain is significant (the same connector that powers the card also provides the USB signals).

In the case of the MCP23017 emulating a PIO chip, the slow speed of the I2C interface to the PSoC might be an issue. but it might be possible to use the MCP23018 and the SPI bus which is significantly faster and could connect to the 4-pin header on the card.

Also, the automatic ready/strobe of the PIO could be emulated at the cost of slower I/O since the PSoC would be tied up with handing those control/status lines and would limit I/O during that time.

Simple applications, like reading a joystick or blinking an LED could easily be accommodated by this approach.

If the MCP23017 is run at 400 KHz (around 40 KB/sec) and it takes 4 I2C transfers to do a read/write then it's running at a speed of around 10,000 transfers a second. That's not nearly as fast as the PIO, but certainly fast enough to read joystick switches.

Files To Be Created

Let's work from the bottom up here. A couple of C and header files need to be created that will contain the lower level functions. Create files to describe the lowest level of the part. For this example, that's two files; Z80_PIO_emul.c and Z80_PIO_emul.h .

The .h file contains the #defines which describe the register and bits. The .c file will contain the low level code that handles I/O.

Update the Z80_IO_Handle Functions

The Z80_IO_Handle contains the memory map and calls the lower level emulation functions. For this step, add the peripheral to the memory map in the Z80_IO_Handle.h file as #defines.

The functions to call the lower level code get added to the HandleZ80IO( ) loop in the Z80_IO_Handle.c file. These handlers are coded in a switch-case statement so the case values need to be added for the port addresse(s) of the peripheral. The address #defines from the .h file allow these values to be easily moved around in the memory space (if needed) as a compile time option. If they needed to be dynamically configured they could be stored in variables although this would likely affect compiler optimizations negatively. The need to have I/O ports at re-configurable locations seems like a remote possibility.

We'll dig into the details of these files in the next logs.