Enables and gated clocks
Write a register that holds its value with an enable, see why an AND gate on the clock adds clock edges, and know the clock-gating cell a real flow uses instead.
- Lesson
- R2.3
- Time
- 25 min
- Runs on
- Your browser
- Check
- 5 questions
To save power, an engineer stopped a log buffer's clock with assign gclk = clk & en;. The enable came from a flop, so it changed just after each rising edge, while the clock was still high. Every time en rose, the AND gate passed the rest of that high phase as an extra clock edge. The buffer wrote one entry twice per burst, and every later entry landed one place off.
- Cycle 3
enrose 1 ns after edge 2, whileclkwas high, sogclkrose too. That extra edge wrote 11 into entry 0. Edge 3 wrote 11 again, into entry 1: the gated pointer is 2. - Cycle 5
Inside the burst
enstays 1, sogclkcopiesclkand each edge writes once. The gated pointer stays one ahead. - Cycle 6
enfalls whileclkis high.gclkfalls early: a pulse cut short, but no extra rising edge. - Cycle 8
One more write, and
enrises whileclkis high again. The gated pointer jumps by 2, to 6. The enabled one moves by 1, to 4.
Measured on the lab bench. Read back, the gated log is 11 11 22 33 44 44; the enabled log is 11 22 33 44.
An enable belongs on the data path
A register that must hold its value in some cycles needs an enable: a condition inside the clocked block, if (en) q <= d;. The clock reaches the flop on every edge. At each edge the flop takes either the new value or its own old one, decided by en.
An AND gate on the clock
assign gclk = clk & en;
always_ff @(posedge gclk or negedge rst_n)
if (!rst_n) wptr <= 3'd0;
else wptr <= wptr + 3'd1;
Every flop is clocked by a logic gate. When en rises while clk is high, gclk gets an extra rising edge.
An enable inside the block
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) wptr <= 3'd0;
else if (en) wptr <= wptr + 3'd1;
The clock is untouched. en is read only at the edge, after it has had a whole period to settle.
Synthesis builds the enable as a flop with an enable pin, or as a flop with a multiplexer that feeds the old value back. Either way the decision sits on the data path, where the flop's setup time protects it. The cell names in the Gates tab say which kind of flop you got.
| RTL inside always_ff | Cell on this flow |
|---|---|
q <= d; | $_DFF_P_ |
if (en) q <= d; | $_DFFE_PP_ |
asynchronous rst_n, then q <= d; | $_DFF_PN0_ |
asynchronous rst_n, then if (en) q <= d; | $_DFFE_PN0P_ |
synchronous rst_n, then if (en) q <= d; | $_SDFFE_PN0P_ |
Measured one bit at a time with Yosys 0.70. P: rising clock edge. N0: active-low reset to 0. The last P: active-high enable.
Why the AND gate adds edges
An enable made by a flop changes a moment after the rising edge, while the clock is still high. That is the worst moment for an AND gate. A rising en raises gclk at once, and a falling en cuts the clock pulse short.
- Edge k
clkrises whileenis 0, sogclkstays low. The en flop takes its new value at this edge. - 1 ns later
enrises whileclkis still high. The AND output rises with it: an edge the clock never had. The bank writes.Follow the calls, values and waits
- data en rises: en flop → AND gate
- data extra edge: AND gate → wptr and 8 entries
- Edge k+1
clkrises withenat 1: the write that was meant. The data has not changed yet, so it is written twice.
Read the diagram as text
- en flop: changes after each edge
- clk: high for half a period
- AND gate: gclk = clk & en
- wptr and 8 entries: clocked by gclk
- en flop → AND gate: en
- clk → AND gate: clk
- AND gate → wptr and 8 entries: gclk
The timing of the lab bench. Verilator's lint reports nothing about the gated clock: the code is legal.
Two more rules follow. A clock is never made by logic inside the design: a divider is a counter that produces an enable, and the block stays on one clock. A clock is never used as data either.
Clock gating done right: the ICG cell
Stopping the clock does save power, and chips do it everywhere. They use an integrated clock-gating cell (ICG), a library cell with a latch in front of the AND gate. The latch is open while clk is low and closed while it is high, so the enable cannot change during a high phase.
- clk low
The latch is open: a new
enpasses to the AND gate. The AND output is 0, becauseclkis 0.Follow the calls, values and waits
- data en: en → Latch
- data en: Latch → AND gate
- clk high
The latch is closed. A change on
enstops at the latch, sogclkis either a whole clock pulse or nothing.
Read the diagram as text
- en: from a flop
- clk: free-running
- Latch: open while clk is low
- AND gate: clk & held en
- gclk: whole pulses only
- en → Latch: en
- Latch → AND gate: held en
- clk → AND gate: clk
- AND gate → gclk
A latch-and-AND model of this cell, run on the lab bench, writes 11 22 33 44: no extra entries.
A real flow inserts ICGs from enables you wrote, or you instantiate the library's cell by name. Yosys has a clockgate command for this, but it needs a cell library. This site's flow maps to built-in gates, which include no ICG, so here clock gating stays a description.
The latch-and-AND model, for reading only
$_DLATCH_N_ latch and an AND gate: 175 cells.// A model of a clock-gating cell, for reading. In a chip this is one
// library cell, inserted by the flow or instantiated; never hand-written.
logic en_l;
always_latch if (!clk) en_l = en; // open while clk is low, closed while it is high
assign gclk = clk & en_l;| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| rst_n | input | 1 | reset, asynchronous, active-low |
| en | input | 1 | when 1 at a rising edge, write din at wptr and add one to wptr |
| din | input | [7:0] | data to write |
| raddr | input | [2:0] | entry to read |
| rdata | output | [7:0] | shows the entry at raddr at once |
- Clock
- one clock,
clk, reaching every flop untouched - Reset
wptrto 0; the 8 entries have no reset, because none is read before it is written- Parameters
- Memory: 64 entry flops and 3 pointer flops
- Corner cases
enchanging whileclkis high;wptrwrapping from 7 to 0
The lab ships the AND-gated version. Run it and read the doubled entries in the log line. Then move the enable into the two blocks and run again.
Asked in interviews
How do you write a register with an enable?
Put the condition inside the clocked block:
else if (en) q <= d;, with nothing aboutenin the event list. Synthesis builds an enabled flop or a feedback mux, and the clock stays untouched.Why not gate a clock with an AND gate, and what is an ICG?
An enable from a flop changes while the clock is high, so the AND output gains or loses part of a pulse. An ICG is a library cell that latches the enable while the clock is low, so it only ever passes whole pulses.
Can you make a slower clock with a divider in RTL?
Write the divider as a counter that produces an enable, and keep one clock. A divided clock made by logic is a new clock domain that the tools must be told about. Real clock generation belongs to the clocking logic.
Try it
Four values were written and the log holds six: 11 and 44 twice. Each time en rose while clk was high, gclk had an extra edge. Synthesize: 174 cells and 67 flops, every one clocked by the AND output.
Delete gclk and its assign, clock both blocks with clk, and add the enable: else if (en) wptr <= wptr + 3'd1; and if (en) mem[wptr] <= din;. Run again: the pointer ends at 4 and the log reads 11 22 33 44 xx xx xx xx. Synthesize: 176 cells, 67 flops, all on clk; the pointer flops are now $_DFFE_PN0P_.
Synthesize the design to measure its cells and inspect the mapped gates.
e0 en=0 din=00 wptr=0 e1 en=0 din=00 wptr=0 e2 en=0 din=00 wptr=0 e3 en=1 din=11 wptr=2 e4 en=1 din=22 wptr=3 e5 en=1 din=33 wptr=4 e6 en=0 din=33 wptr=4 e7 en=0 din=33 wptr=4 e8 en=1 din=44 wptr=6 e9 en=0 din=44 wptr=6 log: 11 11 22 33 44 44 xx xxOriginal design · 174 cells · 67 flip-flops · 0 latches · 5 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
int edge_n = -1;
always @(posedge clk) edge_n = edge_n + 1;
logic rst_n = 1;
logic en = 0;
logic [7:0] din = 0;
logic [2:0] raddr = 0;
logic [7:0] rdata;
logbuf dut (.clk(clk), .rst_n(rst_n), .en(en), .din(din), .raddr(raddr), .rdata(rdata));
// Advance to the next rising edge, wait for the design to settle, then print one line.
task automatic tick();
@(posedge clk); #1;
$display("e%0d en=%b din=%h wptr=%0d", edge_n, en, din, dut.wptr);
endtask
initial begin
#1 rst_n = 0; // reset asserted before edge 0
tick(); tick(); rst_n = 1; // released 1 ns after edge 1
tick(); // e2: idle
// From here en and din change 1 ns after an edge, while clk is still high,
// the way the output of a flop clocked by clk would.
din = 8'h11; en = 1; tick(); // e3: three writes, one per edge ...
din = 8'h22; tick(); // e4
din = 8'h33; tick(); // e5
en = 0; tick(); // e6: ... then nothing to write
tick(); // e7
din = 8'h44; en = 1; tick(); // e8: one more write
en = 0; tick(); // e9
$write("log:"); // read the log back, entry 0 first
for (int i = 0; i < 8; i++) begin
raddr = i; #1 $write(" %h", rdata);
end
$display("");
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- A register that holds is written with an enable inside the clocked block; the clock reaches the flop untouched.
- An enable made by a flop changes while the clock is high, so
clk & engains an edge when en rises and loses part of a pulse when en falls. - Synthesis builds an enable as an enabled flop such as
$_DFFE_PP_, or as a mux that feeds the old value back. - Real clock gating uses an ICG library cell, a latch plus an AND gate; this site's flow has none, so the lab uses the enable.
- A divider is a counter that makes an enable; logic never makes a clock, and a clock is never data.