CIRCT 24.0.0git
Loading...
Searching...
No Matches
GatedClockConversion.h
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef CIRCT_DIALECT_FIRRTL_GATEDCLOCKCONVERSION_H
10#define CIRCT_DIALECT_FIRRTL_GATEDCLOCKCONVERSION_H
11
14#include "llvm/ADT/MapVector.h"
15
16namespace circt {
17namespace firrtl {
18
19//===----------------------------------------------------------------------===//
20// GatedClockConversion
21//===----------------------------------------------------------------------===//
22
24
25struct ClockEdge {
26 Value dst;
27 Operation *op; // null for Alias
29
30 // Checked views of `op`, so using the wrong one for a kind is a hard error.
31 ClockGateIntrinsicOp gate() const { return cast<ClockGateIntrinsicOp>(op); }
32 InstanceOp instance() const { return cast<InstanceOp>(op); }
33};
34
35/// Sink gated-clock enables into ops across module boundaries.
36///
37/// Backward traversal from each root's clock builds the clock flow graph, a
38/// forward pass *plans* the (base, AND-of-enables) pair of every clock value,
39/// and `applyPlan()` then materializes the whole plan in one shot.
40///
41/// Invariant: analysis and planning never mutate the IR, and no plan record is
42/// read after the mutation it describes happened. That is what makes stale
43/// value remapping structurally impossible; see `MatRef`.
44///
45/// Preconditions: run after `firrtl-expand-whens`, so every clock net and
46/// register has a single driver. Clock loops are not diagnosed here; see
47/// `firrtl-check-comb-loops`.
48///
49/// NOT thread-safe: port insertion mutates module signatures globally.
51public:
53
54 LogicalResult addRoot(Operation *op);
55
56 LogicalResult run();
57
58 void dump() const;
59
60private:
61 // -- The plan data model --------------------------------------------
62
63 /// Sentinel `EnableNode` index meaning "no enable at all".
64 static constexpr unsigned kNoEnable = ~0u;
65
66 /// A reference to a clock/enable value that can also name values which do not
67 /// exist yet (a planned port, a planned wire).
68 ///
69 /// Inserting a port re-creates every instance of a module, so instance
70 /// results are the only values `applyPlan()` invalidates. Hence the rule this
71 /// class enforces: name them symbolically as `(instance, resultIndex)`, never
72 /// raw.
73 class MatRef {
74 public:
75 enum class Kind {
76 None, ///< Null reference, e.g. "no enable".
77 Direct, ///< A `Value` that `applyPlan()` never invalidates.
78 InstResult, ///< Result #index of an instance (existing or planned port).
79 ModuleArg, ///< Block argument #index of a module (existing or planned).
80 PlannedWire, ///< Entry #index of `plannedWireValues`.
81 GateEnable, ///< `gate.enable | gate.test_enable`, lowered on demand.
82 };
83
84 MatRef() = default;
85
86 static MatRef direct(Value v) {
87 assert(v && "use MatRef() for a null reference");
88 assert(!v.getDefiningOp<FInstanceLike>() &&
89 "instance results must be symbolic; use MatRef::instResult()");
90 MatRef r;
92 r.value = v;
93 return r;
94 }
95 static MatRef instResult(FInstanceLike inst, unsigned index) {
96 return opRef(Kind::InstResult, inst, index);
97 }
98 static MatRef moduleArg(FModuleOp mod, unsigned index) {
99 return opRef(Kind::ModuleArg, mod, index);
100 }
101 static MatRef plannedWire(unsigned index) {
102 return opRef(Kind::PlannedWire, nullptr, index);
103 }
104 static MatRef gateEnable(ClockGateIntrinsicOp gate) {
105 return opRef(Kind::GateEnable, gate, 0);
106 }
107
108 /// Instance results become symbolic refs, every other value is stable.
109 static MatRef of(Value v) {
110 if (!v)
111 return MatRef();
112 if (auto inst = v.getDefiningOp<FInstanceLike>())
113 return instResult(inst, cast<OpResult>(v).getResultNumber());
114 return direct(v);
115 }
116
117 Kind getKind() const { return kind; }
118 Value getValue() const { return value; }
119 unsigned getIndex() const { return index; }
120 Operation *getOp() const { return op; }
121 ClockGateIntrinsicOp gate() const { return cast<ClockGateIntrinsicOp>(op); }
122
123 void print(llvm::raw_ostream &os) const;
124
125 private:
126 static MatRef opRef(Kind kind, Operation *op, unsigned index) {
127 MatRef r;
128 r.kind = kind;
129 r.op = op;
130 r.index = index;
131 return r;
132 }
133
135 Value value; ///< `Direct` only.
136 Operation *op = nullptr; ///< Instance / module / gate, by kind.
137 unsigned index = 0; ///< Result, argument or wire index.
138 };
139
140 /// Enable accumulation DAG node:
141 /// value(id) = parent == kNoEnable ? term : (value(parent) & term)
142 /// Nodes are shared by every consumer of a pair, so a cascade of gates emits
143 /// one `and` per gate no matter how many roots it feeds.
144 struct EnableNode {
145 unsigned parent;
147 /// Insert the `and` after this value.
149 Location loc;
150 };
151
152 /// (baseClk, enable) pair planned for a clock value.
157
158 /// Root rewrite applied by `applyPlan()`: clock the op by `baseClk` and sink
159 /// `enableId` into it.
160 struct RootRewrite {
161 Operation *op;
163 unsigned enableId = kNoEnable;
164 };
165
166 /// (baseClock, enable) port pair to append to a module.
168 FModuleOp mod;
169 /// Clock port this pair shadows (naming only).
172 /// Final port indices, pre-assigned at planning time.
173 unsigned baseIdx, enIdx;
174 /// `dir == Out` only: values inside `mod` driving the new output ports.
177 };
178
179 /// The `(module, clock port index)` key of a `PortPairPlan`.
180 using PortPlanKey = std::pair<FModuleOp, unsigned>;
181
182 /// Caller-side connects driving a planned *input* port pair.
184 InstanceOp inst;
185 unsigned baseIdx, enIdx;
187 /// `kNoEnable` drives a constant 1.
188 unsigned enableId = kNoEnable;
189 };
190
191 /// Temporary (base clock, enable) carrier wires at the top of `mod`, standing
192 /// in for a wire/node alias of a gated clock.
193 /// Wires `2*i` / `2*i+1` of `plannedWireValues` belong to `wirePlans[i]`.
195 FModuleOp mod;
197 unsigned enableId;
198 Location loc;
199 };
200
201 // -- Worklist analysis (no IR mutation) -------------------------------
202
203 // Fails on an undriven clock net, which this utility cannot plan around.
204 LogicalResult analyzeFrom(ArrayRef<Value> seeds);
205
206 // Mark every clock value downstream of a clock gate. This answers "does this
207 // module clock port need a (base, enable) pair?" before planning starts, so
208 // that planning never has to block on a sibling instance. Monotone, hence a
209 // single sweep suffices and a cycle saturates instead of diverging.
210 void computeGatedClocks();
211
212 // -- Planning (no IR mutation) ----------------------------------------
213
214 void plan();
215
216 // Plan one outgoing edge of `srcClk` and return the value to enqueue next:
217 // the destination, or null if it was already planned.
218 Value processEdge(const ClockEdge &edge, Value srcClk, FModuleOp srcMod,
219 MatRef baseClk, unsigned enableId);
220
221 // Append an accumulation node and return its id. A node with no parent is a
222 // leaf: its value is `term` and the anchor is unused.
223 unsigned newEnableNode(unsigned parent, MatRef term, Location loc,
224 MatRef anchor);
225 unsigned newEnableLeaf(MatRef term, Location loc) {
226 return newEnableNode(kNoEnable, term, loc, MatRef());
227 }
228
229 // -- Plan application (the only IR-mutating phase) --------------------
230
231 LogicalResult applyPlan();
232
233 // Sweep A: create the planned carrier wires.
234 void createPlannedWires();
235
236 // Sweep B: append the planned ports and re-create every affected instance.
237 void insertPlannedPorts();
238
239 // Sweep C: emit the planned expressions, connects and root rewrites.
240 LogicalResult emitPlannedIR();
241
242 // Resolve a reference to the live value it names. Only valid once the sweep
243 // that creates the referenced value has run.
244 Value resolve(MatRef ref);
245
246 // Materialize the accumulated enable of a node, memoized per node so each
247 // node emits at most one `and`. Null for `kNoEnable`.
248 Value lower(unsigned enableId);
249
250 LogicalResult rewriteRoot(Operation *op, Value baseClk, Value enable);
251
252 // -- Helpers ----------------------------------------------------------
253
254 // Cached: returns `enable | test_enable` or just `enable`.
255 Value gateEnableOf(ClockGateIntrinsicOp gate);
256
257 // Cached: a constant 1 at the top of `mod`, so it dominates every use.
258 Value getOrCreateConstU1One(FModuleOp mod);
259
260 void connectMaterializedToInstancePorts(InstanceOp inst,
261 unsigned clkPortIndex,
262 unsigned enPortIndex,
263 Value materializedClk,
264 Value materializedEn);
265
266 // Forward wire to source when exactly one writer dominates one reader.
268
269 // The live instance for `inst`, which `insertPlannedPorts()` may have
270 // re-created. A single lookup suffices: all of a module's port pairs are
271 // inserted in one call, so an instance is re-created at most once.
272 Operation *liveInstance(Operation *inst) const {
273 auto *clone = instClones.lookup(inst);
274 return clone ? clone : inst;
275 }
276
277 void dumpPlan() const;
278
279 // -- plan() dispatched handlers ---------------------------------------
280
281 void planAlias(Value dstClk, FModuleOp srcMod, MatRef baseClk,
282 unsigned enableId);
283
284 void planGate(ClockGateIntrinsicOp gate, Value dstClk, MatRef baseClk,
285 unsigned enableId);
286
287 void planInstancePort(Direction dir, InstanceOp inst, Value dstClk,
288 Value srcClk, MatRef baseClk, unsigned enableId);
289
290 // Handle the 2nd-and-later callers of a multiply-instantiated module.
291 void planMultiplyInstantiatedInput(Value srcClk, MatRef baseClk,
292 unsigned enableId);
293
294 // Plan (or look up) the gated port pair of a child module, and record the
295 // drive of a planned input pair at `inst`.
296 std::pair<unsigned, unsigned>
297 planGatedPorts(InstanceOp inst, FModuleOp childMod, unsigned gatedClkIndex,
298 Direction dir, MatRef baseClk, unsigned enableId);
299
300 void recordInstanceDrive(InstanceOp inst, const PortPairPlan &plan,
301 MatRef baseClk, unsigned enableId);
302
304
305 SmallVector<std::pair<Operation *, Value>> roots;
306
307 // -- Analysis state and output ----------------------------------------
308
309 DenseSet<Value> visited;
310
311 DenseMap<Value, SmallVector<ClockEdge>> srcToDstClocks;
312
313 SmallVector<Value> baseClks;
314
315 // Every clock value downstream of a clock gate; see `computeGatedClocks()`.
316 DenseSet<Value> gatedClocks;
317
318 DenseMap<Value, ClockPairPlan> clockEnablePairs;
319
320 // -- The plan ---------------------------------------------------------
321
322 SmallVector<EnableNode> enableNodes;
323
324 // Values of the wires created by `applyPlan()`, indexed by
325 // `MatRef::plannedWire`.
326 SmallVector<Value> plannedWireValues;
327
328 // Memoized result of `lower()`, indexed like `enableNodes`.
329 SmallVector<Value> loweredEnables;
330
331 SmallVector<RootRewrite> rootRewrites;
332
333 SmallVector<WirePairPlan> wirePlans;
334
335 // The gated port pairs to append, and the order in which each module's pairs
336 // are appended. `MapVector` keeps emission deterministic.
339
340 // Per-module port index allocator, seeded with the module's port count.
341 DenseMap<FModuleOp, unsigned> nextPortIdx;
342
343 // Keyed by `(caller instance, base port index)`, which de-duplicates repeated
344 // drives of the same port pair.
347
348 // -- applyPlan() state ------------------------------------------------
349
350 DenseMap<ClockGateIntrinsicOp, Value> gateEnableCache;
351
352 // Cache of constant 1 values per module
353 DenseMap<FModuleOp, Value> constU1Cache;
354
355 // Old instance -> the instance re-created with the planned ports.
356 DenseMap<Operation *, Operation *> instClones;
357
358 // Replaced instances, erased once nothing reads the plan any more.
359 SmallVector<InstanceOp> deadInstances;
360
361 // Temporary carrier wires awaiting elimination.
362 SmallVector<WireOp> wireOps;
363
364 MLIRContext *context;
365
367};
368
369} // namespace firrtl
370} // namespace circt
371
372#endif // CIRCT_DIALECT_FIRRTL_GATEDCLOCKCONVERSION_H
assert(baseType &&"element must be base type")
A reference to a clock/enable value that can also name values which do not exist yet (a planned port,...
unsigned index
Result, argument or wire index.
Operation * op
Instance / module / gate, by kind.
static MatRef instResult(FInstanceLike inst, unsigned index)
static MatRef gateEnable(ClockGateIntrinsicOp gate)
@ PlannedWire
Entry index of plannedWireValues.
@ None
Null reference, e.g. "no enable".
@ ModuleArg
Block argument index of a module (existing or planned).
@ InstResult
Result index of an instance (existing or planned port).
@ GateEnable
gate.enable | gate.test_enable, lowered on demand.
@ Direct
A Value that applyPlan() never invalidates.
static MatRef of(Value v)
Instance results become symbolic refs, every other value is stable.
static MatRef moduleArg(FModuleOp mod, unsigned index)
static MatRef opRef(Kind kind, Operation *op, unsigned index)
Sink gated-clock enables into ops across module boundaries.
void planInstancePort(Direction dir, InstanceOp inst, Value dstClk, Value srcClk, MatRef baseClk, unsigned enableId)
llvm::MapVector< FModuleOp, SmallVector< PortPlanKey > > plansPerModule
LogicalResult analyzeFrom(ArrayRef< Value > seeds)
void planMultiplyInstantiatedInput(Value srcClk, MatRef baseClk, unsigned enableId)
DenseMap< Operation *, Operation * > instClones
void recordInstanceDrive(InstanceOp inst, const PortPairPlan &plan, MatRef baseClk, unsigned enableId)
void planGate(ClockGateIntrinsicOp gate, Value dstClk, MatRef baseClk, unsigned enableId)
std::pair< unsigned, unsigned > planGatedPorts(InstanceOp inst, FModuleOp childMod, unsigned gatedClkIndex, Direction dir, MatRef baseClk, unsigned enableId)
DenseMap< ClockGateIntrinsicOp, Value > gateEnableCache
std::pair< FModuleOp, unsigned > PortPlanKey
The (module, clock port index) key of a PortPairPlan.
unsigned newEnableLeaf(MatRef term, Location loc)
llvm::MapVector< PortPlanKey, PortPairPlan > portPlans
void planAlias(Value dstClk, FModuleOp srcMod, MatRef baseClk, unsigned enableId)
static constexpr unsigned kNoEnable
Sentinel EnableNode index meaning "no enable at all".
LogicalResult rewriteRoot(Operation *op, Value baseClk, Value enable)
SmallVector< WirePairPlan > wirePlans
unsigned newEnableNode(unsigned parent, MatRef term, Location loc, MatRef anchor)
void connectMaterializedToInstancePorts(InstanceOp inst, unsigned clkPortIndex, unsigned enPortIndex, Value materializedClk, Value materializedEn)
llvm::MapVector< std::pair< InstanceOp, unsigned >, InstanceDrive > instanceDrives
DenseMap< FModuleOp, unsigned > nextPortIdx
SmallVector< RootRewrite > rootRewrites
SmallVector< std::pair< Operation *, Value > > roots
DenseMap< Value, SmallVector< ClockEdge > > srcToDstClocks
Value gateEnableOf(ClockGateIntrinsicOp gate)
DenseMap< Value, ClockPairPlan > clockEnablePairs
Value processEdge(const ClockEdge &edge, Value srcClk, FModuleOp srcMod, MatRef baseClk, unsigned enableId)
Operation * liveInstance(Operation *inst) const
DenseMap< FModuleOp, Value > constU1Cache
This graph tracks modules and where they are instantiated.
Direction
This represents the direction of a single port.
Definition FIRRTLEnums.h:27
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
ClockGateIntrinsicOp gate() const
(baseClk, enable) pair planned for a clock value.
Enable accumulation DAG node: value(id) = parent == kNoEnable ? term : (value(parent) & term) Nodes a...
MatRef anchor
Insert the and after this value.
Caller-side connects driving a planned input port pair.
unsigned enableId
kNoEnable drives a constant 1.
(baseClock, enable) port pair to append to a module.
MatRef outBaseClk
dir == Out only: values inside mod driving the new output ports.
unsigned gatedClkIndex
Clock port this pair shadows (naming only).
unsigned baseIdx
Final port indices, pre-assigned at planning time.
Root rewrite applied by applyPlan(): clock the op by baseClk and sink enableId into it.
Temporary (base clock, enable) carrier wires at the top of mod, standing in for a wire/node alias of ...