A dot product made of wires
You will learn
What on_comb turns a spec into, and how one wrong sign in tmul shows up in exactly one test.
on_comb in comb_ternary_dot.t27 is combinational: the generated Verilog drives result with one assign and no register, so the output follows the inputs. It adds tp over all 27 lanes of a and b, and tp calls tmul, which returns 0 if either trit is Z, +1 if they match and -1 otherwise. So N times N is +1, and dot_all_n_x_all_n expects 27. The browser skips all 4 tests because it does not know assert_eq yet; the native t27c runs all 4, all pass, none vacuous. The recording changes return -1 to return 1 on line 22, and exactly one test fails, dot_all_n_x_all_p, the one test that sets N against P. Every byte in the recording was printed by the command; only the typing is staged.
Try it
In the recording, find the tmul line that changes and the test that fails; then in the spec frame find dot_all_n_x_all_n and explain why it expects 27 and not -27.

t27c on the t27c lab (Railway), spec at t27 5ff0ec512: 4 tests pass natively; tmul returning +1 for mismatched signs fails exactly one test, dot_all_n_x_all_p; git restores the spec.
specs/ternary/comb_ternary_dot.t27
module CombTernaryDot;
// #1764: a COMBINATIONAL spec-first datapath that synthesizes to real hardware.
//
// `on_comb` is the combinational counterpart of `on_clock`: its parameters
// become input data ports and its return is a continuously-driven `output wire
// result` (`assign result = on_comb(...)`). This is what makes the combinational
// half of the ternary stack real hardware -- without it, a bare `fn` result
// never reaches a module port, so a synthesizer dead-code-eliminates the whole
// design to zero cells (it was only ever exercised by testbenches that call the
// Verilog function hierarchically).
//
// Here `on_comb(a, b)` is the bit-exact 27-trit dot product (the same tmul/tp
// primitives verified vs an independent reference on 300 vectors, #1743). It
// synthesizes to a real Artix-7 LUT tree (yosys synth_xilinx: ~160 LUT6 + a
// CARRY4 reduction, no flip-flops -- pure combinational).
// Sign-only ternary multiply of two packed trits {N=0b00, Z=0b01, P=0b10}.
fn tmul(ta: u8, tb: u8) -> i8 {
if (ta == 1) { return 0; }
if (tb == 1) { return 0; }
if (ta == tb) { return 1; }
return -1;
}
// One trit position i of two 54-bit packed vectors (trit i at [2i+1:2i]).
fn tp(a: u64, b: u64, i: u32) -> i8 {
return tmul(((a >> (i << 1)) & 3) as u8, ((b >> (i << 1)) & 3) as u8);
}
// The combinational data interface: (a, b) input ports -> `result` output port.
fn on_comb(a: u64, b: u64) -> i8 {
return tp(a,b,0) + tp(a,b,1) + tp(a,b,2) + tp(a,b,3) + tp(a,b,4)
+ tp(a,b,5) + tp(a,b,6) + tp(a,b,7) + tp(a,b,8) + tp(a,b,9)
+ tp(a,b,10) + tp(a,b,11) + tp(a,b,12) + tp(a,b,13) + tp(a,b,14)
+ tp(a,b,15) + tp(a,b,16) + tp(a,b,17) + tp(a,b,18) + tp(a,b,19)
+ tp(a,b,20) + tp(a,b,21) + tp(a,b,22) + tp(a,b,23) + tp(a,b,24)
+ tp(a,b,25) + tp(a,b,26);
}
test dot_all_n_x_all_n { assert_eq(on_comb(0, 0), 27); }
test dot_all_n_x_all_p { assert_eq(on_comb(0, 12009599006321322), -27); }
test dot_all_p_x_all_p { assert_eq(on_comb(12009599006321322, 12009599006321322), 27); }
test dot_all_z { assert_eq(on_comb(6004799503160661, 6004799503160661), 0); }
endmodule
All lessons
Module 1 · Lab: our own research
A number format of our own, an honest scoreboard, and a model's tables multiplied on the board.
Module 2 · 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.
Module 3 · 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.
Module 4 · The Ternary Network Float
A rule the compiler enforces before any test runs, and a 17-bit float whose exponent is four balanced trits.
Module 5 · Arithmetic on signed numbers
Multiply two signed numbers, add them when their signs differ, and do both at once in a multiply-accumulate.
Module 6 · Parts of a neuron
A ReLU that bends at zero, a power of two for softmax, and an argmax that names the answer.
Module 7 · 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.
Module 8 · 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.
Module 9 · 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.