CIRCT 24.0.0git
Loading...
Searching...
No Matches
GatedClockConversion.cpp
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// This file implements the GatedClockConversion utility class.
10//
11//===----------------------------------------------------------------------===//
12
20#include "mlir/IR/Builders.h"
21#include "mlir/IR/Dominance.h"
22#include "mlir/IR/Value.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/Support/Debug.h"
25#include "llvm/Support/raw_ostream.h"
26#include <deque>
27
28#define DEBUG_TYPE "firrtl-gated-clock-conversion"
29
30using namespace circt;
31using namespace firrtl;
32
33namespace {
34
35StringRef edgeKindName(EdgeKind kind) {
36 switch (kind) {
37 case EdgeKind::Alias:
38 return "Alias";
39 case EdgeKind::Gate:
40 return "Gate";
41 case EdgeKind::InstanceIn:
42 return "InstanceIn";
43 case EdgeKind::InstanceOut:
44 return "InstanceOut";
45 }
46 return "?";
47}
48
49/// The gate's effective enable, `enable | test_enable` or just `enable`.
50Value materializeGateEnable(ClockGateIntrinsicOp gate) {
51 if (!gate.getTestEnable())
52 return gate.getEnable();
53 ImplicitLocOpBuilder b(gate.getLoc(), gate);
54 return b.createOrFold<OrPrimOp>(gate.getEnable(), gate.getTestEnable());
55}
56
57/// Build the (baseClock, gateEnable) PortInfo pair for the given direction.
58std::pair<PortInfo, PortInfo>
59makeGatedClockPortInfos(MLIRContext *ctx, StringRef tag, Direction dir,
60 Location loc, Type clockType, Type u1Type) {
61 return {PortInfo(StringAttr::get(ctx, ("_gatedClock_baseClock_" + tag).str()),
62 clockType, dir, /*symName=*/StringAttr(), loc),
63 PortInfo(StringAttr::get(ctx, ("_gatedClock_enable_" + tag).str()),
64 u1Type, dir, /*symName=*/StringAttr(), loc)};
65}
66
67/// The FModuleOp `value` lives in.
68FModuleOp getParentModule(Value value) {
69 if (isa<BlockArgument>(value))
70 return cast<FModuleOp>(value.getParentBlock()->getParentOp());
71 return value.getDefiningOp()->getParentOfType<FModuleOp>();
72}
73
74/// The clock operand of a supported root op, null otherwise. The `*_initial`
75/// ref force/release variants have no clock, so they are not roots.
76Value clockOperandOf(Operation *op) {
77 if (auto fop = dyn_cast<RefForceOp>(op))
78 return fop.getClock();
79 if (auto rop = dyn_cast<RefReleaseOp>(op))
80 return rop.getClock();
81 if (auto reg = dyn_cast<RegOp>(op))
82 return reg.getClockVal();
83 if (auto regr = dyn_cast<RegResetOp>(op))
84 return regr.getClockVal();
85 if (auto gc = dyn_cast<ClockGateIntrinsicOp>(op))
86 return gc.getInput();
87 return Value();
88}
89
90} // namespace
91
92//===----------------------------------------------------------------------===//
93// GatedClockConversion: the plan's value model
94//===----------------------------------------------------------------------===//
95
96void GatedClockConversion::MatRef::print(llvm::raw_ostream &os) const {
97 switch (kind) {
98 case Kind::None:
99 os << "<none>";
100 return;
101 case Kind::Direct:
102 os << "direct(" << value << ")";
103 return;
104 case Kind::InstResult:
105 os << "instResult(" << cast<InstanceOp>(op).getName() << ", " << index
106 << ")";
107 return;
108 case Kind::ModuleArg:
109 os << "moduleArg(" << cast<FModuleOp>(op).getModuleName() << ", " << index
110 << ")";
111 return;
113 os << "plannedWire(" << index << ")";
114 return;
115 case Kind::GateEnable:
116 os << "gateEnable(" << *op << ")";
117 return;
118 }
119}
120
122 switch (ref.getKind()) {
124 return Value();
126 return ref.getValue();
128 return liveInstance(ref.getOp())->getResult(ref.getIndex());
130 return cast<FModuleOp>(ref.getOp())
131 .getBodyBlock()
132 ->getArgument(ref.getIndex());
134 return plannedWireValues[ref.getIndex()];
136 return gateEnableOf(ref.gate());
137 }
138 llvm_unreachable("unhandled MatRef kind");
139}
140
141unsigned GatedClockConversion::newEnableNode(unsigned parent, MatRef term,
142 Location loc, MatRef anchor) {
143 enableNodes.push_back({parent, term, anchor, loc});
144 return enableNodes.size() - 1;
145}
146
147Value GatedClockConversion::lower(unsigned enableId) {
148 if (enableId == kNoEnable)
149 return Value();
150 loweredEnables.resize(enableNodes.size());
151 if (Value cached = loweredEnables[enableId])
152 return cached;
153
154 // Walk up to the first already-lowered node, then emit from there back down.
155 // Iterative so that a long gate cascade cannot overflow the stack.
156 SmallVector<unsigned> chain;
157 unsigned cur = enableId;
158 while (cur != kNoEnable && !loweredEnables[cur]) {
159 chain.push_back(cur);
160 cur = enableNodes[cur].parent;
161 }
162
163 Value upstream = cur == kNoEnable ? Value() : loweredEnables[cur];
164 for (unsigned id : llvm::reverse(chain)) {
165 const EnableNode &node = enableNodes[id];
166 Value result = resolve(node.term);
167 if (node.parent != kNoEnable) {
168 // AND with the upstream enable, so the register holds whenever any gate
169 // in the chain is closed. The anchor is the clock this enable
170 // accompanies, which dominates every consumer of the pair.
171 assert(upstream && "an upstream enable must lower to a value");
172 ImplicitLocOpBuilder builder(node.loc, context);
173 builder.setInsertionPointAfterValue(resolve(node.anchor));
174 result = builder.createOrFold<AndPrimOp>(upstream, result);
175 }
176 loweredEnables[id] = result;
177 upstream = result;
178 }
179 return upstream;
180}
181
182//===----------------------------------------------------------------------===//
183// GatedClockConversion: value materialization helpers
184//===----------------------------------------------------------------------===//
185
186Value GatedClockConversion::gateEnableOf(ClockGateIntrinsicOp gate) {
187 auto it = gateEnableCache.find(gate);
188 if (it != gateEnableCache.end())
189 return it->second;
190 Value v = materializeGateEnable(gate);
191 gateEnableCache[gate] = v;
192 return v;
193}
194
196 auto it = constU1Cache.find(mod);
197 if (it != constU1Cache.end())
198 return it->second;
199
200 // At the top of the body, so it dominates every possible use.
201 ImplicitLocOpBuilder builder(mod.getLoc(), context);
202 builder.setInsertionPointToStart(mod.getBodyBlock());
203 Value constOne = builder.createOrFold<ConstantOp>(
204 APSInt(APInt(1, 1, /*isSigned=*/false), /*isUnsigned=*/true));
205 constU1Cache[mod] = constOne;
206 return constOne;
207}
208
210 InstanceOp inst, unsigned clkPortIndex, unsigned enPortIndex,
211 Value materializedClk, Value materializedEn) {
212 ImplicitLocOpBuilder builder(inst.getLoc(), context);
213 // At the end of the block, so the materialized clock dominates the connect.
214 builder.setInsertionPointToEnd(inst->getBlock());
215
216 MatchingConnectOp::create(builder, inst->getResult(clkPortIndex),
217 materializedClk);
218
219 if (!materializedEn)
220 materializedEn = getOrCreateConstU1One(inst->getParentOfType<FModuleOp>());
221 MatchingConnectOp::create(builder, inst->getResult(enPortIndex),
222 materializedEn);
223}
224
225//===----------------------------------------------------------------------===//
226// GatedClockConversion: worklist analysis (no IR mutation)
227//===----------------------------------------------------------------------===//
228
229LogicalResult GatedClockConversion::addRoot(Operation *op) {
230 Value clk = clockOperandOf(op);
231 if (!clk)
232 return op->emitError(
233 "unsupported operation type for gated clock "
234 "conversion; expected RefForceOp, RefReleaseOp, RegOp, "
235 "RegResetOp or ClockGateIntrinsicOp");
236 roots.emplace_back(op, clk);
237 return success();
238}
239
240LogicalResult GatedClockConversion::analyzeFrom(ArrayRef<Value> seeds) {
241 LLVM_DEBUG(llvm::dbgs() << "[analyzeFrom] " << seeds.size() << " seeds\n");
242 SmallVector<Value> worklist(seeds.begin(), seeds.end());
243 LogicalResult result = success();
244
245 // Record the edge `srcClk` -> `dstClk` through `op` and enqueue the driver of
246 // `srcClk`, looking through wire/node/cast aliases.
247 auto pushIfFresh = [&](Value dstClk, Value srcClk, Operation *op,
248 EdgeKind kind) {
249 if (!dstClk || !srcClk)
250 return;
251 LLVM_DEBUG(llvm::dbgs()
252 << " [pushIfFresh] edge kind=" << edgeKindName(kind) << "\n");
253 Value baseClkDriver =
254 getModuleScopedDriver(srcClk, /*lookThroughWires=*/true,
255 /*lookThroughNodes=*/true,
256 /*lookThroughCasts=*/true);
257 // An undriven clock net has no source to thread a (base, enable) pair from,
258 // and skipping it would break the invariant that every caller drives a
259 // planned input pair. Report it while the IR is still untouched.
260 if (!baseClkDriver) {
261 mlir::emitError(srcClk.getLoc())
262 << "gated clock conversion: this clock is not driven; run this "
263 "utility after firrtl-expand-whens and firrtl-check-init";
264 result = failure();
265 return;
266 }
267 // `srcToDstClocks` is replayed forwards, from base clocks to users.
268 if (kind != EdgeKind::Alias)
269 srcToDstClocks[srcClk].push_back({dstClk, op, kind});
270 if (baseClkDriver != srcClk)
271 // Drives `srcClk` through wires/nodes/casts, so no op is needed.
272 srcToDstClocks[baseClkDriver].push_back(
273 {srcClk, nullptr, EdgeKind::Alias});
274 if (!visited.insert(baseClkDriver).second)
275 return;
276 worklist.push_back(baseClkDriver);
277 };
278
279 // Backward DFS from leaf clock values to the base clock that drives them.
280 while (!worklist.empty()) {
281 Value clk = worklist.pop_back_val();
282 // Case 1: clk is an input-port BlockArg (fan out to every caller).
283 if (auto blockArg = dyn_cast<BlockArgument>(clk)) {
284 auto mod = dyn_cast<FModuleOp>(blockArg.getOwner()->getParentOp());
285 assert(mod &&
286 mod.getPortDirection(blockArg.getArgNumber()) == Direction::In &&
287 "expected input port of an FModuleOp");
288 unsigned portIdx = blockArg.getArgNumber();
289 auto *node = ig.lookup(mod);
290 // Top-level module: this is the base clock, nothing else to traverse.
291 if (node->uses().empty()) {
292 LLVM_DEBUG(llvm::dbgs() << " top-level port, base clock\n");
293 baseClks.push_back(clk);
294 continue;
295 }
296 for (auto *use : node->uses()) {
297 if (auto callerInst = dyn_cast<InstanceOp>(*use->getInstance()))
298 pushIfFresh(clk, callerInst.getResult(portIdx), callerInst,
300 else
301 use->getInstance()->emitError("can only handle InstanceOp");
302 }
303 continue;
304 }
305 auto *defOp = clk.getDefiningOp();
306
307 // Case 2: clk is the result of a clock gate.
308 if (auto gate = dyn_cast<ClockGateIntrinsicOp>(defOp)) {
309 pushIfFresh(clk, gate.getInput(), gate, EdgeKind::Gate);
310 continue;
311 }
312
313 // Case 3: clk is an instance result (descend into the referenced module).
314 if (auto inst = dyn_cast<InstanceOp>(defOp)) {
315 auto refMod = inst.getReferencedModule(ig);
316 auto childMod = dyn_cast_or_null<FModuleOp>(refMod.getOperation());
317 if (!childMod) {
318 // External module: treat as base.
319 LLVM_DEBUG(llvm::dbgs() << " external module, base clock\n");
320 baseClks.push_back(clk);
321 continue;
322 }
323 unsigned portIdx = cast<OpResult>(clk).getResultNumber();
324 pushIfFresh(clk, childMod.getBodyBlock()->getArgument(portIdx), inst,
326 continue;
327 }
328 if (isa<WireOp, NodeOp>(defOp)) {
329 pushIfFresh(clk, clk, defOp, EdgeKind::Alias);
330 continue;
331 }
332
333 // A clock mux is the post-ExpandWhens form of a multi-driver gated clock.
334 // There is no single enable to sink through it, so say so rather than
335 // silently leaving the gate in place. Remark only when a gate feeds the
336 // mux, so that ordinary clock selection stays silent.
337 if (auto mux = dyn_cast<MuxPrimOp>(defOp)) {
338 Value inputs[] = {mux.getHigh(), mux.getLow()};
339 if (llvm::any_of(inputs, [](Value v) {
340 Value d = getModuleScopedDriver(v, /*lookThroughWires=*/true,
341 /*lookThroughNodes=*/true,
342 /*lookThroughCasts=*/true);
343 return d && d.getDefiningOp<ClockGateIntrinsicOp>();
344 }))
345 mlir::emitRemark(mux.getLoc())
346 << "gated clock conversion: clock selection is not supported; the "
347 "clock gate feeding this mux was left in place";
348 }
349
350 // Any other op generating the clock is a base clock; stop tracing here.
351 LLVM_DEBUG(llvm::dbgs() << " base clock\n");
352 baseClks.push_back(clk);
353 }
354 LLVM_DEBUG(llvm::dbgs() << "[analyzeFrom] " << baseClks.size()
355 << " base clocks\n");
356 return result;
357}
358
359//===----------------------------------------------------------------------===//
360// GatedClockConversion: root rewriting
361//===----------------------------------------------------------------------===//
362
363LogicalResult GatedClockConversion::rewriteRoot(Operation *op, Value baseClk,
364 Value enable) {
365 if (!enable)
366 return success();
367
368 // RefForce/RefRelease: rebind the clock to the ungated base and fold the
369 // enable into the predicate.
370 if (auto fop = dyn_cast<RefForceOp>(op)) {
371 fop.getClockMutable().assign(baseClk);
372 ImplicitLocOpBuilder b(fop.getLoc(), fop);
373 fop.getPredicateMutable().assign(
374 b.createOrFold<AndPrimOp>(fop.getPredicate(), enable));
375 return success();
376 }
377 if (auto rop = dyn_cast<RefReleaseOp>(op)) {
378 rop.getClockMutable().assign(baseClk);
379 ImplicitLocOpBuilder b(rop.getLoc(), rop);
380 rop.getPredicateMutable().assign(
381 b.createOrFold<AndPrimOp>(rop.getPredicate(), enable));
382 return success();
383 }
384
385 Value regData;
386 if (auto reg = dyn_cast<RegOp>(op))
387 regData = reg.getData();
388 else if (auto regr = dyn_cast<RegResetOp>(op))
389 regData = regr.getData();
390 else
391 return op->emitError("unsupported for gated clock conversion");
392
393 // ExpandWhens leaves exactly one write per register. Rebinding the clock
394 // without sinking the enable would silently drop the gate, so bail out if
395 // that precondition does not hold.
396 FConnectLike dataWrite;
397 unsigned writers = 0;
398 for (auto &use : regData.getUses()) {
399 auto fconn = dyn_cast<FConnectLike>(use.getOwner());
400 if (fconn && fconn.getDest() == regData) {
401 ++writers;
402 dataWrite = fconn;
403 }
404 }
405 if (writers != 1) {
406 op->emitWarning() << "gated clock conversion: expected exactly one connect "
407 "driving this register (run after "
408 "firrtl-expand-whens); found "
409 << writers << "; leaving the gated clock in place";
410 return success();
411 }
412
413 // Rebind to the ungated base and wrap the write with mux(enable, RHS,
414 // regData), so the register holds while the clock gate is closed.
415 op->setOperand(0, baseClk);
416 ImplicitLocOpBuilder b(dataWrite.getLoc(), dataWrite);
417 Value newRhs = b.createOrFold<MuxPrimOp>(enable, dataWrite.getSrc(), regData);
418 dataWrite->setOperand(1, newRhs);
419 return success();
420}
421
422//===----------------------------------------------------------------------===//
423// GatedClockConversion: planning (no IR mutation)
424//===----------------------------------------------------------------------===//
425
426void GatedClockConversion::planAlias(Value dstClk, FModuleOp srcMod,
427 MatRef baseClk, unsigned enableId) {
428 if (enableId == kNoEnable) {
429 clockEnablePairs[dstClk] = {baseClk, kNoEnable};
430 return;
431 }
432 // A wire pair carries (base, enable) past the alias;
433 // eliminateTemporaryWires() forwards it away when safe.
434 unsigned wireId = 2 * wirePlans.size();
435 wirePlans.push_back({srcMod, baseClk, enableId, dstClk.getLoc()});
436 clockEnablePairs[dstClk] = {
437 MatRef::plannedWire(wireId),
438 newEnableLeaf(MatRef::plannedWire(wireId + 1), dstClk.getLoc())};
439}
440
441void GatedClockConversion::planGate(ClockGateIntrinsicOp gate, Value dstClk,
442 MatRef baseClk, unsigned enableId) {
443 // The base clock passes through unchanged, so cascaded gates all resolve to
444 // the same ungated base.
445 auto gateEn = newEnableNode(enableId, MatRef::gateEnable(gate), gate.getLoc(),
446 /*anchor=*/MatRef::of(dstClk));
447 clockEnablePairs[dstClk] = {baseClk, gateEn};
448}
449
451 const PortPairPlan &plan,
452 MatRef baseClk,
453 unsigned enableId) {
454 assert(plan.dir == Direction::In &&
455 "only input port pairs are driven at the caller");
456 // A clock loop closed through an instance (`inst.clk_in <- inst.clk_out`) is
457 // deliberately not diagnosed here: doing it precisely needs an
458 // instance-path-sensitive analysis, and `firrtl-check-comb-loops` already
459 // rejects such input.
460
461 // Keyed by (instance, port index), so a second edge reaching an already
462 // planned pair from the same caller is a no-op, not a duplicate connect.
463 instanceDrives.try_emplace(
464 {inst, plan.baseIdx},
465 InstanceDrive{inst, plan.baseIdx, plan.enIdx, baseClk, enableId});
466}
467
468std::pair<unsigned, unsigned>
469GatedClockConversion::planGatedPorts(InstanceOp inst, FModuleOp childMod,
470 unsigned gatedClkIndex, Direction dir,
471 MatRef baseClk, unsigned enableId) {
472 PortPlanKey key{childMod, gatedClkIndex};
473 auto *it = portPlans.find(key);
474 if (it == portPlans.end()) {
475 // The final indices are known already: `insertPlannedPorts()` only appends
476 // ports, so existing indices never shift (asserted when applying).
477 unsigned &nextIdx =
478 nextPortIdx.try_emplace(childMod, childMod.getNumPorts()).first->second;
479 PortPairPlan plan({childMod, gatedClkIndex, dir, nextIdx, nextIdx + 1});
480 nextIdx += 2;
481 if (dir == Direction::Out) {
482 assert(enableId != kNoEnable &&
483 "unless this is a gated clock, no need to add output enable port");
484 plan.outBaseClk = baseClk;
485 plan.outEnableId = enableId;
486 }
487 it = portPlans.insert({key, plan}).first;
488 plansPerModule[childMod].push_back(key);
489 }
490
491 const PortPairPlan &plan = it->second;
492 assert(plan.dir == dir && "a port cannot change direction");
493 // Every caller of the module must drive a planned input pair.
494 if (dir == Direction::In)
495 recordInstanceDrive(inst, plan, baseClk, enableId);
496 return {plan.baseIdx, plan.enIdx};
497}
498
500 Value dstClk, Value srcClk,
501 MatRef baseClk, unsigned enableId) {
502 auto childMod =
503 dyn_cast_or_null<FModuleOp>(inst.getReferencedModule(ig).getOperation());
504 auto gatedClkIndex = cast<OpResult>(srcClk).getResultNumber();
505 if (enableId == kNoEnable) {
506 if (dir == Direction::Out) {
507 // Symbolic, because a port pair for a *different* clock port of this
508 // module would clone all of its instances.
509 clockEnablePairs[dstClk] = {MatRef::of(dstClk), kNoEnable};
510 return;
511 }
512 // This instance drives an ungated clock input, but a sibling instance may
513 // drive a gated one, in which case the pair is added for *every* instance.
514 // `gatedClocks` answers that without blocking on the sibling's pair, which
515 // in a same-module cascade would depend on this very port.
516 if (!gatedClocks.contains(dstClk)) {
517 clockEnablePairs[dstClk] = {MatRef::of(dstClk), kNoEnable};
518 return;
519 }
520
521 // A sibling is gated: add the pair here too. `kNoEnable` on the resulting
522 // instance drive connects a constant 1.
523 }
524 assert((enableId == kNoEnable || dir == Direction::Out ||
525 gatedClocks.contains(dstClk)) &&
526 "a gated pair must imply a gated mark");
527 auto [baseClkIndex, enableIndex] =
528 planGatedPorts(inst, childMod, gatedClkIndex, dir, baseClk, enableId);
529
530 // An output pair is read on the instance result side, an input pair on the
531 // child's block-argument side. Neither exists yet, hence symbolic refs.
532 MatRef baseRef, enRef;
533 if (dir == Direction::Out) {
534 baseRef = MatRef::instResult(inst, baseClkIndex);
535 enRef = MatRef::instResult(inst, enableIndex);
536 } else {
537 baseRef = MatRef::moduleArg(childMod, baseClkIndex);
538 enRef = MatRef::moduleArg(childMod, enableIndex);
539 }
540 assert((dir == Direction::Out ? inst->getParentOfType<FModuleOp>()
541 : childMod) == getParentModule(dstClk) &&
542 "parent modules must match");
543 clockEnablePairs[dstClk] = {baseRef, newEnableLeaf(enRef, dstClk.getLoc())};
544}
545
547 MatRef baseClk,
548 unsigned enableId) {
549 // `srcClk` is a result of the caller instance; drive the port pair that an
550 // earlier caller of this module already planned.
551 auto inst = srcClk.getDefiningOp<InstanceOp>();
552 assert(inst);
553 auto childMod =
554 dyn_cast_or_null<FModuleOp>(inst.getReferencedModule(ig).getOperation());
555 auto gatedClkIndex = cast<OpResult>(srcClk).getResultNumber();
556 auto *it = portPlans.find({childMod, gatedClkIndex});
557 // No plan means an output port, which the InstanceOut path handles.
558 if (it == portPlans.end()) {
559 LLVM_DEBUG(llvm::dbgs() << " no plan for index " << gatedClkIndex
560 << ", skipping drive (handled by InstanceOut)\n");
561 return;
562 }
563 recordInstanceDrive(inst, it->second, baseClk, enableId);
564}
565
567 // Forward closure of every `Gate` edge over `srcToDstClocks`.
568 SmallVector<Value> worklist;
569 for (const auto &[src, edges] : srcToDstClocks)
570 for (const auto &edge : edges)
571 if (edge.kind == EdgeKind::Gate && gatedClocks.insert(edge.dst).second)
572 worklist.push_back(edge.dst);
573
574 // Iterating a DenseMap above is fine: the result is a set, not an order.
575 while (!worklist.empty()) {
576 Value clk = worklist.pop_back_val();
577 auto it = srcToDstClocks.find(clk);
578 if (it == srcToDstClocks.end())
579 continue;
580 for (const auto &edge : it->second)
581 if (gatedClocks.insert(edge.dst).second)
582 worklist.push_back(edge.dst);
583 }
584 LLVM_DEBUG(llvm::dbgs() << "[computeGatedClocks] " << gatedClocks.size()
585 << " gated clock values\n");
586}
587
588Value GatedClockConversion::processEdge(const ClockEdge &edge, Value srcClk,
589 FModuleOp srcMod, MatRef baseClk,
590 unsigned enableId) {
591 LLVM_DEBUG(llvm::dbgs() << " edge kind=" << edgeKindName(edge.kind) << "\n");
592
593 if (clockEnablePairs.count(edge.dst)) {
594 // For InstanceIn this is a multiply-instantiated module whose ports an
595 // earlier caller planned; this caller still has to drive them.
596 if (edge.kind == EdgeKind::InstanceIn)
597 planMultiplyInstantiatedInput(srcClk, baseClk, enableId);
598 return Value();
599 }
600
601 switch (edge.kind) {
602 case EdgeKind::Alias:
603 planAlias(edge.dst, srcMod, baseClk, enableId);
604 break;
605 case EdgeKind::Gate:
606 planGate(edge.gate(), edge.dst, baseClk, enableId);
607 break;
609 planInstancePort(Direction::In, edge.instance(), edge.dst, srcClk, baseClk,
610 enableId);
611 break;
613 planInstancePort(Direction::Out, edge.instance(), edge.dst, edge.dst,
614 baseClk, enableId);
615 break;
616 }
617 // `edge.dst` now has a pair, so it is safe to visit next.
618 assert(clockEnablePairs.count(edge.dst) && "the destination must be planned");
619 return edge.dst;
620}
621
623 LLVM_DEBUG(llvm::dbgs() << "[plan] " << baseClks.size() << " base clocks\n");
624 // Propagate (base, enable) pairs from the base clocks through the clock flow
625 // graph, planning ports as needed.
626 //
627 // This terminates on any graph, cyclic or not: a node is enqueued only once
628 // it has a pair and `processEdge` skips destinations that already have one.
629 // Clocks in a loop with no base clock are never planned; `run()` reports
630 // them.
631 //
632 // BFS rather than DFS purely to keep the emission order stable.
633 std::deque<Value> worklist(baseClks.begin(), baseClks.end());
634 for (auto baseClk : baseClks)
635 clockEnablePairs[baseClk] = {MatRef::of(baseClk), kNoEnable};
636
637 while (!worklist.empty()) {
638 auto srcClk = worklist.front();
639 worklist.pop_front();
640 FModuleOp srcMod = getParentModule(srcClk);
641
642 auto it = clockEnablePairs.find(srcClk);
643 assert(it != clockEnablePairs.end() &&
644 "a node is only enqueued once it has a pair");
645 // Copy the pair out: `processEdge` inserts into `clockEnablePairs` and
646 // invalidates `it`.
647 MatRef baseClk = it->second.baseClk;
648 unsigned enableId = it->second.enableId;
649
650 for (auto &edge : srcToDstClocks[srcClk])
651 if (Value next = processEdge(edge, srcClk, srcMod, baseClk, enableId))
652 worklist.push_back(next);
653 }
654 LLVM_DEBUG(llvm::dbgs() << "[plan] complete\n");
655}
656
657//===----------------------------------------------------------------------===//
658// GatedClockConversion: applyPlan (the only IR-mutating phase)
659//===----------------------------------------------------------------------===//
660
662 auto createWire = [&](Type type, ImplicitLocOpBuilder &builder) {
663 auto w = WireOp::create(builder, type);
664 wireOps.push_back(w);
665 return w.getData();
666 };
667 plannedWireValues.reserve(2 * wirePlans.size());
668 for (auto &wirePlan : wirePlans) {
669 // Wires have no operands, so they can all be created up front.
670 auto builder = ImplicitLocOpBuilder::atBlockBegin(
671 wirePlan.loc, wirePlan.mod.getBodyBlock());
672 plannedWireValues.push_back(createWire(clockType, builder));
673 plannedWireValues.push_back(createWire(u1Type, builder));
674 }
675}
676
678 for (auto &[mod, keys] : plansPerModule) {
679 // All pairs of a module are appended in one call, so every instance is
680 // re-created exactly once no matter how many pairs the module needs.
681 const unsigned origNumPorts = mod.getNumPorts();
682 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
683 for (auto key : keys) {
684 const PortPairPlan &portPlan = portPlans.find(key)->second;
685 assert(portPlan.baseIdx == origNumPorts + newPorts.size() &&
686 "port index pre-assignment invalidated: ports were inserted "
687 "outside applyPlan()");
688 auto [baseInfo, enableInfo] = makeGatedClockPortInfos(
689 context, mod.getPortName(portPlan.gatedClkIndex), portPlan.dir,
690 mod.getLoc(), clockType, u1Type);
691 newPorts.emplace_back(origNumPorts, baseInfo);
692 newPorts.emplace_back(origNumPorts, enableInfo);
693 }
694 mod.insertPorts(newPorts);
695
696 // A result list cannot grow in place, so every instance has to be
697 // re-created. Collect them first: cloning updates the use list.
698 auto *node = ig.lookup(mod);
699 SmallVector<InstanceOp> oldInsts;
700 for (auto *use : node->uses())
701 if (auto i = dyn_cast<InstanceOp>(*use->getInstance()))
702 oldInsts.push_back(i);
703
704 for (auto oldInst : oldInsts) {
705 auto cloneIface = oldInst.cloneWithInsertedPortsAndReplaceUses(newPorts);
706 auto newInst = cast<InstanceOp>(cloneIface.getOperation());
707 ig.replaceInstance(oldInst, newInst);
708 assert(!instClones.count(oldInst) && "instance re-created twice");
709 instClones[oldInst] = newInst;
710 // Defer erasure until nothing reads the plan any more.
711 deadInstances.push_back(oldInst);
712 }
713 }
714}
715
717 // Planned output port pairs: drive the new ports from inside the module.
718 for (auto &[key, portPlan] : portPlans) {
719 if (portPlan.dir != Direction::Out)
720 continue;
721 Value materializedClk = resolve(portPlan.outBaseClk);
722 Value materializedEn = lower(portPlan.outEnableId);
723 auto *body = portPlan.mod.getBodyBlock();
724 ImplicitLocOpBuilder builder(portPlan.mod.getLoc(), context);
725 builder.setInsertionPointToEnd(body);
726 MatchingConnectOp::create(builder, body->getArgument(portPlan.baseIdx),
727 materializedClk);
728 MatchingConnectOp::create(builder, body->getArgument(portPlan.enIdx),
729 materializedEn);
730 }
731
732 // Planned input port pairs: drive them at every caller instance.
733 for (auto &[key, drive] : instanceDrives) {
734 Value materializedClk = resolve(drive.baseClk);
735 Value materializedEn = lower(drive.enableId);
737 cast<InstanceOp>(liveInstance(drive.inst)), drive.baseIdx, drive.enIdx,
738 materializedClk, materializedEn);
739 }
740
741 // Planned carrier wires: connect them to their source pair.
742 for (auto [index, wirePlan] : llvm::enumerate(wirePlans)) {
743 Value clockWire = plannedWireValues[2 * index];
744 Value enWire = plannedWireValues[2 * index + 1];
745 Value materializedClk = resolve(wirePlan.baseClk);
746 Value materializedEn = lower(wirePlan.enableId);
747 ImplicitLocOpBuilder builder(wirePlan.loc, context);
748 builder.setInsertionPointAfter(enWire.getDefiningOp());
749 if (!isa<BlockArgument>(materializedClk))
750 builder.setInsertionPointAfterValue(materializedClk);
751 MatchingConnectOp::create(builder, clockWire, materializedClk);
752 if (!isa<BlockArgument>(materializedEn))
753 builder.setInsertionPointAfterValue(materializedEn);
754 MatchingConnectOp::create(builder, enWire, materializedEn);
755 }
756
757 // Root rewrites, last: they consume the fully materialized pairs.
758 for (const auto &rewrite : rootRewrites)
759 if (failed(rewriteRoot(rewrite.op, resolve(rewrite.baseClk),
760 lower(rewrite.enableId))))
761 return failure();
762 return success();
763}
764
770
772 DenseMap<FModuleOp, mlir::DominanceInfo> dominanceInfo;
773 for (auto wire : wireOps) {
774 auto wireData = wire.getData();
775 FModuleOp mod = wire->getParentOfType<FModuleOp>();
776 if (!dominanceInfo.count(mod))
777 dominanceInfo.try_emplace(mod, mod);
778 auto &modDomInfo = dominanceInfo.find(mod)->second;
779
780 FConnectLike writeConnect = {}; // Connect writing to the wire.
781 bool cannotRemove = false;
782 SmallVector<Operation *> wireReaders;
783
784 for (auto *user : wireData.getUsers()) {
785 if (auto connect = dyn_cast<MatchingConnectOp>(user)) {
786 if (connect.getDest() == wireData) {
787 // A second write means we can't safely forward; bail out.
788 if (writeConnect) {
789 cannotRemove = true;
790 break;
791 }
792 writeConnect = connect;
793 continue;
794 }
795 } else if (!isa<RegOp, RegResetOp, RefForceOp, RefReleaseOp, MuxPrimOp>(
796 user)) {
797 // Unhandled user; can't optimize.
798 cannotRemove = true;
799 break;
800 }
801 wireReaders.push_back(user);
802 }
803 if (cannotRemove || !writeConnect)
804 continue;
805
806 // Bypass the wire if the write dominates every read.
807 Value writeSource = writeConnect.getSrc();
808 if (llvm::all_of(wireReaders, [&](Operation *user) {
809 return modDomInfo.dominates(writeConnect, user);
810 })) {
811 wireData.replaceAllUsesWith(writeSource);
812 writeConnect.erase();
813 wire.erase();
814 }
815 }
816}
817
818//===----------------------------------------------------------------------===//
819// GatedClockConversion: the driver
820//===----------------------------------------------------------------------===//
821
823 LLVM_DEBUG(llvm::dbgs() << "===== GatedClockConversion::run() =====\n");
824
825 if (roots.empty())
826 return success();
827 context = roots[0].first->getContext();
828 clockType = ClockType::get(context);
829 u1Type = UIntType::get(context, 1);
830
831 // Phase 1: analysis. A failure here means invalid input; the IR is untouched,
832 // so returning early leaves it intact.
833 LLVM_DEBUG(llvm::dbgs() << "--- Phase 1: Analysis ---\n");
834 if (failed(analyzeFrom(llvm::to_vector(llvm::make_second_range(roots)))))
835 return failure();
836 LLVM_DEBUG(dump());
837
839
840 // Phase 2: plan the whole mutation. Still no IR mutation.
841 LLVM_DEBUG(llvm::dbgs() << "--- Phase 2: Planning ---\n");
842 plan();
843
844 // Record the root rewrites now, so that nothing reads `clockEnablePairs`
845 // after the IR has been mutated.
846 for (auto &[op, clk] : roots) {
847 auto it = clockEnablePairs.find(clk);
848 // An unplanned clock is one no base clock reaches, i.e. a clock feedback
849 // loop, which is invalid input. Skipping the rewrite is always safe.
850 if (it == clockEnablePairs.end()) {
851 mlir::emitWarning(clk.getLoc())
852 << "gated clock conversion: this clock is not reachable from any "
853 "free-running base clock (clock feedback loop?); leaving the op "
854 "unchanged";
855 continue;
856 }
857 rootRewrites.push_back({op, it->second.baseClk, it->second.enableId});
858 }
859 LLVM_DEBUG(dumpPlan());
860
861 // Phase 3: apply the plan. This is the only phase that mutates the IR.
862 LLVM_DEBUG(llvm::dbgs() << "--- Phase 3: Applying the plan ---\n");
863 if (failed(applyPlan()))
864 return failure();
865
866 // Phase 4: cleanup. Nothing reads the plan any more, so the instances that
867 // were replaced can be erased.
868 LLVM_DEBUG(llvm::dbgs() << "--- Phase 4: Cleanup (" << deadInstances.size()
869 << " ops) ---\n");
871 for (auto oldInst : deadInstances)
872 oldInst.erase();
873 deadInstances.clear();
874
875 roots.clear();
876 LLVM_DEBUG(llvm::dbgs() << "===== run() complete =====\n");
877 return success();
878}
879
880//===----------------------------------------------------------------------===//
881// GatedClockConversion: debug printing
882//===----------------------------------------------------------------------===//
883
885 llvm::dbgs() << "=== srcToDstClocks ===\n";
886 for (const auto &[srcClk, dstList] : srcToDstClocks) {
887 llvm::dbgs() << "Source clock: " << getParentModule(srcClk).getModuleName()
888 << "\n";
889 srcClk.print(llvm::dbgs());
890 llvm::dbgs() << "\n";
891 for (const auto &edge : dstList) {
892 llvm::dbgs() << " -> Destination clock: "
893 << getParentModule(edge.dst).getModuleName() << "\n";
894 edge.dst.print(llvm::dbgs());
895 llvm::dbgs() << " via op: ";
896 if (edge.op)
897 edge.op->print(llvm::dbgs());
898 else
899 llvm::dbgs() << "<alias>";
900 llvm::dbgs() << " [" << edgeKindName(edge.kind) << "]\n";
901 }
902 }
903 llvm::dbgs() << "=== Base clocks ===\n";
904 for (const auto &baseClk : baseClks) {
905 llvm::dbgs() << " ";
906 baseClk.print(llvm::dbgs());
907 llvm::dbgs() << "\n";
908 }
909 llvm::dbgs() << "======================\n";
910}
911
913 auto &os = llvm::dbgs();
914 auto printEnable = [&](unsigned id) {
915 if (id == kNoEnable) {
916 os << "<none>";
917 return;
918 }
919 // Print the accumulation chain leaf-to-root, i.e. as it will be ANDed.
920 for (unsigned cur = id; cur != kNoEnable; cur = enableNodes[cur].parent) {
921 if (cur != id)
922 os << " & ";
923 enableNodes[cur].term.print(os);
924 }
925 };
926
927 os << "=== Planned wire pairs ===\n";
928 for (auto [index, wirePlan] : llvm::enumerate(wirePlans)) {
929 FModuleOp mod = wirePlan.mod;
930 os << " #" << 2 * index << "/" << 2 * index + 1 << " in "
931 << mod.getModuleName() << " <- ";
932 wirePlan.baseClk.print(os);
933 os << ", ";
934 printEnable(wirePlan.enableId);
935 os << "\n";
936 }
937 os << "=== Planned port pairs ===\n";
938 for (const auto &[key, portPlan] : portPlans) {
939 FModuleOp mod = portPlan.mod;
940 os << " " << mod.getModuleName() << "."
941 << mod.getPortName(portPlan.gatedClkIndex) << ": "
942 << (portPlan.dir == Direction::In ? "in" : "out") << " @"
943 << portPlan.baseIdx << "/" << portPlan.enIdx;
944 if (portPlan.dir == Direction::Out) {
945 os << " <- ";
946 portPlan.outBaseClk.print(os);
947 os << ", ";
948 printEnable(portPlan.outEnableId);
949 }
950 os << "\n";
951 }
952 os << "=== Planned instance drives ===\n";
953 for (const auto &[key, drive] : instanceDrives) {
954 InstanceOp inst = drive.inst;
955 os << " " << inst.getName() << " @" << drive.baseIdx << "/" << drive.enIdx
956 << " <- ";
957 drive.baseClk.print(os);
958 os << ", ";
959 printEnable(drive.enableId);
960 os << "\n";
961 }
962 os << "=== Planned root rewrites ===\n";
963 for (const auto &rewrite : rootRewrites) {
964 os << " " << rewrite.op->getName() << " <- ";
965 rewrite.baseClk.print(os);
966 os << ", ";
967 printEnable(rewrite.enableId);
968 os << "\n";
969 }
970 os << "======================\n";
971}
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)
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
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Direction
This represents the direction of a single port.
Definition FIRRTLEnums.h:27
Value getModuleScopedDriver(Value val, bool lookThroughWires, bool lookThroughNodes, bool lookThroughCasts)
Return the value that drives another FIRRTL value within module scope.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
ClockGateIntrinsicOp gate() const
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.
(baseClock, enable) port pair to append to a module.
unsigned gatedClkIndex
Clock port this pair shadows (naming only).
unsigned baseIdx
Final port indices, pre-assigned at planning time.
This holds the name and type that describes the module's ports.