A four-stage pipeline stalls with one signal: when the output is blocked, stall goes high and every register in every stage holds its value. It is simple and it works. The pipeline grows to sixteen stages, each 256 bits wide, and the stall net becomes the slowest path in the block. No amount of logic optimisation fixes it, because the problem is not logic.
Why does a single stall signal stop scaling, and what replaces it?
A global stall is the obvious way to add back-pressure to a pipeline: one signal, computed where the blockage is, drives the enable of every pipeline register. Its problem is physical. Within one clock period it must travel back across the whole pipeline and fan out to every flip-flop of every stage: thousands of loads, needing a tree of buffers whose depth grows with the fan-out, plus wire across the full length. Add stages or width and the path gets longer, while the clock period does not.
An elastic pipeline replaces the single stall with a valid/ready handshake between every pair of stages. Each stage decides only for itself: "I can take a word if I am empty, or if the stage after me is taking mine". A blockage at the output then spreads backwards one stage per cycle rather than everywhere in one cycle. Each decision depends only on neighbours, so its timing path is short and stays the same length however deep the pipeline grows.
The model estimates, with illustrative delays, how long the global stall path is for pipelines of 2 to 32 stages, each 256 bits wide, against the fixed path of one stage's local ready logic.
"""Why one global stall signal stops scaling (illustrative numbers).
A global stall is computed at the end of a pipeline and must reach the enable of
every register in every stage within one clock period. Its fan-out grows with
depth x width, so it needs a buffer tree whose depth grows with the log of the
fan-out, plus wire delay across the pipeline. An elastic pipeline instead gives each
stage its own local ready, so the path length stays the same however deep it is.
"""
import math
T_DETECT = 0.20 # ns: logic that decides to stall
T_BUFFER = 0.06 # ns per level of a fan-out-of-4 buffer tree
T_WIRE_STAGE = 0.05 # ns of wire per stage the stall must travel back across
T_LOCAL = 0.35 # ns: one stage's local ready logic (elastic)
WIDTH = 256 # register bits per stage
print("stages global stall path elastic path")
rows = []
for depth in (2, 4, 8, 16, 32):
fanout = depth * WIDTH
levels = math.ceil(math.log(fanout, 4))
t_global = T_DETECT + levels * T_BUFFER + depth * T_WIRE_STAGE
rows.append((depth, t_global))
print(f"{depth:6d} {t_global:12.2f} ns {T_LOCAL:9.2f} ns")
assert all(a[1] < b[1] for a, b in zip(rows, rows[1:]))
print("PASS: the global stall path grows with depth; the elastic path does not")It prints:
stages global stall path elastic path
2 0.60 ns 0.35 ns
4 0.70 ns 0.35 ns
8 0.96 ns 0.35 ns
16 1.36 ns 0.35 ns
32 2.22 ns 0.35 ns
PASS: the global stall path grows with depth; the elastic path does notThe global path nearly quadruples from 2 to 32 stages, and past a few stages it is longer than one stage's whole logic budget. The elastic path does not change at all. That is the whole case for elastic pipelines: they turn a path that grows with the design into one that does not.
More logic per stage: each stage needs a valid bit and a small handshake.
Throughput needs care: a stage that cannot accept a word in the same cycle it hands one on halves the throughput (lesson 3.10).
The ready chain can still be long: if every stage's ready is combinational from the next one's, ready ripples through all stages in one cycle and the global-stall problem returns in disguise. Register slices and skid buffers break the chain (lessons 3.8 and 3.9).
Approach | What it costs | When it wins |
|---|---|---|
Global stall | One net with huge fan-out; slows with depth and width | Short, narrow pipelines |
Elastic, ready combinational through each stage | Ready chain as long as the pipeline | Short pipelines that need per-stage valid bits |
Elastic with registered ready every few stages (skid buffers) | One spare entry per registered boundary | Deep or wide pipelines; long on-chip routes |
Credits instead of ready (Part 4) | A counter and buffers sized to the round trip | Very long paths |
Where | Choice |
|---|---|
On-chip interfaces such as AXI | Per-channel valid/ready, so long routes can be pipelined with register slices without a global stall. |
Processor pipelines | Often a mix: local stalls within a few stages, decoupling buffers between groups of stages. |
A team converts a global-stall pipeline to valid/ready but makes every stage's ready combinational from the next (illustrative). Functionally it is now elastic; for timing, the ready signal from the output ripples through all sixteen stages in one cycle, and the path is as long as the old stall net. Inserting a skid buffer every four stages cut the chain into short pieces and closed timing, at the cost of one spare entry per boundary.
Its fan-out (depth × width) needs more buffer-tree levels, and it must travel back across more stages of wire. Both grow with the pipeline.
One stage per cycle, if each stage's ready is registered or local; in one cycle if readies are chained combinationally.
Ready from the output passes through every stage's logic in one cycle, so its path grows with the pipeline exactly as the stall net did.
A global stall must reach every register in one cycle; its path grows with depth and width until it limits the clock.
Elastic pipelines use a handshake between neighbouring stages, so back-pressure travels one stage per cycle and paths stay short.
A chain of combinational readies recreates the problem; break it with registered boundaries (skid buffers).