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:

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:

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.