Loading header...

Pipelining — Why Modern CPUs Cheat at Fetch-Decode-Execute

You learned the fetch-decode-execute cycle as a clean, sequential loop: fetch one instruction, decode it, execute it, then start the next. That model is true, but it's also the slow, honest version of the story — every real high-performance CPU since the 1980s has been quietly cheating on it, running several instructions' worth of fetch-decode-execute simultaneously, like an assembly line instead of one craftsman building a whole car from scratch before starting the next. This closing lesson is your forward-looking hook into that cheat — pipelining — and the strange new problems it creates.


Learning Objectives

By the end of this lesson, you should be able to:

  • explain why pipelining improves instruction throughput but not the latency of one instruction;
  • calculate the ideal cycle count for N instructions in a S-stage pipeline;
  • identify data, control, and structural hazards;
  • explain stalls, bubbles, forwarding, and flushes at a beginner-friendly level.

The Honest, Slow Version

flowchart LR classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef clk fill:#fef9c3,stroke:#ca8a04,color:#713f12 A["Fetch I1"]:::cpu --> B["Decode I1"]:::cpu --> C["Execute I1"]:::cpu --> D["Fetch I2"]:::clk --> E["Decode I2"]:::cpu --> F["Execute I2"]:::cpu

If each stage takes one clock cycle, instruction I2 doesn't even start being fetched until I1 has completely finished. Three instructions cost nine cycles. This is exactly the model from lesson 5 — and it leaves most of the CPU's hardware idle most of the time. While the ALU is busy executing I1, the instruction-fetch circuitry is sitting there, doing nothing, perfectly capable of fetching I2 right now.


The Cheat: Overlap the Stages

A pipeline splits instruction processing into discrete stages — classically five — and lets different instructions occupy different stages in the same clock cycle. Nobody waits for anybody else to fully finish.

The classic 5-stage RISC pipeline:

Stage Abbreviation What happens
Fetch F Read the next instruction word from memory
Decode D Identify opcode, read source registers, compute control signals (lesson 2)
Execute E ALU performs the operation (lesson 3), or compute a branch/memory address
Memory M Access data memory — load or store — if the instruction needs it
Writeback W Write the result back into the register file
flowchart TD classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef mem fill:#dcfce7,stroke:#16a34a,color:#14532d classDef bus fill:#f3e8ff,stroke:#9333ea,color:#581c87 classDef clk fill:#fef9c3,stroke:#ca8a04,color:#713f12 classDef per fill:#ffedd5,stroke:#ea580c,color:#9a3412 F["F: Fetch"]:::clk --> D["D: Decode"]:::cpu --> E["E: Execute"]:::cpu --> M["M: Memory"]:::mem --> W["W: Writeback"]:::bus

With five independent stages acting like five workstations on an assembly line, five different instructions can be in flight at once — one being fetched while another is decoded, while a third executes, while a fourth touches memory, while a fifth writes back its result, all in the very same clock cycle.


Watching It Fill Up

title "Pipeline fill, illustrative stage activity"
time start=0 end=6 unit=cycles divisions=6
I1: pulse label="I1 active F-D-E-M-W" low=0 high=1 at=0 width=5 color=#2563eb
I2: pulse label="I2 starts one cycle later" low=0 high=1 at=1 width=5 color=#16a34a
I3: pulse label="I3 starts two cycles later" low=0 high=1 at=2 width=4 color=#9333ea
I4: pulse label="I4 starts three cycles later" low=0 high=1 at=3 width=3 color=#ea580c
marker FULL at=4 label="steady overlap"

The waveform is illustrative rather than a transistor-level simulation. Each high region means the instruction is somewhere in the five-stage pipeline; the stage labels are explained in the table above.

After the pipeline fills (a brief startup delay), the CPU completes roughly one instruction per clock cycle in steady state, instead of one instruction per five cycles. That's the entire point of pipelining: it doesn't make any single instruction finish faster — I1 still takes 5 cycles start to finish — but it dramatically increases throughput, the rate at which instructions complete.

flowchart LR classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d A["Non-pipelined:\n5 cycles × N instructions"]:::warn --> B["Pipelined:\n5 + (N − 1) cycles\nfor N instructions"]:::cpu

For large N, that's nearly a 5x speedup, purely from keeping idle hardware busy — no faster clock, no smarter ALU, just better scheduling of work that was already there.


Worked Example: Ideal Pipeline Timing

If a pipeline has S stages and runs N independent instructions, the ideal cycle count is:

cycles = S + (N - 1)

For a 5-stage pipeline running 20 independent instructions:

cycles = 5 + (20 - 1) = 24 cycles

A non-pipelined design that takes 5 cycles per instruction would need:

cycles = 5 x 20 = 100 cycles

Ideal speedup:

speedup = 100 / 24 = 4.17x

It is not exactly 5x because the pipeline needs fill time at the beginning and drain time at the end. Hazards reduce the real speedup further.


The Catch: Hazards

Overlapping instructions only works cleanly if they're truly independent. The moment one instruction depends on another, the assembly-line metaphor breaks down, and the pipeline must either stall (insert a bubble — a wasted cycle) or get clever with forwarding. There are three classic categories.

Data hazards

flowchart TD classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d A["ADD R1, R2, R3\n(R1 = R2 + R3)"]:::cpu B["SUB R4, R1, R5\n(needs R1 — but R1\nisn't written back yet!)"]:::warn A --> B

If SUB enters Decode while ADD's result for R1 hasn't reached Writeback yet, SUB would read a stale value of R1. Real pipelines solve much of this with forwarding (routing the ALU's output directly back into the next instruction's Execute stage, bypassing the register file entirely) but some cases still force a stall.

Control hazards (branches)

flowchart TD classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d A["BEQ R1, R2, target"]:::cpu B{"Branch taken\nor not?\n(not known until\nExecute stage)"}:::warn C["Meanwhile, Fetch has\nalready grabbed the\nNEXT sequential instruction\n— maybe the wrong one!"]:::warn A --> B A -.->|Fetch races ahead| C

The pipeline must fetch something for the cycle right after a branch, but it doesn't yet know whether the branch will be taken — that's only resolved once the branch reaches Execute. Guess wrong, and every instruction fetched on the wrong path must be discarded (a "pipeline flush"), costing several wasted cycles. This is exactly why branch prediction exists in real CPUs — a topic for further study beyond this course.

Structural hazards

flowchart LR classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d A["Instruction in\nMemory stage\nneeds the data bus"]:::cpu B["Instruction in\nFetch stage\nALSO needs the\nsame bus, same cycle"]:::warn A -.->|conflict| B

If instruction and data memory share the same physical bus (recall the tri-state bus arbitration from lesson 9), a load/store instruction in the Memory stage can collide with a Fetch needing the same bus in the same cycle. Harvard-architecture designs sidestep exactly this by giving instruction and data memory separate buses — one of the deepest payoffs of a decision made all the way back in your very first Fundamentals lessons.


Stalls in Action

title "Load-use stall, illustrative bubble"
time start=0 end=6 unit=cycles divisions=6
LDR: pulse label="I1 load in pipeline" low=0 high=1 at=0 width=5 color=#2563eb
USE: pulse label="I2 waits one cycle" low=0 high=1 at=1 width=2 color=#16a34a
USE2: pulse label="I2 resumes after bubble" low=0 high=1 at=4 width=2 color=#16a34a
BUBBLE: pulse label="stall bubble" low=0 high=1 at=3 width=1 color=#dc2626
marker WAIT at=3 label="value not ready"

That flat, repeated -- segment is a bubble — a cycle where the pipeline stage does nothing useful, deliberately, because going forward would compute the wrong answer. Bubbles are the price pipelining pays for hazards; clever CPU design is largely about minimizing how often they're needed.


Why This Is the Right Place to Stop — For Now

Pipelining only makes sense once you've internalized two things this course already gave you: that the fetch-decode-execute cycle is a sequence of distinct hardware stages (lesson 5), and that hardwired control logic is what drives each stage without "thinking" (lesson 4). Pipelining is simply running several copies of that hardwired control logic, staggered in time, against several instructions at once. Everything beyond this — branch prediction, out-of-order execution, superscalar issue, speculative execution, multi-level caching — is built directly on top of the five-stage idea you just saw, and is exactly where a follow-up course on computer architecture would begin.


Key Takeaway

A modern CPU doesn't execute instructions one at a time any more than a factory builds one car at a time on an assembly line — it overlaps stages to turn "one instruction every five cycles" into "nearly one instruction every cycle," at the cost of carefully managing data, control, and structural hazards. You now understand, end to end, the journey from a reset vector forcing a known starting address, through opcode decoding, addressing modes, interrupts, shared buses, and finally the pipelined overlap that makes all of it fast — the complete internal life of a CPU, from power-on to full speed.

Common Mistakes

Mistake Correction
Saying pipelining makes one instruction finish faster One instruction still takes about S stages; throughput improves after fill
Ignoring pipeline fill and drain Use S + (N - 1) for ideal cycles, not simply N
Assuming all dependencies are solved by forwarding Load-use and some multi-cycle operations still need stalls
Thinking branch prediction changes correctness It guesses for speed; wrong-path work is flushed
Treating Harvard architecture as only a memory-size choice Separate instruction/data paths can remove structural fetch/data conflicts

Further Reading

  • Patterson and Hennessy, Computer Organization and Design, pipeline datapath and hazards chapters.
  • ARM Cortex-M technical reference manuals, for practical examples of simple embedded pipelines.
  • RISC-V unprivileged ISA and educational pipeline materials, for clean examples of five-stage pipeline behavior.

Mind Map

mindmap root((Pipelining)) Core concept Overlap stages Higher throughput Same instruction latency Five stages Fetch Decode Execute Memory Writeback Formulas Ideal cycles S plus N minus 1 Non pipeline S times N Speedup old over new CPI tends to 1 Hazards Data dependency Control branch Structural conflict Load use stall Fixes Forwarding Stall bubble Flush wrong path Separate buses Practical checks Count dependencies Check branch cost Find memory conflicts Measure real CPI Common mistakes Ignoring fill time Assuming no stalls Confusing latency throughput Forgetting branch flush