Chapter 5
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 5.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 CPU of Chapter 5 is the main machine this part set out to build. Now stay on that machine and ask how a circuit too large to draw by hand is written down. 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 source description, and eventually a list of which gate drives which wire. A hardware description language such as Verilog is the source; a netlist is the gate-and-wire description tools compile from it; and a simulator is how a chip is tested long before it is built in silicon.

5.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 5.2 draws the netlist for the XOR circuit Chapter 2 built from four NAND gates.

netlist-graphnetlist-graph
Figure 5.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 5-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")),
]

5.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 5-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 5.3 shows the waves for the XOR netlist: from the inputs, shared fires, then left and right, then out.

wavefrontwavefront
Figure 5.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.

A netlist turns a large circuit into data a tool can inspect. The next chapter keeps the same CPU circuit and makes it faster, not by redrawing it from scratch, but by overlapping the instructions that run through it.

Exercises

PreviousNext
✎ Suggest an edit