CIRCT 24.0.0git
Loading...
Searching...
No Matches
InferClockPorts.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// Retype `i1` module ports that carry clocks to `!seq.clock`.
10//
11// Outputs are only promoted if already driven by a clock, so that logic on a
12// clock path (e.g. a gated clock `clk & en`) stays in the `i1` domain.
13//
14//===----------------------------------------------------------------------===//
15
20#include "mlir/IR/Builders.h"
21#include "mlir/Pass/Pass.h"
22#include "llvm/ADT/BitVector.h"
23#include "llvm/ADT/DenseMap.h"
24#include "llvm/ADT/SetVector.h"
25#include "llvm/ADT/SmallVector.h"
26#include "llvm/Support/Debug.h"
27
28#define DEBUG_TYPE "seq-infer-clock-ports"
29
30using namespace circt;
31using namespace circt::seq;
32using namespace mlir;
33
34namespace circt {
35namespace seq {
36#define GEN_PASS_DEF_INFERCLOCKPORTS
37#include "circt/Dialect/Seq/SeqPasses.h.inc"
38} // namespace seq
39} // namespace circt
40
41namespace {
42
43class InferClockPortsPass
44 : public circt::seq::impl::InferClockPortsBase<InferClockPortsPass> {
45public:
46 using InferClockPortsBase::InferClockPortsBase;
47
48 void runOnOperation() override;
49
50private:
51 hw::HWModuleOp getTarget(hw::InstanceOp instance);
52 bool isRewritable(hw::HWModuleOp module);
53 bool canPromote(hw::HWModuleOp module, size_t portId);
54 bool canDriveClock(hw::HWModuleOp module, unsigned output);
55 void markClocks(SmallVectorImpl<Value> &worklist);
56 void rewriteModule(hw::HWModuleOp module);
57
58 hw::InstanceGraph *instanceGraph = nullptr;
59
60 /// Indexed by input and output number respectively.
61 DenseMap<Operation *, BitVector> clockInputs;
62 DenseMap<Operation *, BitVector> clockOutputs;
63
64 /// Memoized so the analysis stays linear in the size of the design.
65 DenseMap<Operation *, bool> rewritable;
66 DenseMap<std::pair<Operation *, unsigned>, bool> drivesClock;
67};
68
69} // namespace
70
71/// Returns false if already marked, so that each port is visited only once.
72static bool markPort(DenseMap<Operation *, BitVector> &ports, Operation *op,
73 unsigned idx, unsigned numPorts) {
74 auto &bits = ports[op];
75 if (bits.empty())
76 bits.resize(numPorts);
77 if (bits.test(idx))
78 return false;
79 bits.set(idx);
80 return true;
81}
82
83static hw::OutputOp getOutputOp(hw::HWModuleOp module) {
84 return cast<hw::OutputOp>(module.getBodyBlock()->getTerminator());
85}
86
87/// Returns null for external and generated modules, whose ports we can't
88/// retype.
89hw::HWModuleOp InferClockPortsPass::getTarget(hw::InstanceOp instance) {
90 auto *node =
91 instanceGraph->lookupOrNull(instance.getReferencedModuleNameAttr());
92 if (!node)
93 return {};
94 return dyn_cast<hw::HWModuleOp>(node->getModule().getOperation());
95}
96
97/// A module's signature may only change if we can update every instance of it.
98bool InferClockPortsPass::isRewritable(hw::HWModuleOp module) {
99 auto [it, inserted] = rewritable.try_emplace(module, false);
100 if (!inserted)
101 return it->second;
102 if (!promotePublicPorts && module.isPublic())
103 return false;
104 for (auto *use : instanceGraph->lookup(module)->uses()) {
105 // Entry node edge of a public module; there is no instance to update.
106 if (!use->getInstance())
107 continue;
108 if (!use->getInstance<hw::InstanceOp>())
109 return false;
110 if (!isa<hw::HWModuleOp>(use->getParent()->getModule().getOperation()))
111 return false;
112 }
113 return rewritable[module] = true;
114}
115
116/// Ports with an inner symbol may be referenced elsewhere, so keep their type.
117bool InferClockPortsPass::canPromote(hw::HWModuleOp module, size_t portId) {
118 auto port = module.getPort(portId);
119 return port.type.isInteger(1) && !port.getSym() && isRewritable(module);
120}
121
122/// Only promote an output if its driver already is a clock, so no logic is
123/// moved into the clock domain. Instance chains are followed iteratively to
124/// avoid deep recursion on deep hierarchies.
125bool InferClockPortsPass::canDriveClock(hw::HWModuleOp module,
126 unsigned output) {
127 SmallVector<std::pair<Operation *, unsigned>> chain;
128 bool result = false;
129 while (true) {
130 auto key = std::make_pair(module.getOperation(), output);
131 auto [it, inserted] = drivesClock.try_emplace(key, false);
132 if (!inserted) {
133 result = it->second;
134 break;
135 }
136 chain.push_back(key);
137 if (!canPromote(module, module.getPortIdForOutputId(output)))
138 break;
139
140 auto driver = getOutputOp(module).getOperand(output);
141 if (driver.getDefiningOp<seq::FromClockOp>()) {
142 result = true;
143 break;
144 }
145 if (auto arg = dyn_cast<BlockArgument>(driver)) {
146 result =
147 canPromote(module, module.getPortIdForInputId(arg.getArgNumber()));
148 break;
149 }
150 auto instance = driver.getDefiningOp<hw::InstanceOp>();
151 if (!instance)
152 break;
153 module = getTarget(instance);
154 if (!module)
155 break;
156 output = cast<OpResult>(driver).getResultNumber();
157 }
158
159 for (auto key : chain)
160 drivesClock[key] = result;
161 return result;
162}
163
164void InferClockPortsPass::markClocks(SmallVectorImpl<Value> &worklist) {
165 while (!worklist.empty()) {
166 auto value = worklist.pop_back_val();
167 if (!value.getType().isInteger(1))
168 continue;
169
170 // A clock input needs a clock from every instantiation.
171 if (auto arg = dyn_cast<BlockArgument>(value)) {
172 auto module = dyn_cast<hw::HWModuleOp>(arg.getOwner()->getParentOp());
173 unsigned input = arg.getArgNumber();
174 if (!module || !canPromote(module, module.getPortIdForInputId(input)) ||
175 !markPort(clockInputs, module, input, module.getNumInputPorts()))
176 continue;
177 LLVM_DEBUG(llvm::dbgs() << "Clock input " << module.getModuleName() << "."
178 << module.getInputName(input) << "\n");
179 ++numInputPortsPromoted;
180 for (auto *use : instanceGraph->lookup(module)->uses())
181 if (auto instance = use->getInstance<hw::InstanceOp>())
182 worklist.push_back(instance.getOperand(input));
183 continue;
184 }
185
186 if (auto instance = value.getDefiningOp<hw::InstanceOp>()) {
187 auto module = getTarget(instance);
188 unsigned output = cast<OpResult>(value).getResultNumber();
189 if (!module || !canDriveClock(module, output) ||
190 !markPort(clockOutputs, module, output, module.getNumOutputPorts()))
191 continue;
192 LLVM_DEBUG(llvm::dbgs() << "Clock output " << module.getModuleName()
193 << "." << module.getOutputName(output) << "\n");
194 ++numOutputPortsPromoted;
195 worklist.push_back(getOutputOp(module).getOperand(output));
196 continue;
197 }
198
199 // Anything else stays `i1` behind its `seq.to_clock`.
200 }
201}
202
203void InferClockPortsPass::rewriteModule(hw::HWModuleOp module) {
204 auto clockType = seq::ClockType::get(&getContext());
205 auto *body = module.getBodyBlock();
206 auto inputs = clockInputs.lookup(module);
207 auto outputs = clockOutputs.lookup(module);
208 SmallVector<std::pair<hw::InstanceOp, hw::HWModuleOp>> instances;
209 module.walk([&](hw::InstanceOp instance) {
210 if (auto target = getTarget(instance))
211 if (clockInputs.count(target) || clockOutputs.count(target))
212 instances.push_back({instance, target});
213 });
214 if (inputs.none() && outputs.none() && instances.empty())
215 return;
216
217 // Candidates for folding once all operands are updated.
218 SmallSetVector<seq::FromClockOp, 8> casts;
219
220 auto retype = [&](Value value, OpBuilder &builder) {
221 value.setType(clockType);
222 // Existing users still expect `i1`.
223 auto cast = seq::FromClockOp::create(builder, value.getLoc(), value);
224 value.replaceAllUsesExcept(cast, cast);
225 casts.insert(cast);
226 };
227
228 // Look through `seq.from_clock` to avoid a redundant cast pair.
229 auto toClock = [&](Value value, OpBuilder &builder) -> Value {
230 if (auto cast = value.getDefiningOp<seq::FromClockOp>()) {
231 casts.insert(cast);
232 return cast.getInput();
233 }
234 return seq::ToClockOp::create(builder, value.getLoc(), value);
235 };
236
237 // Retype values first so `toClock` can look through the new casts.
238 auto builder = OpBuilder::atBlockBegin(body);
239 for (unsigned input : inputs.set_bits())
240 retype(body->getArgument(input), builder);
241 for (auto [instance, target] : instances) {
242 builder.setInsertionPointAfter(instance);
243 // Copy: `lookup` returns by value, so iterating its temporary would dangle.
244 BitVector targetOutputs = clockOutputs.lookup(target);
245 for (unsigned output : targetOutputs.set_bits())
246 retype(instance.getResult(output), builder);
247 }
248
249 auto outputOp = getOutputOp(module);
250 builder.setInsertionPoint(outputOp);
251 for (unsigned output : outputs.set_bits())
252 outputOp.setOperand(output, toClock(outputOp.getOperand(output), builder));
253 for (auto [instance, target] : instances) {
254 builder.setInsertionPoint(instance);
255 BitVector targetInputs = clockInputs.lookup(target);
256 for (unsigned input : targetInputs.set_bits())
257 instance.setOperand(input, toClock(instance.getOperand(input), builder));
258 }
259
260 // Pre-existing `seq.to_clock` users of retyped values are now redundant.
261 for (auto cast : casts) {
262 for (auto *user : llvm::make_early_inc_range(cast->getUsers())) {
263 if (auto toClockOp = dyn_cast<seq::ToClockOp>(user)) {
264 toClockOp.replaceAllUsesWith(cast.getInput());
265 toClockOp.erase();
266 ++numCastsRemoved;
267 }
268 }
269 if (cast.use_empty())
270 cast.erase();
271 }
272
273 if (inputs.none() && outputs.none())
274 return;
275 auto type = module.getHWModuleType();
276 SmallVector<hw::ModulePort> ports(type.getPorts());
277 for (unsigned input : inputs.set_bits())
278 ports[type.getPortIdForInputId(input)].type = clockType;
279 for (unsigned output : outputs.set_bits())
280 ports[type.getPortIdForOutputId(output)].type = clockType;
281 module.setHWModuleType(hw::ModuleType::get(&getContext(), ports));
282}
283
284void InferClockPortsPass::runOnOperation() {
285 instanceGraph = &getAnalysis<hw::InstanceGraph>();
286 clockInputs.clear();
287 clockOutputs.clear();
288 rewritable.clear();
289 drivesClock.clear();
290
291 SmallVector<Value> worklist;
292 getOperation().walk(
293 [&](seq::ToClockOp op) { worklist.push_back(op.getInput()); });
294 markClocks(worklist);
295
296 if (clockInputs.empty() && clockOutputs.empty())
297 return markAllAnalysesPreserved();
298
299 for (auto module : getOperation().getOps<hw::HWModuleOp>())
300 rewriteModule(module);
301 // Only port types change, not the hierarchy.
302 markAnalysesPreserved<hw::InstanceGraph>();
303}
static hw::OutputOp getOutputOp(hw::HWModuleOp module)
static bool markPort(DenseMap< Operation *, BitVector > &ports, Operation *op, unsigned idx, unsigned numPorts)
Returns false if already marked, so that each port is visited only once.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition seq.py:1