User Tools

Site Tools


home

Table of Contents

The Blinking Computer

The Blinking Computer will be an easy-to-build educational computer. As the name suggests, the idea is that you can write code and see it execute at every level, that means discrete components and lots of LEDs. The design has evolved very many times since my Prof forbid me from canablising the old departmental Strowger switch telephone exchange in the late 1980's. Right now there are 32 words of RAM and the program memory is a tape loop, just like Colossus.

The objectives are:

  • Simple enough so that everyone can see how it works, that means lots of LEDs
  • Complex enough to demonstrate the main ideas of a computer
  • Small enough to be affordable by anyone with the time to build it
  • Quick enough to build and keep my (and those that follow me) family life
  • Fast enough to run some complex software
  • Slow enough so that everyone can see it running and understand it step by step
  • Flat enough so that the entire state can be seen in one go

This is the plan:

  • It's an 8 bit processor with only 32 bytes RAM but loads of program memory
  • There are no registers, only a single bank of data memory (the program memory is completely separate)
  • All instructions are of the form: cnd Mi = Mj OP Mk
    • i, j, k are 6 bit integers
    • cnd indicates that all instructions are conditionally executed
  • Most of the bottom 5 bits of the address space (M0 to M29) are DRAM
    • Probably this will based on Homebrew Dynamic RAM but with a single bipolar transistor as the gate. The LEDs are optional, the reference design has them to show the state of everything.
    • It may be based on Yann Guidon's single capacitor, two diode DRAM expecially if the read diode could be a LED
    • There will be a DRAM refresh circuit, every read will force a write.
  • M0 and M1 may be used as stack pointers. M30 contains the contents of the address in M0 (and M31 pairs with M1)
    • Reading/writing M0 or M1 works as any other memory location
    • Reading/writing M30 or M31 results in the contents of M0/M1 being placed on the address bus, so the memory accessed is in the DRAM range of M0 to M31
  • The top five bits of the address space are constants if read and control if written.
    • M32 to M47 are the constants 0 to 15 and M49 to M63 multiples of 16, both implemented by wiring the address to the data lines
    • M48 is toggle switch input
    • When written the address line is used to control:
      • Bit0 JUMPs to the value written
      • Bit1 outputs the value written
      • Bit2 sets the clock rate (potentially including HCF)
  • Whilst the RAM/ROM/CTRL space is addressed by 6 bits, the top half never needs to be decoded. If it used to read from ROM then the (shifted) address lines are used. If it is used to write control signals then only one address line is high at once so the address line can be used directly.
  • There is no program counter!
    • Ordinarily the next instruction is read from the tape and executed
    • If a JUMP occurs the tape spins until the desired label is read.
      • Not all instructions have a label so there can be more instructions than the data width
      • Duplicate labels are valid, this may be useful…
  • The instruction rate is intentionally slow. 1Hz is maybe typical, everything should be visible. There will be a single step button that is active after HALT so that everything can be as slow as needed.
  • There will be a twelve phase clock:
    • Phase 0: read the instruction from the tape and decode
    • Phase 1: read Mj to latch/register IN0
    • Phase 2: read Mk to latch/register IN1
    • Phase 3 to 10: run the ALU storing the result in latch/register OUT
    • Phase 11: write Mi
    • Phase 12: wait for tape to spin and do DRAM refresh. This is the rate limiting step and it is where the state can be seen.
  • There exists several status flags:
    • the carry bit
    • whether the last ALU result was zero
    • whether the last ALU result had the top bit set (negative)
  • The ALU has AND, OR, XOR, ASL, ADD, ADDC, SUB, SUBC
    • ASL is hardwired in parallel, the rest are via a 1-bit serial ALU
    • There is a carry flag, ADD/SUB ignore it, ADDC/SUBC use it
  • Conditional execution is one of: never, always, ==0, !=0, <0, ⇐0, >=0, >0
  • The construction cost and time are very important. Ideally the bill of materials should come in at under £100 so that schools can buy and build it.
    • It's not quite a minimal processor, it is more important that it's understandable with school level electronics and be able to run complex programs (slowly!)
    • I expect that laminated A4 will work (print A4, laminate, punch holes, spaghetti wire through holes)
  • Input is likely to be from a simple scanner. A4 pages are taped into a loop, a motor runs until the label is reached, photodiodes/photoresistors read the instruction.
  • Output is likely to be thermal print paper and nicrome wire (alt: DIY pen plotter, eight marker pens on solonoids/springs)
  • Breadboard to begin with, but ideally A4 laminated paper or acrylic so that the circuit can be seen
  • Framed, ideally running on batteries
  • Some permissive FOSS licence will be adopted (e.g. Apache2/MIT) and experimentation and evolution are encouraged

There is an emulation with some example code in Google sheets

All instructions really are if(cond) Mi = Mj OP Mk, however the assembly language can be more more flexible and compile in constants, writing to high bit addresses and the like. We can split the generic instruction into the left hand side, Mi = and the right hand side, Mj OP Mk

human readable machine readable comment
Ri = Ri = the general case for 0 <= i < 32
JUMP R32 = writing to M32 is trapped and a JUMP is made to the RHS
OUT R48 = output the RHS
*R01 R56 = output the RHS to the 16 bits in (R0,R1)
FREQ R62 = set the system clock frequency
CPU R63 = other CPU behaviour (e.g. set flags)
HCF R63 = every homebrew computer needs this instruction!
human readable machine readable comment
Rj OP Rk Rj OP Rk the general case for all OPs except LSL
Rj » 1 Rj »1 Rk k is ignored in the case of LSL
Rj Rj | M32 OR with zero (M32) results in only Rj
Rj OP c Rj OP Rk c is a constant in the set (0, 1, … 16, 32, 64 … 240), k selects
Rj OP c Rj OP Rk c is a constant in the set (0, 1, … 16, 32, 64 … 240), k selects
c OP Rk Rj OP Rk c is a constant in the set (0, 1, … 16, 32, 64 … 240), j selects
k Rj & Rk any 8 bit number, j is from (0, 16,…240), k from (0,1,..15)
IN wait for input from toggle switch
*M01 input from address (M0, M1)

'cond' is probably the standard 3bits/8 values: Always, Never, =0, !=0, >0, >=0, 0⇐, <0. However, look at the 6502 flags, Carry, Overflow, Negative and look at what values of 'cond' were used in 'third' code.

SIMPL

SIMPL is a SIMPL IMperative Procedural Language. It's deliberately minimal and very very simple - the aim is to show how languages and complilation work in an easily ingestible way.

statements are of the form: `a = function(b c)` where a, b and c are variables. b and/or c can of course be other function calls, so 2x+1 is `add(mul(x 2) 1)`

Variables follow :

Type size/bytes Syntax Example
boolean 1 True|False True
byte 1 \d+[U] 42
short 2 \d+[U]S 65535US
long 4 \d+[U]L -2147483647L
character 1 '\w' 'a'
string 1+ “\w*” “Hello World!”
float 3 \d+.\d*[e[-]\d+] 3.1415926535

Only fixed sized arrays are supported, the size must be known at compile time and memory is allocated as contiguous variables of the same type.

Indentation defines a code block so allow us to define functions and implement control flow:

def mul2(x)
  return mul(x 2)

and also to implement control flow:

if eq(answer 42) 
  print("The Answer is 42")
else
  print("The Answer is not 42")

a = 0
while lt(a 42)
  a = inc(a)
  print(a)
  

The aim is to do type resolution at compile time, so types do not have to be specified. The type of a constant can be determined. The return type of a function is the return type of the last call that function makes (with a check that all returns have the same type). The types of the function arguments are determined by the usage of those arguments (again, with a consistacny check). The aim is for a minimal language. There is no automatic type promotion.

History

I've wanted to build my own computer since the late 1980's when the old departmental relay based telephone system was thrown out. I asked my professor if I could take it out of the skip, he very sensibly said “no” quite firmly.

Since then I've thought about many designs: some have been relay based, some transistor; some have included memory and other have been CPU only. Noteably:

  • in 2017 I started the parent of this design on hackaday.io. The data size and address size was 16 bits with five conventional registers addressing 64k words of RAM. The idea was to use a Raspberry Pi for external memory and to sanity check the computations. A Forth like language was written and many thousands of lines of code was written, including floating point and random numbers. The code, compiler and emulator is on github.
  • Even with only five registers the design of each memory cell is critical because the majority of the transistors (and other components) are taken by the memory cells. This led me down a long random search of weird memory cells and dippy which used DIP switches for ROM/program memory.
  • External memory is somewhat cheating, and from the educational point-of-view it's expensive (a Raspberry Pi is a significant proportion of the total cost). What's more, the instruction set was all register to register except the load/store instructions. Hence I've simplified everything and got rid of the external memory and increased the number of registers considerably. For ease of explanation I don't call the storage space 'registers' but RAM. Two RAM locations are read into latches/registers for presentation to the ALU and then stored back into RAM.