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"
28#define DEBUG_TYPE "firrtl-gated-clock-conversion"
31using namespace firrtl;
35StringRef edgeKindName(
EdgeKind kind) {
41 case EdgeKind::InstanceIn:
43 case EdgeKind::InstanceOut:
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());
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, StringAttr(), loc),
63 PortInfo(StringAttr::get(ctx, (
"_gatedClock_enable_" + tag).str()),
64 u1Type, dir, StringAttr(), loc)};
68FModuleOp getParentModule(Value value) {
69 if (isa<BlockArgument>(value))
70 return cast<FModuleOp>(value.getParentBlock()->getParentOp());
71 return value.getDefiningOp()->getParentOfType<FModuleOp>();
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))
102 os <<
"direct(" <<
value <<
")";
105 os <<
"instResult(" << cast<InstanceOp>(
op).getName() <<
", " <<
index
109 os <<
"moduleArg(" << cast<FModuleOp>(
op).getModuleName() <<
", " <<
index
113 os <<
"plannedWire(" <<
index <<
")";
116 os <<
"gateEnable(" << *
op <<
")";
130 return cast<FModuleOp>(ref.
getOp())
138 llvm_unreachable(
"unhandled MatRef kind");
142 Location loc,
MatRef anchor) {
143 enableNodes.push_back({parent, term, anchor, loc});
156 SmallVector<unsigned> chain;
157 unsigned cur = enableId;
159 chain.push_back(cur);
164 for (
unsigned id : llvm::reverse(chain)) {
171 assert(upstream &&
"an upstream enable must lower to a value");
172 ImplicitLocOpBuilder builder(node.
loc,
context);
174 result = builder.createOrFold<AndPrimOp>(upstream, result);
190 Value v = materializeGateEnable(gate);
201 ImplicitLocOpBuilder builder(mod.getLoc(),
context);
202 builder.setInsertionPointToStart(mod.getBodyBlock());
203 Value constOne = builder.createOrFold<ConstantOp>(
204 APSInt(APInt(1, 1,
false),
true));
210 InstanceOp inst,
unsigned clkPortIndex,
unsigned enPortIndex,
211 Value materializedClk, Value materializedEn) {
212 ImplicitLocOpBuilder builder(inst.getLoc(),
context);
214 builder.setInsertionPointToEnd(inst->getBlock());
216 MatchingConnectOp::create(builder, inst->getResult(clkPortIndex),
221 MatchingConnectOp::create(builder, inst->getResult(enPortIndex),
230 Value
clk = clockOperandOf(op);
232 return op->emitError(
233 "unsupported operation type for gated clock "
234 "conversion; expected RefForceOp, RefReleaseOp, RegOp, "
235 "RegResetOp or ClockGateIntrinsicOp");
241 LLVM_DEBUG(llvm::dbgs() <<
"[analyzeFrom] " << seeds.size() <<
" seeds\n");
242 SmallVector<Value> worklist(seeds.begin(), seeds.end());
243 LogicalResult result = success();
247 auto pushIfFresh = [&](Value dstClk, Value srcClk, Operation *op,
249 if (!dstClk || !srcClk)
251 LLVM_DEBUG(llvm::dbgs()
252 <<
" [pushIfFresh] edge kind=" << edgeKindName(kind) <<
"\n");
253 Value baseClkDriver =
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";
270 if (baseClkDriver != srcClk)
274 if (!
visited.insert(baseClkDriver).second)
276 worklist.push_back(baseClkDriver);
280 while (!worklist.empty()) {
281 Value
clk = worklist.pop_back_val();
283 if (
auto blockArg = dyn_cast<BlockArgument>(
clk)) {
284 auto mod = dyn_cast<FModuleOp>(blockArg.getOwner()->getParentOp());
286 mod.getPortDirection(blockArg.getArgNumber()) ==
Direction::In &&
287 "expected input port of an FModuleOp");
288 unsigned portIdx = blockArg.getArgNumber();
291 if (node->uses().empty()) {
292 LLVM_DEBUG(llvm::dbgs() <<
" top-level port, base clock\n");
296 for (
auto *use : node->uses()) {
297 if (
auto callerInst = dyn_cast<InstanceOp>(*use->getInstance()))
298 pushIfFresh(
clk, callerInst.getResult(portIdx), callerInst,
301 use->getInstance()->emitError(
"can only handle InstanceOp");
305 auto *defOp =
clk.getDefiningOp();
308 if (
auto gate = dyn_cast<ClockGateIntrinsicOp>(defOp)) {
314 if (
auto inst = dyn_cast<InstanceOp>(defOp)) {
315 auto refMod = inst.getReferencedModule(
ig);
316 auto childMod = dyn_cast_or_null<FModuleOp>(refMod.getOperation());
319 LLVM_DEBUG(llvm::dbgs() <<
" external module, base clock\n");
323 unsigned portIdx = cast<OpResult>(
clk).getResultNumber();
324 pushIfFresh(
clk, childMod.getBodyBlock()->getArgument(portIdx), inst,
328 if (isa<WireOp, NodeOp>(defOp)) {
337 if (
auto mux = dyn_cast<MuxPrimOp>(defOp)) {
338 Value inputs[] = {mux.getHigh(), mux.getLow()};
339 if (llvm::any_of(inputs, [](Value v) {
343 return d && d.getDefiningOp<ClockGateIntrinsicOp>();
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";
351 LLVM_DEBUG(llvm::dbgs() <<
" base clock\n");
354 LLVM_DEBUG(llvm::dbgs() <<
"[analyzeFrom] " <<
baseClks.size()
355 <<
" base clocks\n");
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));
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));
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();
391 return op->emitError(
"unsupported for gated clock conversion");
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) {
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";
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);
427 MatRef baseClk,
unsigned enableId) {
435 wirePlans.push_back({srcMod, baseClk, enableId, dstClk.getLoc()});
442 MatRef baseClk,
unsigned enableId) {
455 "only input port pairs are driven at the caller");
464 {inst,
plan.baseIdx},
468std::pair<unsigned, unsigned>
471 MatRef baseClk,
unsigned enableId) {
478 nextPortIdx.try_emplace(childMod, childMod.getNumPorts()).first->second;
483 "unless this is a gated clock, no need to add output enable port");
484 plan.outBaseClk = baseClk;
485 plan.outEnableId = enableId;
492 assert(
plan.dir == dir &&
"a port cannot change direction");
500 Value dstClk, Value srcClk,
501 MatRef baseClk,
unsigned enableId) {
503 dyn_cast_or_null<FModuleOp>(inst.getReferencedModule(
ig).getOperation());
504 auto gatedClkIndex = cast<OpResult>(srcClk).getResultNumber();
526 "a gated pair must imply a gated mark");
527 auto [baseClkIndex, enableIndex] =
528 planGatedPorts(inst, childMod, gatedClkIndex, dir, baseClk, enableId);
541 : childMod) == getParentModule(dstClk) &&
542 "parent modules must match");
551 auto inst = srcClk.getDefiningOp<InstanceOp>();
554 dyn_cast_or_null<FModuleOp>(inst.getReferencedModule(
ig).getOperation());
555 auto gatedClkIndex = cast<OpResult>(srcClk).getResultNumber();
556 auto *it =
portPlans.find({childMod, gatedClkIndex});
559 LLVM_DEBUG(llvm::dbgs() <<
" no plan for index " << gatedClkIndex
560 <<
", skipping drive (handled by InstanceOut)\n");
568 SmallVector<Value> worklist;
570 for (
const auto &edge : edges)
572 worklist.push_back(edge.dst);
575 while (!worklist.empty()) {
576 Value
clk = worklist.pop_back_val();
580 for (
const auto &edge : it->second)
582 worklist.push_back(edge.dst);
584 LLVM_DEBUG(llvm::dbgs() <<
"[computeGatedClocks] " <<
gatedClocks.size()
585 <<
" gated clock values\n");
589 FModuleOp srcMod,
MatRef baseClk,
591 LLVM_DEBUG(llvm::dbgs() <<
" edge kind=" << edgeKindName(edge.
kind) <<
"\n");
623 LLVM_DEBUG(llvm::dbgs() <<
"[plan] " <<
baseClks.size() <<
" base clocks\n");
637 while (!worklist.empty()) {
638 auto srcClk = worklist.front();
639 worklist.pop_front();
640 FModuleOp srcMod = getParentModule(srcClk);
644 "a node is only enqueued once it has a pair");
647 MatRef baseClk = it->second.baseClk;
648 unsigned enableId = it->second.enableId;
651 if (Value next =
processEdge(edge, srcClk, srcMod, baseClk, enableId))
652 worklist.push_back(next);
654 LLVM_DEBUG(llvm::dbgs() <<
"[plan] complete\n");
662 auto createWire = [&](Type type, ImplicitLocOpBuilder &builder) {
663 auto w = WireOp::create(builder, type);
670 auto builder = ImplicitLocOpBuilder::atBlockBegin(
671 wirePlan.loc, wirePlan.mod.getBodyBlock());
681 const unsigned origNumPorts = mod.getNumPorts();
682 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
683 for (
auto key : keys) {
686 "port index pre-assignment invalidated: ports were inserted "
687 "outside applyPlan()");
688 auto [baseInfo, enableInfo] = makeGatedClockPortInfos(
691 newPorts.emplace_back(origNumPorts, baseInfo);
692 newPorts.emplace_back(origNumPorts, enableInfo);
694 mod.insertPorts(newPorts);
699 SmallVector<InstanceOp> oldInsts;
700 for (
auto *use : node->uses())
701 if (
auto i = dyn_cast<InstanceOp>(*use->getInstance()))
702 oldInsts.push_back(i);
704 for (
auto oldInst : oldInsts) {
705 auto cloneIface = oldInst.cloneWithInsertedPortsAndReplaceUses(newPorts);
706 auto newInst = cast<InstanceOp>(cloneIface.getOperation());
718 for (
auto &[key, portPlan] :
portPlans) {
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),
728 MatchingConnectOp::create(builder, body->getArgument(portPlan.enIdx),
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);
742 for (
auto [index, wirePlan] : llvm::enumerate(
wirePlans)) {
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);
760 lower(rewrite.enableId))))
772 DenseMap<FModuleOp, mlir::DominanceInfo> dominanceInfo;
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;
780 FConnectLike writeConnect = {};
781 bool cannotRemove =
false;
782 SmallVector<Operation *> wireReaders;
784 for (
auto *user : wireData.getUsers()) {
785 if (
auto connect = dyn_cast<MatchingConnectOp>(user)) {
786 if (connect.getDest() == wireData) {
792 writeConnect = connect;
795 }
else if (!isa<RegOp, RegResetOp, RefForceOp, RefReleaseOp, MuxPrimOp>(
801 wireReaders.push_back(user);
803 if (cannotRemove || !writeConnect)
807 Value writeSource = writeConnect.getSrc();
808 if (llvm::all_of(wireReaders, [&](Operation *user) {
809 return modDomInfo.dominates(writeConnect, user);
811 wireData.replaceAllUsesWith(writeSource);
812 writeConnect.erase();
823 LLVM_DEBUG(llvm::dbgs() <<
"===== GatedClockConversion::run() =====\n");
833 LLVM_DEBUG(llvm::dbgs() <<
"--- Phase 1: Analysis ---\n");
841 LLVM_DEBUG(llvm::dbgs() <<
"--- Phase 2: Planning ---\n");
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 "
857 rootRewrites.push_back({op, it->second.baseClk, it->second.enableId});
862 LLVM_DEBUG(llvm::dbgs() <<
"--- Phase 3: Applying the plan ---\n");
868 LLVM_DEBUG(llvm::dbgs() <<
"--- Phase 4: Cleanup (" <<
deadInstances.size()
876 LLVM_DEBUG(llvm::dbgs() <<
"===== run() complete =====\n");
885 llvm::dbgs() <<
"=== srcToDstClocks ===\n";
887 llvm::dbgs() <<
"Source clock: " << getParentModule(srcClk).getModuleName()
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: ";
897 edge.op->print(llvm::dbgs());
899 llvm::dbgs() <<
"<alias>";
900 llvm::dbgs() <<
" [" << edgeKindName(edge.kind) <<
"]\n";
903 llvm::dbgs() <<
"=== Base clocks ===\n";
904 for (
const auto &baseClk :
baseClks) {
906 baseClk.print(llvm::dbgs());
907 llvm::dbgs() <<
"\n";
909 llvm::dbgs() <<
"======================\n";
913 auto &os = llvm::dbgs();
914 auto printEnable = [&](
unsigned id) {
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);
934 printEnable(wirePlan.enableId);
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) <<
": "
943 << portPlan.baseIdx <<
"/" << portPlan.enIdx;
946 portPlan.outBaseClk.print(os);
948 printEnable(portPlan.outEnableId);
952 os <<
"=== Planned instance drives ===\n";
954 InstanceOp inst = drive.inst;
955 os <<
" " << inst.getName() <<
" @" << drive.baseIdx <<
"/" << drive.enIdx
957 drive.baseClk.print(os);
959 printEnable(drive.enableId);
962 os <<
"=== Planned root rewrites ===\n";
964 os <<
" " << rewrite.op->getName() <<
" <- ";
965 rewrite.baseClk.print(os);
967 printEnable(rewrite.enableId);
970 os <<
"======================\n";
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 * getOp() const
ClockGateIntrinsicOp gate() const
Operation * op
Instance / module / gate, by kind.
static MatRef instResult(FInstanceLike inst, unsigned index)
void print(llvm::raw_ostream &os) const
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.
unsigned getIndex() const
static MatRef moduleArg(FModuleOp mod, unsigned index)
static MatRef plannedWire(unsigned index)
DenseSet< Value > gatedClocks
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.
SmallVector< Value > baseClks
unsigned newEnableLeaf(MatRef term, Location loc)
void createPlannedWires()
void eliminateTemporaryWires()
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
void computeGatedClocks()
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
Value getOrCreateConstU1One(FModuleOp mod)
LogicalResult addRoot(Operation *op)
LogicalResult emitPlannedIR()
DenseMap< FModuleOp, unsigned > nextPortIdx
SmallVector< RootRewrite > rootRewrites
SmallVector< Value > plannedWireValues
SmallVector< std::pair< Operation *, Value > > roots
SmallVector< EnableNode > enableNodes
DenseMap< Value, SmallVector< ClockEdge > > srcToDstClocks
SmallVector< WireOp > wireOps
DenseSet< Value > visited
Value gateEnableOf(ClockGateIntrinsicOp gate)
SmallVector< Value > loweredEnables
void insertPlannedPorts()
SmallVector< InstanceOp > deadInstances
DenseMap< Value, ClockPairPlan > clockEnablePairs
LogicalResult applyPlan()
Value processEdge(const ClockEdge &edge, Value srcClk, FModuleOp srcMod, MatRef baseClk, unsigned enableId)
Operation * liveInstance(Operation *inst) const
Value lower(unsigned enableId)
Value resolve(MatRef ref)
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.
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.
InstanceOp instance() const
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.