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:
- Operand isolation — a big combinational block (a multiplier, say) whose result
is only used sometimes still ripples with every input twitch, burning power computing answers
nobody reads. Freeze its inputs (an AND mask or an enabled register) when the result is unused, and
the whole cloud goes quiet.
- Encoding choices — a one-hot state machine toggles exactly two bits per
transition; a dense binary encoding can toggle many at once (7 → 8 flips four). Buses that count or
step can use Gray code for the same reason it helped in the CDC lesson: fewer transitions, less
\alpha.
- Avoid wasteful recomputation — hoist the invariant work out of the
every-cycle path, exactly as you would hoist code out of a loop: compute once, register it, reuse
it.
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");
- P_{\text{dyn}} = \alpha C V^2 f; each net toggle costs
\tfrac{1}{2}CV^2 from the supply;
- the clock has \alpha = 1 into huge capacitance — often
30–50% of chip power;
- clock gating silences idle registers — always via the latch-based
ICG cell (a naive
clk & en glitches), usually inferred from
if (en) q <= d RTL;
- operand isolation freezes unused datapath inputs; encoding choices (one-hot,
Gray) cut \alpha;
- verify with switching-activity-annotated power reports, not intuition.
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.