Pipeline Hazards

A ``hazard'' is a circumstance that arises because of pipelining, that either will make implementing an instruction difficult or will cause errors in execution (i.e. the pipelining changes the semantics of instruction execution). There are three basic types of hazard, with subdivisions possible. We'll mention the hazards first, and the fixes later, as they are somewhat orthogonal.
Structural Hazards
You can't use a single piece of hardware for two things at once. A Princeton architecture runs into a structural hazard on ld or st instructions, since there is only one path to memory and the pipeline is attempting to fetch an instruction on every cycle.
Data Hazards
An instruction may depend on data that is not available yet. There are three types of data hazards:
Read After Write (RAW)
The code calls for a read to occur after a write, but the pipeline causes the read to occur first. This is the most common (and is the only one that can occur in the simple example pipeline).
Write After Read (WAR)
The code calls for a write to occur after a read, but the pipeline causes the write to occur first. This can occur in a longer pipeline in which some instructions read registers in a late stage while others write registers in an early stage.
Write After Write (WAW)
The code calls for two writes to occur; the pipeline reorders them. This can occur in a pipeline in which register writes can occur in more than one pipeline stage.
Control Hazards
A decision needs to be made as to whether to take a branch, before the information to make the decision is available.

There are also three ways to fix a hazard. Going from easiest but least desireable to most desireable but not always possible, we get:

Document
Simply document the instruction set as supporting the semantics as defined by the pipeline (in other words, punt). This is the origin of delayed branch and delayed load instructions. It results in non-intuitive instruction semantics, and is tied to a particular pipeline implementation. If the implementation changes then the hazards change, and you find yourself making a screwy semantics work. You can't just document the new semantics like you did the old, since you have legacy code to support.
Stall
Delay the execution of the second instruction until the hazard is resolved. This preserves reasonable semantics, but slows things down.

An improvement on simply stalling which can be used to resolve structural hazards is to use branch prediction: take a guess as to whether the branch is likely to be taken or not, and continue executing with whatever guess you've taken. If you guessed wrong you cancel any instructions that you started to execute, with a penalty no greater than what you would have had if you'd just stalled.

Forward
The data you need may actually exist somewhere in the system, just not where you want it to be. This can be resolved by adding extra data paths, which ``forward'' the data to where it is needed. This is the ideal solution, since it neither messes up the semantics nor slows down the system. But, if the data is simply not there yet, then it can't be done.