close-circle
Close
0%
0%

8-bit color computer from TTL

"Just because":
The 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

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?

Can fewer than 40 old-skool TTL chips implement a multi-MHz 64 color computer?

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.

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 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 instructions per clock (sometimes 2...)
• 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

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.

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...

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

  • Moving up the ladder of abstraction

    Marcel van Kervinck11/12/2017 at 17:54 0 comments

    This photo captures the inner application interpreter's first sign of life. Very early this morning, or late last night, the virtual CPU ran its first program. It calculated the largest 16-bit Fibonacci number to be 1011010100100000 and plotted that in the middle of the screen. The video loop was still playing the balls and bricks game, unaware of what was happening, but sometimes the ball bounces off the 16-bit result.

    The interpreter is the virtual CPU that makes it possible for mere mortals to write applications without worrying about the arcane video timing requirements.

    For this test I hand-compiled the BASIC program into interpreter code and preloaded it into RAM. Shown here is the interpreter running during every 4th visible VGA scan line. It dispatches instructions and keeps track of their duration until it runs out of time for the next sync pulse. It can't stream pixels at the same time so these lines render black. I don't mind the bonus retro look at all.

    With this the system undergoes quite a metamorphosis:

    • The TTL computer: 8-bits, planar RAM address space, RISC, Harvard architecture, ROM programmable
    • The inner virtual CPU: 16-bits, (mostly) linear address space, CISC, Von Neumann architecture, RAM programmable

    A typical interpreter instruction takes between 14 and 28 clock cycles. The slowest is 'ADDW' or 16-bits addition. This timing includes advancing the (virtual) program counter and checking the elapsed time before the video logic must take back control. It also needs a couple of branches and operations to figure out the carry between low byte an high byte. That is the price you pay for not having a status register. But is that slow? Lets compare this with 16-bit addition on the MOS 6502, which looks like this:

    CLC     ;2 cycles
    LDA $10 ;3 cycles
    ADC $20 ;3 cycles
    STA $30 ;3 cycles
    LDA $11 ;3 cycles
    ADC $21 ;3 cycles
    STA $31 ;3 cycles
            ;total 20 cycles or 20 µsec

    The TTL computer executes its equivalent ADDW instruction in 28/6.25MHz = 4.5 µsec.

    We should be able to get out roughly 60k virtual instructions per second while the screen is active, or 300k per second with the screen off. So I believe the interpreter's raw speed is quite on par with the microprocessors of the day. The system itself of course loses effective speed because its hardware multiplexes the function of multiple components: CPU, video chip and audio chip are all handled by the same parts. And to make things worse, the computer "wastes" most of its time redrawing every pixel row 3 times and maintaining a modern 60Hz frame rate. PAL or NTSC signals of the day were 4 times less demanding than even the easiest VGA mode.

    Next step at the software front is finding a good notation for the source language.

    On the hardware side, there is some progress on a nice enclosure. I hope to have a preview soon.

  • Bouncing ball and pad

    Marcel van Kervinck10/30/2017 at 01:44 0 comments

  • Interaction

    Marcel van Kervinck10/16/2017 at 20:50 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.

View all 34 project logs

Enjoy this project?

Share

Discussions

monsonite wrote 11/08/2017 at 18:28 point

Hi Marcel,

Great work!  Faster, simpler and cheaper than the original IBM PC - love it!

Is there a schematic available for your design?  I'm interested in the final approach you took for your ALU.

Interested to hear some more about the virtual machine you hinted upon.

I have shared your work with anycpu.org

Well Done

Ken

  Are you sure? yes | no

Marcel van Kervinck wrote 11/08/2017 at 19:10 point

The full schematic might appear in some later stage as it is quite a mess in need of serious cleanup and I'm working on software the upcoming weeks. But the ALU is described adequately elsewhere: the "Logic" stage comes from here  http://www.6502.org/users/dieter/a1/a1_4.htm  (bottom of the page) and the ALU uses two of those with two 4-bit adders, as described here: http://www.6502.org/users/dieter/a1/a1_6.htm  except that my design does without the 3rd stage that only provides right-shifting. So in total it is 8x'153 and 2x'283 with 5 control signals going in. Dieter's pages suggest that you need 8 signals, but most of them have the same value for the common operations. The carry-out goes back into the condition decoder where it acts as an allzero-indicator during jumps. There is no flags register as it costs too many chips for too little added value. I do right-shifting in software using the "single-instruction subroutine" technique described in the log on pipelining. This computer doesn't need shifting that much because the graphics layout is 1 byte = 1 pixel.

  Are you sure? yes | no

monsonite wrote 11/11/2017 at 18:33 point

Hi Marcel,

I'm suitably impressed with the elegance of your design that I have taken time to transcribe it to EagleCAD, so that I can get a better understanding of the control and data paths.

After pondering your Control Unit schematic for an hour yesterday lunchtime, and how it decodes the ALU instructions and generates the various addressing modes and jumps, I was happy that I could figure from the bus and dataflow diagram how most of the rest of it worked. 

Now that the bulk of the design is captured, I'm figuring out a way of using an ATmega1284 as an easy to update ROMulator device, running code from faster RAM and a way of extending by adding more VRAM.

When you consider that the first Sinclair ZX80 machine took 18 TTL chips, plus Z80, ROM and RAM to create a monochrome TV picture of 32 x 24 8x8 characters, for just another 18 chips they could have replaced the Z80 and had something that produced colour graphics and ran at 4 or 5 times the speed.

regards

Ken

  Are you sure? yes | no

Yann Guidon / YGDES wrote 10/28/2017 at 12:41 point

Amazinger :-D

  Are you sure? yes | no

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