CIRCT 24.0.0git
Loading...
Searching...
No Matches
CombToSynth.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// This is the main Comb to Synth Conversion Pass Implementation.
10//
11// High-level Comb Operations
12// |
13// v
14// +-------------------+
15// | and, or, xor, mux |
16// +---------+---------+
17// |
18// +-----+
19// | AIG |
20// +-----+
21//
22//===----------------------------------------------------------------------===//
23
30#include "circt/Support/LLVM.h"
32#include "mlir/Pass/Pass.h"
33#include "mlir/Transforms/DialectConversion.h"
34#include "llvm/ADT/APInt.h"
35#include "llvm/ADT/PointerUnion.h"
36#include "llvm/ADT/STLExtras.h"
37#include "llvm/Support/Debug.h"
38#include "llvm/Support/DivisionByConstantInfo.h"
39#include <array>
40#include <utility>
41
42#define DEBUG_TYPE "comb-to-synth"
43
44namespace circt {
45#define GEN_PASS_DEF_CONVERTCOMBTOSYNTH
46#include "circt/Conversion/Passes.h.inc"
47} // namespace circt
48
49using namespace circt;
50using namespace comb;
51
52//===----------------------------------------------------------------------===//
53// Utility Functions
54//===----------------------------------------------------------------------===//
55
56// A wrapper for comb::extractBits that returns a SmallVector<Value>.
57static SmallVector<Value> extractBits(OpBuilder &builder, Value val) {
58 SmallVector<Value> bits;
59 comb::extractBits(builder, val, bits);
60 return bits;
61}
62
63// Construct a mux tree for shift operations. `isLeftShift` controls the
64// direction of the shift operation and is used to determine order of the
65// padding and extracted bits. Callbacks `getPadding` and `getExtract` are used
66// to get the padding and extracted bits for each shift amount. `getPadding`
67// could return a nullptr as i0 value but except for that, these callbacks must
68// return a valid value for each shift amount in the range [0, maxShiftAmount].
69// The value for `maxShiftAmount` is used as the out-of-bounds value.
70template <bool isLeftShift>
71static Value createShiftLogic(ConversionPatternRewriter &rewriter, Location loc,
72 Value shiftAmount, int64_t maxShiftAmount,
73 llvm::function_ref<Value(int64_t)> getPadding,
74 llvm::function_ref<Value(int64_t)> getExtract) {
75 // Extract individual bits from shift amount
76 auto bits = extractBits(rewriter, shiftAmount);
77
78 // Create nodes for each possible shift amount
79 SmallVector<Value> nodes;
80 nodes.reserve(maxShiftAmount);
81 for (int64_t i = 0; i < maxShiftAmount; ++i) {
82 Value extract = getExtract(i);
83 Value padding = getPadding(i);
84
85 if (!padding) {
86 nodes.push_back(extract);
87 continue;
88 }
89
90 // Concatenate extracted bits with padding
91 if (isLeftShift)
92 nodes.push_back(
93 rewriter.createOrFold<comb::ConcatOp>(loc, extract, padding));
94 else
95 nodes.push_back(
96 rewriter.createOrFold<comb::ConcatOp>(loc, padding, extract));
97 }
98
99 // Create out-of-bounds value
100 auto outOfBoundsValue = getPadding(maxShiftAmount);
101 assert(outOfBoundsValue && "outOfBoundsValue must be valid");
102
103 // Construct mux tree for shift operation
104 auto result =
105 comb::constructMuxTree(rewriter, loc, bits, nodes, outOfBoundsValue);
106
107 // Add bounds checking
108 auto inBound = rewriter.createOrFold<comb::ICmpOp>(
109 loc, ICmpPredicate::ult, shiftAmount,
110 hw::ConstantOp::create(rewriter, loc, shiftAmount.getType(),
111 maxShiftAmount));
112
113 return rewriter.createOrFold<comb::MuxOp>(loc, inBound, result,
114 outOfBoundsValue);
115}
116
117// Return a majority function implemented with Comb operations. `carry` has
118// slightly smaller depth than the other inputs.
119static Value createMajorityFunction(OpBuilder &rewriter, Location loc, Value a,
120 Value b, Value carry) {
121 // maj(a, b, c) = (c & (a ^ b)) | (a & b)
122 auto aXnorB = comb::XorOp::create(rewriter, loc, ValueRange{a, b}, true);
123 auto andOp =
124 comb::AndOp::create(rewriter, loc, ValueRange{carry, aXnorB}, true);
125 auto aAndB = comb::AndOp::create(rewriter, loc, ValueRange{a, b}, true);
126 return comb::OrOp::create(rewriter, loc, ValueRange{andOp, aAndB}, true);
127}
128
129static Value extractMSB(OpBuilder &builder, Value val) {
130 return builder.createOrFold<comb::ExtractOp>(
131 val.getLoc(), val, val.getType().getIntOrFloatBitWidth() - 1, 1);
132}
133
134static Value extractOtherThanMSB(OpBuilder &builder, Value val) {
135 return builder.createOrFold<comb::ExtractOp>(
136 val.getLoc(), val, 0, val.getType().getIntOrFloatBitWidth() - 1);
137}
138
139namespace {
140// A union of Value and IntegerAttr to cleanly handle constant values.
141using ConstantOrValue = llvm::PointerUnion<Value, mlir::IntegerAttr>;
142} // namespace
143
144// Return the number of unknown bits and populate the concatenated values.
146 Value value, llvm::SmallVectorImpl<ConstantOrValue> &values) {
147 // Constant or zero width value are all known.
148 if (value.getType().isInteger(0))
149 return 0;
150
151 // Recursively count unknown bits for concat.
152 if (auto concat = value.getDefiningOp<comb::ConcatOp>()) {
153 int64_t totalUnknownBits = 0;
154 for (auto concatInput : llvm::reverse(concat.getInputs())) {
155 auto unknownBits =
156 getNumUnknownBitsAndPopulateValues(concatInput, values);
157 if (unknownBits < 0)
158 return unknownBits;
159 totalUnknownBits += unknownBits;
160 }
161 return totalUnknownBits;
162 }
163
164 // Constant value is known.
165 if (auto constant = value.getDefiningOp<hw::ConstantOp>()) {
166 values.push_back(constant.getValueAttr());
167 return 0;
168 }
169
170 // Consider other operations as unknown bits.
171 // TODO: We can handle replicate, extract, etc.
172 values.push_back(value);
173 return hw::getBitWidth(value.getType());
174}
175
176// Return a value that substitutes the unknown bits with the mask.
177static APInt
179 llvm::SmallVectorImpl<ConstantOrValue> &constantOrValues,
180 uint32_t mask) {
181 uint32_t bitPos = 0, unknownPos = 0;
182 APInt result(width, 0);
183 for (auto constantOrValue : constantOrValues) {
184 int64_t elemWidth;
185 if (auto constant = dyn_cast<IntegerAttr>(constantOrValue)) {
186 elemWidth = constant.getValue().getBitWidth();
187 result.insertBits(constant.getValue(), bitPos);
188 } else {
189 elemWidth = hw::getBitWidth(cast<Value>(constantOrValue).getType());
190 assert(elemWidth >= 0 && "unknown bit width");
191 assert(elemWidth + unknownPos < 32 && "unknown bit width too large");
192 // Create a mask for the unknown bits.
193 uint32_t usedBits = (mask >> unknownPos) & ((1 << elemWidth) - 1);
194 result.insertBits(APInt(elemWidth, usedBits), bitPos);
195 unknownPos += elemWidth;
196 }
197 bitPos += elemWidth;
198 }
199
200 return result;
201}
202
203// Emulate a binary operation with unknown bits using a table lookup.
204// This function enumerates all possible combinations of unknown bits and
205// emulates the operation for each combination.
206static LogicalResult emulateBinaryOpForUnknownBits(
207 ConversionPatternRewriter &rewriter, int64_t maxEmulationUnknownBits,
208 Operation *op,
209 llvm::function_ref<APInt(const APInt &, const APInt &)> emulate) {
210 SmallVector<ConstantOrValue> lhsValues, rhsValues;
211
212 assert(op->getNumResults() == 1 && op->getNumOperands() == 2 &&
213 "op must be a single result binary operation");
214
215 auto lhs = op->getOperand(0);
216 auto rhs = op->getOperand(1);
217 auto width = op->getResult(0).getType().getIntOrFloatBitWidth();
218 auto loc = op->getLoc();
219 auto numLhsUnknownBits = getNumUnknownBitsAndPopulateValues(lhs, lhsValues);
220 auto numRhsUnknownBits = getNumUnknownBitsAndPopulateValues(rhs, rhsValues);
221
222 // If unknown bit width is detected, abort the lowering.
223 if (numLhsUnknownBits < 0 || numRhsUnknownBits < 0)
224 return failure();
225
226 int64_t totalUnknownBits = numLhsUnknownBits + numRhsUnknownBits;
227 if (totalUnknownBits > maxEmulationUnknownBits)
228 return failure();
229
230 SmallVector<Value> emulatedResults;
231 emulatedResults.reserve(1 << totalUnknownBits);
232
233 // Emulate all possible cases.
234 DenseMap<IntegerAttr, hw::ConstantOp> constantPool;
235 auto getConstant = [&](const APInt &value) -> hw::ConstantOp {
236 auto attr = rewriter.getIntegerAttr(rewriter.getIntegerType(width), value);
237 auto it = constantPool.find(attr);
238 if (it != constantPool.end())
239 return it->second;
240 auto constant = hw::ConstantOp::create(rewriter, loc, value);
241 constantPool[attr] = constant;
242 return constant;
243 };
244
245 for (uint32_t lhsMask = 0, lhsMaskEnd = 1 << numLhsUnknownBits;
246 lhsMask < lhsMaskEnd; ++lhsMask) {
247 APInt lhsValue = substitueMaskToValues(width, lhsValues, lhsMask);
248 for (uint32_t rhsMask = 0, rhsMaskEnd = 1 << numRhsUnknownBits;
249 rhsMask < rhsMaskEnd; ++rhsMask) {
250 APInt rhsValue = substitueMaskToValues(width, rhsValues, rhsMask);
251 // Emulate.
252 emulatedResults.push_back(getConstant(emulate(lhsValue, rhsValue)));
253 }
254 }
255
256 // Create selectors for mux tree.
257 SmallVector<Value> selectors;
258 selectors.reserve(totalUnknownBits);
259 for (auto &concatedValues : {rhsValues, lhsValues})
260 for (auto valueOrConstant : concatedValues) {
261 auto value = dyn_cast<Value>(valueOrConstant);
262 if (!value)
263 continue;
264 extractBits(rewriter, value, selectors);
265 }
266
267 assert(totalUnknownBits == static_cast<int64_t>(selectors.size()) &&
268 "number of selectors must match");
269 auto muxed = constructMuxTree(rewriter, loc, selectors, emulatedResults,
270 getConstant(APInt::getZero(width)));
271
272 replaceOpAndCopyNamehint(rewriter, op, muxed);
273 return success();
274}
275
276static Value createLShrByConstant(OpBuilder &builder, Location loc, Value value,
277 unsigned amount) {
278 if (amount == 0)
279 return value;
280 return builder.createOrFold<comb::ShrUOp>(
281 loc, value,
283 builder, loc,
284 APInt(value.getType().getIntOrFloatBitWidth(), amount)));
285}
286
287static Value createAShrByConstant(OpBuilder &builder, Location loc, Value value,
288 unsigned amount) {
289 if (amount == 0)
290 return value;
291 return builder.createOrFold<comb::ShrSOp>(
292 loc, value,
294 builder, loc,
295 APInt(value.getType().getIntOrFloatBitWidth(), amount)));
296}
297
298template <bool isSigned>
299static Value createMulHigh(OpBuilder &builder, Location loc, Value lhs,
300 const APInt &rhs) {
301 unsigned width = lhs.getType().getIntOrFloatBitWidth();
302 auto destTy = builder.getIntegerType(width << 1);
303 // Compute the high half of a double-width product. For signed division,
304 // sign-extend both operands so this acts like a signed multiply-high.
305 Value wideLhs = isSigned ? comb::createOrFoldSExt(builder, loc, lhs, destTy)
306 : comb::createZExt(builder, loc, lhs, width << 1);
307 Value wideRhs = hw::ConstantOp::create(
308 builder, loc, isSigned ? rhs.sext(width << 1) : rhs.zext(width << 1));
309 Value product = builder.createOrFold<comb::MulOp>(
310 loc, ValueRange{wideLhs, wideRhs}, /*twoState=*/true);
311 return builder.createOrFold<comb::ExtractOp>(loc, product, width, width);
312}
313
314static Value lowerUnsignedDivByConstant(OpBuilder &builder, Location loc,
315 Value lhs, const APInt &divisor) {
316 auto info = llvm::UnsignedDivisionByConstantInfo::get(divisor);
317 Value q = createLShrByConstant(builder, loc, lhs, info.PreShift);
318 q = createMulHigh<false>(builder, loc, q, info.Magic);
319 if (info.IsAdd) {
320 Value diff = builder.createOrFold<comb::SubOp>(loc, lhs, q);
321 diff = createLShrByConstant(builder, loc, diff, 1);
322 q = builder.createOrFold<comb::AddOp>(loc, q, diff);
323 }
324 return createLShrByConstant(builder, loc, q, info.PostShift);
325}
326
327static Value lowerSignedDivByConstant(OpBuilder &builder, Location loc,
328 Value lhs, const APInt &divisor) {
329 unsigned width = lhs.getType().getIntOrFloatBitWidth();
330 auto info = llvm::SignedDivisionByConstantInfo::get(divisor);
331 Value q = createMulHigh<true>(builder, loc, lhs, info.Magic);
332 // Depending on the magic constant the signed magic may need to
333 // add or subtract the dividend before the final shift.
334 if (divisor.isStrictlyPositive() && info.Magic.isNegative())
335 q = builder.createOrFold<comb::AddOp>(loc, q, lhs);
336 else if (divisor.isNegative() && info.Magic.isStrictlyPositive())
337 q = builder.createOrFold<comb::SubOp>(loc, q, lhs);
338 q = createAShrByConstant(builder, loc, q, info.ShiftAmount);
339 // Signed division rounds to zero. Add one back for negative tentative
340 // quotients after the arithmetic shift.
341 Value signBit = builder.createOrFold<comb::ExtractOp>(loc, q, width - 1, 1);
342 Value signPadded = comb::createZExt(builder, loc, signBit, width);
343 return builder.createOrFold<comb::AddOp>(loc, q, signPadded);
344}
345
346/// Lower an unsigned division with a non-constant divisor to a combinational
347/// restoring array divider. Returns the quotient and the remainder.
348///
349/// The divider processes the dividend from MSB to LSB. In every step the
350/// partial remainder is shifted left by one, the next dividend bit is shifted
351/// in, and the divisor is trially subtracted. If the subtraction does not
352/// borrow, the quotient bit is one and the difference becomes the new partial
353/// remainder; otherwise the quotient bit is zero and the partial remainder is
354/// restored. The partial remainder is kept `width + 1` bits wide so the borrow
355/// can be read off the MSB of the difference.
356///
357/// Division by zero yields an all-ones quotient and the dividend as remainder.
358static std::pair<Value, Value>
359lowerUnsignedDivRem(OpBuilder &builder, Location loc, Value lhs, Value rhs) {
360 unsigned width = lhs.getType().getIntOrFloatBitWidth();
361 unsigned remWidth = width + 1;
362 Value wideRhs = comb::createZExt(builder, loc, rhs, remWidth);
363
364 SmallVector<Value> lhsBits = extractBits(builder, lhs);
365
366 // Partial remainder, `width + 1` bits wide.
367 Value remainder =
368 hw::ConstantOp::create(builder, loc, APInt::getZero(remWidth));
369 // Quotient bits, produced MSB first (the order `comb.concat` expects).
370 SmallVector<Value> quotientBits;
371 quotientBits.reserve(width);
372 for (Value lhsBit : llvm::reverse(lhsBits)) {
373 // remainder = (remainder << 1) | lhsBit
374 Value shifted =
375 builder.createOrFold<comb::ExtractOp>(loc, remainder, 0, remWidth - 1);
376 remainder = builder.createOrFold<comb::ConcatOp>(loc, shifted, lhsBit);
377 // Trial subtraction. The MSB of the difference is set iff it borrowed.
378 Value diff = builder.createOrFold<comb::SubOp>(loc, remainder, wideRhs);
379 Value borrow =
380 builder.createOrFold<comb::ExtractOp>(loc, diff, remWidth - 1, 1);
381 quotientBits.push_back(comb::createOrFoldNot(builder, loc, borrow));
382 remainder = builder.createOrFold<comb::MuxOp>(loc, borrow, remainder, diff);
383 }
384
385 Value quotient = builder.createOrFold<comb::ConcatOp>(loc, quotientBits);
386 Value finalRemainder =
387 builder.createOrFold<comb::ExtractOp>(loc, remainder, 0, width);
388 return {quotient, finalRemainder};
389}
390
391/// Lower a signed division with a non-constant divisor to a restoring array
392/// divider operating on the magnitudes of the operands. Returns the quotient
393/// and the remainder, rounded towards zero like `comb.divs` / `comb.mods`: the
394/// quotient is negative iff the signs of the operands differ, and the
395/// remainder takes the sign of the dividend.
396static std::pair<Value, Value>
397lowerSignedDivRem(OpBuilder &builder, Location loc, Value lhs, Value rhs) {
398 unsigned width = lhs.getType().getIntOrFloatBitWidth();
399 Value zero = hw::ConstantOp::create(builder, loc, APInt::getZero(width));
400 auto negateIf = [&](Value value, Value shouldNegate) {
401 Value negated = builder.createOrFold<comb::SubOp>(loc, zero, value);
402 return builder.createOrFold<comb::MuxOp>(loc, shouldNegate, negated, value);
403 };
404
405 Value lhsNegative =
406 builder.createOrFold<comb::ExtractOp>(loc, lhs, width - 1, 1);
407 Value rhsNegative =
408 builder.createOrFold<comb::ExtractOp>(loc, rhs, width - 1, 1);
409 // Materialize the magnitudes in a fixed order; the order in which function
410 // arguments are evaluated is unspecified.
411 Value lhsMagnitude = negateIf(lhs, lhsNegative);
412 Value rhsMagnitude = negateIf(rhs, rhsNegative);
413 auto [quotient, remainder] =
414 lowerUnsignedDivRem(builder, loc, lhsMagnitude, rhsMagnitude);
415
416 Value quotientNegative =
417 builder.createOrFold<comb::XorOp>(loc, lhsNegative, rhsNegative, true);
418 Value signedQuotient = negateIf(quotient, quotientNegative);
419 Value signedRemainder = negateIf(remainder, lhsNegative);
420 return {signedQuotient, signedRemainder};
421}
422
423//===----------------------------------------------------------------------===//
424// Conversion patterns
425//===----------------------------------------------------------------------===//
426
427namespace {
428
429/// Lower a comb::AndOp operation to synth::aig::AndInverterOp
430struct CombAndOpConversion : OpConversionPattern<AndOp> {
431 using OpConversionPattern<AndOp>::OpConversionPattern;
432
433 LogicalResult
434 matchAndRewrite(AndOp op, OpAdaptor adaptor,
435 ConversionPatternRewriter &rewriter) const override {
436 SmallVector<bool> nonInverts(adaptor.getInputs().size(), false);
438 rewriter, op, adaptor.getInputs(), nonInverts);
439 return success();
440 }
441};
442
443/// Lower a comb::OrOp operation to synth::aig::AndInverterOp with invert flags
444struct CombOrToAIGConversion : OpConversionPattern<OrOp> {
445 using OpConversionPattern<OrOp>::OpConversionPattern;
446
447 LogicalResult
448 matchAndRewrite(OrOp op, OpAdaptor adaptor,
449 ConversionPatternRewriter &rewriter) const override {
450 // Implement Or using And and invert flags: a | b = ~(~a & ~b)
451 SmallVector<bool> allInverts(adaptor.getInputs().size(), true);
452 auto andOp = synth::aig::AndInverterOp::create(
453 rewriter, op.getLoc(), adaptor.getInputs(), allInverts);
455 rewriter, op, andOp,
456 /*invert=*/true);
457 return success();
458 }
459};
460
461struct CombXorOpToSynthConversion : OpConversionPattern<XorOp> {
462 using OpConversionPattern<XorOp>::OpConversionPattern;
463
464 LogicalResult
465 matchAndRewrite(XorOp op, OpAdaptor adaptor,
466 ConversionPatternRewriter &rewriter) const override {
467 SmallVector<bool> inverted(adaptor.getInputs().size(), false);
469 rewriter, op, adaptor.getInputs(), inverted);
470 return success();
471 }
472};
473
474/// Lower a synth::XorOp operation to AIG operations
475struct SynthXorInverterOpConversion
476 : OpConversionPattern<synth::XorInverterOp> {
477 using OpConversionPattern<synth::XorInverterOp>::OpConversionPattern;
478
479 LogicalResult
480 matchAndRewrite(synth::XorInverterOp op, OpAdaptor adaptor,
481 ConversionPatternRewriter &rewriter) const override {
482 if (op.getNumOperands() != 2)
483 return failure();
484 // Xor using And with invert flags: a ^ b = (a | b) & (~a | ~b)
485
486 // (a | b) = ~(~a & ~b)
487 // (~a | ~b) = ~(a & b)
488 auto inputs = adaptor.getInputs();
489 auto allNotInverts = op.getInverted();
490 std::array<bool, 2> allInverts = {!allNotInverts[0], !allNotInverts[1]};
491
492 auto notAAndNotB = synth::aig::AndInverterOp::create(rewriter, op.getLoc(),
493 inputs, allInverts);
494 auto aAndB = synth::aig::AndInverterOp::create(rewriter, op.getLoc(),
495 inputs, allNotInverts);
496
498 rewriter, op, notAAndNotB, aAndB,
499 /*lhs_invert=*/true,
500 /*rhs_invert=*/true);
501 return success();
502 }
503};
504
505/// Lower a comb::MuxOp operation to synth::MuxInverterOps.
506struct CombMuxOpToSynthConversion : OpConversionPattern<MuxOp> {
507 using OpConversionPattern<MuxOp>::OpConversionPattern;
508
509 LogicalResult
510 matchAndRewrite(MuxOp op, OpAdaptor adaptor,
511 ConversionPatternRewriter &rewriter) const override {
512 Value cond = adaptor.getCond();
513 Value trueVal = adaptor.getTrueValue();
514 Value falseVal = adaptor.getFalseValue();
515
516 if (!op.getType().isInteger()) {
517 auto widthType = rewriter.getIntegerType(hw::getBitWidth(op.getType()));
518 trueVal =
519 hw::BitcastOp::create(rewriter, op.getLoc(), widthType, trueVal);
520 falseVal =
521 hw::BitcastOp::create(rewriter, op.getLoc(), widthType, falseVal);
522 }
523
524 if (!trueVal.getType().isInteger(1))
525 cond = comb::ReplicateOp::create(rewriter, op.getLoc(), trueVal.getType(),
526 cond);
527
528 Value result = synth::MuxInverterOp::create(rewriter, op.getLoc(), cond,
529 trueVal, falseVal);
530
531 if (result.getType() != op.getType())
532 result =
533 hw::BitcastOp::create(rewriter, op.getLoc(), op.getType(), result);
534
535 replaceOpAndCopyNamehint(rewriter, op, result);
536 return success();
537 }
538};
539
540/// Lower a synth::MuxInverterOp operation to AIG operations.
541struct SynthMuxInverterOpConversion
542 : OpConversionPattern<synth::MuxInverterOp> {
543 using OpConversionPattern<synth::MuxInverterOp>::OpConversionPattern;
544
545 LogicalResult
546 matchAndRewrite(synth::MuxInverterOp op, OpAdaptor adaptor,
547 ConversionPatternRewriter &rewriter) const override {
548 auto inputs = adaptor.getInputs();
549 auto inverted = op.getInverted();
550
551 auto lhs = synth::aig::AndInverterOp::create(
552 rewriter, op.getLoc(), inputs[0], inputs[1], inverted[0], inverted[1]);
553
554 auto rhs = synth::aig::AndInverterOp::create(
555 rewriter, op.getLoc(), inputs[0], inputs[2], !inverted[0], inverted[2]);
556
557 auto nand = synth::aig::AndInverterOp::create(rewriter, op.getLoc(), lhs,
558 rhs, true, true);
560 nand, true);
561 return success();
562 }
563};
564
565template <typename OpTy>
566struct CombLowerVariadicOp : OpConversionPattern<OpTy> {
567 using OpConversionPattern<OpTy>::OpConversionPattern;
568 using OpAdaptor = typename OpConversionPattern<OpTy>::OpAdaptor;
569 LogicalResult
570 matchAndRewrite(OpTy op, OpAdaptor adaptor,
571 ConversionPatternRewriter &rewriter) const override {
572 auto result = lowerFullyAssociativeOp(op, op.getOperands(), rewriter);
573 replaceOpAndCopyNamehint(rewriter, op, result);
574 return success();
575 }
576
577 static Value lowerFullyAssociativeOp(OpTy op, OperandRange operands,
578 ConversionPatternRewriter &rewriter) {
579 Value lhs, rhs;
580 switch (operands.size()) {
581 case 0:
582 llvm_unreachable("cannot be called with empty operand range");
583 break;
584 case 1:
585 return operands[0];
586 case 2:
587 lhs = operands[0];
588 rhs = operands[1];
589 return OpTy::create(rewriter, op.getLoc(), ValueRange{lhs, rhs}, true);
590 default:
591 auto firstHalf = operands.size() / 2;
592 lhs =
593 lowerFullyAssociativeOp(op, operands.take_front(firstHalf), rewriter);
594 rhs =
595 lowerFullyAssociativeOp(op, operands.drop_front(firstHalf), rewriter);
596 return OpTy::create(rewriter, op.getLoc(), ValueRange{lhs, rhs}, true);
597 }
598 }
599};
600
601//===----------------------------------------------------------------------===//
602// Adder Architecture Selection
603//===----------------------------------------------------------------------===//
604
605enum AdderArchitecture { RippleCarry, Sklanskey, KoggeStone, BrentKung };
606AdderArchitecture determineAdderArch(Operation *op, int64_t width) {
607 auto strAttr = op->getAttrOfType<StringAttr>("synth.test.arch");
608 if (strAttr) {
609 return llvm::StringSwitch<AdderArchitecture>(strAttr.getValue())
610 .Case("SKLANSKEY", Sklanskey)
611 .Case("KOGGE-STONE", KoggeStone)
612 .Case("BRENT-KUNG", BrentKung)
613 .Case("RIPPLE-CARRY", RippleCarry);
614 }
615 // Determine using width as a heuristic.
616 // TODO: Perform a more thorough analysis to motivate the choices or
617 // implement an adder synthesis algorithm to construct an optimal adder
618 // under the given timing constraints - see the work of Zimmermann
619
620 // For very small adders, overhead of a parallel prefix adder is likely not
621 // worth it.
622 if (width < 8)
623 return AdderArchitecture::RippleCarry;
624
625 // Sklanskey is a good compromise for high-performance, but has high fanout
626 // which may lead to wiring congestion for very large adders.
627 if (width <= 32)
628 return AdderArchitecture::Sklanskey;
629
630 // Kogge-Stone uses greater area than Sklanskey but has lower fanout thus
631 // may be preferable for larger adders.
632 return AdderArchitecture::KoggeStone;
633}
634
635//===----------------------------------------------------------------------===//
636// Parallel Prefix Tree
637//===----------------------------------------------------------------------===//
638// Implement the Kogge-Stone parallel prefix tree
639// Described in https://en.wikipedia.org/wiki/Kogge%E2%80%93Stone_adder
640// Slightly better delay than Brent-Kung, but more area.
641void lowerKoggeStonePrefixTree(OpBuilder &builder, Location loc,
642 SmallVector<Value> &pPrefix,
643 SmallVector<Value> &gPrefix) {
644
645 auto width = static_cast<int64_t>(pPrefix.size());
646 assert(width == static_cast<int64_t>(gPrefix.size()));
647 SmallVector<Value> pPrefixNew = pPrefix;
648 SmallVector<Value> gPrefixNew = gPrefix;
649
650 // Kogge-Stone parallel prefix computation
651 for (int64_t stride = 1; stride < width; stride *= 2) {
652
653 for (int64_t i = stride; i < width; ++i) {
654 int64_t j = i - stride;
655
656 // Group generate: g_i OR (p_i AND g_j)
657 Value andPG = comb::AndOp::create(builder, loc, pPrefix[i], gPrefix[j]);
658 gPrefixNew[i] = comb::OrOp::create(builder, loc, gPrefix[i], andPG);
659
660 // Group propagate: p_i AND p_j
661 pPrefixNew[i] = comb::AndOp::create(builder, loc, pPrefix[i], pPrefix[j]);
662 }
663
664 pPrefix = pPrefixNew;
665 gPrefix = gPrefixNew;
666 }
667
668 LLVM_DEBUG({
669 int64_t stage = 0;
670 for (int64_t stride = 1; stride < width; stride *= 2) {
671 llvm::dbgs()
672 << "--------------------------------------- Kogge-Stone Stage "
673 << stage << "\n";
674 for (int64_t i = stride; i < width; ++i) {
675 int64_t j = i - stride;
676 // Group generate: g_i OR (p_i AND g_j)
677 llvm::dbgs() << "G" << i << stage + 1 << " = G" << i << stage
678 << " OR (P" << i << stage << " AND G" << j << stage
679 << ")\n";
680
681 // Group propagate: p_i AND p_j
682 llvm::dbgs() << "P" << i << stage + 1 << " = P" << i << stage
683 << " AND P" << j << stage << "\n";
684 }
685 ++stage;
686 }
687 });
688}
689
690// Implement the Sklansky parallel prefix tree
691// High fan-out, low depth, low area
692void lowerSklanskeyPrefixTree(OpBuilder &builder, Location loc,
693 SmallVector<Value> &pPrefix,
694 SmallVector<Value> &gPrefix) {
695 auto width = static_cast<int64_t>(pPrefix.size());
696 assert(width == static_cast<int64_t>(gPrefix.size()));
697 SmallVector<Value> pPrefixNew = pPrefix;
698 SmallVector<Value> gPrefixNew = gPrefix;
699 for (int64_t stride = 1; stride < width; stride *= 2) {
700 for (int64_t i = stride; i < width; i += 2 * stride) {
701 for (int64_t k = 0; k < stride && i + k < width; ++k) {
702 int64_t idx = i + k;
703 int64_t j = i - 1;
704
705 // Group generate: g_idx OR (p_idx AND g_j)
706 Value andPG =
707 comb::AndOp::create(builder, loc, pPrefix[idx], gPrefix[j]);
708 gPrefixNew[idx] = comb::OrOp::create(builder, loc, gPrefix[idx], andPG);
709
710 // Group propagate: p_idx AND p_j
711 pPrefixNew[idx] =
712 comb::AndOp::create(builder, loc, pPrefix[idx], pPrefix[j]);
713 }
714 }
715
716 pPrefix = pPrefixNew;
717 gPrefix = gPrefixNew;
718 }
719
720 LLVM_DEBUG({
721 int64_t stage = 0;
722 for (int64_t stride = 1; stride < width; stride *= 2) {
723 llvm::dbgs() << "--------------------------------------- Sklanskey Stage "
724 << stage << "\n";
725 for (int64_t i = stride; i < width; i += 2 * stride) {
726 for (int64_t k = 0; k < stride && i + k < width; ++k) {
727 int64_t idx = i + k;
728 int64_t j = i - 1;
729 // Group generate: g_i OR (p_i AND g_j)
730 llvm::dbgs() << "G" << idx << stage + 1 << " = G" << idx << stage
731 << " OR (P" << idx << stage << " AND G" << j << stage
732 << ")\n";
733
734 // Group propagate: p_i AND p_j
735 llvm::dbgs() << "P" << idx << stage + 1 << " = P" << idx << stage
736 << " AND P" << j << stage << "\n";
737 }
738 }
739 ++stage;
740 }
741 });
742}
743
744// Implement the Brent-Kung parallel prefix tree
745// Described in https://en.wikipedia.org/wiki/Brent%E2%80%93Kung_adder
746// Slightly worse delay than Kogge-Stone, but less area.
747void lowerBrentKungPrefixTree(OpBuilder &builder, Location loc,
748 SmallVector<Value> &pPrefix,
749 SmallVector<Value> &gPrefix) {
750 auto width = static_cast<int64_t>(pPrefix.size());
751 assert(width == static_cast<int64_t>(gPrefix.size()));
752 SmallVector<Value> pPrefixNew = pPrefix;
753 SmallVector<Value> gPrefixNew = gPrefix;
754 // Brent-Kung parallel prefix computation
755 // Forward phase
756 int64_t stride;
757 for (stride = 1; stride < width; stride *= 2) {
758 for (int64_t i = stride * 2 - 1; i < width; i += stride * 2) {
759 int64_t j = i - stride;
760
761 // Group generate: g_i OR (p_i AND g_j)
762 Value andPG = comb::AndOp::create(builder, loc, pPrefix[i], gPrefix[j]);
763 gPrefixNew[i] = comb::OrOp::create(builder, loc, gPrefix[i], andPG);
764
765 // Group propagate: p_i AND p_j
766 pPrefixNew[i] = comb::AndOp::create(builder, loc, pPrefix[i], pPrefix[j]);
767 }
768 pPrefix = pPrefixNew;
769 gPrefix = gPrefixNew;
770 }
771
772 // Backward phase
773 for (; stride > 0; stride /= 2) {
774 for (int64_t i = stride * 3 - 1; i < width; i += stride * 2) {
775 int64_t j = i - stride;
776
777 // Group generate: g_i OR (p_i AND g_j)
778 Value andPG = comb::AndOp::create(builder, loc, pPrefix[i], gPrefix[j]);
779 gPrefixNew[i] = comb::OrOp::create(builder, loc, gPrefix[i], andPG);
780
781 // Group propagate: p_i AND p_j
782 pPrefixNew[i] = comb::AndOp::create(builder, loc, pPrefix[i], pPrefix[j]);
783 }
784 pPrefix = pPrefixNew;
785 gPrefix = gPrefixNew;
786 }
787
788 LLVM_DEBUG({
789 int64_t stage = 0;
790 for (stride = 1; stride < width; stride *= 2) {
791 llvm::dbgs() << "--------------------------------------- Brent-Kung FW "
792 << stage << " : Stride " << stride << "\n";
793 for (int64_t i = stride * 2 - 1; i < width; i += stride * 2) {
794 int64_t j = i - stride;
795
796 // Group generate: g_i OR (p_i AND g_j)
797 llvm::dbgs() << "G" << i << stage + 1 << " = G" << i << stage
798 << " OR (P" << i << stage << " AND G" << j << stage
799 << ")\n";
800
801 // Group propagate: p_i AND p_j
802 llvm::dbgs() << "P" << i << stage + 1 << " = P" << i << stage
803 << " AND P" << j << stage << "\n";
804 }
805 ++stage;
806 }
807
808 for (; stride > 0; stride /= 2) {
809 if (stride * 3 - 1 < width)
810 llvm::dbgs() << "--------------------------------------- Brent-Kung BW "
811 << stage << " : Stride " << stride << "\n";
812
813 for (int64_t i = stride * 3 - 1; i < width; i += stride * 2) {
814 int64_t j = i - stride;
815
816 // Group generate: g_i OR (p_i AND g_j)
817 llvm::dbgs() << "G" << i << stage + 1 << " = G" << i << stage
818 << " OR (P" << i << stage << " AND G" << j << stage
819 << ")\n";
820
821 // Group propagate: p_i AND p_j
822 llvm::dbgs() << "P" << i << stage + 1 << " = P" << i << stage
823 << " AND P" << j << stage << "\n";
824 }
825 --stage;
826 }
827 });
828}
829
830// TODO: Generalize to other parallel prefix trees.
831class LazyKoggeStonePrefixTree {
832public:
833 LazyKoggeStonePrefixTree(OpBuilder &builder, Location loc, int64_t width,
834 ArrayRef<Value> pPrefix, ArrayRef<Value> gPrefix)
835 : builder(builder), loc(loc), width(width) {
836 assert(width > 0 && "width must be positive");
837 for (int64_t i = 0; i < width; ++i)
838 prefixCache[{0, i}] = {pPrefix[i], gPrefix[i]};
839 }
840
841 // Get the final group and propagate values for bit i.
842 std::pair<Value, Value> getFinal(int64_t i) {
843 assert(i >= 0 && i < width && "i out of bounds");
844 // Final level is ceil(log2(width)) in Kogge-Stone.
845 return getGroupAndPropagate(llvm::Log2_64_Ceil(width), i);
846 }
847
848private:
849 // Recursively get the group and propagate values for bit i at level `level`.
850 // Level 0 is the initial level with the input propagate and generate values.
851 // Level n computes the group and propagate values for a stride of 2^(n-1).
852 // Uses memoization to cache intermediate results.
853 std::pair<Value, Value> getGroupAndPropagate(int64_t level, int64_t i);
854 OpBuilder &builder;
855 Location loc;
856 int64_t width;
857 DenseMap<std::pair<int64_t, int64_t>, std::pair<Value, Value>> prefixCache;
858};
859
860std::pair<Value, Value>
861LazyKoggeStonePrefixTree::getGroupAndPropagate(int64_t level, int64_t i) {
862 assert(i < width && "i out of bounds");
863 auto key = std::make_pair(level, i);
864 auto it = prefixCache.find(key);
865 if (it != prefixCache.end())
866 return it->second;
867
868 assert(level > 0 && "If the level is 0, we should have hit the cache");
869
870 int64_t previousStride = 1ULL << (level - 1);
871 if (i < previousStride) {
872 // No dependency, just copy from the previous level.
873 auto [propagateI, generateI] = getGroupAndPropagate(level - 1, i);
874 prefixCache[key] = {propagateI, generateI};
875 return prefixCache[key];
876 }
877 // Get the dependency index.
878 int64_t j = i - previousStride;
879 auto [propagateI, generateI] = getGroupAndPropagate(level - 1, i);
880 auto [propagateJ, generateJ] = getGroupAndPropagate(level - 1, j);
881 // Group generate: g_i OR (p_i AND g_j)
882 Value andPG = comb::AndOp::create(builder, loc, propagateI, generateJ);
883 Value newGenerate = comb::OrOp::create(builder, loc, generateI, andPG);
884 // Group propagate: p_i AND p_j
885 Value newPropagate =
886 comb::AndOp::create(builder, loc, propagateI, propagateJ);
887 prefixCache[key] = {newPropagate, newGenerate};
888 return prefixCache[key];
889}
890
891struct CombAddOpConversion : OpConversionPattern<AddOp> {
892 using OpConversionPattern<AddOp>::OpConversionPattern;
893
894 LogicalResult
895 matchAndRewrite(AddOp op, OpAdaptor adaptor,
896 ConversionPatternRewriter &rewriter) const override {
897 auto inputs = adaptor.getInputs();
898
899 // Detect add with a constant carryIn
900 if (inputs.size() == 3) {
901 auto constOp =
902 dyn_cast_or_null<hw::ConstantOp>(op.getOperand(2).getDefiningOp());
903 if (!constOp || !constOp.getValue().isOne())
904 return failure();
905
906 // Make a single bit constant 1
907 auto constOne =
908 hw::ConstantOp::create(rewriter, op.getLoc(), APInt(1, 1));
909
910 // Every adder (parallel prefix or ripple-carry) can consume a single-bit
911 // carry-in without additional logic.
912 return lowerAdder(op, inputs.take_front(2), constOne, rewriter);
913 }
914
915 // Lower only when there are two inputs.
916 // Variadic operands must be lowered in a different pattern.
917 if (inputs.size() != 2)
918 return failure();
919
920 auto width = op.getType().getIntOrFloatBitWidth();
921 // Skip a zero width value.
922 if (width == 0) {
924 op.getType(), 0);
925 return success();
926 }
927
928 return lowerAdder(op, inputs, Value(), rewriter);
929 }
930
931 // Implement a basic ripple-carry adder for small bitwidths.
932 LogicalResult
933 lowerRippleCarryAdder(comb::AddOp op, ValueRange inputs, Value carryIn,
934 ConversionPatternRewriter &rewriter) const {
935 auto width = op.getType().getIntOrFloatBitWidth();
936 // Implement a naive Ripple-carry full adder.
937 Value carry = carryIn;
938
939 auto aBits = extractBits(rewriter, inputs[0]);
940 auto bBits = extractBits(rewriter, inputs[1]);
941 SmallVector<Value> results;
942 results.resize(width);
943 for (int64_t i = 0; i < width; ++i) {
944 SmallVector<Value> xorOperands = {aBits[i], bBits[i]};
945 if (carry)
946 xorOperands.push_back(carry);
947
948 // sum[i] = xor(carry[i-1], a[i], b[i])
949 // NOTE: The result is stored in reverse order.
950 results[width - i - 1] =
951 comb::XorOp::create(rewriter, op.getLoc(), xorOperands, true);
952
953 // If this is the last bit, we are done.
954 if (i == width - 1)
955 break;
956
957 // carry[i] = (carry[i-1] & (a[i] ^ b[i])) | (a[i] & b[i])
958 if (!carry) {
959 // This is the first bit, so the carry is the next carry.
960 carry = comb::AndOp::create(rewriter, op.getLoc(),
961 ValueRange{aBits[i], bBits[i]}, true);
962 continue;
963 }
964
965 carry = createMajorityFunction(rewriter, op.getLoc(), aBits[i], bBits[i],
966 carry);
967 }
968 LLVM_DEBUG(llvm::dbgs() << "Lower comb.add to Ripple-Carry Adder of width "
969 << width << "\n");
970
972 return success();
973 }
974
975 // Implement a parallel prefix adder - with Kogge-Stone or Brent-Kung trees
976 // Will introduce unused signals for the carry bits but these will be removed
977 // by the AIG pass.
978 LogicalResult lowerAdder(comb::AddOp op, ValueRange inputs, Value carryIn,
979 ConversionPatternRewriter &rewriter) const {
980
981 // Check that the carryIn is a 1-bit value if it is provided (not
982 // necessarily a constant 1).
983 assert(carryIn == nullptr ||
984 carryIn.getType().getIntOrFloatBitWidth() == 1 &&
985 "carryIn must be a 1-bit value");
986
987 // Check if the architecture is specified by an attribute.
988 auto width = op.getType().getIntOrFloatBitWidth();
989 auto arch = determineAdderArch(op, width);
990 if (arch == AdderArchitecture::RippleCarry)
991 return lowerRippleCarryAdder(op, inputs, carryIn, rewriter);
992
993 auto aBits = extractBits(rewriter, inputs[0]);
994 auto bBits = extractBits(rewriter, inputs[1]);
995
996 // Construct propagate (p) and generate (g) signals
997 SmallVector<Value> p, g;
998 p.reserve(width);
999 g.reserve(width);
1000
1001 for (auto [aBit, bBit] : llvm::zip(aBits, bBits)) {
1002 // p_i = a_i XOR b_i
1003 p.push_back(comb::XorOp::create(rewriter, op.getLoc(), aBit, bBit));
1004 // g_i = a_i AND b_i
1005 g.push_back(comb::AndOp::create(rewriter, op.getLoc(), aBit, bBit));
1006 }
1007
1008 // With carry_in, adjust g[0]: g[0] = (a[0] AND b[0]) OR (p[0] AND carry_in)
1009 // This bakes the carry_in into the prefix tree, avoiding a separate adder.
1010 if (carryIn) {
1011 Value pAndC = comb::AndOp::create(rewriter, op.getLoc(), p[0], carryIn);
1012 g[0] = comb::OrOp::create(rewriter, op.getLoc(), g[0], pAndC);
1013 }
1014
1015 LLVM_DEBUG({
1016 llvm::dbgs() << "Lower comb.add to Parallel-Prefix of width " << width
1017 << "\n--------------------------------------- Init\n";
1018
1019 for (int64_t i = 0; i < width; ++i) {
1020 // p_i = a_i XOR b_i
1021 llvm::dbgs() << "P0" << i << " = A" << i << " XOR B" << i << "\n";
1022 if (i == 0 && carryIn)
1023 llvm::dbgs() << "G0" << i << " = (A" << i << " AND B" << i
1024 << ") OR (P" << i << " AND CARRY_IN)\n";
1025 else
1026 // g_i = a_i AND b_i
1027 llvm::dbgs() << "G0" << i << " = A" << i << " AND B" << i << "\n";
1028 }
1029 });
1030
1031 // Create copies of p and g for the prefix computation
1032 SmallVector<Value> pPrefix = p;
1033 SmallVector<Value> gPrefix = g;
1034
1035 // Select the Parallel Prefix Architecture
1036 switch (arch) {
1037 case AdderArchitecture::RippleCarry:
1038 llvm_unreachable("Ripple-Carry handled above");
1039 break;
1040 case AdderArchitecture::Sklanskey:
1041 lowerSklanskeyPrefixTree(rewriter, op.getLoc(), pPrefix, gPrefix);
1042 break;
1043 case AdderArchitecture::KoggeStone:
1044 lowerKoggeStonePrefixTree(rewriter, op.getLoc(), pPrefix, gPrefix);
1045 break;
1046 case AdderArchitecture::BrentKung:
1047 lowerBrentKungPrefixTree(rewriter, op.getLoc(), pPrefix, gPrefix);
1048 break;
1049 }
1050
1051 // Generate result sum bits
1052 // NOTE: The result is stored in reverse order.
1053 SmallVector<Value> results;
1054 results.resize(width);
1055 // sum[0] = p[0] XOR carry_in (carry_in = 0 when null -> just p[0])
1056 results[width - 1] =
1057 carryIn ? comb::XorOp::create(rewriter, op.getLoc(), p[0], carryIn)
1058 : p[0];
1059
1060 // For remaining bits, sum_i = p_i XOR g_(i-1)
1061 // The carry into position i is the group generate from position i-1
1062 for (int64_t i = 1; i < width; ++i)
1063 results[width - 1 - i] =
1064 comb::XorOp::create(rewriter, op.getLoc(), p[i], gPrefix[i - 1]);
1065
1067
1068 LLVM_DEBUG({
1069 llvm::dbgs() << "--------------------------------------- Completion\n";
1070
1071 if (carryIn)
1072 llvm::dbgs() << "RES0 = P0 XOR CARRY_IN\n";
1073 else
1074 llvm::dbgs() << "RES0 = P0\n";
1075 for (int64_t i = 1; i < width; ++i)
1076 llvm::dbgs() << "RES" << i << " = P" << i << " XOR G" << i - 1 << "\n";
1077 });
1078
1079 return success();
1080 }
1081};
1082
1083struct CombMulOpConversion : OpConversionPattern<MulOp> {
1084 using OpConversionPattern<MulOp>::OpConversionPattern;
1085 using OpAdaptor = typename OpConversionPattern<MulOp>::OpAdaptor;
1086 LogicalResult
1087 matchAndRewrite(MulOp op, OpAdaptor adaptor,
1088 ConversionPatternRewriter &rewriter) const override {
1089 if (adaptor.getInputs().size() != 2)
1090 return failure();
1091
1092 Location loc = op.getLoc();
1093 Value a = adaptor.getInputs()[0];
1094 Value b = adaptor.getInputs()[1];
1095 unsigned width = op.getType().getIntOrFloatBitWidth();
1096
1097 // Skip a zero width value.
1098 if (width == 0) {
1099 rewriter.replaceOpWithNewOp<hw::ConstantOp>(op, op.getType(), 0);
1100 return success();
1101 }
1102
1103 // Extract individual bits from operands
1104 SmallVector<Value> aBits = extractBits(rewriter, a);
1105 SmallVector<Value> bBits = extractBits(rewriter, b);
1106
1107 auto falseValue = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
1108
1109 // Generate partial products
1110 SmallVector<SmallVector<Value>> partialProducts;
1111 partialProducts.reserve(width);
1112 for (unsigned i = 0; i < width; ++i) {
1113 SmallVector<Value> row(i, falseValue);
1114 row.reserve(width);
1115 // Generate partial product bits
1116 for (unsigned j = 0; i + j < width; ++j)
1117 row.push_back(
1118 rewriter.createOrFold<comb::AndOp>(loc, aBits[j], bBits[i]));
1119
1120 partialProducts.push_back(row);
1121 }
1122
1123 // If the width is 1, we are done.
1124 if (width == 1) {
1125 rewriter.replaceOp(op, partialProducts[0][0]);
1126 return success();
1127 }
1128
1129 // Wallace tree reduction - reduce to two addends.
1130 datapath::CompressorTree comp(width, partialProducts, loc, rewriter);
1131 auto addends = comp.compressToHeight(rewriter, 2);
1132
1133 // Sum the two addends using a carry-propagate adder
1134 auto newAdd = comb::AddOp::create(rewriter, loc, addends, true);
1135 replaceOpAndCopyNamehint(rewriter, op, newAdd);
1136 return success();
1137 }
1138};
1139
1140template <typename OpTy>
1141struct DivModOpConversionBase : OpConversionPattern<OpTy> {
1142 DivModOpConversionBase(MLIRContext *context, int64_t maxEmulationUnknownBits)
1143 : OpConversionPattern<OpTy>(context),
1144 maxEmulationUnknownBits(maxEmulationUnknownBits) {
1145 assert(maxEmulationUnknownBits < 32 &&
1146 "maxEmulationUnknownBits must be less than 32");
1147 }
1148 const int64_t maxEmulationUnknownBits;
1149};
1150
1151struct CombDivUOpConversion : DivModOpConversionBase<DivUOp> {
1152 using DivModOpConversionBase<DivUOp>::DivModOpConversionBase;
1153 LogicalResult
1154 matchAndRewrite(DivUOp op, OpAdaptor adaptor,
1155 ConversionPatternRewriter &rewriter) const override {
1156 // Check if the divisor is a power of two.
1157 if (llvm::succeeded(comb::convertDivUByPowerOfTwo(op, rewriter)))
1158 return success();
1159
1160 // Lower constant divisors with magic-number division; otherwise fall back
1161 // to emulation for small rhs values.
1162 if (auto rhsConst = adaptor.getRhs().getDefiningOp<hw::ConstantOp>()) {
1163 APInt divisor = rhsConst.getValue();
1164 // Division by zero is undefined, just return zero.
1165 if (divisor.isZero()) {
1167 op.getType(), 0);
1168 return success();
1169 }
1170 replaceOpAndCopyNamehint(rewriter, op,
1171 lowerUnsignedDivByConstant(rewriter, op.getLoc(),
1172 adaptor.getLhs(),
1173 divisor));
1174 return success();
1175 }
1176
1177 // When rhs is not power of two and the number of unknown bits are small,
1178 // create a mux tree that emulates all possible cases.
1179 if (succeeded(emulateBinaryOpForUnknownBits(
1180 rewriter, maxEmulationUnknownBits, op,
1181 [](const APInt &lhs, const APInt &rhs) {
1182 // Division by zero is undefined, just return zero.
1183 if (rhs.isZero())
1184 return APInt::getZero(rhs.getBitWidth());
1185 return lhs.udiv(rhs);
1186 })))
1187 return success();
1188
1189 // Otherwise, build a restoring array divider.
1190 replaceOpAndCopyNamehint(rewriter, op,
1191 lowerUnsignedDivRem(rewriter, op.getLoc(),
1192 adaptor.getLhs(),
1193 adaptor.getRhs())
1194 .first);
1195 return success();
1196 }
1197};
1198
1199struct CombModUOpConversion : DivModOpConversionBase<ModUOp> {
1200 using DivModOpConversionBase<ModUOp>::DivModOpConversionBase;
1201 LogicalResult
1202 matchAndRewrite(ModUOp op, OpAdaptor adaptor,
1203 ConversionPatternRewriter &rewriter) const override {
1204 // Check if the divisor is a power of two.
1205 if (llvm::succeeded(comb::convertModUByPowerOfTwo(op, rewriter)))
1206 return success();
1207
1208 // Lower constant divisors by calculating q = lhs / rhs and returning
1209 // lhs - q * rhs; otherwise fall back to emulation for small rhs values.
1210 if (auto rhsConst = adaptor.getRhs().getDefiningOp<hw::ConstantOp>()) {
1211 APInt divisor = rhsConst.getValue();
1212 // Remainder by zero is undefined, just return zero.
1213 if (divisor.isZero()) {
1215 op.getType(), 0);
1216 return success();
1217 }
1218 auto loc = op.getLoc();
1219 Value q =
1220 lowerUnsignedDivByConstant(rewriter, loc, adaptor.getLhs(), divisor);
1221 Value product =
1222 rewriter.createOrFold<comb::MulOp>(loc, q, adaptor.getRhs());
1223 Value remainder =
1224 rewriter.createOrFold<comb::SubOp>(loc, adaptor.getLhs(), product);
1225 replaceOpAndCopyNamehint(rewriter, op, remainder);
1226
1227 return success();
1228 }
1229
1230 // When rhs is not power of two and the number of unknown bits are small,
1231 // create a mux tree that emulates all possible cases.
1232 if (succeeded(emulateBinaryOpForUnknownBits(
1233 rewriter, maxEmulationUnknownBits, op,
1234 [](const APInt &lhs, const APInt &rhs) {
1235 // Division by zero is undefined, just return zero.
1236 if (rhs.isZero())
1237 return APInt::getZero(rhs.getBitWidth());
1238 return lhs.urem(rhs);
1239 })))
1240 return success();
1241
1242 // Otherwise, build a restoring array divider.
1243 replaceOpAndCopyNamehint(rewriter, op,
1244 lowerUnsignedDivRem(rewriter, op.getLoc(),
1245 adaptor.getLhs(),
1246 adaptor.getRhs())
1247 .second);
1248 return success();
1249 }
1250};
1251
1252struct CombDivSOpConversion : DivModOpConversionBase<DivSOp> {
1253 using DivModOpConversionBase<DivSOp>::DivModOpConversionBase;
1254
1255 LogicalResult
1256 matchAndRewrite(DivSOp op, OpAdaptor adaptor,
1257 ConversionPatternRewriter &rewriter) const override {
1258 // Lower constant divisors with magic-number division; otherwise fall back
1259 // to emulation for small rhs values.
1260 if (auto rhsConst = adaptor.getRhs().getDefiningOp<hw::ConstantOp>()) {
1261 APInt divisor = rhsConst.getValue();
1262 unsigned width = op.getType().getIntOrFloatBitWidth();
1263 // Division by zero is undefined, just return zero.
1264 if (divisor.isZero()) {
1266 op.getType(), 0);
1267 return success();
1268 }
1269 // divs(lhs, 1) = lhs.
1270 if (divisor.isOne()) {
1271 replaceOpAndCopyNamehint(rewriter, op, adaptor.getLhs());
1272 return success();
1273 }
1274 // divs(lhs, -1) = -lhs = sub(0, lhs).
1275 if (divisor.isAllOnes()) {
1277 rewriter, op,
1278 rewriter.createOrFold<comb::SubOp>(
1279 op.getLoc(),
1280 hw::ConstantOp::create(rewriter, op.getLoc(),
1281 APInt::getZero(width)),
1282 adaptor.getLhs()));
1283 return success();
1284 }
1285 replaceOpAndCopyNamehint(rewriter, op,
1286 lowerSignedDivByConstant(rewriter, op.getLoc(),
1287 adaptor.getLhs(),
1288 divisor));
1289 return success();
1290 }
1291
1292 // When the number of unknown bits are small, create a mux tree that
1293 // emulates all possible cases.
1294 if (succeeded(emulateBinaryOpForUnknownBits(
1295 rewriter, maxEmulationUnknownBits, op,
1296 [](const APInt &lhs, const APInt &rhs) {
1297 // Division by zero is undefined, just return zero.
1298 if (rhs.isZero())
1299 return APInt::getZero(rhs.getBitWidth());
1300 return lhs.sdiv(rhs);
1301 })))
1302 return success();
1303
1304 // Otherwise, build a restoring array divider.
1305 replaceOpAndCopyNamehint(rewriter, op,
1306 lowerSignedDivRem(rewriter, op.getLoc(),
1307 adaptor.getLhs(),
1308 adaptor.getRhs())
1309 .first);
1310 return success();
1311 }
1312};
1313
1314struct CombModSOpConversion : DivModOpConversionBase<ModSOp> {
1315 using DivModOpConversionBase<ModSOp>::DivModOpConversionBase;
1316 LogicalResult
1317 matchAndRewrite(ModSOp op, OpAdaptor adaptor,
1318 ConversionPatternRewriter &rewriter) const override {
1319 // Lower constant divisors by calculating q = lhs / rhs and returning
1320 // lhs - q * rhs; otherwise fall back to emulation for small rhs values.
1321 if (auto rhsConst = adaptor.getRhs().getDefiningOp<hw::ConstantOp>()) {
1322 APInt divisor = rhsConst.getValue();
1323 // Remainder by 0 is undefined; remainder by +/-1 is always zero.
1324 if (divisor.isZero() || divisor.isOne() || divisor.isAllOnes()) {
1326 op.getType(), 0);
1327 return success();
1328 }
1329 auto loc = op.getLoc();
1330 Value q =
1331 lowerSignedDivByConstant(rewriter, loc, adaptor.getLhs(), divisor);
1332 Value product =
1333 rewriter.createOrFold<comb::MulOp>(loc, q, adaptor.getRhs());
1334 Value remainder =
1335 rewriter.createOrFold<comb::SubOp>(loc, adaptor.getLhs(), product);
1336 replaceOpAndCopyNamehint(rewriter, op, remainder);
1337 return success();
1338 }
1339
1340 // When the number of unknown bits are small, create a mux tree that
1341 // emulates all possible cases.
1342 if (succeeded(emulateBinaryOpForUnknownBits(
1343 rewriter, maxEmulationUnknownBits, op,
1344 [](const APInt &lhs, const APInt &rhs) {
1345 // Division by zero is undefined, just return zero.
1346 if (rhs.isZero())
1347 return APInt::getZero(rhs.getBitWidth());
1348 return lhs.srem(rhs);
1349 })))
1350 return success();
1351
1352 // Otherwise, build a restoring array divider.
1353 replaceOpAndCopyNamehint(rewriter, op,
1354 lowerSignedDivRem(rewriter, op.getLoc(),
1355 adaptor.getLhs(),
1356 adaptor.getRhs())
1357 .second);
1358 return success();
1359 }
1360};
1361
1362struct CombICmpOpConversion : OpConversionPattern<ICmpOp> {
1363 using OpConversionPattern<ICmpOp>::OpConversionPattern;
1364
1365 // Simple comparator for small bit widths
1366 static Value constructRippleCarry(Location loc, Value a, Value b,
1367 bool includeEq,
1368 ConversionPatternRewriter &rewriter) {
1369 // Construct following unsigned comparison expressions.
1370 // a <= b ==> (~a[n] & b[n]) | (a[n] == b[n] & a[n-1:0] <= b[n-1:0])
1371 // a < b ==> (~a[n] & b[n]) | (a[n] == b[n] & a[n-1:0] < b[n-1:0])
1372 auto aBits = extractBits(rewriter, a);
1373 auto bBits = extractBits(rewriter, b);
1374 Value acc = hw::ConstantOp::create(rewriter, loc, APInt(1, includeEq));
1375
1376 for (auto [aBit, bBit] : llvm::zip(aBits, bBits)) {
1377 auto aBitXorBBit =
1378 rewriter.createOrFold<comb::XorOp>(loc, aBit, bBit, true);
1379 auto aEqualB = rewriter.createOrFold<synth::aig::AndInverterOp>(
1380 loc, aBitXorBBit, true);
1381 auto pred = rewriter.createOrFold<synth::aig::AndInverterOp>(
1382 loc, aBit, bBit, true, false);
1383
1384 auto aBitAndBBit = rewriter.createOrFold<comb::AndOp>(
1385 loc, ValueRange{aEqualB, acc}, true);
1386 acc = rewriter.createOrFold<comb::OrOp>(loc, pred, aBitAndBBit, true);
1387 }
1388 return acc;
1389 }
1390
1391 // Compute prefix comparison using parallel prefix algorithm
1392 // Note: This generates all intermediate prefix values even though we only
1393 // need the final result. Optimizing this to skip intermediate computations
1394 // is non-trivial because each iteration depends on results from previous
1395 // iterations. We rely on DCE passes to remove unused operations.
1396 // TODO: Lazily compute only the required prefix values. Kogge-Stone is
1397 // already implemented in a lazy manner below, but other architectures can
1398 // also be optimized.
1399 static Value computePrefixComparison(ConversionPatternRewriter &rewriter,
1400 Location loc, SmallVector<Value> pPrefix,
1401 SmallVector<Value> gPrefix,
1402 bool includeEq, AdderArchitecture arch) {
1403 auto width = pPrefix.size();
1404 Value finalGroup, finalPropagate;
1405 // Apply the appropriate prefix tree algorithm
1406 switch (arch) {
1407 case AdderArchitecture::RippleCarry:
1408 llvm_unreachable("Ripple-Carry should be handled separately");
1409 break;
1410 case AdderArchitecture::Sklanskey: {
1411 lowerSklanskeyPrefixTree(rewriter, loc, pPrefix, gPrefix);
1412 finalGroup = gPrefix[width - 1];
1413 finalPropagate = pPrefix[width - 1];
1414 break;
1415 }
1416 case AdderArchitecture::KoggeStone:
1417 // Use lazy Kogge-Stone implementation to avoid computing all
1418 // intermediate prefix values.
1419 std::tie(finalPropagate, finalGroup) =
1420 LazyKoggeStonePrefixTree(rewriter, loc, width, pPrefix, gPrefix)
1421 .getFinal(width - 1);
1422 break;
1423 case AdderArchitecture::BrentKung: {
1424 lowerBrentKungPrefixTree(rewriter, loc, pPrefix, gPrefix);
1425 finalGroup = gPrefix[width - 1];
1426 finalPropagate = pPrefix[width - 1];
1427 break;
1428 }
1429 }
1430
1431 // Final result: `finalGroup` gives us "a < b"
1432 if (includeEq) {
1433 // a <= b iff (a < b) OR (a == b)
1434 // a == b iff `finalPropagate` (all bits are equal)
1435 return comb::OrOp::create(rewriter, loc, finalGroup, finalPropagate);
1436 }
1437 // a < b iff `finalGroup`
1438 return finalGroup;
1439 }
1440
1441 // Construct an unsigned comparator using either ripple-carry or
1442 // parallel-prefix architecture. Comparison uses parallel prefix tree as an
1443 // internal component, so use `AdderArchitecture` enum to select architecture.
1444 static Value constructUnsignedCompare(Operation *op, Location loc, Value a,
1445 Value b, bool isLess, bool includeEq,
1446 ConversionPatternRewriter &rewriter) {
1447 // Ensure a <= b by swapping for simplicity.
1448 if (!isLess)
1449 std::swap(a, b);
1450 auto width = a.getType().getIntOrFloatBitWidth();
1451
1452 // Check if the architecture is specified by an attribute.
1453 auto arch = determineAdderArch(op, width);
1454 if (arch == AdderArchitecture::RippleCarry)
1455 return constructRippleCarry(loc, a, b, includeEq, rewriter);
1456
1457 // For larger widths, use parallel prefix tree
1458 auto aBits = extractBits(rewriter, a);
1459 auto bBits = extractBits(rewriter, b);
1460
1461 // For comparison, we compute:
1462 // - Equal bits: eq_i = ~(a_i ^ b_i)
1463 // - Greater bits: gt_i = ~a_i & b_i (a_i < b_i)
1464 // - Propagate: p_i = eq_i (equality propagates)
1465 // - Generate: g_i = gt_i (greater-than generates)
1466 SmallVector<Value> eq, gt;
1467 eq.reserve(width);
1468 gt.reserve(width);
1469
1470 auto one =
1471 hw::ConstantOp::create(rewriter, loc, rewriter.getIntegerType(1), 1);
1472
1473 for (auto [aBit, bBit] : llvm::zip(aBits, bBits)) {
1474 // eq_i = ~(a_i ^ b_i) = a_i == b_i
1475 auto xorBit = comb::XorOp::create(rewriter, loc, aBit, bBit);
1476 eq.push_back(comb::XorOp::create(rewriter, loc, xorBit, one));
1477
1478 // gt_i = ~a_i & b_i = a_i < b_i
1479 auto notA = comb::XorOp::create(rewriter, loc, aBit, one);
1480 gt.push_back(comb::AndOp::create(rewriter, loc, notA, bBit));
1481 }
1482
1483 return computePrefixComparison(rewriter, loc, std::move(eq), std::move(gt),
1484 includeEq, arch);
1485 }
1486
1487 LogicalResult
1488 matchAndRewrite(ICmpOp op, OpAdaptor adaptor,
1489 ConversionPatternRewriter &rewriter) const override {
1490 auto lhs = adaptor.getLhs();
1491 auto rhs = adaptor.getRhs();
1492
1493 switch (op.getPredicate()) {
1494 default:
1495 return failure();
1496
1497 case ICmpPredicate::eq:
1498 case ICmpPredicate::ceq: {
1499 // a == b ==> ~(a[n] ^ b[n]) & ~(a[n-1] ^ b[n-1]) & ...
1500 auto xorOp = rewriter.createOrFold<comb::XorOp>(op.getLoc(), lhs, rhs);
1501 auto xorBits = extractBits(rewriter, xorOp);
1502 SmallVector<bool> allInverts(xorBits.size(), true);
1504 rewriter, op, xorBits, allInverts);
1505 return success();
1506 }
1507
1508 case ICmpPredicate::ne:
1509 case ICmpPredicate::cne: {
1510 // a != b ==> (a[n] ^ b[n]) | (a[n-1] ^ b[n-1]) | ...
1511 auto xorOp = rewriter.createOrFold<comb::XorOp>(op.getLoc(), lhs, rhs);
1513 rewriter, op, extractBits(rewriter, xorOp), true);
1514 return success();
1515 }
1516
1517 case ICmpPredicate::uge:
1518 case ICmpPredicate::ugt:
1519 case ICmpPredicate::ule:
1520 case ICmpPredicate::ult: {
1521 bool isLess = op.getPredicate() == ICmpPredicate::ult ||
1522 op.getPredicate() == ICmpPredicate::ule;
1523 bool includeEq = op.getPredicate() == ICmpPredicate::uge ||
1524 op.getPredicate() == ICmpPredicate::ule;
1525 replaceOpAndCopyNamehint(rewriter, op,
1526 constructUnsignedCompare(op, op.getLoc(), lhs,
1527 rhs, isLess, includeEq,
1528 rewriter));
1529 return success();
1530 }
1531 case ICmpPredicate::slt:
1532 case ICmpPredicate::sle:
1533 case ICmpPredicate::sgt:
1534 case ICmpPredicate::sge: {
1535 if (lhs.getType().getIntOrFloatBitWidth() == 0)
1536 return rewriter.notifyMatchFailure(
1537 op.getLoc(), "i0 signed comparison is unsupported");
1538 bool isLess = op.getPredicate() == ICmpPredicate::slt ||
1539 op.getPredicate() == ICmpPredicate::sle;
1540 bool includeEq = op.getPredicate() == ICmpPredicate::sge ||
1541 op.getPredicate() == ICmpPredicate::sle;
1542
1543 // Get a sign bit
1544 auto signA = extractMSB(rewriter, lhs);
1545 auto signB = extractMSB(rewriter, rhs);
1546 auto aRest = extractOtherThanMSB(rewriter, lhs);
1547 auto bRest = extractOtherThanMSB(rewriter, rhs);
1548
1549 // Compare magnitudes (all bits except sign)
1550 auto sameSignResult = constructUnsignedCompare(
1551 op, op.getLoc(), aRest, bRest, isLess, includeEq, rewriter);
1552
1553 // XOR of signs: true if signs are different
1554 auto signsDiffer =
1555 comb::XorOp::create(rewriter, op.getLoc(), signA, signB);
1556
1557 // Result when signs are different
1558 Value diffSignResult = isLess ? signA : signB;
1559
1560 // Final result: choose based on whether signs differ
1562 rewriter, op, signsDiffer, diffSignResult, sameSignResult);
1563 return success();
1564 }
1565 }
1566 }
1567};
1568
1569struct CombParityOpConversion : OpConversionPattern<ParityOp> {
1570 using OpConversionPattern<ParityOp>::OpConversionPattern;
1571
1572 LogicalResult
1573 matchAndRewrite(ParityOp op, OpAdaptor adaptor,
1574 ConversionPatternRewriter &rewriter) const override {
1575 // Parity is the XOR of all bits.
1577 rewriter, op, extractBits(rewriter, adaptor.getInput()), true);
1578 return success();
1579 }
1580};
1581
1582struct CombShlOpConversion : OpConversionPattern<comb::ShlOp> {
1583 using OpConversionPattern<comb::ShlOp>::OpConversionPattern;
1584
1585 LogicalResult
1586 matchAndRewrite(comb::ShlOp op, OpAdaptor adaptor,
1587 ConversionPatternRewriter &rewriter) const override {
1588 auto width = op.getType().getIntOrFloatBitWidth();
1589 auto lhs = adaptor.getLhs();
1590 auto result = createShiftLogic</*isLeftShift=*/true>(
1591 rewriter, op.getLoc(), adaptor.getRhs(), width,
1592 /*getPadding=*/
1593 [&](int64_t index) {
1594 // Don't create zero width value.
1595 if (index == 0)
1596 return Value();
1597 // Padding is 0 for left shift.
1598 return rewriter.createOrFold<hw::ConstantOp>(
1599 op.getLoc(), rewriter.getIntegerType(index), 0);
1600 },
1601 /*getExtract=*/
1602 [&](int64_t index) {
1603 assert(index < width && "index out of bounds");
1604 // Exract the bits from LSB.
1605 return rewriter.createOrFold<comb::ExtractOp>(op.getLoc(), lhs, 0,
1606 width - index);
1607 });
1608
1609 replaceOpAndCopyNamehint(rewriter, op, result);
1610 return success();
1611 }
1612};
1613
1614struct CombShrUOpConversion : OpConversionPattern<comb::ShrUOp> {
1615 using OpConversionPattern<comb::ShrUOp>::OpConversionPattern;
1616
1617 LogicalResult
1618 matchAndRewrite(comb::ShrUOp op, OpAdaptor adaptor,
1619 ConversionPatternRewriter &rewriter) const override {
1620 auto width = op.getType().getIntOrFloatBitWidth();
1621 auto lhs = adaptor.getLhs();
1622 auto result = createShiftLogic</*isLeftShift=*/false>(
1623 rewriter, op.getLoc(), adaptor.getRhs(), width,
1624 /*getPadding=*/
1625 [&](int64_t index) {
1626 // Don't create zero width value.
1627 if (index == 0)
1628 return Value();
1629 // Padding is 0 for right shift.
1630 return rewriter.createOrFold<hw::ConstantOp>(
1631 op.getLoc(), rewriter.getIntegerType(index), 0);
1632 },
1633 /*getExtract=*/
1634 [&](int64_t index) {
1635 assert(index < width && "index out of bounds");
1636 // Exract the bits from MSB.
1637 return rewriter.createOrFold<comb::ExtractOp>(op.getLoc(), lhs, index,
1638 width - index);
1639 });
1640
1641 replaceOpAndCopyNamehint(rewriter, op, result);
1642 return success();
1643 }
1644};
1645
1646struct CombShrSOpConversion : OpConversionPattern<comb::ShrSOp> {
1647 using OpConversionPattern<comb::ShrSOp>::OpConversionPattern;
1648
1649 LogicalResult
1650 matchAndRewrite(comb::ShrSOp op, OpAdaptor adaptor,
1651 ConversionPatternRewriter &rewriter) const override {
1652 auto width = op.getType().getIntOrFloatBitWidth();
1653 if (width == 0)
1654 return rewriter.notifyMatchFailure(op.getLoc(),
1655 "i0 signed shift is unsupported");
1656 auto lhs = adaptor.getLhs();
1657 // Get the sign bit.
1658 auto sign =
1659 rewriter.createOrFold<comb::ExtractOp>(op.getLoc(), lhs, width - 1, 1);
1660
1661 // NOTE: The max shift amount is width - 1 because the sign bit is
1662 // already shifted out.
1663 auto result = createShiftLogic</*isLeftShift=*/false>(
1664 rewriter, op.getLoc(), adaptor.getRhs(), width - 1,
1665 /*getPadding=*/
1666 [&](int64_t index) {
1667 return rewriter.createOrFold<comb::ReplicateOp>(op.getLoc(), sign,
1668 index + 1);
1669 },
1670 /*getExtract=*/
1671 [&](int64_t index) {
1672 return rewriter.createOrFold<comb::ExtractOp>(op.getLoc(), lhs, index,
1673 width - index - 1);
1674 });
1675
1676 replaceOpAndCopyNamehint(rewriter, op, result);
1677 return success();
1678 }
1679};
1680
1681} // namespace
1682
1683//===----------------------------------------------------------------------===//
1684// Convert Comb to AIG pass
1685//===----------------------------------------------------------------------===//
1686
1687namespace {
1688struct ConvertCombToSynthPass
1689 : public impl::ConvertCombToSynthBase<ConvertCombToSynthPass> {
1690 void runOnOperation() override;
1691 using ConvertCombToSynthBase<ConvertCombToSynthPass>::ConvertCombToSynthBase;
1692};
1693} // namespace
1694
1695static void
1697 uint32_t maxEmulationUnknownBits,
1698 bool forceAIG) {
1699 patterns.add<
1700 // Bitwise Logical Ops
1701 CombAndOpConversion, CombParityOpConversion, CombXorOpToSynthConversion,
1702 CombMuxOpToSynthConversion,
1703 // Arithmetic Ops
1704 CombMulOpConversion, CombICmpOpConversion,
1705 // Shift Ops
1706 CombShlOpConversion, CombShrUOpConversion, CombShrSOpConversion,
1707 // Variadic ops that must be lowered to binary operations
1708 CombLowerVariadicOp<AddOp>, CombLowerVariadicOp<MulOp>>(
1709 patterns.getContext());
1710
1711 if (forceAIG) {
1712 patterns.add<SynthXorInverterOpConversion, SynthMuxInverterOpConversion>(
1713 patterns.getContext());
1714 }
1715 patterns.add(comb::convertSubToAdd);
1716
1717 patterns.add<CombOrToAIGConversion, CombAddOpConversion>(
1718 patterns.getContext());
1720
1721 if (forceAIG)
1723
1724 // Add div/mod patterns with a threshold given by the pass option.
1725 patterns.add<CombDivUOpConversion, CombModUOpConversion, CombDivSOpConversion,
1726 CombModSOpConversion>(patterns.getContext(),
1727 maxEmulationUnknownBits);
1728}
1729
1730void ConvertCombToSynthPass::runOnOperation() {
1731 ConversionTarget target(getContext());
1732
1733 // Comb is source dialect.
1734 target.addIllegalDialect<comb::CombDialect>();
1735 // Keep data movement operations like Extract, Concat and Replicate.
1736 target.addLegalOp<comb::ExtractOp, comb::ConcatOp, comb::ReplicateOp,
1737 hw::BitcastOp, hw::ConstantOp>();
1738
1739 // Treat array operations as illegal. Strictly speaking, other than array
1740 // get operation with non-const index are legal in AIG but array types
1741 // prevent a bunch of optimizations so just lower them to integer
1742 // operations. It's required to run HWAggregateToComb pass before this pass.
1743 target.addIllegalOp<hw::ArrayGetOp, hw::ArrayCreateOp, hw::ArrayConcatOp,
1744 hw::AggregateConstantOp>();
1745
1746 target.addLegalDialect<synth::SynthDialect>();
1747 if (forceAIG)
1748 target.addIllegalOp<synth::XorInverterOp, synth::MuxInverterOp>();
1749
1750 // If additional legal ops are specified, add them to the target.
1751 if (!additionalLegalOps.empty())
1752 for (const auto &opName : additionalLegalOps)
1753 target.addLegalOp(OperationName(opName, &getContext()));
1754
1755 RewritePatternSet patterns(&getContext());
1756 populateCombToAIGConversionPatterns(patterns, maxEmulationUnknownBits,
1757 forceAIG);
1758
1759 if (failed(mlir::applyPartialConversion(getOperation(), target,
1760 std::move(patterns))))
1761 return signalPassFailure();
1762}
assert(baseType &&"element must be base type")
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition CalyxOps.cpp:555
static SmallVector< Value > extractBits(OpBuilder &builder, Value val)
static Value createShiftLogic(ConversionPatternRewriter &rewriter, Location loc, Value shiftAmount, int64_t maxShiftAmount, llvm::function_ref< Value(int64_t)> getPadding, llvm::function_ref< Value(int64_t)> getExtract)
static Value createAShrByConstant(OpBuilder &builder, Location loc, Value value, unsigned amount)
static Value createMulHigh(OpBuilder &builder, Location loc, Value lhs, const APInt &rhs)
static APInt substitueMaskToValues(size_t width, llvm::SmallVectorImpl< ConstantOrValue > &constantOrValues, uint32_t mask)
static std::pair< Value, Value > lowerUnsignedDivRem(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Lower an unsigned division with a non-constant divisor to a combinational restoring array divider.
static Value lowerSignedDivByConstant(OpBuilder &builder, Location loc, Value lhs, const APInt &divisor)
static Value createLShrByConstant(OpBuilder &builder, Location loc, Value value, unsigned amount)
static LogicalResult emulateBinaryOpForUnknownBits(ConversionPatternRewriter &rewriter, int64_t maxEmulationUnknownBits, Operation *op, llvm::function_ref< APInt(const APInt &, const APInt &)> emulate)
static int64_t getNumUnknownBitsAndPopulateValues(Value value, llvm::SmallVectorImpl< ConstantOrValue > &values)
static Value createMajorityFunction(OpBuilder &rewriter, Location loc, Value a, Value b, Value carry)
static Value extractOtherThanMSB(OpBuilder &builder, Value val)
static Value extractMSB(OpBuilder &builder, Value val)
static void populateCombToAIGConversionPatterns(RewritePatternSet &patterns, uint32_t maxEmulationUnknownBits, bool forceAIG)
static Value lowerUnsignedDivByConstant(OpBuilder &builder, Location loc, Value lhs, const APInt &divisor)
static std::pair< Value, Value > lowerSignedDivRem(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Lower a signed division with a non-constant divisor to a restoring array divider operating on the mag...
static std::unique_ptr< Context > context
static std::optional< APSInt > getConstant(Attribute operand)
Determine the value of a constant operand for the sake of constant folding.
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
static Value lowerFullyAssociativeOp(Operation &op, OperandRange operands, SmallVector< Operation * > &newOps)
Lower a variadic fully-associative operation into an expression tree.
create(data_type, value)
Definition hw.py:444
create(data_type, value)
Definition hw.py:433
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition HWTypes.cpp:122
void populateVariadicXorInverterLoweringPatterns(mlir::RewritePatternSet &patterns)
void populateVariadicAndInverterLoweringPatterns(mlir::RewritePatternSet &patterns)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
static OpTy replaceOpWithNewOpAndCopyNamehint(PatternRewriter &rewriter, Operation *op, Args &&...args)
A wrapper of PatternRewriter::replaceOpWithNewOp to propagate "sv.namehint" attribute.
Definition Naming.h:41
void replaceOpAndCopyNamehint(PatternRewriter &rewriter, Operation *op, Value newValue)
A wrapper of PatternRewriter::replaceOp to propagate "sv.namehint" attribute.
Definition Naming.cpp:73
Definition comb.py:1