Resolving hazards
Forwarding
An ALU result exists at the end of EX, but a simple pipeline waits until WB to write it to the register file, two cycles later. Forwarding (also called bypassing) adds multiplexers in front of the ALU that can take an operand straight from the EX/MEM or MEM/WB pipeline register instead of the register file. With it, back-to-back dependent ALU instructions run with no stalls at all.
A forwarding unit compares register numbers carried in the pipeline registers. For the first ALU operand, in the textbook five-stage RISC-V design:
if (EX/MEM.RegWrite and EX/MEM.rd != 0 and EX/MEM.rd == ID/EX.rs1)
ForwardA = from EX/MEM
else if (MEM/WB.RegWrite and MEM/WB.rd != 0 and MEM/WB.rd == ID/EX.rs1)
ForwardA = from MEM/WB
else
ForwardA = from register file
The same logic, with rs2, drives the second operand. EX/MEM is checked first because it holds the more recent value. The check for rd != 0 keeps writes to the hardwired zero register from being forwarded.
Hazard detection
Forwarding cannot send a value backward in time. A load's data arrives at the end of MEM, but an instruction right behind it needs that value at the start of EX in the same cycle. This load-use hazard needs one stall. A hazard detection unit in ID spots it:
if (ID/EX.MemRead and (ID/EX.rd == IF/ID.rs1 or ID/EX.rd == IF/ID.rs2))
hold the PC and the IF/ID register
clear the control signals entering ID/EX
Holding PC and IF/ID makes the two younger instructions repeat their stages; clearing the control signals turns the slot behind the load into a bubble that writes nothing. One cycle later the forwarding unit supplies the loaded value from MEM/WB.
Hazard detection predates RISC. The CDC 6600 (1964) used a scoreboard to track which functional unit would write each register and to hold instructions until their operands were ready. Tomasulo's algorithm for the IBM 360/91 went further, letting later independent instructions proceed past a stalled one, the root of today's out-of-order processors.
Pipeline simulator
Edit the RISC-V code or load an example, then switch forwarding on and off. With forwarding on, the only remaining stalls are load-use hazards caught by the hazard detection unit. Supported: add sub and or xor sll srl sra slt sltu mul div rem, the matching -i forms, and loads and stores.
Assumes separate instruction and data memories, straight-line code, register writes in the first half of a cycle and reads in the second, and store data forwarded into MEM.
Compiler scheduling
The "Unscheduled" example computes a = b + e; c = b + f; in source order and hits two load-use stalls: 13 cycles even with forwarding. In "Scheduled", the compiler moves the third load up so that an independent instruction sits between each load and its use: 11 cycles, no stalls, same result. The hardware still checks for hazards; the compiler simply arranges the code so the checks rarely fire.
Handling branches
| Technique | How it works | Cost per branch |
|---|---|---|
| Stall | Stop fetching until the branch resolves in EX | 2 cycles, always |
| Resolve in ID | Add a comparator and target adder to the decode stage | 1 cycle, but needs forwarding into ID and may lengthen the cycle |
| Predict not taken | Keep fetching sequentially; flush the wrong instructions if the branch is taken | Penalty only on taken branches |
| Delayed branch | The ISA defines the instruction after a branch as always executing; the compiler fills that slot | 0 if the slot is filled usefully; ties the ISA to one pipeline |
| Dynamic prediction | Hardware predicts direction and target from past behavior | Penalty only on mispredictions |
With 15% branches, 60% of them taken, and a 2-cycle penalty: stalling costs 0.15 × 2 = 0.30 cycles per instruction; predict not taken costs 0.15 × 0.6 × 2 = 0.18; a predictor that is 92% accurate costs 0.15 × 0.08 × 2 = 0.024.
Dynamic prediction
A branch history table, indexed by low bits of the branch address, holds a 2-bit saturating counter for each entry. Two wrong guesses in a row are needed to change the prediction, so a loop branch that is taken many times and falls through once costs one misprediction per loop rather than two. A branch target buffer supplies the target address during fetch, so a predicted-taken branch costs nothing when the prediction is right.
Later designs use history. Two-level predictors (Yeh and Patt) index counters by the recent outcomes of a branch or of all branches, capturing patterns and correlations a single counter cannot. Current high-end cores use descendants of TAGE (Seznec) and perceptron predictors (Jiménez and Lin), with accuracies well above 95% on typical code.
Predictor simulator
Type a sequence of branch outcomes (T for taken, N for not taken) or pick a pattern. Filled cells were correct predictions; outlined cells were mispredictions.
The two-level predictor keeps this branch's last 4 outcomes and uses them to pick one of 16 two-bit counters. Counters start in Weak NT.
Superpipelining and VLIW
Superpipelining
A superpipelined processor splits the slowest stages, usually instruction fetch and data memory access, into several shorter ones so the clock can run faster. The MIPS R4000 (1991) is the classic example: its eight stages split both cache accesses in two.
| Stage | IF | IS | RF | EX | DF | DS | TC | WB |
|---|---|---|---|---|---|---|---|---|
| Work | Fetch, first half | Fetch, second half | Decode, read registers, tag check | Execute | Data fetch, first half | Data fetch, second half | Tag check | Write back |
The price is longer hazard penalties measured in cycles. On the R4000 a load's value arrives two cycles later than in the five-stage pipeline, so a load-use hazard costs 2 stalls instead of 1, and a branch costs 3 cycles instead of 2. Intel's Pentium 4 pushed further, to 20 stages and later 31, before its successors cut the pipeline back as mispredictions and clock power grew.
Studies of optimal depth (Kunkel and Smith; Hartstein and Puzak; Sprangle and Carmean; Hrishikesh and colleagues) agree on the shape of the trade-off. Performance rises as stages are added, peaks, then falls as register overhead and hazard penalties dominate. Where the peak lies depends on the workload, the predictor and the circuit overhead.
VLIW
A very long instruction word processor issues one wide instruction per cycle, often called a bundle. Each bundle holds several independent operations, one slot per functional unit. The compiler, not the hardware, decides which operations can run together and fills unused slots with no-ops. With no dependence checking between slots and no dynamic scheduling, the issue logic stays simple even at high width.
VLIW came out of Fisher's trace-scheduling research and machines such as the Multiflow TRACE in the 1980s. Intel and HP's Itanium (EPIC) added predication, speculation support and bundle templates. Itanium was discontinued, but VLIW thrives where code is regular and power is tight: digital signal processors such as TI's C6000 family and Qualcomm's Hexagon, and many AI accelerators.
A VLIW schedule
The loop for (i = 0; i < n; i++) x[i] = x[i] + s; on doubles, unrolled four times, on a VLIW with two memory slots, two floating-point slots and one integer/branch slot. The latencies are 2 cycles from a load to its use and 3 cycles from an FP add to its use:
| Cycle | Memory 1 | Memory 2 | FP 1 | FP 2 | Integer / branch |
|---|---|---|---|---|---|
| 1 | fld f0, 0(x1) | fld f2, 8(x1) | nop | nop | nop |
| 2 | fld f4, 16(x1) | fld f6, 24(x1) | nop | nop | addi x1, x1, 32 |
| 3 | nop | nop | fadd.d f0, f0, f8 | fadd.d f2, f2, f8 | nop |
| 4 | nop | nop | fadd.d f4, f4, f8 | fadd.d f6, f6, f8 | nop |
| 5 | nop | nop | nop | nop | nop |
| 6 | fsd f0, -32(x1) | fsd f2, -24(x1) | nop | nop | nop |
| 7 | fsd f4, -16(x1) | fsd f6, -8(x1) | nop | nop | bne x1, x2, loop |
| Version | Cycles per element | Notes |
|---|---|---|
| Scalar pipeline, one element per iteration | 8 | 5 instructions plus 3 stall cycles |
| Scalar pipeline, unrolled ×4 and scheduled | 3.5 | 14 instructions, no stalls, loop overhead shared |
| VLIW, unrolled ×4 | 1.75 | 7 bundles; 14 of 35 slots used (40%) |
Operations in the same bundle read their registers before any of them write, so the loads in cycle 2 see the old x1. Unrolling further fills more slots and approaches one element per cycle, at the cost of code size and registers.
Compilers for VLIW
| Technique | What it does | Cost |
|---|---|---|
| Loop unrolling | Copies the loop body so there are more independent operations to pack | Code size, registers |
| Software pipelining | Overlaps iterations: each bundle holds parts of several iterations at different stages (Lam) | Prologue and epilogue code; register pressure |
| Trace scheduling | Schedules the most likely path through several basic blocks as one unit, with fix-up code off the path (Fisher) | Compensation code; depends on profile accuracy |
| Predication | Turns short branches into operations guarded by a predicate register, removing control hazards (the hyperblock of Mahlke and colleagues) | Executes both paths' operations |
VLIW's weaknesses are the flip side of its strength. Schedules are fixed at compile time, so an unexpected cache miss stalls the whole bundle. Binaries encode the slot layout, so a wider implementation needs recompiled code. Empty slots waste instruction memory unless the encoding compresses no-ops, which most modern VLIWs do.
Evaluating strategies
The CPI stack
Every technique on this page attacks one term of this product, and many make another term worse. Forwarding shrinks data stalls but adds multiplexers to the critical path. Resolving branches early cuts the control penalty but may lengthen the cycle. Superpipelining shortens the cycle but multiplies stall penalties. The only fair comparison is the product, on a realistic workload.
Strategy evaluator
Set a workload and the hazard-handling options. The evaluator compares a 5-stage pipeline, an 8-stage superpipeline like the R4000's, and a 10-stage design that also splits the ALU.
Model assumptions
All designs share 5,000 ps of logic, divided evenly among stages, plus the register overhead per stage. With forwarding, dependent ALU instructions stall 0, 0 and 1 cycles in the 5-, 8- and 10-stage designs; load-use stalls are 1, 2 and 3 cycles; branch penalties are 2, 3 and 5 cycles. Without forwarding, an instruction that uses the previous result stalls 2, 4 or 6 cycles (one fewer for a result two back). Structural hazards are assumed to be designed out. Real processors differ in many details; the model shows the shape of the trade-off.
Things to try. With the defaults, the 8-stage design is fastest: its shorter cycle more than pays for its longer penalties, but at 10 stages the penalties win. Turn forwarding off or switch to "Stall" and the 5-stage design wins instead, because deeper pipelines magnify every unhandled hazard. Raise the register overhead and the advantage of depth shrinks.
Comparing approaches
| Approach | Gains from | Who finds parallelism | Main costs | Where it is used |
|---|---|---|---|---|
| Scalar pipeline with forwarding and prediction | Fewer stalls | Hardware, adjacent instructions | Forwarding paths, predictor tables | Microcontrollers, small embedded cores |
| Superpipelining | Shorter cycle | Hardware | Longer penalties, more registers and clock power | Nearly every high-performance core, to a moderate depth |
| Superscalar (in or out of order) | Several instructions per cycle | Hardware, at run time | Issue logic grows quickly with width | Phone, laptop and server CPUs |
| VLIW | Several operations per cycle | Compiler, ahead of time | Code size, compatibility, sensitivity to unpredictable latency | DSPs, accelerators |
Jouppi and Wall compared superpipelined and superscalar machines on the same code in 1989 and found that, for the same degree of parallelism, the two delivered roughly the same performance, because both were limited by the parallelism in the programs. Modern processors combine all of these techniques: a moderately deep pipeline, forwarding everywhere, accurate prediction, and several instructions issued per cycle.
Speed is only one axis. Power, area, design and verification effort, code size, and the ability to run existing binaries all weigh in, and they usually decide which strategy a product uses.
Resources
Interactive tools and references
Peer-reviewed reading
- Thornton, J. E. (1964). Parallel operation in the Control Data 6600. Proc. AFIPS Fall Joint Computer Conference, 33–40.The scoreboard: hardware hazard detection across multiple units.doi.org/10.1145/1464039.1464045
- Tomasulo, R. M. (1967). An efficient algorithm for exploiting multiple arithmetic units. IBM Journal of Research and Development, 11(1), 25–33.Dynamic scheduling that lets independent instructions pass stalled ones.doi.org/10.1147/rd.111.0025
- Yeh, T.-Y., & Patt, Y. N. (1991). Two-level adaptive training branch prediction. Proc. MICRO-24, 51–61.History-based prediction, the two-level row in the simulator.doi.org/10.1145/123465.123475
- Seznec, A. (2005). Analysis of the O-GEometric history length branch predictor. Proc. ISCA, 394–405.Geometric history lengths, the idea behind TAGE.doi.org/10.1109/ISCA.2005.13
- Jouppi, N. P., & Wall, D. W. (1989). Available instruction-level parallelism for superscalar and superpipelined machines. Proc. ASPLOS III, 272–282.Superpipelining and superscalar issue compared on the same code.doi.org/10.1145/70082.68207
- Mirapuri, S., Woodacre, M., & Vasseghi, N. (1992). The MIPS R4000 processor. IEEE Micro, 12(2), 10–22.The classic superpipelined design.doi.org/10.1109/40.127580
- Hartstein, A., & Puzak, T. R. (2002). The optimum pipeline depth for a microprocessor. Proc. ISCA, 7–13.An analytical model of the depth trade-off.doi.org/10.1109/ISCA.2002.1003557
- Sprangle, E., & Carmean, D. (2002). Increasing processor performance by implementing deeper pipelines. Proc. ISCA, 25–34.Intel's case for deep pipelines, from the Pentium 4 era.doi.org/10.1109/ISCA.2002.1003559
- Hrishikesh, M. S., et al. (2002). The optimal logic depth per pipeline stage is 6 to 8 FO4 inverter delays. Proc. ISCA, 14–24.Where register overhead stops deeper pipelines from paying off.doi.org/10.1109/ISCA.2002.1003558
- Fisher, J. A. (1981). Trace scheduling: A technique for global microcode compaction. IEEE Transactions on Computers, C-30(7), 478–490.The compiler technique that made VLIW practical.doi.org/10.1109/TC.1981.1675827
- Fisher, J. A. (1983). Very long instruction word architectures and the ELI-512. Proc. ISCA, 140–150.The founding VLIW paper.doi.org/10.1145/800046.801649
- Colwell, R. P., et al. (1988). A VLIW architecture for a trace scheduling compiler. IEEE Transactions on Computers, 37(8), 967–979.The Multiflow TRACE machines.doi.org/10.1109/12.2247
- Lam, M. (1988). Software pipelining: An effective scheduling technique for VLIW machines. Proc. ACM PLDI, 318–328.Overlapping loop iterations in the compiler.doi.org/10.1145/53990.54022
- Mahlke, S. A., et al. (1992). Effective compiler support for predicated execution using the hyperblock. Proc. MICRO-25, 45–54.Removing branches with predication.doi.org/10.1109/MICRO.1992.696999
- Rau, B. R., & Fisher, J. A. (1993). Instruction-level parallel processing: History, overview, and perspective. The Journal of Supercomputing, 7(1–2), 9–50.A survey by two of VLIW's originators.doi.org/10.1007/BF01205181
- Schlansker, M. S., & Rau, B. R. (2000). EPIC: Explicitly parallel instruction computing. IEEE Computer, 33(2), 37–45.The philosophy behind Itanium.doi.org/10.1109/2.820037
- Codrescu, L., et al. (2014). Hexagon DSP: An architecture optimized for mobile multimedia and communications. IEEE Micro, 34(2), 34–43.A VLIW in billions of phones.doi.org/10.1109/MM.2014.12