LM80C Color Computer

A Z80 homebrew computer with 32KB SRAM & 32KB ROM, TMS9918A VDP (video) and AY-3-8910 PSG (audio)

Similar projects worth following
This is a full working 8-bits computer based on the Z80 processor with BASIC interpreter, TMS9918A for video and AY-3-8910 for sound.

I always desired to build my own 8-bits computers. Back in the '80s I was a Commodore 16 user, and a lot of friends of mine had C64 and MSX computers. They had sprites and sound, I only had 121 colors. Now I want to build my own system, to bridge that gap. 

I looked around, started studying projects of other homebrew enthusiasts and eventually made my choices:

  • CPU: I chose a Zilog Z80 because it has a lot of registers, it has I/O features, it can set the stack anywhere into the RAM, it can handle interrupts in different ways.
  • RAM: 32KB of SRAM. I think that, at the moment, this amount of memory is enough to do some good programming.
  • ROM: 32 KB of EEPROM. I chose an EEPROM chip because I can easily burm my firmware with an Arduino board connected to my computer. Also, 32KB are enough to store a BASIC interpreter and some other stuff.
  • VIDEO: TMS9918A. I wanted a video chip that was able to render a colorful image, with a good image size, easy to interface and that had support for hardware sprites. 
  • AUDIOAY-3-8910 (or its variant from Yamaha, the YM219F). It features 3 voices with environment and white noise. It also has 2 input/output ports, that were used to read the keyboard or the joystick ports.
  • I/O
    • parallel I/O: a Z80 PIO that, at the moment, only drives some LEDs
    • serial I/O: a Z80 SIO/0, used to interface with a computer through a serial connection (FT232 serial/USB converter)
    • timer/counter: a Z80 CTC used to generate the serial clock and to increment a software timer counter

Why did I choose the Z80? Initially, I chose the Motorola 6809, then the 6502 but both of them had PROs and CONs. The 6502 has very few registers and it doens't support I/O instructions so every peripheral chip must be mapped into RAM, leading to complex address decoding systems. The 6809 is similar to the 6502 but it has some more registers, it has I/O instructions and a lot of addressing modes but it wasn't very used at its time so there aren't a lot of resources available. So I decided to use the Z80, because it's still manufactured by Zilog, it has a lot of peripheral chips specifically made for it (so that it's easy to interface each other), it can use I/O ports, it has a lot of registers, and so on.

The RAM and ROM are at the moment of 32 KB each: I think that this is a good amount of space, both for user programs and for firmware too. Maybe in the future I could implement a system to deactivate the system ROM and add another 32KB SRAM chip, to let the user to load very big programs (the same thing was done by engineers for the C64, where the ROM could be disabled, showing up the underlying RAM).

Video section. I only had few options. I discarded the MOS VIC-II from Commodore because it's very expensive online and it was developed to work with a 65xx processor. I also discarder the 6845 because it was just a video signal generator. I also discarded solutions based on moderm microcontrollers: I could have used an Atmel MCU and program it to geneate a VGA signal but I wanted to use only solutions that were available at that time. So I chose the TMS9918A, that is still available on Ebay at a resonable price, it's easy to interface since it only needs few lines between the CPU and itself and since it can generate an image of 256x192 pixels with 15 colors and 32 sprites.

For the audio section the choise was easy, the AY-3-8910: widely available, easy to interface, good to use as an I/O periphery too, and cheap. There is also the YM2149F: this is (almost) the same chip manufactured by Yamaha under license by GI. So that I could choose the one I was able to find first.

The I/O is the reachest part of the computer. Since Zilog made a lot of peripheral chips and since all of these chips are still in production, you can find parallel & serial interfaces very easily. I decided to add a Z80 PIO (Parallel Input/Output)  to drive 8 LEDs via...

Read more »

Schematics and Arduino code to realize the EEPROM programmer used to burn the firmware for the LM80C computer.

Zip Archive - 1.15 MB - 05/01/2019 at 09:23


This archive contains both the firmware source and the schematics of the computer at the current stage: PIO, SIO, CTC, TMS9918A fully working

Zip Archive - 203.68 kB - 04/29/2019 at 21:08


  • 1 × Zilog Z80B CPU Possible product code: Zilog Z86C0006PEG (Z80 CPU in CMOS, 6MHz)
  • 1 × Zilog Z80 PIO Possible product code: Zilog Z84C2006PEG (Z80 PIO in CMOS, 6MHz)
  • 1 × Zilog Z80 SIO/0 Possible product code: Zilog Z80C4006PEC (Z80 SIO/0 in CMOS, 6MHz)
  • 1 × Zilog Z80 CTC Possible product code: Zilog Z84C3006PEG (Z80 CTC in CMOS, 6MHz)
  • 1 × GI AY-3-8910 or Yamaha YM2149F

View all 9 components

  • Sound made easy

    Leonardo Miliani3 days ago 0 comments

    After a while (summer holidays) I got my hands on my computer again. This time I added a couple of commands to make the sound management easier. I coded a while and now VOLUME and SOUND are ready to be used.

    VOLUME has a very simple sintax and just gets 2 params: the first one is the audio channel, the second one is the volume level. The channel can be an integer in the range 0~4: 1, 2, & 3 point to the corresponding channel (1->A, 2->B, 3->C) while 0 stands for “every channel”,  meaning that it’s like a global setting. The volume level is an integer in the range 0~15, where 0 is for OFF and 15 is for MAX volume level.

    SOUND required more work because I had to manage 3 different user params: the channel, the frequency and the duration. The first one is clearly understandable: it chooses the channel used to reproduce the sound. The second one sets the frequency of the tone to be reproduced, in the range 0~4,095. We have to pay attention that this value doesn't represent the real frequency of the tone but it is just an index that has some sort of correlation between its value and the frequency. It gives to users the feeling that higher numbers mean higher tones, the same way of working of some old computers, i.e. my C16 did the same way. The last param sets the duration in hundreds of seconds, and this was possible thanks to the timer I implemented in the firmware using a counter of the Z80 CTC.

    The new statements are available in the R2.1 release.

  • Let's make some sounds

    Leonardo Miliani07/14/2019 at 17:53 0 comments

    After a short period of absence I got my hands back on LM80C. I wired up the Yamaha YM2149F (but this is valid for the GI AY-3-8910, too: they are almost identical. I got the first one with a kit of ICs where the seller on eBay said he was selling an AY while sent me the YM) programmable sound generator (PSG) that I bought some time ago, and then I connected the audio output to the RCA mono input of my TV set, getting the first whimpers of my home-brew computer.

    In the video you can hear that there is a complex audio: in fact,in this first test I activated all the 3 analog channels: channel A is reproducing a fixed tone; channel B is reproducing a fixed note mixed with some white noise; channel C is reproducing a tone using an envelope mode (saw tooth). To get the sound, I simply connected the 3 output channels to the mono audio input of the RCA port of my TV set.

    To obtain this result I had to add another address decoder to my circuit, since the original AY-3-8910 from General Instrument (and the YM2149F clone from Yamaha, of course) was made to work in conjunction with the old CP1600 microprocessor from GI, that used 3 lines to set the operating modes of the PSG. Since the PSG settings are redundant (there are several settings that get the same effect), it was the same manufacturer that suggested to pull up one of the setting lines and use the other two. Thanks to this, we can use the just 3 signals: one for the chip selection, one to choose between register and data modes, and the last one to choose to read from/write to the PSG.

    The PSG is a register-based sound processor: this means that the CPU has just to send the data to the PSG, that stores these infos inside its registers and then use them to generate the sounds, so that the CPU doesn't have to take part in the process anymore once the PSG starts its work. But I discovered an issue concerning the way we choose the register: in the datasheet registers go from 0 to 7 and then from 10 to 17 but in reality they go from 0 to 15. I didn't investigate why but to choose the register 10 I had to send the value "8", and so on. In the video you can see the list of the program I used to get the sounds you hear in the video: as you can see, there a lot of OUTs: outputting a value to port 64 selects the write mode to the register pointed by the passed value, followed by an output to port 65 to send the value to be written into such register.

    Once I was able to get some sounds from the PSG, I added a couple of statements to the NASCOM BASIC to write to/read from the registers of the PSG. Firmware release R2.0 now contains the SREG command and the SSTAT function. The first one is used to write a byte into a specific register while SSTAT is used to read from any of the registers.

    The new firmware with the updated schematic is available at my Github repository.

  • VREG, VSTAT, and LOCATE statements

    Leonardo Miliani06/20/2019 at 16:29 0 comments

    I've worked hard to add some new useful statements to the BASIC interpreter.

    These commands are very useful when working in graphics because:

    • VREG: this command writes a value into a specific VDP register. Usage: VREG reg, val, where "reg" is the register where to write to (in the range 0..7), while "val" is the value to write (0..255).
    • VSTAT: this function returns the current value of the VDP register status, so the user can look for sprites collisions and also for the number of the 5th sprite on a row (the VDP cannot visualize more then 4 sprites at the same time on the same row). Usage: VSTAT(x), where "x" is any integer number. The returned value is in the range 0..255.
    • LOCATE: this command positions the cursor at a specific video cell. Usage: LOCATE X,Y, where "x" is the horizontal coordinate and "y" is the vertical coordinate. "x" must be in the range 0..(width-1), where width is the screen width of the current video mode. I.e.: in mode 0 (text), width is 40, so "x" can only have a value between 0 and 39. Similarly, "y" must be comprised between 0 and the (height-1) where the screen can goes from 0 to 23 (the height is 24 chars both in text and in graphics 1 modes).

    I've also completed the 8x8 pixels font by adding the missing chars in the first release of the charset and expanded the max. default string size to 100.

    The files of the new firmware, marked R1.9, have been uploaded on my GitHub repo.


    Leonardo Miliani06/15/2019 at 14:32 0 comments

    In the last days I've worked to expand the commands and functions of NASCOM BASIC by adding VPOKE & VPEEK.

    VPOKE is a command that, as you can understand by the name, lets you write a value into a specific VRAM cell. VPEEK is a function that, obviously, reads a byte from a VRAM cell. These two are necessary because the LM80C firmware manages the cursor through an interrupt raised by a timer of the Z80 CTC: due to this way of working, at regular intervals the main code flow is interrupted and the cursor management code is executed. Since the VDP has a data buffer for reading and writing from/to the VRAM and since it also has an auto increment counter that, after the first read/write, automatically increments the address pointer, if you use OUTs and INs to write/read to the VRAM, you will experience glitches and graphical artifacts because the read/write operation you are making could be interrupted in the middle of its course by the cursor manager: this will lead to collisions between the two codes that make a VRAM access through the VDP and unpredictable side effects, like random chars that could appear on the screen or wrong data written into memory. 

    VPOKE & VPEEK avoid this, since they disable interrupts before accessing the VRAM and re-enable them after they did their work.

    The new firmware is marked R1.8 and is available for download from my Github repo.

View all 4 project logs

  • 1
    Z80 chips

    Choose CMOS versions of the Z80 family chips: they usually have a "C" in the middle of the produc's code, i.e. Z84C006PEG for the Z80 CPU.

    Also, choose 4MHz- or, better, 6MHz-compliant chips. You can see the different models by looking at the last number of the product's code, i.e. Z84C006PEG is 6MHz tolerant.

  • 2
    74HCTxx family

    Choose chips of the 74HCTxx family. The "HCT" indicates a type of circuitry that is 74LS-compatible (the "LS" series was the original 74 family). The chips of the HCT series are compatible with the LS ones, because they have the same logic levels (despite the HC series, that don't). Since the LS series is difficult to find, I suggest to use chips of the HCT series.

  • 3
    TMS9918 Vs. TMS9918A

    Pay attention when you buy the video chip. The "A" letter at the far right of the name of the chip indicates a newer variant of the chip that Texas Instruments released to add support for bitmap graphics. The TMS9918 only supports graphics 1 (tiles) mode, text mode and muticolor mode, while TMS9918A adds the bitmap support, in graphics 2. 

View all 4 instructions

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates