Z80 Peripheral Emulation (Part 3)

A project log for 3-Chip Z80 Design

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

Probably one of the best references for the Z80 peripherals is um0081. um0081  describes the PIO starting on p 175. One of the more useful diagrams is the block diagram:

This shows the internal structure that the PIO has. Most importantly it shows the specific registers which need to be created as data values in the low level code contained in Z80_PIO_emul (.c and .h files). since they are all 8 bits values (or less in a couple of cases) they can be represented as uint8 types. These look like:

volatile uint8 PIO_Mask_Port_0;
volatile uint8 PIO_Vector_Address_Port_0;       // Mode 2 interrupt vector
volatile uint8 PIO_Interrupt_Vector_Port_0;
volatile uint8 PIO_Output_Register_Port_0;

volatile uint8 PIO_Mask_Port_1;
volatile uint8 PIO_Vector_Address_Port_1;
volatile uint8 PIO_Interrupt_Vector_Port_1;
volatile uint8 PIO_Output_Register_Port_1;

These values are defined as volatile since they could be changed by interrupt routines.  They get placed into the .c file.

Digging further into the data sheet shows the bit fields of the registers and these are added to the .h file.

#define PIO_OP_MODE_0       0x00 // Output
#define PIO_OP_MODE_1       0x40 // Input
#define PIO_OP_MODE_2       0x80 // Bidirectional
#define PIO_OP_MODE_3       0xC0 // Control which bits are ins/outs
#define PIO_OP_DIR          0x01 // 1=input, 0-output

#define PIO_INT_EN_BIT      0x80 // 1=enable interrupts, 0-disable interrupts
#define PIO_AND_OR_BIT      0x40 // 1=AND, 0=OR
#define PIO_HIGH_LOW        0x20 // 1=monitor for high, 0=monitor for low
#define PIO_MASK_FOLLOWS    0x10 // Define mask bits follow
#define PIO_INT_CTL_WORD    0x07 // Signifies Interrupt Control Word
#define PIO_MASK_BITS       0xFF // Bit mask values

#define PIO_OP_MODE_MASK    0xC0 // Relevant bits
#define PIO_OP_MODES_WORD   0x0F // Relevant bits

We will probably tweek these values as we go along, but this is a good starting point.  In the previous log we created the following function stubs which should be put into the .h file as function prototypes.

void PioReadDataA(void);
void PioWriteDataA(void);
void PioWriteCtrlA(void);
void PioReadDataB(void);
void PioWriteDataB(void);
void PioWriteCtrlB(void);

These also need to be added to the .c file and will be filled in as the functions are developed to handle each Z80 transfer. For now, just do something like this for each function:

void PioReadDataA(void)

 The Z80_PIO_emul.h file should be added to the #includes at the top of the Z80_IO_Handle.c file so that it knows picks up the function prototypes.

#include <project.h>
#include "Z80_IO_Handle.h"
#include "Z80_SIO_emul.h"
#include "Z80_PIO_emul.h"

The include file gets added along with the other interfaces.

At this point the code compiles without error. Of course, we are missing the meat, but we have a solid skeleton for putting the meat onto.

In the next part we'll start looking at the details of the lowest level functions. What should happen when the Z80 reads the Status of Port A or writes the data of Port B?