Static-1 hazard
An output that should have stayed high dips for two ticks, because two paths through the logic are not the same length. The consensus term covers the gap.
- Level
- gate
- Code
- static-hazard
- Checked
- Verilator 5.052, Icarus Verilog 13.0, Yosys 0.66
- Published
- Authorship
- Drafted with AI assistance
- Status
- In progress — published so it can be reviewed, not finished.
f = a&b | ~b&c is 1 whenever a and c are 1, whatever b is doing: the two terms
between them cover both halves of b. Hold a and c high, drop b from 1 to 0, and the
output dips to 0 anyway — for as long as it takes the slower term to catch up with the faster
one. That dip is a static-1 hazard, and it is neither a simulation artefact nor a mistake in
the Boolean algebra. It is two paths of different length through the same logic.
The figure runs in delay mode: each gate carries the number of ticks it takes to pass a
change on, printed beside it as d=1 or d=2, and the wires show the value that has actually
reached them. An edge therefore walks across the drawing one gate at a time. The two drawings,
labelled hazard and covered, are the same function written two ways: the same three
inputs drive both, and one waveform records both.
hazard Two paths
covered A third path
Run plays this scenario from the start; driving an input continues from here.
Two circuits driven by the same inputs, hazard and covered, under one waveform. Inputs: a, b, c. Outputs: f. It is built from 1 inverter, 2 AND gates, 1 OR gate. The waveform shows a, b, c, ~b [hazard], a&b [hazard], ~b&c [hazard], f [hazard], f [covered], a&c [covered].
Figure 1. Press Run. b falls at tick 10 while a and c stay high; f dips for ticks 12 and 13 in the hazard drawing, and never moves in the covered one.
How it works, tick by tick
Follow the waveform. a and c are held at 1 for the whole run, so every row that moves moves
because of b.
At tick 10 b falls. One tick later a&b is 0: that path is a single AND gate, and it lets go
immediately. The other term has further to go. ~b only appears at tick 12, because the
inverter is marked d=2, and ~b&c cannot follow before tick 13.
So during ticks 11 and 12 both inputs of the OR are 0. Neither term is holding the output up,
and the function they are meant to implement has not changed at all. The OR passes that on one
tick later: f is 0 at ticks 12 and 13 and back to 1 at 14. The width of the dip is the
difference between the two paths — three ticks against one — and nothing else.
Now look at the covered drawing on the same two ticks. Its third gate computes a&c, which does
not depend on b. It was 1 before the edge, it is 1 after it, and it cannot move when b
moves, so the OR always has a term holding it high. That is the whole of the trick: the
consensus term is the path that avoids the variable that is changing.
Finding it is mechanical rather than inspired. On a Karnaugh map, a&b and ~b&c are two
groups that touch without overlapping, and a&c is the group that straddles the boundary
between them. That boundary is the b edge, and the boundary is where the hazard lives. The
Boolean simplifier draws the map of any function of up to six
variables and opens on this one, where it shows the opposite move: its minimum drops a&c,
because the other two terms already cover every row it covers.
The b returns scenario drives the same edge in the other direction. There is no dip, because
the fast path arrives before the slow one leaves: a&b is back one tick after b rises while
~b&c stays up for three more. A static hazard belongs to one direction of one input, and which
direction depends on which path is longer.
Ideal gates turns delay mode off. Both terms change in the same tick, the handover is exact, and the hazard is not there to be found — which is why a zero-delay simulation of a hazardous circuit reports nothing wrong.
The code
// A static-1 hazard, and the consensus term that covers it.
//
// f_hazard and f_covered are the same Boolean function. Wherever a & c is 1,
// a & b | ~b & c is already 1, whatever b holds, so the third term adds no
// minterm. It is there for the moment in which the other two disagree.
//
// This module is combinational: no clock, no state, and therefore no reset
// and no reset style to state.
module static_hazard (
input logic a,
input logic b,
input logic c,
output logic f_hazard,
output logic f_covered
);
// Two paths of unequal length. a & b reaches the OR through one gate; ~b & c
// reaches it through an inverter and a gate.
assign f_hazard = (a & b) | (~b & c);
// The same function with the consensus term a & c. It is 1 before the edge
// on b and 1 after it, so it holds the output up while the other two terms
// change.
assign f_covered = (a & b) | (~b & c) | (a & c);
endmoduleThe module is combinational — no clock, no state, and so no reset style to declare. Both outputs are written as one expression each, because the point of the page is the shape of the expression. The two assignments describe the same truth table, and a tool that is told only the truth table is free to treat the third term as redundant.
And a synthesis tool takes that freedom. Yosys's generic synthesis reduces both outputs to the
one $_MUX_ in the result below, b ? a : c: the consensus term does not survive, and
f_hazard and f_covered leave synthesis as the same net. Marking the term's net (* keep *)
does not bring the cover back. Tried with Yosys 0.66, it keeps an AND gate for a & c and still
drives f_covered from the multiplexer: the term survives, and the cover does not.
Synthesis result
Yosys's generic synthesis turns static_hazard.sv into 1 cell and no flip-flops. The cells are Yosys's own gate library, not an FPGA's or a standard-cell library's: the count measures the logic, not the area of any chip.
| Cell | What it is | Count |
|---|---|---|
$_MUX_ | 2-to-1 multiplexer | 1 |
| Total | 1 | |
Lint by Verilator 5.052, the testbench by Icarus Verilog 13.0, synthesis by Yosys 0.66.
Icarus replayed one of this page's three scenarios, Ideal gates, on this code, and all 170 values it compared match. It compared every row except ~b, a&b, ~b&c and a&c, which no signal in the code carries. The glitch and b returns are left out: they show gate delays, which this engine models and an RTL simulation does not.
The netlist before synthesis
What Yosys's prep reads in static_hazard.sv, before any gate is chosen, drawn by netlistsvg 1.0.2.
Pitfalls
A glitch only matters where something can remember it. Into the d pin of a flip-flop that
gets a whole clock period to settle, the dip above is harmless — it is gone long before the
setup window opens. Into a latch enable, an asynchronous reset or anything used as a clock, it
is a pulse like any other, and the circuit downstream has no way to know it was not meant.
A generic synthesis removes the cover. The consensus term is redundant by construction, and the synthesis result above shows what a tool does with a redundant term: it is gone, and both outputs are one multiplexer. That is right for synchronous logic that settles inside its period, where the dip is over before the next edge samples it. It is wrong where the cover was the point: asynchronous logic, and anything feeding an edge-sensitive or level-sensitive input.
This engine passes every pulse through. Its delay model is transport delay: a change that
arrives comes out the other side d ticks later, however short it was. Real gates do not —
a pulse shorter than the gate's own delay is attenuated or swallowed
entirely. So the dip drawn here is what the path lengths imply, not a prediction of what a
scope would show on any particular technology.
Check yourself
- Does adding the consensus term change what the circuit computes?
- No. Wherever a and c are both 1, one of a&b and ~b&c is already 1, whichever value b holds — so a&c covers no minterm the other two did not. It adds a path, not an output value.
- b falls and f dips. Why is there no dip when b rises again?
- Because on the rise the fast path arrives before the slow one leaves. a&b goes high one tick after b, while ~b&c only goes low three ticks after it, so the OR always has a term at 1. On the fall the order is reversed, and the two ticks in between are the hazard.
- The inverter is marked d=2 and every other gate d=1. Where do the two ticks of the dip come from?
- From the difference between the two paths. a&b reaches the OR one tick after b changes; ~b&c reaches it three ticks after, two of them in the inverter. The OR adds one tick to both, so it passes on a low that is 3 − 1 = 2 ticks wide.