With the CPU completed, it is time to think of an I/O to be able to play with the system somewhat more interactively than by plugging EPROM chips in and out and attaching logic probes to data and memory buses. At first, my plan was to build a PS/2 keyboard controller (input) and a rudimentary LCD display (output). Armando Acosta convinced me that it is not a right way. Minicomputers from the era when TTL was a mainstream technology had no keyboards or video. Operators controlled them by front panels with lots of lights and switches or by communicating with the computer using a terminal over a serial port. Keyboards and displays came later, with the arrival of personal computers. Apart from being more historically correct, a serial port will give me more flexibility. I can connect the computer to anything ranging from an old dumb terminal from eBay, to a modern laptop PC. Not to mention that it may be fairly easy to connect the machine to Internet over SLIP protocol, which is one of the ultimate goals of this project.
Serial communication is about converting parallel data (a bus) to a serial representation (1 wire), delimited by control bits (depending on the actual protocol used) and the opposite. I could attempt to build a serial port out of TTL chips or use a readily available UART chip to take care of all the details of parallel-to-serial and serial-to-parallel conversion. Since building a serial port from scratch sound to me like a project of its own, I decided to go for the latter option and use one of the commercial off-the-shelf UARTS. My choice is a 16550 chip, which is still available, fairly inexpensive and comes in a 40-pin DIP package. The 16550 is a successor of 8250 and 16450 UARTS, which were common in early PCs. It is typically used with a MAX232 or similar chip to convert TTL voltage levels of 16550 to RS-232, and from RS-232 to TTL.
As per its datasheet, the 16550D chip (its current revision) has the following features:
- It performs serial-to-parallel (device to CPU) and parallel-to-serial communication (CPU to device) conversions.
- It has a programmable interrupt system to present to the CPU interrupts on occurrences of data transmit, data receive or specific line status.
- It has a 16-byte FIFO buffers on the transmitter and receiver to reduce the load on CPU (by firing interrupts less frequently).
- It adds and deletes asynchronous communication control bits (start, stop and parity) in a programmable notation (like a common 8-N-1 or something more exotic).
- It generates standard RS-232 modem control signals like CTS, RTS, DSR, DTR, RI and DCD.
- It exposes 12 programmer visible read/write registers to control and read the UART’s status.
- It has a programmable baud generator, allowing the setting of any baud rate from 50 to 128000, depending on the programmable divisor setting and a crystal frequency used.
- It is fully TTL compatible.
It seems from the above that 16550 does everything I need on its own. All I have to do is to map it correctly to my CPU’s I/O space with few decoder chips, assign it one of the interrupt numbers, tie D0..D7 to my data bus and give it a try. Additional components I need are: a crystal oscillator, the already mentioned MAX232 voltage converter and of course a DB9 connector.
The problem I may have with 16550 is its timing constraints. The full read/write cycle of the UART is 280ns, which is quite long. I have already been thinking of extending my clock subsystem with extra logic to slow down the clock by half whenever I/O space (slow devices) are accessed by the CPU (/SLOW signal). It may turn out that now is the right moment to implement it. Otherwise, the devices I attach to the CPU become a bottleneck significantly reducing maximum CPU clock frequency.
I have previously assigned the range 1000h-1FFFh as I/O (devices). Now it is time to use it. I am assuming each device will need not more than 16-bytes of control block, so I am going to divide this space into chunks of this size and dedicate them to the devices. UARTs (I want to have two of them) will occupy first two blocks. As a result, here is how the memory map will look like:
|$1000-$1FFF||I/O area (devices)|
|$3000-$FFFF||general purpose RAM|
Each UART will occupy only the first 8 bytes of the assigned space (the other 8-byte block will mirror the same physical registers). The trick with 12 registers mapping only to 8-byte address space is that some write-only registers share the same address with other read-only registers. Here is an extract from National Semiconductors’ 16550D datasheet showing what registers the device exposes.
My CPU has 8 priority driven interrupt input lines, IRQ0 being the highest priority interrupt. I will assign both UARTs, UART0 and UART1 to IRQ channels #2 and #3, respectively, leaving highest priority channels for something else I may want to add in the future (like timer interrupt from RTC or IDE controller interrupt). For the sake of documenting things, here is how my IRQ assignment map will look like:
|IRQ0 (highest priority)||unused|
|IRQ7 (lowest priority)||unused|
The drawback of this approach is that UARTs are not equal. UART0 has higher priority IRQ assignment than UART1. I don’t see a real problem with that at this point, so I will not add any multiplexing on the IRQ lines of both 16550s and leave them independent, as shown above.
Now it is time to run my CadSoft Eagle again, prepare the initial revision of schematics and start wire wrapping. Both UARTs will occupy one euro size (100x160mm) prototyping card, which I am going to wire wrap using a slightly different technique. I will solder pins to the card, instead of using expensive wire wrapping board. I hope to be able to report on progress soon, so stay tuned.