The fetch-decode-execute cycle in detail
Register transfer notation, the buses at each step, a full register trace, and a stored program that plays notes.
Do this lesson in the simulatorAt GCSE (F9.4) you described the cycle in three words: fetch, decode, execute. At A level you must describe it as a sequence of register transfers: exactly which value is copied into which register at each step, and what each bus carries. Exam questions give a starting state and ask you to fill in the registers as the cycle runs, so this lesson traces it step by step.
Register transfer notation
Textbooks and mark schemes write each step in a short notation:
| Notation | Means |
|---|---|
[PC] |
the contents of the PC |
MAR ← [PC] |
copy the contents of the PC into the MAR |
[Memory]addressed |
the contents of the memory location whose address is in the MAR |
PC ← [PC] + 1 |
add 1 to the PC's contents and put the answer back in the PC |
Square brackets mean "the contents of". Without them, PC names the register itself.
Fetch
| Step | Transfer | What happens on the buses |
|---|---|---|
| 1 | MAR ← [PC] |
the address of the next instruction is copied to the MAR, which puts it on the address bus |
| 2 | PC ← [PC] + 1 |
the PC is incremented so it points at the following instruction; this can happen at the same time as step 3 |
| 3 | MDR ← [Memory]addressed |
the control unit sends a memory read signal on the control bus; memory puts the instruction on the data bus, and it is copied into the MDR |
| 4 | CIR ← [MDR] |
the instruction is copied to the CIR, freeing the MDR for data during execution |
AQA writes MBR for the MDR; the steps are the same.
Decode
The control unit splits the instruction in the CIR into its opcode (what to do) and its operand (what to do it to). From the opcode it works out which sequence of control signals will carry the instruction out.
Execute
What happens now depends on the instruction. Four common ones:
| Instruction | Transfers |
|---|---|
LOAD 4: copy the value at address 4 into the accumulator |
MAR ← [CIR(operand)], then MDR ← [Memory]addressed, then ACC ← [MDR] |
ADD 5: add the value at address 5 to the accumulator |
MAR ← [CIR(operand)], then MDR ← [Memory]addressed, then ACC ← [ACC] + [MDR], done by the ALU |
STORE 6: copy the accumulator into address 6 |
MAR ← [CIR(operand)], then MDR ← [ACC], then [Memory]addressed ← [MDR], with a memory write signal on the control bus |
BRANCH 0: jump to address 0 |
PC ← [CIR(operand)]: the next fetch uses the new address |
A branch is just an execute step that writes to the PC. That is the whole mechanism behind every loop and if in every program.
After execute, the processor checks whether an interrupt is waiting (lesson A9.7), then starts the next fetch.
A full trace
Memory holds this program, starting at address 0:
| Address | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| Contents | LOAD 4 |
ADD 5 |
STORE 6 |
HALT |
20 | 22 | 0 |
The trace shows every register after each transfer. A blank means unchanged.
| Transfer | PC | MAR | MDR | CIR | ACC |
|---|---|---|---|---|---|
| start | 0 | 0 | 0 | ||
MAR ← [PC] |
0 | ||||
PC ← [PC] + 1 |
1 | ||||
MDR ← [Memory]addressed |
LOAD 4 |
||||
CIR ← [MDR] |
LOAD 4 |
||||
MAR ← [CIR(operand)] |
4 | ||||
MDR ← [Memory]addressed |
20 | ||||
ACC ← [MDR] |
20 | ||||
MAR ← [PC] |
1 | ||||
PC ← [PC] + 1 |
2 | ||||
MDR ← [Memory]addressed |
ADD 5 |
||||
CIR ← [MDR] |
ADD 5 |
||||
MAR ← [CIR(operand)] |
5 | ||||
MDR ← [Memory]addressed |
22 | ||||
ACC ← [ACC] + [MDR] |
42 | ||||
MAR ← [PC] |
2 | ||||
PC ← [PC] + 1 |
3 | ||||
MDR ← [Memory]addressed |
STORE 6 |
||||
CIR ← [MDR] |
STORE 6 |
||||
MAR ← [CIR(operand)] |
6 | ||||
MDR ← [ACC] |
42 | ||||
[Memory]addressed ← [MDR] |
|||||
MAR ← [PC] |
3 | ||||
PC ← [PC] + 1 |
4 | ||||
MDR ← [Memory]addressed |
HALT |
||||
CIR ← [MDR] |
HALT |
At the end, address 6 holds 42. Notice that the MAR and MDR change far more often than the PC: they are used for every memory access, instructions and data alike. Notice too that while HALT is in the CIR, the PC already holds 4: the PC always points one instruction ahead.
Here is the same machine in Python, printing each transfer as it happens:
memory = ["LOAD 4", "ADD 5", "STORE 6", "HALT", 20, 22, 0]
pc, mar, mdr, cir, acc = 0, 0, None, None, 0
while True:
mar = pc; print(f"MAR <- [PC] MAR={mar}")
pc = pc + 1; print(f"PC <- [PC] + 1 PC={pc}")
mdr = memory[mar]; print(f"MDR <- [Memory] MDR={mdr}")
cir = mdr; print(f"CIR <- [MDR] CIR={cir}")
opcode, *operand = cir.split()
if opcode == "HALT":
break
mar = int(operand[0]); print(f"MAR <- operand MAR={mar}")
if opcode == "STORE":
mdr = acc; print(f"MDR <- [ACC] MDR={mdr}")
memory[mar] = mdr; print(f"[Memory] <- [MDR] memory[{mar}]={mdr}")
else:
mdr = memory[mar]; print(f"MDR <- [Memory] MDR={mdr}")
acc = mdr if opcode == "LOAD" else acc + mdr
print(f"ACC <- ... ACC={acc}")
print("memory:", memory)
Memory-mapped output
A processor with no special output instructions can still control hardware. Some addresses are wired not to memory but to an I/O controller, so writing to that address sends a value to a device. This is memory-mapped I/O. In the task, the OUT instruction plays the accumulator as a note on the robot's piezo: a stored program making a sound.
Task: fetch in register transfers
The program below already decodes and executes. Write the fetch as four register transfers, in the order in the table above, using the variables pc, mar, mdr and cir. Straight after the fetch, before decoding, print the four registers in the form PC=1 MAR=0 MDR=LOAD 7 CIR=LOAD 7.
Memory holds LOAD 7, OUT, ADD 8, OUT, ADD 8, OUT, HALT, then the data 300 and 100. OUT plays the accumulator for 0.2 seconds, so a working fetch plays 300, 400 and 500 Hz and prints seven lines, the last PC=7 MAR=6 MDR=HALT CIR=HALT. The robot does not move.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
memory = ["LOAD 7", "OUT", "ADD 8", "OUT", "ADD 8", "OUT", "HALT", 300, 100]
pc, mar, mdr, cir, acc = 0, 0, 0, "", 0
while True:
# fetch: four register transfers, then print the registers
cir = "HALT"
# decode
parts = cir.split()
opcode = parts[0]
# execute
if opcode == "LOAD":
mar = int(parts[1]); mdr = memory[mar]; acc = mdr
elif opcode == "ADD":
mar = int(parts[1]); mdr = memory[mar]; acc = acc + mdr
elif opcode == "OUT":
tone(acc, 0.2)
elif opcode == "HALT":
break
Challenges
- Add a
BRANCH ninstruction that sets the PC, and make the program play its three notes twice. - Add
STORE nwith memory-mapped output: storing to address 99 plays the value as a note. - Write out the register transfers for executing
SUB 9.