Divorceengine

(czech)

Relay computer

jakub.kasse (at) seznam.cz, june 2023

Predecessors and contemporaries

My rules of game

Main properties

Pictures

Front view.

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.

Selection of result from ALU.

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

LD/ST


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

Videos

Knight Rider

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.

Multiplication by adding to Y

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.

Multiplication by adding with carry

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.

Plans for the future

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.