digiwleeaLearn
Open the lab →

The control unit

The sequencer that pulls the levers

7 min read

The control unit is the sequencer that steps through the micro-operations of each instruction, raising the control lines that gate values onto the bus and trigger register loads. A ring counter supplies the steps, and the current opcode selects which lines fire on each one.

All the blocks are built, but something has to orchestrate them: decide, each clock, which register drives the bus, which register loads, and what the ALU computes. That is the control unit. It is the part that makes the others cooperate, and it works in two layers: a stepper that counts through the sub-steps of an instruction, and decode logic that turns the current step plus the opcode into a set of control-line levels.

Why an instruction needs several steps

A single instruction cannot happen in one clock, because the shared bus can carry only one value at a time. Consider 'add the number at address 9 to the accumulator'. The CPU must, in order: put 9 on the address bus and read memory, send that byte to the ALU's B input, and finally load the ALU result into the accumulator. Each of those is a separate micro-step, and each uses the bus differently. So the control unit walks through a short fixed sequence of steps for every instruction.

A ring counter makes the steps

The stepper is a ring counter: a loop of flip-flops in which exactly one is high at a time, and the high bit rotates one position each clock. Its outputs T0, T1, T2, ... are the micro-step lines, lit one after another, then wrapping around. It is one-hot, just like a decoder's output, but it advances by itself on the clock instead of being chosen by an address. T0 marks the first step of an instruction, T1 the second, and so on.
A 4-step ring counter (RING4), the heart of the control sequencer: after reset, exactly one of T0-T3 is high, and the hot step advances each clock, T0 to T1 to T2 to T3 and back. Before the first clock the outputs read Z; open it in the lab, pulse reset, then run the clock and watch the single high step march around.

The clock: one heartbeat, two jobs

Every storage element in the machine, the ring counter, the program counter, the accumulator, the instruction register, shares one clock line. That single signal does two jobs each tick, and the order matters. During the time the clock is steady, the control lines (decoded from the current step and opcode) settle, and exactly one tri-state drives the bus while the destination's D input watches it. Then the clock edge fires: every enabled register captures whatever was on the bus at that instant, and the ring counter advances to the next step. So one clock period equals one micro-step: levers set up while the clock is level, transfer committed on the edge.
This is why a synchronous CPU is predictable. Nothing moves *between* edges; values just settle. Everything moves *at* the edge, all at once, on the same clock. Slow the clock down and the machine runs in slow motion but computes the identical result; the clock sets the pace, not the logic. A real CPU's "3 GHz" is just this edge firing three billion times a second.

From steps to control lines

The first two steps are the fetch, and they are the same for every instruction: step T0 puts the program counter on the bus and reads memory into the instruction register; a step then increments the PC. After that the steps branch on the opcode. The decode logic is a wall of AND/OR gates: a control line is raised when the right step is active and the opcode matches. For example, the accumulator's load line is (T2 AND opcode==ADD) OR (T1 AND opcode==LOAD) OR .... Each control line is just an OR of the step-and-opcode conditions that should turn it on.
control_line = over (step, opcode) pairs that should assert it
It helps to see the lines laid out as a control matrix: one row per micro-step, one column per control line, a 1 where that line is asserted. Here is the ADD addr instruction, with PC.out (drive the PC onto the bus), MEM.read (RAM drives the addressed byte out), IR.load, PC.inc, B.load (load the ALU's B operand), and ACC.load:
stepPC.outMEM.readIR.loadPC.incB.loadACC.load
T0 (fetch)111000
T1 (incr)000100
T2 (operand)010010
T3 (compute)000001
The micro-steps of ADD addr. Each row is one clock: T0 fetches (PC drives the bus, RAM reads, IR loads); T1 increments the PC; T2 reads the operand byte into the ALU's B; T3 loads the ALU sum into the accumulator. Reading down a column tells you the logic for that one control line.
Read down the ACC.load column: it is 1 only on T3 of an ADD, so its logic is T3 AND opcode==ADD (ORed with the steps of any other instruction that also loads the accumulator, like T2 of LOAD). Read across a row and you get the full set of levers for one micro-step. The whole control unit is just this matrix turned into AND/OR gates, one gate cluster per column.
The cardinal rule of the matrix: in any single row, **at most one *.out line may be 1**. Two sources driving the bus at once is the tri-state short (X) that fights itself. Loads are safe to overlap (many registers can sample the bus on one edge), but drivers are not. A bug in the control logic that raises two *.out lines on the same step is the classic way a hand-built CPU corrupts its bus. (The one place the table above bends this for brevity is fetch: on a strict single bus, T0 is really two bus phases, first the PC drives the bus so a memory-address latch captures the address, then memory drives the byte back to the IR. We fold both into one T0 row here; on each real phase only one driver is live, so the rule still holds.)
Check yourself
On step T2 of ADD, the control unit raises MEM.read and B.load. Why is it safe to assert both at once, but it would *not* be safe to also raise ACC.out on that same step?
This style is a hard-wired control unit: the instruction set is literally a pattern of gates. Bigger CPUs often store the control-line patterns in a small memory (a microcode ROM) indexed by step and opcode, so the instruction set can be changed without rewiring. Both compute the same thing: which levers to pull on each micro-step.
That is the last piece. The ring counter gives the rhythm, the opcode picks the pattern, and the control lines drive every other block. Hang the ALU, registers, program counter, RAM, and instruction register on the shared bus, wire the control lines to their enables, and you have a datapath a control unit can run, which is a working CPU.

Frequently asked

What does a control unit do?

The control unit sequences fetch-decode-execute: each clock it raises the control lines that gate one value onto the shared bus and trigger the right register loads. A ring counter supplies the micro-steps T0, T1, T2, ..., and the current opcode selects which lines fire on each step.

Why does one instruction take several clock cycles?

Because the shared bus can carry only one value at a time. Adding a memory value to the accumulator must, in order: drive the address and read RAM, send that byte to the ALU's B input, then load the result into the accumulator. Each is a separate micro-step using the bus differently, so the control unit walks a short fixed sequence.

How does a control unit decode an opcode into control lines?

Decoding is combinational: a control line is raised when the right step is active and the opcode matches, so each line is just an OR of ANDed (step, opcode) conditions. For example ACC.load = (T3 AND opcode==ADD) OR (T2 AND opcode==LOAD) OR .... The whole control unit is the control matrix turned into gate clusters, one per column.

What is the cardinal rule of the control matrix?

In any single step, at most one *.out driver line may be 1. Two drivers on the bus at once is a tri-state short (X) that fights itself. Loads can safely overlap (many registers may sample the bus on one edge), but drivers cannot, so raising two *.out lines on the same step is the classic way a hand-built CPU corrupts its bus.

Every lesson here builds toward one thing: a working CPU, from the transistor up.

Open the free lab →
Found this useful? Share itShare on X