Timing and the Critical Path

The synchronous discipline promised us discrete time: logic settles between edges, flip-flops capture on the edge. Now we cash in the promise and answer the question every architect ultimately gets paid to answer — how fast can the clock tick? The answer is set by a single, unforgiving path through the chip: the longest chain of logic that must finish between two edges. That is the critical path, and it alone determines the maximum clock frequency. Every trick for going faster is, at bottom, a trick for shortening it.

What must fit inside one clock period

Picture two flip-flops with a cloud of combinational logic between them. On the rising edge, the first (launching) flip-flop releases its stored value; that value must travel through the logic and arrive, stable, at the second (capturing) flip-flop before the next edge. Three delays stack up in that journey, and their sum must fit inside one clock period T_{\text{clk}}:

T_{\text{clk}} \;\ge\; t_{cq} + t_{\text{logic}} + t_{\text{setup}} \; (+\, t_{\text{skew}}).

Rearranged, the fastest you can clock is f_{\max} = 1/T_{\text{clk}}. The t_{\text{logic}} term is the only big, designable one — and it is the length of the longest path, because the clock must accommodate the worst case, not the average.

The clock-period budget, drawn

Think of one clock period as a fixed budget you must spend without going over. Here is that budget laid out between a launching edge and a capturing edge:

The green slack at the end is your safety margin. Speeding up the clock shrinks the period from the right, eating the slack; when slack reaches zero you are at f_{\max}. Push further and the data arrives after the setup window closes — a setup violation, and the chip computes wrong answers. The whole point of timing analysis is to find the path with the least slack (the critical path) and make sure it stays non-negative.

Setup and hold: two windows around the edge

A flip-flop needs its data input to sit still in a little window bracketing the clock edge — not just before it:

Clock skew: when the tick arrives at different times

We pretended the clock edge reaches every flip-flop simultaneously. It does not — the clock is a signal on a wire, and it arrives at different flip-flops at slightly different times. That difference is clock skew t_{\text{skew}}. If the capturing flip-flop's edge arrives late, it steals from your setup budget and lowers f_{\max}; but that same late edge relaxes the hold constraint. Skew is double-edged, which is why clock distribution — the H-tree networks and armies of buffers that spread the clock across a die — is one of the most carefully engineered structures on the chip.

Pipelining: cut the path in half

If the critical path sets the clock, shorten it. Pipelining inserts registers into a long stretch of combinational logic, splitting it into shorter stages. A path that took t_{\text{logic}} becomes k stages of roughly t_{\text{logic}}/k each — so the clock period drops toward t_{\text{logic}}/k + t_{cq} + t_{\text{setup}} and the frequency climbs. You pay for it: extra latency (a result now takes k cycles to emerge) and a fixed t_{cq} + t_{\text{setup}} overhead per stage that eventually dominates and caps the benefit. Deep pipelines are Amdahl's law in silicon — diminishing returns as the register overhead swallows the ever-thinner slices of real logic.

// Compute max clock frequency from stage delays, and see what pipelining buys. const t_cq = 0.05; // ns, clock-to-Q const t_setup = 0.04; // ns, setup time const t_skew = 0.02; // ns, clock skew function fMaxGHz(logicNs: number): number { const Tclk = t_cq + logicNs + t_setup + t_skew; // ns return 1 / Tclk; // GHz (1/ns) } const totalLogic = 1.20; // ns of combinational logic on the critical path console.log(`1 stage : Tclk = ${(t_cq + totalLogic + t_setup + t_skew).toFixed(2)} ns -> ${fMaxGHz(totalLogic).toFixed(2)} GHz`); // Split the SAME logic across k balanced stages: each stage sees totalLogic/k. for (const k of [2, 4, 8, 16]) { const stage = totalLogic / k; console.log(`${k} stages: stage logic = ${stage.toFixed(3)} ns -> ${fMaxGHz(stage).toFixed(2)} GHz`); } console.log("Note: gains flatten as the fixed t_cq + t_setup + t_skew overhead dominates.");

Intel's NetBurst (Pentium 4) chased clock frequency with a very deep pipeline — up to 31 stages — betting that megahertz would sell. Two walls stopped it. First, the per-stage t_{cq}+t_{\text{setup}} overhead became a large fraction of each ever-shorter stage, so frequency stopped scaling. Second, a deep pipeline makes every branch misprediction and cache miss far more expensive — you flush and refill many more stages — so real-world CPI ballooned. The iron law had the last word: a higher clock ($1/T_{\text{cycle}}$) bought at the cost of a much worse CPI is often a net loss. Shorter, wider pipelines (the Core line) won.

The most common timing confusion: "my design fails timing, I'll just clock it slower." That cures a setup violation, which is about the longest path finishing in time. But a hold violation is about the shortest path — a new value racing through too little logic and corrupting the value being captured at the same edge. The clock period never enters the hold equation (t_{cq} + t_{\text{logic,min}} \ge t_{\text{hold}} + t_{\text{skew}}), so slowing down does nothing. Hold violations are fixed by adding delay (buffers) to the fast path. Mixing these up has sunk many a first tape-out.

The knob behind every "GHz"

When a chip is advertised at 5 GHz, that number is 1/T_{\text{clk}} for a T_{\text{clk}} squeezed down to the critical path plus flip-flop overhead. Every technique in the rest of a computer-architecture course — faster adders, deeper pipelines, logic restructuring — is ultimately about that one path. And the reason we cannot simply keep shrinking it forever is what the next lesson is about: as transistors got tiny and fast, the wires between them refused to cooperate.