close-circle
Close
0%
0%

8-bit color computer from TTL

"Just because":
The single-board microcomputer that runs without a microprocessor

Similar projects worth following
close
History followed one path
There were many other ways
It will always be like that

Can fewer than 40 old-skool TTL chips implement a multi-MHz 64 color computer? This is what we have now:
• 8-bits system built out of 1970s TTL chips (74LS)
• 34 ICs for the CPU
• No microcontroller and no complex chips (such as the 74181 ALU)
• Only simple SSI/MSI ICs, such as AND/OR, 4-bit adders, multiplexers, registers and so on
• 6.3 MHz. Might be pushed to 8 MHz
• 32kB 70ns RAM
• Harvard architecture with EPROM for program/data
• Operates on 2.5W, or below 0.5W for the 74HCT version
• RISC with pipelining: 1 (sometimes 2!) instructions per clock
• Instruction decoding with diodes
• Nice instruction set: ADD/SUB/AND/OR/XOR, conditional jumps, many useful addressing modes
• 60Hz 64 color VGA and 4 sound channels bit-banged from software
• Designed and built on a solderless breadboard in 6 weeks

In 1975 Wozniak famously designed the Breakout arcade game out of 44 simple chips, without using a microprocessor, simply because those weren't available to him at the time. When one year later the MOS 6502 and Zilog Z80 were launched, his Apple 1 started the microcomputer revolution. The debate still rages about which processor was the better one. But more interesting is to investigate if these devices were really necessary for the personal computer revolution at all: what would have happened if they had never appeared?

This project started as an exploration of what you can build from 30-40 simple logic chips. It has turned into a general purpose 8-bit microcomputer without any microprocessor driving it. I initially designed this on a breadboard, but this summer I have converted it all to a small PCB. It has VGA-compatible 60 Hz video in 64 colors and can display full-screen images, scroll them and play sound. Soon it will be running games like Pac Man and Space Invaders. But just for fun, I'll keep everything compatible with the breadboard design.

The build has become a contradiction of itself. Every hardware function is essentially software-defined: video, audio and I/O are all handled by software. Video at the pixel level. Audio at the sample level, in 4 channels. Even the applications themselves will be running in an interpreter (aka a virtual processor). Yet there is no microprocessor that runs any of that. And not only does it work, the board is smaller and faster than the microcomputers of the day, including the first IBM PC. Having no microprocessor might have been better than having any of the time :-)

Check the videos for the current capabilities. The HaD blog section has the full story in all detail, so don't miss that if you're interested.

[ Note: I don't maintain this "Project Details" section very often. See the blog section on this site for the real updates. ]

Some concepts to ponder about before starting

  1. How many bits? 4, 8, 16, 32, ...
  2. Software-generated video or hardwired?
  3. Harvard or Von Neumann architecture?
  4. Single cycle or multi cycle? Pipelining?
  5. ALU chips or not?
  6. Sliced ALU or full width?

A rule of thumb is that a minimalistic four bit system can be done in 10 chips, an eight bitter needs no more than 20 chips and going to 16 bits roughly doubles that again. Not all units double in chip count, but by extending the buses you will also have need for more addressing modes for it all to make sense. For example, a four bitter might work fine with a 256 word memory and absolute addressing, but with larger memories you'll need ways to construct addresses dynamically. Also, add more chips if extra functionality is required, such as high speed, a stack pointer, interrupts, a custom ALU or video.

Simplest possible concept

One concept, probably the simplest, is to replace the TrinketPro from the earlier breadboard VGA from TTL and SRAM with a minimalistic 4-bit TTL CPU. We will then get a working system with around 25 chips, or about 30 if we make a custom ALU. It will indeed be good enough for Pac Man and Space Invaders, but that will then also be the limit: no chance at all to have any fast scrolling, fast color changes, smooth moving objects, large objects, etcetera. Upgrading the CPU part to 8-bits won't bring much, because there will still be a communication bottleneck between the two parts. For any more exciting video we will need more complexity in the video part. If we do both, we certainly end up with more than 40 chips. It is absolutely interesting to try out this 25 chip concept (I would do it with a 74'181 ALU chip), because Space Invaders from such a small system can be very appealing. But first I want to explore another path.

A more daring approach

Lets look at it from another angle. If we look at my earlier video generation circuit it already contains many elements for a CPU: there are counters, ROM, RAM and buses. So the idea I'm going to pursue first is to extend the video part...

Read more »

theloop.asm

Disassembly of ROM file

asm - 560.83 kB - 10/19/2017 at 20:10

download-circle
Download

theloop.2.rom

Object ROM file for 27C1024

rom - 42.65 kB - 10/19/2017 at 20:10

download-circle
Download

Scroller64.rgb

Scroll text

x-rgb - 8.25 kB - 10/19/2017 at 20:07

download-circle
Download

rrggbb-Selfie-160x120.rgb

Photo of breadboard version

x-rgb - 56.25 kB - 10/19/2017 at 20:07

download-circle
Download

theloop.py

Operating system showing core capabilities

x-python-script - 24.12 kB - 10/19/2017 at 20:06

download-circle
Download

View all 7 files

  • 6 × 74LS161 4-bit adder
  • 3 × 74LS273 8-bit register
  • 2 × 74LS244 Octal non-inverting buffers
  • 2 × 74LS138 3-to-8 decoder
  • 1 × 74LS139 Dual 2-to-4 decoders

View all 14 components

  • Interaction

    Marcel van Kervinck6 days ago 0 comments

    With the instruction set, video, sound and blinkenlights already checked, the last remaining unknown is the input. I will be using a simple NES-type of controller for the games. Unlike the simpler Atari joysticks these controllers have a serial interface. In this video you see the computer read the input once during every scanline in vertical blank, and render the received value in binary somewhere in the middle of the screen.

    The controller needs sync pulses from the computer. For simplicity I feed it with the VGA vSync and hSync signals. The software loop already generates these anyway. Then on the receiving end I decode the controller's single data line with a 74HC595 shift register, which in turn connects to the computer's data bus. The 74HC595 is also designed to work with two sync signals, but I didn't bother and hooked up both pins to hSync. With that, 60 times per second the controller state flows through the shift register at a rate of one bit per scanline. Using the in bus mode the software can read its contents. It just needs to catch it at exactly the right time otherwise the buttons get confused. But the timing is measured in scanlines, and this computer races pixels, so no sweat there. 

    The code running in the video above is this:

    address
    |    encoding
    |    |     instruction
    |    |     |    operands
    |    |     |    |
    V    V     V    V
    016f 0043  ld   $43          # Calculate which Y position we want to draw
    0170 b508  suba [$08],y
    0171 1014  ld   $14,x        # Start at fixed X position (20)
    0172 dc00  st   $00,[y,x++]  # Draw black pixel and advance X
    0173 c313  st   in,[$13]     # Read controller input
    0174 0001  ld   $01          # Set mask to bit 1
    0175 c214  st   [$14]
    0176 2113  anda [$13]        # Is this bit On or Off?
    0177 f07a  beq  $7a          # (See previous blog post on this idiom)
    0178 fc7b  bra  $7b
    0179 000f  ld   $0f          # On: yellow
    017a 0004  ld   $04          # Off: dark green
    017b de00  st   [y,x++]      # Draw pixel and advance X
    017c 0114  ld   [$14]
    017d f475  bge  $75          # Shift mask and loop over all bits
    017e 8200  adda ac
    017f dc00  st   $00,[y,x++]  # Draw a final black pixel

    With this last hurdle taken, the TTL microcomputer is complete: we have video in 64 colors, 4 channels of sound and we have input to make it interactive.

    Everything is now ready to start writing games.

  • Pipelining and the single-instruction subroutine

    Marcel van Kervinck10/15/2017 at 20:13 2 comments

    Pipelining basics

    Our computer uses simple pipelining: while the current instruction is stable in the IR and D registers and executing, the next instruction is already being fetched from program memory.

    This makes high speeds possible, but comes with an artefact: when a branch is executing, the instruction immediately behind the branch has already been fetched and will be executed before the branch taking effect and arriving on the new program location. Or worded more properly: the effect of branches is always delayed by 1 clock cycle.

    When you don't want to think about these spurious instructions you just place a nop (no-operation) behind every branch instruction. Well, our instruction set doesn't have an explicit nop, but ld ac will do. My disassembler even shows that instruction as a nop. The Fibonacci program uses this all over:

    address
    |    encoding
    |    |     instruction
    |    |     |    operands
    |    |     |    |
    V    V     V    V
    0000 0000  ld   $00
    0001 c200  st   [$00]
    0002 0001  ld   $01
    0003 fc0a  bra  $0a
    0004 0200  nop
    0005 0100  ld   [$00]
    0006 c202  st   [$02]
    0007 0101  ld   [$01]
    0008 c200  st   [$00]
    0009 8102  adda [$02]
    000a c201  st   [$01]
    000b 1a00  ld   ac,out
    000c f405  bge  $05
    000d 0200  nop
    000e fc00  bra  $00
    000f 0200  nop
    

    If you don't want to waste those cycles, usually you can let the extra slot do something useful instead. There are two common folding methods. The first is jumping to a position one step ahead of where you want to go, and copy the missed instruction after the branch instruction. In the example above, look at the branch on address $000e, it can be rewritten as follows:

    000e fc01  bra  $01   # was: bra $00
    000f 0000  ld   $00   # instruction on address 0
    

    The second method is to exchange the branch with the previous instruction. Look for example at the branch on address $0003. The snippet can be rewritten as follows:

    0002 0001  bra  $0a   # was: ld   $01
    0003 fc0a  ld   $01   # was: bra  $0a
    0004 0200  nop        # will not be executed and can be removed

    One of these folding methods is often possible, but not always. Needless to say, applying this throughout can lead to code that is both very fast and very difficult to follow. Here is a delay loop that runs for 13 cycles:

    address
    |    encoding
    |    |     instruction
    |    |     |    operands
    |    |     |    |
    V    V     V    V
    0125 0005  ld   $05 # load 5 into AC
    0126 ec26  bne  $26 # jump to $0126 if AC not equal to 0
    0127 a001  suba $01 # decrement AC by 1
    

    This looks like nonsense: the branch is jumping to its own address and the countdown is in the instruction behind. But due to the pipelining this is just how it works. 

    Advanced pipelining: lookup tables

    The mind really boggles when the extra instruction is a branch itself. But there is a useful application for that: the single-instruction subroutine, a technique to implement efficient inline lookup tables.

    Here we jump to an address that depends on a register value. Then immediately following we put another branch instruction to where we want to continue. On the target location of the first branch we now have room for "subroutines" that can execute exactly one instruction (typically loading a value). These subroutines don't need to be followed by a return sequence, because the caller conveniently just provided that... With this trick we can make compact and fast lookup tables. The LED sequencer from yesterday's video uses this to implement a state machine:

    address
    |    encoding
    |    |     instruction
    |    |     |    operands
    |    |     |    |
    V    V     V    V
    0105 0009  ld   $09    # Start of lookup table (we stay in the same code page $0100)
    0106 8111  adda [$11]  # Add current state to it (a value from 0-15)
    0107 fe00  bra  ac     # "Jump subroutine"
    0108 fc19  bra  $19    # "Return" !!!
    0109 0010  ld   $10    # table[0] Exactly one of these is executed
    010a 002f  ld   $2f    # table[1]
    010b 0037  ld   $37    # table[2]
    010c 0047  ld   $47    # table[3]
    010d 0053  ld   $53    # table[4]
    010e 0063  ld   $63    # table[5]
    010f 0071  ld   $71    # table[6]
    0110 0081  ld   $81    # table[7]
    0111 0090  ld   $90    # table[8]
    0112 00a0  ld   $a0    # table[9]
    0113 00b1  ld   $b1    # table[10]
    0114 00c2 ld $c2 # table[11]
    ...
    Read more »

  • Anatomy of an 8-bits TTL microcomputer

    Marcel van Kervinck10/15/2017 at 11:45 0 comments

    36 simple TTL chips for CPU, output and clock. A static RAM, an EPROM and a hand full of passives to make the system complete. I kept the unit coloring as in the block diagram, which in turn follows the Velleman wire colors on the breadboard version.

  • G-major and scanner lights

    Marcel van Kervinck10/15/2017 at 00:32 0 comments

    After software-generated video we now also have a software generated sound playing. In four channels! And the blinkenlights are blinking, slowly getting there...

    Sound and LEDs are driven by the same 8-bit extended output register which receives updates during the horizontal video sync pulse. I have split its output into 4 bits for sound and 4 bits for the LEDs. Every VGA scan line one of four independent software oscillators gets updated by the software. Every four scan lines their sum goes through the R2R DAC into the 3.5mm audio jack to the PC speakers that I picked up from a thrift store. Here I initialised the channels to play a G-major chord. I don't have a music sequencer yet, so it is just one chord. But as you can see there is a LED sequencer in place already, the music sequencer will be similar.

    The extended output XOUT is just 1 extra 74273 chip that is not directly visible to the processor. It lurks on the horizontal sync for its clock and it gets its data from the accumulator. So typically it takes 3 instructions to update it. Here is how I switch  off all LEDs immediately after power-on:

    address
    |    encoding
    |    |     instruction
    |    |     |    operands
    |    |     |    |
    V    V     V    V
    0000 0000  ld   $00     # Clear AC
    0001 1880  ld   $80,out # Negative flank (bit 6)
    0002 18c0  ld   $c0,out # Positive flank (bit 6). This causes XOUT to be updated.

    It is a simple way to get 8 additional output pins. Here it is in the block diagram:

    Hardware-wise this is the second PCB build, the difference with the first board being the logic family soldered onto it: The breadboard and first PCB were using the classic 74LS series of TTL, while this board is using 74HCT chips for a change. They are completely compatible, only the video resistors needed somewhat higher values because the 74HCT outputs are always near the rails. As a result of this change, this specific board uses a lot less power: Just 80 mA for the FETs vs. 530 mA for the bipolar versions (or <0.5 Watts vs. >2.5 Watts). It should run for days on that battery pack. The difference between 1983s technology vs. 1971s...

  • Retro font

    Marcel van Kervinck10/13/2017 at 18:50 4 comments

    The resolution is good for games but 8x8 characters will still be huge. It is amazing how much variety there is among the available 5x7 and 5x8 fonts. I just have to add another one.

    Tool chain: Excel --> Print as PDF --> Preview --> Save as PNG --> ImageMagick --> Scale and save as RGB --> Python. This was simpler 30 years ago.

  • Working PCB version

    Marcel van Kervinck10/04/2017 at 16:33 2 comments

    The breadboard TTL color computer has a PCB baby now. Her heart beats at 6.25 MHz. Here you see the little one sleeping and dreaming of her mom. Mother and baby are doing well. Father will be fine soon also.

  • Subconsciously some logo has snuck into the layout

    Marcel van Kervinck09/30/2017 at 21:50 2 comments

     Indeed, the plans to consolidate the breadboard into something more robust begin to materialise.

    (And yes, U29's pin 10 isn't soldered due to a little bug.)

  • Getting rid of the breadboard

    Marcel van Kervinck09/20/2017 at 22:39 0 comments

    For reliability it is better to solder everything onto a board. So I took the plunge and learnt how to use KiCad. It seems the circuit can be made on a roughly 9x6 inch board, using very relaxed spacing rules. Even if you ignore the time it takes to draw a proper schematic, making a PCB layout took a lot more time than doing the breadboard version. This surprised me a bit.

    Ok, wires are flexible and that speeds things up. Traces are not flexible, and that is why they are more reliable of course. But drawing each segment manually gets harder and harder as the circuit completes. And you don't now if you have to undo major steps until you are almost done. On the other hand, in a breadboard layout you lose a lot of time on debugging wiring mistakes. You win back all that time with the EDA circuit checks, but overall the process is slower.

    Perhaps my first PCB shouldn't have been a 144 component, 2 layer monster. But here it is prior to tape-out.

  • Audio output

    Marcel van Kervinck08/06/2017 at 16:16 0 comments

    I've been busy adding audio output while trying to keep the component count down. First I figured out a way to insert a second output register into the circuit. It's a 74'273 that takes its input data from the accumulator and the clock from the video hsync. Good enough for 31 kHz software-generated samples and with a bit of luck I can squeeze out 4 voices. I will split this extended output register into 4 bits for audio and 4 bits for blinkenlights.

    This is the circuit for the audio out part. The idea is that this is good enough to connect PC speakers to. The attenuation from logic level to line level is done by the filters. The calculated output signal should be ok:

    My tests run fine over the interesting audio frequency range. Here's a 555 hiding behind a pot and driving a 74HCT161 that in turn generates a 16-step sawtooth. The DAC is a 10kΩ R-2R ladder whose output gets filtered by the simple RC stage. Overall output impedance is quite high but more than suitable for the PC speakers.


    Close up of the unfiltered DAC output:

    All in all, it seems that I won't need an op-amp for this stage, which is what I was after.

  • Wire wrap sockets meet solderless breadboard

    Marcel van Kervinck07/02/2017 at 10:28 0 comments

    As the programming is done externally, the breadboard suffers from the continuous removal and insertion of EEPROM's, to the point where they barely stay in place anymore. Following a comment in the Hackaday feature, this looks like a promising solution:

    These are IC sockets intended for wire wrapping. I just use them for a better mechanical interface to the breadboard.

View all 32 project logs

Enjoy this project?

Share

Discussions

f4hdk wrote 05/26/2017 at 11:34 point

I like it! 

Every homebrew computer should have video output, even made with TTL!

Have you seen my project? It is quite similar (custom CPU architecture, video output) even if I used an FPGA  instead of TTL components.

https://hackaday.io/project/18206-a2z-computer

It's fully documented and open source. 

And welcome on the "homebrew CPU Webring" !

  Are you sure? yes | no

Marcel van Kervinck wrote 05/26/2017 at 13:52 point

Nice! FPGA's are a step up for me. Clearly the TTL and the RAM size are limiting my video resolution, but it is also cute in its own way.

  Are you sure? yes | no

Peabody1929 wrote 04/01/2017 at 18:46 point

Consider one or two AMD2901 or a single CY7C115/116/117 -> More computer, fewer chips.

  Are you sure? yes | no

Marcel van Kervinck wrote 04/02/2017 at 20:09 point

Thanks. I can find the first. I do have ALU chips available, I just don't feel like using them in this build. Do you have data sheets for the second series? I have trouble finding out what it is. Some memory? I plan on using 62256 for RAM and 2x AT28C256 for ROM, with a possibility to extend the memory using the AT27C1024.

  Are you sure? yes | no

Big Boy Pete wrote 03/31/2017 at 18:19 point

Good luck Marcel.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

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