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