The Pipelined Datapath
Imagine a laundromat with one washer, one dryer, and one folding table. It serves five loads of laundry. If every load must finish washing, drying, and folding before the next starts, the machines sit idle most of the time. A pipelined laundromat instead starts washing load 2 the moment load 1 moves to the dryer. The hardware analog of this idea is the pipelined datapath. A single-cycle processor's five stages — fetch, decode, execute, memory, writeback — are turned into five overlapping stations. Each station works on a different instruction in the same clock cycle. This chapter is about a specific engineering problem. It concerns cutting a single-cycle datapath into stages. It also concerns stitching it back together correctly. The classic five-stage RISC pipeline (as in MIPS or RISC-V teaching cores) does not change what each stage computes. Instead, it changes when and how information flows between stages. The central mechanism is the pipeline register. This is a bank of flip-flops placed between every pair of adjacent stages. It latches every signal — data values, computed addresses, and control bits — an instruction will need in later stages. Why does this matter for real systems? Every general-purpose CPU shipped in the last four decades uses pipelining. This includes embedded ARM cores and Intel and AMD superscalar giants. Pipelining raises clock frequency and instruction throughput. The five-stage pipeline is the pedagogical skeleton on which all deeper pipelining is built. Modern designs use 10–20+ stages. Understanding exactly how pipeline registers replicate and forward control information matters greatly. It is the prerequisite for understanding hazards, stalls, and forwarding. These are the mechanisms that keep pipelines correct when instructions interact. This article builds the pipelined datapath from the single-cycle datapath by mechanical transformation. First, insert registers. Then, replicate control signals stage by stage. Finally, verify that each instruction's information arrives exactly when its consuming stage needs it. We verify the design with a cycle-by-cycle trace. We also expose the structural assumptions (independent instructions, no hazards) that later chapters will challenge and repair.
- The pipelined datapath is the single-cycle datapath split into five stages: IF, ID, EX, MEM, WB.
- Pipeline registers (IF/ID, ID/EX, EX/MEM, MEM/WB) latch data and control between stages every cycle.
- Control signals are generated once in decode and then carried alongside the instruction through later pipeline registers.
- With five overlapping stages, ideal throughput is one instruction completed per cycle after fill.
- Latency per instruction does not shrink — it usually grows slightly — but throughput improves dramatically.
- This design assumes no hazards; correctness under overlapping dependent instructions is the subject of later chapters.
Pipelining a datapath means inserting registers between stages that carry both data and control forward in lockstep with the instruction that produced them.
The idea
A single-cycle datapath computes every instruction in one long clock cycle, sized to accommodate the slowest instruction (typically a load: register file read, ALU address computation, memory access, and register file writeback all in series). This wastes time on every faster instruction and, more importantly, forbids starting a new instruction until the current one fully finishes. Pipelining breaks this monolithic cycle into a sequence of stages, each doing a bounded amount of work, so that a new instruction can enter the first stage every cycle while earlier instructions occupy later stages. The five classic stages are: Instruction Fetch (IF) — read the instruction from memory and increment the program counter; Instruction Decode / Register Fetch (ID) — decode the opcode, read source registers, and generate control signals; Execute (EX) — perform the ALU operation or compute a branch/memory address; Memory Access (MEM) — read or write data memory; Writeback (WB) — write a result back into the register file. Each stage is a combinational block bounded by the same clock period, sized to the slowest single stage rather than the slowest whole instruction — this is precisely why pipelining can increase clock frequency. The mechanism that makes this legal is the pipeline register, a set of edge-triggered flip-flops inserted between each pair of adjacent stages. A pipeline register does two jobs simultaneously: it holds the data an instruction has computed so far (e.g., the ALU result, the destination register number, a memory address), and it holds the control signals that later stages will need to know how to treat that data (e.g., "this is a store," "write this register," "branch was taken"). Without control signals traveling in the same registers as the data, a later stage would have no way to know which instruction's data it is looking at or what to do with it — this is the single most important design discipline in building a correct pipelined datapath.
How it works
Start from a single-cycle datapath: PC, instruction memory, register file, sign extender, ALU, data memory, and the control unit that decodes the opcode into control signals. The transformation into a pipeline proceeds in four mechanical steps.
Step 1 — Draw stage boundaries. Partition the combinational datapath into five sequential blocks matching IF, ID, EX, MEM, WB, in the order data naturally flows.
Step 2 — Insert pipeline registers. Between every adjacent pair of stages, insert a register: IF/ID, ID/EX, EX/MEM, MEM/WB. Each register is as wide as everything that must survive from that boundary onward.
Step 3 — Replicate control forward. The control unit lives in ID (it decodes the opcode once). But EX, MEM, and WB each need some of those control bits. So the control signals themselves are stored in ID/EX, then copied into EX/MEM, then copied into MEM/WB — control signals ride the pipeline exactly like data, just carrying fewer bits as stages that no longer need them are dropped.
Step 4 — Verify per-instruction consistency. At every clock edge, the values latched into a pipeline register must all belong to the same instruction. This is the invariant that makes the pipeline correct in the absence of hazards (hazards are handled by separate mechanisms covered elsewhere). Below is a stage-by-stage account of exactly what crosses each register, followed by pseudocode for one iteration of the pipeline's clocked behavior. ``` // State held in pipeline registers (conceptually, one struct per boundary) struct IF_ID { instruction, PC_plus_4 } struct ID_EX { PC_plus_4, readData1, readData2, signExtImm, rs, rt, rd, controlSignals_EX_MEM_WB } struct EX_MEM { branchTarget, zeroFlag, aluResult, writeDataForStore, destReg, controlSignals_MEM_WB } struct MEM_WB { memReadData, aluResult, destReg, controlSignals_WB } // One simulated clock cycle, in program order per stage. // All five stages execute "simultaneously": read old register values, // compute new ones, latch on the same clock edge. function CLOCK_TICK(regs): // ---- WB stage: consumes MEM_WB, no output register (writes regfile) ---- if regs.MEM_WB.controlSignals_WB.RegWrite: writeBackData = regs.MEM_WB.controlSignals_WB.MemToReg ? regs.MEM_WB.memReadData : regs.MEM_WB.aluResult RegisterFile.write(regs.MEM_WB.destReg, writeBackData) // ---- MEM stage: consumes EX_MEM, produces next MEM_WB ---- ctrl_MEM = regs.EX_MEM.controlSignals_MEM_WB memReadData = ctrl_MEM.MemRead ? DataMemory.read(regs.EX_MEM.aluResult) : undefined if ctrl_MEM.MemWrite: DataMemory.write(regs.EX_MEM.aluResult, regs.EX_MEM.writeDataForStore) next_MEM_WB = { memReadData, regs.EX_MEM.aluResult, regs.EX_MEM.destReg, ctrl_MEM.WB_bits } // ---- EX stage: consumes ID_EX, produces next EX_MEM ---- ctrl_EX = regs.ID_EX.controlSignals_EX_MEM_WB aluInputB = ctrl_EX.ALUSrc ? regs.ID_EX.signExtImm : regs.ID_EX.readData2 aluResult = ALU(regs.ID_EX.readData1, aluInputB, ctrl_EX.ALUOp) zeroFlag = (aluResult == 0) branchTarget = regs.ID_EX.PC_plus_4 + (regs.ID_EX.signExtImm << 2) destReg = ctrl_EX.RegDst ? regs.ID_EX.rd : regs.ID_EX.rt next_EX_MEM = { branchTarget, zeroFlag, aluResult, regs.ID_EX.readData2, destReg, ctrl_EX.MEM_WB_bits } // ---- ID stage: consumes IF_ID, produces next ID_EX ---- instr = regs.IF_ID.instruction ctrl = MainControl(opcode(instr)) // generated fresh each cycle readData1 = RegisterFile.read(rs(instr)) readData2 = RegisterFile.read(rt(instr)) signExtImm = SignExtend(immediate(instr)) next_ID_EX = { regs.IF_ID.PC_plus_4, readData1, readData2, signExtImm, rs(instr), rt(instr), rd(instr), ctrl } // ---- IF stage: fetches new instruction, produces next IF_ID ---- instructionWord = InstructionMemory.read(PC) next_IF_ID = { instructionWord, PC + 4 } PC = PC + 4 // (branch resolution is handled in EX/MEM in later chapters) // ---- Latch all pipeline registers simultaneously ---- regs.IF_ID = next_IF_ID regs.ID_EX = next_ID_EX regs.EX_MEM = next_EX_MEM regs.MEM_WB = next_MEM_WB return regs ``` Notice the update order in the pseudocode: WB is computed from old register state before EX computes new state, and so on down to IF — this mirrors real hardware, where all five stages read their input pipeline register (set at the end of the previous cycle) and compute new values that are latched into their output pipeline register at the end of the current cycle, all in parallel. The pseudocode processes them in reverse pipeline order (WB first) purely so that a straight-line simulation can read old values before they are overwritten; in real hardware there is no such ordering — everything happens combinationally and simultaneously between two clock edges.
Complexity & correctness
Throughput and latency. Let a single-cycle datapath require a clock period sized to the slowest instruction's total combinational delay, and let a pipelined design split that same work into stages, each with delay , plus a fixed register overhead (setup/hold + clock skew) per stage boundary. The pipelined clock period is
For instructions, single-cycle execution takes . Pipelined execution takes
As , throughput approaches one instruction per , and since (a fifth of the work, roughly), overall speedup approaches a factor near in the ideal case — though in practice, imperfect stage balancing and register overhead reduce this. Notice that per-instruction latency does not decrease (it is , generally slightly more than due to the added register delays); pipelining improves throughput, not the time to execute any single instruction.
Space. The additional hardware cost is the pipeline registers themselves: four registers whose combined width equals roughly the sum of all data and control signals live at each boundary — a modest, fixed overhead, independent of , that does not asymptotically grow with program size. This is extra state relative to the single-cycle datapath's combinational logic.
Correctness argument. The design is correct as long as the following invariant holds at every clock edge: > Every pipeline register, at every cycle, contains data and control signals belonging to exactly one instruction, consistent with that instruction's position in the pipeline. We can verify this invariant inductively. Base case: before any instruction enters, all pipeline registers hold a designated "bubble" (no-op) state, trivially satisfying the invariant (there is no instruction to be inconsistent about). Inductive step: assume at the start of cycle , each pipeline register correctly holds instruction 's state for the appropriate (the instruction that is stages ahead of instruction ... more simply: register holds the instruction that has completed exactly stages). During cycle , each stage reads only its own input register (never another stage's), computes a pure function of that data plus global unchanging state (like the register file, which is separately guarded — see below), and writes only its own output register. Since the write to every register happens on the same clock edge, and each stage's computation depended only on data that was itself consistently tagged to one instruction, the newly latched registers again satisfy the invariant, now advanced by one stage per instruction. By induction, the invariant holds at every cycle. The one shared piece of global state, the register file, deserves a specific correctness note: with a single write port active only in WB and read ports active in ID, this design is only fully sound for straight-line, mutually independent instructions. If an instruction in ID needs a value that an earlier, still-in-flight instruction will write back later, the naive register file read in ID sees stale data — this is exactly the data hazard problem, addressed by forwarding and stalling logic in later material. Likewise, if two instructions want the same functional unit or memory port in the same cycle, that is a structural hazard; the datapath described here assumes separate instruction and data memories precisely to avoid one common structural hazard by construction. This article's correctness proof therefore comes with an explicit caveat: it establishes only that control and data are threaded consistently through the pipeline registers, assuming no cross-instruction dependency conflicts — the deeper correctness guarantees under hazards are the subject of the Data Hazards and Forwarding treatment.
Worked example
Trace four instructions through the pipeline, assuming no hazards between them (chosen so writeback and reads never collide): ``` I1: add s1, t1, s4 I3: lw s5) I4: or s6, $s7 ```
| Cycle | IF | ID | EX | MEM | WB |
|---|---|---|---|---|---|
| 1 | I1 | – | – | – | – |
| 2 | I2 | I1 | – | – | – |
| 3 | I3 | I2 | I1 | – | – |
| 4 | I4 | I3 | I2 | I1 | – |
| 5 | – | I4 | I3 | I2 | I1 |
| 6 | – | – | I4 | I3 | I2 |
| 7 | – | – | – | I4 | I3 |
| 8 | – | – | – | – | I4 |
Follow I3 (`lw s5)`) specifically to see what rides in each pipeline register:
- Cycle 3 (IF): PC fetches the `lw` instruction word; `IF/ID` latches `{instruction = lw..., PC+4}` at the end of cycle 3.
- Cycle 4 (ID): Control unit decodes opcode `lw`, sets `RegDst=0, ALUSrc=1, MemToReg=1, RegWrite=1, MemRead=1, MemWrite=0, Branch=0`. Register file reads `s5`. Sign-extends the offset `0`. `ID/EX` latches `{readData1=s5, signExtImm=0, rt=$t2, all control bits}` at end of cycle 4.
- Cycle 5 (EX): ALU computes `s5 + 0` (the effective address). `EX/MEM` latches `{aluResult=address, destReg=t2, control bits for MEM and WB only: MemRead=1, MemToReg=1, RegWrite=1}` — note `ALUSrc` and `RegDst`, needed only in EX, are not propagated further; they are consumed and dropped.
- Cycle 6 (MEM): `MemRead=1` triggers a data memory read at the latched address. `MEM/WB` latches `{memReadData, destReg=$t2, control bits for WB only: MemToReg=1, RegWrite=1}`.
- Cycle 7 (WB): `MemToReg=1` selects `memReadData` (not `aluResult`) as the value written into `$t2`.
This traces the exact claim from the "How it works" section: control signals shrink as they pass through registers (5 stage's worth in ID/EX down to 1 bit's worth of relevance by MEM/WB), because each later stage only needs the subset of control bits still relevant to it. Meanwhile I1 finishes writeback in cycle 5, two cycles before I3 even reaches MEM — instructions genuinely overlap, and (since none of I1–I4 depend on each other's registers) no stall is required. Total time: 8 cycles for 4 instructions, matching exactly.
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Forgetting to carry a control signal into a later pipeline register | Designer assumes a stage "already used" the signal and drops it too early | Trace every control bit to its last consuming stage before removing it from the register |
| Re-decoding control signals in every stage instead of passing them forward | Seems to save register bits, but risks decoding a different instruction's opcode if IF/ID has since advanced | Decode once in ID; propagate decoded bits, never the raw opcode, downstream |
| Assuming PC+4 computed in IF is still valid by the time a branch resolves in EX/MEM | Confusing which cycle's PC belongs to which instruction | Carry PC+4 (or branch target components) explicitly through ID/EX and EX/MEM registers, tagged to that instruction |
| Treating pipeline registers as if they update at different times per stage | Misreading the diagram as sequential rather than simultaneous | All four registers latch on the same clock edge; think of five stages as parallel hardware, not five sequential function calls |
| Ignoring the register-file read/write ordering within one cycle | Read and write to the same register number in the same cycle is legal only with defined half-cycle ordering | Specify explicitly whether WB's write happens in the first half of the cycle so ID's read in the second half sees it (a common real convention) |
Pioneers & history
The pipelined datapath as taught today crystallized from decades of practice in scaling processor throughput without a proportional cost increase in circuit complexity, culminating in the RISC design philosophy of the 1980s that made regular, evenly-staged pipelines practical to teach and build.
Origins · 1 minJohn L. Hennessy and how this was discovered
Hennessy co-developed the MIPS architecture, whose clean, orthogonal instruction set was explicitly designed to make five-stage pipelining straightforward — every instruction fits neatly into IF/ID/EX/MEM/WB with minimal special-casing. His textbook collaboration with David Patterson established the pedagogical framework (this five-stage pipeline diagram, its pipeline registers, and its hazard vocabulary) used in nearly every computer architecture course since.
Where this matters
Every mainstream CPU core — from a microcontroller's simple in-order pipeline to a superscalar out-of-order server chip — descends conceptually from this five-stage skeleton, even when the real pipeline has ten, fifteen, or twenty stages instead of five. RISC-V teaching cores (like the widely used single-issue in-order RISC-V pipeline implementations), ARM Cortex-M microcontroller cores, and early MIPS and SPARC workstations all implement close variants of exactly this IF/ID/EX/MEM/WB structure. Understanding how control signals must be threaded through pipeline registers is a direct prerequisite for understanding structural hazards (what happens when two stages want the same resource simultaneously) and data hazards (what happens when a later stage needs a value an earlier, still-in-flight instruction has not yet produced) — both of which require additional hardware (forwarding paths, stall/bubble logic, or hazard-detection units) layered directly onto the datapath and pipeline registers built here. Compiler instruction scheduling, branch prediction, and superscalar issue logic in real high-performance processors are all elaborations that assume this basic overlapped-stage machinery as their foundation, alongside broader ideas from Introduction to Pipelining and Single-Cycle Control.
Ask the tutor: How does the hazard detection unit modify this pipeline register scheme when a load is immediately followed by a dependent instruction?
Why does pipelining not reduce the time to execute a single instruction?
Pipelining overlaps different instructions in different stages of the same clock cycle; it does not shorten how many stages any one instruction must pass through. A single instruction still travels through IF, ID, EX, MEM, and WB, each taking one clock cycle, so its own latency is still five cycles (plus, in practice, a small addition from pipeline register delay at each boundary). What changes is that four other instructions can be in flight at the same time, so once the pipeline is full, one instruction completes every single cycle rather than every five. The benefit is throughput — instructions per second — not latency, which is why we describe pipelining as raising instruction throughput rather than speeding up any individual instruction.
Why must control signals be regenerated in ID and then only passed forward, never regenerated later?
Only the ID stage has direct access to the freshly fetched instruction's opcode via the IF/ID register; by the time an instruction reaches EX, MEM, or WB, the original instruction word may have already been overwritten in IF/ID by a newer instruction fetched in a later cycle. If a later stage tried to "re-decode" using IF/ID, it would decode the wrong instruction entirely — the one currently in fetch, not the one it's supposed to be processing. The only safe approach is to decode once, in ID, and carry the resulting control bits forward through ID/EX, EX/MEM, and MEM/WB, exactly tagged to the instruction they belong to.
Do all control signals need to be carried all the way to WB?
No — only the subset each downstream stage actually consumes needs to persist that far. For example, `ALUSrc` and `RegDst` are needed only in EX (to pick the ALU's second operand and the destination register number), so they can be dropped after EX/MEM. `MemRead` and `MemWrite` are needed only in MEM, so they're dropped after EX/MEM as well (having been used), while `MemToReg` and `RegWrite` are needed as late as WB, so they must survive all the way through EX/MEM and MEM/WB. Designing pipeline register widths correctly means tracing each control signal to its last consuming stage and stopping there — carrying unused bits further wastes area without adding correctness.
Why does the design assume separate instruction and data memories?
If instructions and data shared a single memory with one port, then in any cycle where one instruction is fetching (IF) while an earlier instruction is simultaneously accessing data memory (MEM), both would need the same memory port at once — a structural hazard. By using two physically or logically separate memories (instruction memory and data memory, as in a Harvard-style split), the datapath sidesteps this specific structural hazard by construction, which is why the pipeline diagram in this article draws them as separate blocks. Other structural hazards (e.g., a single unpipelined multiplier needed by two instructions at once) still require explicit handling, covered separately.
What happens during pipeline fill and drain, and does that hurt performance?
Pipeline fill is the first cycles, during which the pipeline is not yet full (later stages are idle because no instruction has reached them yet); drain is the analogous tail after the last instruction is fetched, while earlier stages sit idle waiting for the last instructions to finish. Both contribute the "" term in the total cycle count , representing fixed overhead independent of program length. For long programs (large ), this overhead becomes negligible relative to , which is why pipelining's benefits scale well for realistic workloads, even though short programs see comparatively less speedup.
Is the pipelined datapath ever slower than the single-cycle datapath?
For a single isolated instruction, yes in latency terms — pipeline register delay adds a small fixed overhead per stage, so total latency for one instruction is slightly higher than the single-cycle version. But this article's core claim is about throughput over a full program: because the pipelined clock period is close to (each stage does roughly a fifth of the work), and because most real programs contain far more than five instructions, the amortized throughput gain dominates. The design only becomes a net loss if (register overhead) is large relative to stage delay, or if hazards (covered in later material) force frequent stalls that erase the overlap benefit.