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.isInOut() && port.type.isInteger(1) && !port.getSym() &&
120 isRewritable(module);
121}
122
123/// Only promote an output if its driver already is a clock, so no logic is
124/// moved into the clock domain. Instance chains are followed iteratively to
125/// avoid deep recursion on deep hierarchies.
126bool InferClockPortsPass::canDriveClock(hw::HWModuleOp module,
127 unsigned output) {
128 SmallVector<std::pair<Operation *, unsigned>> chain;
129 bool result = false;
130 while (true) {
131 auto key = std::make_pair(module.getOperation(), output);
132 auto [it, inserted] = drivesClock.try_emplace(key, false);
133 if (!inserted) {
134 result = it->second;
135 break;
136 }
137 chain.push_back(key);
138 if (!canPromote(module, module.getPortIdForOutputId(output)))
139 break;
140
141 auto driver = getOutputOp(module).getOperand(output);
142 if (driver.getDefiningOp<seq::FromClockOp>()) {
143 result = true;
144 break;
145 }
146 if (auto arg = dyn_cast<BlockArgument>(driver)) {
147 result =
148 canPromote(module, module.getPortIdForInputId(arg.getArgNumber()));
149 break;
150 }
151 auto instance = driver.getDefiningOp<hw::InstanceOp>();
152 if (!instance)
153 break;
154 module = getTarget(instance);
155 if (!module)
156 break;
157 output = cast<OpResult>(driver).getResultNumber();
158 }
159
160 for (auto key : chain)
161 drivesClock[key] = result;
162 return result;
163}
164
165void InferClockPortsPass::markClocks(SmallVectorImpl<Value> &worklist) {
166 while (!worklist.empty()) {
167 auto value = worklist.pop_back_val();
168 if (!value.getType().isInteger(1))
169 continue;
170
171 // A clock input needs a clock from every instantiation.
172 if (auto arg = dyn_cast<BlockArgument>(value)) {
173 auto module = dyn_cast<hw::HWModuleOp>(arg.getOwner()->getParentOp());
174 unsigned input = arg.getArgNumber();
175 if (!module || !canPromote(module, module.getPortIdForInputId(input)) ||
176 !markPort(clockInputs, module, input, module.getNumInputPorts()))
177 continue;
178 LLVM_DEBUG(llvm::dbgs() << "Clock input " << module.getModuleName() << "."
179 << module.getInputName(input) << "\n");
180 ++numInputPortsPromoted;
181 for (auto *use : instanceGraph->lookup(module)->uses())
182 if (auto instance = use->getInstance<hw::InstanceOp>())
183 worklist.push_back(instance.getOperand(input));
184 continue;
185 }
186
187 if (auto instance = value.getDefiningOp<hw::InstanceOp>()) {
188 auto module = getTarget(instance);
189 unsigned output = cast<OpResult>(value).getResultNumber();
190 if (!module || !canDriveClock(module, output) ||
191 !markPort(clockOutputs, module, output, module.getNumOutputPorts()))
192 continue;
193 LLVM_DEBUG(llvm::dbgs() << "Clock output " << module.getModuleName()
194 << "." << module.getOutputName(output) << "\n");
195 ++numOutputPortsPromoted;
196 worklist.push_back(getOutputOp(module).getOperand(output));
197 continue;
198 }
199
200 // Anything else stays `i1` behind its `seq.to_clock`.
201 }
202}
203
204void InferClockPortsPass::rewriteModule(hw::HWModuleOp module) {
205 auto clockType = seq::ClockType::get(&getContext());
206 auto *body = module.getBodyBlock();
207 auto inputs = clockInputs.lookup(module);
208 auto outputs = clockOutputs.lookup(module);
209 SmallVector<std::pair<hw::InstanceOp, hw::HWModuleOp>> instances;
210 module.walk([&](hw::InstanceOp instance) {
211 if (auto target = getTarget(instance))
212 if (clockInputs.count(target) || clockOutputs.count(target))
213 instances.push_back({instance, target});
214 });
215 if (inputs.none() && outputs.none() && instances.empty())
216 return;
217
218 // Candidates for folding once all operands are updated.
220
221 auto retype = [&](Value value, OpBuilder &builder) {
222 value.setType(clockType);
223 // Existing users still expect `i1`.
224 auto cast = seq::FromClockOp::create(builder, value.getLoc(), value);
225 value.replaceAllUsesExcept(cast, cast);
226 casts.insert(cast);
227 };
228
229 // Look through `seq.from_clock` to avoid a redundant cast pair.
230 auto toClock = [&](Value value, OpBuilder &builder) -> Value {
231 if (auto cast = value.getDefiningOp<seq::FromClockOp>()) {
232 casts.insert(cast);
233 return cast.getInput();
234 }
235 return seq::ToClockOp::create(builder, value.getLoc(), value);
236 };
237
238 // Retype values first so `toClock` can look through the new casts.
239 auto builder = OpBuilder::atBlockBegin(body);
240 for (unsigned input : inputs.set_bits())
241 retype(body->getArgument(input), builder);
242 for (auto [instance, target] : instances) {
243 builder.setInsertionPointAfter(instance);
244 for (unsigned output : clockOutputs.lookup(target).set_bits())
245 retype(instance.getResult(output), builder);
246 }
247
248 auto outputOp = getOutputOp(module);
249 builder.setInsertionPoint(outputOp);
250 for (unsigned output : outputs.set_bits())
251 outputOp.setOperand(output, toClock(outputOp.getOperand(output), builder));
252 for (auto [instance, target] : instances) {
253 builder.setInsertionPoint(instance);
254 for (unsigned input : clockInputs.lookup(target).set_bits())
255 instance.setOperand(input, toClock(instance.getOperand(input), builder));
256 }
257
258 // Pre-existing `seq.to_clock` users of retyped values are now redundant.
259 for (auto cast : casts) {
260 for (auto *user : llvm::make_early_inc_range(cast->getUsers())) {
261 if (auto toClockOp = dyn_cast<seq::ToClockOp>(user)) {
262 toClockOp.replaceAllUsesWith(cast.getInput());
263 toClockOp.erase();
264 ++numCastsRemoved;
265 }
266 }
267 if (cast.use_empty())
268 cast.erase();
269 }
270
271 if (inputs.none() && outputs.none())
272 return;
273 auto type = module.getHWModuleType();
274 SmallVector<hw::ModulePort> ports(type.getPorts());
275 for (unsigned input : inputs.set_bits())
276 ports[type.getPortIdForInputId(input)].type = clockType;
277 for (unsigned output : outputs.set_bits())
278 ports[type.getPortIdForOutputId(output)].type = clockType;
279 module.setHWModuleType(hw::ModuleType::get(&getContext(), ports));
280}
281
282void InferClockPortsPass::runOnOperation() {
283 instanceGraph = &getAnalysis<hw::InstanceGraph>();
284 clockInputs.clear();
285 clockOutputs.clear();
286 rewritable.clear();
287 drivesClock.clear();
288
289 SmallVector<Value> worklist;
290 getOperation().walk(
291 [&](seq::ToClockOp op) { worklist.push_back(op.getInput()); });
292 markClocks(worklist);
293
294 if (clockInputs.empty() && clockOutputs.empty())
295 return markAllAnalysesPreserved();
296
297 for (auto module : getOperation().getOps<hw::HWModuleOp>())
298 rewriteModule(module);
299 // Only port types change, not the hierarchy.
300 markAnalysesPreserved<hw::InstanceGraph>();
301}
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.
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
Definition seq.py:1