The Blinking Computer: 2026 design

Draft of 13 Nov 2026. Markdown source for blinkingcomputer.org.

Summary

Electrical rules

The bus

Sixteen lines BUS0..BUS15, active high, 1k pull-up to 3.3 V on each.

Pico 2 pins

These are the CPU-facing pins. The I/O section explains why the final board is likely an RP2350B with more GPIO rather than a Pico 2.

Signal Direction Count Meaning
BUS0..15 both 16 the bus
CLR1, SET1 out 2 strobe pair that writes data latches and the ring masters
CLR2, SET2 out 2 strobe pair that copies ring masters to ring slaves, i.e. advances the phase
RESET out 1 clear P, the flags and the ring; set ring stage 0
MEMREQ in 1 the current phase is a memory access
PHASE0 in 1 ring slave 0, used to resynchronise
total 23 of 26 available

Host communication is over USB serial, no pins.

Storage cells

Naming: a cell is transistors TA and TB, cross-coupled. Node NA is TA's collector, NB is TB's collector. Bit = 1 means TA on, NA low, NB high. The LED on TA's collector lights for 1, the LED on TB's collector lights for 0.

Register bit, 4 transistors

Part Connections
TA base from NB via 10k; collector NA with LED+1k to 5 V; emitter on the register's lift line ER
TB base from NA via 10k; collector NB with LED+1k to 5 V; emitter to ground
TSET base from BUSn via 47k; collector to NA; emitter on the register's write line WE
TREAD base from NA via 10k; collector to BUSn; emitter on the register's read line RE

TSET can only write a 1. Clearing is done for the whole register by one transistor TLIFT in the return of the ER line: TLIFT is normally saturated (base via 1k to 5 V); pulling its base low floats the sixteen TA emitters and every bit falls to 0. A write is therefore: lift (all bits to 0), then set (the 1 bits from the bus).

TREAD conducts when the bit is 0 and RE is active, pulling the bus low. So the bus shows the register value.

Per register: 16 bits x 4 plus TLIFT, the WE driver and the RE driver = 67.

Latch bit, 3 transistors

IR, IN0 and IN1 are register bits without TREAD. Their outputs are taken straight from NA and NB, which gives both polarities free. Per latch: 16 x 3 plus lift and WE drivers = 50.

Flag and carry-in bits

eq0, lt0, carry and CIN are single latch bits (3 transistors each) with their own lift and set gating.

Line currents

Ring counter

Thirteen one-hot stages, each a master latch bit and a slave latch bit (3 transistors each).

The master-slave arrangement means the SET1 strobe can be arbitrarily long without the token rippling through. 13 x 6 plus four line drivers plus reset = 84 transistors.

One instruction, phase by phase

The Pico performs one phase as: drive the bus if it is a data phase it owns; wait; sample the bus if it is an address or write-data phase; pulse CLR1 then SET1; release the bus; pulse CLR2 then SET2. The gap between phases is the speed control.

INC2 means: the instruction read through P and did not execute a direct write to P, exactly the emulator's rule.

F Name Bus driven by MEMREQ Pico Written at SET1
0 FETCH-A P 1 sample address
1 FETCH-D Pico, m[address] 1 drive IR
2 INC-LD P 0 IN0; IN1 lifted only, so 0
3 INC-WB ALU, ADD with carry-in 1 0 P
4 OP0-A src0 register or constant isrc0 sample address if MEMREQ
5 OP0-D Pico if isrc0, else src0 again isrc0 drive if MEMREQ IN0
6 OP1-A src1 register or constant isrc1 sample address if MEMREQ
7 OP1-D Pico if isrc1, else src1 again isrc1 drive if MEMREQ IN1
8 CIN nothing 0 CIN from the op and the carry flag
9 WB-A dst register or constant if idst idst and exec sample address if MEMREQ
10 WB-D ALU if exec idst and exec sample data if MEMREQ, store it, OS call if address is 0xFFFF dst register if exec and not idst and dst is P A B or C; flags if exec
11 INC2-LD P if INC2 0 IN0; IN1 lifted, if INC2
12 INC2-WB ALU, ADD with carry-in 1, if INC2 0 P if INC2

Notes. - The carry flag is never read by the ALU directly. F8 copies the right carry-in into CIN, so updating the carry flag in F10 cannot disturb the result that is being written in the same strobe. - The increment phases force the ALU to ADD and force carry-in to 1 by wired-OR onto the control lines, and never touch the flags. - Non-executed instructions still walk every phase and still get INC2, as in the emulator.

ALU, per bit

Inputs a and not-a from IN0, b and not-b from IN1, cin from the bit below. Gates are RTL with diode AND-OR fan-in, one transistor each. Result drivers are open collectors onto the bus; a driver pulls its bus line low when the result bit is 0, so each driver's base wants the complement of the result, and in most cases that complement is already available.

Transistors Produces
2 b' = b for ADD ADC, not-b for SUB SBC; both polarities
2 x = a xor b'; both polarities
1 not-sum = not(x xor cin), which is exactly what the SUM driver needs
2 cout = (a and b') or (x and cin); both polarities for the next bit
1 SUM driver, base from not-sum
1 AND driver, base from the diode OR of not-a and not-b
1 OR driver, base from the diode AND of not-a and not-b
1 XOR driver, base from not-x
1 SHIFT driver, base from not-a of bit n+1
12 per bit, 192 for 16 bits, plus 7 enable line drivers and 3 for the bit 15 shift input

Decode and control

Decode uses both polarities of every IR bit, which the latch gives free. Diode AND of three IR bits plus one transistor makes one select line.

Instruction set

The rule is: as many instructions as the 16 opcode slots allow, provided each is cheap in transistors. Most instructions cost one open-collector driver per bit, about 17 transistors, and some cost almost nothing because they are wiring.

Code Mnemonic Result Carry Cost beyond the adder
0000 AND a and b 17
0001 LSR a >> 1, bit 15 = 0 a0 17, shared with ASR and ROR
0010 ADC a + b + C carry out
0011 SBC a - b - C borrow out
0100 XOR a xor b 17
0101 OR a or b 17
0110 ROR a >> 1, bit 15 = C a0 3
0111 ASR a >> 1, bit 15 = a15 a0 2
10cc ADD a + b, conditional on cc
11cc SUB a - b, conditional on cc

cc is 00 always, 01 lt0, 10 eq0, 11 ne0. The carry flag is a borrow flag after SBC: set when a is less than b, as the emulator has always done. Changes from emu/emu.c: ROR and ASR replace HCF0 and HCF1, and the right shifts now write the carry. HCF, exit and all I/O stay as writes to 0xFFFF, which the Pico services.

Things that are already free and need no opcode: move is ADD 0, compare is a write to a constant register, shift left is ADD with both sources the same, rotate left through carry is ADC with both sources the same, negate is 0 SUB x. Bitwise NOT is two instructions and would cost 17 transistors to add; it is the first candidate if a slot is ever freed.

Transistor budget

Block Count
P A B C 268
IR IN0 IN1 150
flags, CIN 16
ring counter 84
ALU 202
decode and control 65
bus indicators 16
reset and misc 5
total about 800

LEDs about 760. At 3 mA each the worst case is 2.2 A at 5 V, in practice about half light at once. A 5 V 3 A supply.

Pico firmware

C with the Pico SDK.

The Pico as logic analyser

The build will have mistakes in it, and schools building it will have more. So the Pico checks the hardware against the reference emulator at every phase, not just when it services memory, and stops on the first disagreement.

The Pico runs a copy of emu.c in lock step with the ring counter. It samples the bus in every phase, whether or not it is a memory phase, because the bus carries something checkable in almost all of them:

F What is on the bus Checked against Which block is suspect if wrong
0 P emulator P register P, its read line, or whatever last wrote P
1 the instruction the Pico drove nothing, but IR is checked through everything below
2 P emulator P register P
3 P + 1 from the ALU emulator P + 1 adder, carry-in forcing, IN0 or IN1 load
4 src0 register or constant emulator r[src0] that register, src0 decode, constant diodes
5 src0 again or memory data emulator operand 0 as above, or the Pico bus drive
6, 7 as 4 and 5 for src1 emulator operand 1
9 dst register if idst emulator r[dst] that register, dst decode
10 ALU result if exec emulator result ALU op decode, that op's drivers, CIN, exec logic
11, 12 P and P + 1 if INC2 emulator P INC2 logic

It also checks MEMREQ in every phase against what the instruction says it should be, which tests the decode of isrc0, isrc1, idst and exec, and it checks that PHASE0 comes back exactly every 13 phases, which tests the ring.

The latches themselves are checked indirectly: a bad IN0 shows up at F10, a bad IR shows up as wrong operands, wrong MEMREQ or a wrong result, and a bad register shows up the next time it is read. Between them these checks cover every transistor in the machine within one or two instructions of it going wrong.

On a mismatch the Pico holds the clock, so the LEDs freeze at the failing phase for probing, and reports over USB serial: the instruction address and its disassembly in the asm.py notation, the phase name, the expected and seen bus values in binary with the differing bits marked, and the suspect block from the table above. Commands then let you continue ignoring the error, single step onwards, or reset. The same machinery gives self-tests that need no program: walk a 1 across the bus, write and read back each register with random words, and run the ring on its own.

Software stack

Three layers, each visible to a student and each runnable on the emulator and on the hardware.

Layer What it is State
Assembler asm.py, one line per instruction in A = B ADD C form done
Forth sys.4th and forth2asm.py, threaded code with the data stack in B, the call stack in C and the execution pointer in A; words for 16 and 32 bit integer arithmetic, a 24-bit float format, text output done, 247 tests
C chibicc with a new back end that emits the threaded-code layer; emu/cc.sh compiles and runs a program working, 13 Nov 2026: integers, longs, pointers, arrays, structs, switch, function pointers, printf

C is the right top layer: it is standard, it is the original portable assembler, it has no large runtime of its own, and it is what schools and the museum will recognise.

How the C compiler works

Decision, 13 Nov 2026: port chibicc rather than write a compiler from scratch or retarget TinyCC. chibicc is about nine thousand lines of C under the MIT licence, supports most of C11, and was written as a teaching compiler with one feature per commit, which suits the project. The port lives in chibicc/ with the changes listed in chibicc/BLINKING.md; the front end is untouched apart from type sizes, and codegen.c is new.

Each C function becomes one colon definition. Expressions are evaluated on the data stack, so a + b is literally the Forth +. A function's arguments and locals are a frame on the data stack addressed through a frame pointer cell, with the words enter, leave, fload, fstore and faddr in emu/clib.4th doing the work. Names defined in C get a c_ prefix so they cannot collide with runtime words; a C declaration with no definition names a runtime word directly, which is how putc and putd are called. printf with a literal format is expanded at compile time into the runtime's output words, so no formatting code runs on the machine.

Type sizes follow the machine, which is word addressed with no bytes:

C type Words Notes
char, short, int 1 CHAR_BIT is 16, which C permits
long 2 high word first in memory and on the stack
pointers 1 addressing 64K words
float not yet; the Forth F3 and F2 formats are the candidates

Not yet supported: floating point, variadic functions other than printf, structs passed or returned by value, bitfields, alloca and variable length arrays.

Testing: emu/ctest.sh compiles and runs every program in emu/ctest/ and compares the output with the // out line in the source; emu/clibtest.sh runs the tests embedded in clib.4th; emu/utest.sh is unchanged and still passes. The emulator's SBC sets the carry on a borrow, which the runtime words and the ISA table above now both state.

System calls

A write to 0xFFFF is a system call serviced by the Pico, with arguments in the low words of memory as emu.c does now. The C library is therefore mostly on the Pico: putchar, getchar, puts, printf's formatting, string functions, and anything else that would cost more machine code than it teaches. A student can still write their own printf in C if the lesson is about printf.

Input and output

The Pico 2 has 26 GPIO and the CPU interface uses 23. The RP2350's video output, HSTX, needs eight of them, GPIO 12 to 19, so a single Pico 2 cannot do both. Two ways forward:

Decision, 13 Nov 2026: one RP2350B board with HDMI, so the machine stands alone with its own screen and keyboard, which is the demonstration that matters. It costs about £15 in the UK, 15 percent of the budget, and buys both the video output and the spare pins. A laptop over USB serial remains the fallback and the debug channel.

The display

The screen is a character map, as on the BBC Micro, and it lives in the machine's own memory so a program can draw by writing to it.

A class that cannot read the LEDs from the back of the room can also have the registers and phase drawn on the screen beside the program's output.

Physical form

Budget

The target is £100 for everything, so a school can build one. Rough UK prices in November 2026, quantities as needed for one machine with a small surplus:

Item Quantity About
NPN transistors, 2N3904 class 1000 £6
LEDs, red, 3 mm or 5 mm 1000 £8
resistors, 1k, 10k, 47k 3000 £6
diodes, 1N4148 1000 £3
RP2350B board with HDMI 1 £15
5 V 3 A supply 1 £8
printed circuit boards, 16 bit slices and a control board 17 £35 to £50
wire, headers, hinges, fixings £10
total £90 to £105

The boards are the item that decides it. Sixteen identical slices keep the per-board price down, and a school that already has a laminator can build the earlier plan's laminated-sheet version of a slice for almost nothing, at the cost of more wiring.

Build order

  1. One register bit on a breadboard with the Pico driving CLR and SET. Confirm the 47k base from a 3.3 V bus sets the bit, that lift clears it, and that it holds for a day.
  2. One full register of 16 bits with its three line drivers. Pico writes random words and reads them back.
  3. One ring stage pair, then the whole ring, stepped by the Pico.
  4. One ALU bit slice, then 16, fed from IN0 and IN1.
  5. Decode, then run the Forth system with the compare mode on.

Open questions