Static Timing Analysis
You now have a netlist of real gates, each with a real delay. One question decides whether the chip
works: does every signal arrive on time, on every path, every cycle? You already met
the
critical path and the budget T_{\text{clk}} \ge t_{cq} + t_{\text{logic}} +
t_{\text{setup}}. But a real chip has millions of register-to-register paths.
Simulating enough input patterns to exercise the slowest one is hopeless — you would need the one
pattern, among billions, that sensitises exactly the worst chain of gates. So the industry does
something more radical: it checks timing without simulating at all.
Static timing analysis (STA) treats the netlist as a graph of delays and checks
every path by arithmetic. No input vectors, no luck — exhaustive by construction.
Arrival, required, slack
STA runs on three numbers, computed at every point in the circuit:
- Arrival time — when the data actually gets there: the launching flip-flop's
clock-to-Q delay, plus every cell and wire delay accumulated along the path. Where paths merge, the
latest arrival wins (the data is not ready until the slowest input is).
- Required time — when the data must be there: for a setup check at a
capturing flip-flop, the next clock edge minus the setup time,
t_{\text{req}} = T_{\text{clk}} - t_{\text{setup}}.
- Slack — the margin: \text{slack} \;=\; t_{\text{req}} -
t_{\text{arrival}}. Positive slack: fine, with room to spare. Zero: exactly on time.
Negative slack: the chip fails at this clock. The path with the least
slack is the critical path, and the worst negative slack (WNS) is the first number every engineer
reads in the morning report.
A worked path, at a 1 ns clock (1 GHz), with a 0.05 ns setup time: the launching flop's clock-to-Q is
0.1 ns, the data crosses three gates of 0.2 ns each, then 0.15 ns of wire.
t_{\text{arrival}} = 0.1 + 3(0.2) + 0.15 = 0.85\ \text{ns}, \qquad
t_{\text{req}} = 1.0 - 0.05 = 0.95\ \text{ns}, \qquad \text{slack} = +0.10\ \text{ns}.
This path passes with 100 ps to spare. Squeeze the clock to 0.9 ns and the slack hits zero — that
path alone caps the design at about 1.11 GHz. Slide the delay slider below and watch the crossing
point move: the zero-slack clock period is your f_{\max}.
Setup checks and hold checks: too slow, and too fast
Every path gets two checks, because there are two ways to lose:
- The setup check asks: is the path too slow? Did the data launched at
edge n arrive before edge n+1 closes the
setup window? Fails are fixed by slowing the clock or shortening the path.
- The hold check asks: is the path too fast? Data launched at edge
n must not race through and corrupt what the capturing flop is
still latching at that same edge: the new value has to hold off until
t_{\text{hold}} after the edge. This is the scarier failure, because
no clock slowdown fixes it — the race happens at one edge, whatever the period.
A chip with a hold violation is dead at every frequency; the only fix is adding delay to the
too-short path, in silicon.
- arrival = clock-to-Q + accumulated cell and wire delays (latest input wins at
merges);
- setup (max) check: slack = (T_{\text{clk}} - t_{\text{setup}}) -
t_{\text{arrival,max}} \ge 0 — bounds the longest path, sets
f_{\max};
- hold (min) check: t_{\text{arrival,min}} \ge
t_{\text{hold}} at the same edge — clock-period independent;
- the critical path is the least-slack path; negative slack anywhere = timing
failure.
STA as a graph algorithm — run it
Under the hood, STA is a longest-path computation on a DAG: process nodes in topological order,
propagate t_{\text{arrival}} = \max(\text{inputs}) + \text{delay}, then
compare against required times at the endpoints. Millions of paths collapse into one linear-time
sweep — that is why STA conquered: a simulator samples paths, STA covers all of them, and it
finishes before lunch. Here it is on a five-gate netlist:
// STA in miniature: longest-path arrival times over a netlist DAG (delays in ps).
const CLK_TO_Q = 110, T_CLK = 1000, T_SETUP = 60;
// Edges: [from, to, delay] — cell + wire delay lumped per hop.
const edges: [string, string, number][] = [
["FF1/Q", "g1", 180], ["g1", "g2", 220], ["g2", "FF3/D", 90],
["FF2/Q", "g3", 200], ["g3", "g2", 260],
["g1", "g4", 240], ["g4", "FF4/D", 100],
];
const sources = ["FF1/Q", "FF2/Q"];
const endpoints = ["FF3/D", "FF4/D"];
// Longest path by relaxation (a topological sweep in disguise), remembering predecessors.
const arrival: Record<string, number> = {};
const pred: Record<string, string> = {};
for (const s of sources) arrival[s] = CLK_TO_Q;
for (let pass = 0; pass < 10; pass++)
for (const [from, to, d] of edges) {
if (arrival[from] === undefined) continue;
const t = arrival[from] + d;
if (arrival[to] === undefined || t > arrival[to]) { arrival[to] = t; pred[to] = from; }
}
const required = T_CLK - T_SETUP;
console.log("required time at every endpoint: " + required + " ps");
console.log("");
let worst = endpoints[0], worstSlack = Infinity;
for (const e of endpoints) {
const slack = required - arrival[e];
console.log(e + ": arrival " + arrival[e] + " ps slack " + (slack >= 0 ? "+" : "") + slack + " ps");
if (slack < worstSlack) { worstSlack = slack; worst = e; }
}
// Walk the predecessor chain back from the worst endpoint: the critical path.
const path: string[] = [worst];
while (pred[path[0]] !== undefined) path.unshift(pred[path[0]]);
console.log("");
console.log("critical path: " + path.join(" -> "));
console.log("f_max for this netlist: " + (1e6 / (arrival[worst] + T_SETUP)).toFixed(0) + " MHz");
Try it: shave the g3 → g2 hop to 100 ps and the critical path jumps to the
g4 branch — exactly the whack-a-mole the next lesson (timing closure) is about.
They did, and through the 1980s "timing verification" meant simulating with delays and hoping your
vectors hit the worst case. The trouble is combinatorial: a 32-bit adder alone has billions of input
transitions, and the slowest path may wake only for one carry pattern in a million. Miss it in
simulation and you ship it. STA, championed as designs crossed the hundred-thousand-gate line, flips
the burden of proof: assume every path can be exercised, check them all with a linear-time
graph sweep, and sign off on arithmetic instead of luck. The trade is deliberate pessimism — STA also
checks paths that can never actually happen (a mux that would have to select both inputs at once).
Those false paths can be declared and excluded — carefully, as the next lesson shows
— but the default posture is the safe one. Silicon has no testbench: once the mask set is cut, "we
probably covered it" is not a sentence anyone wants to say.
Newcomers discover that STA flags a path that cannot occur — logic in a mode the chip never
enters, a path between two configurations that are mutually exclusive — and conclude the tool is
broken. It is not broken; it is conservative. STA enumerates structural paths, not reachable
behaviours, so its worst case is at least as bad as reality — never better. That direction of error
is the feature: a pessimistic pass means real silicon passes, while an optimistic tool would sign off
chips that fail in the field. When the pessimism genuinely costs you (a false path throttling your
clock), the remedy is an explicit, reviewed exception — a promise to the tool that you will
meet in the next lesson, along with the ceremony that surrounds it. Deleting the check is easy;
being right about it is the hard part.