Hardware Engineering · Digital Logic
Combinational Hazards and Why Consensus Terms Eliminate Glitches
How unequal path delays produce output pulses in a logically correct circuit, how static and dynamic hazards differ, and when adding redundant logic is the fix.
A circuit that implements a Boolean function exactly can still produce a wrong output for a fraction of a clock period after an input changes. The steady-state value is right; the transient is not. This is not a fabrication defect and not a modeling error. It is a structural property of the network, and it follows from the fact that the paths from inputs to output have different delays.
The mechanism
Consider the function $f = \bar x y + x z$, built as two AND gates feeding an OR gate. Set $y = z = 1$ and the function reduces to $\bar x + x$, which is 1 for both values of $x$. The output should stay high while $x$ changes.
Before the change, $x = 1$: the term $\bar x y$ is 0 and $xz$ is 1, so $f = 1$. After the change, $x = 0$: $\bar x y$ is 1 and $xz$ is 0, so $f = 1$ again. The output should never move.
In the physical network, the two paths have different delays. The term $xz$ sees $x$ directly and falls one gate delay after $x$ falls. The term $\bar x y$ needs the inverter’s output, so it rises only after the inverter delay plus the AND gate’s delay. For the interval between those two events, both terms are 0 and the output — which should be held at 1 — is pulled low. If the circuit drives a clock gate or an asynchronous reset, that dip is a real event.
The mirror case exists too: a static-0 hazard produces a transient 1 on an output that should remain 0, and arises when two adjacent zeros are covered by different sum terms in a product-of-sums implementation.
A static-1 hazard is a transient 0 on an output that should remain 1. A static-0 hazard is a transient 1 on an output that should remain 0. A dynamic hazard is worse: an output that should change once changes two or three times, which requires a network of depth three or more along the relevant paths.
Detecting hazards in the map
Hazards have a clean characterization in the Karnaugh map, which is why the map remains useful long after minimization by hand has stopped being practical. A static-1 hazard exists for a single variable change exactly when two adjacent cells both containing 1 are covered by different prime implicants and no single implicant covers both.
Two adjacent ones covered by the same implicant cannot glitch, because the implicant’s product term stays true throughout the transition — every literal in that term is constant across the adjacent pair by construction. Two adjacent ones covered by different implicants each have a literal that flips, so each term can drop out while the other has not yet risen.
The fix follows directly: add a term that covers the adjacent pair. That term is the consensus of the two implicants: for the pair $\bar x y$ and $x z$, the consensus is $y z$. It contains no occurrence of $x$, so it stays true across the entire transition, and adding it holds the output at 1 throughout because both $y$ and $z$ are constant while $x$ moves.
The consensus theorem in algebra states that $\bar x y + x z + y z = \bar x y + x z$: the consensus term is logically redundant. Hazard elimination deliberately adds logically redundant terms. This is the one place in combinational design where a non-minimal expression is the correct answer, and it is why a purely literal-count-driven minimizer can produce a circuit that is functionally right and transiently wrong.
The consensus exists whenever two terms contain a variable in opposite polarity and share at least one other literal. When they share none — as with $\bar x y$ and $x \bar y$ — the consensus is empty, and the function has a hazard that no redundant term can remove at two levels. That case is the exclusive-OR pattern, which is one reason XOR is built as a dedicated structure rather than from discrete AND and OR gates.
A two-level sum of products with all consensus terms added is free of static-1 hazards for single-variable changes. That guarantee does not extend to multiple variables changing simultaneously, and it does not extend to multi-level networks at all.
Why multi-level logic has more hazards
A two-level network has exactly two levels of gates, so a path from any input to the output has either one or two gate delays depending on whether the input feeds the first or second level. That small set of path lengths is what makes the hazard analysis tractable.
A multi-level network has paths of many different lengths, and reconvergent fan-out — a signal that splits and later recombines — creates the same race in many places at once. Each reconvergence point is a potential hazard site, and eliminating them all by adding consensus terms can double the logic.
In synchronous design the usual answer is to not care. A combinational block whose output is sampled by a flip-flop at a known time only needs to be correct at the sampling instant, not throughout the settling interval. The glitch is absorbed by the timing budget: the block’s worst-case delay must be less than the clock period minus the setup requirement, and whatever happens in between is invisible. This is the single most important simplification in digital design, and it is why the synchronous timing budget is the real constraint rather than hazard freedom.
The exceptions are the signals that are not sampled but acted upon. An asynchronous reset, a clock gate enable, a write strobe, and any signal crossing into another clock domain are all consumed edge-triggered or level-sensitively, and a glitch on them is a functional event. Those signals are where hazard analysis pays, and the standard practice is to generate them from registers rather than from combinational logic so that no glitch can occur.
A simulation that shows the race
Modeling gates with unit delays makes the race visible. The following simulation of the network $\bar x y + x z$ shows a static-1 hazard on a specific transition.
def simulate(gates, inputs, changes, steps):
"""One unit of delay per gate, so each gate reads the previous step's node values."""
values = dict(inputs)
for name, op, args in gates: # list is topologically sorted: this settles time zero
values[name] = op(*[values[a] for a in args])
history = [values.copy()]
for step in range(1, steps):
if step in changes:
inputs.update(changes[step])
new = dict(values)
new.update(inputs)
for name, op, args in gates:
new[name] = op(*[values[a] for a in args])
values = new
history.append(values.copy())
return history
gates = [
("n_x", lambda a: 1 - a, ["x"]),
("t1", lambda a, b: a & b, ["n_x", "y"]),
("t2", lambda a, b: a & b, ["x", "z"]),
("f", lambda a, b: a | b, ["t1", "t2"]),
]
# y and z held at 1; x falls from 1 to 0 at step 2. Output should stay 1 throughout.
hist = simulate(gates, {"x": 1, "y": 1, "z": 1}, {2: {"x": 0}}, 7)
for i, h in enumerate(hist):
print(i, h["n_x"], h["t1"], h["t2"], h["f"])
With one unit of delay per gate, t2 falls one step after x falls because it reads x directly,
while t1 cannot rise until n_x has propagated through the inverter. For one step t1 and t2 are
both 0, and the OR gate’s own unit delay carries that into a single 0 on f on the following step —
a dip between two 1s. Adding the consensus term $yz$ — a third AND gate whose inputs do not depend on
x — keeps f high through the transition and removes the dip.
Design rules that follow
Several practices are direct consequences of the hazard mechanism.
Never drive an edge-sensitive input from combinational logic. Clock inputs, reset inputs, and latch enables come from registers or from dedicated clock networks. A glitch on a clock edge is a spurious edge, and no amount of downstream logic can undo it.
Use Gray code for signals that cross an asynchronous boundary and are read combinationally. A Gray counter changes one bit per increment, so a reader sampling during the transition sees either the old value or the new one, never an intermediate pattern. This is the same adjacency argument as the map, applied to state encoding rather than to gates, and it is developed further in clock domain crossing.
Keep asynchronous paths short and single-source. If a reset must be deasserted asynchronously, it should be deasserted through a synchronizer so that the release is not a race. The assertion can be asynchronous because asserting is safe; the release cannot, because a release that reaches different registers on different cycles leaves the machine in a state that was never specified.
Register the outputs of wide combinational functions before they leave a block. Not because the function is wrong, but because the glitch is a real signal on the wire, and the wire may go somewhere that samples it.
Where the model is pessimistic and where it is optimistic
The analysis above assumes that gate delays are finite and that the function’s terms settle at different times. Both are true. It also assumes that a glitch is a rectangular pulse that reaches full logic levels, which is pessimistic: a narrow pulse may be attenuated by the output node’s capacitance and by the receiving gate’s input capacitance to the point that it never crosses the threshold. A pulse shorter than the receiving gate’s delay is largely absorbed, which is why glitch power is real but glitch function is often not.
The optimistic side is worse. The analysis assumes delays are constant, but they depend on temperature, supply voltage, and the data pattern itself. A path that is fast in one cycle may be slow in the next, and a hazard that does not appear at the nominal corner may appear at a slow corner where the inverter’s delay grows faster than the direct path’s. Corner analysis is what catches this, and it is why hazard elimination by consensus is a proof only for the delay model it assumes.
The practical position: eliminate hazards on signals that are consumed asynchronously or edge-sensitively, accept them everywhere else, and make sure that everything else is sampled by a clock whose timing budget is verified.
References
- Switching and Finite Automata Theory — Zvi Kohavi, McGraw-Hill
- Logic and Computer Design Fundamentals — Mano and Kime, Pearson
- Digital Design and Computer Architecture — Harris and Harris, Morgan Kaufmann