How the CPU actually runs code
This loop is the ground floor of the whole course. Processes in unit 3, race conditions in unit 5, and the scheduler in unit 6 are all things done to this loop, and none of them can be reasoned about without it.
It is also why a stack trace or a debugger can say exactly where a program was, since the machine always knows precisely which instruction comes next.
The CPU does exactly one thing, forever, billions of times per second:
- Fetch the next instruction from RAM. The CPU keeps a counter called the program counter holding the address of that instruction.
- Decode it, working out what kind of instruction it is, whether an add, a copy, or a jump.
- Execute it, doing the work, usually updating a value in one of the tiny extremely fast storage slots inside the CPU called registers.
Then it returns to step 1. That is the whole trick, and every game, browser, and AI model is this loop running very simple instructions very fast.
Nothing in the cycle knows what a program means. There is no notion of a function, a loop, or an object at this level, only one instruction after another and a counter saying where to look next.
A CPU you can read
The simulator below is a fetch-decode-execute loop in fifteen lines of Python. Three pieces of state stand in for real hardware:
| In the simulator | In a real CPU |
|---|---|
memory, a list of instruction strings | RAM holding encoded instructions |
pc, an index into that list | the program counter register |
acc, one running value | an accumulator register |
Real instructions are numbers rather than text such as "ADD 3", and a real CPU has dozens of registers rather than one. Neither difference changes the shape of the loop.
The one detail worth watching is that pc advances before the instruction executes. That ordering is what lets a jump instruction work at all, since a jump is nothing more than an instruction that overwrites pc with a different address.
A CPU in fifteen lines
The program in memory loads 5, adds 3, adds 2, prints, and halts.
memory = ["LOAD 5", "ADD 3", "ADD 2", "PRINT", "HALT"] acc = 0 pc = 0 while True: instruction = memory[pc] # FETCH pc = pc + 1 parts = instruction.split() # DECODE op = parts[0] if op == "LOAD": # EXECUTE acc = int(parts[1]) elif op == "ADD": acc = acc + int(parts[1]) elif op == "PRINT": print(acc) elif op == "HALT": break
Output
10Traced by hand, acc starts at 0, LOAD 5 makes it 5, ADD 3 makes it 8, and ADD 2 makes it 10 before PRINT reports it.
The three comments mark the three phases, and each pass of the while loop is one complete cycle. HALT is the only instruction that ends the loop, which is why a real CPU with nothing to run executes an idle instruction rather than stopping.
Adding an instruction to the instruction set
A SUB branch subtracts its argument, and the program prints 6.
memory = ["LOAD 10", "SUB 4", "PRINT", "HALT"] acc = 0 pc = 0 while True: instruction = memory[pc] pc = pc + 1 parts = instruction.split() op = parts[0] if op == "LOAD": acc = int(parts[1]) elif op == "ADD": acc = acc + int(parts[1]) elif op == "SUB": acc = acc - int(parts[1]) elif op == "PRINT": print(acc) elif op == "HALT": break
Output
6The new branch is the ADD branch with one character changed, since subtraction differs from addition only in the operator applied to the accumulator.
Extending a real CPU is not this easy, and that is the interesting part. An instruction set is fixed in silicon, so adding one means a new chip generation, which is why compilers work so hard to express everything in terms of the instructions that already exist.
What the program counter holds
The variable pc holds the position in memory of the next instruction to fetch.
Fetch reads memory[pc] and then bumps pc forward, so it always points one step ahead of the instruction currently executing. A real CPU works the same way, and jump instructions work by simply overwriting pc.
elif op == "JUMP": pc = int(parts[1]) # the whole implementation of a loop
That one line is where every loop, if, and function call in every language ultimately lands. High-level control flow is compiled into conditional and unconditional writes to the program counter.
It is also why a crash can report an exact location. The counter is a real value the operating system can read out of the stopped process, which the next unit puts to work.