KENBAK-1 · Volume 10

KENBAK-1 — Volume 10 — Programs to Run — Games, Patterns and Puzzles

Five programs to key into a KENBAK-1 or a replica — a bouncing light, Kill the Bit, the Fibonacci numbers, every prime below 256, and a game of Hi-Lo — each in octal as keyed, each explained line by line, and each run in the series' simulator with the lamps it lights

Figure 1 — A µKENBAK-1 replica running, with data lamps lit across the top row. Every program in this volume runs on the original instruction set, so it can be keyed into an original KENBAK-1, a TTL reproduct…
Figure 1 — A µKENBAK-1 replica running, with data lamps lit across the top row. Every program in this volume runs on the original instruction set, so it can be keyed into an original KENBAK-1, a TTL reproduction or a microcontroller replica alike. — Photo: Adwater & Stir (Chris Davis), product page, https://adwaterandstir.com/product/kenbak-1/

10.1 About This Volume

A KENBAK-1 has eight lamps for output and eight buttons for input. Everything a program shows must be shown in those lamps, and everything it is told must come through those buttons. This volume is a small collection of programs that make the most of that: two that make patterns, one that is a game of reflexes, two that compute something worth seeing, and one guessing game in which the lamps answer back.

Each program is given in octal as keyed: the address, the byte to store there, and the symbolic form in the notation of Volume 6. Every listing was produced by the series’ assembler and every program was run in the series’ simulator, 03-outputs/tools/kenbak_sim.py; the programs and the scripted runs are in kenbak_programs.py, and its output file programs_output.txt holds everything quoted here. Where a program waits for the player, the simulator presses the buttons on a script, and the script is described with the run. Times “on the original” come from the simulator’s timing model of Volume 3 and are estimates.

10.1.1 How to key a program

Every program starts at address 004, and its start address must also be stored in P, at 003. The procedure is the original’s (Volume 6): power on; Start, then Stop; Clear, key 003, Set (Address); Clear, key 004, Store (Memory); then for each byte of the listing in order, Clear, key the byte, Store. Read it all back from 003 before pressing Start. On a µKENBAK-1 (Volume 9), Clear plus Store first erases memory and sets P to 004 and the address to 004, which saves a step, and a finished program can be saved to the replica’s EEPROM with a data key plus Store.

10.1.2 A note on speed

Two of these programs contain delay loops whose length is set by a single byte called SPEED. On an original machine, by the timing model, the values given make the patterns move at a few steps a second. A replica may run at a different rate — the µKENBAK-1’s firmware adds an adjustable delay to every instruction (Volume 9) — so SPEED is the byte to change: a larger value is slower.

10.2 A Light That Bounces

The simplest pleasing program for a row of lamps is a single light that runs from one end to the other and back. This one rotates A left until the lit bit reaches lamp 7, then right until it reaches lamp 0, and calls a delay subroutine between steps.

Loc  Contents  Label   Symbolic
003  004               P: start at 004
004  023       START   LOAD A C=001
005  001
006  034       LEFT    STORE A OUTPUT
007  200
010  364               JMD UNC DELAY
011  030
012  311               ROTL A 1
013  047               JPD A>0 LEFT     until the bit reaches lamp 7
014  006
015  034       RIGHT   STORE A OUTPUT
016  200
017  364               JMD UNC DELAY
020  030
021  111               ROTR A 1
022  302               SKP1 0 A         back at lamp 0?
023  000
024  344               JPD UNC RIGHT
025  015
026  344               JPD UNC LEFT
027  006
030  000       DELAY   (mark)
031  224               LOAD X SPEED
032  041
033  213       DLOOP   SUB X C=001
034  001
035  243               JPD X!=0 DLOOP
036  033
037  354               JPI UNC DELAY
040  030
041  040       SPEED   040

How it works. The leftward run uses a jump condition: after ROTL, A is positive and non-zero until the bit arrives in b7, when A becomes 200 — negative — and “JPD A>0 LEFT” falls through. The rightward run cannot use the same trick, because nothing about A’s sign changes when the bit reaches b0, so it tests the bit directly: “SKP1 0 A” (302 000) skips the next jump when bit 0 of A — address 000 — is a 1, landing on “JPD UNC LEFT”. The DELAY subroutine counts X down from SPEED; it uses only X, so A is preserved across the call. It is entered with Jump and Mark and left through its mark with JPI, exactly as in Volume 7.

The run. In the simulator the lamps step 001, 002, 004, 010, 020, 040, 100, 200, 100, 040, 020, 010, 004, 002, 001, 002 and on, without repeating the end lamps. With SPEED at 040 (32), the timing model gives about 0.18 seconds per step on an original, or a complete sweep there and back in under three seconds.

Figure 2 — The first sixteen steps of the bouncing light, as the simulator recorded them. Each row is one state of the eight data lamps, lamp 7 on the left.
Figure 2 — The first sixteen steps of the bouncing light, as the simulator recorded them. Each row is one state of the eight data lamps, lamp 7 on the left. — Diagram drawn for this series from kenbak_programs.py (03-outputs/diagrams/diagrams_B.py)

Variations. Change the starting value at 005 to 003 or 007 and a bar of two or three lamps bounces instead. Change ROTL and ROTR (311 and 111) to the B register (351 and 151) and use B throughout, and A is free for something else.

10.3 Kill the Bit

Kill the Bit is the most famous front-panel game in computing. Dean McDaniel wrote it for the MITS Altair 8800 in May 1975, in a couple of dozen bytes of 8080 code: a lit bit runs across the panel’s lamps, and the player tries to switch it off by flipping the switch beneath it at the moment it is lit; a switch flipped at the wrong moment adds a bit instead (altairclone.com, “Kill the Bit” listing). The KENBAK-1 predates the Altair by four years and the game by nearly as much, but its panel suits the game well, with one difference: its data buttons do not stay down. A press sets a bit in the input byte, 377, which stays set until the program clears it — so the program reads 377 once per step and then wipes it.

The game’s arithmetic is an exclusive-OR: each button pressed flips the matching bit of the pattern. The KENBAK-1 has no exclusive-OR instruction, so the program builds one from what it has. For pattern p and input i, p XOR i = (p OR i) AND NOT (p AND i), and NOT x is −x − 1, which is LNEG followed by subtracting one.

Loc  Contents  Label   Symbolic
003  004               P: start at 004
004  023       START   LOAD A C=001     one bit to begin
005  001
006  034               STORE A PAT
007  074
010  023               LOAD A C=000
011  000
012  034               STORE A INPUT
013  377
014  024       LOOP    LOAD A PAT
015  074
016  034               STORE A OUTPUT   show the pattern
017  200
020  364               JMD UNC DELAY    the player's chance
021  062
022  024               LOAD A PAT       PAT xor INPUT:
023  074
024  324               AND A INPUT        PAT and IN
025  377
026  334               LNEG A A           -(PAT and IN)
027  000
030  013               SUB A C=001        -x - 1 = not x
031  001
032  034               STORE A T
033  075
034  024               LOAD A PAT
035  074
036  304               OR A INPUT         PAT or IN
037  377
040  324               AND A T            ... and not (PAT and IN)
041  075
042  311               ROTL A 1         move the pattern one lamp left
043  034               STORE A PAT
044  074
045  123               LOAD B C=000
046  000
047  134               STORE B INPUT    forget the buttons
050  377
051  043               JPD A!=0 LOOP
052  014
053  023       WIN     LOAD A C=377     every bit killed
054  377
055  034               STORE A OUTPUT
056  200
057  000               HALT
060  344               JPD UNC START    Start plays again
061  004
062  000       DELAY   (mark)
063  224               LOAD X SPEED
064  073
065  213       DLOOP   SUB X C=001
066  001
067  243               JPD X!=0 DLOOP
070  065
071  354               JPI UNC DELAY
072  062
073  100       SPEED   100
074  000       PAT
075  000       T

Two instructions show off the KENBAK-1’s registers-in-memory design. “LNEG A A” (334 000) loads A with the negative of the byte at address 000 — that is, of A itself. “LOAD B C=000 / STORE B INPUT” clears the input byte from the program, so that the player never needs the Clear button.

The run. The simulator plays one game on a script. It lets the bit travel once from lamp 0 to lamp 7; presses button 2 by mistake while lamp 7 is lit, which adds a second bit; and then presses button 7 each time a bit reaches lamp 7. Pattern by pattern:

 1  ○○ ○○○ ○○●   001
 2  ○○ ○○○ ○●○   002
 3  ○○ ○○○ ●○○   004
 4  ○○ ○○● ○○○   010
 5  ○○ ○●○ ○○○   020
 6  ○○ ●○○ ○○○   040
 7  ○● ○○○ ○○○   100
 8  ●○ ○○○ ○○○   200   button 2 pressed (a mistake)
 9  ○○ ○○● ○○●   011
10  ○○ ○●○ ○●○   022
11  ○○ ●○○ ●○○   044
12  ○● ○○● ○○○   110
13  ●○ ○●○ ○○○   220   button 7 pressed: bit killed
14  ○○ ●○○ ○○○   040
15  ○● ○○○ ○○○   100
16  ●○ ○○○ ○○○   200   button 7 pressed: bit killed
17  ●● ●●● ●●●   377   all bits gone: HALT

Step 9 shows the rules at work: the pattern 200 with button 2 pressed became 204, and the left rotation carried the top bit round to lamp 0, giving 011. With SPEED at 100 (64), the timing model gives about a third of a second per step on an original — a fair game. Lower SPEED to make it harder.

Figure 3 — The same game drawn as lamps. A wrong press at step 8 doubles the bits; correct presses at steps 13 and 16 kill them; the program lights every lamp and halts.
Figure 3 — The same game drawn as lamps. A wrong press at step 8 doubles the bits; correct presses at steps 13 and 16 kill them; the program lights every lamp and halts. — Diagram drawn for this series from kenbak_programs.py (03-outputs/diagrams/diagrams_B.py)

10.4 Fibonacci Until the Byte Overflows

The Fibonacci numbers — 1, 1, 2, 3, 5, 8, each the sum of the two before — grow fast enough to run out of a byte in a dozen steps, which makes them a good way to watch the KENBAK-1’s carry at work. The program shows one number per press of Start, and when the next sum no longer fits in eight bits it lights every lamp and stops.

Loc  Contents  Label   Symbolic
003  004               P: start at 004
004  123       START   LOAD B C=000     B = the last number
005  000
006  023               LOAD A C=001     A = this number
007  001
010  034       LOOP    STORE A OUTPUT
011  200
012  000               HALT             press Start for the next one
013  034               STORE A T
014  034
015  004               ADD A B          next = this + last
016  001
017  124               LOAD B T         last = this
020  034
021  312               SKP1 1 201       carry out of A?
022  201
023  344               JPD UNC LOOP
024  010
025  023               LOAD A C=377     overflow: all lamps
026  377
027  034               STORE A OUTPUT
030  200
031  000               HALT
032  344               JPD UNC START
033  004
034  000       T

How it works. “ADD A B” (004 001) adds register B to register A by B’s address, and updates the overflow-and-carry byte for A at 201. LOAD does not touch 201, so after “LOAD B T” the carry from the addition is still there to be tested: “SKP1 1 201” (312 201) skips the jump back when bit 1 — the carry — is set. The numbers are treated as unsigned, which is why the program tests carry rather than overflow; overflow (bit 0) would trip a step earlier, at 89 + 55 = 144, the first sum above 127.

The run. Successive presses of Start show:

001 (1)   001 (1)   002 (2)   003 (3)   005 (5)   010 (8)   015 (13)
025 (21)  042 (34)  067 (55)  131 (89)  220 (144) 351 (233) 377 (overflow)

The fourteenth press lights all eight lamps: 144 + 233 = 377 decimal does not fit in a byte.

Figure 4 — The Fibonacci numbers as the lamps show them, one per press of Start, ending with every lamp lit when the sum carries out of the byte.
Figure 4 — The Fibonacci numbers as the lamps show them, one per press of Start, ending with every lamp lit when the sum carries out of the byte. — Diagram drawn for this series from kenbak_programs.py (03-outputs/diagrams/diagrams_B.py)

10.5 Every Prime Below 256

This is the longest-running program in the volume, and the most satisfying to leave on a desk: it finds every prime number that fits in a byte, halting to show each one, and continues when Start is pressed. It uses trial division: for each number N from 2 upward, try the divisors D = 2, 3, 4 … while D × D is no larger than N. If none divides N, N is prime.

The KENBAK-1 has neither multiply nor divide, so both are done the long way. D × D is kept in a byte SQ and updated by adding, since (D + 1)² = D² + 2D + 1. “Does D divide N?” is answered by subtracting D from N repeatedly: if the remainder reaches exactly zero, D divides N; if it goes below zero first, it does not. “Below zero” here means a borrow, read from the carry bit at 201, because N and SQ are unsigned numbers up to 255.

Loc  Contents  Label   Symbolic
003  004               P: start at 004
004  023       START   LOAD A C=002
005  002
006  034               STORE A N
007  114
010  023       NEWN    LOAD A C=002     D = 2
011  002
012  034               STORE A D
013  115
014  023               LOAD A C=004     SQ = D x D = 4
015  004
016  034               STORE A SQ
017  116
020  024       TRY     LOAD A N
021  114
022  014               SUB A SQ
023  116
024  212               SKP0 1 201       borrow means N < D x D ...
025  201
026  344               JPD UNC PRIME    ... so N is prime
027  074
030  024               LOAD A N         A = N, less D until it goes below zero
031  114
032  014       MOD     SUB A D
033  115
034  312               SKP1 1 201
035  201
036  344               JPD UNC MODC
037  042
040  344               JPD UNC NEXTD    went below zero: D does not divide N
041  046
042  043       MODC    JPD A!=0 MOD
043  032
044  344               JPD UNC COMP     exactly zero: D divides N
045  101
046  024       NEXTD   LOAD A SQ        (D+1)^2 = D^2 + 2D + 1
047  116
050  004               ADD A D
051  115
052  004               ADD A D
053  115
054  003               ADD A C=001
055  001
056  034               STORE A SQ
057  116
060  024               LOAD A D
061  115
062  003               ADD A C=001
063  001
064  034               STORE A D
065  115
066  013               SUB A C=020      D reached 16: no divisor found
067  020
070  044               JPD A=0 PRIME
071  074
072  344               JPD UNC TRY
073  020
074  024       PRIME   LOAD A N
075  114
076  034               STORE A OUTPUT
077  200
100  000               HALT             show the prime; Start continues
101  024       COMP    LOAD A N
102  114
103  003               ADD A C=001
104  001
105  034               STORE A N
106  114
107  043               JPD A!=0 NEWN    until N wraps from 377 to 000
110  010
111  034               STORE A OUTPUT
112  200
113  000               HALT
114  000       N
115  000       D
116  000       SQ

One guard is needed. For the largest primes, D reaches 16, and 16 × 16 = 256 does not fit in a byte: SQ would wrap to 0 and the test would go wrong. Since a composite number below 256 always has a divisor smaller than 16, the program stops trying at D = 16 (020 octal) and declares N prime. Every prime 227 and above passes this way.

The run. The simulator presses Start after each halt and collects all 54 primes below 256, 2, 3, 5, 7, 11 … 241, 251, matching the list computed independently in Python. The last three, 239, 241 and 251, light ●● ●○● ●●● (357), ●● ●●○ ○○● (361) and ●● ●●● ○●● (373). After 251 the program carries on to 255, finds no more primes, lets N wrap to zero, darkens the lamps and halts. The whole run takes 145,994 instructions, which by the timing model is about three and a half minutes of computing on an original KENBAK-1, not counting the time spent reading each prime and pressing Start. Most of it is spent in the subtraction loop, dividing the larger numbers by 2 and 3 one subtraction at a time.

10.6 Hi-Lo: A Guessing Game

The last program plays a game. The KENBAK-1 thinks of a number from 0 to 127; the player guesses; the lamps say whether the guess was too high, too low or right. The player keys a guess on buttons 0–6 and presses button 7 to enter it, using the interlock of Volume 7.

A computer with no clock and no random-number generator still has one source of unpredictability: the player. While the player keys the first guess, the program spins the X register round as fast as it can; when button 7 is pressed, the low seven bits of X become the secret. On an original machine, by the timing model, X goes round about once every 1.3 seconds, while the first guess takes several seconds to key; the exact moment of the press decides the secret, and no player can time that to a fraction of a second.

Loc  Contents  Label   Symbolic
003  004               P: start at 004
004  223       START   LOAD X C=000
005  000
006  023               LOAD A C=000
007  000
010  034               STORE A INPUT
011  377
012  034               STORE A COUNT
013  116
014  034               STORE A OUTPUT
015  200
016  203       SPIN    ADD X C=001      the secret spins while the first guess is keyed
017  001
020  372               SKP1 7 INPUT
021  377
022  344               JPD UNC SPIN
023  016
024  234               STORE X SECRET
025  117
026  024               LOAD A SECRET
027  117
030  323               AND A C=177      0 to 127
031  177
032  034               STORE A SECRET
033  117
034  344               JPD UNC CHECK
035  042
036  372       GUESS   SKP1 7 INPUT     wait for button 7
037  377
040  344               JPD UNC GUESS
041  036
042  024       CHECK   LOAD A COUNT
043  116
044  003               ADD A C=001      count the guesses
045  001
046  034               STORE A COUNT
047  116
050  024               LOAD A INPUT
051  377
052  323               AND A C=177      drop the interlock bit
053  177
054  123               LOAD B C=000
055  000
056  134               STORE B INPUT    clear for the next guess
057  377
060  014               SUB A SECRET     guess - secret
061  117
062  044               JPD A=0 RIGHT
063  102
064  045               JPD A<0 LOW
065  074
066  023               LOAD A C=017     too high: the right-hand lamps
067  017
070  034               STORE A OUTPUT
071  200
072  344               JPD UNC GUESS
073  036
074  023       LOW     LOAD A C=360     too low: the left-hand lamps
075  360
076  034               STORE A OUTPUT
077  200
100  344               JPD UNC GUESS
101  036
102  023       RIGHT   LOAD A C=377
103  377
104  034               STORE A OUTPUT
105  200
106  000               HALT
107  024               LOAD A COUNT     Start shows the number of guesses
110  116
111  034               STORE A OUTPUT
112  200
113  000               HALT
114  344               JPD UNC START
115  004
116  000       COUNT
117  000       SECRET

How it works. The comparison is a subtraction followed by two jumps on A: zero means right, negative means the guess was low. Because both numbers are between 0 and 127, their difference always lies between −127 and +127 and its sign is reliable; that is why the secret and the guesses are kept to seven bits. The lamp signals point the way to go: the four left-hand lamps (360) mean “aim higher”, the four right-hand lamps (017) mean “aim lower”.

The run. In the simulator the program runs for 1,234 instructions while the “player” keys the first guess, which fixes the secret at 033 (27). The scripted player then halves the range each time, as a sensible human would:

guess 100 (64)   ○○ ○○● ●●●   too high
guess 037 (31)   ○○ ○○● ●●●   too high
guess 017 (15)   ●● ●●○ ○○○   too low
guess 027 (23)   ●● ●●○ ○○○   too low
guess 033 (27)   ●● ●●● ●●●   right
Start            ○○ ○○○ ●○●   005: five guesses

Halving always finds a number from 0 to 127 in seven guesses or fewer, so a score of seven is par.

Figure 5 — One game of Hi-Lo as the lamps played it in the simulator: two "too high", two "too low", a hit, and the guess count after Start.
Figure 5 — One game of Hi-Lo as the lamps played it in the simulator: two "too high", two "too low", a hit, and the guess count after Start. — Diagram drawn for this series from kenbak_programs.py (03-outputs/diagrams/diagrams_B.py)

10.7 Writing More

These five programs take between 25 and 76 bytes each — a tenth to a third of the KENBAK-1’s memory — and there is room for much more: a binary clock, built with a delay loop calibrated against a watch; a reaction timer that counts while waiting for a button; a sorting program, which the Laboratory Exercises set as Exercise 28; or a two-player game in which the buttons are split four and four. The method is the one Kenbak Corporation taught in 1971 and Volume 7 follows: write it symbolically, assign locations from 004 with constants well away from the code, assemble it from the coding table, key it, read it back, and single-step it when it misbehaves. The simulator and its assembler in 03-outputs/tools/ accept programs in the notation used throughout this series, and will print the octal to key.


Back to the start — Volume 1: KENBAK-1 — The First Personal Computer. The series began with a blue box of lights and switches, a 1971 advertisement and a 1986 museum contest; with the hardware, the instruction set and a working replica now in hand, Volume 1’s account of what the KENBAK-1 was, and why it is called the first personal computer, reads differently.

Comments (0)

  1. Loading…

Comments are held for moderation — nothing appears until approved.