The wires nobody traced yet
In guide 1 we laid out the datapath — the program counter, the instruction memory, the register file, the ALU, the data memory, the sign-extender, and the multiplexers that splice them together. In guide 2 we pushed one instruction through and watched the data move. But look back at every mux we drew. Each one has a select line dangling off it, asking a yes-or-no question: take the immediate, or the register value? Write to the register file this cycle, or not? Read memory, or skip it? In those guides we quietly answered those questions by hand. The control unit is the part of the chip that answers them automatically, on every clock tick, just by looking at the instruction.
Here is the cleanest way to hold the whole design in your head, a split that runs through this entire rung: the datapath is where data flows, and the control unit is what steers it. The datapath is plumbing — pipes and valves carrying bits. The control unit is the hand on every valve. They are deliberately kept apart, because the same plumbing can carry an add, a load, a store, or a branch; only the pattern of open and closed valves changes from one instruction to the next. That separation is called the datapath/control split, and it is one of the most reused ideas in the whole machine.
From opcode to a fistful of flags
Every instruction carries an opcode — the few bits near the front that say which kind of instruction this is. The control unit's whole job is a lookup: feed it the opcode, and out come the control signals, a little bundle of single-bit flags, one per switch in the datapath. Think of it as a vending machine. You press one button (the opcode), and a fixed combination of levers drops into place: this mux flips left, that write-enable goes high, the memory-read line stays low. The same set of physical wires gets a different setting for an add than for a load, but the machine deciding the setting never changes — it is pure decoding.
Which flags are we talking about? For our single-cycle RISC-V, a handful covers everything. RegWrite says "write the ALU or memory result back into the register file at the end of this cycle." MemRead and MemWrite say whether the data memory is read or written. ALUSrc picks the second ALU input: a register value, or the sign-extended immediate. MemToReg picks what gets written back: the ALU's answer, or a word fetched from memory. And Branch arms the logic that may redirect the program counter. Set those correctly for the opcode in front of you, and the datapath does the right thing without ever knowing what instruction it is running.
The control table: one row per opcode
Because the control unit is pure decoding — opcode in, flags out — you can write its entire behavior as a single control table. Each row is an instruction type; each column is one control signal; each cell is the 0 or 1 that signal must take. There is no cleverness hidden here: the table is the control unit. Let's fill in four rows for the four instructions we've been following — an arithmetic R-type (add), a load (lw), a store (sw), and a conditional branch (beq).
Instruction | ALUSrc | MemToReg | RegWrite | MemRead | MemWrite | Branch | ALUop ------------+--------+----------+----------+---------+----------+--------+------- add (R) | 0 | 0 | 1 | 0 | 0 | 0 | add lw (load) | 1 | 1 | 1 | 1 | 0 | 0 | add sw (store) | 1 | x | 0 | 0 | 1 | 0 | add beq (branch)| 0 | x | 0 | 0 | 0 | 1 | sub ( x = don't-care: that result is never written, so the flag's value is irrelevant )
Read it slowly and the whole datapath comes alive. The add reads two registers (ALUSrc 0), tells the ALU to add, and writes the answer straight back (MemToReg 0, RegWrite 1) — memory untouched. The lw uses the immediate as an offset (ALUSrc 1), adds it to a base register to form an address, reads memory (MemRead 1), and writes that fetched word back (MemToReg 1). The sw also adds an offset but writes to memory (MemWrite 1) and writes nothing to a register (RegWrite 0). And beq does a subtraction — not to keep the result, but to test it: if the two registers are equal the difference is zero, and Branch 1 lets that zero steer the program counter. One table, four very different behaviors, zero changes to the wires.
Two layers of control: the main unit and ALU control
You may have noticed the last column says "add" or "sub" rather than a clean 0 or 1. That is because the ALU does more than two things — add, subtract, AND, OR, set-less-than — and choosing among them takes a few bits, not one. Architects keep this tidy by splitting control into two layers. The main control unit looks only at the opcode and emits the high-level flags plus a tiny 2-bit hint called ALUop. A small second decoder, ALU control, takes that hint together with the instruction's function field and produces the exact operation code the ALU needs.
Why the extra layer instead of one big table? Economy. Every load and store and the simple address arithmetic all just need "add," so the main unit can say ALUop = 00 ("add") and be done. A branch needs "subtract," so ALUop = 01. But an R-type could be add, sub, AND, OR, or many more, all sharing one opcode and differing only in their function bits — so the main unit says ALUop = 10 ("look at the function field") and hands the fine decision to ALU control. This two-step decode keeps the main control table small while still covering a rich ALU. It is a clean example of pushing detail to the place that has the information to decide it.
Why one cycle isn't enough
So far our control unit lives in a single-cycle processor: every instruction starts and finishes in exactly one clock tick. That is wonderfully simple to reason about — one row of the table per tick — but it hides a brutal cost. The clock can tick no faster than the slowest instruction's path through the datapath. A load is the slow one: it must read instruction memory, read the register file, do an ALU add to form the address, read data memory, then write back — a long chain. Yet a simple add finishes far sooner. In a single-cycle design, the fast add is forced to wait out a clock period stretched to fit the slow load. Everybody runs at the speed of the slowest.
This is the single-cycle limitation, and it bites because of the iron law of performance: run time = instruction count x CPI x cycle time. Single-cycle keeps CPI at a flattering 1 — one cycle per instruction — but pays for it with a cycle time bloated to the worst case. Worse, every functional block is used at most once per cycle, so the design must duplicate hardware (separate instruction and data memories, extra adders) it could otherwise share. The fix is to stop forcing every instruction into one tick.
Enter the multi-cycle datapath. Break each instruction into a few short steps — fetch, decode, execute, memory, write-back — and give each step its own short clock tick. Now the clock runs at the speed of one step, not one whole instruction. A fast add takes only the three steps it needs; the slow load takes all five and pays only for what it uses. Hardware can be reused across steps too: a single ALU computes the address in one step and could increment the PC in another. The control unit grows up to match: it is no longer a flat lookup table but a finite-state machine walking through the steps, emitting a different bundle of control signals at each one. We unpack this single-cycle-versus-multi-cycle trade fully in guide 4.
Two ways to build the controller: hardwired vs microprogrammed
However you organize it, the controller can be physically built in one of two styles, and the distinction outlives single-cycle and multi-cycle alike. Hardwired control turns the control table directly into logic gates: the opcode comes in, runs through a fixed mesh of AND/OR gates, and the control signals come out. It is fast and compact, but the table is frozen into silicon — to change the control behavior you redesign the gates. RISC chips, with their small, regular instruction sets, lean on hardwired control precisely because their tables are simple enough to gate cheaply.
Microprogramming takes the opposite tack: store the control signals as data. Each opcode's behavior becomes a tiny program of "microinstructions" kept in a small fast memory inside the chip; the control unit becomes a little interpreter that fetches and runs that micro-routine, each microinstruction laying down one cycle's worth of control signals. It is slower than gates but gloriously flexible — supporting a fat CISC instruction, or even fixing a control bug, becomes a matter of rewriting the stored microprogram instead of respinning the silicon. This is why complex CISC machines historically favored it.
Don't read this as a strict rivalry with a winner. Real chips blend the two, and the same blurring we met with RISC-versus-CISC shows up here: a modern x86 cracks each complex instruction into RISC-like micro-ops, so it carries microcode for the gnarly, rare instructions while running the common ones through fast hardwired-style paths. The deeper point is the seam itself. Once control is just a table — whether burned into gates or stored as a microprogram — the door is open to overlap several instructions' steps at once. That overlap is the pipeline, the idea this rung has been building toward and the subject of the rungs ahead.