---
title: The Complete PIPE Processor
module: Pipelining
moduleNumber: 5
lessonNumber: 5
order: 505
summary: >
  We assemble the full pipelined Y86-64: five stages, five pipeline registers,
  forwarding paths, and a small control unit that decides, each cycle, whether to
  stall or bubble each register. The subtle part is when hazards combine: one
  pairing hides a genuine bug. A fourth control case reads stat and keeps
  exceptions precise. Performance reduces to CPI = 1 + lp + mp + rp,
  worked out to 1.27 with realistic frequencies, and PIPE beats SEQ by several
  times despite every penalty.
topics: [Pipelining]
sources:
  - book: Bryant & O'Hallaron
    ref: "CS:APP — §4.5 Pipelined Y86-64 Implementations"
---

PIPE now has its
[five stages and pipeline registers](/computer-architecture/pipelining/from-seq-to-pipe),
[forwarding and the load-use stall](/computer-architecture/pipelining/data-hazards-stalling-and-forwarding)
for data hazards, and
[prediction, squashing, and the ret stall](/computer-architecture/pipelining/control-hazards-and-branch-prediction)
for control hazards. This lesson bolts them together into one processor, works
through the control logic that orchestrates it (including the one place where
two hazards colliding exposed a real bug), and then does the accounting: how
close PIPE gets to the ideal of one instruction per cycle, and why real machines
pay to go deeper.

## The full PIPE overview

The complete datapath is the SEQ datapath with the F/D/E/M/W registers inserted
and two extra networks laid over it: the **forwarding paths** carrying late
results back to Decode, and the **control logic** watching every register for
hazard conditions. The stages still do their familiar jobs; the new wiring is
what makes overlap safe.

$$
% caption: The complete PIPE. Five stages separated by pipeline registers F D E M
% caption: W; forwarding paths (acc) carry results from Execute, Memory, and
% caption: Write-back back to Decode; the control logic (dotted) issues a stall or
% caption: bubble decision to each register every cycle.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  st/.style={draw, fill=acc!8, minimum width=15mm, minimum height=10mm,
             inner sep=1pt, align=center},
  reg/.style={draw, fill=black!10, minimum width=3mm, minimum height=12mm,
              inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  \node[reg] (F) at (0,0) {};    \node[anchor=south, text=acc] at (0,0.72) {\scriptsize F};
  \node[st]  (fe) at (1.05,0) {Fetch};
  \node[reg] (D) at (2.1,0) {};  \node[anchor=south, text=acc] at (2.1,0.72) {\scriptsize D};
  \node[st]  (de) at (3.15,0) {Decode};
  \node[reg] (E) at (4.2,0) {};  \node[anchor=south, text=acc] at (4.2,0.72) {\scriptsize E};
  \node[st]  (ex) at (5.25,0) {Execute};
  \node[reg] (M) at (6.3,0) {};  \node[anchor=south, text=acc] at (6.3,0.72) {\scriptsize M};
  \node[st]  (me) at (7.35,0) {Memory};
  \node[reg] (W) at (8.4,0) {};  \node[anchor=south, text=acc] at (8.4,0.72) {\scriptsize W};
  \node[st]  (wb) at (9.45,0) {Write\\back};
  % datapath wires along the row
  \draw (F.east)--(fe.west); \draw (fe.east)--(D.west); \draw (D.east)--(de.west);
  \draw (de.east)--(E.west); \draw (E.east)--(ex.west); \draw (ex.east)--(M.west);
  \draw (M.east)--(me.west); \draw (me.east)--(W.west); \draw (W.east)--(wb.west);
  % forwarding paths (below) from ex, me, wb back to Decode's south edge
  \draw[acc, thick, ->] (5.25,-0.5) .. controls (5.25,-1.05) and (3.55,-1.05) .. (3.55,-0.55);
  \draw[acc, thick, ->] (7.35,-0.5) .. controls (7.35,-1.45) and (3.15,-1.45) .. (3.15,-0.55);
  \draw[acc, thick, ->] (9.45,-0.5) .. controls (9.45,-1.85) and (2.75,-1.85) .. (2.75,-0.55);
  \node[anchor=north, text=acc] at (6.1,-1.95) {\scriptsize \texttt{forwarding: E, M, W values back to Decode}};
  % control unit above, dotted decision lines down toward each register
  \node[draw, fill=acc!8, inner sep=3pt] (ctl) at (4.2,1.85) {pip\/eline control logic};
  \foreach \x in {0.25, 2.35, 4.45, 6.55, 8.65}
    \draw[black, dotted, thick, ->] (4.2,1.62) -- (\x,1.02);
  \node[anchor=west, black] at (6.6,1.85) {\scriptsize stall or bubble, per register, per cycle};
\end{tikzpicture}
$$

## The pipeline control logic

The control unit's whole job is to make one decision per pipeline register per
cycle, using two control inputs each register now carries:

| `stall` | `bubble` | Effect at the clock edge |
| --- | --- | --- |
| 0 | 0 | **normal** — load the input, the instruction advances |
| 1 | 0 | **stall** — keep the current state, the instruction freezes |
| 0 | 1 | **bubble** — reset to the state of a `nop` |
| 1 | 1 | error — never allowed |

Everything the previous two lessons described reduces to patterns of these
signals. The three hazard conditions are detected by comparing a handful of
pipeline-register fields:

| Condition | Trigger |
| --- | --- |
| Processing `ret` | `IRET in { D_icode, E_icode, M_icode }` |
| Load/use hazard | `E_icode in { IMRMOVQ, IPOPQ } && E_dstM in { d_srcA, d_srcB }` |
| Mispredicted branch | `E_icode == IJXX && !e_Cnd` |
| Exception | `m_stat` or `W_stat` in `{ SADR, SINS, SHLT }` |

and each condition maps to a row of actions:

| Condition | F | D | E | M | W |
| --- | --- | --- | --- | --- | --- |
| Processing `ret` | stall | bubble | normal | normal | normal |
| Load/use hazard | stall | stall | bubble | normal | normal |
| Mispredicted branch | normal | bubble | bubble | normal | normal |
| Exception | normal | normal | normal | bubble | stall |

Read a row as the cycle's action. A **`ret`** freezes Fetch and bubbles Decode,
repeating for the three cycles until the return address surfaces. A **load/use**
hazard freezes Fetch _and_ Decode (so the consumer waits where it stands) and
bubbles Execute. A **mispredicted branch** bubbles Decode and Execute, squashing
the two wrong-path instructions, while Fetch proceeds normally; the PC
selection logic already has the fall-through address. The fourth row is
different in kind: an **exception** is not a hazard between instructions but a
promise about program order, and it gets its own section below.

## When hazards combine

Each row of that table assumes its condition fires alone, and a common design
bug is to stop there. During any given cycle _several_ conditions can hold at
once, and the control logic must produce one coherent action per register.
Enumerate the pairs. A load/use hazard cannot coincide with a mispredicted
branch: one needs a load in Execute, the other a jump there. But `ret` moves
through Decode, Execute, and Memory over three cycles, and while it sits in
Decode it can coincide with either condition in Execute:

$$
% caption: Pipeline states that trigger special control, as D/E/M snapshots. Most
% caption: pairs are mutually exclusive (they compete for the Execute slot), but a
% caption: ret in Decode can coincide with a mispredicted jump in Execute
% caption: (combination A) or with a load/use hazard on the ret itself (combination B).
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  cell/.style={draw, minimum width=13mm, minimum height=6.5mm, inner sep=1pt}]
  \definecolor{acc}{HTML}{2348F2}
  % row labels
  \node[anchor=east] at (-0.9,0) {M};
  \node[anchor=east] at (-0.9,-0.75) {E};
  \node[anchor=east] at (-0.9,-1.5) {D};
  % five scenario columns
  \foreach \x/\ttl in {0/{load/use}, 2.2/{mispredict}, 4.4/{ret 1}, 6.6/{ret 2}, 8.8/{ret 3}}
    \node[anchor=south] at (\x,0.5) {\scriptsize \ttl};
  % load/use: E=load, D=use
  \node[cell] at (0,0) {}; \node[cell, fill=acc!8] at (0,-0.75) {load};
  \node[cell, fill=acc!8] at (0,-1.5) {use};
  % mispredict: E=jXX
  \node[cell] at (2.2,0) {}; \node[cell, fill=acc!8] at (2.2,-0.75) {jXX};
  \node[cell] at (2.2,-1.5) {};
  % ret 1: D=ret
  \node[cell] at (4.4,0) {}; \node[cell] at (4.4,-0.75) {};
  \node[cell, fill=acc!8] at (4.4,-1.5) {ret};
  % ret 2: E=ret, D=bubble
  \node[cell] at (6.6,0) {}; \node[cell, fill=acc!8] at (6.6,-0.75) {ret};
  \node[cell, fill=black!8] at (6.6,-1.5) {bb};
  % ret 3: M=ret, E=bubble, D=bubble
  \node[cell, fill=acc!8] at (8.8,0) {ret};
  \node[cell, fill=black!8] at (8.8,-0.75) {bb};
  \node[cell, fill=black!8] at (8.8,-1.5) {bb};
  % combination brackets below
  \draw[acc] (2.2,-2.0) -- (2.2,-2.15) -- (4.4,-2.15) -- (4.4,-2.0);
  \node[anchor=north, text=acc] at (3.3,-2.2) {\scriptsize combination A};
  \draw[black] (0,-2.6) -- (0,-2.75) -- (4.4,-2.75) -- (4.4,-2.6);
  \node[anchor=north, black] at (2.2,-2.8) {\scriptsize combination B};
\end{tikzpicture}
$$

**Combination A** — a mispredicted jump in Execute whose (wrongly fetched)
target instruction is a `ret`, now in Decode. Merge the two action rows and the
requirements are compatible: stall F (from the `ret` row), bubble D, bubble E.
The bubbles cancel the `ret` (correctly, since it lives on the wrong path), and
the stalled F register turns out not to matter, because the PC selection logic
ignores the predicted PC and fetches the fall-through. Combination A works by
accident of good design; no extra logic needed.

**Combination B** — a load in Execute whose destination feeds the `ret` in
Decode. This is just a load/use hazard whose consumer happens to be `ret`
(which reads `%rsp` to pop the return address). Now merge the rows: load/use
says _stall_ D, `ret` says _bubble_ D. Both at once is the forbidden 1/1 input:
a broken register. The desired behavior is the load/use action alone: let the
`ret` wait one cycle for the forwarded stack pointer, and begin the usual
`ret` sequence a cycle late. So the `D_bubble` condition must explicitly
_exclude_ the load/use case:

```c [pipe-control.hcl]
bool F_stall =
    # load/use hazard: hold Fetch while the consumer waits in Decode
    E_icode in { IMRMOVQ, IPOPQ } && E_dstM in { d_srcA, d_srcB } ||
    # ret passing through Decode/Execute/Memory: hold Fetch
    IRET in { D_icode, E_icode, M_icode };

bool D_bubble =
    # mispredicted branch: squash the wrongly fetched instruction
    (E_icode == IJXX && !e_Cnd) ||
    # ret passing through - but NOT while a load/use hazard holds the ret
    !(E_icode in { IMRMOVQ, IPOPQ } && E_dstM in { d_srcA, d_srcB })
      && IRET in { D_icode, E_icode, M_icode };
```

Bryant and O'Hallaron report that their original control logic had exactly this
bug: it passed every simulation test, because no ordinary program pops into the
stack pointer right before a `ret`, and surfaced only under systematic analysis
of condition combinations. This generalizes: pipeline control bugs hide in
combinations of rare events that no test suite covers by luck. They are found by
enumerating cases, or much more expensively in silicon.

## Exceptions in a pipeline

Every pipeline register has carried a `stat` field since the
[registers were laid out](/computer-architecture/pipelining/from-seq-to-pipe),
and so far nothing has read it. It exists for the fourth control case: what to
do when an instruction's status is not `AOK`. Pipelining makes this genuinely
awkward, because an exception is _detected_ far from where it must _act_. Fetch
discovers an invalid opcode or an instruction-address fault three stages before
the faulting instruction would complete; by then its successors are already in
flight and its predecessors have not finished. Halting at the moment of
detection would be wrong twice over — younger, wrong-path instructions could
still change programmer-visible state, and older instructions the program did
reach would be cut off half done.

So PIPE does neither. A bad status rides along in `stat` like any other field,
and the control logic acts only when the excepting instruction nears the end of
the pipe: the trigger is `m_stat` or `W_stat` holding one of `SADR`, `SINS`,
`SHLT` — the HCL constants for the `ADR`, `INS`, and `HLT` status codes. From
that cycle on, the **M register is bubbled** every cycle, so nothing younger
ever enters Memory or Write-back: no memory write, no register write. One leak
remains — an `OPq` already sitting in Execute would still update the condition
codes this very cycle — so `set_cc` is gated on the same test:

```c [pipe-exceptions.hcl]
# an exception is in Memory or Write-back
bool exc_MW = m_stat in { SADR, SINS, SHLT } || W_stat in { SADR, SINS, SHLT };

# cancel everything younger: inject bubbles into Memory from now on
bool M_bubble = exc_MW;
# park the excepting instruction: its status is the machine's status
bool W_stall  = W_stat in { SADR, SINS, SHLT };

# a wrong-path OPq in Execute must not wreck the condition codes
bool set_cc = E_icode == IOPQ && !exc_MW;
```

Stalling W parks the excepting instruction in the last register, so the
processor's reported status (`Stat = W_stat`) keeps saying what went wrong and
the machine makes no further progress; in a full system this is the point where
a handler would take over.

The net effect is a clean cut at the excepting instruction's place in program
order. Everything **older** is deeper in the pipeline and drains normally —
those writes were owed to the program. Everything **younger** is squashed
before it touches programmer-visible state. And when two instructions in
flight both fault, the older one reaches Memory first, so its exception wins:
priority falls out of program order for free. To software the exception
appears to strike exactly at an instruction boundary, however messily the
pipeline had overlapped the work — the property the
[interrupt machinery](/computer-architecture/exceptions-and-io/interrupts-and-the-kernel)
of the exceptions module takes as its starting point.

> **Definition (Precise exceptions).** An implementation delivers exceptions
> precisely when, at the moment an exception is reported, every instruction
> before the faulting one has completed and no instruction after it has changed
> any programmer-visible state. The pipeline may have run arbitrarily far
> ahead; none of that speculative work may be visible.

## Performance: CPI = 1 + penalties

The ideal pipeline retires one instruction per cycle, a **CPI** (cycles per
instruction) of $1.0$. Bubbles are cycles in which the execute stage does no
useful work, so if a program executes $C_i$ instructions and the hazards inject
$C_b$ bubbles, the processor spends about $C_i + C_b$ cycles:

$$
\text{CPI} = \frac{C_i + C_b}{C_i} = 1.0 + \frac{C_b}{C_i}
= 1.0 + \text{lp} + \text{mp} + \text{rp},
$$

splitting the bubble rate into the three causes: load/use penalty (lp),
misprediction penalty (mp), return penalty (rp). Each term is the product of
three measurable numbers — instruction frequency $f$, condition frequency $c$,
and bubbles per event $b$:

$$
\text{penalty} = f \cdot c \cdot b.
$$

With frequencies typical of compiled code:

| Cause | Instruction frequency | Condition frequency | Bubbles | Product |
| --- | --- | --- | --- | --- |
| Load/use | 0.25 | 0.20 | 1 | **0.05** |
| Mispredict | 0.20 | 0.40 | 2 | **0.16** |
| Return | 0.02 | 1.00 | 3 | **0.06** |

A quarter of instructions are loads and a fifth of those feed the next
instruction; a fifth are conditional jumps and predict-taken misses 40% of
them; one in fifty is a `ret`, which always pays full price. Summing:
$\text{CPI} = 1.0 + 0.05 + 0.16 + 0.06 = 1.27$.

$$
% caption: CPI accounting for PIPE. Per 1000 instructions: 1000 base cycles plus
% caption: 50 load/use bubbles, 160 misprediction bubbles, and 60 ret bubbles,
% caption: totalling 1270 cycles, CPI 1.27. Mispredictions dominate the waste.
\begin{tikzpicture}[font=\footnotesize,>=stealth]
  \definecolor{acc}{HTML}{2348F2}
  % stacked horizontal bar, 4 units per 1.0 CPI
  \draw[draw, fill=acc!8]  (0,0)    rectangle (4.0,0.7);
  \node at (2.0,0.35) {base 1.0};
  \draw[draw, fill=acc!25] (4.0,0)  rectangle (4.2,0.7);
  \draw[draw, fill=acc!45] (4.2,0)  rectangle (4.84,0.7);
  \draw[draw, fill=acc!70] (4.84,0) rectangle (5.08,0.7);
  \node[anchor=west, text=acc] at (5.3,0.35) {CPI = 1.27};
  % staggered leader labels below, left to right, deeper as they go right
  \draw[black] (4.1,-0.02) -- (4.1,-0.5);
  \node[anchor=east, black] at (4.0,-0.7) {\scriptsize load/use +0.05};
  \draw[black] (4.52,-0.02) -- (4.52,-1.1);
  \node[anchor=east, black] at (4.42,-1.1) {\scriptsize mispredict +0.16};
  \draw[black] (4.96,-0.02) -- (4.96,-1.7);
  \node[anchor=west, black] at (5.06,-1.7) {\scriptsize ret +0.06};
\end{tikzpicture}
$$

> **Definition (CPI).** Cycles per instruction — the average number of clock
> cycles each instruction takes, measured over a program. A perfectly pipelined
> processor approaches CPI $= 1$; hazard penalties raise it. Throughput is
> $\text{clock rate} / \text{CPI}$.

The breakdown says where to spend effort. Mispredictions contribute 0.16 of the
0.27 total, more than the other two causes combined, because conditional jumps
are common, predict-taken misses often, and each miss costs double. Swap in the
BTFNT predictor (65% accuracy, so condition frequency 0.35) and mp drops to
$0.20 \times 0.35 \times 2 = 0.14$; a modern dynamic predictor at 95% cuts it to
$0.02$. A return-address stack all but erases rp the same way. The load/use term
is the compiler's to fix, by scheduling an independent instruction into the slot
after each load.

Combining these upgrades brings CPI toward the ideal. Keep lp and rp
fixed and vary only the branch predictor, then add a return-address stack that
drops the return penalty to near zero:

| Configuration | lp | mp | rp | CPI |
| --- | --- | --- | --- | --- |
| Predict-taken (baseline) | 0.05 | 0.16 | 0.06 | **1.27** |
| BTFNT static | 0.05 | 0.14 | 0.06 | **1.25** |
| Dynamic predictor (95%) | 0.05 | 0.02 | 0.06 | **1.13** |
| Dynamic + return-address stack | 0.05 | 0.02 | 0.00 | **1.07** |
| + compiler fills load slots | 0.01 | 0.02 | 0.00 | **1.03** |

Each row is a real design decision with a measurable payoff, and none touches the
datapath — they are all about _predicting better_ and _scheduling better_. From
1.27 down to 1.03 is a $19\%$ throughput gain layered on top of the raw
pipelining speedup, which is why so much microarchitecture effort goes into
prediction and compiler scheduling rather than the pipeline itself. The last
tenth of CPI is the most expensive to remove; out-of-order cores exist to
reach it.

## What did pipelining buy? PIPE versus SEQ

CPI 1.27 looks like a 27% loss, but the comparison that matters is against
SEQ. Compare the two processors end to end with illustrative delays.
[SEQ's](/computer-architecture/processor-design/assembling-seq) clock must span
the _entire_ worst-case instruction, fetch through write-back in one cycle, say
1000 ps of logic plus 20 ps to latch: 1020 ps per instruction, CPI exactly 1.
PIPE cuts the same 1000 ps into five 200 ps stages; its clock is $200 + 20 =
220$ ps, and each instruction costs $220 \times 1.27 \approx 279$ ps on average.

$$
% caption: Average time per instruction, SEQ versus PIPE, with 1000 ps of stage
% caption: logic and 20 ps registers. Even paying every hazard penalty (CPI 1.27),
% caption: PIPE completes instructions about 3.7 times faster.
\begin{tikzpicture}[font=\footnotesize,>=stealth]
  \definecolor{acc}{HTML}{2348F2}
  \node[anchor=east] at (-0.3,1.0) {SEQ};
  \draw[draw, fill=black!10] (0,0.7) rectangle (5.1,1.3);
  \node[anchor=west] at (5.3,1.0) {1020 ps};
  \node[anchor=east] at (-0.3,0.0) {PIPE};
  \draw[draw, fill=acc!25] (0,-0.3) rectangle (1.4,0.3);
  \node[anchor=west] at (1.6,0.0) {279 ps = 220 ps x 1.27};
\end{tikzpicture}
$$

The speedup is the ratio of per-instruction times,

$$
S = \frac{T_\text{SEQ}}{T_\text{PIPE}}
  = \frac{T_\text{SEQ}}{(T_\text{stage}+t_\text{reg}) \cdot \text{CPI}}
  = \frac{1020}{220 \times 1.27} \approx 3.7,
$$

short of the ideal $5\times$ that five stages promise. The shortfall factors
cleanly: register overhead shrinks the clock gain to $1020/220 = 4.6\times$, and
the CPI of 1.27 gives up the rest ($4.6/1.27 \approx 3.7$). Both losses were
predicted by the
[principles lesson](/computer-architecture/pipelining/pipelining-principles);
here they finally have exact prices.

## Why real pipelines go deeper — and what it costs

PIPE has five stages. Real processors run a dozen to twenty-odd, because more
stages mean shorter stages, a faster clock, and higher peak throughput. But
depth compounds exactly the costs this module has priced out.

- **Branch penalties scale with depth.** If the branch resolves at stage 12
  instead of stage 3, a misprediction squashes eleven instructions, not two. The
  mp term becomes $0.20 \times \text{miss rate} \times 11$, which is why deep
  pipelines lean on serious dynamic predictors (history tables, not
  predict-taken) to hold the miss rate near a few percent.
- **More forwarding paths and hazard cases.** Every added stage is another place
  a value can be in flight, so the forwarding network, the priority logic, and
  the combination analysis all grow, and the combination bugs multiply.
- **Register overhead and power.** The per-stage register tax caps the clock
  gain, and switching power rises with frequency; past some depth the energy
  cost outruns the throughput.

This is the central tension of pipelined design: throughput pulls toward more
stages, while hazard penalties and overhead pull toward fewer. PIPE's five
stages sit at the simple end of that trade, the right place to _understand_ the
mechanism; the same mechanism, scaled up and hardened with prediction and
out-of-order execution, is what makes a modern core fast.

## The modern out-of-order superscalar core

Every mechanism in this module reappears, enlarged, in a modern performance core,
and it is worth assembling the pieces the earlier closing sections named
into one picture. PIPE is **in-order, single-issue**: instructions execute in
program order, one per cycle, and a stall freezes everything behind it. A
contemporary core such as Intel's Golden Cove or Apple's Firestorm is **out-of-order
and superscalar**, and it earns its speed by relaxing both constraints.

- **Superscalar issue** (from the principles lesson's beyond-the-book): the core
  fetches, decodes, and retires several instructions per cycle — eight-wide decode
  is common — so the ideal CPI drops below 1. PIPE's single-issue ceiling of
  CPI $=1$ no longer applies; a wide machine retires four to six
  instructions in a good cycle.
- **Register renaming** (from the SEQ-to-PIPE beyond-the-book) maps architectural
  registers onto a large physical pool, erasing the false dependencies that would
  otherwise serialize instructions and giving each value a unique name — the same
  goal as PIPE's `D_stat`/`d_stat` naming discipline, automated for hundreds of
  in-flight instructions.
- **Dynamic scheduling with reservation stations** (from the data-hazards
  beyond-the-book) lets an instruction wait for its operands off to the side while
  independent younger instructions execute past it, so a cache-missing load no
  longer freezes the machine the way PIPE's load-use interlock does.
- **Aggressive branch prediction** (from the control-hazards beyond-the-book) —
  TAGE-class predictors and return-address stacks — keeps the deep speculation
  window full, because at a dozen-plus stages every mispredict is very expensive.
- **The reorder buffer** ties it together: instructions execute out of order but
  **retire** in program order, committing their results to architectural state
  only when every older instruction has. This is precisely PIPE's precise-exception
  property scaled up — the machine may run arbitrarily far ahead speculatively, but
  the programmer-visible state advances one instruction at a time, and a fault or
  mispredict rolls back everything younger. The reorder buffer is what makes
  hundreds of overlapping, reordered, speculative instructions still honor the
  ISA's one-at-a-time fiction (Smith and Pleszkun, 1985, ISCA, on in-order retirement;
  Hennessy and Patterson, _Computer Architecture: A Quantitative Approach_, ch. 3).

Everything in this module reappears at larger scale. Forwarding becomes the
common data bus; the naming discipline becomes register renaming;
predict-verify-recover becomes speculative execution with a reorder buffer;
precise exceptions become in-order retirement. PIPE is a faithful miniature:
small enough to understand completely, and its principles carry all the way up
to a core that overlaps five hundred instructions instead of five.

> **Takeaway.** The complete **PIPE** is SEQ's five stages plus the **F/D/E/M/W**
> registers, **forwarding** paths from E/M/W back to Decode, and a **control
> unit** issuing a stall-or-bubble decision per register per cycle. Single
> hazards map to simple action rows; **combinations** need explicit analysis:
> mispredict + `ret` composes safely, but load/use + `ret` would assert stall
> and bubble together, a real bug fixed by excluding load/use from `D_bubble`.
> The fourth control case reads `stat`: an exception in M or W bubbles M and
> stalls W, so older instructions finish, younger ones vanish, and the fault
> lands **precisely** at an instruction boundary.
> Performance is $\text{CPI} = 1 + \text{lp} + \text{mp} + \text{rp} = 1.27$
> with typical frequencies, dominated by mispredictions, yet still roughly
> $3.7\times$ faster than SEQ end to end.

That closes the processor: from a single transistor up through gates, a
datapath, the SEQ control, and now a pipelined PIPE that overlaps five
instructions while keeping the simple one-at-a-time model true. What remains is
feeding it fast enough — the memory hierarchy and caches of the next module.
