Clock Gating and Low-Power RTL

Your design now meets timing. The next bill arrives: power. You met the power wall as an architectural fact; front-end design is where you actually fight it, cycle by cycle and net by net. Dynamic power obeys one formula worth tattooing somewhere:

P_{\text{dyn}} \;=\; \alpha \, C \, V^2 f,

where f is the clock frequency, V the supply voltage, C the capacitance being switched, and \alpha the activity factor — the fraction of cycles a given net actually toggles. Every charge-up of a net costs \tfrac{1}{2}CV^2 from the supply, dumped as heat on the way back down (the deeper physics of that half-CV² — and the strange far horizon where computing needn't dissipate at all — lives in the energy cost of switching). The front-end levers are \alpha and C; the next lesson pulls the mighty V and f levers. And one net towers over all others in \alpha

The clock: the hungriest net on the chip

Data nets toggle when data changes — \alpha is typically 0.05–0.2. The clock toggles every single cycle, by definition: \alpha = 1, the maximum possible, into an enormous capacitance — the buffer tree spanning the die plus the clock pin of every flip-flop. Multiply the biggest \alpha by one of the biggest Cs and you get the standard, slightly scandalous fact: the clock network burns 30–50% of many chips' dynamic power — much of it delivered to registers that are not even changing value. A register holding the same word for a thousand cycles still receives, and pays for, a thousand clock edges.

Hence the biggest single power optimisation in digital design: clock gating — switch the clock off for registers with nothing to do. Slide the sliders: the savings scale with how much of the clock tree you can gate and how often those registers are genuinely idle.

Gating the clock without wrecking it

The obvious circuit is a trap. Write assign gclk = clk & en; and whenever en changes while the clock is high, the AND output changes mid-cycle — emitting a partial-width runt pulse or truncating a real one. Downstream flip-flops may treat the runt as a genuine clock edge and capture garbage — occasionally, unreproducibly, on every flop downstream of the gate. The industrial fix is the integrated clock-gating cell (ICG): a level-sensitive latch, transparent while the clock is low, feeding the AND. The latch freezes the enable for the entire high phase, so the gated clock can only ever produce full, clean pulses:

You almost never instantiate an ICG by hand. Write enable-based RTL and the tools infer it:

always_ff @(posedge clk) begin if (en) q <= d; // "load only when en" — synthesis maps the whole end // register bank onto an ICG cell from the library

A register bank written this way tells synthesis exactly which cycles matter; the tool replaces the per-bit feedback muxes with one ICG driving the bank's clock pin — hundreds of clock pins silenced by one gate. This is the pleasing pattern of low-power design: say what you mean in RTL, and let the tool turn intent into savings.

Beyond the clock: making the data hold still

Once the clock is gated, the remaining dynamic power is data toggling — shrink \alpha and C there too:

And measure, don't guess: power tools re-run the netlist against switching-activity data recorded from realistic simulations, producing a report of exactly which nets and clocks burn what. Here is that style of accounting in miniature:

// A toy power report: P = alpha * C * V^2 * f per component, before and after gating. const V = 0.8, f_GHz = 1.0; // volts, GHz const parts = [ { name: "clock tree ", alphaF: 1.00, C_pF: 800 }, { name: "datapath ", alphaF: 0.15, C_pF: 1200 }, { name: "memory ", alphaF: 0.05, C_pF: 900 }, { name: "control ", alphaF: 0.10, C_pF: 300 }, ]; // P(mW) = alpha * C_pF * V^2 * f_GHz (units: pF * V^2 * GHz = mW) const power = (a: number, c: number) => a * c * V * V * f_GHz; let total = 0; for (const p of parts) total += power(p.alphaF, p.C_pF); console.log("component alpha C(pF) P(mW) share"); for (const p of parts) { const P = power(p.alphaF, p.C_pF); console.log(p.name + " " + p.alphaF.toFixed(2) + " " + p.C_pF + " " + P.toFixed(0).padStart(4) + " " + ((100 * P) / total).toFixed(0) + "%"); } console.log("total: " + total.toFixed(0) + " mW"); console.log(""); // Clock gating: 80% of the tree sits behind ICGs; those registers idle 70% of cycles. const gatedFrac = 0.8, idleFrac = 0.7; const clkP = power(1.0, 800); const saved = clkP * gatedFrac * idleFrac; console.log("clock gating saves " + saved.toFixed(0) + " mW (" + ((100 * saved) / total).toFixed(0) + "% of the whole chip)"); console.log("new total: " + (total - saved).toFixed(0) + " mW");

Almost all of it, almost always. A modern mobile SoC is a masterpiece of aggressive gating: at any instant, the video decoder, the neural engine, most CPU cores, and most of the GPU are receiving no clock at all — hierarchies of ICG cells gate whole subsystems, then units, then individual register banks, often with 95%+ of flops unclocked in a typical idle-ish second. Designers speak of "dark silicon": the power wall means far more logic is fabricated than can ever be clocked simultaneously, so the chip is a city at night where lights flick on only in the rooms being used. That is also why racing back to idle matters — finishing a burst of work quickly so everything can be gated again often beats trickling along — a theme the next lesson's voltage lever makes quantitative.

The tempting one-liner assign gclk = clk & en; is a classic silicon killer. If en rises or falls while clk is high, the AND emits a runt pulse — a partial-width edge that some downstream flip-flops will register and others will not, corrupting state randomly across the whole gated domain. It will pass RTL simulation (which happily models ideal edges), and fail in silicon, intermittently, at some voltages and temperatures. Always use the library's ICG cell or — better — write enable-based RTL (if (en) q <= d) and let the tool infer proper gating. Most companies enforce this with lint rules that flag any logic on a clock net; treat hand-rolled clock logic the way a chemist treats hand-rolled explosives.