notebook № 017 plates · 25 min to read, longer to play
What a CPU is, and how it works
On the clock that never stops
A CPU is a few billion switches and a clock that never stops. This notebook builds one from the bottom up: logic gates, arithmetic, the fetch–decode–execute loop, then the three tricks that make modern cores fast: pipelining, caches and guessing.
plate I · the switch
Everything from NAND
Strip a CPU down far enough and you find one part, repeated tens of billions of times: the transistor, a switch that one voltage turns on and off. Wire a few together and you get a gate, a tiny machine that looks at its inputs and answers 1 or 0.
The NAND gate (“not and”) answers 0 only when both inputs are 1. It takes four transistors, and it has a party trick: every other gate can be built from NANDs alone. NOT is a NAND with its inputs tied together. AND is a NAND followed by a NOT. XOR takes four.
Pick a gate and flip the switches. Watch the signal walk through the circuit one gate at a time. That delay is real; every gate takes a few picoseconds to make up its mind, and a CPU’s clock speed is limited by the longest chain of them.
Build XOR, set A = 1 and B = 1, then flip B. Count the gate delays before the lamp agrees.
plate II · arithmetic
Counting with wires
Two XORs, two ANDs and an OR make a full adder: it adds two bits plus a carry, and passes a carry on. Chain eight of them and you can add two bytes. That is the whole trick behind every number your computer has ever produced.
The chain has a flaw you can see. Each adder waits for the carry from its right-hand neighbour, so the answer ripples across the byte. On 64 bits that is too slow, so real ALUs use carry-lookahead adders that compute all the carries in parallel with more gates. Spending transistors to save time is the oldest trade in this notebook, and it comes back on every plate.
Then there is the edge of the world. Eight bits hold 0 to 255. Ask for 200 + 100 and the ninth bit falls off the end. Switch to signed and the very same bits mean −128 to 127. Two’s complement is the convention that lets one adder serve both: −1 is just 11111111, and adding 1 rolls it over to zero.
Make 127 + 1 in signed mode. The hardware did nothing wrong. Your interpretation did.
plate III · the loop
Fetch, decode, execute
Gates and adders compute, but they don’t follow instructions. The leap that makes a CPU is to store the instructions as numbers in the same memory as the data, and build a little machine that reads them one by one. That machine runs a loop forever: fetch the next instruction from the address in the program counter, decode its bits into “what” and “to which registers”, execute it. Then again. Four billion times a second.
Below is a complete CPU: four registers, an ALU, 32 bytes of memory and eight instructions. It has no multiply instruction, so the first program multiplies by adding in a loop, the way the earliest machines had to. Press tick to advance one phase, instruction to run a whole one, and watch the bytes travel.
Three things are worth watching. First, the program counter moves on during fetch; a jump simply overwrites it during execute, and that is all a loop is. Second, the zero flag: SUB leaves a note saying “the result was zero”, and JNZ reads the note. Every if you have ever written compiles down to that handshake. Third, run Fibonacci with n = 7 and look at R1. Eight bits again.
Count the ticks for 6 × 7, then for 7 × 6. Same answer, different bill. Which operand should go in the counter?
plate IV · overlap
The assembly line
The toy CPU sits idle most of the time: while it fetches, the ALU waits; while the ALU works, memory waits. Real cores fix this the way Henry Ford did. Split the work into stages, typically IF (fetch), ID (decode), EX (execute), MEM and WB (write back), and start a new instruction every cycle, so five are in flight at once.
The catch is that instructions depend on each other. ADD wants the value LD is still fetching; that is a hazard, and the pipeline must stall (the dots). Forwarding wires pass a result straight from one stage to the next instruction without the round trip through the registers. Branches are worse: by the time the CPU learns it must jump, it has already fetched the wrong instructions, and has to throw them away.
The classic five-stage pipeline is a teaching model. A modern core has fifteen to twenty stages, decodes four to eight instructions per cycle, and executes them out of order, as soon as their inputs are ready, with several hundred in flight. Then it carefully retires them in the original order, so your program never notices. It is the most elaborate magic trick in engineering, and its whole purpose is to keep the ALUs busy.
plate V · distance
Far from home
At 4 GHz one cycle lasts a quarter of a nanosecond, which is long enough for light to cross about 7.5 centimetres. Main memory sits a few centimetres away, behind a memory controller and a protocol, and it answers in roughly 90 nanoseconds. That is several hundred cycles of a core doing nothing.
The numbers are too small to feel, so the ladder rescales them: if one cycle took one second, an L1 cache hit would take four, a trip to RAM would take six minutes, and an SSD read would take a long weekend.
This is the single most important fact about performance on any processor. Arithmetic is cheap. Moving data is expensive. Almost every trick in the following plates, and in the GPU and TPU notebooks, is a way of not waiting for memory.
plate VI · memory
The cache
The answer to distance is to keep copies close. A cache is a small, fast memory next to the core that holds recently used data. It never fetches one number at a time: it fetches a whole line (64 bytes on most CPUs, four elements in this drawing), betting that if you wanted one value, you will soon want its neighbours.
Whether that bet pays depends on you. A two-dimensional array is stored row after row. Walk it row by row and each line you fetch serves four accesses: one miss, three hits. Walk it column by column and every access lands on a different line; with a small cache, each one is evicted before you come back for its neighbour.
Same array, same arithmetic, same answer. Run both and compare the cycle counts.
Walk by column, then raise the cache to 16 lines. The access pattern didn’t change; the working set started to fit.
plate VII · prophecy
Betting on the future
Back to plate IV’s problem: a deep pipeline fetches instructions long before it knows which way a branch goes. Waiting would waste fifteen-odd cycles on every if. So the CPU guesses, runs ahead on the guess, and throws the work away if it was wrong.
The simplest useful predictor is a two-bit counter per branch. Taken pushes it right, not taken pushes it left, and it predicts whichever side it is on. Two bits instead of one means a single surprise doesn’t flip the prediction, which is exactly what a loop needs: taken, taken, taken… and one exit.
Real predictors keep long histories and correlate branches with each other; on typical code they are right well over 95% of the time. Which means the cheapest branch is a predictable one, and the most expensive is a coin toss. Try alternating on the two-bit counter for the worst case, then you decide and try to fool it.
A CPU, then, is a machine for running one stream of instructions as fast as physics allows, by overlapping, caching and guessing. The GPU notebook starts from the opposite bet: give up on speeding up one stream, and run thousands.
Questions people ask
- What are the main parts of a CPU?
A control unit that decodes instructions, an arithmetic logic unit (ALU) that does the maths, a handful of registers that hold the values being worked on, and caches that keep recently used memory close. A clock keeps them all in step.
- What is the fetch–decode–execute cycle?
It is the loop every CPU runs: fetch the next instruction from the address in the program counter, decode it to work out what it asks for, execute it, then move the program counter on. Plate III lets you step through it one tick at a time.
- Why does a CPU need a cache?
Main memory is around a hundred times slower than the core. A cache keeps recently used data in small, fast memory right next to it, so most loads finish in a few cycles instead of hundreds.
Related material
elsewhere in the notebooks
further reading
- Nand2Tetris Nisan & Schocken Build a whole computer from NAND gates up, then write its compiler and OS. The course this notebook’s first three plates bow to.
- Build an 8-bit computer from scratch Ben Eater The same machine as plate III, on breadboards, with real wires and real blinking LEDs.
- What Every Programmer Should Know About Memory Ulrich Drepper The long version of plates V and VI. Old, and still right.
- Software optimization resources Agner Fog Instruction latencies and microarchitecture manuals for every x86 core worth knowing.
- Chips and Cheese Deep dives into real cores: pipelines, caches and predictors measured, not guessed.
- Why I’m building a GPU-first programming language Where this curiosity leads me: Miri, and why the CPU is no longer the only machine worth targeting.