Draft of 13 Nov 2026. Markdown source for blinkingcomputer.org.
emu/emu.c with all 16 opcode slots used: the two HCF slots become ROR and ASR.Sixteen lines BUS0..BUS15, active high, 1k pull-up to 3.3 V on each.
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.
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.
| 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.
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.
eq0, lt0, carry and CIN are single latch bits (3 transistors each) with their own lift and set gating.
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.
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.
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 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.
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.
emu/utest.sh runs the 247 in/out tests embedded in sys.4th through forth2asm, asm, txt2bin and emu. It passes with no output on 13 Nov 2026 and must still do so after the emulator and assembler change.| 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.
C with the Pico SDK.
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.
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.
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.
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.
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 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.
dvi_out_hstx_encoder example streams a 640 by 480 frame buffer this way.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.
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.