CIRCT 24.0.0git
Loading...
Searching...
No Matches
HWArithToHW.cpp
Go to the documentation of this file.
1//===- HWArithToHW.cpp - HWArith to HW Lowering pass ------------*- 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 is the main HWArith to HW Lowering Pass Implementation.
10//
11//===----------------------------------------------------------------------===//
12
21#include "mlir/IR/AttrTypeSubElements.h"
22#include "mlir/Pass/Pass.h"
23
24#include "mlir/Transforms/DialectConversion.h"
25#include "llvm/ADT/TypeSwitch.h"
26
27namespace circt {
28#define GEN_PASS_DEF_HWARITHTOHW
29#include "circt/Conversion/Passes.h.inc"
30} // namespace circt
31
32using namespace mlir;
33using namespace circt;
34using namespace hwarith;
35
36//===----------------------------------------------------------------------===//
37// Utility functions
38//===----------------------------------------------------------------------===//
39
40// Function for setting the 'sv.namehint' attribute on 'newOp' based on any
41// currently existing 'sv.namehint' attached to the source operation of 'value'.
42// The user provides a callback which returns a new namehint based on the old
43// namehint.
44static void
45improveNamehint(Value oldValue, Operation *newOp,
46 llvm::function_ref<std::string(StringRef)> namehintCallback) {
47 if (auto *sourceOp = oldValue.getDefiningOp()) {
48 if (auto namehint =
49 sourceOp->getAttrOfType<mlir::StringAttr>("sv.namehint")) {
50 auto newNamehint = namehintCallback(namehint.strref());
51 newOp->setAttr("sv.namehint",
52 StringAttr::get(oldValue.getContext(), newNamehint));
53 }
54 }
55}
56
57// Extract a bit range, specified via start bit and width, from a given value.
58static Value extractBits(OpBuilder &builder, Location loc, Value value,
59 unsigned startBit, unsigned bitWidth) {
60 Value extractedValue =
61 builder.createOrFold<comb::ExtractOp>(loc, value, startBit, bitWidth);
62 Operation *definingOp = extractedValue.getDefiningOp();
63 if (extractedValue != value && definingOp) {
64 // only change namehint if a new operation was created.
65 improveNamehint(value, definingOp, [&](StringRef oldNamehint) {
66 return (oldNamehint + "_" + std::to_string(startBit) + "_to_" +
67 std::to_string(startBit + bitWidth))
68 .str();
69 });
70 }
71 return extractedValue;
72}
73
74// Perform the specified bit-extension (either sign- or zero-extension) for a
75// given value to a desired target width.
76static Value extendTypeWidth(OpBuilder &builder, Location loc, Value value,
77 unsigned targetWidth, bool signExtension) {
78 unsigned sourceWidth = value.getType().getIntOrFloatBitWidth();
79 unsigned extensionLength = targetWidth - sourceWidth;
80
81 if (extensionLength == 0)
82 return value;
83
84 Value extensionBits;
85 // https://circt.llvm.org/docs/Dialects/Comb/RationaleComb/#no-complement-negate-zext-sext-operators
86 if (signExtension) {
87 // Sign extension
88 Value highBit = extractBits(builder, loc, value,
89 /*startBit=*/sourceWidth - 1, /*bitWidth=*/1);
90 extensionBits =
91 builder.createOrFold<comb::ReplicateOp>(loc, highBit, extensionLength);
92 } else {
93 // Zero extension
94 extensionBits =
95 hw::ConstantOp::create(builder, loc,
96 builder.getIntegerType(extensionLength), 0)
97 ->getOpResult(0);
98 }
99
100 auto extOp = comb::ConcatOp::create(builder, loc, extensionBits, value);
101 improveNamehint(value, extOp, [&](StringRef oldNamehint) {
102 return (oldNamehint + "_" + (signExtension ? "sext_" : "zext_") +
103 std::to_string(targetWidth))
104 .str();
105 });
106
107 return extOp->getOpResult(0);
108}
109
110static bool isSignednessType(Type type) {
111 auto match =
112 llvm::TypeSwitch<Type, bool>(type)
113 .Case<IntegerType>([](auto type) { return !type.isSignless(); })
114 .Case<hw::ArrayType>(
115 [](auto type) { return isSignednessType(type.getElementType()); })
116 .Case<hw::UnpackedArrayType>(
117 [](auto type) { return isSignednessType(type.getElementType()); })
118 .Case<hw::StructType>([](auto type) {
119 return llvm::any_of(type.getElements(), [](auto element) {
120 return isSignednessType(element.type);
121 });
122 })
123 .Case<hw::UnionType>([](auto type) {
124 return llvm::any_of(type.getElements(), [](auto element) {
125 return isSignednessType(element.type);
126 });
127 })
128 .Case<hw::InOutType>(
129 [](auto type) { return isSignednessType(type.getElementType()); })
130 .Case<hw::TypeAliasType>(
131 [](auto type) { return isSignednessType(type.getInnerType()); })
132 .Default([](auto type) { return false; });
133
134 return match;
135}
136
137static bool isSignednessAttr(Attribute attr) {
138 if (auto typeAttr = dyn_cast<TypeAttr>(attr))
139 return isSignednessType(typeAttr.getValue());
140 return false;
141}
142
143/// Returns true if the given `op` is considered as legal for HWArith
144/// conversion.
145static bool isLegalOp(Operation *op) {
146 if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
147 return llvm::none_of(funcOp.getArgumentTypes(), isSignednessType) &&
148 llvm::none_of(funcOp.getResultTypes(), isSignednessType) &&
149 llvm::none_of(funcOp.getFunctionBody().getArgumentTypes(),
151 }
152
153 if (auto modOp = dyn_cast<hw::HWModuleLike>(op)) {
154 return llvm::none_of(modOp.getPortTypes(), isSignednessType) &&
155 llvm::none_of(modOp.getModuleBody().getArgumentTypes(),
157 }
158
159 auto attrs = llvm::map_range(op->getAttrs(), [](const NamedAttribute &attr) {
160 return attr.getValue();
161 });
162
163 bool operandsOK = llvm::none_of(op->getOperandTypes(), isSignednessType);
164 bool resultsOK = llvm::none_of(op->getResultTypes(), isSignednessType);
165 bool attrsOK = llvm::none_of(attrs, isSignednessAttr);
166 return operandsOK && resultsOK && attrsOK;
167}
168
169//===----------------------------------------------------------------------===//
170// Conversion patterns
171//===----------------------------------------------------------------------===//
172
173namespace {
174struct ConstantOpLowering : public OpConversionPattern<ConstantOp> {
176
177 LogicalResult
178 matchAndRewrite(ConstantOp constOp, OpAdaptor adaptor,
179 ConversionPatternRewriter &rewriter) const override {
180 // The result may be a type alias, which is retained with a signless inner
181 // type.
182 Type resultType = getTypeConverter()->convertType(constOp.getType());
183 if (!resultType)
184 return failure();
185 auto valueAttr = rewriter.getIntegerAttr(
186 hw::type_cast<IntegerType>(resultType), constOp.getConstantValue());
187 rewriter.replaceOpWithNewOp<hw::ConstantOp>(constOp, resultType, valueAttr);
188 return success();
189 }
190};
191
192struct AggregateConstantOpLowering
193 : public OpConversionPattern<hw::AggregateConstantOp> {
194 using OpConversionPattern<hw::AggregateConstantOp>::OpConversionPattern;
195
196 LogicalResult
197 matchAndRewrite(hw::AggregateConstantOp op, OpAdaptor adaptor,
198 ConversionPatternRewriter &rewriter) const override {
199 auto fields = op.getFieldsAttr().replace([&](IntegerType type) {
200 return getTypeConverter()->convertType(type);
201 });
202 if (!fields)
203 return rewriter.notifyMatchFailure(op,
204 "attribute type conversion failed");
205 rewriter.modifyOpInPlace(
206 op, [&] { op.setFieldsAttr(cast<ArrayAttr>(fields)); });
207 return doTypeConversion(op, adaptor.getOperands(), rewriter,
208 getTypeConverter());
209 }
210};
211
212struct DivOpLowering : public OpConversionPattern<DivOp> {
214
215 LogicalResult
216 matchAndRewrite(DivOp op, OpAdaptor adaptor,
217 ConversionPatternRewriter &rewriter) const override {
218 auto loc = op.getLoc();
219 auto isLhsTypeSigned =
220 cast<IntegerType>(op.getOperand(0).getType()).isSigned();
221 auto rhsType = cast<IntegerType>(op.getOperand(1).getType());
222 auto targetType = cast<IntegerType>(op.getResult().getType());
223
224 // comb.div* needs identical bitwidths for its operands and its result.
225 // Hence, we need to calculate the minimal bitwidth that can be used to
226 // represent the result as well as the operands without precision or sign
227 // loss. The target size only depends on LHS and already handles the edge
228 // cases where the bitwidth needs to be increased by 1. Thus, the targetType
229 // is good enough for both the result as well as LHS.
230 // The bitwidth for RHS is bit tricky. If the RHS is unsigned and we are
231 // about to perform a signed division, then we need one additional bit to
232 // avoid misinterpretation of RHS as a signed value!
233 bool signedDivision = targetType.isSigned();
234 unsigned extendSize = std::max(
235 targetType.getWidth(),
236 rhsType.getWidth() + (signedDivision && !rhsType.isSigned() ? 1 : 0));
237
238 // Extend the operands
239 Value lhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[0],
240 extendSize, isLhsTypeSigned);
241 Value rhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[1],
242 extendSize, rhsType.isSigned());
243
244 Value divResult;
245 if (signedDivision)
246 divResult = comb::DivSOp::create(rewriter, loc, lhsValue, rhsValue, false)
247 ->getOpResult(0);
248 else
249 divResult = comb::DivUOp::create(rewriter, loc, lhsValue, rhsValue, false)
250 ->getOpResult(0);
251
252 // Carry over any attributes from the original div op.
253 auto *divOp = divResult.getDefiningOp();
254 rewriter.modifyOpInPlace(
255 divOp, [&]() { divOp->setDialectAttrs(op->getDialectAttrs()); });
256
257 // finally truncate back to the expected result size!
258 Value truncateResult = extractBits(rewriter, loc, divResult, /*startBit=*/0,
259 /*bitWidth=*/targetType.getWidth());
260 rewriter.replaceOp(op, truncateResult);
261
262 return success();
263 }
264};
265} // namespace
266
267namespace {
268struct CastOpLowering : public OpConversionPattern<CastOp> {
270
271 LogicalResult
272 matchAndRewrite(CastOp op, OpAdaptor adaptor,
273 ConversionPatternRewriter &rewriter) const override {
274 auto sourceType = cast<IntegerType>(op.getIn().getType());
275 auto sourceWidth = sourceType.getWidth();
276 bool isSourceTypeSigned = sourceType.isSigned();
277 auto targetWidth = cast<IntegerType>(op.getOut().getType()).getWidth();
278
279 Value replaceValue;
280 if (sourceWidth == targetWidth) {
281 // the width does not change, we are done here and can directly use the
282 // lowering input value
283 replaceValue = adaptor.getIn();
284 } else if (sourceWidth < targetWidth) {
285 // bit extensions needed, the type of extension required is determined by
286 // the source type only!
287 replaceValue = extendTypeWidth(rewriter, op.getLoc(), adaptor.getIn(),
288 targetWidth, isSourceTypeSigned);
289 } else {
290 // bit truncation needed
291 replaceValue = extractBits(rewriter, op.getLoc(), adaptor.getIn(),
292 /*startBit=*/0, /*bitWidth=*/targetWidth);
293 }
294 rewriter.replaceOp(op, replaceValue);
295
296 return success();
297 }
298};
299} // namespace
300
301namespace {
302
303// Utility lowering function that maps a hwarith::ICmpPredicate predicate and
304// the information whether the comparison contains signed values to the
305// corresponding comb::ICmpPredicate.
306static comb::ICmpPredicate lowerPredicate(ICmpPredicate pred, bool isSigned) {
307 switch (pred) {
308 case ICmpPredicate::eq:
309 return comb::ICmpPredicate::eq;
310 case ICmpPredicate::ne:
311 return comb::ICmpPredicate::ne;
312 case ICmpPredicate::lt:
313 return isSigned ? comb::ICmpPredicate::slt : comb::ICmpPredicate::ult;
314 case ICmpPredicate::ge:
315 return isSigned ? comb::ICmpPredicate::sge : comb::ICmpPredicate::uge;
316 case ICmpPredicate::le:
317 return isSigned ? comb::ICmpPredicate::sle : comb::ICmpPredicate::ule;
318 case ICmpPredicate::gt:
319 return isSigned ? comb::ICmpPredicate::sgt : comb::ICmpPredicate::ugt;
320 }
321
322 llvm_unreachable(
323 "Missing hwarith::ICmpPredicate to comb::ICmpPredicate lowering");
324 return comb::ICmpPredicate::eq;
325}
326
327struct ICmpOpLowering : public OpConversionPattern<ICmpOp> {
329
330 LogicalResult
331 matchAndRewrite(ICmpOp op, OpAdaptor adaptor,
332 ConversionPatternRewriter &rewriter) const override {
333 auto lhsType = cast<IntegerType>(op.getLhs().getType());
334 auto rhsType = cast<IntegerType>(op.getRhs().getType());
335 IntegerType::SignednessSemantics cmpSignedness;
336 const unsigned cmpWidth =
337 inferAddResultType(cmpSignedness, lhsType, rhsType) - 1;
338
339 ICmpPredicate pred = op.getPredicate();
340 comb::ICmpPredicate combPred = lowerPredicate(
341 pred, cmpSignedness == IntegerType::SignednessSemantics::Signed);
342
343 const auto loc = op.getLoc();
344 Value lhsValue = extendTypeWidth(rewriter, loc, adaptor.getLhs(), cmpWidth,
345 lhsType.isSigned());
346 Value rhsValue = extendTypeWidth(rewriter, loc, adaptor.getRhs(), cmpWidth,
347 rhsType.isSigned());
348
349 auto newOp = comb::ICmpOp::create(rewriter, op->getLoc(), combPred,
350 lhsValue, rhsValue, false);
351 rewriter.modifyOpInPlace(
352 newOp, [&]() { newOp->setDialectAttrs(op->getDialectAttrs()); });
353 rewriter.replaceOp(op, newOp);
354
355 return success();
356 }
357};
358
359template <class BinOp, class ReplaceOp>
360struct BinaryOpLowering : public OpConversionPattern<BinOp> {
362 using OpAdaptor = typename OpConversionPattern<BinOp>::OpAdaptor;
363
364 LogicalResult
365 matchAndRewrite(BinOp op, OpAdaptor adaptor,
366 ConversionPatternRewriter &rewriter) const override {
367 auto loc = op.getLoc();
368 auto isLhsTypeSigned =
369 cast<IntegerType>(op.getOperand(0).getType()).isSigned();
370 auto isRhsTypeSigned =
371 cast<IntegerType>(op.getOperand(1).getType()).isSigned();
372 auto targetWidth = cast<IntegerType>(op.getResult().getType()).getWidth();
373
374 Value lhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[0],
375 targetWidth, isLhsTypeSigned);
376 Value rhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[1],
377 targetWidth, isRhsTypeSigned);
378 auto newOp =
379 ReplaceOp::create(rewriter, op.getLoc(), lhsValue, rhsValue, false);
380 rewriter.modifyOpInPlace(
381 newOp, [&]() { newOp->setDialectAttrs(op->getDialectAttrs()); });
382 rewriter.replaceOp(op, newOp);
383
384 return success();
385 }
386};
387
388} // namespace
389
391 auto it = conversionCache.find(type);
392 if (it != conversionCache.end())
393 return it->second.type;
394
395 auto convertedType =
396 llvm::TypeSwitch<Type, Type>(type)
397 .Case<IntegerType>([](auto type) {
398 if (type.isSignless())
399 return type;
400 return IntegerType::get(type.getContext(), type.getWidth());
401 })
402 .Case<hw::ArrayType>([this](auto type) {
403 return hw::ArrayType::get(removeSignedness(type.getElementType()),
404 type.getNumElements());
405 })
406 .Case<hw::UnpackedArrayType>([this](auto type) {
407 return hw::UnpackedArrayType::get(
408 removeSignedness(type.getElementType()), type.getNumElements());
409 })
410 .Case<hw::StructType>([this](auto type) {
411 // Recursively convert each element.
412 llvm::SmallVector<hw::StructType::FieldInfo> convertedElements;
413 for (auto element : type.getElements()) {
414 convertedElements.push_back(
415 {element.name, removeSignedness(element.type)});
416 }
417 return hw::StructType::get(type.getContext(), convertedElements);
418 })
419 .Case<hw::UnionType>([this](auto type) {
420 llvm::SmallVector<hw::UnionType::FieldInfo> convertedElements;
421 for (auto element : type.getElements()) {
422 convertedElements.push_back({element.name,
423 removeSignedness(element.type),
424 element.offset});
425 }
426 return hw::UnionType::get(type.getContext(), convertedElements);
427 })
428 .Case<hw::InOutType>([this](auto type) {
429 return hw::InOutType::get(removeSignedness(type.getElementType()));
430 })
431 .Case<hw::TypeAliasType>([this](auto type) {
432 return hw::TypeAliasType::get(
433 type.getRef(), removeSignedness(type.getInnerType()));
434 })
435 .Default([](auto type) { return type; });
436
437 return convertedType;
438}
439
441 // Pass any type through the signedness remover.
442 addConversion([this](Type type) { return removeSignedness(type); });
443
444 addTargetMaterialization([&](mlir::OpBuilder &builder, mlir::Type resultType,
445 mlir::ValueRange inputs,
446 mlir::Location loc) -> mlir::Value {
447 if (inputs.size() != 1)
448 return Value();
449 return UnrealizedConversionCastOp::create(builder, loc, resultType,
450 inputs[0])
451 ->getResult(0);
452 });
453
454 addSourceMaterialization([&](mlir::OpBuilder &builder, mlir::Type resultType,
455 mlir::ValueRange inputs,
456 mlir::Location loc) -> mlir::Value {
457 if (inputs.size() != 1)
458 return Value();
459 return UnrealizedConversionCastOp::create(builder, loc, resultType,
460 inputs[0])
461 ->getResult(0);
462 });
463}
464
465//===----------------------------------------------------------------------===//
466// Pass driver
467//===----------------------------------------------------------------------===//
468
470 HWArithToHWTypeConverter &typeConverter, RewritePatternSet &patterns) {
471 patterns.add<ConstantOpLowering, AggregateConstantOpLowering, CastOpLowering,
472 ICmpOpLowering, BinaryOpLowering<AddOp, comb::AddOp>,
473 BinaryOpLowering<SubOp, comb::SubOp>,
474 BinaryOpLowering<MulOp, comb::MulOp>, DivOpLowering>(
475 typeConverter, patterns.getContext());
476}
477
478namespace {
479
480class HWArithToHWPass : public circt::impl::HWArithToHWBase<HWArithToHWPass> {
481public:
482 void runOnOperation() override {
483 ModuleOp module = getOperation();
484
485 ConversionTarget target(getContext());
486 target.markUnknownOpDynamicallyLegal(isLegalOp);
487 RewritePatternSet patterns(&getContext());
488 HWArithToHWTypeConverter typeConverter;
489 target.addIllegalDialect<HWArithDialect>();
490
491 // Add HWArith-specific conversion patterns.
493
494 // ALL other operations are converted via the TypeConversionPattern which
495 // will replace an operation to an identical operation with replaced
496 // result types and operands.
497 patterns.add<TypeConversionPattern>(typeConverter, patterns.getContext());
498
499 // Apply a full conversion - all operations must either be legal, be caught
500 // by one of the HWArith patterns or be converted by the
501 // TypeConversionPattern.
502 if (failed(applyFullConversion(module, target, std::move(patterns))))
503 return signalPassFailure();
504 }
505};
506} // namespace
507
508//===----------------------------------------------------------------------===//
509// Pass initialization
510//===----------------------------------------------------------------------===//
511
512std::unique_ptr<Pass> circt::createHWArithToHWPass() {
513 return std::make_unique<HWArithToHWPass>();
514}
static SmallVector< Value > extractBits(OpBuilder &builder, Value val)
static bool isLegalOp(Operation *op)
Returns true if the given op is considered as legal - i.e.
Definition DCToHW.cpp:844
static bool isSignednessAttr(Attribute attr)
static bool isLegalOp(Operation *op)
Returns true if the given op is considered as legal for HWArith conversion.
static void improveNamehint(Value oldValue, Operation *newOp, llvm::function_ref< std::string(StringRef)> namehintCallback)
static Value extractBits(OpBuilder &builder, Location loc, Value value, unsigned startBit, unsigned bitWidth)
static bool isSignednessType(Type type)
static Value extendTypeWidth(OpBuilder &builder, Location loc, Value value, unsigned targetWidth, bool signExtension)
A helper type converter class that automatically populates the relevant materializations and type con...
Definition HWArithToHW.h:32
mlir::Type removeSignedness(mlir::Type type)
llvm::DenseMap< mlir::Type, ConvertedType > conversionCache
Definition HWArithToHW.h:50
create(data_type, value)
Definition hw.py:433
unsigned inferAddResultType(IntegerType::SignednessSemantics &signedness, IntegerType lhs, IntegerType rhs)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void populateHWArithToHWConversionPatterns(HWArithToHWTypeConverter &typeConverter, RewritePatternSet &patterns)
Get the HWArith to HW conversion patterns.
std::unique_ptr< mlir::Pass > createHWArithToHWPass()
LogicalResult doTypeConversion(Operation *op, ValueRange operands, ConversionPatternRewriter &rewriter, const TypeConverter *typeConverter)
Generic pattern which replaces an operation by one of the same operation name, but with converted att...