Abstraction
How a cluster of gates becomes a named block you can reuse — and how the same move scales from a half-adder up to a CPU.
The same circuit, two ways
A half adder is two gates — an XOR for the sum, an AND for the carry. The version below collapses those two gates into a single labeled HalfAdder node with the same external ports. Both produce identical outputs because they are the same circuit.
That collapse is abstraction. Nothing is hidden — the gates are still there, doing the same work — but once a structure has a name, you can stop thinking about its parts.
In code, the rule is: declaring inputs and/or outputs on a circuit() is what turns it into a block you can drop into another circuit as a node. Without either, you have a self-contained simulation — runnable on its own but not composable.
circuit('FlatDemo', {
nodes: {
a: Switch(), b: Switch(),
xor: Xor, and: And,
sum: Led, carry: Led,
},
connect: ({ nodes }) => [
nodes.a.out.to(nodes.xor.a, nodes.and.a),
nodes.b.out.to(nodes.xor.b, nodes.and.b),
nodes.xor.out.to(nodes.sum.in),
nodes.and.out.to(nodes.carry.in),
],
})circuit('HalfAdder', {
inputs: { a: bit, b: bit },
outputs: { sum: bit, carry: bit },
nodes: { xor: Xor, and: And },
connect: ({ inputs, outputs, nodes }) => [
inputs.a.to(nodes.xor.a, nodes.and.a),
inputs.b.to(nodes.xor.b, nodes.and.b),
nodes.xor.out.to(outputs.sum),
nodes.and.out.to(outputs.carry),
],
})Building up: full adders from half adders
A full adder adds three bits — a, b, and a carry-in. Built directly from gates, it’s five: two XORs, two ANDs, and one OR. Built from HalfAdder blocks, it’s two half adders feeding into an OR — same five gates inside, but the structure now has a name at every level.
Why this scales
Once full adders have a name, building an 8-bit adder is eight nodes chained tail-to-head — each stage’s carry-out feeding the next stage’s carry-in. The flat equivalent would have 40+ gates with criss-crossing carry wires. Same behavior, but you couldn’t glance at it and see “eight adders in a chain.”
This is why hardware design works the way it does. Every CPU you have ever used is, at the gate level, hundreds of millions of standard cells. Nobody designs CPUs by drawing them. They describe FullAdder, then Adder, then ALU, then Datapath, then Core — and at each level, the previous level is “a node with a name.” (Real production CPUs use parallel-prefix adders, not ripple-carry, for speed — see /learn/adders for why and what they use instead.)
When to encapsulate
Three rules of thumb for when a cluster of gates deserves a name:
- You use it more than once. A 4-bit adder uses four full adders; without the abstraction, you’d wire the same five-gate pattern four times.
- It’s conceptually one thing. A half adder isn’t “an XOR and an AND” — it’s an adder. The abstraction matches the level you think at.
- It would clutter the parent. If inlining the cluster would make the enclosing circuit harder to read, it wants to be its own node.
The other half of abstraction is parameterization — Adder({ width: 8 }) and Adder({ width: 32 }) are the same abstraction specialized differently. One definition, many uses.
This same hierarchy is what Verilog and SystemVerilog modules express in real chip design. Designers describe modules with named ports, parameters, and instances of other modules; a synthesis tool maps that hierarchy down to cells from a foundry library (NAND, NOR, AOI, full adders, flip-flops — already well above gate level). Nobody, anywhere, is drawing NAND gates by hand.