CIRCT 24.0.0git
Loading...
Searching...
No Matches
RegisterOptimizer.cpp
Go to the documentation of this file.
1//===- RegisterOptimizer.cpp - Register Optimizer ---------------*- C++ -*-===//
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// This pass optimized registers as allowed by historic firrtl register
9// behaviors.
10//
11//===----------------------------------------------------------------------===//
12
16#include "circt/Support/Debug.h"
17#include "mlir/IR/Dominance.h"
18#include "mlir/Pass/Pass.h"
19#include "llvm/Support/Debug.h"
20
21#define DEBUG_TYPE "firrtl-register-optimizer"
22
23namespace circt {
24namespace firrtl {
25#define GEN_PASS_DEF_REGISTEROPTIMIZER
26#include "circt/Dialect/FIRRTL/Passes.h.inc"
27} // namespace firrtl
28} // namespace circt
29
30using namespace circt;
31using namespace firrtl;
32
33// Instantiated for RegOp and RegResetOp
34template <typename T>
35static bool canErase(T op) {
36 return !(hasDontTouch(op.getResult()) || op.isForceable() ||
37 (op.getAnnotationsAttr() && !op.getAnnotationsAttr().empty()));
38}
39
40namespace {
41
42//===----------------------------------------------------------------------===//
43// Pass Infrastructure
44//===----------------------------------------------------------------------===//
45
46struct RegisterOptimizerPass
47 : public circt::firrtl::impl::RegisterOptimizerBase<RegisterOptimizerPass> {
48 void runOnOperation() override;
49 void checkRegReset(mlir::DominanceInfo &dom,
50 SmallVector<Operation *> &toErase, RegResetOp reg);
51 void checkReg(mlir::DominanceInfo &dom, SmallVector<Operation *> &toErase,
52 RegOp reg);
53};
54
55} // namespace
56
57void RegisterOptimizerPass::checkReg(mlir::DominanceInfo &dom,
58 SmallVector<Operation *> &toErase,
59 RegOp reg) {
60 if (!canErase(reg))
61 return;
62 auto con = getSingleConnectUserOf(reg.getResult());
63 if (!con)
64 return;
65
66 // Register is only written by itself. Without a time-zero `initial` value
67 // its value is unspecified, so replace it with invalid. With one, the
68 // register holds that constant forever, so replace it with the constant.
69 if (con.getSrc() == reg.getResult()) {
70 auto builder = OpBuilder(reg);
71 Value replacement;
72 if (auto initial = reg.getInitialAttr())
73 replacement =
74 ConstantOp::create(builder, reg.getLoc(),
75 type_cast<IntType>(reg.getResult().getType()),
76 initial.getValue())
77 .getResult();
78 else
79 replacement = InvalidValueOp::create(builder, reg.getLoc(),
80 reg.getResult().getType())
81 .getResult();
82 reg.getResult().replaceAllUsesWith(replacement);
83 toErase.push_back(reg);
84 toErase.push_back(con);
85 return;
86 }
87 // Register is only written by a constant
88 if (isConstant(con.getSrc())) {
89 // Bail if replacing the register with the constant would change its
90 // time-zero simulation value.
91 auto cstOp = con.getSrc().getDefiningOp<ConstantOp>();
92 if (!preservesInitial(reg.getInitialAttr(),
93 cstOp ? std::optional<APInt>(cstOp.getValue())
94 : std::nullopt))
95 return;
96 // constant may not dominate the register. But it might be the next
97 // operation, so we can't just move it. Straight constants can be
98 // rematerialized. Derived constants are piped through wires.
99
100 if (cstOp) {
101 // Simple constants we can move safely
102 auto *fmodb = con->getParentOfType<FModuleOp>().getBodyBlock();
103 cstOp->moveBefore(fmodb, fmodb->begin());
104 reg.getResult().replaceAllUsesWith(cstOp.getResult());
105 toErase.push_back(con);
106 } else {
107 bool dominatesAll = true;
108 for (auto *use : reg->getUsers()) {
109 if (use == con)
110 continue;
111 if (!dom.dominates(con.getSrc(), use)) {
112 dominatesAll = false;
113 break;
114 }
115 }
116 if (dominatesAll) {
117 // Dominance is fine, just replace the op.
118 reg.getResult().replaceAllUsesWith(con.getSrc());
119 toErase.push_back(con);
120 } else {
121 auto builder = OpBuilder(reg);
122 auto bounce =
123 WireOp::create(builder, reg.getLoc(), reg.getResult().getType());
124 reg.replaceAllUsesWith(bounce);
125 }
126 }
127 toErase.push_back(reg);
128 return;
129 }
130}
131
132void RegisterOptimizerPass::checkRegReset(mlir::DominanceInfo &dom,
133 SmallVector<Operation *> &toErase,
134 RegResetOp reg) {
135 if (!canErase(reg))
136 return;
137 auto con = getSingleConnectUserOf(reg.getResult());
138 if (!con)
139 return;
140
141 // Both folds below replace the register with its reset value, so bail if
142 // that would change the register's time-zero (`initial`) value.
143 auto resetCstOp = reg.getResetValue().getDefiningOp<ConstantOp>();
144 if (!preservesInitial(reg.getInitialAttr(),
145 resetCstOp ? std::optional<APInt>(resetCstOp.getValue())
146 : std::nullopt))
147 return;
148
149 // Register is only written by itself, and reset with a constant.
150 if (reg.getResetValue().getType() == reg.getResult().getType()) {
151 if (con.getSrc() == reg.getResult() && isConstant(reg.getResetValue())) {
152 // constant obviously dominates the register.
153 reg.getResult().replaceAllUsesWith(reg.getResetValue());
154 toErase.push_back(reg);
155 toErase.push_back(con);
156 return;
157 }
158 // Register is only written by a constant, and reset with the same constant.
159 if (con.getSrc() == reg.getResetValue() &&
160 isConstant(reg.getResetValue())) {
161 // constant obviously dominates the register.
162 reg.getResult().replaceAllUsesWith(reg.getResetValue());
163 toErase.push_back(reg);
164 toErase.push_back(con);
165 return;
166 }
167 }
168}
169
170void RegisterOptimizerPass::runOnOperation() {
172
173 auto mod = getOperation();
174 SmallVector<Operation *> toErase;
175 mlir::DominanceInfo dom(mod);
176
177 for (auto &op : *mod.getBodyBlock()) {
178 if (auto reg = dyn_cast<RegResetOp>(&op))
179 checkRegReset(dom, toErase, reg);
180 else if (auto reg = dyn_cast<RegOp>(&op))
181 checkReg(dom, toErase, reg);
182 }
183 for (auto *op : toErase)
184 op->erase();
185
186 if (!toErase.empty())
187 return markAllAnalysesPreserved();
188}
static Block * getBodyBlock(FModuleLike mod)
static bool canErase(T op)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
bool isConstant(Operation *op)
Return true if the specified operation has a constant value.
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
bool preservesInitial(IntegerAttr initial, std::optional< APInt > foldedValue=std::nullopt)
Return true if replacing a register carrying the time-zero initial value with foldedValue does not ch...
MatchingConnectOp getSingleConnectUserOf(Value value)
Scan all the uses of the specified value, checking to see if there is exactly one connect that has th...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21