A short tour
Each step adds one new idea on top of the previous one. By the end you'll
have read enough circ to feel at home in the
reference. Source on the left, the
--preview output on the right.
Step 1. A single NOT gate
The smallest possible circuit. One input pin `a`, one inverter, one output pin. Every program is a flat list of declarations: name a thing, then wire its ports. The `.out` suffix on a primitive's instance name is its single implicit output port.
input a
not n(in=a)
output out(in=n.out) ╭───╮ ╭───╮ ╭─────╮
│ a ├○───▶┤NOT├○───▶┤ out │
╰───╯ ╰───╯ ╰─────╯ Step 2. AND of two inputs
Two inputs, named `a` and `b`, feed an AND gate. The gate has two input ports — `a` and `b` — that you bind by name in the parenthesised list. The output of the gate goes to a single output pin named `out`.
input a, b
and g(a=a, b=b)
output out(in=g.out) ╭───╮ ╭───╮ ╭─────╮
│ a ├○───▶┤ │ ╭──▶┤ out │
╰───╯ │AND├○╯ ╰─────╯
╭─▶┤ │
│ ╰───╯
│
╭───╮ │
│ b ├○─╯
╰───╯ Step 3. Naming an intermediate signal
A `wire` is a one-port pass-through: the value on `in` is, after evaluation, exactly the value on `out`. It exists so you can give a derived signal a name. Without `clock_buf` here, `gate.a` would be wired directly to `clk` — same logic, less self-documenting.
input clk, data
wire clock_buf(in=clk)
and gate(a=clock_buf.out, b=data)
output out(in=gate.out) ╭─────╮ ╭───╮ ╭─────╮
│ clk ├○────▶┤ │ ╭──▶┤ out │
╰─────╯ │AND├○╯ ╰─────╯
╭──▶┤ │
│ ╰───╯
│
╭──────╮ │
│ data ├○╯
╰──────╯ Step 4. Anonymous nested components
A component can be instantiated inline as the value of a port. The inverter here has no instance name; its `.out` is wired immediately into `gate1`'s `b` port. The same wiring rules apply — anonymous nesting is just syntactic sugar for declaring an unnamed component.
input a
input b
not inv(in=b)
and gate1(a=a, b=inv.out)
output out(in=gate1.out) ╭───╮ ╭───╮ ╭───╮ ╭─────╮
│ a ├○┬──▶┤NOT├○╮ ╭▶┤ │ ╭──▶┤ out │
╰───╯ │ ╰───╯ │ │ │AND├○╯ ╰─────╯
├─────────┴─┴▶┤ │
│ ╰───╯
│
╭───╮ │
│ b ├○╯
╰───╯ Step 5. A half-adder
Two single-bit numbers `a` and `b` sum to `(carry, sum)` where `sum = a XOR b` and `carry = a AND b`. The `xor` keyword is a built-in macro that expands to primitives at compile time. In a single-file program you import macros explicitly; this is the classic Nand2Tetris milestone, eight lines of source.
// half_adder.circ
import xor "<builtin>/xor.circ"
input a, b
xor s(a=a, b=b)
and c(a=a, b=b)
output sum(in=s.out)
output carry(in=c.out) ╭───╮ ╭───╮ ╭───────╮
│ a ├○─●─▶┤ │ ╭──────▶┤ carry │
╰───╯ │ │AND├○╯ ╰───────╯
╭┼─▶┤ │
││ ╰───╯
││
╭───╮ ││ ╭───────╮ ╭─────╮
│ b ├○●╰─▶┤ │ ╭──▶┤ sum │
╰───╯ │ │[xor:s]├○╯ ╰─────╯
╰──▶┤ │
╰───────╯ Step 6. A full-adder, by importing the half-adder
Imports glue files together. The root file declares a `half_adder` alias and uses it twice — once for the low-bit sum, once to fold in the carry-in. The carry-out is the OR of the two intermediate carries. The half-adder's ports (`a`, `b`, `sum`, `carry`) are exactly its `input` and `output` declarations. Notice in the preview how the half-adders show up as `[half_adder:ha1]` and `[half_adder:ha2]` boxes — opaque sub-circuits, just like the built-in macros.
// half_adder.circ
import xor "<builtin>/xor.circ"
input a, b
xor s(a=a, b=b)
and c(a=a, b=b)
output sum(in=s.out)
output carry(in=c.out)
// root.circ
import half_adder "half_adder.circ"
input a, b, cin
half_adder ha1(a=a, b=b)
half_adder ha2(a=ha1.sum, b=cin)
or cout_or(a=ha1.carry, b=ha2.carry)
output sum(in=ha2.sum)
output cout(in=cout_or.out) ╭───╮ ╭────────────────╮ ╭────────────────╮ ╭────────────╮ ╭─────╮
│ a ├○─────▶┤ │ ╭──▶┤ │ ╭▶┤ │ ╭▶┤ sum │
╰───╯ │[half_adder:ha1]├○● │[half_adder:ha2]├○● │ │[or:cout_or]├○╮ │ ╰─────╯
╭───▶┤ │ │ ╭▶┤ │ ●─┼▶┤ │ │ │
│ ╰────────────────╯ │ │ ╰────────────────╯ │ │ ╰────────────╯ │ │
│ ╰─┼────────────────────┼─╯ │ │
╭───╮ │ │ │ │ │ ╭──────╮
│ b ├○─╯ │ ╰──────────────────┴─┴▶┤ cout │
╰───╯ │ ╰──────╯
│
╭─────╮ │
│ cin ├○─────────────────────────╯
╰─────╯ Step 7. Stable feedback through wires
Combinational feedback is rejected at compile time — a chain of gates whose output drives its own input is a hard error (`E008`). But the validator looks at the signal graph, not the textual order. Here two `not` gates connect end to end through two `wire` pass-throughs; the chain has length four and no cycle, so it compiles cleanly. This is the substrate the language gives you for building latches and other feedback structures.
not n1(in=w2.out)
wire w1(in=n1.out)
not n2(in=w1.out)
wire w2(in=n2.out) ╭───╮ ╭───╮
╭▶┤NOT├○───▶┤NOT├○╮
│ ╰───╯ ╰───╯ │
╰─────────────────╯ Done. The reference covers the full surface — every keyword, every diagnostic code, the rules the validator enforces. The examples gallery has more circuits to read through.