Z80 blinkenlights

Author: Andre Adrian, DL1ADR
Version: 29.Apr.2015

Introduction

There are some web pages about Z80 homebrew computers. The Zilog Z80 and the Intel 8080 are both well known by me. My first own microcomputer was a Sinclair ZX81 kit. Before I bought the ZX81 kit, I tried to solder a 8080 system together. But I did not have the knowledge nor the patience to succeed. That 8080 system of mine used an 1KByte 2708 EPROM for a little monitor program I found in a computer book. Before I could build my 8080 system I had to build an EPROM programmer. The most simple solution I found was to connect twenty switches to the EPROM data bus and address bus and a push button switch for the programming voltage. EPROM programming was to flip the switches and hit the "program" push button. The monitor program was only 768 bytes long, but I soon gave up. There was too much human error. At this time I had no EPROM eraser and I did not know that to erase a EPROM it is sufficient to expose it to direct sunshine for some hours. Over the years my more then primitive EPROM programmer and the other parts of the 8080 project got lost.
Today we live in the age of laptop and USB. As another "nostalgia project" I want to build the Z80 blinkenlights computer. For the moment the Z80 blinkenlights specification requests only 64KByte of static RAM, a RS232 port and a tiny monitor program of say 256Bytes. I do not want to spend much time on this project, therefore there is no fancy stuff like a PS/2 adapter for a PS/2 keyboard or an graphics adapter for a VGA monitor or a Compact Flash card as hard disk replacement.
The Z80 blinkenlights computer has to find its monitor program. Again I have to build a programmer. This time it is a SRAM programmer. Instead of switches and a program push button I use an ATmega 16A microcontroller. This custom made "DMA controller" connects the Z80 blinkenlights to my laptop through an USB-UART bridge like Silicon Labs CP2102, CP2104 or FTDI FT232RL.

Table of contents

Z80 blinkenlights List of parts

The most important part is the Z80. I have a Z84C0006. This is the CMOS version for up to 6.17MHz clock speed. Second is the ATmega 16A-PU. This 8-bit RISC chip operates at maximum 16MHz clock speed. Within the Z80 blinkenlights the Z80 and ATmega both operate at 6.144MHz. One gate of a 74HC04 chip implements a Pierce quartz oscillator. Next to the quartz and the 74HC04 two resistors and two capacitors are needed. The memory of the Z80 blinkenlights is 64KByte static RAM. The MAX811 voltage monitor chip, two Schottky diodes and one Lithium battery realize the battery buffered RAM. A CP2102, CP2104 or FT232RL USB-UART bridge on a tiny PCB connects the ATmega 16A RS232 interface to the "mainframe" computer, my laptop.

Clock generator

The Z80 needs a "single-phase MOS-level clock". The standard clock generator circuit is the Pierce oscillator. As in every oscillator there is a frequency dependent network and an amplifier. The amplifier should be linear, that is the output signal should be a scaled (enlarged) version of the input signal. There are no "real" digital devices, just analog devices that are "tuned" in a specific way. An inverter like the HCMOS 74HC04 has a high voltage amplification and a low output impedance. There is a small input voltage range where the output voltage depends on the input voltage. In the Pierce oscillator circuit below the resistor R2 connects inverter output to inverter input to give the input a bias to bring the inverter into the linear region. The resistor R1 reduces the RF power for the quartz. The quartz can only handle a tiny power. Further R1 isolates the "analog" frequency dependent network from the "digital" clock generator output. The quartz Q1 and the two capacitors C1, C2 form a two port network. The input voltage is applied at the two pins of C1, the output voltage is available at the two pins of C2. The dependency from output voltage to input voltage is linear, but frequency dependent. The quartz behaves for the resonance frequency as a resistor of some 10Ω. Above and below the resonance frequency the quartz has a high impedance. If we switch on the oscillator there is only some thermal noise in the circuit. Noise is a signal that contains all frequencies. Of all these frequencies the resonance frequency of the quartz gets the highest amplification. Sooner or later the supply voltage limits the amplifier output voltage. The clock generator now produces a square wave signal. Because the amplifier is not very linear, the duty cycle is not exactly 50%, but a duty cycle of 45% to 55% is typical.


The following oscilloscope picture shows the output voltage of the 74HC04 clock generator. The load is one HCMOS input gate and the capacitance load of a 10:1 oscilloscope probe. The oscilloscope bandwidth is 200MHz. We see damped oscillations every time the output voltage changes from nearly zero volt to nearly five volt or back. This "over shoot" is due to inductances and capacitances we can not avoid. Please remember, a piece of wire is an inductance and every wire has a capacitive coupling to "earth" or "ground". The inductance may be only some nano Henry and the capacitance may be some pico Farad, but at a frequency of 6.144MHz they make an effect.



For the next oscilloscope picture a 4069 device replaces the 74HC04. The 40xx logic family is older than the 74HCxx family. The 40xx devices are slower and have a higher output impedance than the 74HCxx devices. The output of the 4069 clock generator is a distorted sine wave. This picture should show everybody that there is only analog electronics and digital electronics is just a simplification. The 4069 has no "amplifier reserve". If we connect a higher frequency quartz there will be no more oscillation.



The last oscilloscope picture uses a 74AC04 as amplifier. The 74ACxx logic family is faster than the 74HCxx family. We can see that faster is not always better. The inverter oscillates at the delay time frequency of the inverter, not at the resonance frequency of the quartz. There are five oscillations per grid box in x direction. One grid box has the "length" of 20 nano seconds. One oscillation has a length of 4 nano seconds. As f = 1/T, the frequency is 1/4ns or 250MHz. An analog oscilloscope can display information that is above the oscilloscope bandwidth, a digital oscilloscope can not. We have to assume that the voltage swing is larger than the display we see, this is due to the low pass nature of the oscilloscope amplifier. The resistor R2 connects amplifier output to amplifier input- Parallel to this resistor there is a small capacitance of some Pico Farad. Mostly it is the parasitic capacitance of the breadboard connectors. The path through the parasitic capacitance has a lower impedance than the path through the quartz. The circuit behaves like a ring oscillator where the output of the inverter is directly connected to the input and the signal travel time of the inverter defines the output frequency.



Everybody can see that the 74HC04 device is the correct device for a 6.144MHz clock generator. In our case decision is easy. Sometimes the tolerances of devices are large. Most examples of the device behave as expected, but some examples behave like a sine wave oscillator or like a ring oscillator. Because tolerances have a Gaussian distribution, it is possible that we do not see the "worst cases" at the lab bench, but only later in mass production.

The first Z80 blinkenlights experiment

What can we do with a clock generator and a Z80? We can build our first blinkenlights experiment. The most simple CPU command or opcode is NOP, no operation. The CPU reads the NOP opcode, does nothing and increases the program counter. The next opcode is fetched from the next memory location. If the next opcode is again NOP, the program counter works like a binary counter.



Picture: Z80 blinkenlights NOP test circuit. From left to right is 6.144MHz quartz, 74HC04, Z80 and MAX811 voltage monitor chip on an adapter board.


Picture: Z80 blinkenlights NOP test circuit schematics.

Hardware fun or the meaning of data-sheet values

The supply voltage for the CMOS version for the Z80 is 4.5V to 5.5V, according to the Z80 data sheet. What happens below 4.5V? Microcontrollers of today have a supply voltage monitor function or "brown-out detection" and stop program execution below the brown-out voltage. My Z80 CPU performs the NOP test at a supply voltage of 1.2V, the lowest voltage my power supply can provide. I do not know if the Z80 can execute every opcode at this low supply voltage within the full temperature range. I only know that there are chances that the Z80 will corrupt the battery buffered SRAM after I switch of the power supply and the voltage drops slowly to zero volts.
In the 1980s I worked with CMOS Z80 and battery buffered SRAM at my job. It was quite a work to give the Z80 decent brown-out capabilities. Finally we used a MAX690 chip. This 8 pin voltage monitor chip did cost more than the Z80 CPU!

The SRAM programmer

The Z80 blinkenlights needs a monitor program to get things started. The traditional solution in the 1970s and 1980s was to have ROM (read only memory). The Z80 blinkenlights has no ROM, PROM, EPROM or EEPROM, it has only 64KByte of battery buffered SRAM. The SRAM can hold the contents between "sessions" due to a Lithium battery. But at some point in time the monitor program must be loaded into the SRAM. A SRAM programmer does this cold start job. The "Ecstatic Lyrics" web page explains how to build a Z80 EEPROM programmer with a FT245RL chip and (many) glue logic chips. This is a hardware only solution. The author uses an Atmel ATmega 16A microcontroller. The ATmega has a DIL40 package and offers 32 digital input/output pins. The ATmega behaves like a DMA (direct memory access) controller. A DMA controller uses the /BUSRQ input of the Z80 to get control over the address bus, the data bus and the control lines. The SRAM control lines /CE, /OE and /WE connect to the Z80 control lines /MREQ, /RD and /WR and to the ATmega control lines. The ATmega can read and write the SRAM like the Z80 does.



Picture: SRAM programmer. Top board has Atmel ATmega 16A and 128KByte SRAM. Buttom board has programming adapter, USB-UART adapter and four LEDs for /WR, /RD, /MREQ and /BUSRQ.

Default behavior fun or read the f***ing manual

At first, the SRAM programmer showed some strange behaviour. The Z80 data bus connects to Atmel port C. It was possible to write all zeros in a memory cell, but it was not possible to write all ones. The Atmel program did not control port C bits 2, 3, 4 and 5. These four bits have an alternate use as the JTAG interface. The Atmel manual tells us in chapter 26: "To be able to use the JTAG interface, the JTAGEN Fuse must be programmed. The device is default shipped with the fuse programmed". The author did carefully read chapter 12.3.3 "Alternate Functions of Port C". It would have helped a lot if the document writers at Atmel told me in chapter 12 that by default the JTAG interface is enabled.

Z80 blinkenlights Monitor program

The monitor or BIOS program executes after a CPU reset. The monitor program initializes the hardware, like configure the UART to the correct speed. Then the monitor program enters the monitor command loop. Traditionally the monitor has some primitive commands to read and write the memory. The Z80 blinkenlights monitor can read Intelhex record types 00 and 01. The monitor program is written in PL/M-80 and assembler.

Intel hexadecimal object file format

The early Intel development systems Intellec 4 (4004, 4040) and Intellec 8 (8008, 8080) had no mass storage. The "computer terminal" of the day was an electromechanical teletype like the ASR 33. The paper tape read and punch unit of the teletype provided the first mass storage. The ASR-33 used 7-bit ASCII, but implemented only upper case letters. The Intelhex format uses two ASCII characters to encode one byte. At the 20mA current loop interface the ASR-33 used a parity bit to allow error detection. The paper tape had no parity bit track. Therefore Intelhex uses a checksum byte for error detection.
Intelhex record type 00 and 01 were defined in 1973. Other record types were added for 8086 and later microprocessors.

PL/M-80

Gary Kindall invented the programming language PL/M and the operating system CP/M. In 1975 one could choose between the programming languages Pascal, PL/M and C. Only C is in use today. Pascal was used to teach students structured programming. PL/M was used to write the single-user/single-task operating system CP/M. C was used to write the multi-user/multi-task operating system UNIX. Pascal and C allow recursive functions. PL/M functions are not recursive by default. This is a good optimization feature for the Intel 8080 that has no efficient stack-relative addressing modes. The REENTRANT attribute makes a PL/M function recursive. The implementation of the strcpy() function shows the power and elegance of C. Here is the ANSI-C version:
void strcpy(char *d, char *s)
{
    while(*d++ = *s++)
        ;
}
The PL/M version is:
STRCPY: PROCEDURE (D, S);
    DECLARE (D, S) ADDRESS,
        (DI BASED D, SI BASED S) BYTE;
LOOP: DI = SI;
    S = S + 1;
    D = D + 1;
    IF DI <> 0 THEN GOTO LOOP;
END STRCPY;

The GOTO statement is needed because there is no do..while loop in PL/M. Pointer arithmetic in PL/M is strange for a C programmer. There is no dereference operator in PL/M, one has to create a variable that is BASED on the pointer variable for indirection. Like Pascal, PL/M allows nested procedures. The CP/M sources make very little use of nested procedures. The author wonders: Structured programming with nested blocks were a great invention in computer science. Did some people think that nested procedures were even better?
The 8080 microprocessor can handle three pointers in registers. The H, L register pair is more flexible than the B, C or D, E register pair. All "pointer registers" allow to load and store the accumulator from the pointer address. One implementation of the strcpy() function in 8080 assembler is:
LOOP: LDAX B
    STAX D
    INX B
    INX D
    JNZ LOOP
The pointer s is in registers B, C and the pointer d is in registers D, E.

PL/M Parameter passing conventions

The strcpy() function above uses two parameters. One method of parameter passing is to use the stack. The calling function puts the values of argument 1 and argument 2 on the stack, the called function gets the parameter off the stack. This method allows recursive functions. Another method is to pass arguments in registers. A third method is to have "hidden" global variables for parameter passing. The second and third method do not allow recursive functions. The Intel "A Guide to PL/M Programming" document from September 1973 tells in section "Subroutine linkage conventions" how parameter passing is done on a PL/M 8008 system. The first argument is passed in registers B, C. The second argument is passed in registers D, E. Additional parameters are passed as "hidden" global variables. Strange for me, the author, is the convention to pass low byte in register B or D and high byte in register C or E. The 8080 microcomputer has 16-bit addition opcodes. These opcodes require low byte in registers C, E or L and high byte in registers B, D or H.