CIRCT 24.0.0git
Loading...
Searching...
No Matches
HWSpecialize.cpp
Go to the documentation of this file.
1//===- HWSpecialize.cpp - hw module specialization ------------------------===//
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 transform performs specialization of parametric hw.module's.
10//
11//===----------------------------------------------------------------------===//
12
20#include "mlir/IR/Builders.h"
21#include "mlir/IR/IRMapping.h"
22#include "mlir/IR/PatternMatch.h"
23#include "mlir/Pass/Pass.h"
24#include "mlir/Transforms/DialectConversion.h"
25#include "llvm/ADT/TypeSwitch.h"
26
27namespace circt {
28namespace hw {
29#define GEN_PASS_DEF_HWSPECIALIZE
30#include "circt/Dialect/HW/Passes.h.inc"
31} // namespace hw
32} // namespace circt
33
34using namespace mlir;
35using namespace circt;
36using namespace hw;
37
38namespace {
39
40// Generates a module name by composing the name of 'moduleOp' and the set of
41// provided 'parameters'.
42static std::string generateModuleName(Namespace &ns, hw::HWModuleOp moduleOp,
43 ArrayAttr parameters) {
44 assert(parameters.size() != 0);
45 std::string name = moduleOp.getName().str();
46 for (auto param : parameters) {
47 auto paramAttr = cast<ParamDeclAttr>(param);
48 int64_t paramValue = cast<IntegerAttr>(paramAttr.getValue()).getInt();
49 name += "_" + paramAttr.getName().str() + "_" + std::to_string(paramValue);
50 }
51
52 // Query the namespace to generate a unique name.
53 return ns.newName(name).str();
54}
55
56// Returns true if any operand or result of 'op' is parametric.
57static bool isParametricOp(Operation *op) {
58 return llvm::any_of(op->getOperandTypes(), isParametricType) ||
59 llvm::any_of(op->getResultTypes(), isParametricType);
60}
61
62// Narrows 'value' using a comb.extract operation to the width of the
63// hw.array-typed 'array'.
64static FailureOr<Value> narrowValueToArrayWidth(OpBuilder &builder, Value array,
65 Value value) {
66 OpBuilder::InsertionGuard g(builder);
67 builder.setInsertionPointAfterValue(value);
68 auto arrayType = cast<hw::ArrayType>(array.getType());
69 unsigned hiBit = llvm::Log2_64_Ceil(arrayType.getNumElements());
70
71 return hiBit == 0
72 ? hw::ConstantOp::create(builder, value.getLoc(),
73 APInt(arrayType.getNumElements(), 0))
74 .getResult()
75 : comb::ExtractOp::create(builder, value.getLoc(), value,
76 /*lowBit=*/0, hiBit)
77 .getResult();
78}
79
80static hw::HWModuleOp targetModuleOp(hw::InstanceOp instanceOp,
81 const SymbolCache &sc) {
82 auto *targetOp = sc.getDefinition(instanceOp.getModuleNameAttr());
83 auto targetHWModule = dyn_cast<hw::HWModuleOp>(targetOp);
84 if (!targetHWModule)
85 return {}; // Won't specialize external modules.
86
87 if (targetHWModule.getParameters().size() == 0)
88 return {}; // nothing to record or specialize
89
90 return targetHWModule;
91}
92
93// Stores unique module parameters and references to them
94struct ParameterSpecializationRegistry {
95 llvm::MapVector<hw::HWModuleOp, llvm::SetVector<ArrayAttr>>
96 uniqueModuleParameters;
97
98 bool isRegistered(hw::HWModuleOp moduleOp, ArrayAttr parameters) const {
99 const auto *it = uniqueModuleParameters.find(moduleOp);
100 return it != uniqueModuleParameters.end() &&
101 it->second.contains(parameters);
102 }
103
104 void registerModuleOp(hw::HWModuleOp moduleOp, ArrayAttr parameters) {
105 uniqueModuleParameters[moduleOp].insert(parameters);
106 }
107};
108
109struct EliminateParamValueOpPattern : public OpRewritePattern<ParamValueOp> {
110 EliminateParamValueOpPattern(MLIRContext *context, ArrayAttr parameters)
111 : OpRewritePattern<ParamValueOp>(context), parameters(parameters) {}
112
113 LogicalResult matchAndRewrite(ParamValueOp op,
114 PatternRewriter &rewriter) const override {
115 // Substitute the param value op with an evaluated constant operation.
116 FailureOr<Attribute> evaluated =
117 evaluateParametricAttr(op.getLoc(), parameters, op.getValue());
118 if (failed(evaluated))
119 return failure();
120 rewriter.replaceOpWithNewOp<hw::ConstantOp>(
121 op, op.getType(),
122 cast<IntegerAttr>(*evaluated).getValue().getSExtValue());
123 return success();
124 }
125
126 ArrayAttr parameters;
127};
128
129// hw.array_get operations require indexes to be of equal width of the
130// array itself. Since indexes may originate from constants or parameters,
131// emit comb.extract operations to fulfill this invariant.
132struct NarrowArrayGetIndexPattern : public OpConversionPattern<ArrayGetOp> {
133public:
134 using OpConversionPattern<ArrayGetOp>::OpConversionPattern;
135
136 LogicalResult
137 matchAndRewrite(ArrayGetOp op, OpAdaptor adaptor,
138 ConversionPatternRewriter &rewriter) const override {
139 auto inputType = type_cast<ArrayType>(op.getInput().getType());
140 Type targetIndexType = IntegerType::get(
141 getContext(), inputType.getNumElements() == 1
142 ? 1
143 : llvm::Log2_64_Ceil(inputType.getNumElements()));
144
145 if (op.getIndex().getType().getIntOrFloatBitWidth() ==
146 targetIndexType.getIntOrFloatBitWidth())
147 return failure(); // nothing to do
148
149 // Narrow the index value.
150 FailureOr<Value> narrowedIndex =
151 narrowValueToArrayWidth(rewriter, op.getInput(), op.getIndex());
152 if (failed(narrowedIndex))
153 return failure();
154 rewriter.replaceOpWithNewOp<ArrayGetOp>(op, op.getInput(), *narrowedIndex);
155 return success();
156 }
157};
158
159// Generic pattern to convert parametric result types.
160struct ParametricTypeConversionPattern : public ConversionPattern {
161 ParametricTypeConversionPattern(MLIRContext *ctx,
162 TypeConverter &typeConverter,
163 ArrayAttr parameters)
164 : ConversionPattern(typeConverter, MatchAnyOpTypeTag(), /*benefit=*/1,
165 ctx),
166 parameters(parameters) {}
167
168 LogicalResult
169 matchAndRewrite(Operation *op, ArrayRef<Value> operands,
170 ConversionPatternRewriter &rewriter) const override {
171 llvm::SmallVector<Value, 4> convertedOperands;
172 // Update the result types of the operation
173 bool ok = true;
174 rewriter.modifyOpInPlace(op, [&]() {
175 // Mutate result types
176 for (auto it : llvm::enumerate(op->getResultTypes())) {
177 FailureOr<Type> res =
178 evaluateParametricType(op->getLoc(), parameters, it.value());
179 ok &= succeeded(res);
180 if (!ok)
181 return;
182 op->getResult(it.index()).setType(*res);
183 }
184
185 // Note: 'operands' have already been converted with the supplied type
186 // converter to this pattern. Make sure that we materialize this
187 // conversion by updating the operands to op.
188 op->setOperands(operands);
189 });
190
191 return success(ok);
192 };
193 ArrayAttr parameters;
194};
195
196struct HWSpecializePass
197 : public circt::hw::impl::HWSpecializeBase<HWSpecializePass> {
198 void runOnOperation() override;
199};
200
201static void populateTypeConversion(Location loc, TypeConverter &typeConverter,
202 ArrayAttr parameters) {
203 // Possibly parametric types
204 typeConverter.addConversion([=](hw::IntType type) {
205 return evaluateParametricType(loc, parameters, type);
206 });
207 typeConverter.addConversion([=](hw::ArrayType type) {
208 return evaluateParametricType(loc, parameters, type);
209 });
210
211 // Valid target types.
212 typeConverter.addConversion([](mlir::IntegerType type) { return type; });
213}
214
215// Registers any nested parametric instance ops of `target` for the next
216// specialization loop
217static LogicalResult registerNestedParametricInstanceOps(
218 HWModuleOp target, ArrayAttr parameters, SymbolCache &sc,
219 const ParameterSpecializationRegistry &currentRegistry,
220 ParameterSpecializationRegistry &nextRegistry,
221 llvm::DenseMap<hw::HWModuleOp,
222 llvm::DenseMap<ArrayAttr, llvm::SmallVector<hw::InstanceOp>>>
223 &parametersUsers) {
224 // Register any nested parametric instance ops for the next loop
225 auto walkResult = target->walk([&](InstanceOp instanceOp) -> WalkResult {
226 auto instanceParameters = instanceOp.getParameters();
227 // We can ignore non-parametric instances
228 if (instanceParameters.empty())
229 return WalkResult::advance();
230
231 // Replace instance parameters with evaluated versions
232 llvm::SmallVector<Attribute> evaluatedInstanceParameters;
233 evaluatedInstanceParameters.reserve(instanceParameters.size());
234 for (auto instanceParameter : instanceParameters) {
235 auto instanceParameterDecl = cast<hw::ParamDeclAttr>(instanceParameter);
236 auto instanceParameterValue = instanceParameterDecl.getValue();
237 auto evaluated = evaluateParametricAttr(target.getLoc(), parameters,
238 instanceParameterValue);
239 if (failed(evaluated))
240 return WalkResult::interrupt();
241 evaluatedInstanceParameters.push_back(
242 hw::ParamDeclAttr::get(instanceParameterDecl.getName(), *evaluated));
243 }
244
245 auto evaluatedInstanceParametersAttr =
246 ArrayAttr::get(target.getContext(), evaluatedInstanceParameters);
247
248 if (auto targetHWModule = targetModuleOp(instanceOp, sc)) {
249 if (!currentRegistry.isRegistered(targetHWModule,
250 evaluatedInstanceParametersAttr))
251 nextRegistry.registerModuleOp(targetHWModule,
252 evaluatedInstanceParametersAttr);
253 parametersUsers[targetHWModule][evaluatedInstanceParametersAttr]
254 .push_back(instanceOp);
255 }
256
257 return WalkResult::advance();
258 });
259
260 return failure(walkResult.wasInterrupted());
261}
262
263// Specializes the provided 'base' module into the 'target' module. By doing
264// so, we create a new module which
265// 1. has no parameters
266// 2. has a name composing the name of 'base' as well as the 'parameters'
267// parameters.
268// 3. Has a top-level interface with any parametric types resolved.
269// 4. Any references to module parameters have been replaced with the
270// parameter value.
271static LogicalResult specializeModule(
272 OpBuilder builder, ArrayAttr parameters, SymbolCache &sc, Namespace &ns,
273 HWModuleOp source, HWModuleOp &target,
274 const ParameterSpecializationRegistry &currentRegistry,
275 ParameterSpecializationRegistry &nextRegistry,
276 llvm::DenseMap<hw::HWModuleOp,
277 llvm::DenseMap<ArrayAttr, llvm::SmallVector<hw::InstanceOp>>>
278 &parametersUsers) {
279 auto *ctx = builder.getContext();
280 // Update the types of the source module ports based on evaluating any
281 // parametric in/output ports.
282 ModulePortInfo ports(source.getPortList());
283 for (auto in : llvm::enumerate(source.getInputTypes())) {
284 FailureOr<Type> resType =
285 evaluateParametricType(source.getLoc(), parameters, in.value());
286 if (failed(resType))
287 return failure();
288 ports.atInput(in.index()).type = *resType;
289 }
290 for (auto out : llvm::enumerate(source.getOutputTypes())) {
291 FailureOr<Type> resolvedType =
292 evaluateParametricType(source.getLoc(), parameters, out.value());
293 if (failed(resolvedType))
294 return failure();
295 ports.atOutput(out.index()).type = *resolvedType;
296 }
297
298 // Create the specialized module using the evaluated port info.
299 target = HWModuleOp::create(
300 builder, source.getLoc(),
301 StringAttr::get(ctx, generateModuleName(ns, source, parameters)), ports);
302
303 // Erase the default created hw.output op - we'll copy the correct operation
304 // during body elaboration.
305 (*target.getOps<hw::OutputOp>().begin()).erase();
306
307 // Clone body of the source into the target. Use ValueMapper to ensure safe
308 // cloning in the presence of backedges.
309 BackedgeBuilder bb(builder, source.getLoc());
310 ValueMapper mapper(&bb);
311 for (auto &&[src, dst] : llvm::zip(source.getBodyBlock()->getArguments(),
312 target.getBodyBlock()->getArguments()))
313 mapper.set(src, dst);
314 builder.setInsertionPointToStart(target.getBodyBlock());
315
316 for (auto &op : source.getOps()) {
317 IRMapping bvMapper;
318 for (auto operand : op.getOperands())
319 bvMapper.map(operand, mapper.get(operand));
320 auto *newOp = builder.clone(op, bvMapper);
321 for (auto &&[oldRes, newRes] :
322 llvm::zip(op.getResults(), newOp->getResults()))
323 mapper.set(oldRes, newRes);
324 }
325
326 // Register any nested parametric instance ops for the next loop
327 auto nestedRegistrationResult = registerNestedParametricInstanceOps(
328 target, parameters, sc, currentRegistry, nextRegistry, parametersUsers);
329 if (failed(nestedRegistrationResult))
330 return failure();
331
332 // We've now created a separate copy of the source module with a rewritten
333 // top-level interface. Next, we enter the module to convert parametric
334 // types within operations.
335 RewritePatternSet patterns(ctx);
336 TypeConverter t;
337 populateTypeConversion(target.getLoc(), t, parameters);
338 patterns.add<EliminateParamValueOpPattern>(ctx, parameters);
339 patterns.add<NarrowArrayGetIndexPattern>(ctx);
340 patterns.add<ParametricTypeConversionPattern>(ctx, t, parameters);
341 ConversionTarget convTarget(*ctx);
342 convTarget.addLegalOp<hw::HWModuleOp>();
343 convTarget.addIllegalOp<hw::ParamValueOp>();
344
345 // Generic legalization of converted operations.
346 convTarget.markUnknownOpDynamicallyLegal(
347 [](Operation *op) { return !isParametricOp(op); });
348
349 return applyPartialConversion(target, convTarget, std::move(patterns));
350}
351
352void HWSpecializePass::runOnOperation() {
353 ModuleOp module = getOperation();
354
355 // Record unique module parameters and references to these.
356 llvm::DenseMap<hw::HWModuleOp,
357 llvm::DenseMap<ArrayAttr, llvm::SmallVector<hw::InstanceOp>>>
358 parametersUsers;
359 ParameterSpecializationRegistry registry;
360
361 // Maintain a symbol cache for fast lookup during module specialization.
362 SymbolCache sc;
363 sc.addDefinitions(module);
364 Namespace ns;
365 ns.add(sc);
366
367 for (auto hwModule : module.getOps<hw::HWModuleOp>()) {
368 // If this module is parametric, defer registering its parametric
369 // instantiations until this module is specialized
370 if (!hwModule.getParameters().empty())
371 continue;
372 for (auto instanceOp : hwModule.getOps<hw::InstanceOp>()) {
373 if (auto targetHWModule = targetModuleOp(instanceOp, sc)) {
374 auto parameters = instanceOp.getParameters();
375 registry.registerModuleOp(targetHWModule, parameters);
376
377 parametersUsers[targetHWModule][parameters].push_back(instanceOp);
378 }
379 }
380 }
381
382 // Create specialized modules.
383 OpBuilder builder = OpBuilder(&getContext());
384 builder.setInsertionPointToStart(module.getBody());
385 llvm::DenseMap<hw::HWModuleOp, llvm::DenseMap<ArrayAttr, hw::HWModuleOp>>
386 specializations;
387
388 // For every module specialization, any nested parametric modules will be
389 // registered for the next loop. We loop until no new nested modules have been
390 // registered.
391 while (!registry.uniqueModuleParameters.empty()) {
392 // The registry for the next specialization loop
393 ParameterSpecializationRegistry nextRegistry;
394 for (auto it : registry.uniqueModuleParameters) {
395 for (auto parameters : it.second) {
396 HWModuleOp specializedModule;
397 if (failed(specializeModule(builder, parameters, sc, ns, it.first,
398 specializedModule, registry, nextRegistry,
399 parametersUsers))) {
400 signalPassFailure();
401 return;
402 }
403
404 // Extend the symbol cache with the newly created module.
405 sc.addDefinition(specializedModule.getNameAttr(), specializedModule);
406
407 // Add the specialization
408 specializations[it.first][parameters] = specializedModule;
409 }
410 }
411
412 // Transfer newly registered specializations to iterate over
413 registry.uniqueModuleParameters =
414 std::move(nextRegistry.uniqueModuleParameters);
415 }
416
417 // Rewrite instances of specialized modules to the specialized module.
418 for (auto it : specializations) {
419 auto unspecialized = it.getFirst();
420 auto &users = parametersUsers[unspecialized];
421 for (auto specialization : it.getSecond()) {
422 auto parameters = specialization.getFirst();
423 auto specializedModule = specialization.getSecond();
424 for (auto instanceOp : users[parameters]) {
425 instanceOp->setAttr("moduleName",
426 FlatSymbolRefAttr::get(specializedModule));
427 instanceOp->setAttr("parameters", ArrayAttr::get(&getContext(), {}));
428 }
429 }
430 }
431}
432
433} // namespace
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void populateTypeConversion(TypeConverter &converter)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
Instantiate one of these and use it to build typed backedges.
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:86
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
void addDefinition(mlir::Attribute key, mlir::Operation *op) override
In the building phase, add symbols.
Definition SymCache.h:88
mlir::Operation * getDefinition(mlir::Attribute attr) const override
Lookup a definition for 'symbol' in the cache.
Definition SymCache.h:94
The ValueMapper class facilitates the definition and connection of SSA def-use chains between two loc...
Definition ValueMapper.h:35
create(low_bit, result_type, input=None)
Definition comb.py:187
create(data_type, value)
Definition hw.py:433
mlir::FailureOr< mlir::TypedAttr > evaluateParametricAttr(mlir::Location loc, mlir::ArrayAttr parameters, mlir::Attribute paramAttr, bool emitErrors=true)
Evaluates a parametric attribute (param.decl.ref/param.expr) based on a set of provided parameter val...
BaseTy type_cast(Type type)
Definition HWTypes.h:222
mlir::FailureOr< mlir::Type > evaluateParametricType(mlir::Location loc, mlir::ArrayAttr parameters, mlir::Type type, bool emitErrors=true)
Returns a resolved version of 'type' wherein any parameter reference has been evaluated based on the ...
bool isParametricType(mlir::Type t)
Returns true if any part of t is parametric.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
This holds a decoded list of input/inout and output ports for a module or instance.