Chapter 6
How is a circuit described?

  1. The browser
  2. The network
  3. Software and languages
  4. The CPU
  5. Memory
  6. Arithmetic
  7. Logic gates
  8. The switch
Figure 6.1: Where we are in the climb from transistor to browser: still on the processor, looking closer. This chapter asks how a circuit too large to draw is described, as data a program can read.

The chapters so far have built circuits by writing one gate function that calls another: a full adder calls half adders, which call XOR and AND. That is fine for a handful of gates. The processor of Chapter 5 already has hundreds, and a real one has billions of transistors. Nobody wires those by hand. Instead a circuit is written down as data, a list of which gate drives which wire, and a program reads that list and simulates the circuit. The list is what a hardware description language like Verilog is, and the simulator is how a chip is tested long before it is built in silicon.

6.1 A circuit is a netlist

Strip a circuit to its essentials and it is a graph: gates are nodes, and the wires between them are edges. Writing that graph down gives a netlist: one record per gate, naming the wire it drives, its kind, and the wires it reads. Figure 6.2 draws the netlist for the XOR circuit Chapter 2 built from four NAND gates.

netlist-graphnetlist-graph
Figure 6.2: The XOR circuit as a graph. Each gate drives one named wire; each wire carries a value from one gate to the next. This picture and the list of gate records are the same circuit, written two ways.

The data is a direct transcription of the drawing.

Example 6-1. The XOR circuit, written as data

# XOR, described as the four-NAND circuit Chapter 2 built by hand.
XOR_FROM_NAND = [
    Gate("shared", "nand", ("a", "b")),1
    Gate("left", "nand", ("a", "shared")),
    Gate("right", "nand", ("b", "shared")),
    Gate("out", "nand", ("left", "right")),
]

6.2 Simulating the description

To find what the circuit computes, the simulator works outward from the inputs. A gate can fire only once every wire it reads is known; firing it makes its output wire known, which may let other gates fire. The value spreads in waves until every wire is settled. Because a gate waits on its data, not on its place in the list, the order the gates are written in does not matter.

Example 6-2. Evaluating a netlist

def simulate(netlist: list, inputs: dict) -> dict:
    """Evaluate a combinational netlist. `inputs` gives the input wires' values;
    the result holds every wire's value. A gate fires once all the wires it reads
    are known, so the listing order does not matter."""
    values = dict(inputs)
    pending = list(netlist)
    while pending:
        ready = [g for g in pending if all(w in values for w in g.ins)]1
        if not ready:
            raise ValueError("netlist has an undriven wire or a feedback loop")2
        for gate in ready:
            values[gate.out] = _GATES[gate.kind](*[values[w] for w in gate.ins])3
        pending = [g for g in pending if g not in ready]
    return values

Figure 6.3 shows the waves for the XOR netlist: from the inputs, shared fires, then left and right, then out.

wavefrontwavefront
Figure 6.3: Simulation spreads in rounds. A wire becomes known once the gate driving it has all its inputs, so the values fan out from a and b to out. Listing the gates in any order gives the same rounds.

The interactive version needs the in-browser runtime.

The next chapter makes the processor faster, not by redrawing its circuit but by overlapping the instructions that run through it.

Exercises

PreviousNext
✎ Suggest an edit