Relay computer
jakub.kasse (at) seznam.cz, june 2023
This one was the frist for some of us. Even the mechanical Z1 had floating-point in 1938 (electrical was only a motor), relay Z3 from 1941 is theoretically Turing complete. This was achieved in Berlin during the war.
Konrad's son, Horst Zuse, built a replica of Z3: Horst Zuse‘s Z3 Part 1: Demonstration, Horst Zuse‘s Z3 Part 2: Interview
History remembers this one because of nuclear bomb and John von Neumann. However this machine had Harvard architecture (that's why the architecture is called so) and was designed by Howard Aiken.
Group of freaks soldering their own computers. Somebody from TTL, somebody from transistors and somebody from relays.
They quite increased since my last visit couple years ago. Perhaps they will take me to their gang.
I've seen this guy's web a long time ago. Very nicely explained, it was the main source of information for me. Required reading and twice at least.
He has 16bit addresses, various count of steps for one instruction (even various count of bytes for one instruction), more registers, ALU directly from B and C (but result can also go to D), doesn't have subtraction/comparison. He has interestingly solved clocks because he uses solely 4PDT relays (perhaps smart decision). I think that his trick with one more relay for delay during write could be solved better.
Somebody made a simulator for him: The TOFU Circuit Simulator
He admits he took adder from Zuse (and others took it from him).
Uses diodes, RAM made from capacitors and program read from tape.
Not much relays with some TTL. I think the main saving of relays is that he gets 32bits long "quite universial" instruction (data and addresses are 8bits) which isn't difficult to decode.
This one lives by it. Admirer of Konrad Zuse, he even drove with his friend from Sweden to Germany couple times in summer to see Zuse machines in person.
Bad example for me - he has nice schematics, nice design in 3D, simulation, but he doesn't have computer. I have it opposite - I have computer but no schematics.
Special sport. They taught us already in the first year that anything could be built from NAN or NOR. But this is too minimalistic for me.
This one caused it! My former colleague saw this "paper" and was excited. I didn't understand it then which lasts until now but I like it.
Freak with acid and microscope dismounting old computers or processors. 1bit rarity MC14500B. Did you know that Z80 has 4bit ALU?
I couldn't resist to implement ZF and logics around CF. On the contrary I don't have "A += B" (because for calculating new value of A you need to remember the old value, see PC or CF), only "A = B + C" is possible. I considered SHR more important than SHL, because SHL could be substituted by ADD.
| 0 | 0 | 00 | offs | JMP(offs) | Unconditional relative jump. JMP(0) = LOOP, JMP(1) = NOP | ||||
|---|---|---|---|---|---|---|---|---|---|
| 01 | JZF(offs) | Relative jump if A == 0. | |||||||
| 10 | JCF(offs) | Relative jump if carry. | |||||||
| 11 | JSF(offs) | Relative jump if the highest bit A.7 == 1. | |||||||
| 1 | NOP, JNZF, JNCF, JNSF | Negation of previous conditions. | |||||||
| 10 | 00 | imm | LD A, imm | Load value from instruction to A. | |||||
| 01 | 0 | reg | LD A, Rx | Loads value from register (00=B, 01=C, 10=Y, 11=PC) to A. | |||||
| 1 | ST A, Rx | Stores value from A to register. ("ST A, PC" is absolute jump.) | |||||||
| 1 | 0 | offs | LD A, [Y+offs] | Loads value from RAM from address [Y+offs] to A. | |||||
| 1 | offs | ST A, [Y+offs] | Writes value to RAM to address [Y+offs] from A. | ||||||
| 11 | 0 | 0 | 0 | 0 | NOT B | A = ~B | |||
| 1 | NOT Y | A = ~Y | |||||||
| 0 | 1 | 0 | SHR B | A = B >> 1 (without carry) | |||||
| 1 | SHR Y | A = Y >> 1 (without carry) | |||||||
| 1 | 0 | SHRCF B | A = B >> 1 (with carry) | ||||||
| 1 | SHRCF Y | A = Y >> 1 (with carry) | |||||||
| 1 | 00 | 0 | 0 | OR B C | A = B | C | ||||
| 1 | OR B NOTC | A = B | ~C | |||||||
| 1 | 0 | OR Y C | A = Y | C | ||||||
| 1 | OR Y NOTC | A = Y | ~C | |||||||
| 01 | 0 | 0 | AND B C | A = B & C | |||||
| 1 | AND B NOTC | A = B & ~C | |||||||
| 1 | 0 | AND Y C | A = Y & C | ||||||
| 1 | AND Y NOTC | A = Y & ~C | |||||||
| 10 | 0 | 0 | XOR B C | A = B ^ C | |||||
| 1 | XOR Y C | A = Y ^ C | |||||||
| 1 | NOT C | A = ~C (Tried B ^ ~C, resp. Y ^ ~C, but no luck.) | |||||||
| 1 | 0 | imm | ADD B imm | A = B + imm | |||||
| 1 | 0 | 0 | 0 | ADD B C | A = B + C | ||||
| 1 | SUB B C | A = B − C | |||||||
| 1 | 0 | ADD Y C | A = Y + C | ||||||
| 1 | SUB Y C | A = Y − C | |||||||
| 1 | 0 | 0 | ADDCF B C | A = B + C + CF | |||||
| 1 | SUBCF B C | A = B − C − CF | |||||||
| 1 | 0 | ADDCF Y C | A = Y + C + CF | ||||||
| 1 | SUBCF Y C | A = Y − C − CF | |||||||
0000 offs JMP(offs) 0001 offs JZF(offs) 0010 offs JCF(offs) 0011 offs JSF(offs) 0100 .... NOP 0101 offs JNZF(offs) 0110 offs JNCF(offs) 0111 offs JNSF(offs) 1000 imm4 LD A, imm4 1001 0reg LD A, reg 1001 1reg ST A, reg 1010 offs LD A, [Y+offs] 1011 offs ST A, [Y+offs] 1100 .0y. NOT y 1100 01y. SHR y 1100 11y. SHRCF y 1101 00yn OR y n 1101 01yn AND y n 1101 10y0 XOR y C 1101 10.1 NOT C 1110 imm4 ADD B, imm4 1111 0.y0 ADD y, C 1111 0.y1 SUB y, C 1111 1.y0 ADDCF y, C 1111 1.y1 SUBCF y, C reg: .00=B, .01=C, .10=Y, .11=PC n: 0=C, 1=NOTC y: 0=B, 1=Y
All ALU operations affect CF but it has some meaning only at ADD, SUB and SHR. ZF and SF comes always from current value of A.
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|
| PC, OP, JMP | WROP | ||||||
| ADDR=PC ALU1=PC | |||||||
| RDM OP=MEMR | |||||||
| ALU2=+1/imm | |||||||
| WRPC2 | WRPC | ||||||
| PC2=SUM | PC=PC2 | ||||||
| LD/ST Rx | WRRx/WRA | ||||||
| A=Rx | |||||||
| LD [Y+offs] | ALU1=Y ALU2=imm ADDR=SUM | ||||||
| WRA | |||||||
| RDM A=MEMR | |||||||
| ST [Y+offs] | ALU1=Y ALU2=imm ADDR=SUM MEMW=A | ||||||
| WRM | |||||||
| MATH | ALU1=B/Y ALU2=C/~C | ||||||
| WRCF2 | WRA | ||||||
| CF2=CF | A=ALU CF=... | ||||||
Generally, registers are written the way that the first pulse clears the register (cut off self-hold, eg. WROP) and next pulse connects destination rgister with data source (eg. OP=MEMR). After data disconnection, the register relays hold themselves. ALU or address must be prepared in advance.
At "ST [Y+offs]" I didn't comply with table and "MEMW=A" is only during steps 5 and 6, it doesn't exceed to step 7.
On contrary of simple RAM, I made a cheat that I wait about 5 ms for inputs being steady before Arduino does something!
Because PC and ALU are only 4bits, the highest bit of address can be connected to "ADDR=SUM" and effect of Harvard architecture can be achieved. Program is on different addresses than data.
To make it real retro I have connected switches and buttons parallel to Arduino inputs to be able to write and read RAM manually.
Top board. Clock on the left, right top loaded instruction (OP), right bottom registers (PC2, PC, A), between instruction and registers is decoder, RAM/Arduino on the left.
Middle board, ALU. Top row selection of operands (PC/B/Y, ~C/C/+1/imm), two rows of adder, one row of logical functions (AND, OR, XOR, NOT), bottom row selection of result, on the right playing with carry, right bottom write to A.
Bottom board. Left top evaluation of zero and decision whether to jump, right registers (B, C, Y), between flags and registers is selection of register for LD/ST. Left bottom manual control of RAM.
Back view. Green is +24VDC, green/white is ground (with exception of 8 bits of RAM). If I would build this next time then I would distribute ground for all THT relays on the board and then it will not be such a mess (see area around ZF on the bottom board).
Steps of instruction processing.
Some whole pages, will be explained later.
Clock in detail. Realys switching off one by one. The last relay turns on the first one again which can be blocked by switch (bottom). The second switch (right) allows to skip step 7.
Connection of Arduino inputs/outputs. In input-mode there is assumed internal pull-up big enough to make low voltage level together with 10k pull-down when MEMW is disconnected. On the contrary 10k pull-down can't be too small otherwise there would be too high current through red LED. PNP transistor should have resistor between E-B but it works without it.
ALU1=MATH, ALU2=C, ALU2=~C, WRAmath, A=ALU, ALU1=B.
Remembering previous value of carry (CF2), preparation of CF2 and ~CF2 for SUM, remembering new value of CF.
B − C = B + ~C + 1
B − C − CF2 = B + ~C + ~CF2
CFsub = ~CFadd
Use carry with ADDCF, SUBCF or SHRCF.
Konrad's one bit adder with carry. Brilliantly simple.
Logical functions. B can be replaced by Y. C can be replaced by ~C, so it is possible to get eg. Y|~C instead of B|C. On the contrary ~C is fixed, so instead of B^~C you get ~C.
"Bus" data flow. It's not plotted here but all realys are 4PDT.
Decision whether to jump (imm) or not to jump (+1).
On of ORs, this one is for reading from RAM.
Switch for program stepping without its execution.
Map of upper part of upper board. (Dimensions don't fit.)
Bit is moving from right to left and back in register A.
; Move up. 0: 1000 0001 LDI 1 A = 1 1: 1001 1000 ST B B = A 2: 1001 1001 ST C C = A 3: 1111 0000 ADD B C A = B + C 4: 0110 1101 JNCF -3 if (!CF) PC -= 3 ; Jumps to address 1. ; Move down, we have carry set after move up. 5: 1001 1000 ST B B = A 6: 1100 1100 SHR B CF (A[7:0], CF) = (CF, B[7:0]) 7: 0110 1110 JNCF -2 if (!CF) PC -= 2 ; Jumps to address 5. 8: 1001 1011 ST PC PC = A ; A is 0, so jumps to the beginning.
C = 3, B = 4, C is gradually added to Y, while B is decrementing, program loops at address 10 at the end and Y = B * C. Arduino is powerd in advance this time and instruction processing steps 4 and 7 are omitted.
; Initialization. 0 : 1000 0011 LDI 3 ; A = 3 1 : 1001 1001 ST C ; C = A 2 : 1000 0100 LDI 4 ; A = 4 3 : 1001 1000 ST B ; B = A 4 : 0001 0000 JZF 0 ; If by some luck B is zero then loop. ; Main loop. 5 : 1111 0010 ADD Y C ; A = Y + C 6 : 1001 1010 ST Y ; Y = A 7 : 1110 1111 ADD B -1 ; A = B - 1 8 : 1001 1000 ST B ; B = A 9 : 0101 1100 JNZ -4 ; Done? ; End. 10: 0000 0000 JMP 0 ; Loop.
Perhaps the most you can program into 16 bytes. It is necessary to switch to Harvard because you need not only 16 bytes for program but also 4 (half)bytes for data. Multipliers must be written to RAM manually before the program starts. The first multiplier is expected on address 0x80 and will be decremented one by one. The second multiplier is expected on address 0x81 and will by gradually added to address 0x82 and in case of overflow the value on address 0x83 will be incremented. Example shows 15 × 15, the result must be read manually from addresses 0x82 (lower halfbyte) and 0x83 (higher halfbyte) and will be 1 + 14 × 16 = 225.
; Load the second multiplier to C. 0 : 1010 0001 LDM(1) ; A = MEM[Y+1] 1 : 1001 1001 STC ; C = A ; Decrement the first multiplier. 2 : 1000 0000 LDM(0) ; A = MEM[Y+0] 3 : 0001 0000 JZF(0) ; If finished then loop. 4 : 1001 1000 STB ; B = A 5 : 1110 1111 ADDBI(-1) ; A = B - 1 6 : 1011 0000 STM(0) ; MEM[Y+0] = A ; Add the second multiplier to lower halfbyte of result. 7 : 1010 0010 LDM(2) ; A = MEM[Y+2] 8 : 1001 1000 STB ; B = A 9 : 1111 0000 ADDBC ; A = B + C 10: 1011 0010 STM(2) ; MEM[Y+2] = A 11: 0110 0111 JNCF(-9) ; If sum didn't overflow then jump to step 2. ; Necessary to increment upper halfbyte of result. 12: 1010 0011 LDM(3) ; A = MEM[Y+3] 13: 1001 1000 STB ; B = A 14: 1110 0001 ADDBI(1) ; A = B + 1 15: 1011 0011 STM(3) ; MEM[Y+3] = A ; Now JMP(-14) should come but there is no place so PC will overflow to zero.
I would like SHL instruction but I got out of 4PDT relays.
If there were more working registers then ALU could work with some of them instead of Y.
It is necessary to make it 8bit becuase there can't be done much with 16 bytes of code and numbers 0...15, respectively -8...+7.
And when the processors becomes 8bit then I imagine that the first "LD A, imm" clears A and loads imm into lower halfbyte while the immediately following "LD A, imm" will overwrite only the upper halfbyte. This will make possible to read one-byte constant by two bytes of code and will remain compatible with halfbyte loading.
Some instruction for halting the clock could be made. But clock relays are sometimes so unreliable that it would be needed to recognize whether clock stops due to instruction or failure.