You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
The fetch-decode-execute (FDE) cycle is the heartbeat of every processor: the unending loop that turns stored bytes into useful work. This lesson traces the cycle as precise register transfers, follows the role of each register through every stage, explains how interrupts suspend and resume it, and surveys the factors that determine CPU performance.
This lesson develops OCR H446 section 1.1.1 (Structure and function of the processor). It sets out the three stages of the FDE cycle described in register-transfer terms, the contribution of each register (PC, MAR, MDR, CIR, ACC, status register) at each stage, the handling of interrupts via an interrupt service routine (ISR), and the principal factors affecting performance — clock speed, number of cores, cache, and pipelining. Pipelining is treated in detail in its own lesson; here it is summarised and cross-referenced.
The cycle is a continuous loop the processor repeats once per machine instruction:
flowchart LR
F["FETCH<br/>(get next instruction)"] --> D["DECODE<br/>(interpret opcode/operand)"] --> E["EXECUTE<br/>(carry it out)"]
E -- "check for interrupts, then next instruction" --> F
After execute, the processor checks for pending interrupts and then begins the next cycle.
The most rigorous way to describe the cycle — and the way that earns top marks — is in register-transfer notation (RTN), where [X] means "the contents of register X" and ← means "is copied into". The pseudocode below traces the fetch phase exactly:
; --- FETCH ---
MAR <- [PC] ; copy address of next instruction into MAR
PC <- [PC] + 1 ; increment PC (can occur in parallel with the read)
; CU asserts MEMORY READ on the control bus
MDR <- [Memory[MAR]] ; word at that address travels back over the data bus
CIR <- [MDR] ; move fetched instruction into the CIR ready to decode
Note that step 2, incrementing the PC, can overlap step 3 in real hardware because the PC's adder and the memory system are independent units — a useful timing point to make in an exam. The PC may still be overwritten later if the instruction turns out to be a branch.
| Step | Action | Register transfer |
|---|---|---|
| 1 | Address of next instruction copied to MAR | MAR ← [PC] |
| 2 | PC incremented to point past this instruction | PC ← [PC] + 1 |
| 3 | CU asserts MEMORY READ on the control bus | (control signal) |
| 4 | Word at MAR returns over the data bus to the MDR | MDR ← [Memory[MAR]] |
| 5 | Fetched instruction moved into the CIR | CIR ← [MDR] |
Exam Tip: Incrementing the PC (step 2) typically happens at the same time as the memory read, because the PC and memory are separate hardware. Stating this concurrency, and noting that a branch may later change the PC again, distinguishes a top-band answer.
During decode the control unit (CU) examines the instruction now in the CIR and works out what must happen.
; --- DECODE ---
split [CIR] into opcode + operand
interpret opcode ; e.g. LOAD / ADD / STORE / BRANCH
generate control signals ; route data; select ALU operation
if operand is a memory address:
MAR <- operand ; stage it for the execute read/write
An instruction is laid out as two fields:
| Opcode | Operand |
|---|
What happens in execute depends entirely on the opcode. The three common families are shown as register transfers.
; --- EXECUTE: ADD <addr> (direct addressing) ---
MAR <- operand ; address of the value to add
; CU asserts MEMORY READ
MDR <- [Memory[MAR]] ; fetch the operand value
ACC <- [ACC] + [MDR] ; ALU adds; result returns to ACC
update status flags ; Z, N, C, V set from the result
; --- LOAD <addr> ---
MAR <- operand
MDR <- [Memory[MAR]] ; after MEMORY READ
ACC <- [MDR]
; --- STORE <addr> ---
MAR <- operand
MDR <- [ACC]
Memory[MAR] <- [MDR] ; after MEMORY WRITE
; --- Unconditional branch ---
PC <- operand ; next fetch starts at the target
; --- Conditional branch (e.g. branch if zero) ---
if Zero flag is set:
PC <- operand ; take the branch
else:
(PC already points to the next instruction; fall through)
A conditional branch is where the status register earns its keep: the CU inspects a flag (here the Zero flag) and only overwrites the PC if the condition holds. This single mechanism is what makes loops and IF statements possible at machine level.
ADD 50Consider ADD 50 ("add the value held at memory address 50 to the accumulator"), with the instruction itself stored at address 10 and the value 7 held at address 50.
; FETCH
MAR <- [PC] ; MAR = 10
PC <- [PC] + 1 ; PC = 11
MDR <- [Memory[10]] ; MDR = "ADD 50" (MEMORY READ)
CIR <- [MDR] ; CIR = "ADD 50"
; DECODE
opcode = ADD, operand = 50 ; arithmetic; ALU needed; direct addressing
; EXECUTE
MAR <- 50
MDR <- [Memory[50]] ; MDR = 7 (MEMORY READ)
ACC <- [ACC] + [MDR] ; e.g. ACC 3 -> 10
update Z,N,C,V flags
If the next instruction were BRANCH 4 and the Zero flag were clear, execution would simply continue; if it were set, PC <- 4 and the cycle would resume from address 4.
A single instruction shows the mechanism, but the power of the cycle only appears across several instructions, where the PC marches forward, the accumulator carries a running value, and a branch redirects the flow. Consider a short program that doubles the value at address 90 and stores it back. Assume the program begins at address 0, with these instructions in memory:
| Address | Instruction | Meaning |
|---|---|---|
| 0 | LDA 90 | Load the contents of address 90 into the ACC |
| 1 | ADD 90 | Add the contents of address 90 to the ACC |
| 2 | STA 91 | Store the ACC into address 91 |
| 3 | HLT | Halt |
Suppose address 90 holds the value 6. The full register trace across all three working instructions is shown below. Watch how the PC advances by one after each fetch, how the ACC accumulates (0 → 6 → 12), and how the MAR and MDR are reused on every single instruction.
; ===== INSTRUCTION 1 : LDA 90 (PC starts at 0) =====
; FETCH
MAR <- [PC] ; MAR = 0
PC <- [PC] + 1 ; PC = 1
MDR <- [Memory[0]] ; MDR = "LDA 90" (MEMORY READ)
CIR <- [MDR] ; CIR = "LDA 90"
; DECODE -> opcode LDA, operand 90 (direct addressing)
; EXECUTE
MAR <- 90
MDR <- [Memory[90]] ; MDR = 6 (MEMORY READ)
ACC <- [MDR] ; ACC = 6
; ===== INSTRUCTION 2 : ADD 90 (PC now 1) =====
; FETCH
MAR <- [PC] ; MAR = 1
PC <- [PC] + 1 ; PC = 2
MDR <- [Memory[1]] ; MDR = "ADD 90" (MEMORY READ)
CIR <- [MDR] ; CIR = "ADD 90"
; DECODE -> opcode ADD, operand 90
; EXECUTE
MAR <- 90
MDR <- [Memory[90]] ; MDR = 6 (MEMORY READ)
ACC <- [ACC] + [MDR] ; ACC = 6 + 6 = 12
update Z,N,C,V flags ; result non-zero, positive: Z=0, N=0
; ===== INSTRUCTION 3 : STA 91 (PC now 2) =====
; FETCH
MAR <- [PC] ; MAR = 2
PC <- [PC] + 1 ; PC = 3
MDR <- [Memory[2]] ; MDR = "STA 91" (MEMORY READ)
CIR <- [MDR] ; CIR = "STA 91"
; DECODE -> opcode STA, operand 91
; EXECUTE
MAR <- 91
MDR <- [ACC] ; MDR = 12
Memory[91] <- [MDR] ; address 91 now holds 12 (MEMORY WRITE)
By the end, address 91 holds 12 — the doubled value — and the PC sits at 3, poised to fetch the HLT. Three points worth making in an exam are now concrete. First, the fetch phase is identical for every instruction; only the execute phase differs by opcode. Second, the direction of the data bus reverses on the STORE: instructions 1 and 2 read memory into the MDR, whereas instruction 3 writes memory from the MDR (MDR <- [ACC] then Memory[MAR] <- [MDR]), with the control unit asserting MEMORY WRITE rather than MEMORY READ. Third, had instruction 3 been a conditional branch with its condition met, PC <- operand would have overwritten the value just incremented during fetch — which is exactly why a branch can redirect the program even though the PC was already advanced.
An interrupt is a signal — from hardware or software — that demands the CPU's prompt attention, allowing it to respond to events without wastefully polling devices in a loop.
end of EXECUTE:
check the interrupt register/lines ; once per cycle
if an interrupt is pending and not masked:
push [PC] and working registers -> stack ; save state
PC <- address of the ISR for this interrupt ; vector table lookup
run the ISR ; service the device
pop saved registers and PC <- stack ; restore state
resume the interrupted program
The sequence diagram below shows the interaction between the four participants — the running program, the CPU, the interrupt vector table that maps each interrupt source to its handler address, and the stack that holds the saved context. Reading it top to bottom makes the save/restore symmetry impossible to miss: every push at the start has a matching pop at the end.
sequenceDiagram
participant Prog as Running Program
participant CPU
participant Stack
participant VT as Interrupt Vector Table
participant ISR as Interrupt Service Routine
Prog->>CPU: executing instructions
Note over CPU: device raises IRQ on control bus
CPU->>CPU: finish CURRENT FDE cycle
CPU->>CPU: check priority vs any running ISR
CPU->>Stack: push PC + working registers (save state)
CPU->>VT: look up handler address for this IRQ
VT-->>CPU: address of ISR
CPU->>ISR: PC <- ISR address; run handler
ISR-->>CPU: service device, then RETURN
CPU->>Stack: pop saved registers + PC (restore state)
CPU->>Prog: resume at the exact next instruction
How does the CPU know which handler to run? Under vectored interrupts the interrupting device supplies an identifying number (a vector) that indexes the interrupt vector table — a table in memory holding the start address of each device's ISR. The CPU multiplies the vector by the entry size, reads the handler address from the table, and loads it straight into the PC. This is far faster than the alternative polled-interrupt scheme, in which a single generic handler must interrogate each device in turn to discover who raised the signal. Vectoring turns "who needs me?" from a search into a direct table lookup.
Priority resolves simultaneous interrupts. Each interrupt source is assigned a priority level; when several are pending, the highest-priority one is serviced first. Crucially, a higher-priority interrupt may pre-empt a running lower-priority ISR — a nested interrupt. This is precisely where saving state on a stack (last-in, first-out) becomes essential: each pre-empted handler's context is pushed, and as the nested handlers complete, the stack unwinds in reverse order, restoring each suspended context exactly. A flat save area with no stack discipline would let one ISR overwrite another's saved registers, and correct resumption would be impossible. Some flags can also be masked (temporarily disabled) so that a critical section of code runs without interruption — at the cost of increased interrupt latency for any device that must wait.
| Step | Action |
|---|---|
| 1 | At the end of the current cycle, the CPU checks for pending interrupts |
| 2 | If one is pending (and higher priority than any running ISR), the CPU saves state — the PC and working registers — onto the stack |
| 3 | The PC is loaded with the ISR address, found via an interrupt vector table |
| 4 | The ISR runs, servicing the device or event |
| 5 | State is restored from the stack and the original program resumes exactly where it left off |
| Type | Example |
|---|---|
| Hardware interrupt | A keypress, a timer reaching zero, a disk signalling that a transfer is complete |
| Software interrupt | A system call to the OS, a divide-by-zero, an illegal-instruction trap |
When several interrupts arrive together, an interrupt priority scheme decides the order, and a higher-priority interrupt may pre-empt a running lower-priority ISR — this is a nested interrupt. Saving state on a stack (LIFO) is exactly what makes nesting work: each ISR's context is pushed and later popped in reverse order.
Exam Tip: For "describe what happens when an interrupt occurs", always include: (1) the current cycle finishes, (2) the CPU checks for interrupts, (3) state (PC + registers) is pushed to the stack, (4) the ISR address is loaded into the PC, (5) after the ISR, state is restored and the program resumes. Mentioning the priority check and the stack lifts the answer.
The system clock issues a steady square-wave of pulses that synchronise the CPU; each FDE stage needs at least one clock cycle.
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.