LM-512 homebrew 8-bit microcomputer

The LM-512 is an 8-bit microcomputer which I designed and built over the course of 2013 and 2014. It's based on the Z80 processor, clocked at 10MHz (about 3 times faster than was typical in the Z80's hey-day), has 512KB of RAM (hence the name) and 8KB of ROM. On the mainboard are two UARTs (enabling a modern computer to act as a dumb terminal), a Real Time Clock, a 16-bit interval timer (which is actually provided by the UART chip) and an IDE interface which I use with an adaptor for CompactFlash cards. There is also an expansion interface that lets me connect up to 4 external peripherals via a ribbon cable. All of the components are through hole rather than surface mount, and all connections are made with hand-soldered point-to-point wiring. The board is of standard Eurocard dimensions, 160mm x 100mm (roughly 6 x 4 inches), and is enclosed between two sheets of laser-cut acrylic.

Memory map

The LM-512 has 8KB of ROM, which is permanently mapped to the beginning of the address space (0x0000 through 0x1FFF). Anything else is a little bit awkward with a Z80 CPU, as when the CPU is powered up, it begins execution from 0x0000. The LM-512 also has 512KB of RAM. It's static RAM, rather than dynamic, because this is a lot easier to work with (and in 201x perfectly affordable), despite the Z80's inbuilt hardware support for refreshing dynamic RAM. The Z80 can only address 64KB of RAM (using 16-bit addresses), so the full 512KB can only be used via bank switching. The 512KB is divided into 16 pages of 32KB each. The upper 32KB of the CPU's address space (0x8000 - 0xFFFF) can be pointed to any one of these pages. The lower 32KB (0x0000-0x7FFF) is permanently pointed at the first page, but the first 8KB is obscured by the ROM.

This memory map was chosen with the goal of supporting a multitasking operating system. The plan is for the ROM to hold code for interfacing with the hardware and well as basic utility functions (like string manipulation), and for the visible 24KB of RAM in the lower part of the address space to hold a simple operating system which loads programs into the higher 32KB. Each program gets its own page of memory, and context switching involves not much more than bank switching and updating the program counter and stack pointer. This system will support simultaneous execution of 15 programs (each program being limited to 32KB of memory or less), and it will be impossible for different programs to read or write to each other's memory (though there's nothing to stop any program reading or writing to the operating system's memory, so this falls short of full blown memory protection).

UARTs, timer and LEDs

The largest chips on the board are two 40-pin DIPs: one is the Z80 CPU, and the other is an NXP SCC2692 Dual UART (DUART) chip. This is a very handy chip which gives you two UARTs, a 16-bit interval timer and 8 general purpose digital IO pins all on a single chip - the same size as the more traditional 16550 chip, which gives you only a single UART! Unfortunately, the SCC2692 has been discontinued and can no longer be bought new. In fact, new through-hole UART chips with parallel data and address buses are starting to become rare and relatively expensive, which is bad news for homebrew computing enthusiasts. UART channels allow you to use terminal emulator software on a modern PC as a simple text-based interface to your homebrew computer, in a way which is much quicker, easier and cheaper than building a keyboard-and-TV interface.

The two UART channels are connected to MAX232 chips which convert between the 0/5V "TTL" levels of the SCC2692 to the +/-12V levels defined by the RS-232 standard. I decided to do this both because USB to RS-232 cables are cheaper than USB to 5V TTL cables and because I wanted to have the opportunity to hook up old RS-232 hardware, like modems or, if I should ever be lucky enough to acquire one, a genuine dumb terminal. In retrospect, it probably would have made sense to leave the RS-232 converters off and build an off-board converter in the rare event I want to use old hardware. The 5V serial connections are probably more useful as they are, for connecting to say an Arduino or to any of the cheap UART wireless adapters available on eBay (take your pick of 433MHz, Bluetooth, 2.4GHz or even WiFi!).

The 16-bit interval timer is used to trigger an interrupt, which allows applications to perform tasks with precise timing.

Two of the general purpose output pins are used to operate two green LEDs on the board, which can be used by programs for basic status indication.

Real time clock

The only other significant onboard peripheral is a Texas Instruments BQ4845 Real Time Clock. This is another fairly handy chip, which provides a battery-backed clock and calendar, a watchdog timer, a periodic interrupt generator and power brownout detector. I plan to use the periodic interrupt generator to provide a system clock of roughly 50Hz which the operating system will use for context switching between running tasks, as well as keeping track of the time.

IDE interface

A computer like this isn't a lot of use without some kind of persistent storage. The historically accurate thing to do would be to read and write data from an audio cassette tape using Kansas City Standard FSK encoding, but I wanted something which I could conveniently use to get data between the LM-512 and a Linux machine. SD cards are possible in theory, but quite a pain - they use a 3V serial interface, and the Z80 is based around 5V parallel interfaces, so you'd need to do level shifting and use software "bit-banging" to implement the SPI protocol, which would make things quite slow (although still a lot faster than an old tape drive!). CompactFlash or CF cards are a little more exotic, but easier overall. They use the same hardware interface and protocol as IDE hard drives and CD/DVD drives, which is 5V and parallel like the Z80. The IDE protocol uses a 16-bit data bus, and you can use 7400 series IC latches to do this on a Z80, but CF cards are required by the CompactFlash standard to support an older 8-bit mode of IDE (modern hard drives and optical drives do not), and I opted for this approach to keep my glue chip count down. You can't get through-hole CF card slots, so I bought a passive adaptor on eBay designed for using CF cards with IDE ribbon cables. The corresponding enclosed header is a very cheap and easy to find through hole part. I bought a used USB CF reader for my Linux machine and was good to go.

I thought CF cards would be very cheap in 201x, since SD cards had totally replaced them in consumer hardware. As it turns out, CF cards are still used by some very high-end Japanese camera manufacturers, and brand new ones cost more than equivalently sized SD cards! After learning this I bought an old 64MB CF card, manufactured by Wintec Industries, on eBay for a few bucks. With the USB CF reader I confirmed that it worked just fine, but I couldn't get the Z80 to read from it correctly. It was almost working but not quite. At this point I corresponded a lot John Coffman, who is involved with the N8VEM project and James Pearce, who runs lo-tech.co.uk, both of whom have built this kind of interface before. Both gentlemen were very helpful and I learned a lot from them, but nothing they suggested solved my problem. In the end, I bought a brand new Kingston brand 4GB CF card from Amazon, and it worked immediately and perfectly. I eventually realised that the Wintec card did not correctly support the 8-bit data mode. It was operating in 16-bit mode and the Z80 was only reading every second byte. I don't know how common it is for CF cards to not support 8-bit mode like they are supposed to, all I can say with confidence is that the latest Kingston cards work well.

Interrupt system

Everything described so far is pretty standard fare for homebrew Z80 computers. Possibly the most unique thing about the LM-512 is its prioritised interrupt system. I wish I could claim credit for its design, but I basically just adapted one I found in an old article entitled "the vectored interrupt". The system is pretty simple, and uses just two chips, a 74148 priority encoder and a 74540 inverting tristate octal buffer (in place of the 8212 chip used in the article). By using the Z80 in interrupt mode 2, you can have 8 different, prioritised interrupt inputs, with each one causing the Z80 to execute a different interrupt handler, which can be located anywhere in the address space.

The majority of homebrew Z80 designs I have seen have a pretty basic interrupt support, typically just using the Z80's maskable and non-maskable interrupt pins as two individual interrupts and nothing more. I hope to make the most of the LM-512's system by implementing fast interrupt-driven IO to the UARTs and CF cards.

This system does have one fairly major drawback which took me a while to realise and which I now don't have board space to fix. The problem is that there is no way to truly disable any individual interrupt. You can disable interrupts altogether in software easily enough, but if you want to, say, ignore the CF card interrupts but still get UART interrupts, this is not possible. You can kind of achieve this by making the CF card interrupt handler a function which just immediately returns, but then you are still wasting the time taken to push and pop the program counter to the stack. The solution is pretty simple, but chip-heavy: add an octal flip-flop like the 74575, which the Z80 can write to, and use two 7432s to OR each output with one of your interrupt sources. Feed the 8 OR outputs to the input of the 74148 priority encoder, instead of connecting the interrupt sources directly. This way you can disable an interrupt completely by setting the corresponding flip-flop high, with no software performance penalty.

Expansion port

The LM-512 has a basic expansion port for hooking up external peripherals. I hope to make a range of boards to connect to it, including sound cards built using old soundchips (especially the AY-3-891x or YM-2149 family, and Yamaha's early FM chips like the YM-2151), some kind of basic graphics card and an analog interface card using 8-bit DACs and ADCs. External cards can be connected using a ribbon cable with a 26-pin IDC connector, the same kind of cable used for the Raspberry Pi's GPIO pins (which makes cables easy to find and cheap). The 26-pins carry +5V and GND, the 8-bit data bus, 4 active-low device select lines, the lowest order 5 bits of the address bus, the RD and WR lines, two interrupt inputs (high priority and low priority) as well as the reset line, system clock and WAIT line.

I decided to do the expansion port "properly", which added more complexity than I expected. Most "out only" lines, like the address bus, device select, etc. are buffered by a 74541 chip (it's "most" and not "all" of these simply because I didn't have room on the board for a second buffer chip). The data bus is fed through a 74245 transceiver chip, whose enable and direction pins are held in the appropriate state for one clock cycle longer than the RD and WR lines, using flip-flops (the design is largely borrowed from this Z80 IDE interface at retroleum.co.uk). This arrangement means that there are no direct connections between the Z80 and any devices attached to the expansion port, so that a poorly designed peripheral cannot damage the Z80. As an extra precaution there are 5.1V zener diodes on some of the lines, to protect against accidental high voltages, but there are no current limiting resistors (again, no board room!) so the protection is not complete. This half-assed overvoltage protection was inspired by the whole-assed protection on the Ruggeduino (which I've never used, but like the idea of). or put it needs to be quickly wrapped in some paper or light cardboard before being put into a bag full of small things

Glue logic

There's quite a bit of "glue logic" on the LM-512, all of it from the venerable 7400 series. In particular, I have used 74HC chips made by Texas Instruments, with no apparent problems. A fun part of the process of designing a computer like this one is in trying to minimise the number of glue chips required (especially if you ever hope to have a professional PCB made - in hobbyist quantities, the board space for one logic chip costs a lot more than the chip itself). I actually did a pretty poor job this time around.

The 10MHz system clock is made three gates of a 7404 hex inverter, using the "series resonant oscillator circuit" shown here. The 10MHz output is fed into a 744040 ripple counter, which produces out clock signals at 10MHz, 5MHz, 2.5MHz, 1.25MHz and so on down through the powers of two. A jumper is used to select one of the 4 fastest clocks to feed to the CPU. I built the clock this way because I did not know whether or not my system would run reliably at 10MHz or whether all the nasty little gremlins associated with circuit layout and messy hand-wiring would rear their ugly head. I planned to just run things at the fastest jumper setting which worked reliably. As it turns out, everything works just fine at 10MHz, so the 744040 could be eliminated, reducing chip count by one.

The reset circuit is made with two of the remaining gates on the 7404 hex inverter. It is of the design shown in figure 4.9 on page 98 of Steve Ciarcia's "Build Your Own Z80 Computer", available freely (legitimately!) as a 13MB PDF file.

The low order byte of the address bus (which is used for the IN and OUT instructions) is connected to a 74138 3-to-8 line decoder, which provides 8 active-low device select signals. The first four of these are used for the memory bank selector, the IDE interface, the DUART and the RTC. The remaining four are exposed to the expansion port.

Bank switching is controlled by a 74175 quad flip-flop (the complementary outputs are not used, so this is probably not the ideal choice for this job). The four outputs are fed into the inputs of the four AND gates of a 7408, whose outputs are fed to the four highest order address lines of the RAM chip. The other input of the four chips are all fed from the highest order bit of the Z80's address bus. When accessing a memory address between 0x0000 and 0x7FFF, this bit is 0, so all of the 7408's AND gates output 0 and the first 32KB page of the RAM is selected. When an address between 0x8000 and 0xFFFF is accessed, the highest address bit is 1 and so the 7408's outputs mirror the 74145's outputs and the corresponding page of RAM is selected. I think this solution could perhaps be replaced by a 74173 and some pull-down resistors, cutting the chip count by one.

Some logic is required is to select either the RAM or ROM chip during memory access. The 4 highest order bits of the address bus are fed to the four OR gates of a 7432 chip, to produce a single output which is 1 if any of the individual lines are 1, i.e. if an address above the first 8KB is selected. This output is fed into the final gate of the 7404 used for the clock and reset switch, and the inverted output selects the RAM chip. If an address within the first 8KB is selected, the non-inverted signal selects the RAM chip. The four device select outputs on the expansion port are also all ORed together using a dedicated 7432, to operate the 74245 transceiver correctly. A single 744072 chip, which contains two 4-input OR gates, could replace both of these chips, but they're not super common.

Construction

Board and wiring

The mainboard is a 160mm x 100mm Eurocard prototyping board made by Velleman. It's of the "pad per hole" variety, with the standard 2.54mm pitch between pins. Everything is wired point to point using 30 AWG wire coated in kynar.

A more traditional method for wiring up a computer with this kind of construction is wire-wrap. This uses the same kind of wire as I used, but rather than soldering them to pins, stripped wire is wrapped very tightly around the pins using a special tool. Wire-wrapping is old-school. The tools aren't too hard to find today, at least the manual variety, but DIP IC sockets designed for wire-wrapping are rare and expensive curiosities. You can tell this by the fact that every webpage about a wire-wrapped homebrew computer has a few sentences in there where the author begs for people to donate surplus sockets to them. A nice alternative is to use standard pin headers, placed by the side of regular through hole IC sockets and soldered to them on the underside. You can then use the header pins for wire wrapping on the component side. This is cheap and easy and if I'd heard of this before I started soldering the LM-512, I may have gone down that route and saved some gray hairs.

Enclosure

I had to move the LM-512 from the USA to New Zealand about half-way through the project, and I wanted to make sure that it would travel safely. An enclosure was thus called for, and I wanted one that was as small as possible. I eventually decided to have some acrylic sheet cut to just the right size, using Ponoko, a web-based service for custom 3D printing and laser cutting. Ponoko have facilities all over the world, but by a stroke of great luck their US location was in the Bay Area, just a short bus ride from my house, so I could pick things up the day they were made without paying for postage. I chose a semi-transparent black acrylic, so that I'd always be able to see the wiring as a reminder of what I went through building this thing. I just had one sheet cut at first, to make sure I'd measured everything correctly. I had, so I got a second sheet cut up, and this time I had the computer's name etched into one corner in a retro-futuristic font. I'm pretty happy with how that turned out.

The circuit board is held firmly between the two acrylic sheets using some screws, nuts and hexagonal spacers. The result is quite rigid, and only about 15mm thick. The sides are unprotected, so if I'm going to put it into a bag full of random things I'll usually just wrap it up in some paper or light cardboard or bubble-wrap.