How Do Control Hazards Disrupt Instruction Fetching?

Control hazards occur in pipelined processors when the CPU encounters a conditional branch instruction and does not yet know which instruction to fetch next. Because modern processors overlap the execution of multiple instructions, the instruction fetch stage cannot wait for the branch outcome without slowing down the entire pipeline. This uncertainty forces the processor to either pause fetching or guess the next instruction path, which can lead to wasted work and performance penalties.

Understanding Sequential Instruction Fetching

In a classical von Neumann architecture, a processor executes instructions sequentially by default. A special register called the program counter (PC) holds the memory address of the next instruction to be fetched. During normal execution, the processor increments the PC by the size of one instruction—typically 4 bytes in a 32-bit fixed-length instruction set—so that:

\[\text{PC}_{\text{next}} = \text{PC}_{\text{current}} + 4\]

In a non-pipelined processor, each instruction completes all of its stages (fetch, decode, execute, memory access, write-back) before the next instruction begins. In a pipelined processor, however, a new instruction is fetched on every clock cycle while earlier instructions are still moving through downstream stages.

Why Conditional Branches Break the Pipeline

A conditional branch instruction (such as BEQ or BNE in RISC architectures) evaluates a condition—like comparing two registers—and either continues sequentially or jumps to a different target address. The disruption happens because of a timing gap:

  1. Fetch Stage (IF): The branch instruction is fetched from memory, and the processor immediately needs to know the next address to fetch on the very next clock cycle.
  2. Decode and Execute Stages (ID / EX): The branch condition is evaluated and the target address is calculated several cycles later.

Until the branch is resolved in the execute or memory stage, the instruction fetch unit does not know whether the next instruction is at \(\text{PC} + 4\) (branch not taken) or at the branch target address (branch taken).

Consequences of Control Hazards

When a conditional branch enters the pipeline, the processor faces a dilemma. If it simply continues fetching sequentially, it may load instructions that should never be executed.

  • Pipeline Stalls (Bubbles): The simplest solution is to freeze the fetch stage and insert no-operation (NOP) bubbles until the branch outcome is known. While this guarantees correctness, it wastes clock cycles on every branch.
  • Mispredicted Execution: If the processor guesses that the branch will not be taken and keeps fetching sequentially, a taken branch means the wrong instructions were fetched. Those speculatively fetched instructions must be flushed (discarded) before they modify any architectural state, resulting in a branch misprediction penalty.

The performance impact of control hazards can be quantified by the branch penalty. If \(p\) is the probability of a branch instruction, \(m\) is the misprediction rate, and \(k\) is the number of cycles lost per misprediction, the average cycles per instruction (CPI) increases by:

\[\Delta \text{CPI} = p \times m \times k\]

How Processors Mitigate Control Hazards

To minimize the disruption caused by control hazards, classical processors use several hardware and compiler techniques:

  • Branch Prediction: Hardware predictors guess whether a branch will be taken or not taken based on past behavior, allowing the fetch stage to continue speculatively with high accuracy.
  • Branch Target Buffers (BTB): A specialized cache stores the target addresses of recently executed branches so the fetch stage can redirect the PC immediately without waiting for the decode stage.
  • Delayed Branching: Some older RISC architectures expose a branch delay slot, where the instruction immediately following the branch is always executed regardless of the branch outcome, allowing the compiler to fill the slot with useful work.