Saturday, February 16, 2013

Phoenix: I/O

A computer that you can type programs into is nice, but sometimes you'd like a way to save things to a more persistent medium, and later load it again. Early home computers used cassette tapes for storage, and although I'm sure I could make something like that work with for example a phone's headset connector, I decided to use SD card storage instead. Despite a 30-year generation gap between the 8-bit Z80 and a current-day 4 GB microSD card, it's actually not that hard to get the two to talk to each other successfully.

First of all, SD cards use 3.3 volts power and logic levels, and most SD card holders are surface mount only. So I decided to use the Adafruit microSD card breakout board: this includes a voltage regulator, level shifter and microSD card holder all on a small PCB with standard .1" spacing suitable for breadboarding.

Now that the electrical hook-up was out of the way, I needed a way to make the Z80 and the microSD card talk to each other. Fortunately, the SD card standard allows for SPI communication, mostly for use with microcontrollers. The Z80 doesn't speak SPI, but it's not hard to bit-bang it.

I could have used a 82C55 parallel I/O interface chip (this was used in the original IBM PC for the parallel port, for example), but using a giant 40-pin DIP chip just for 4 data lines seems massive overkill. Instead I used a few 7400-series logic chips to create the interface:



The 74HC541 buffer (the same type I used in the video circuit) is used for data from the SD card to the Z80: when the Z80 issues an "IN" instruction it takes IORQ and RD both low, and the combination of both enables the '541 output drivers, placing the MISO signal from the SD card onto the Z80 data bus.
Note: both the keyboard controller a few posts back and this circuit have no address decoding logic, so they can't be used together, but more on that below.

For the other direction, I used a 74HC175 quad D-type flip-flop. It's essentially being used as a 4-bit register: when the Z80 issues an "OUT" instruction, IORQ and WR are both pulsed low and through the 74HC32 OR-gate generate a clock pulse to the flip-flops updating their state with the value being output by the Z80. Note that I used /Q4 to hook up the microSD enable signal, which is active-low, so that when the flip-flop is reset at startup the enable signal is high (inactive).

I've written some code to perform the SD card initialization sequence and here is what it looks like on the logic analyzer:

Note that since the protocol is SPI, there is a byte being transferred in both directions for each 8 clock transitions and it is up to the higher level protocol whether the MOSI or MISO direction is actually being used. After taking ENABLE low, the Z80 first reads a byte to verify the card is ready, then writes the sequence 0x40 0x00 0x00 0x00 0x00 0x95 which is the SD initialization command (CMD0). It then reads another byte to wait for the SD card's response being ready, which comes in the next byte: 0x01, which indicates successful initialization.

Unfortunately, this is all the software I've written so far for the SD card interface at this point. I do plan to expand it so I can load and save programs on the SD card, just like I used to do on tape with my ZX Spectrum. I'll miss the loading sounds though.

Now, back to the keyboard controller: given that the ATtiny85 used for the keyboard controller can do SPI as well, and there still are a few lines available on the 74HC541 and 74HC175, we can actually eliminate the 74HC595 shift register and hook up the ATtiny85 similar to the SD card.

The keyboard protocol is now changed as follows: the ATtiny's SPI hardware is initialized in slave mode; when a key is ready the ATtiny puts the scan code into the SPI output register and takes the PB0 line high. Once the Z80 sees this line has gone high, it toggles the PB2 line 8 times which causes the SPI hardware in the ATtiny to shift out the bits.

I've updated the code on GitHub for the ATtiny keyboard controller program as well as the Z80 machine monitor program to use the new protocol, which looks like this on the logic analyzer when the "a" key is pressed:

Thursday, February 7, 2013

Phoenix says "Hello, world."


Now that Phoenix has a CPU, memory, display and a keyboard, it's time to actually make it do something. The first "real" program I wrote was loosely inspired by the Apple 1's Woz Monitor: a simple text-based program that can be used to inspect and modify memory contents, useful for debugging during the development of more advanced programs.

Since Phoenix display hardware is graphics based, the first thing I needed was a way to display characters on the screen. Using 8x8-pixel characters on the 256x192-pixel screen gives 24 lines of 32 characters each. Not spectacular, but the same as used in the ZX Spectrum, for example.

It's fairly straightforward to display 8x8-pixel characters: the framebuffer is organized as bytes of 8 pixels each. A line of pixels is 32 bytes, and so a line of text is 8 * 32 = 256 bytes. This makes cursor addressing especially easy: converting from (row,column) in a register pair to the location in memory just requires adding the start address of the framebuffer.

In addition to just drawing the characters, we need to keep track of the current position and increment  it after each character, handle newlines and scroll the screen when the bottom is reached. The latter can be done easily using the Z80 "LDIR" (Load, Increment, Repeat) instruction.

Since the monitor deals with memory addresses and contents, I also wrote some functions to print and parse hexadecimal values.

The monitor program (as before, available on GitHub) consists of a main loop reading a character from the keyboard using the routine described in the previous post, and interpreting it as one of the commands listed below. There's a separate function implementing each of these commands, reading additional arguments as needed and then executing it, followed by jumping back to the start of the loop.

The commands supported are:

  • e value -- enable/disable keyboard [e]cho: whether keyboard input is shown on the screen.
  • d address size -- [d]ump the contents of size number of bytes at address.
  • l address size byte... -- [l]oad size number of bytes into memory starting at address.
  • j address -- [j]ump to address.
And here is a demonstration of the monitor being used to enter a simple Z80 machine code program, verify its contents, and run it:


Sunday, February 3, 2013

Phoenix: Keyboard


Compared to the graphics subsystem, Phoenix's keyboard interface is much more straightforward. Home computers of the early '80s usually had a built-in keyboard, but building my own keyboard seemed a bit much. Instead I opted for an external keyboard with PS/2 connector: still widely available (albeit usually using an adapter), and easy to interface, especially compared to USB.

The PS/2 interface contains just 4 wires: ground, 5 V power, clock and data. The clock and data signals form a synchronous serial signal. The bus is bidirectional (so the computer can send data to the keyboard, e.g. to update the state of the caps lock and num lock LEDs), but in this project I'm only using only the keyboard->computer direction. As a result the caps lock indicator LED won't work.

As the controller I used an Atmel ATtiny85 microcontroller. Compared to the ATmega324 and ATmega328p used earlier in this project, the ATtiny does live up to its name: it only has 8 pins (5 of which usable as I/O pins), 8 KB of program memory, and just 512 bytes of RAM. Nevertheless that's way more than enough for a keyboard controller.

The PS/2 protocol uses a start bit, 8 data bits, parity bit and stop bit. The approach I took was very simplistic: just wait in a busy loop for transitions on the clock line, and after 9 bits (start bit + 8 data bits) are received, process the received byte. There's a simple lookup table translating the scan codes sent by the keyboard to their corresponding ASCII values.

There's also a few state variables keeping track of the state of the modifier keys (shift, ctrl, etc.) as well as to handle key release events (scan code 0xF0 followed by the scan code of the key being released).

To interface the keyboard controller with the Z80 CPU, I used a 74HC595 shift register. It  takes 3 lines to hook it up to the ATtiny (data, clock, latch). Data is written by clocking it one bit at a time, and then latched into the register by pulsing the latch pin.


When a key is pressed, the ATtiny writes the ASCII value of the key into the shift register, and when a key is released, a value of 0x00 is written, so at any point the shift register contains the ASCII value of the currently pressed key, or 0x00 if no keys are being pressed. The Z80 can poll the contents of the shift register using the "IN" instruction.

The 8 data output lines of the shift register are hooked up directly to the Z80's data bus. However, they are only enabled when the 74HC595's "G" (gate, or output enable) pin is active (low). When the Z80 "IN" instruction is executed, IORQ and RD are simultaneously active (low). By hooking up IORQ and RD to an OR-gate, the desired behavior is achieved: only when they are both low, the shift register's output will be enabled.

Note that as long as the shift register is the only device attached this way, there's no need for address decoding logic and reading from any I/O address will work.

Pressing the A key on the keyboard (scan code 0x1C). There are 11 clock cycles in the PS/2 protocol: 1 start bit, 8 data bits, a parity bit, and one stop bit. The data is sent to the shift register during the parity bit.


Zooming in: the ASCII value for the "a" key 0x61 is being sent to the shift register.


The AVR C program for the keyboard controller is available on GitHub. The Z80 code to read a key is as follows:

getchar:
  ; Wait for previous key to be released.
  LD A, (lastkey)
  LD B, A
getchar1:
  IN A, (0xFE)
  CP B
  JR Z, getchar1
  ; Wait for a key to be pressed.
getchar2:
  OR A
  JR NZ, getchar3
  IN A, (0xFE)
  JR getchar2
getchar3:
  LD (lastkey), A
  RET
lastchar:
  DB 0x00

Wednesday, January 30, 2013

Phoenix: Graphics


The video functionality was the most interesting and most rewarding aspect of the Phoenix hardware design.

I decided to use composite video output ("analog TV") for Phoenix, as this is what most Z80-based computers tended to use, and it's fairly easy to generate using a microcontroller (at least for monochrome).

Some articles I used as inspiration:


A composite video signal contains horizontal sync, vertical sync, brightness and color information. If we just look at monochrome (no colors or even greyscales) the signal has 3 voltage levels: 0.3 volts for black, 1.0 volts for white, and 0.0 volts for synchronization pulses (both horizontal and vertical).  It's easy to generate these voltage levels using 2 digital output pins and a few diodes and resistors, as described in the articles above.

I decided to use an ATmega328p as the microcontroller for the video signal generation. It may seem like cheating to use a modern microcontroller in a retrocomputing project; on the other hand many Z80-based computers used custom ICs for video. Programming a microcontroller is just a different way of making a "custom IC".

I started with generating the sync pulses. This is basically just an exercise in careful timing. Rather than using the AVR timers, I just wrote this in AVR assembler as a single loop with pin toggles and delay loops. Counting the number of clock cycles for each instruction, this is a relatively easy (but very boring) process. The result on the logic analyzer:


On the left a few normal horizontal sync pulses (the last few scan lines of a frame), followed by the series of pulses making up a vertical sync: these are half a scan line long (6 half-lines of short pulses, followed by 6 half-lines of long pulses, and then 6 short ones again).

Now we need the actual image. Since I grew up with a ZX Spectrum, I decided on a resolution of 256x192 pixels. For 256x192 monochrome pixels a framebuffer of 256/8*192 = 6144 bytes (6 KB) is needed. Since Phoenix already has 32 KB of RAM, I can just set aside 6 KB for the framebuffer, although simultaneous access from the video circuitry and the CPU needs to be figured out (more on that below).

An ATmega328p is not fast enough to read from memory and produce the brightness signal. Instead, I used a slightly different approach: the ATmega will just put addresses on the bus, and since each byte in the frame buffer contains 8 pixels, a 74HC165 8-bit parallel-in-serial-out shift register is used to output the pixels one at a time:



Each group of 8 pixels is basically generated as follows (assuming the ATmega328p has full access to the memory):

  • place correct framebuffer address on the address bus
  • load byte from data bus into shift register (this outputs the first bit in the byte)
  • shift the bits in the shift register repeatedly to display the remaining 7 pixels

To get uniform pixels, the load and shift pulses must happen exactly evenly spaced at the pixel clock frequency.

To get roughly square pixels the pixel clock needs to be about 6 MHz. Toggling a pin on and off requires 2 clock cycles on the AVR. However, we also need to increment the address and put it on the bus every 8 pixels. To keep an even pixel size, we therefore need 3 clock cycles per pixel to do all these operations. At a 6 MHz pixel clock, this means the ATmega needs to run at 18 MHz, which is within its specifications.


The logic analyzer shows this pattern: a load pulse every eight pixels, and shift pulses for the 7 remaining pixels. The pixel data from the shift register is updated at a 6 MHz rate, and the address bus is updated every 8 pixels.

The only remaining issue is making sure the Z80 CPU and the ATmega play nice together for access to the RAM. The solution I chose is not very sophisticated: the Z80 has a "bus request" signal which allows an external device (such as the ATmega in this case) to request access to the address and data bus. After asserting this signal, it may take a few clock cycles for the Z80 to grant access, which it indicates using the "bus acknowledgement" signal.

Given the tight pixel timing requirements, we have to request the bus for the entire period of the 256 horizontal pixels. This means that while pixel data is being sent to the screen the Z80 is effectively halted. This works out to approximately 50% of the time, so even though the Z80 is clocked at 6 MHz, it performs like a 3 MHz one.

The final bit of the video design involves taking control of the bus as soon as the Z80 grants it. This is implemented using a 74HC541 buffer whose output is gated by the Z80 bus acknowledgement signal. As soon as the Z80 acknowledges the bus request, the 74HC541 puts the MREQ, IORQ, RD, WR, A15 and A14 signals in a defined state.

The AVR assembler code for this project can be found on GitHub.

And finally two pictures:
Early prototype of the video circuit: the ATmega328p and 74HC165 wired together. There's no memory, instead I'm using the address lines as the input to the shift register.
Final version of Phoenix showing a test pattern on an Adafruit 4" NTSC monitor.

Monday, January 21, 2013

Phoenix: Memory (part 1)

Z80-based computers had memory sizes varying from 1 KB (e.g. ZX81) to 128 KB (e.g. ZX Spectrum 128). Because the Z80 only has a 16-bit address bus, addressing more than 64 KB of memory requires bank switching.

Even for relatively small memory sizes, multiple memory chips were needed. For example, 16 KB of memory could consist of 8 separate 16 kilobit memory chips, each hooked up to a data line of the CPU. For Phoenix, the situation is easier: we can just use a single 32 KB SRAM chip.

This SRAM chip has 15 address lines, 8 data lines and 3 control lines:
  • CE (Chip Enable): if this is inactive (high) the other control signals (OE and WE) are ignored.
  • OE (Output Enable): setting this active (low) does a memory read and outputs the byte on the bus.
  • WE (Write Enable): setting this active (low) does a memory write.
These signals have the exact same semantics as the Z80 MREQ (memory request), RD (read) and WR (write) signals, so we could hook up the memory directly to the CPU without any "glue" logic.

A computer with just RAM isn't very useful, so we either need to add some (programmable) ROM or find a way to fill the RAM before the CPU starts up. Again, inspired by Veronica, I chose the latter way (for now at least: the final Phoenix design does have an EEPROM).

To pre-fill the memory, I used an Atmel ATmega324 microcontroller. If you're familiar with Arduino, the ATmega324 is very similar to the ATmega328 used in the Arduino, except it has more I/O pins. The ATmega324 has a total of 32 I/O pins, more than enough to drive the 15 address lines, 8 data lines and 3 control lines.

However, the ATmega and Z80 can't both drive the memory at the same time, so we do need some glue logic after all:

On the left are the signals from the ATmega (PB0 and PB1) and from the Z80 (MREQ and WR). On the right are the signals to the memory (CE and WE).

While pre-filling the memory, the ATmega holds pin PB0 low. This has two effects: it enables the SRAM chip (through the AND gate, keep in mind that the signals are active-low), and keeps the Z80 in the reset state, so the address and data buses are kept in high-impedance state by the Z80, and can be controlled by the ATmega.

It then puts an address on PORTC and PORTD, data on PORTA, and briefly sets PB1 low to write this to the SRAM. After cycling through the entire memory contents this way, it then sets PORTA, PORTC and PORTD to high-impedance (so the Z80 can control them again), and sets PB0 and PB1 to high. Now the Z80 starts up and MREQ and WR are propagated through the AND gates to the SRAM.

We can reprogram the ATmega while it's connected to the memory and CPU: this uses pins PB5, PB6 and PB7, which are not connected to either the Z80 or the memory. During programming all I/O ports are in high-impedance state, the pull-down resistor ensures the Z80 is held in reset during this time as well.

To verify that everything works, I uploaded a trivial Z80 program (only 4 bytes). It doesn't do much useful: it just tries to write a dummy value to a nonexistent output port. At least we should be able to see the IORQ (I/O request) signal being asserted.

loop: OUT (0xFE), A
      JR loop

The full ATmega source code to pre-fill the memory:

#define F_CPU 8000000UL

#include <avr/io.h>

#include <avr/pgmspace.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <util/delay.h>

#define CE PB0

#define WE PB1

uint8_t data[] PROGMEM = {

              // loop:
  0xd3, 0xfe, //   OUT (0xFE), A
  0x18, 0xfc, //   JR loop
};

int main() {

  DDRB = _BV(CE) | _BV(WE);  // CE and WE are outputs.
  PORTB = _BV(WE);           // CE low (chip enabled), WE high (not writing).

  // CE is asserted, Z80 is in reset, we can take the bus.

  DDRA = 0xFF;
  DDRC = 0xFF;
  DDRD = 0xFF;

  _delay_ms(20);  // Wait for bus to stabilize.


  for (uint16_t address = 0; address < sizeof(data); ++address) {

    // Place address and data on the bus.
    PORTC = address >> 8;
    PORTD = address;
    PORTA = pgm_read_byte(data + address);
    // Toggle WE.
    PORTB &= ~_BV(WE);
    PORTB |= _BV(WE);
  }

  // All done - release the bus.

  PORTA = 0;
  PORTC = 0;
  PORTD = 0;
  DDRA = 0;
  DDRC = 0;
  DDRD = 0;

  // Take CE high, Z80 can take over.

  PORTB |= _BV(CE);

  // Go to sleep, our work here is done.

  power_all_disable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  for (;;) {
    sleep_enable();
    sleep_bod_disable();
    sleep_cpu();
  }
}

And here is how this looks on the logic analyzer:


The first 3 signals are observed from the point of view of the SRAM. At the start the CE signal is kept low while the 4 write pulses load the program into the memory. Then execution starts as normal and we see the pattern where opcodes and operands are being fetched from memory. The OUT instruction is easily recognizable by IORQ.

And another breadboard picture:


In the front, the Z80 with on its left the 4 MHz oscillator and to the right the 74HC08 AND chip. In the second row, on the left is the big ATmega324, and on the right is the 32 KB SRAM chip. Note that at this point, I had wired up only A0 and A1, enough for the trivial 4-byte program shown above, but anything more serious requires more address lines obviously.

Phoenix is now a working computer able to execute programs, but there's no way to interact with it yet (and hooking up logic analyzer probes, fun as it may be, doesn't really count).

Saturday, January 19, 2013

Phoenix: CPU


8-bit home computers in the early '80s were predominantly based around the 6502 and Z80 processors. Since I grew up with a ZX Spectrum, I decided to base Phoenix around the Z80 (unlike Veronica, which is 6502-based).

The Z80 is an 8-bit processor, with an 8-bit data bus and 16-bit address bus. The latter means it can address up to 64 KB of memory. CMOS Z80's can run at up to 10 MHz. As a comparison, the ZX Spectrum ran at 3.5 MHz.

Apparently, brand new Z80's are still being made even today, more than 30 years after the chip was first introduced. The one I bought from Digi-Key had a date code of 1229, which if I read it right means it was produced in July of 2012.

To get a Z80 up and running, you need a clock signal (not just a crystal). The final Phoenix design uses a 6 MHz oscillator chip but for this post I used a oscillator made out of some components I had at hand: a 16 MHz ceramic resonator, 74HC00 NAND IC, some resistors and and a 74HC74 dual flip flop.  The resonator, resistors and 74HC00 together produce a 16 MHz clock signal, which is then halved twice by the flip flops down to 4 MHz.

The Z80 needs a reset pulse to properly initialize itself. This can be generated easily using a capacitor to ground and a resistor to 5 V.


In addition to the clock and reset signals, all that's needed is to keep the control signals (interrupt, bus request, etc.) in a defined state. Note that these are active-low so need to be connected to 5 V.

Since the data bus is bidirectional, I hooked up the data pins to ground using 10 kΩ resistors. This way there won't be a short if the Z80 issues a write, and a read cycle will produce all zeroes. Conveniently the 0x00 opcode is NOP, so if all works well the Z80 would just execute a steady stream of NOPs, as if it were connected to 64 KB worth of memory filled with zeroes.

Using my Saleae Logic analyzer, we can observe this in practice:


This looks exactly like the timing diagram in the Z80 data sheet. The first line is the 4 MHz clock signal, and we can see that the other signals repeat every 4 clock cycles. Keep in mind that all these are active-low. The MREQ (memory request) is pulsed twice during each instruction, first together with RD and M1 to read an opcode from memory, and then together with RFSH as a DRAM refresh signal.

Let's try a more interesting instruction. Changing pin D4 from 0 to 1 puts 0x10 on the data bus, which is the DJNZ instruction (decrement and jump if not zero):


Here the pattern takes a whopping 13 clock cycles. It starts out like the last one: first an opcode read (MREQ, M1, RD), followed by a memory refresh (MREQ, RFSH). Then there is another memory read to get the jump displacement (MREQ and RD, but without M1 this time). And then another 5 cycles where nothing happens on the bus and the Z80 executes the jump internally.

As you can see, the Z80 is a relatively slow processor requiring 4 clock cycles for even the simplest instructions, in contrast to the 6502 which could execute instructions in a single cycle. The ZX Spectrum ran at 3.5 MHz Z80 whereas the Commmodore 64 had a 1 MHz 6510.

Here's a picture of the Z80 on my breadboard hooked up as described above. On the left are the two ICs to generate the 4 MHz clock signal and on the right is the big Z80 chip with the logic analyzer probes still attached to it.


Of course, executing the same instruction over and over is not very exciting, so in the next post we'll add some memory.

Friday, January 18, 2013

Phoenix

Inspired by Quinn Dunki's Veronica project, I've been working off-and-on the past few months on my own design for a "retro" computer: a computer similar to the home computers of the early '80s.

Here is the result, code named Phoenix:


  • CPU: Zilog Z80 at 6 MHz
  • Memory: 32 KB RAM and 8 KB EEPROM
  • Input: PS/2 compatible keyboard
  • Output: composite video, 256 x 192 monochrome pixels
Over the next few days, I'll post a few articles describing the design and build.