The Instruction Cycle as a Finite State Machine
A processor looks, from the outside, like it is doing a great many different things — adding,
branching, loading, storing. From the inside it is doing the same thing over and over,
forever: a small, fixed loop of steps that reads the next instruction and carries it out. That loop is the
instruction cycle, and the piece of hardware that walks it is nothing more exotic than a
finite
state machine. Understand that one loop and you understand the pulse of every computer ever
built.
This lesson pins down two things at once: the von Neumann model — the five-part sketch of
what a computer is made of — and the control unit that animates it, a state
machine whose states are the phases of executing a single instruction. We use the LC-3,
Patt & Patel's teaching machine, as the running example, but the shape is universal.
The von Neumann model: five parts and one radical idea
In 1945 John von Neumann's report on the EDVAC crystallised a design that every mainstream computer still
follows. It has exactly five parts:
- Memory — an array of numbered cells;
- Processing unit — the ALU
and a handful of fast registers where arithmetic happens;
- Control unit — the conductor that decides what happens each clock tick;
- Input — the way the world gets data in (a keyboard);
- Output — the way results get out (a display).
The radical idea is hidden in the first part. In a von Neumann machine, the program and its data
live in the same memory, encoded the same way — as numbers. This is the
stored-program concept. Before it, "reprogramming" a machine like the ENIAC meant
physically rewiring it for days. After it, a program is just data you can load, which means a
computer can read, write — even generate — its own instructions. Compilers, operating systems, and the
whole software industry are downstream of that one decision.
Notice the two kinds of arrow. Fat data paths (buses) carry numbers — instructions and operands —
between memory, the processing unit and the I/O devices. Thin control lines fan out from the control unit
to everything else. The control unit sends no data; it sends decisions — "open this gate now",
"load that register now" — one fresh set every clock cycle.
The six-phase instruction cycle
Undergraduate courses teach a tidy three-beat loop: fetch → decode → execute. It is true,
but it hides the plumbing. Patt & Patel open the "execute" beat up into the steps the hardware
actually needs, giving a six-phase cycle. Every instruction the machine will ever run is
some walk through these six phases:
- FETCH — read the instruction at the address in the \text{PC}
into the instruction register \text{IR}; increment the
\text{PC};
- DECODE — look at the opcode bits and work out which instruction this is and
what the rest of the phases must do;
- EVALUATE ADDRESS — if the instruction touches memory, compute the memory address
(e.g. base register + offset);
- FETCH OPERANDS — read the input values: registers from the register file, or a word
from memory;
- EXECUTE — do the actual work: the ALU adds, ANDs, compares; a branch tests a
condition;
- STORE RESULT — write the answer back to its destination register or memory cell.
Not every instruction visits every phase. An \text{ADD} of two registers skips
evaluate address entirely — it never touches memory. A store instruction does almost all its work
in store result. The phases are a superset: a menu from which each opcode orders the
courses it needs.
The control unit IS this state machine
Here is the key mental switch of the whole module. The six phases are not a flowchart in a textbook — they
are the states of a finite state machine implemented in hardware. The control unit holds a
small state register (Patt's LC-3 uses a 6-bit one, so 64 possible states). Each clock tick, the
current state does two jobs: it drives a specific pattern of control signals out onto the datapath
(this gate open, that register loaded), and it computes the next state —
usually the next phase, but conditioned on the opcode just decoded and on flags like "the memory is ready".
Then the clock ticks and the FSM steps.
Watch the loop close. After STORE RESULT the machine does not stop — it wraps straight
back to FETCH and begins the next instruction. There is no "end". A processor at rest is
just a machine spinning this loop on \text{NOP}s. Later we will see the one place
the loop peeks outside itself: at the very end of the cycle it checks
whether
an interrupt is pending before looping back — the hook the whole I/O system hangs on.
Three phases or six? Same idea, different resolution
The two views are not rivals; the six-phase version is the three-phase version under a microscope. It is
worth seeing exactly where the extra detail lands:
| Undergrad 3-beat | Patt & Patel 6-phase | Why the split matters |
| Fetch | FETCH | one memory read, then bump the PC |
| Decode | DECODE | the opcode chooses the rest of the path |
| Execute | EVALUATE ADDRESS | address arithmetic is its own step on a real datapath |
| FETCH OPERANDS | reading memory can take many cycles (it waits) |
| EXECUTE | the ALU operation proper |
| STORE RESULT | writing back is a separate datapath action |
The split is not pedantry. Each extra phase is at least one clock cycle on a real machine, and
"fetch operands" from memory can stall for hundreds of cycles on a cache miss. Counting phases is how you
start counting cycles — and cycles are what the
iron
law multiplies into time.
Simulate it: step one instruction through the phases
Let's drive the six-phase FSM by hand over a two-instruction program: an
\text{ADD} that never touches memory, then an \text{LDR}
load that does. Watch evaluate address get skipped by the ADD and used by the LDR, and watch the
\text{PC} advance in FETCH.
// A toy von Neumann machine walked through Patt's six phases.
type Op = "ADD" | "LDR";
interface Instr { op: Op; dr: number; a: number; b?: number; base?: number; offset?: number; }
const reg = [0, 5, 3, 0, 0, 0, 100, 0]; // R1=5, R2=3, R6=100 (a base address)
const mem: number[] = [];
mem[105] = 42; // data sitting at address 105
const program: Instr[] = [
{ op: "ADD", dr: 3, a: 1, b: 2 }, // R3 <- R1 + R2
{ op: "LDR", dr: 4, a: 6, base: 6, offset: 5 },// R4 <- mem[R6 + 5]
];
let PC = 0;
const PHASES = ["FETCH", "DECODE", "EVAL_ADDR", "FETCH_OP", "EXECUTE", "STORE"] as const;
function cycle(): void {
let ir: Instr = program[0]; // set for real in FETCH
let ea = 0, x = 0, y = 0, res = 0;
for (const phase of PHASES) {
if (phase === "FETCH") { ir = program[PC]; PC += 1; console.log(` FETCH IR <- mem[PC]; PC -> ${PC}`); }
else if (phase === "DECODE") { console.log(` DECODE opcode = ${ir.op}`); }
else if (phase === "EVAL_ADDR") {
if (ir.op === "LDR") { ea = reg[ir.base as number] + (ir.offset as number); console.log(` EVAL_ADDR addr = R${ir.base} + ${ir.offset} = ${ea}`); }
else console.log(` EVAL_ADDR (skipped -- ADD needs no address)`);
}
else if (phase === "FETCH_OP") {
if (ir.op === "ADD") { x = reg[ir.a]; y = reg[ir.b as number]; console.log(` FETCH_OP R${ir.a}=${x}, R${ir.b}=${y}`); }
else { x = mem[ea] ?? 0; console.log(` FETCH_OP data = mem[${ea}] = ${x}`); }
}
else if (phase === "EXECUTE") { res = ir.op === "ADD" ? x + y : x; console.log(` EXECUTE result = ${res}`); }
else { reg[ir.dr] = res; console.log(` STORE R${ir.dr} <- ${res}`); }
}
}
console.log("=== Instruction 1 ===");
cycle();
console.log("=== Instruction 2 ===");
cycle();
console.log(`Final registers: R3 = ${reg[3]}, R4 = ${reg[4]}`);
Nothing but the \text{PC}. The stored-program idea means instructions and data
are indistinguishable in memory — the machine only fetches a word as an instruction because the
\text{PC} happened to point there. This is a superpower (compilers, JITs, and
self-modifying code all exploit it) and a curse: a buffer overflow that overwrites a
return address can trick the FETCH phase into pointing at attacker-supplied "data", which the CPU then
dutifully executes as code. Modern chips fight back with a no-execute (NX) bit that marks pages as
data-only — a small crack in the pure von Neumann model, patching a security hole that the model's own
elegance opened.
A classic beginner slip is to imagine the answer is produced in DECODE. It is not. DECODE reads the opcode
bits and selects which sequence of later phases (and which control signals) will run — it is a
multiplexer, a fork in the FSM, not an ALU. The real work happens in EXECUTE; the data is gathered in FETCH
OPERANDS; the answer is written in STORE RESULT. Keep the phases' jobs distinct: FETCH gets the
instruction, FETCH OPERANDS gets the data, DECODE just points the machine down the right
corridor.
Why this framing runs the whole module
Every lesson that follows is a zoom-in on one part of this picture. The
single-bus
datapath is the wires the control signals steer. Hardwired
vs microprogrammed control is two ways to build this FSM.
Memory-mapped
I/O is how the Input and Output boxes plug into that same memory. Keep the loop in your head —
six phases, forever — and none of it will feel like disconnected trivia.