Blog
[no lesson trains a network; the tests of the 15 new lessons run in recordings of native t27c, not in the browser; 35 of 127 mutants survive and are not sorted; software only, no board] The t27 course is now two courses of 27 lessons, 9 modules of 3 lessons each (recordings for lessons 13 to 21 are pending t27#7400): From zero to a chip, on programming an FPGA, and AI numbers with t27, which starts with the 6 lessons that moved and adds 21 more, ending at a small ternary network. Each of the last 15 lessons plants a one-line bug in its spec and shows the one named test that fails. Over 14 of their functions, tri mutate spec made 127 mutants and the tests killed 92.

The t27 course had grown to 33 lessons in 11 modules: 27 on programming an FPGA, then 6 on number formats for AI. That is two topics in one course, and a link to it could not share either topic on its own. It is now two courses of 27 lessons each, and each course is 9 modules of 3 lessons. Course 1, From zero to a chip, keeps the FPGA lessons and its address. Course 2, AI numbers with t27, starts with the 6 lessons that moved and adds 21 more, ending at a small ternary network. Every lesson opens its own widget and one t27 spec, and each of the last 15 lessons plants one bug in its spec and shows the one test that catches it.
27 is 3 x 3 x 3, the shape of a TRI-27 word, and every course is 9 modules of 3 lessons, 27 lessons in all. When a topic needs more than 27 lessons it becomes a second course, chained to the one before it: the last lesson of course 1 ends with a link to lesson 1 of course 2, and lesson 1 of course 2 links back. Each course has its own address in the app, its own page under t27.ai/learn/, its own preview card and its own line in the sitemap, so either one can be shared alone.
No link that worked before the split stops working. A lesson page stays at t27.ai/learn/<lesson>/ whichever course the lesson is in. In the app, the old address of a moved lesson, #/course/<lesson>, now opens it at #/ai-numbers/<lesson>. Progress is one list in your browser for both courses, so a lesson you marked done before the split is still done after it.
| Lessons | Part | What it teaches |
|---|---|---|
| 1-3 | Lab: our own research | A number format of our own, an honest scoreboard, and a model's tables multiplied on the board. |
| 4-6 | AI numbers: the MX block | How AI chips keep weights in a few bits: one shared scale per block, the scale byte itself, and what one outlier does to its neighbours. |
| 7-9 | Ternary weights | Weights that are only minus, zero or plus a scale, the five rules a ternary alphabet must pass, and a test pass that checked nothing. |
| 10-12 | The Ternary Network Float | A rule the compiler enforces before any test runs, and a 17-bit float whose exponent is four balanced trits. |
| 13-15 | Arithmetic on signed numbers | Multiply two signed numbers, add them when their signs differ, and do both at once in a multiply-accumulate. |
| 16-18 | Parts of a neuron | A ReLU that bends at zero, a power of two for softmax, and an argmax that names the answer. |
| 19-21 | Learning from a mistake | A loss that prices a wrong guess in bits, one step that moves a weight against its gradient, and the hidden layer that XOR needs. |
| 22-24 | BitNet: ternary networks | A threshold that squeezes a sum back to three values, one neuron that becomes a different function when its weights change, and a neuron that reads its inputs 27 trits at a time. |
| 25-27 | The ternary MAC as a chip | The 27-trit dot product as wires with no register, the same sum added into a register on every clock, and a small whole network to close the course. |
Course 1 keeps its 27 lessons in its 9 modules of 3: the chip, numbers in hardware, your t27 program, inside t27c, from spec to hardware, reading synthesis, place, route and timing, the bitstream, and on the board.
Lessons 13 to 27 each teach one spec from the ternary directory of t27. The widget of each is a recording of a terminal on our lab machine, where the native t27c compiler and Zig run the tests the browser cannot run yet. The recording shows the lines that matter and runs the spec's tests. Then tri mutate plant changes one line, runs the tests again and passes only if exactly the named test fails, and git diff shows the file back as it was. In all 15, exactly one named test fails.
Recordings pending for lessons 13 to 21. Their nine specs (gft_smul, gft_sadd, gft_signed_mac, gft_relu, gft_exp2, gft_argmax4, gft_nll, gft_sgd_step and gft_xornet) are recorded with tri test and tri mutate plant from t27#7400, which is not merged yet. Until it is, each of those lessons opens a placeholder page that says the recording is pending and shows no run; the lesson text says so too. Lessons 22 to 27 open their recordings now.
| Lesson | Spec | Tests | The planted bug | The one test that fails |
|---|---|---|---|---|
| 13 A sign is one bit | gft_smul.t27 | 3 | the sign of the product is always 0 | m2 |
| 14 Opposite signs subtract | gft_sadd.t27 | 3 | adds the sizes when the signs differ | a2 |
| 15 Multiply, then add | gft_signed_mac.t27 | 4 | the sign is set with OR, not XOR | pp |
| 16 A bend at zero | gft_relu.t27 | 4 | negative inputs pass through | negz |
| 17 Two to the x | gft_exp2.t27 | 4 | drops the minus sign of k for negative x | em1 |
| 18 Pick the largest | gft_argmax4.t27 | 4 | > becomes >= for two positive scores | tie_low |
| 19 A loss in bits | gft_nll.t27 | 3 | neg turns 0 into a zero with the sign bit set | perfect |
| 20 One step downhill | gft_sgd_step.t27 | 3 | multiplies g by itself, not by eta | ascend |
| 21 XOR needs a bend | gft_xornet.t27 | 4 | no relu on one hidden unit | x00 |
| 22 Back to three values | activation_quantizer.t27 | 7 | > becomes >= at the threshold | quantize_boundary_hi |
| 23 Same neuron, new weights | bitnet_majority.t27 | 12 | one weight goes from P to Z | maj_p_n_n |
| 24 A neuron in chunks | bitnet_neuron_nchunk.t27 | 11 | the loop reads one chunk, not all of them | neuron_zero_chunks |
| 25 A dot product made of wires | comb_ternary_dot.t27 | 4 | N times P gives +1 | dot_all_n_x_all_p |
| 26 Add it up on every clock | stream_ternary_mac.t27 | 4 | a P in a reads as N | dot_all_p_x_all_p |
| 27 A whole network | bitnet_mlp.t27 | 4 | the third hidden trit lands on the wrong bit | pack3_zzz |
One planted bug shows that a test can fail. It says little about the tests that were never written. So each recording ends with tri mutate spec, which makes every one-line change it knows in the lesson's function and counts how many of them the tests notice.
Over 14 of the 15 functions, tri mutate spec made 127 mutants. 92 were killed, every one of them by a failing test, and 35 survived; none hung and none failed to build. Eight functions killed every mutant. Six did not:
| Spec | Function | Mutants | Killed | Survived |
|---|---|---|---|---|
| gft_exp2.t27 | on_comb | 34 | 17 | 17 |
| gft_sadd.t27 | sadd | 18 | 10 | 8 |
| gft_smul.t27 | smul | 16 | 12 | 4 |
| gft_argmax4.t27 | gt | 9 | 6 | 3 |
| comb_ternary_dot.t27 | tmul | 7 | 5 | 2 |
| gft_relu.t27 | on_comb | 5 | 4 | 1 |
A survivor is a one-line change that no test notices. Either a test is missing, or the change does not change what the function returns. Both kinds occur in specs like these. In gft_relu.t27, line 11, if (x == 0) { return 0; }, can be deleted without changing any result, because line 13 returns x, and x is already 0. Which of the 35 survivors is which kind is not sorted yet; gft_exp2.t27, with 4 tests and 17 survivors, is where to look first. For gft_xornet.t27 the count printed no summary at all, so it is left out of the totals.
Making the recordings found two problems in the tools. tri test used to print a green line without running a single test, so a recording made with it proved nothing. Pull request t27#7400 makes it run the tests and fail when one fails or none ran, and adds tri mutate plant, which plants a bug, demands that exactly the named tests fail, and checks the file's hash before and after. t27c test-report still exits 0 when a test fails, so every verdict in these recordings is read from its text (t27#7370).
Before writing this, we read the pages of 16 other resources that teach or build ternary networks, quantized networks on FPGAs, or a network from scratch, on 7 October 2026. The table keeps 11 of them.
| Resource | Kind | Ternary weights | Runs where | Tests behind it |
|---|---|---|---|---|
| The Era of 1-bit LLMs, S. Ma et al. (2024) | paper | yes, the definition of BitNet b1.58 | GPUs | perplexity and accuracy against LLaMA |
| bitnet.cpp, Microsoft | inference library | yes | CPUs, and GPUs since 2025 | no test suite named |
| Fine-tuning LLMs to 1.58bit, Hugging Face (2024) | article | yes, the quantizer step by step | GPUs | benchmarks and loss curves |
| A Visual Guide to Quantization, M. Grootendorst (2024) | illustrated article | yes, with worked numbers | static figures | none, there is no code |
| Ternary Weight Encoder, binarycon.com | interactive page | packs weights that are already ternary | your browser | none stated |
| FINN, AMD Research | compiler and notebooks | binarized and quantized; ternary is not named on the pages read | FPGA boards; Python, C++ and RTL simulation | yes: each stage against a golden reference |
| TernaryCore (2026) | Verilog library | yes: a MAC, a dot product, a matrix multiply | simulation; Arty A7-100T and Tang Nano 9k boards | testbench pass counts and RTL against Python; tests not named |
| TeLLMe, Y. Qiao et al. (arXiv:2504.16266) | paper | yes, 1.58-bit weights | a Kria KV260 FPGA | no correctness check described |
| micrograd and Neural Networks: Zero to Hero, A. Karpathy | library and video course | no | Python | gradients checked against PyTorch |
| MiniTorch, S. Rush | teaching library | no | Python, and a CUDA module | unit and property tests the learner must pass |
| nand2tetris, Project 2 | course and book | no | a desktop hardware simulator | a test script and a compare file per chip |
| This course, lessons 13 to 27 | course | yes: a 27-trit dot product, a MAC and a small network | specs in your browser; tests in recordings of native t27c | yes: 74 tests, one planted bug per lesson, 127 mutants |
Each of these does something this course does not. The BitNet paper, bitnet.cpp and the Hugging Face article work at the scale of real models, from 700M to 70B parameters, with measured speedups; no lesson here touches a trained model. FINN compiles trained networks into FPGA accelerators and checks every stage in simulation, and TeLLMe runs a whole ternary language model on an edge FPGA. TernaryCore is the closest: open Verilog that goes from a ternary MAC to a dot product to a matrix multiply, with tests, and runs on real boards. micrograd, MiniTorch and nand2tetris have the learner build each piece against tests.
What we did not find on any of those pages is the shape of these lessons: one planted one-line bug per lesson that fails exactly one named test, with the file put back afterwards. nand2tetris and MiniTorch come near, since a learner's own mistake fails their tests, but they do not plant one and show it. That is a narrow claim about the pages we read, not about every course there is. Nor do the tests of these 15 lessons run in your browser yet; they run in the recordings.
Work with me
I audit RTL and build independent, bit-exact models, then take the result through synthesis and, when useful, onto an Artix-7 board. The first conformance module is free.