I have built a computer out of relays.  More info, including pictures:

     http://www.cs.pdx.edu/~harry/Relay/index.html

The computer consists of 4 components:
     ALU (Arithmetic Logic Unit)
     Register Unit
     Program Control Unit
     Sequencing Unit

Each component is housed in a wooden cabinet and all wiring is visible behind the acrylic front panel.  Switches and lights allow the operator to see and change the internal state.

Each cabinet is Mahogany and meant to hang on the wall or stand on a table.  Each cabinet is approximately 28" x 40" x 8".

Features:
     8 general purpose registers (of 8 bits each)
     instruction register (8 bits)
     program counter (16 bits)
     2 additional registers (16 bits each)
     7 bit ALU (operations: AND, OR, XOR, NOT, SHL, ADD, INC)
     16 bit increment unit
     32 Kbytes of main memory (implemented using one CMOS chip)

Each instruction is 8 bits long.  Some instructions (JUMP, CALL, BRANCH, SET-16) are followed by 16 additional bits of immediate data.

The instruction set:
     MOVE       register to register
     CLEAR      set register to zero
     LOAD       1 byte from memory to register
     STORE      1 byte from register to memory
     AND        8 bit, register to register
     OR         8 bit, register to register
     XOR        8 bit, register to register
     NOT        8 bit, register to register
     SHL        8 bit, register to register, shift left 1 bit
     ADD        8 bit, register to register
     INCREMENT  8 bit, register to register
     INCR-XY    16 bit, XY register
     GOTO       unconditional
     CALL       return address is stored in XY register
     RETURN     jump indirect through a register
     BRANCH     conditional (beq, bne, blt, bcy)
     SET-16     load 16-bit immediate value into register
                   (The 2-byte value follows the instruction.)
     SET-8      load 5-bit sign-extended immediate value
                   into register (with sign extension)
     HALT       suspend execution

The clock cycle time is approximately 5 Hz.  Instructions take between 8 and 24 cycles.  Obviously not fast, but lights blink and it makes noise.

The Clock Circuit
=================

The clock circuit has 4 relays and 4 capacitors.  While the clock circuit has several internal states, its output is a single wire with a square wave.  It's inputs are (1) the run/stop switch, (2) a line to freeze the clock, which is only activated when the HALT instruction is executed, and (4) a reset switch, for resuming after a HALT.

In more detail: the clock circuit involves 4 relays, connected in a chain, one after the other.  Each relay has a capacitor (each is .5 farad).  The relays can be numbered 1, 2, 3, 4.  The chain is a cycle so relay 1 follows 4.  At any one time, two relays are on.  Call them relay i and i+1.  Relay i is being kept on by its capacitor, which is discharging.  Relay i+1 has a signal to it, so relay i+1 is charging up its capacitor.  When the capacitor for relay i finally depletes and the relay switches off, this triggers a change in the circuit.  It will shut off the signal to relay i+1 (which will then begin to discharge) and it will also turn on the signal to i+2, which will turn on and begin charging.

Registers and System Organization
=================================

Please consult the block diagram showing the overall system architecture.

The general purpose registers, which each have 8 bits, are called:
     A, B, C, D, M1, M2, X, and Y
In some instructions, M1 and M2 are combined to form a 16-bit register, called M.  Likewise, in some instructions, X and Y are combined to form a 16-bit register, called XY.

The program counter (PC) is 16-bits.

There is a 16 increment unit, which adds 1, and there is a 16-bit register called INC dedicated to the increment unit.  This register is not visible to the programmer's model and is used only (1) to increment the PC during each instruction, and (2) in the INCR-16 instruction.

The instruction register (INST) is 8-bits and holds the instruction being executed.

There is a 16-bit register, called J, which is used during the CALL, JUMP, and GOTO instructions.  These instructions load the register from the immediate 16-bit value following the instruction, and then to complete the transfer of control, the J register is copied to the PC register.

The computer is not pipelined.  Each instruction is executed to completion before the next instruction is fetched.

There is a 3-bit condition code register, with the following bits:
     Z = set when value is zero
     Cy = set when there is a carry from the ALU for ADD or INCREMENT operations
     S = set when the value is negative, i.e., when most significant bit is 1
The condition code register is set after any ALU instruction.  The ALU instructions are:
     AND, OR, XOR, NOT, SHL, ADD, INCREMENT

There are 2 buses.  The Data Bus is 8-bits wide and the Address Buss is 16-bits wide.

The Main Memory is connected to both the the address bus and the data bus.

The Main Memory
===============

The Main Memory Unit is organized as 32K bytes, addressed from hex 0000 through 7FFF.  It is implemented with a single CMOS Static RAM chip, Hitachi part HM62256 Series.

While all the realys are driven by the large 12 Volt power supply, there is a small 5 Volt power supply used only to power the RAM chip.  A set of 8 of FET amplifiers are used to change from the chip's TTL levels to 12 Volts to drive the relays, which is needed for transferring data from the memory to the CPU in a "memory-read" operation.  There are also three small LED arrays, each containing 10 LEDs that run at TTL levels.  These are used to monitor the address lines (15) and data lines (8) right at the chip.

The MOV Instruction
===================

The MOV instruction "selects" one register, whose contents are then dumped (i.e., gated) onto the Data Bus.  Then, the destination register is "loaded" from the bus.  Any general purpose register can be the source and any general purpose register can be the destination.

The MOV instruction is encoded as follows:

     0 0 d d d s s s

where "ddd" and "sss" specify the destination and source registers according to this encoding:

     0 0 0  =  A
     0 0 1  =  B
     0 1 0  =  C
     0 1 1  =  D
     1 0 0  =  M1
     1 0 1  =  M2
     1 1 0  =  X
     1 1 1  =  Y

The CLEAR Instruction
=====================

This instructuion moves 0 into one of the 8-bit registers (A, B, C, D, M1, M2, X, or Y).

This instruction is encoded as a variation of the MOV instruction, where "sss" and "ddd" are the same.

The LOAD and STORE Instructions
===============================

The memory is organized as 32K bytes, addressed from hex 0000 through 7FFF.

The LOAD instruction uses the 16-bit value in the M register as an address and reads a byte from Main Memory into either the A, B, C, or D registers.  Likewise, the STORE instruction assumes the address is in the M register and stores the value from either A, B, C, or D into a byte in Main Memory.

The LOAD instruction is encoded as follows:

     1 0 0 1 0 x r r

where "x" is ignored and "rr" specifies the destination register according to this encoding:

     0 0  =  A
     0 1  =  B
     1 0  =  C
     1 1  =  D

The STORE instruction is encoded as follows:

     1 0 0 1 1 x r r

where "x" is ignored and "rr" specifies the source register according to this encoding:

     0 0  =  A
     0 1  =  B
     1 0  =  C
     1 1  =  D

The ALU Instructions
====================

The ALU takes as inputs the current values of registers B and C.  Its result is placed on the 8-bit bus.  In theory, the result can be transfered into any general purpose register, except B or C, which would cause feedback.  However, the ALU instructions can place the result into either A or D.  This class of instructions can perform these operations:

     A = B + C     D = B + C      8-bit addition
     A = B + 1     D = B + 1      8-bit increment
     A = B & C     D = B & C      logical AND
     A = B | C     D = B | C      logical OR
     A = B xor C   D = B xor C    logical EXCLUSIVE-OR
     A = !B        D = !B         logical NOT
     A = B << 1    D = B << 1     left-shift circular

The 3-bit condition code register is set by this instruction, according to the result:

     S    "Sign bit"    Set if result is negative, i.e., iff most sign. bit is "1".
     Cy   "Carry bit"   Set if there was a carry-out for ADD or INCREMENT.
     Z    "Zero bit"    Set if result is "00000000".

The ALU instructions are encoded as follows:

     1 0 0 0 r f f f

where "r" specifies the destination register according to this encoding:

     0  =  A
     1  =  D

And "fff" specifies the operation according to this encoding:

     0 0 0  =  B + C
     0 0 1  =  B + 1
     0 1 0  =  B & C
     0 1 1  =  B | C
     1 0 0  =  B xor C
     1 0 1  =  !B
     1 1 0  =  B << 1

The INCR-XY Instruction
=======================

This instruction adds 1 to the 16-bit XY register.  It is encoded as:

     1 0 1 1 0 0 0 0

The condition code register is not updated.

The GOTO Instruction
====================

The GOTO instruction is 3 bytes long.  The 2nd and 3rd bytes contain a 16-bit address.  A jump to this address is made.  This instruction is encoded as:

     1 1 1 0 0 1 1 0    a a a a a a a a    a a a a a a a a

where the field "aaaaaaaa aaaaaaaa" is the target address.

The CALL Instruction
====================

The CALL instruction is exactly like the GOTO instruction, except that, in addition, the address of the next instruction after the CALL instruction is saved in the XY register.

This instruction is encoded as:

     1 1 1 0 0 1 1 1    a a a a a a a a    a a a a a a a a

where the field "aaaaaaaa aaaaaaaa" is the address of the subroutine.

The RETURN Instruction
======================

The RETURN instruction moves the contents of XY back to the PC.  This can be used to effect a RETURN (when used after a CALL) or an indirect jump, when XY contains a computed value.

This instruction is slightly more flexible and is actually a 16-bit move instruction that can perform any one of the following moves:

     PC  =  XY
     PC  =  M
     PC  =  J
     XY  =  M
     XY  =  J
     XY  =  0

This instruction is encoded as:

     1 0 1 0 d s s 0

The field "d" specifies the destination register, according to this encoding:

     0  =  XY
     1  =  PC

The field "ss" specifies the source register, according to this encoding:

     0 0  =  M
     0 1  =  XY
     1 0  =  J
     1 1  =  0 and halt the machine (See the HALT instruction)

The Conditional BRANCH Instructions
===================================

The conditional BRANCH is 3 bytes long.  The 2nd and 3rd bytes contain the 16-bit target address.  The condition BRANCH instruction will test the condition code register and optionally jump to the target address.  Here are different tests that can be performed:

     BE   Take the branch if Z=1, i.e., if the last result was zero
     BNE  Take the branch if Z=0, i.e., if the last result was not zero
     BNC  Take the branch if Cy=0, i.e., if there was no carry for the ADD or INCR
     BNEG Take the branch if S=1, i.e., if the last result was negative

These can also be combined, in which case the branch is taken if any of the conditions is met.  (The unconditional GOTO is actually a conditional branch testing for "Z=1 OR Z=0", which is always true.)

Note that "BE" and "BZ" are the same.  Also "BNE" and "BNZ" are the same.  Which is preferred depends on the context in which the instruction is used.  "BE" would be preferred after a XOR instruction to compare 2 values for equality:

     A = B XOR C
     BE target
     < execution reaches here if B and C are different >

This instruction is encoded as:

     BNEG =  1 1 1 1 0 0 0 0    a a a a a a a a    a a a a a a a a
     BNC  =  1 1 1 0 1 0 0 0    a a a a a a a a    a a a a a a a a
     BE   =  1 1 1 0 0 1 0 0    a a a a a a a a    a a a a a a a a
     BNE  =  1 1 1 0 0 0 1 0    a a a a a a a a    a a a a a a a a

where the field "aaaaaaaa aaaaaaaa" is the target address.

The SET-16 Instruction
======================

The SET-16 instruction is 3 bytes long.  The 2nd and 3rd bytes contain a 16-bit value.  This value is loaded into the M register.

This instruction is encoded as:

     1 1 0 0 0 0 0 0    v v v v v v v v    v v v v v v v v

where the field "vvvvvvvv vvvvvvvv" is the value to load into the M1/M2 register pair.

The GOTO/CALL/BRANCH/SET-16 Instruction Class
=============================================

These instructions are all really variations of a single instruction.  This super-instruction has the following encoding:

     1 1 r s c z n x    a a a a a a a a    a a a a a a a a

The bit field in the op-code have the following meanings:

     r: Load the 2nd and 3rd bytes of the instruction into
        either the M or J registers.  (r=0 means load M; r=1 means load J)
     x: When set, copy the PC into XY before jumping (used for CALL)
     s: When set and when S=1, copy register J to PC (take the branch)
     c: When set and when Cy=0, copy register J to PC (take the branch)
     z: When set and when Z=1, copy register J to PC (take the branch)
     n: When set and when Z=0, copy register J to PC (take the branch)

Thus it is possible to load the J register without jumping, which can be used to load the XY register.  (The instruction would be followed by a variant of the "return" instruction which performed a "XY=J" move.)  Also, the condition branching can be combined with saving the PC in XY, resulting in a "CONDITIONAL CALL" instruction.

The SET-8 Instruction
=====================

This instruction can be used to load register A or B with any value in the range:

     -16 .. +15

This instruction is encoded as follows:

     0 1 r v v v v v

The 5-bit "vvvvv" field is sign-extended to 8 bits.  The "r" bit is used to select the destination register, according to the following encoding:

     0  =  A
     1  =  B

The HALT Instruction
====================

This instruction will suspend execution by freezing the clock.  Instruction execution is resumed by toggling a RESET switch.  This instruction will also clear the PC.  It is encoded as:

     1 0 1 0 1 1 1 0

Timeline, History, and General Comments
=======================================

I teach CS and I have always loved computers and been interested in how they work.  Over the years I have found that you can read and study and make paper designs, but there is no substitute for actually building things.  There are always processes at work that you can't understand until you actually build a working unit.  I had wanted to build a computer out of relays when I was much younger, but I didn't have the knowledge, patience, money, or time.

A couple of years ago, at the technical museum in Munich, I saw a replica of Konrad Zuse's Z3 computer and I copied down his circuit diagram for the fuller adder, which was quite clever.  Later, as a little project, I built a simple adder, using this circuit and really enjoyed the physicality of it, although it could only add two 2-bit numbers.  I then started thinking about constructing a full 8-bit ALU.  After I had completed the basic design, I couldn't help thinking about building an entire computer.

Giving final exams are a panic for students, but are really different for the professors.  It is the only time that there are no lectures to plan and no exams to grade.  As I sat there during a final one term, I sketched out the outline of an architecture that I might use.  So I designed the ALU to "fit" into a complete machine.  However, when I decided to build the ALU, I still wasn't committed to building anything more.  It is important to stay focussed on a project that you can complete.  It is easy (and fun) to fantasize about building something big, but unless one is able to concentrate long enough to get something completed, it is really just idle day-dreaming!

Once I finished the ALU, I decided to go ahead and build the second cabinet, the register unit.  However, in my mind, I had not committed to building anything beyond that.  I figured that if I finished it and it worked, I would then decide whether I wanted to proceed.  I knew there was a possibility I might be burned out, or might run into problems that would make a full computer non-functional.  I always want successes, not failures, so I decided I would rather have a fully-working half-computer that a half-finished full-computer.

After finishing the ALU and the register unit, I found I enjoyed all aspects of the construction.  Usually design is the most fun and, when building stuff, we tend to leave the most unpleasant or difficult tasks until the very end, but after completing 2 cabinets, I knew pretty much what would be involved.  At that time I decided to keep going.

All in all, it took over a year, but I didn't work on it very often.  However, projects that are fun always go faster and seem to take less time!!!  You squeeze in the time while watching TV, you go over the circuits in your head while going to sleep, and you never forget to stop at the hardware store when you happen to be in the area!  Part of the secret to being able to do big projects is your own personal motivation.

I was not on a budget, so this helped.

Costs
=====

415 Relays, 4PRLY-12, plus extras for prototyping ($3.40 each)
350 LEDs ($1.49 each)
111 Switches, SPDT, on-on mini toggle, MTS-4 ($0.90 each)
Acrylic boards, pre-cut and pre-drilled, 4 cabinets (total $1,095.37, aprx $275 per cabinet)
     ALU (1 board), 82.19
     Register Cabinet (10 boards), $251.60 total
     PGM-CTRL Cabinet (10 boards), $381.80 total
     Sequencer Cabinet (5 boards): $379.78 total
4 Mahogany Cabinets (est $300 each)
2 Power Supplies, 12V, 10Amp ($79.99 each)
20 Capacitors, 100UF, 100V non-polar ($1.55 each)
Memory Board:
     1 SRAM, 32K dip, Jameco # 82472CA, Other #: 62256LP-70 ($5.49)
     1 eight-channel FET driver module, NCD, www.controlanything.com, 8-FET ($49.00)
     3 eight-channel LED array module, NCD, www.controlanything.com, IOTEST-L ($10 each)
     1 Prototyping board ($5.99)
     1 small power supply (for memory) 5V, 4Amp, 20Watt, Jameco #: 213583CA, ($26.95)
DB-9 Sub-miniature Connectors
     32 Plugs ($1.59 each)
     32 Receptacles ($1.76 each)
     64 Connector hoods ($0.49 each)
22-Gauge black solid copper wire, 100 feet ($4.49 each), est. 20 rolls
8-connector cable, CAT5e, 4 pairs, 24AWG solid, 328 feet ($42.00)
4 Fans ($10 each)
Misc Hardware. (Est $100)

Summary:
     Relays, $1,411
     LEDs, $521
     Switches, $100
     Acrylic Boards, $1,095
     Cabinets, $1,200
     Power Supplies $160
     SRAM Memory, $117
     Capacitors, $148
     Connectors, $138
     Wire, $132
     Fans, $20
     Hardware, $100

Grand Total, $5,142  (per unit: $1,285)

Operation of the Machine
========================

There are no input or output instructions.  You use the computer by loading memory with a program.  This is done through the switches, as follows:

     REPEAT
          Set 16 bit address switches to the memory address of the next instruction.
          Set 8 data switches to the bits of the instruction.
          Toggle the "memory write" switch.
     UNTIL the entire program has been entered.

You can put the input data into registers bit-by-bit by toggling the switches.

To execute the program:

     Initialize the "Program Counter" register to the address of the first instruction.
     Toggle a "Reset" switch.
     Flip the "Run-Stop" switch to "Run".

There is a HALT instruction which, when encountered, while cause the machine to freeze up.  So far, all programs leave their results in registers, and you can read the contents of the registers (in binary) by looking at the lights.