CIRCT 23.0.0git
Loading...
Searching...
No Matches
LowerIntmodules.cpp
Go to the documentation of this file.
1//===- LowerIntmodules.cpp - Lower intmodules to ops ------------*- 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//
9// This file defines the LowerIntmodules pass. This pass processes
10// FIRRTL intmodules and replaces all instances with generic intrinsic ops.
11//
12//===----------------------------------------------------------------------===//
13
18#include "mlir/IR/Diagnostics.h"
19#include "mlir/Pass/Pass.h"
20
21namespace circt {
22namespace firrtl {
23#define GEN_PASS_DEF_LOWERINTMODULES
24#include "circt/Dialect/FIRRTL/Passes.h.inc"
25} // namespace firrtl
26} // namespace circt
27
28using namespace circt;
29using namespace firrtl;
30
31//===----------------------------------------------------------------------===//
32// Pass Infrastructure
33//===----------------------------------------------------------------------===//
34
35namespace {
36struct LowerIntmodulesPass
37 : public circt::firrtl::impl::LowerIntmodulesBase<LowerIntmodulesPass> {
38 using Base::Base;
39
40 void runOnOperation() override;
41};
42} // namespace
43
44static LogicalResult checkModForAnnotations(FModuleLike mod, StringRef name) {
45 if (!AnnotationSet(mod).empty())
46 return mod.emitError(name)
47 << " cannot have annotations since it is an intrinsic";
48 return success();
49}
50
51static LogicalResult checkInstForAnnotations(FInstanceLike inst,
52 StringRef name) {
53 if (!AnnotationSet(inst).empty())
54 return inst.emitError(name)
55 << " instance cannot have annotations since it is an intrinsic";
56 return success();
57}
58
59static Value replaceResultWithWire(ImplicitLocOpBuilder &builder, Value result,
60 ValueRange domains = {}) {
61 auto wire = WireOp::create(builder, result.getType(), /*name=*/StringRef{},
62 NameKindEnum::DroppableName,
63 /*annotations=*/ArrayRef<Attribute>{},
64 /*innerSym=*/StringAttr{},
65 /*forceable=*/false, domains)
66 .getResult();
67 result.replaceAllUsesWith(wire);
68 return wire;
69}
70
71static LogicalResult swapEICGEnableInputs(FExtModuleOp op, InstanceOp inst,
72 SmallVectorImpl<Value> &inputs) {
73 if (inputs.size() <= 2)
74 return success();
75
76 auto port1 = inst.getPortName(1);
77 auto port2 = inst.getPortName(2);
78 if (port1 != "test_en")
79 return mlir::emitError(op.getPortLocation(1),
80 "expected port named 'test_en'");
81 if (port2 != "en")
82 return mlir::emitError(op.getPortLocation(2), "expected port named 'en'");
83 std::swap(inputs[1], inputs[2]);
84 return success();
85}
86
87static LogicalResult lowerLegacyEICGWrapperInstance(FExtModuleOp op,
88 InstanceOp inst) {
89 ImplicitLocOpBuilder builder(op.getLoc(), inst);
90
91 SmallVector<Value> inputs;
92 for (auto result : inst.getResults().drop_back())
93 inputs.push_back(replaceResultWithWire(builder, result));
94
95 // en and test_en are swapped between extmodule and intrinsic.
96 if (failed(swapEICGEnableInputs(op, inst, inputs)))
97 return failure();
98
99 auto intop = GenericIntrinsicOp::create(builder, builder.getType<ClockType>(),
100 "circt_clock_gate", inputs,
101 op.getParameters());
102 inst.getResults().back().replaceAllUsesWith(intop.getResult());
103 return success();
104}
105
106static LogicalResult lowerDomainEICGWrapperInstance(FExtModuleOp op,
107 InstanceOp inst) {
108 ImplicitLocOpBuilder builder(op.getLoc(), inst);
109
110 auto numPorts = op.getNumPorts();
111 unsigned numDataInputs = numPorts - 3;
112 unsigned outIdx = numDataInputs;
113
114 // Domain ports are emitted first so that data wires can refer to them in
115 // their `domains [...]` operand list.
116 auto domA = replaceResultWithWire(builder, inst.getResult(numPorts - 2));
117 auto domB = replaceResultWithWire(builder, inst.getResult(numPorts - 1));
118
119 SmallVector<Value> inputs;
120 for (unsigned i = 0; i != numDataInputs; ++i)
121 inputs.push_back(replaceResultWithWire(builder, inst.getResult(i), {domA}));
122 auto out = replaceResultWithWire(builder, inst.getResult(outIdx), {domB});
123
124 // en and test_en are swapped between extmodule and intrinsic.
125 if (failed(swapEICGEnableInputs(op, inst, inputs)))
126 return failure();
127
128 auto intop = GenericIntrinsicOp::create(builder, builder.getType<ClockType>(),
129 "circt_clock_gate", inputs,
130 op.getParameters());
131 auto cast = UnsafeDomainCastOp::create(builder, out.getType(),
132 intop.getResult(), ValueRange{domB})
133 .getResult();
134 MatchingConnectOp::create(builder, out, cast);
135 return success();
136}
137
138// This is the main entrypoint for the conversion pass.
139void LowerIntmodulesPass::runOnOperation() {
140 auto &ig = getAnalysis<InstanceGraph>();
141
142 bool changed = false;
143 bool warnEICGwrapperDropsDedupAnno = false;
144
145 // Convert to int ops.
146 for (auto op :
147 llvm::make_early_inc_range(getOperation().getOps<FIntModuleOp>())) {
148 auto *node = ig.lookup(op);
149 changed = true;
150
151 if (failed(checkModForAnnotations(op, op.getIntrinsic())))
152 return signalPassFailure();
153
154 for (auto *use : llvm::make_early_inc_range(node->uses())) {
155 auto inst = use->getInstance<InstanceOp>();
156 // The verifier should have already checked that intmodules are only
157 // instantiated with InstanceOp, not InstanceChoiceOp.
158 assert(inst && "intmodule must be instantiated with instance op");
159
160 if (failed(checkInstForAnnotations(inst, op.getIntrinsic())))
161 return signalPassFailure();
162
163 // Replace the instance of this intmodule with firrtl.int.generic.
164 // Inputs become operands, outputs are the result (if any).
165 ImplicitLocOpBuilder builder(op.getLoc(), inst);
166
167 SmallVector<Value> inputs;
168 struct OutputInfo {
169 Value result;
170 BundleType::BundleElement element;
171 };
172 SmallVector<OutputInfo> outputs;
173 for (auto [idx, result] : llvm::enumerate(inst.getResults())) {
174 // Replace inputs with wires that will be used as operands.
175 if (inst.getPortDirection(idx) != Direction::Out) {
176 auto w = WireOp::create(builder, result.getLoc(), result.getType())
177 .getResult();
178 result.replaceAllUsesWith(w);
179 inputs.push_back(w);
180 continue;
181 }
182
183 // Gather outputs. This will become a bundle if more than one, but
184 // typically there are zero or one.
185 auto ftype = dyn_cast<FIRRTLBaseType>(inst.getType(idx));
186 if (!ftype) {
187 inst.emitError("intrinsic has non-FIRRTL or non-base port type")
188 << inst.getType(idx);
189 signalPassFailure();
190 return;
191 }
192 outputs.push_back(
193 OutputInfo{inst.getResult(idx),
194 BundleType::BundleElement(inst.getPortNameAttr(idx),
195 /*isFlip=*/false, ftype)});
196 }
197
198 // Create the replacement operation.
199 if (outputs.empty()) {
200 // If no outputs, just create the operation.
201 GenericIntrinsicOp::create(builder, /*result=*/Type(),
202 op.getIntrinsicAttr(), inputs,
203 op.getParameters());
204
205 } else if (outputs.size() == 1) {
206 // For single output, the result is the output.
207 auto resultType = outputs.front().element.type;
208 auto intop = GenericIntrinsicOp::create(builder, resultType,
209 op.getIntrinsicAttr(), inputs,
210 op.getParameters());
211 outputs.front().result.replaceAllUsesWith(intop.getResult());
212 } else {
213 // For multiple outputs, create a bundle with fields for each output
214 // and replace users with subfields.
215 auto resultType = builder.getType<BundleType>(llvm::map_to_vector(
216 outputs, [](const auto &info) { return info.element; }));
217 auto intop = GenericIntrinsicOp::create(builder, resultType,
218 op.getIntrinsicAttr(), inputs,
219 op.getParameters());
220 for (auto &output : outputs)
221 output.result.replaceAllUsesWith(SubfieldOp::create(
222 builder, intop.getResult(), output.element.name));
223 }
224 // Remove instance from IR and instance graph.
225 use->erase();
226 inst.erase();
227 ++numInstances;
228 }
229 // Remove intmodule from IR and instance graph.
230 ig.erase(node);
231 op.erase();
232 ++numIntmodules;
233 }
234
235 // Special handling for magic EICG wrapper extmodule. Deprecate and remove.
236 //
237 // This supports both a domain-less form and a form with domains. Both are
238 // rigid in their structure and expect an exact port order.
239 if (fixupEICGWrapper) {
240 constexpr StringRef eicgName = "EICG_wrapper";
241 for (auto op :
242 llvm::make_early_inc_range(getOperation().getOps<FExtModuleOp>())) {
243 if (op.getDefname() != eicgName)
244 continue;
245
246 // FIXME: Dedup group annotation could be annotated to EICG_wrapper but
247 // it causes an error with `fixupEICGWrapper`. For now drop the
248 // annotation until we fully migrate into EICG intrinsic.
249 if (AnnotationSet::removeAnnotations(op, firrtl::dedupGroupAnnoClass))
250 if (!warnEICGwrapperDropsDedupAnno) {
251 op.emitWarning() << "Annotation " << firrtl::dedupGroupAnnoClass
252 << " on EICG_wrapper is dropped";
253 warnEICGwrapperDropsDedupAnno = true;
254 }
255
256 if (failed(checkModForAnnotations(op, eicgName)))
257 return signalPassFailure();
258
259 // Detect the optional domain-port form. When present, the wrapper has
260 // exactly two trailing domain input ports (A, B) and every preceding
261 // port has its `domains [...]` association resolved by them: data
262 // inputs bind to A, the output binds to B. Without this, the layout is
263 // the legacy `[in, test_en?, en, out]`.
264 auto numPorts = op.getNumPorts();
265 bool hasDomainPorts =
266 numPorts >= 2 && isa<DomainType>(op.getPortType(numPorts - 1));
267
268 auto *node = ig.lookup(op);
269 changed = true;
270 for (auto *use : llvm::make_early_inc_range(node->uses())) {
271 auto inst = use->getInstance<InstanceOp>();
272 if (!inst) {
273 mlir::emitError(use->getInstance()->getLoc())
274 << "EICG_wrapper must be instantiated with instance op, not via '"
275 << use->getInstance().getOperation()->getName() << "'";
276 return signalPassFailure();
277 }
278 if (failed(checkInstForAnnotations(inst, eicgName)))
279 return signalPassFailure();
280
281 if (failed(hasDomainPorts ? lowerDomainEICGWrapperInstance(op, inst)
283 return signalPassFailure();
284
285 // Remove instance from IR and instance graph.
286 use->erase();
287 inst.erase();
288 }
289 // Remove extmodule from IR and instance graph.
290 ig.erase(node);
291 op.erase();
292 }
293 }
294
295 markAnalysesPreserved<InstanceGraph>();
296
297 if (!changed)
298 markAllAnalysesPreserved();
299}
assert(baseType &&"element must be base type")
static Value replaceResultWithWire(ImplicitLocOpBuilder &builder, Value result, ValueRange domains={})
static LogicalResult checkModForAnnotations(FModuleLike mod, StringRef name)
static LogicalResult lowerDomainEICGWrapperInstance(FExtModuleOp op, InstanceOp inst)
static LogicalResult checkInstForAnnotations(FInstanceLike inst, StringRef name)
static LogicalResult swapEICGEnableInputs(FExtModuleOp op, InstanceOp inst, SmallVectorImpl< Value > &inputs)
static LogicalResult lowerLegacyEICGWrapperInstance(FExtModuleOp op, InstanceOp inst)
static InstancePath empty
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.