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<sv::NetType>(
131 [](auto type) { return isSignednessType(type.getElementType()); })
132 .Case<sv::VarType>(
133 [](auto type) { return isSignednessType(type.getElementType()); })
134 .Case<hw::TypeAliasType>(
135 [](auto type) { return isSignednessType(type.getInnerType()); })
136 .Default([](auto type) { return false; });
137
138 return match;
139}
140
141static bool isSignednessAttr(Attribute attr) {
142 if (auto typeAttr = dyn_cast<TypeAttr>(attr))
143 return isSignednessType(typeAttr.getValue());
144 return false;
145}
146
147/// Returns true if the given `op` is considered as legal for HWArith
148/// conversion.
149static bool isLegalOp(Operation *op) {
150 if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
151 return llvm::none_of(funcOp.getArgumentTypes(), isSignednessType) &&
152 llvm::none_of(funcOp.getResultTypes(), isSignednessType) &&
153 llvm::none_of(funcOp.getFunctionBody().getArgumentTypes(),
155 }
156
157 if (auto modOp = dyn_cast<hw::HWModuleLike>(op)) {
158 return llvm::none_of(modOp.getPortTypes(), isSignednessType) &&
159 llvm::none_of(modOp.getModuleBody().getArgumentTypes(),
161 }
162
163 auto attrs = llvm::map_range(op->getAttrs(), [](const NamedAttribute &attr) {
164 return attr.getValue();
165 });
166
167 bool operandsOK = llvm::none_of(op->getOperandTypes(), isSignednessType);
168 bool resultsOK = llvm::none_of(op->getResultTypes(), isSignednessType);
169 bool attrsOK = llvm::none_of(attrs, isSignednessAttr);
170 return operandsOK && resultsOK && attrsOK;
171}
172
173//===----------------------------------------------------------------------===//
174// Conversion patterns
175//===----------------------------------------------------------------------===//
176
177namespace {
178struct ConstantOpLowering : public OpConversionPattern<ConstantOp> {
179 using OpConversionPattern<ConstantOp>::OpConversionPattern;
180
181 LogicalResult
182 matchAndRewrite(ConstantOp constOp, OpAdaptor adaptor,
183 ConversionPatternRewriter &rewriter) const override {
184 // The result may be a type alias, which is retained with a signless inner
185 // type.
186 Type resultType = getTypeConverter()->convertType(constOp.getType());
187 if (!resultType)
188 return failure();
189 auto valueAttr = rewriter.getIntegerAttr(
190 hw::type_cast<IntegerType>(resultType), constOp.getConstantValue());
191 rewriter.replaceOpWithNewOp<hw::ConstantOp>(constOp, resultType, valueAttr);
192 return success();
193 }
194};
195
196struct AggregateConstantOpLowering
197 : public OpConversionPattern<hw::AggregateConstantOp> {
198 using OpConversionPattern<hw::AggregateConstantOp>::OpConversionPattern;
199
200 LogicalResult
201 matchAndRewrite(hw::AggregateConstantOp op, OpAdaptor adaptor,
202 ConversionPatternRewriter &rewriter) const override {
203 auto fields = op.getFieldsAttr().replace([&](IntegerType type) {
204 return getTypeConverter()->convertType(type);
205 });
206 if (!fields)
207 return rewriter.notifyMatchFailure(op,
208 "attribute type conversion failed");
209 rewriter.modifyOpInPlace(
210 op, [&] { op.setFieldsAttr(cast<ArrayAttr>(fields)); });
211 return doTypeConversion(op, adaptor.getOperands(), rewriter,
212 getTypeConverter());
213 }
214};
215
216struct DivOpLowering : public OpConversionPattern<DivOp> {
217 using OpConversionPattern<DivOp>::OpConversionPattern;
218
219 LogicalResult
220 matchAndRewrite(DivOp op, OpAdaptor adaptor,
221 ConversionPatternRewriter &rewriter) const override {
222 auto loc = op.getLoc();
223 auto isLhsTypeSigned =
224 cast<IntegerType>(op.getOperand(0).getType()).isSigned();
225 auto rhsType = cast<IntegerType>(op.getOperand(1).getType());
226 auto targetType = cast<IntegerType>(op.getResult().getType());
227
228 // comb.div* needs identical bitwidths for its operands and its result.
229 // Hence, we need to calculate the minimal bitwidth that can be used to
230 // represent the result as well as the operands without precision or sign
231 // loss. The target size only depends on LHS and already handles the edge
232 // cases where the bitwidth needs to be increased by 1. Thus, the targetType
233 // is good enough for both the result as well as LHS.
234 // The bitwidth for RHS is bit tricky. If the RHS is unsigned and we are
235 // about to perform a signed division, then we need one additional bit to
236 // avoid misinterpretation of RHS as a signed value!
237 bool signedDivision = targetType.isSigned();
238 unsigned extendSize = std::max(
239 targetType.getWidth(),
240 rhsType.getWidth() + (signedDivision && !rhsType.isSigned() ? 1 : 0));
241
242 // Extend the operands
243 Value lhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[0],
244 extendSize, isLhsTypeSigned);
245 Value rhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[1],
246 extendSize, rhsType.isSigned());
247
248 Value divResult;
249 if (signedDivision)
250 divResult = comb::DivSOp::create(rewriter, loc, lhsValue, rhsValue, false)
251 ->getOpResult(0);
252 else
253 divResult = comb::DivUOp::create(rewriter, loc, lhsValue, rhsValue, false)
254 ->getOpResult(0);
255
256 // Carry over any attributes from the original div op.
257 auto *divOp = divResult.getDefiningOp();
258 rewriter.modifyOpInPlace(
259 divOp, [&]() { divOp->setDialectAttrs(op->getDialectAttrs()); });
260
261 // finally truncate back to the expected result size!
262 Value truncateResult = extractBits(rewriter, loc, divResult, /*startBit=*/0,
263 /*bitWidth=*/targetType.getWidth());
264 rewriter.replaceOp(op, truncateResult);
265
266 return success();
267 }
268};
269} // namespace
270
271namespace {
272struct CastOpLowering : public OpConversionPattern<CastOp> {
273 using OpConversionPattern<CastOp>::OpConversionPattern;
274
275 LogicalResult
276 matchAndRewrite(CastOp op, OpAdaptor adaptor,
277 ConversionPatternRewriter &rewriter) const override {
278 auto sourceType = cast<IntegerType>(op.getIn().getType());
279 auto sourceWidth = sourceType.getWidth();
280 bool isSourceTypeSigned = sourceType.isSigned();
281 auto targetWidth = cast<IntegerType>(op.getOut().getType()).getWidth();
282
283 Value replaceValue;
284 if (sourceWidth == targetWidth) {
285 // the width does not change, we are done here and can directly use the
286 // lowering input value
287 replaceValue = adaptor.getIn();
288 } else if (sourceWidth < targetWidth) {
289 // bit extensions needed, the type of extension required is determined by
290 // the source type only!
291 replaceValue = extendTypeWidth(rewriter, op.getLoc(), adaptor.getIn(),
292 targetWidth, isSourceTypeSigned);
293 } else {
294 // bit truncation needed
295 replaceValue = extractBits(rewriter, op.getLoc(), adaptor.getIn(),
296 /*startBit=*/0, /*bitWidth=*/targetWidth);
297 }
298 rewriter.replaceOp(op, replaceValue);
299
300 return success();
301 }
302};
303} // namespace
304
305namespace {
306
307// Utility lowering function that maps a hwarith::ICmpPredicate predicate and
308// the information whether the comparison contains signed values to the
309// corresponding comb::ICmpPredicate.
310static comb::ICmpPredicate lowerPredicate(ICmpPredicate pred, bool isSigned) {
311 switch (pred) {
312 case ICmpPredicate::eq:
313 return comb::ICmpPredicate::eq;
314 case ICmpPredicate::ne:
315 return comb::ICmpPredicate::ne;
316 case ICmpPredicate::lt:
317 return isSigned ? comb::ICmpPredicate::slt : comb::ICmpPredicate::ult;
318 case ICmpPredicate::ge:
319 return isSigned ? comb::ICmpPredicate::sge : comb::ICmpPredicate::uge;
320 case ICmpPredicate::le:
321 return isSigned ? comb::ICmpPredicate::sle : comb::ICmpPredicate::ule;
322 case ICmpPredicate::gt:
323 return isSigned ? comb::ICmpPredicate::sgt : comb::ICmpPredicate::ugt;
324 }
325
326 llvm_unreachable(
327 "Missing hwarith::ICmpPredicate to comb::ICmpPredicate lowering");
328 return comb::ICmpPredicate::eq;
329}
330
331struct ICmpOpLowering : public OpConversionPattern<ICmpOp> {
332 using OpConversionPattern<ICmpOp>::OpConversionPattern;
333
334 LogicalResult
335 matchAndRewrite(ICmpOp op, OpAdaptor adaptor,
336 ConversionPatternRewriter &rewriter) const override {
337 auto lhsType = cast<IntegerType>(op.getLhs().getType());
338 auto rhsType = cast<IntegerType>(op.getRhs().getType());
339 IntegerType::SignednessSemantics cmpSignedness;
340 const unsigned cmpWidth =
341 inferAddResultType(cmpSignedness, lhsType, rhsType) - 1;
342
343 ICmpPredicate pred = op.getPredicate();
344 comb::ICmpPredicate combPred = lowerPredicate(
345 pred, cmpSignedness == IntegerType::SignednessSemantics::Signed);
346
347 const auto loc = op.getLoc();
348 Value lhsValue = extendTypeWidth(rewriter, loc, adaptor.getLhs(), cmpWidth,
349 lhsType.isSigned());
350 Value rhsValue = extendTypeWidth(rewriter, loc, adaptor.getRhs(), cmpWidth,
351 rhsType.isSigned());
352
353 auto newOp = comb::ICmpOp::create(rewriter, op->getLoc(), combPred,
354 lhsValue, rhsValue, false);
355 rewriter.modifyOpInPlace(
356 newOp, [&]() { newOp->setDialectAttrs(op->getDialectAttrs()); });
357 rewriter.replaceOp(op, newOp);
358
359 return success();
360 }
361};
362
363template <class BinOp, class ReplaceOp>
364struct BinaryOpLowering : public OpConversionPattern<BinOp> {
365 using OpConversionPattern<BinOp>::OpConversionPattern;
366 using OpAdaptor = typename OpConversionPattern<BinOp>::OpAdaptor;
367
368 LogicalResult
369 matchAndRewrite(BinOp op, OpAdaptor adaptor,
370 ConversionPatternRewriter &rewriter) const override {
371 auto loc = op.getLoc();
372 auto isLhsTypeSigned =
373 cast<IntegerType>(op.getOperand(0).getType()).isSigned();
374 auto isRhsTypeSigned =
375 cast<IntegerType>(op.getOperand(1).getType()).isSigned();
376 auto targetWidth = cast<IntegerType>(op.getResult().getType()).getWidth();
377
378 Value lhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[0],
379 targetWidth, isLhsTypeSigned);
380 Value rhsValue = extendTypeWidth(rewriter, loc, adaptor.getInputs()[1],
381 targetWidth, isRhsTypeSigned);
382 auto newOp =
383 ReplaceOp::create(rewriter, op.getLoc(), lhsValue, rhsValue, false);
384 rewriter.modifyOpInPlace(
385 newOp, [&]() { newOp->setDialectAttrs(op->getDialectAttrs()); });
386 rewriter.replaceOp(op, newOp);
387
388 return success();
389 }
390};
391
392} // namespace
393
395 return signednessReplacer.replace(type);
396}
397
399 signednessReplacer.addReplacement(
400 [](IntegerType type) -> std::optional<Type> {
401 if (type.isSignless())
402 return std::nullopt;
403 return IntegerType::get(type.getContext(), type.getWidth());
404 });
405
406 // Pass any type through the signedness remover.
407 addConversion([this](Type type) { return removeSignedness(type); });
408
409 addTargetMaterialization([&](mlir::OpBuilder &builder, mlir::Type resultType,
410 mlir::ValueRange inputs,
411 mlir::Location loc) -> mlir::Value {
412 if (inputs.size() != 1)
413 return Value();
414 return UnrealizedConversionCastOp::create(builder, loc, resultType,
415 inputs[0])
416 ->getResult(0);
417 });
418
419 addSourceMaterialization([&](mlir::OpBuilder &builder, mlir::Type resultType,
420 mlir::ValueRange inputs,
421 mlir::Location loc) -> mlir::Value {
422 if (inputs.size() != 1)
423 return Value();
424 return UnrealizedConversionCastOp::create(builder, loc, resultType,
425 inputs[0])
426 ->getResult(0);
427 });
428}
429
430//===----------------------------------------------------------------------===//
431// Pass driver
432//===----------------------------------------------------------------------===//
433
435 HWArithToHWTypeConverter &typeConverter, RewritePatternSet &patterns) {
436 patterns.add<ConstantOpLowering, AggregateConstantOpLowering, CastOpLowering,
437 ICmpOpLowering, BinaryOpLowering<AddOp, comb::AddOp>,
438 BinaryOpLowering<SubOp, comb::SubOp>,
439 BinaryOpLowering<MulOp, comb::MulOp>, DivOpLowering>(
440 typeConverter, patterns.getContext());
441}
442
443namespace {
444
445class HWArithToHWPass : public circt::impl::HWArithToHWBase<HWArithToHWPass> {
446public:
447 void runOnOperation() override {
448 ModuleOp module = getOperation();
449
450 ConversionTarget target(getContext());
451 target.markUnknownOpDynamicallyLegal(isLegalOp);
452 RewritePatternSet patterns(&getContext());
453 HWArithToHWTypeConverter typeConverter;
454 target.addIllegalDialect<HWArithDialect>();
455
456 // Add HWArith-specific conversion patterns.
458
459 // ALL other operations are converted via the TypeConversionPattern which
460 // will replace an operation to an identical operation with replaced
461 // result types and operands.
462 patterns.add<TypeConversionPattern>(typeConverter, patterns.getContext());
463
464 // Apply a full conversion - all operations must either be legal, be caught
465 // by one of the HWArith patterns or be converted by the
466 // TypeConversionPattern.
467 if (failed(applyFullConversion(module, target, std::move(patterns))))
468 return signalPassFailure();
469 }
470};
471} // namespace
472
473//===----------------------------------------------------------------------===//
474// Pass initialization
475//===----------------------------------------------------------------------===//
476
477std::unique_ptr<Pass> circt::createHWArithToHWPass() {
478 return std::make_unique<HWArithToHWPass>();
479}
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)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
NamedAttrList attrs
A helper type converter class that automatically populates the relevant materializations and type con...
Definition HWArithToHW.h:33
mlir::AttrTypeReplacer signednessReplacer
Definition HWArithToHW.h:46
mlir::Type removeSignedness(mlir::Type type)
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...