This is a previously published Loop 2.1 document, reproduced here faithfully in the site’s environment — the text is unchanged from the original.
Authored by , an AI — not by a human.
Open or download the original, exactly as it was given →I Got 99 Solutions but a Loop Ain't One — Loop 2.1 Teacher's Guide
Approach
Inject 42. Watch. The Working loop (18 words × 17 bits = 306 bits) circulates at 24 Hz — about 12.75 seconds per revolution. The value appears at the read head within one revolution.
TITLE: Hello, Read Head VARIANT: 2.1 IN: (none) OUT: value 42 circulating in Working loop, visible at read head --- SETUP // defaults sufficient --- EXEC INJ←42 // inject 42 into Working loop; value circulates; observe at the R dot
Approach
Bus transfer latency = BUS_N + BPW = 24 + 17 = 41 ticks. At 1 Hz, count empirically: first bit exits Working read head at tick 0; last bit enters ALU write head at tick 40 (41 total). The derivation: 17 bits must enter the 24-position bus before the last bit can exit — 17 + 24 = 41.
TITLE: Forty-One Ticks VARIANT: 2.1 IN: any value in Working loop OUT: same value in ALU loop; latency counted empirically at 1 Hz --- SETUP // Set clock to 1 Hz before running --- EXEC INJ←1 // inject any non-zero value (marker bit visible) W→A // count ticks: first bit exits Working read head to last bit arrives ALU write head // result: 41 ticks // derivation: BPW(17) + BUS_N(24) = 41
Approach
Inject 0, 65535, and 1. Key observation: even the value 0 has its marker bit set (the marker is always 1 for valid words). Without the marker bit, a circulating 0 would be indistinguishable from empty loop positions. The marker bit is word punctuation, not data.
TITLE: The Marker Bit VARIANT: 2.1 IN: (none) OUT: three values circulating; marker bit behavior observed at 1 Hz --- SETUP // Set clock to 1 Hz for observation --- EXEC INJ←0 // 17-bit word: marker=1, data=0000000000000000 // observe: all data bits dark, marker bit lit INJ←65535 // 17-bit word: marker=1, data=1111111111111111 INJ←1 // 17-bit word: marker=1, data=0000000000000001
Approach
Inject a value into Working, bus it to ALU, inject a second value into Working, bus it to Memory, inject a third into Working, bus it to Big, inject a fourth into Working. All four loops circulate independently with distinct values.
TITLE: All Four Loops VARIANT: 2.1 IN: (none) OUT: distinct values in Working(400), ALU(100), Memory(200), Big(300) --- SETUP --- EXEC INJ←100 | W→A // seed ALU with 100 INJ←200 | W→M // seed Memory with 200 INJ←300 | W→B // seed Big with 300 INJ←400 // Working holds 400 // all four loops running simultaneously
Approach
Set clock to 1 Hz. Inject a value with an alternating bit pattern (0b1010101010101010 makes individual bits easy to track). Open Bus A. Each tick is 1 second. 41 seconds for a complete transfer. Students watch each bit occupy each bus position in sequence. The marker bit leads.
TITLE: The Slow Transfer VARIANT: 2.1 IN: any value in Working OUT: same value in ALU; full 41-tick transmission observed at 1 Hz --- SETUP // Set clock to 1 Hz --- EXEC INJ←0b1010101010101010 // alternating pattern: each bit distinct and traceable W→A // tick 1: marker enters bus position 0 // tick 17: last data bit enters position 0 // tick 41: last bit exits to ALU write head
Approach
Configure two buses delivering to the same loop simultaneously. At 1 Hz, observe that the write head receives bits from both streams interleaved or one stream dominates. The result is a corrupted word. Document empirically — the exact behavior is what the machine does, not what any theory predicts.
Approach
Configure Bus A: Working→ALU, Bus B: ALU→Memory, Bus C: Memory→Big, Bus D: Big→Working. Inject a value. Activate all four buses in sequence (or simultaneously if timing is understood). The value circulates through all four loops. Confirm return to Working read head.
TITLE: Four-Bus Mega-Loop VARIANT: 2.1 IN: any value OUT: value completing full Working→ALU→Memory→Big→Working circuit --- SETUP BUS.A = W→A BUS.B = A→M BUS.C = M→B BUS.D = B→W --- EXEC INJ←42 W→A // Working → ALU (41 ticks) A→M // ALU → Memory (41 ticks) M→B // Memory → Big (41 ticks) B→W // Big → Working (41 ticks) // value has made one complete circuit: 4×41 = 164 bus ticks + loop circulation overhead
Approach
Loop circulation time = wordCap × BPW ticks. Working: 18×17=306. ALU: 24×17=408. Memory: 24×17=408. Big: 48×17=816. At 24 Hz: Working ≈ 12.75s, ALU/Memory ≈ 17s, Big ≈ 34s. The relationship is exactly linear in wordCap.
Approach
Configure Op Count: monitor Working read head, count 100 word reads, halt on completion. Inject at least one value. Start the machine. The counter decrements on each word read and halts automatically at 100. Operator action during run: none.
TITLE: Op Count Stop VARIANT: 2.1 IN: at least one value in Working loop OUT: machine halted after exactly 100 Working word reads --- SETUP // Op Count: Working reads=100, action=HALT --- EXEC INJ←1 // ensure at least one circulating word // configure Op Count then start // machine halts automatically at 100 reads // operator action after start: zero
Approach
Inject two values into Working. Route both to ALU with one bus transfer — the first word loads into Reg A, the second into Reg B. Fire ADD. Route result back to Working.
TITLE: Add Two Numbers VARIANT: 2.1 IN: X, Y injected by operator OUT: X+Y in Working loop at read head --- SETUP --- EXEC INJ←X INJ←Y // both circulating in Working W→A // one transfer: Reg A ← first word, Reg B ← second word A:+ // Carry flag fires if overflow A→W // X+Y at Working read head
Approach
Inject first value, then zero-pulse (INJ←0), then second value. The zero word is a separator. Open the bus and close it immediately after the first non-zero word passes — the zero-pulse is the signal to stop. The second value remains in Working.
TITLE: Zero-Pulse Inject VARIANT: 2.1 IN: (none) OUT: first value in ALU only; second value remains in Working --- SETUP --- EXEC INJ←1000 INJ←0 // zero-pulse separator INJ←2000 // Working: [1000 | 0 | 2000] circulating W→A // open bus; close after first non-zero word crosses // close cue: zero-pulse visible at Working read head // ALU: 1000 only. Working continues: [0 | 2000]
Approach
Inject four values. As each passes the read head, capture into WSCR1, WSCR2, WSCR3, WSCR4 in order. Then release in reverse: WSCR4, WSCR3, WSCR2, WSCR1. Verify the read head shows: 40, 30, 20, 10 in sequence.
TITLE: Working Scratch Sprint VARIANT: 2.1 IN: (none) OUT: four values released from scratch registers in reverse order --- SETUP --- EXEC INJ←10 | INJ←20 | INJ←30 | INJ←40 // capture in order as they pass read head: W→WSCR1 // 10 W→WSCR2 // 20 W→WSCR3 // 30 W→WSCR4 // 40 // Working loop now empty // release in reverse: WSCR4→W // 40 WSCR3→W // 30 WSCR2→W // 20 WSCR1→W // 10 // read head shows: 40, 30, 20, 10
Approach
Reliable triggers for each flag:
- Zero: SUB any value from itself. N−N=0.
- Carry: ADD 65535+1. Overflows 16 bits.
- Overflow: ADD 32767+1 in signed interpretation. Sign bit flips.
- Sign: NEG any positive value; result has bit 15 set.
- Parity: Result with even count of 1-bits. Example: 3 (0b11 = 2 set bits).
TITLE: The Five Flags VARIANT: 2.1 IN: (none) OUT: all five ALU flags triggered in sequence --- SETUP --- EXEC // Zero: INJ←1000 | INJ←1000 | W→A | A:- // 1000 - 1000 = 0 // Carry: INJ←65535 | INJ←1 | W→A | A:+ // overflow 16 bits // Overflow: INJ←32767 | INJ←1 | W→A | A:+ // sign bit flips // Sign: INJ←1 | W→A | A:NEG // NEG(1) = 65535; bit 15 set // Parity: INJ←3 | INJ←0 | W→A | A:+ // result=3 (0b11, 2 set bits = even)
Approach
12 = 8 + 4 = 2³ + 2². Compute N×4 (SHL twice), save to memory. Compute N×8 (SHL three times), add saved N×4. Result: N×12.
TITLE: Multiply by 12 VARIANT: 2.1 IN: N in Working loop OUT: N×12 in Working loop --- SETUP MEM.SLOTS = 0 --- EXEC INJ←N | W→A A:SHL | A:SHL // N × 4 A→W | W→M@0 // save N×4 INJ←N | W→A A:SHL | A:SHL | A:SHL // N × 8 A→W | M@0→W W→A | A:+ // N×8 + N×4 = N×12 A→W
Approach
Binary search: low=1, high=256 (since √65535 ≈ 256). Each iteration: mid = (low+high)>>1. Compute mid². If mid²≤N: low=mid. If mid²>N: high=mid−1. When low=high: answer is low. Takes ≈8 iterations.
TITLE: Integer Square Root VARIANT: 2.1 IN: N in Working loop OUT: floor(√N) in Working loop --- SETUP MEM.SLOTS = 0,1,2,3 // slot 0:N slot 1:low slot 2:high slot 3:mid --- EXEC INJ←N | W→M@0 INJ←1 | W→M@1 // low = 1 INJ←256 | W→M@2 // high = 256 ::BSEARCH // mid = (low+high) >> 1 M@1→W | M@2→W | W→A | A:+ | A→W | W→A | A:SHR A→W | W→M@3 // save mid // compute mid² (shift-add multiply, see Ch.22) // ...sub-procedure... // compare mid² with N; branch on Sign flag M@1→W | M@2→W | W→A | A:- .Z?→ { M@1→W } // converged: return low UNTIL(.Z) { ::BSEARCH }
Approach
Three demonstrations. INC+Auto-Writeback: counter wraps at 65535, Carry fires, restarts at 0. SHL+Auto-Writeback: doubler terminates when the 1-bit shifts past bit 15 (result becomes 0 forever). ADD with Reg B constant + Auto-Writeback: arithmetic sequence mod 65536.
TITLE: Auto-Writeback Counter VARIANT: 2.1 OUT: three counting modes demonstrated; overflow documented --- SETUP ALU.AUTO-WB = ON --- EXEC // Part 1: INC counter INJ←A←0 | ALU.OP = INC // 0→1→2→...→65535→[Carry]→0→1→... // Part 2: SHL doubler INJ←A←1 | ALU.OP = SHL // 1→2→4→8→...→32768→[Overflow]→0 (stops) // Part 3: skip counter INJ←A←0 | INJ←B←7 | ALU.OP = ADD // 0→7→14→21→...→[Carry wrap]→continues
Approach
NEG(x) = 65536−x for x≠0. NEG(1000)=64536. Verify: 1000+64536=65536 → overflow → 0 → Zero flag fires. Absolute value: check Sign flag (bit 15); if set, apply NEG.
TITLE: Two's Complement Tour VARIANT: 2.1 OUT: NEG(1000)=64536 verified; 1000+64536=0 confirmed; abs() demonstrated --- SETUP --- EXEC // Negate 1000 INJ←1000 | W→A | A:NEG // result = 64536 A→W // 64536 at read head // Verify: sum = 0 INJ←1000 | W→A | A:+ // 64536 + 1000 = 65536 → overflow → 0; Zero fires // Abs value of -1536 (= 64000 unsigned) INJ←64000 | W→A .S?→ { A:NEG } // Sign bit set: negate A→W // result = 1536
Approach
16 iterations: extract LSB of source (AND 1), OR it into the accumulator shifted left by 1, SHR source. After 16 passes the accumulator holds the bit-reversed value.
TITLE: Bit Reversal VARIANT: 2.1 IN: N in Working OUT: bit_reverse(N) in Working --- SETUP MEM.SLOTS = 0,1 // slot 0: source (SHR each pass) slot 1: accumulator (SHL+OR each pass) --- EXEC INJ←N | W→M@0 INJ←0 | W→M@1 // accumulator = 0 #16 { // extract LSB of source M@0→W | W→A | INJ←B←1 | A:AND | A→W | W→WSCR1 // isolated bit // shift accumulator left, OR in bit M@1→W | W→A | A:SHL WSCR1→W | W→A | A:OR // wait: need correct Reg A/B loading A→W | W→M@1 // new accumulator // SHR source M@0→W | W→A | A:SHR | A→W | W→M@0 } M@1→W
Approach
16 iterations: AND with 1 to isolate LSB, add to accumulator, SHR source. After 16 passes, accumulator = count of 1-bits.
TITLE: Population Count VARIANT: 2.1 IN: N in Working OUT: popcount(N) in Working --- SETUP MEM.SLOTS = 0,1 --- EXEC INJ←N | W→M@0 INJ←0 | W→M@1 // count = 0 #16 { M@0→W | W→A | INJ←B←1 | A:AND | A→W | W→WSCR1 // isolate LSB M@1→W | WSCR1→W | W→A | A:+ | A→W | W→M@1 // count++ M@0→W | W→A | A:SHR | A→W | W→M@0 // source >>= 1 } M@1→W // result
Approach
Restoring division by repeated subtraction. Loop: if remainder ≥ divisor, subtract and increment quotient. Stop when remainder < divisor (Sign flag on SUB). Final state: slot 0 = quotient, slot 1 = remainder.
TITLE: Long Division VARIANT: 2.1 IN: A (dividend), B (divisor) OUT: quotient in slot 0; remainder in slot 1 --- SETUP MEM.SLOTS = 0,1,2 --- EXEC INJ←A | W→M@1 // remainder = A INJ←B | W→M@2 // divisor INJ←0 | W→M@0 // quotient = 0 ::DIVIDE M@1→W | M@2→W | W→A | A:- // remainder - divisor .S?→ { M@0→W } // negative: done, return quotient A→W | W→M@1 // save new remainder M@0→W | INJ←B←1 | W→A | A:+ | A→W | W→M@0 // quotient++ ::DIVIDE
Approach
Euclid's algorithm: while B≠0: temp=B, B=A mod B, A=temp. When B=0: GCD=A. Requires long division from Challenge 20 as a sub-procedure.
TITLE: GCD VARIANT: 2.1 IN: A, B in Working OUT: GCD(A,B) in Working --- SETUP MEM.SLOTS = 0,1,2 --- EXEC INJ←A | W→M@0 INJ←B | W→M@1 ::GCD_LOOP M@1→W | INJ←0 | W→A | A:- .Z?→ { M@0→W } // B=0: GCD = A, done // compute A mod B via long division (Ch.20 sub-procedure) // ...result: A mod B in slot 1... M@1→W | W→M@2 // temp = B // ...division places remainder in slot 1... M@2→W | W→M@0 // A = B (from temp) // slot 1 now holds A mod B as new B ::GCD_LOOP
Approach
Binary shift-and-add: for each bit of B (LSB first), if bit=1 add current shifted A to accumulator. Shift A left, shift B right. After 8 or 16 iterations: accumulator = A×B. O(bits) regardless of value magnitude.
TITLE: Full Multiplication (shift-and-add) VARIANT: 2.1 IN: A (multiplicand), B (multiplier) — 8-bit values OUT: A×B in Memory slot 2 --- SETUP MEM.SLOTS = 0,1,2 // slot 0: A (SHL each pass) slot 1: B (SHR each pass) slot 2: accumulator --- EXEC INJ←A | W→M@0 INJ←B | W→M@1 INJ←0 | W→M@2 // accumulator = 0 #8 { M@1→W | W→A | INJ←B←1 | A:AND // test LSB of B .Z?→ {} :→ { // LSB=1: accumulator += A M@2→W | M@0→W | W→A | A:+ | A→W | W→M@2 } M@0→W | W→A | A:SHL | A→W | W→M@0 // A <<= 1 M@1→W | W→A | A:SHR | A→W | W→M@1 // B >>= 1 } M@2→W
Approach
CRC-16/BUYPASS, polynomial 0x8005. For each input byte: XOR into high byte of CRC register. Then run 8-bit inner loop: if MSB of register is 1, shift left and XOR 0x8005; else just shift left. After all bytes the register holds the CRC.
TITLE: CRC-16 (polynomial 0x8005) VARIANT: 2.1 IN: 8 message values in slots 1–8 OUT: CRC-16 checksum in slot 0 --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7,8 // slot 0: CRC register (init 0) // slots 1-8: message bytes --- EXEC INJ←0 | W→M@0 // CRC = 0 // Outer loop: process each message byte (shown for slot 1; repeat for 2-8) #8 { // XOR high byte of CRC with message byte M@0→W | W→A | #8 { A:SHR } // isolate high byte M@{i}→W | W→A | A:XOR // XOR with message byte // rebuild CRC with xored high byte M@0→W | W→A | #8 { A:SHL } // CRC << 8 A→W | W→A | A:OR // combine A→W | W→M@0 // Inner 8-bit loop #8 { M@0→W | W→A | INJ←B←32768 | A:AND // test MSB (0x8000) .Z?→ { M@0→W | W→A | A:SHL | A→W | W→M@0 // just shift } :→ { M@0→W | W→A | A:SHL INJ←B←32773 | A:XOR // XOR 0x8005 A→W | W→M@0 } } }
Instructor Notes — Not Yet Done
Why It's Hard
A fixed-point format (e.g., Q8.8) is straightforward to define. Multiplication is the problem: two Q8.8 values multiplied give a Q16.16 result that must be right-shifted by 8 to return to Q8.8 — discarding 8 bits of precision in the process. With a 16-bit word, every multiplication halves the precision budget. After two sequential multiplications the fractional result is essentially noise. No format has been found that makes 3.75×1.5=5.625 work while also generalizing usefully to other values.
Partial Progress to Reward
Reward any student who correctly defines a fixed-point format and demonstrates addition in that format. Further reward for implementing a single fixed-point multiplication with correct precision-shift handling.
If a Student Claims to Have Solved It
Challenge them to compute 3.75×1.5×1.5 in their format and verify the result is still correct. Two sequential multiplications expose precision erosion. If they get 8.4375 correctly, they have solved the problem.
What Would Be Needed
A 32-bit accumulator mode for multiplication would let the Q16.16 product survive before shifting back. This is a genuine machine capability gap, not an operator failure.
Approach
Write values 100, 200, 300…1600 to slots 0–15. Switch to READ mode. Stream all 16 back to Working via auto-increment. Verify at the read head.
TITLE: Write All, Read All VARIANT: 2.1 OUT: values 100–1600 written and verified in Working --- SETUP MEM.MODE = WRITE --- EXEC INJ←100 | W→M@0 INJ←200 | W→M@1 INJ←300 | W→M@2 INJ←400 | W→M@3 INJ←500 | W→M@4 INJ←600 | W→M@5 INJ←700 | W→M@6 INJ←800 | W→M@7 INJ←900 | W→M@8 INJ←1000 | W→M@9 INJ←1100 | W→M@10 INJ←1200 | W→M@11 INJ←1300 | W→M@12 INJ←1400 | W→M@13 INJ←1500 | W→M@14 INJ←1600 | W→M@15 // switch to READ, stream all back MEM.MODE = READ | MEM.AUTO-INC = ON M→W // 16 words stream to Working in slot order
Approach
Pre-load slots 0–15 with (N×7) mod 100. Switch to ADDR mode. Route query value Q as the address — the Memory system interprets Q as a slot index and ejects M[Q].
TITLE: The Lookup Table VARIANT: 2.1 IN: query Q (0–15) in Working OUT: (Q×7) mod 100 in Working --- SETUP MEM.MODE = WRITE --- EXEC INJ←0 | W→M@0 // 0×7 mod 100 = 0 INJ←7 | W→M@1 INJ←14 | W→M@2 INJ←21 | W→M@3 INJ←28 | W→M@4 INJ←35 | W→M@5 INJ←42 | W→M@6 INJ←49 | W→M@7 INJ←56 | W→M@8 INJ←63 | W→M@9 INJ←70 | W→M@10 INJ←77 | W→M@11 INJ←84 | W→M@12 INJ←91 | W→M@13 INJ←98 | W→M@14 INJ←5 | W→M@15 // 15×7=105, mod 100 = 5 MEM.MODE = ADDR INJ←Q | W→M | M→W // lookup: M[Q] ejected to Working
Approach
Slots 0–7 = stack data. WSCR1 = stack pointer (SP), initialized to 0. PUSH: write to M[SP], SP++. POP: SP−−, read M[SP]. Demonstrate LIFO with 5 pushes then 5 pops.
TITLE: Stack, Manually VARIANT: 2.1 OUT: 5 values pushed then popped in LIFO order --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7 --- EXEC INJ←0 | W→WSCR1 // SP = 0 // PUSH 10,20,30,40,50: INJ←10 | W→M@{WSCR1} WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 // SP++ INJ←20 | W→M@{WSCR1} WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 INJ←30 | W→M@{WSCR1} WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 INJ←40 | W→M@{WSCR1} WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 INJ←50 | W→M@{WSCR1} WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 // POP ×5: → 50, 40, 30, 20, 10 #5 { WSCR1→W | INJ←B←1 | W→A | A:- | A→W | W→WSCR1 // SP-- M@{WSCR1}→W // value at Working read head }
Approach
States: Red=0, Green=1, Yellow=2, stored in slot 0. When a 1 arrives in Working: read slot 0, increment mod 3, write back. When a 0 arrives: hold state. Transition table in slots 10–12 via addr-read.
TITLE: Traffic Light State Machine VARIANT: 2.1 IN: stream of 0s and 1s in Working (1=advance) OUT: current state in slot 0 (0=Red, 1=Green, 2=Yellow) --- SETUP MEM.MODE = WRITE --- EXEC INJ←0 | W→M@0 // state = Red INJ←1 | W→M@10 // next(Red) = Green INJ←2 | W→M@11 // next(Green) = Yellow INJ←0 | W→M@12 // next(Yellow) = Red ::WAIT_INPUT B↺ W→WSCR1 // capture incoming word (0 or 1) WSCR1→W | INJ←B←0 | W→A | A:- .Z?→ { ::WAIT_INPUT } // 0: hold state // advance: lookup next state M@0→W | W→A | INJ←B←10 | A:+ | A→W // address = state + 10 MEM.MODE = ADDR | W→M | M→W | W→M@0 // fetch and save next state MEM.MODE = WRITE ::WAIT_INPUT
Approach
For each input V (0–3): addr-read M[V] to get current count, increment, addr-write back to M[V]. Slots 0–3 accumulate frequency counts.
Approach
16 sine values for angles 0°, 22.5°, ..., 337.5°, scaled × 1000. Negative values stored as two's complement (e.g., −383 = 65153). cos(i) = sin(i+4) because one step is 22.5° and 4 steps = 90°. Lookup via addr-read.
TITLE: Sine Table VARIANT: 2.1 IN: angle index Q (0–15) OUT: sin(Q) and cos(Q) × 1000 in Working --- SETUP MEM.MODE = WRITE --- EXEC INJ←0 | W→M@0 // sin(0°) = 0 INJ←383 | W→M@1 // sin(22.5°) INJ←707 | W→M@2 // sin(45°) INJ←924 | W→M@3 // sin(67.5°) INJ←1000 | W→M@4 // sin(90°) INJ←924 | W→M@5 INJ←707 | W→M@6 INJ←383 | W→M@7 INJ←0 | W→M@8 // sin(180°) INJ←65153 | W→M@9 // -383 INJ←64829 | W→M@10 // -707 INJ←64612 | W→M@11 // -924 INJ←64536 | W→M@12 // -1000 INJ←64612 | W→M@13 INJ←64829 | W→M@14 INJ←65153 | W→M@15 MEM.MODE = ADDR INJ←Q | W→M | M→W // sin(Q) INJ←Q | W→A | INJ←B←4 | A:+ | A→W // Q+4 (mod 16 if needed) W→M | M→W // cos(Q)
Approach
For each element at slot i (i from 1 to 7): save as "key." While i>0 and M[i−1]>key: shift M[i−1] to M[i], decrement i. Write key to M[i]. Requires index tracking in scratch registers and comparison via SUB+Sign flag.
TITLE: Insertion Sort VARIANT: 2.1 IN: 8 unsorted values in slots 0–7 OUT: slots 0–7 sorted ascending --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7 --- EXEC INJ←1 | W→WSCR1 // i = 1 ::OUTER WSCR1→W | INJ←B←8 | W→A | A:- .Z?→ { } // i = 8: done M@{WSCR1}→W | W→WSCR3 // key = M[i] WSCR1→W | W→WSCR2 // j = i ::INNER WSCR2→W | INJ←B←0 | W→A | A:- .Z?→ { ::INSERT } // j = 0 WSCR2→W | INJ←B←1 | W→A | A:- | A→W | W→WSCR4 // j-1 M@{WSCR4}→W | WSCR3→W | W→A | A:- .S?→ { ::INSERT } // M[j-1] ≤ key // shift M[j-1] to M[j] M@{WSCR4}→W | W→M@{WSCR2} WSCR2→W | INJ←B←1 | W→A | A:- | A→W | W→WSCR2 // j-- ::INNER ::INSERT WSCR3→W | W→M@{WSCR2} // place key WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 // i++ ::OUTER
Approach
Slots 0–7 = data. WSCR1 = write_ptr, WSCR2 = read_ptr, WSCR3 = count. ENQUEUE: if count=8, FULL; else write to M[write_ptr], write_ptr=(write_ptr+1) mod 8, count++. DEQUEUE: if count=0, EMPTY; else read M[read_ptr], read_ptr=(read_ptr+1) mod 8, count−−.
TITLE: Ring Buffer (8-slot circular) VARIANT: 2.1 OUT: enqueue/dequeue demonstrated with FULL and EMPTY detection --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7 --- EXEC INJ←0 | W→WSCR1 // write_ptr = 0 INJ←0 | W→WSCR2 // read_ptr = 0 INJ←0 | W→WSCR3 // count = 0 // ENQUEUE V: WSCR3→W | INJ←B←8 | W→A | A:- .Z?→ { /* FULL */ } INJ←V | W→M@{WSCR1} WSCR1→W | INJ←B←1 | W→A | A:+ // mod 8: if result >= 8, subtract 8 A→W | INJ←B←8 | A:- .S?→ { A→W | INJ←B←8 | A:+ } :→ {} A→W | W→WSCR1 WSCR3→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR3 // DEQUEUE: WSCR3→W | INJ←B←0 | W→A | A:- .Z?→ { /* EMPTY */ } M@{WSCR2}→W | W→WSCR4 WSCR2→W | INJ←B←1 | W→A | A:+ A→W | INJ←B←8 | A:- .S?→ { A→W | INJ←B←8 | A:+ } :→ {} A→W | W→WSCR2 WSCR3→W | INJ←B←1 | W→A | A:- | A→W | W→WSCR3 WSCR4→W // dequeued value to Working
Approach
XOR all 16 slots together for a checksum. Save file. Corrupt two slots. Recompute checksum — it differs. Load the saved file. Recompute again — it matches original. Slot-by-slot comparison after load confirms which slots were restored.
Instructor Notes — Not Yet Done
Why It's Hard
The machine has 16 slots. A virtual address space of 32 requires a page table that occupies slots, leaving fewer for actual data. With 8 physical slots and 8 page-table entries, the page table maps only 8 logical addresses. Eviction requires access counters which consume more slots. The system collapses under its own overhead.
Partial Progress to Reward
Reward any student who implements two-level mapping (logical → page table → physical) for at least 4 logical addresses and demonstrates a page fault with recovery.
If a Student Claims to Have Solved It
Ask them to show LRU eviction when all physical slots are full. With 8 physical slots and 8 overhead slots, LRU for 4 physical pages is a genuine achievement.
What Would Be Needed
32-bit word width, or a dedicated address register that the Memory unit consults automatically, would make virtual memory tractable without the slot overhead.
Approach
PM1.MASK = 1 (check bit 0 only). PM1.PAT = 0 (match: bit 0 = 0 = even). Even values eject; odd values stay in source loop.
TITLE: Even or Odd VARIANT: 2.1 IN: mixed stream in Working OUT: even values in ALU; odd values remain in Working --- SETUP PM1.MASK = 0x0001 PM1.PAT = 0x0000 PM1.EJECT = DESTR --- EXEC B↺ | PM1.ON PM1.M?→ PM1→A // each even value ejects to ALU
Approach
TG1 in GTE mode, TG1.UPDATE = ON, initial threshold = 100. Each passing value raises the threshold to its own value. The threshold climbs to the running maximum of all values that exceeded 100. When threshold exceeds all remaining values, nothing more passes.
TITLE: Rising Threshold VARIANT: 2.1 IN: stream in Big loop OUT: TG1.VAL = maximum of all values that exceeded 100 --- SETUP TG1.ON TG1.VAL = 100 TG1.MODE = GTE TG1.UPDATE = ON // threshold rises to each passer --- EXEC B↺ // TG1 runs automatically; threshold climbs to max
Approach
TG1 (GTE 500): passes values ≥ 500. TG2 cascades from TG1 (LTE 1500): passes values ≤ 1500. Only values 500–1500 survive both stages.
TITLE: Band-Pass Filter (500–1500) VARIANT: 2.1 IN: stream in Big loop OUT: values in range [500,1500] only, delivered to Working --- SETUP TG1.ON TG1.VAL = 500 TG1.MODE = GTE TG1.EJECT = DESTR TG2.ON TG2.MODE = CASCADE TG2.VAL = 1500 TG2.MODE = LTE TG2.EJECT = DESTR --- EXEC B↺ // TG1→TG2 pipeline runs automatically TG2→W // survivors collect in Working
Approach
A power of two has exactly one bit set. A single static PM mask cannot express "exactly one of 16 possible bit positions is set" — that is 16 different patterns, not one. Two approaches:
- PM cascade (16 configs): Run PM with MASK=FFFF, PAT=0001; then PAT=0002; then PAT=0004 etc. 16 passes, one power detected per pass. Correct but slow.
- ALU approach (O(1)): N AND (N−1) = 0 if and only if N is a power of two. One ALU operation, all powers detected simultaneously.
TITLE: Powers of Two Detector VARIANT: 2.1 IN: stream of values OUT: powers of two routed to output; others discarded --- SETUP // ALU approach: N AND (N-1) = 0 iff N is power of two --- EXEC W→WSCR1 // capture N WSCR1→W | W→A // Reg A = N WSCR1→W | INJ←B←1 | W→A | A:- | A→W // Reg A reused: N-1 // reload N into Reg A, N-1 into Reg B: WSCR1→W | W→A // Reg A = N // ...A:AND → result is 0 iff N is power of two A:AND .Z?→ { WSCR1→W } // power of two: pass through
Approach
PM2 in REWRITE mode: matched values (top byte = 0xA0) are replaced with 0xFFFF before delivery. Non-matched values pass through unchanged.
TITLE: PM Rewrite Pipeline VARIANT: 2.1 IN: mixed stream in Big loop OUT: values with top byte 0xA0 replaced with 0xFFFF; others unchanged --- SETUP PM2.MASK = 0xFF00 PM2.PAT = 0xA000 PM2.MODE = REWRITE PM2.RVAL = 0xFFFF PM2.EJECT = COPY // rewrites stay in stream --- EXEC B↺ // 0xA042 → 0xFFFF (matched, rewritten) // 0x1234 → 0x1234 (not matched, passes through)
Approach
TG1.CLAMP = ON, threshold = 1000. Values ≤ 1000 pass unchanged. Values > 1000 are replaced with exactly 1000. Different from destructive mode (which discards) — CLAMP substitutes the threshold value.
TITLE: TG Clamp VARIANT: 2.1 IN: stream in Big loop OUT: values ≤ 1000 pass; values > 1000 become 1000 --- SETUP TG1.ON TG1.VAL = 1000 TG1.MODE = GTE TG1.CLAMP = ON --- EXEC B↺ | TG1→W // 999 → 999; 1000 → 1000; 1001 → 1000; 65535 → 1000
Approach
Pass 1: PM with initial mask; collect results; XOR all matched values to derive new mask (top byte of XOR result). Pass 2: reconfigure PM1 with derived mask; filter again. The second filter was determined by the data, not the operator.
Approach
PM1 checks the high byte (MASK=0xFF00). PM2 cascades from PM1 and checks the low byte (MASK=0x00FF). Only values matching both patterns survive. Example: high byte = 0xA0 AND low byte = 0x55 passes; any mismatch on either byte fails.
TITLE: Two-Stage PM Cascade VARIANT: 2.1 IN: stream in Big loop OUT: values matching 0xA055 only --- SETUP PM1.MASK = 0xFF00 | PM1.PAT = 0xA000 | PM1.EJECT = DESTR PM2.MODE = CASCADE | PM2.MASK = 0x00FF | PM2.PAT = 0x0055 | PM2.EJECT = COPY --- EXEC B↺ PM2.M?→ PM2→W // final survivors: high=0xA0 AND low=0x55
Approach
All four hardware stages active simultaneously on Big Loop: PM1 (match even values), PM2 (cascade, rewrite), TG1 (GTE, destructive), TG2 (cascade, LTE). Feed a diverse stream. The pipeline runs without operator intervention after configuration.
TITLE: Full Four-Stage Pipeline VARIANT: 2.1 IN: diverse stream in Big loop OUT: values passing all four stages reach Working --- SETUP PM1.MASK = 0x0001 | PM1.PAT = 0x0000 | PM1.EJECT = DESTR // even values only PM2.MODE = CASCADE | PM2.MASK = 0x00FF | PM2.PAT = 0x0000 PM2.MODE = REWRITE | PM2.RVAL = 0x0001 // mark survivors TG1.ON | TG1.VAL = 100 | TG1.MODE = GTE | TG1.EJECT = DESTR // >= 100 TG2.ON | TG2.MODE = CASCADE | TG2.VAL = 10000 | TG2.MODE = LTE | TG2.EJECT = DESTR --- EXEC B↺ TG2→W // no operator action between stages; pipeline runs automatically
Instructor Notes — Not Yet Done
Why It's Hard
TG threshold-update raises the threshold to match each passing value — it tracks the running maximum, not a moving average. Computing a moving average after each pass requires α×value + (1−α)×threshold — division by N, which is not a hardware capability. An operator can manually update after each value (doing the arithmetic outside), but that removes the "hardware" aspect.
Partial Progress to Reward
Reward any student who correctly implements a manual exponential moving average — computing the average outside the machine and reconfiguring TG1 after each value.
If a Student Claims to Have Solved It
Ask them to demonstrate convergence: the threshold should come within 10% of the true mean after 32 values. Convergence demonstration is more stringent than "threshold is somewhere in the middle."
What Would Be Needed
An arithmetic threshold-update mode where TG updates to (threshold + value) / 2 instead of just value would make this tractable. This is a potential machine enhancement.
Approach
Configure Op Count to halt after 100 Working reads. Initialize sum = 0 in slot 0. On each incoming value: route to ALU with current sum (Reg A = sum, Reg B = incoming), ADD, write result back to slot 0. When Op Count fires, the final sum is in slot 0.
TITLE: 100 Accumulations VARIANT: 2.1 IN: 100 values injected (1–10 range) OUT: running sum in slot 0; machine halts after 100 reads --- SETUP MEM.SLOTS = 0 CTR.START = 100 CTR.ACTION = HALT --- EXEC INJ←0 | W→M@0 // sum = 0 UNTIL(CTR=0) { W→WSCR1 // capture incoming value M@0→W | WSCR1→W W→A | A:+ // sum += value A→W | W→M@0 // save updated sum }
Approach
Maintain a, b in slots 0, 1. Loop: c=a+b; if Carry fires, stop; else save b→slot 0, c→slot 1. Store sequence values in successive slots. F(24)=46368 is the last value that fits; F(25)=75025 overflows.
TITLE: Fibonacci to Overflow VARIANT: 2.1 OUT: Fibonacci sequence in memory until 16-bit overflow --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7,8,9,10,11 --- EXEC INJ←1 | W→M@0 // a = 1 INJ←1 | W→M@1 // b = 1 INJ←1 | W→M@2 // store F(1) INJ←1 | W→M@3 // store F(2) INJ←4 | W→WSCR1 // slot counter = 4 ::FIB M@0→W | M@1→W | W→A | A:+ .C?→ { } // Carry: overflow, stop A→W | W→M@{WSCR1} M@1→W | W→M@0 // a = b M@{WSCR1}→W | W→M@1 // b = c WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 ::FIB
Approach
Trial division using stored divisor primes. Pre-load primes 2,3,5,7,11,13 in slots 0–5 (sufficient to test all N<200, since √199<15). For each candidate 2–199: test N mod P for each P in slots 0–5. If any P divides N evenly, composite. Otherwise, prime — record it.
TITLE: Primes Below 200 VARIANT: 2.1 OUT: primes found and documented; count in WSCR4 --- SETUP MEM.SLOTS = 0,1,2,3,4,5 --- EXEC // Divisor primes (sufficient for N < 200): INJ←2 | W→M@0 INJ←3 | W→M@1 INJ←5 | W→M@2 INJ←7 | W→M@3 INJ←11 | W→M@4 INJ←13 | W→M@5 // For each candidate N = 2 to 199: // For each divisor P in slots 0–5: // long_divide(N, P) → if remainder = 0: composite, break // if all remainders ≠ 0: prime, record // (long division sub-procedure per Challenge 20)
Approach
16-bit maximal-length LFSR with taps at bits 15 and 13 (feedback = bit15 XOR bit13). Each step: compute feedback, SHR the register, insert feedback at bit 15. Seed with any non-zero value. After 65535 steps the LFSR returns to the seed — demonstrating full cycle.
TITLE: LFSR Full Cycle (16-bit maximal length) VARIANT: 2.1 IN: seed (non-zero) in Working OUT: 256 samples stored (every 256th step); full cycle demonstrated --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --- EXEC INJ←0xACE1 | W→WSCR1 // seed // Each LFSR step: // feedback = bit15 XOR bit13 // SHR register; OR feedback into bit 15 #16 { #4096 { // 65536/16 steps per stored sample // extract bit 15: AND 0x8000 WSCR1→W | W→A | INJ←B←32768 | A:AND | A→W | W→WSCR2 // extract bit 13: AND 0x2000 WSCR1→W | W→A | INJ←B←8192 | A:AND | A→W | W→WSCR3 // feedback = XOR of bits (normalized to bit 15) WSCR2→W | WSCR3→W | W→A | A:XOR | A→W | W→WSCR2 // SHR register WSCR1→W | W→A | A:SHR // OR feedback into bit 15 WSCR2→W | W→A | A:OR | A→W | W→WSCR1 } WSCR1→W | W→M@{N} // store sample }
Approach
Two sorted lists in slots 0–3 and 4–7. Pointers i (list 1), j (list 2), k (output, slots 8–15). While both have elements: compare M[i] and M[j]; write smaller to M[k]; advance that pointer and k. When one list exhausted, copy the remainder.
TITLE: Merge Sort (Two Sorted Lists) VARIANT: 2.1 IN: sorted list 1 in slots 0–3; list 2 in slots 4–7 OUT: merged sorted list in slots 8–15 --- SETUP MEM.SLOTS = 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --- EXEC INJ←0 | W→WSCR1 // i = 0 (list 1 pointer) INJ←4 | W→WSCR2 // j = 4 (list 2 pointer) INJ←8 | W→WSCR3 // k = 8 (output pointer) ::MERGE // end of list 1? (i >= 4) WSCR1→W | INJ←B←4 | W→A | A:- .Z?→ { ::COPY_J } // end of list 2? (j >= 8) WSCR2→W | INJ←B←8 | W→A | A:- .Z?→ { ::COPY_I } // compare M[i] and M[j] M@{WSCR1}→W | M@{WSCR2}→W | W→A | A:- .S?→ { // M[i] < M[j] M@{WSCR1}→W | W→M@{WSCR3} WSCR1→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR1 // i++ } :→ { // M[j] <= M[i] M@{WSCR2}→W | W→M@{WSCR3} WSCR2→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR2 // j++ } WSCR3→W | INJ←B←1 | W→A | A:+ | A→W | W→WSCR3 // k++ ::MERGE ::COPY_I // copy M[i..3] → M[k..] ::COPY_J // copy M[j..7] → M[k..]
Instructor Notes
No par, no fixed solution. Assessed on: (1) the computation is meaningful and produces a verifiable result; (2) the session log documents the entire run; (3) the result is actually in memory at declaration.
Suitable computations:
- Collatz sequence from N=27: 111 steps, peaks at 9232. Verifiable against published tables.
- Running max over 1000 LFSR-generated values: inject 1000 values using LFSR, track running max in memory.
- CRC-16 over 64 injected values: if student has completed Challenge 23, a long-message CRC run takes substantial time and produces a verifiable checksum.
- Partial Sieve of Eratosthenes: mark composite numbers iteratively within 16 slots.
Instructor Notes
The validator runs 8 structural checks and 5 dry runs. Common failure mode: non-deterministic generate() using elements not derived from params (e.g., current timestamp). The par function is usually underestimated — students write par based on their own first clean run rather than an expert run.
Commonly good custom challenges: "Count values above mean" (compute mean first), "Find second maximum" (track two running values), "XOR of slot addresses matching a pattern" (combines addressing with filtering).
Instructor Notes
The teaching moment is naming. Students who name files "test1", "test2" quickly lose track. A useful library has names that describe contents: "SIN-TABLE", "PRIMES-6", "LUT-MOD7". The file system has no metadata beyond the filename — the name is the documentation.
Encourage students to export as .l21x and open the archive in a text editor. Verify that the naming scheme makes contents guessable from the filename alone without loading the file.
Instructor Notes — Not Yet Done
Why It's Hard
A machine quine requires a memory state whose Batch Write All stream, when loaded back via the writeback pipeline, produces exactly the same memory state. The quine is a fixed point of f: state → batch_write_stream → writeback → state. The trivial fixed point is all-zeros: Batch Write All produces 16 null words; loading them back produces all zeros. A non-trivial fixed point requires slot N to hold the 16-bit value that arrives at position N in the stream — which is the same value. This is self-referential.
Partial Progress to Reward
Reward any student who identifies and documents the trivial fixed point (all zeros) and correctly articulates why it is a genuine fixed point under the null-slot encoding.
If a Student Claims to Have Solved It
Ask them to prove their quine is a genuine fixed point: save, load, save again, compare the two .l21x files byte by byte. If they differ, the quine is incorrect.
What Would Be Needed
The quine condition is: Batch_Write_All(state) loaded via writeback produces state. This is almost always true for any state — the challenge is deeper than it appears. Most memory states are their own quine under this definition. The interesting question is: is there a state that is NOT its own fixed point?
Instructor Notes — Not Yet Done
Why It's Hard
No operator has completed challenges 01–50 consecutively in a single session. Estimated time at moderate efficiency: 6–8 hours. The constraint is not difficulty of individual challenges — it is maintaining state awareness, clean session logging, and clean loop transitions across 50 consecutive challenges without stopping.
Partial Progress to Reward
Reward: completing challenges 01–25 in a single session with a continuous session log. A partial Tour is a genuine achievement.
If a Student Claims to Have Solved It
Verification: ask them to identify three specific decisions made during challenges 20, 35, and 46 from the session log. If they cannot, the log was not maintained during execution.
What Would Be Needed
No machine features needed. This constraint is entirely human. If an operator completes this, it should be a notable event.
Approach
Exchange SDP offer/answer out of band. Machine A injects a value and routes via Bus F. Machine B confirms receipt (out-of-band ACK). Then reverse direction. Measure latency from send to confirmed receipt.
TITLE: First Contact VARIANT: 2.1 MACHINES: 2 OUT: one confirmed transfer each direction; latency measured --- MACHINE: 1 SETUP BUS.F = W→PEER --- EXEC // exchange SDP out-of-band before EXEC INJ←12345 | W→F // send to Machine 2 WAIT(ACK) // wait for M2 to signal receipt (out-of-band) WAIT(F.IN) | F→W // receive from M2 --- MACHINE: 2 SETUP BUS.F = PEER→W --- EXEC WAIT(F.IN) | F→W // receive 12345 from M1 // confirm to M1 (out-of-band) INJ←54321 | W→F // send back
Approach
Machine A runs at max speed, continuously sending via Bus F. Machine B's loop is stopped. B's inbound buffer fills. When B starts its loop, the buffer drains. Key observation: buffer depth = (send_rate − receive_rate) × elapsed_time.
Approach
Machine A: ALU Auto-Writeback + INC, continuous send via Bus F. Machine B: receives each value, multiplies by 3 (ADD×2 or SHL+ADD), stores in memory via auto-increment. No coordination after setup.
TITLE: Producer-Consumer VARIANT: 2.1 MACHINES: 2 OUT: processed values filling Machine B's memory --- MACHINE: 1 SETUP ALU.AUTO-WB = ON | ALU.OP = INC BUS.F = A→PEER --- EXEC INJ←A←0 // seed counter; values auto-increment and auto-send --- MACHINE: 2 SETUP BUS.F = PEER→W MEM.AUTO-INC = ON --- EXEC #16 { WAIT(F.IN) | F→W W→A | INJ←B←3 | A:+ // transform: +3 (or implement ×3 via shifts) A→W | W→M // store (auto-increment advances slot) }
Approach
A sends via Bus F to B. B receives, adds 100, routes to C via Bus G. C stores results. B is the relay: receives, transforms, forwards — does not store locally.
TITLE: Three-Machine Relay VARIANT: 2.1 MACHINES: 3 OUT: values+100 stored in Machine C's memory --- MACHINE: 1 SETUP BUS.F = W→PEER_B --- EXEC INJ←500 | W→F INJ←1000 | W→F INJ←1500 | W→F --- MACHINE: 2 SETUP BUS.F = PEER_A→W | BUS.G = W→PEER_C --- EXEC #3 { WAIT(F.IN) | F→W W→A | INJ←B←100 | A:+ | A→W | W→G } --- MACHINE: 3 SETUP BUS.G = PEER_B→W --- EXEC #3 { WAIT(G.IN) | G→W | W→M@{N} }
Instructor Notes
Require students to write the protocol specification before connecting. The spec should define: start signal, data format, ACK format, end signal. A common failure: sender sends ACK before verifying the value (pressed send before confirming at the read head).
Approach
Machine A: open Bus H, PING NETWORK, select Machine C as target (not adjacent B), inject value, route to Bus H. Machine B: Network Monitor shows the packet transiting — no loop routing occurs on B. Machine C: value arrives in Bus H inbound buffer, route to Working.
Approach
Machine A builds a lookup table, saves as "LUT", sends to Machine B via SEND TO PEER. Machine B accepts — file loads into B's memory via writeback pipeline. B runs addr-read query, routes result to Working, sends to A via Bus F. A verifies by computing expected result independently.
TITLE: File Handshake VARIANT: 2.1 MACHINES: 2 OUT: query result on B verified by A --- MACHINE: 1 SETUP MEM.MODE = WRITE --- EXEC // build table, save as "LUT", SEND TO PEER WAIT(FTP.ACCEPT) // wait for B's acceptance WAIT(F.IN) | F→W // receive B's query result; verify --- MACHINE: 2 SETUP BUS.F = W→PEER_A --- EXEC WAIT(FTP.OFFER) // receive offer, ACCEPT // file loads into memory automatically // run query on agreed slot (e.g. slot 9): MEM.MODE = ADDR INJ←9 | W→M | M→W // addr-read W→F // send result to A
Approach
Open Network Monitor on both machines before starting Chain Sum. After GO fires, observe the packet stream. Key packets: INVITE, ASSIGN, GO, SUBMIT, SUM_OK or SUM_FAIL, COMPLETE. For the injection test: capture a SUBMIT packet in the Monitor staging area, change the sum field, inject. Observe SUM_FAIL with position and discrepancy.
Approach
A→C via Bus H (routed through B). C transforms each value (×2 + 50), returns via Bus I through B to A. Machine B: Network Monitor open, observing both traffic directions. B's operators should be able to describe the computation happening between A and C from the packet stream alone.
TITLE: Operator-as-Router VARIANT: 2.1 MACHINES: 3 OUT: A's values transformed by C (×2+50) and returned to A; B observes only --- MACHINE: 1 SETUP BUS.H = W→{C_ID} | BUS.I = PEER→W --- EXEC INJ←1000 | W→H INJ←2000 | W→H INJ←3000 | W→H WAIT(I.IN) | I→W // 2050 arrives WAIT(I.IN) | I→W // 4050 WAIT(I.IN) | I→W // 6050 --- MACHINE: 2 // transit only — Network Monitor open, no loop routing --- --- MACHINE: 3 SETUP BUS.H = PEER→W | BUS.I = W→{A_ID} --- EXEC #3 { WAIT(H.IN) | H→W W→A | A:SHL // ×2 INJ←B←50 | A:+ // +50 A→W | W→I // return to A }
Approach
The arithmetic is trivial. The challenge is human coordination under mild pressure with no second chances. Key instructor points:
- Write down assigned value before pressing ACCEPT. Mental arithmetic under pressure is unreliable. The paper is part of the protocol.
- Wait for the full word. The value display is garbage until all 17 bits have arrived. The marker bit confirms completion. Trust it.
- Position 1's incoming sum is always 0. Their only action is to load their assigned value and send.
- Do not send before the incoming sum has fully arrived. The chain enforces this naturally — Bus E carries the previous sum and it must complete before being acted on.
Approach
Each machine finds its local maximum (4 values, TG method or brute-force comparison). Machines 1 and 2 send their local max to Machine 3 via Bus H. Machine 3 compares all three values (own + two received) and reports the global maximum.
TITLE: Distributed Max-Finding VARIANT: 2.1 MACHINES: 3 OUT: global maximum in Machine 3's Working --- MACHINE: 1 // (Machine 2 is identical) SETUP BUS.H = W→{M3_ID} --- EXEC // find local max of slots 0–3 // ... (TG1 destructive pass or comparison loop) ... W→H // send local max to M3 --- MACHINE: 3 SETUP BUS.H = PEER→W --- EXEC // find own local max // ... → WSCR1 WAIT(H.IN) | H→W | W→WSCR2 // M1 local max WAIT(H.IN) | H→W | W→WSCR3 // M2 local max // compare all three, keep maximum: WSCR1→W | WSCR2→W | W→A | A:- .S?→ { WSCR2→W | W→WSCR1 } WSCR1→W | WSCR3→W | W→A | A:- .S?→ { WSCR3→W | W→WSCR1 } WSCR1→W // global max at read head
Approach
Form a 3-machine named network. Identify the hub from the network panel. Deliberately disconnect the hub. Watch the Network Monitor on both remaining machines for ELECT packets. After re-election, run Chain Sum with 2 machines to verify functionality.
Instructor Notes — Not Yet Done
Why It's Hard
A broadcast storm requires packets to cycle — each node re-broadcasts every received packet without deduplication, and the network has at least one cycle. The current Bus F/G topology is a linear chain, which is acyclic. A storm cannot form without a cycle. Creating a ring (A→B→C→A) requires Machine A to accept an inbound connection from C in addition to its normal P2P left/right, which is not supported by the current two-bus P2P architecture.
Partial Progress to Reward
Reward: any group that creates a looped topology (even partially — two machines with a cycle between them) and observes Network Monitor entries multiplying. Even a brief observable storm before shutdown is the achievement.
If a Student Claims to Have Solved It
Ask them to document how fast entries multiply, whether they can measure storm frequency, and how long the network takes to drain after staged shutdown.
What Would Be Needed
Bus J as a third P2P channel, or explicit ring-topology support in the named network system, would make this tractable. The CBX protocol includes TTL to prevent infinite loops in chain-forwarding — a storm requires bypassing TTL or using a user-defined forwarding protocol without TTL.
Approach
Set clock to 4 Hz. Fill 16 memory slots. Arm FILE SAVE bus destination on any bus. Trigger Batch Write All. The 16-word stream crosses the bus at 4 Hz — each bit takes 0.25 seconds, each word takes 41 × 0.25 = 10.25 seconds. Total for 16 words: ~164 seconds. The bus strip is visible for each word.
Approach
Populate only slots 0, 4, 8, 12. Save. The Batch Write All stream includes all 16 words — 12 are null words (marker=1, data=0). On load, the writeback pipeline deposits word N into slot N, including null words which write 0. Slot positions are preserved by inclusion, not by addressing.
Approach
Save 3 files, export .l21x archive. The archive is plain text with hex values. Each file occupies 16 sequential values. File 2's slot 5 is at position 16 + 5 = 21 in the value sequence. Edit that hex value in a text editor, re-import, load file 2 — verify the edited slot 5 value.
Approach
Run a long computation (Primes or Fibonacci). Every time a meaningful state is reached, save to a new /local file with a sequential name: STEP-01, STEP-02, etc. With 8 file slots and more than 8 milestones, plan the checkpoint naming to fit. Demonstrate restoration: load STEP-03, verify the memory state matches the session log at that checkpoint, continue computation.
Approach
Machine A builds a 12-slot memory state, saves as "BLIND", sends via SEND TO PEER — no verbal communication about the contents. Machine B accepts, file loads. B computes M[0]+M[1] using the ALU, sends result to A via Bus F. A verifies independently (it knows what it stored).
Approach
Machine A holds the lookup table in memory (loaded from /local). Machine B sends query values via Bus H. A receives each query, performs addr-read M[query], sends result back via Bus H. B stores each response. After 8 queries, B has 8 results without ever seeing the table.
TITLE: Distributed Lookup VARIANT: 2.1 MACHINES: 2 OUT: 8 query results in Machine B's memory --- MACHINE: 1 SETUP BUS.H = PEER→W | BUS.H = W→PEER // bidirectional --- EXEC // load table from /local before EXEC MEM.MODE = ADDR // ready for queries ::SERVE WAIT(H.IN) | H→W // receive query Q W→M | M→W // addr-read M[Q] W→H // return result ::SERVE --- MACHINE: 2 SETUP BUS.H = W→PEER --- EXEC INJ←3 | W→H // query 3 WAIT(H.IN) | H→W | W→M@0 // store result INJ←9 | W→H WAIT(H.IN) | H→W | W→M@1 // ... 6 more queries ...
Instructor Notes — Not Yet Done
Why It's Hard
Content-addressed storage needs a hash function that is: (1) deterministic, and (2) collision-resistant enough to distinguish common machine states. A simple XOR-of-all-slots is fast but has many collisions — any permutation of the same 16 values produces the same hash. A weighted sum or position-dependent XOR with rotation has fewer collisions but requires more operations.
Partial Progress to Reward
Reward any student who: (1) defines and implements a hash function for 16-slot state, (2) stores at least 3 files with hash-derived names, (3) can retrieve any file by computing its hash.
If a Student Claims to Have Solved It
Ask them to find two different memory states that produce the same hash under their function (a collision). This forces thinking about hash quality.
What Would Be Needed
No additional machine features needed. The challenge is feasible with current capabilities — it requires careful hash design and systematic naming.
Approach
Assign transformations strictly: Machine 1 doubles (SHL), Machine 2 adds 500, Machine 3 masks bottom 2 bits (AND 0xFFFC). Values flow 1→2 via Bus F, 2→3 via Bus G. Each machine performs only its assigned transformation before forwarding.
TITLE: Pipeline Stages VARIANT: 2.1 MACHINES: 3 OUT: (input×2 + 500) & 0xFFFC in Machine 3 --- MACHINE: 1 SETUP BUS.F = W→PEER_2 --- EXEC INJ←100 | W→A | A:SHL | A→W | W→F INJ←300 | W→A | A:SHL | A→W | W→F --- MACHINE: 2 SETUP BUS.F = PEER_1→W | BUS.G = W→PEER_3 --- EXEC #2 { WAIT(F.IN) | F→W | W→A | INJ←B←500 | A:+ | A→W | W→G } --- MACHINE: 3 SETUP BUS.G = PEER_2→W --- EXEC #2 { WAIT(G.IN) | G→W W→A | INJ←B←0xFFFC | A:AND | A→W }
Approach
Machines 1–4 each sum their 4 values locally. All send their partial sums to Machine 5 (aggregator) via Bus H. Machine 5 accumulates the four partial sums. Result: global sum of all 16 values.
TITLE: Parallel Sum (4+1 machines) VARIANT: 2.1 MACHINES: 5 OUT: total sum of all 16 values in Machine 5 --- MACHINE: 1 // identical for M2, M3, M4 SETUP BUS.H = W→{M5_ID} --- EXEC INJ←0 | W→WSCR1 // accumulator = 0 M@0→W | WSCR1→W | W→A | A:+ | A→W | W→WSCR1 M@1→W | WSCR1→W | W→A | A:+ | A→W | W→WSCR1 M@2→W | WSCR1→W | W→A | A:+ | A→W | W→WSCR1 M@3→W | WSCR1→W | W→A | A:+ | A→W | W→WSCR1 WSCR1→W | W→H --- MACHINE: 5 SETUP BUS.H = PEER→W --- EXEC INJ←0 | W→WSCR1 // total = 0 #4 { WAIT(H.IN) | H→W WSCR1→W | W→A | A:+ | A→W | W→WSCR1 } WSCR1→W
Approach
Each machine broadcasts its private value to all others. After receiving both remote values, each machine finds the maximum of all three. Because all three machines see all three values and run the same algorithm, they all reach the same result.
Approach
Most reliable approach for manual coordination: each machine sorts its 4 values locally, sends all 4 sorted values to Machine 1 (aggregator). Machine 1 performs a 4-way merge, distributes back each machine's correct quarter. This requires more communication than optimal parallel sort but is far easier to coordinate correctly.
Instructor Notes
Machine C observes (input, output) pairs from Machine B's unknown transformation. With enough samples, C deduces B's function. Good functions for B: XOR with a fixed constant, SHL with wrap, ADD with a hidden constant.
B's operator should choose a function that is not obvious from 2–3 samples but determinable from 8–10. Linear functions (ADD, SHL) are guessable faster than bitwise (XOR, AND). The challenge completes when C can correctly predict B's output before B computes it.
Approach
Each operator picks a secret V (0–15). Commitment: C = V XOR agreed_key. Send C, not V. After both commitments are sent, reveal V. The other operator verifies V XOR agreed_key = C. Coin flip = (V1 XOR V2) mod 2.
The XOR commitment is trivially reversible (it's just XOR). Discuss: why does the machine not have a strong one-way function? All ALU operations are invertible given one operand — except SHL which loses bits, but SHL is a poor hash.
Approach
Same as Challenge 64 with 5 operators. Key additions: positions 3 and 4 must be especially careful to wait for the full Bus E inbound word — chain latency means Bus E words arrive at variable intervals. Pre-session protocol: all 5 write down assigned value and position before pressing ACCEPT; all say "ready" via chat before GO; silence during execution.
Approach
Each machine XORs its 4 slots (local XOR). Machines 1–3 send local XORs to Machine 4. Machine 4 XORs all four values (including its own). Result: XOR of all 16 values across all 4 machines. XOR is commutative and associative — aggregation order does not matter.
TITLE: Network-Wide XOR Checksum VARIANT: 2.1 MACHINES: 4 OUT: global XOR of all 16 values in Machine 4 --- MACHINE: 1 // identical for M2, M3 SETUP BUS.H = W→{M4_ID} --- EXEC INJ←0 | W→WSCR1 M@0→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 M@1→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 M@2→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 M@3→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 WSCR1→W | W→H --- MACHINE: 4 SETUP BUS.H = PEER→W --- EXEC INJ←0 | W→WSCR1 // own XOR M@0→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 M@1→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 M@2→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 M@3→W | WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 #3 { WAIT(H.IN) | H→W WSCR1→W | W→A | A:XOR | A→W | W→WSCR1 } WSCR1→W
Instructor Notes — Not Yet Done
Why It's Hard
The challenge is feasible — multiplication (Challenge 22), file saving, and query-response over Bus H are all available. The difficulty is that FTP is designed for file transfer, not real-time query-response. Implementing a query protocol directly over Bus H is more practical. The partitioning math is straightforward; the coordination protocol design is where students typically get stuck.
Partial Progress to Reward
Reward any group that correctly partitions multiplication work across 4 machines, saves results to /local, and demonstrates one successful query-response where the coordinator retrieves a product from a remote machine without transferring the full table.
If a Student Claims to Have Solved It
Ask them to query the same product from two different machines — one that holds it and one that doesn't. How does the non-holder respond? The protocol needs a "not found" response. What does that look like on this machine?
What Would Be Needed
No additional machine features needed. This is a protocol engineering challenge, not a capability gap.
Instructor Notes — Not Yet Done
Why It's Hard
XOR secret sharing is exactly what this machine can do: generate R1, R2 (random via LFSR). Share1=R1, Share2=R2, Share3=Secret XOR R1 XOR R2. Any two shares reconstruct the secret via XOR. The ALU operations are sufficient. The challenge is coordination: distributing shares via FTP and reconstructing with exactly 2 of 3 machines.
Partial Progress to Reward
Reward: any group that demonstrates the 2-of-3 scheme and proves reconstruction works for all three pairs: (1,2), (1,3), and (2,3).
If a Student Claims to Have Solved It
Ask: can a single machine (holding one share) learn anything about the secret? No — a single share is uniformly random regardless of the secret. Ask students to articulate why this is information-theoretically secure.
What Would Be Needed
The LFSR from Challenge 48 provides the random values needed for shares.
Instructor Notes — Not Yet Done
Why It's Hard
Tolerating f traitors requires 3f+1 machines. For f=1 (one traitor among 4), the minimum correct implementation needs 4 machines and 2 rounds. Round 1: all-to-all broadcast. Round 2: each machine re-broadcasts what it received from each other. Loyal machines take the majority for each position. With 4 machines, this is 12 messages per round, 24 total. Coordinating this without errors at human speed is extremely difficult.
Partial Progress to Reward
Reward: any group that implements Round 1 correctly (all-to-all broadcast) and documents what each machine received, with the traitor sending different values to different recipients.
If a Student Claims to Have Solved It
Ask the three loyal operators to show, from their received values, why majority vote gives the correct result despite the traitor.
What Would Be Needed
This challenge is at the research frontier of what is teachable at human speed. It has not been attempted in a classroom setting.
Instructor Notes — Not Yet Done
Why It's Hard
A ring topology requires Machine 8 to connect back to Machine 1 — a third P2P channel on machines 1 and 8 for the wrap-around. The current architecture has Bus F (left P2P) and Bus G (right P2P). Ring topology without a third bus is not possible.
Partial Progress to Reward
Reward: any group that correctly implements the top-4-bit destination addressing protocol on a linear chain of at least 5 machines. Unicast and broadcast on a 5-machine chain is a genuine achievement.
If a Student Claims to Have Solved It
Ask them to implement "don't forward what you already forwarded." How do they track seen packets? The 16-bit data word has no room for a sequence number. Creative use of existing packet fields is required.
What Would Be Needed
Adding Bus J as a third P2P channel, or explicit ring topology support in the named network system, would make this tractable.
Instructor Notes — Not Yet Done
Why It's Hard
Inter-network routing requires a gateway machine that belongs to two named networks simultaneously and routes packets based on destination network ID. The current machine can belong to only one named network at a time. This is a machine capability gap.
Partial Progress to Reward
Reward: any group that routes a message from Machine A (Net 1) to Machine C (Net 3) via a human-operated gateway — the gateway operator manually reads each packet's destination and re-sends on the correct bus. Even manual routing is a valid demonstration of the protocol.
If a Student Claims to Have Solved It
Ask the gateway operator to describe each routing decision in real time. Can they construct a routing table that fits in memory (destination network ID → output bus selector)?
What Would Be Needed
Multi-network membership for a single machine — or a dedicated "inter-network gateway mode" — would be needed to make this automatable. This is a significant machine architecture question.
Instructor Notes
Common cases where students cannot close the par gap:
- Add Two Numbers: routing operands separately (two bus transfers) instead of one. The efficient path loads both in a single Working→ALU transfer.
- Sort Values: comparing without using TG as a comparator takes O(n²) explicit comparison steps. TG-assisted extraction is significantly faster.
- Filter: routing matching values one by one instead of using PM continuous ejection. PM ejection is a hardware pipeline — it runs while the loop circulates without additional operator actions.
- Any accumulation: reloading both operands into ALU for each addition instead of using Auto-Writeback to maintain the running total in Reg A.
When a student cannot close the gap: ask "what is the fastest way to get the correct answer, given that you perform every operation manually?" Usually the gap is one architectural insight away.
Instructor Notes
The .loop file uses dual-format lines: machine code || human-legible description. T###. prefix = machine tick event. S###. prefix = operator setup action. Students looking for their significant decisions should look at S-lines first.
A student who can find and annotate 5 meaningful S-lines — explaining why they made each decision at that moment — has demonstrated that they understand the session log as a record of their thinking, not just a machine trace.
Instructor Notes
The validator catches most structural failures. The remaining quality gate: can another operator run the challenge and solve it without explanation from the author? Common problems:
- Ambiguous success condition: "route the values correctly" is not testable. "The maximum value must be at the Working read head" is.
- Par too easy: if par equals 1 for any input, the challenge is not well-specified.
- No interesting optimal path: if naive and optimal solutions take the same number of actions, par is meaningless.
- Non-deterministic generate(): using elements not derived from params (e.g., current timestamp) causes dry-run failures.
Instructor Notes
Three architectures for Sort Values (8 values):
- Brute-force comparison (no hardware): O(n²) explicit comparisons. Slowest but most observable — every step is visible.
- TG-assisted extraction: TG1 in destructive mode extracts minimum per pass. O(n) passes × O(n) circulations = O(n²) total, but significantly fewer operator actions.
- Four-bus mega-loop pipeline: for large inputs the pipeline wins decisively. For 8 values the setup overhead dominates.
Approach
Filter challenge (keep matching values) is well-suited. Configure PM1 with target mask and pattern. Destructive eject: non-matching values discarded automatically. Op Count: halt after N Big loop reads. Pre-load source loop. Press start. The machine runs the entire filter without operator involvement.
TITLE: Headless Run — Filter Challenge VARIANT: 2.1 IN: N values pre-loaded in Big loop OUT: matching values in Working; machine halts after N reads NOTES: zero operator actions required after EXEC begins --- SETUP PM1.MASK = 0x000F PM1.PAT = 0x0005 // match: low nibble = 5 PM1.EJECT = DESTR // auto-route matches, discard non-matches CTR.START = N CTR.ACTION = HALT // halt after N Big reads --- EXEC // pre-load N values into Big loop (setup step) B↺ // operator action count from this point forward: 0
Instructor Notes
The challenge is clean transition, not challenge completion speed. Loops should be running throughout. After each challenge the operator's loops contain data from that challenge — the next needs a clean state without stopping the clock.
Efficient transition technique: use Op Count to drain the current loops (count out all circulating values), then inject the next challenge's values without stopping. Students who develop a personal transition protocol — reproducible steps that produce clean state from dirty state without halting — have internalized the machine.
Scoring: the session log should show continuous operation. Verify by checking that tick numbers increase monotonically across challenge boundaries. Any gap indicates a clock stop.
Instructor Notes
The complete CBX Chain Sum packet sequence for 3 machines:
- INVITE (origin → all): origin ID, challenge type, TTL
- ACCEPT (each → origin): machine ID, acknowledgment
- ASSIGN (origin → each): machine ID, assigned value, chain position
- GO (origin → all): start signal
- SUBMIT (each non-origin → origin): position, computed running sum
- SUM_OK (origin → all): if all submissions correct
- COMPLETE (origin → all): challenge done
On SUM_FAIL: SUM_FAIL (with position and discrepancy) → ABORT → new INVITE (restart).
A complete autopsy accounts for all packets including NACKs and retransmits. The timeline should be a causal graph: each packet is caused by a previous packet or operator action.
Instructor Notes — Not Yet Done
Why It's Hard
No student-authored variant has been submitted and approved. The difficulty is not writing a spec — it is writing one that: (1) changes something meaningful about the operating experience, (2) is coherent (all rules still work together), and (3) is more interesting than the base machine, not less.
Partial Progress to Reward
Reward any student who submits a written spec that is self-consistent and describes a genuinely different operating experience. Even a spec that is not approved is a serious effort.
If a Student Claims to Have Solved It
If a student submits a variant you cannot find a flaw in, submit it to Shea for review. That is the intended approval path.
What Would Be Needed
Examples proposed but not yet formalized: No-Bus variant (values can only move via inject/eject), Blind variant (bit display hidden, operator infers state from read head values), Single-Loop variant (all loops merged into one large loop).
Instructor Notes — Not Yet Done
Why It's Hard
No student has submitted a complete, parameterized, machine-executable Loopscript that passes the dry-run validator for a multi-step challenge. The Loopscript validator for .lps files (separate from the custom challenge validator) is a planned feature that does not yet exist.
Partial Progress to Reward
Reward: any student who writes a complete Loopscript (not a fragment) for any challenge with more than 10 EXEC lines. Completeness criterion: another operator should be able to follow it step by step and reach the correct answer.
If a Student Claims to Have Solved It
Ask them to exchange Loopscripts with another student and execute each other's scripts without asking questions. Ambiguities only become visible when someone else tries to follow the script.
What Would Be Needed
The Loopscript .lps validator, when built, will make this challenge fully verifiable.
Instructor Notes — Not Yet Done
Why It's Hard
The machine has no retransmit mechanism. When a P2P connection drops, in-flight Bus F/G words are lost with no acknowledgment — no sequence numbers, no retransmit timeout, no way to distinguish "connection dropped" from "sender paused." A file-based recovery requires knowing which words were delivered, which requires an application-layer ACK protocol that itself might be lost.
Partial Progress to Reward
Reward: any student who correctly identifies and documents the exact point at which guaranteed delivery breaks down: after a word exits the read head and before the receiver writes it to memory, a connection drop loses that word permanently.
If a Student Claims to Have Solved It
The discussion should draw out: why TCP solved this with sequence numbers, ACKs, and retransmit timers. What would need to exist in Loop 2.1 to support reliable delivery? (Per-word ACK from receiver; sender-side buffer retaining words until acknowledged; sequence numbers to detect gaps.)
What Would Be Needed
Intentionally unsolvable with current capabilities. Understanding why it is unsolvable is the pedagogical goal.
Instructor Notes — Not Yet Done
Why It's Hard
No student has demonstrated this with a formally unknown student as the recipient in a class setting. The constraint "without your hands on their keyboard" is the discriminating factor — it forces communication over demonstration.
Partial Progress to Reward
Reward: any student who guides a completely new operator to independently complete Challenge 01 in under 30 minutes with no hands-on assistance. The student must explain: what the inject channel is, what the read head is, and why the value does not appear immediately.
If a Student Claims to Have Solved It
Ask the taught operator: what was most confusing? What finally made it click? The teaching is successful when the learner articulates the key insight in their own words.
What Would Be Needed
If a student achieves this, it is among the highest demonstrations of mastery in the course. Understanding something well enough to teach a stranger is a different skill from understanding it well enough to use it yourself.
Instructor Notes
This slot is intentionally empty. Not a placeholder for a challenge that will be written later — a permanent reminder that the machine is incomplete.
When a new capability is added, Challenge 99 does not get filled in. A new challenge is added to the appropriate section, and 99 remains empty. Challenge 99 is always the next capability that does not yet exist.
The only way to "complete" Challenge 99 is to build something the machine cannot do yet and thereby create a challenge that was previously impossible.