CIRCT 24.0.0git
Loading...
Searching...
No Matches
DatapathFolds.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
13#include "mlir/IR/Matchers.h"
14#include "mlir/IR/PatternMatch.h"
15#include "llvm/Support/Casting.h"
16#include "llvm/Support/KnownBits.h"
17#include <algorithm>
18
19using namespace mlir;
20using namespace circt;
21using namespace datapath;
22using namespace matchers;
23
24//===----------------------------------------------------------------------===//
25// Utility Functions
26//===----------------------------------------------------------------------===//
27static FailureOr<size_t> calculateNonZeroBits(Value operand,
28 size_t numResults) {
29 // If the extracted bits are all known, then return the result.
30 auto knownBits = comb::computeKnownBits(operand);
31 if (knownBits.isUnknown())
32 return failure(); // Skip if we don't know anything about the bits
33
34 size_t nonZeroBits = operand.getType().getIntOrFloatBitWidth() -
35 knownBits.Zero.countLeadingOnes();
36
37 // If all bits non-zero we will not reduce the number of results
38 if (nonZeroBits == numResults)
39 return failure();
40
41 return nonZeroBits;
42}
43
44// zext(input<<trailingZeros) to targetWidth
45static Value zeroPad(PatternRewriter &rewriter, Location loc, Value input,
46 size_t targetWidth, size_t trailingZeros) {
47 assert(trailingZeros > 0 && "zeroPad called with zero trailing zeros");
48 auto trailingZerosValue =
49 hw::ConstantOp::create(rewriter, loc, APInt::getZero(trailingZeros));
50 auto padTrailing = comb::ConcatOp::create(
51 rewriter, loc, ValueRange{input, trailingZerosValue});
52 return comb::createZExt(rewriter, loc, padTrailing, targetWidth);
53}
54
55//===----------------------------------------------------------------------===//
56// Compress Operation
57//===----------------------------------------------------------------------===//
58// Check that all compressor results are included in this list of operands
59// If not we must take care as manipulating compressor results independently
60// could easily introduce a non-equivalent representation.
61static bool areAllCompressorResultsSummed(ValueRange compressResults,
62 ValueRange operands) {
63 for (auto result : compressResults) {
64 if (!llvm::is_contained(operands, result))
65 return false;
66 }
67 return true;
68}
69
71 : public OpRewritePattern<datapath::CompressOp> {
72 using OpRewritePattern::OpRewritePattern;
73
74 // compress(compress(a,b,c), add(e,f)) -> compress(a,b,c,e,f)
75 LogicalResult matchAndRewrite(datapath::CompressOp compOp,
76 PatternRewriter &rewriter) const override {
77 auto operands = compOp.getOperands();
78 llvm::SmallSetVector<Value, 8> processedCompressorResults;
79 SmallVector<Value, 8> newCompressOperands;
80
81 for (Value operand : operands) {
82
83 // Skip if already processed this compressor
84 if (processedCompressorResults.contains(operand))
85 continue;
86
87 // If the operand has multiple uses, we do not fold it into a compress
88 // operation, so we treat it as a regular operand to maintain sharing.
89 if (!operand.hasOneUse()) {
90 newCompressOperands.push_back(operand);
91 continue;
92 }
93
94 // Found a compress op - add its operands to our new list
95 if (auto compressOp = operand.getDefiningOp<datapath::CompressOp>()) {
96
97 // Check that all results of the compressor are summed in this add
98 if (!areAllCompressorResultsSummed(compressOp.getResults(), operands))
99 return failure();
100
101 llvm::append_range(newCompressOperands, compressOp.getOperands());
102 // Only process each compressor once as multiple operands will point
103 // to the same defining operation
104 processedCompressorResults.insert(compressOp.getResults().begin(),
105 compressOp.getResults().end());
106 continue;
107 }
108
109 if (auto addOp = operand.getDefiningOp<comb::AddOp>()) {
110 llvm::append_range(newCompressOperands, addOp.getOperands());
111 continue;
112 }
113
114 // Regular operand - just add it to our list
115 newCompressOperands.push_back(operand);
116 }
117
118 // If unable to collect more operands then this pattern doesn't apply
119 if (newCompressOperands.size() <= compOp.getNumOperands())
120 return failure();
121
122 // Create a new CompressOp with all collected operands
123 rewriter.replaceOpWithNewOp<datapath::CompressOp>(
124 compOp, newCompressOperands, compOp.getNumResults());
125 return success();
126 }
127};
128
129struct FoldAddIntoCompress : public OpRewritePattern<comb::AddOp> {
130 using OpRewritePattern::OpRewritePattern;
131
132 // add(compress(a,b,c),d) -> add(compress(a,b,c,d))
133 // FIXME: This should be implemented as a canonicalization pattern for
134 // compress op. Currently `hasDatapathOperand` flag prevents introducing
135 // datapath operations from comb operations.
136 LogicalResult matchAndRewrite(comb::AddOp addOp,
137 PatternRewriter &rewriter) const override {
138 // comb.add canonicalization patterns handle folding add operations
139 if (addOp.getNumOperands() <= 2)
140 return failure();
141
142 // Get operands of the AddOp
143 auto operands = addOp.getOperands();
144 llvm::SmallSetVector<Value, 8> processedCompressorResults;
145 SmallVector<Value, 8> newCompressOperands;
146 // Only construct compressor if can form a larger compressor than what
147 // is currently an input of this add. Also check that there is at least
148 // one datapath operand.
149 bool shouldFold = false, hasDatapathOperand = false;
150
151 for (Value operand : operands) {
152
153 // Skip if already processed this compressor
154 if (processedCompressorResults.contains(operand))
155 continue;
156
157 if (auto *op = operand.getDefiningOp())
158 if (isa_and_nonnull<datapath::DatapathDialect>(op->getDialect()))
159 hasDatapathOperand = true;
160
161 // If the operand has multiple uses, we do not fold it into a compress
162 // operation, so we treat it as a regular operand.
163 if (!operand.hasOneUse()) {
164 shouldFold |= !newCompressOperands.empty();
165 newCompressOperands.push_back(operand);
166 continue;
167 }
168
169 // Found a compress op - add its operands to our new list
170 if (auto compressOp = operand.getDefiningOp<datapath::CompressOp>()) {
171
172 // Check that all results of the compressor are summed in this add
173 if (!areAllCompressorResultsSummed(compressOp.getResults(), operands))
174 return failure();
175
176 // If we've already added one operand it should be folded
177 shouldFold |= !newCompressOperands.empty();
178 llvm::append_range(newCompressOperands, compressOp.getOperands());
179 // Only process each compressor once
180 processedCompressorResults.insert(compressOp.getResults().begin(),
181 compressOp.getResults().end());
182 continue;
183 }
184
185 if (auto addOp = operand.getDefiningOp<comb::AddOp>()) {
186 shouldFold |= !newCompressOperands.empty();
187 llvm::append_range(newCompressOperands, addOp.getOperands());
188 continue;
189 }
190
191 // Regular operand - just add it to our list
192 shouldFold |= !newCompressOperands.empty();
193 newCompressOperands.push_back(operand);
194 }
195
196 // Only fold if we have constructed a larger compressor than what was
197 // already there
198 if (!shouldFold || !hasDatapathOperand)
199 return failure();
200
201 // Create a new CompressOp with all collected operands
202 auto newCompressOp = datapath::CompressOp::create(rewriter, addOp.getLoc(),
203 newCompressOperands, 2);
204
205 // Replace the original AddOp with a new add(compress(inputs))
206 rewriter.replaceOpWithNewOp<comb::AddOp>(addOp, newCompressOp.getResults(),
207 true);
208 return success();
209 }
210};
211
212// compress(..., sext(x),...) ->
213// compress(..., zext({~x[p-1], x[p-2:0]}), (-1) << (width(x)-1), ...)
214// Justification:
215// sext(x) = {x[p-1], x[p-1], ..., x[p-1], x[p-2], ..., x[0]} =
216// = { 0, 0, ..., ~x[p-1], x[p-2], ..., x[0]} +
217// { 1, 1, ..., 1, 0, ..., 0} =
218// = zext({~x[p-1], x[p-2], ..., x[0]}) + ((-1) << (width(x)-1))
219//
220// More generally, for an arbitrary replicated prefix:
221// {r, r, ..., r, x} = zext({~r, x}) + ((-1) << width(x))
222//
223// Note that we are adding arguments to the compressor, but we are reducing the
224// number of unknown bits in the compressor array
225struct SextCompress : public OpRewritePattern<CompressOp> {
226 using OpRewritePattern::OpRewritePattern;
227
228 LogicalResult matchAndRewrite(CompressOp op,
229 PatternRewriter &rewriter) const override {
230 auto inputs = op.getInputs();
231 auto opSize = inputs[0].getType().getIntOrFloatBitWidth();
232 auto size = inputs.size();
233
234 SmallVector<Value> newInputs;
235 for (auto input : inputs) {
236 Value replBits;
237 bool isSext = matchPattern(input, comb::m_SextBy(m_Any(&replBits)));
238 if (!isSext && !matchPattern(input, comb::m_ReplExt(m_Any(&replBits)))) {
239 newInputs.push_back(input);
240 continue;
241 }
242 auto baseWidth = opSize - replBits.getType().getIntOrFloatBitWidth();
243
244 // The replicated prefix must contain at least two bits to be beneficial.
245 if (baseWidth <= 1 || (opSize - baseWidth) <= 1) {
246 newInputs.push_back(input);
247 continue;
248 }
249
250 // For a sign extension, the repeated bit is already the most significant
251 // bit of the base. For a general replicated extension, preserve the
252 // entire base and get the repeated bit from the replicate operation.
253 auto correctionBit = isSext ? baseWidth - 1 : baseWidth;
254 auto base = comb::ExtractOp::create(rewriter, op.getLoc(), input, 0,
255 correctionBit);
256 Value repeatedBit = comb::ExtractOp::create(rewriter, op.getLoc(), input,
257 correctionBit, 1);
258 auto invRepeatedBit =
259 comb::createOrFoldNot(rewriter, op.getLoc(), repeatedBit, true);
260 // Replace the repeated prefix zeros and a constant correction.
261 auto newOp = comb::ConcatOp::create(rewriter, op.getLoc(),
262 ValueRange{invRepeatedBit, base});
263 auto newOpZExt = comb::createZExt(rewriter, op.getLoc(), newOp, opSize);
264
265 newInputs.push_back(newOpZExt);
266
267 // Constant correction (-1) << width (-1)
268 auto ones = APInt::getAllOnes(opSize);
269 auto correction =
270 hw::ConstantOp::create(rewriter, op.getLoc(), ones << correctionBit);
271
272 newInputs.push_back(correction);
273 }
274
275 // If no sext inputs have not updated any arguments
276 if (newInputs.size() == size)
277 return failure();
278
279 auto newCompress = datapath::CompressOp::create(
280 rewriter, op.getLoc(), newInputs, op.getNumResults());
281 rewriter.replaceOp(op, newCompress.getResults());
282 return success();
283 }
284};
285
286struct ConstantFoldCompress : public OpRewritePattern<CompressOp> {
287 using OpRewritePattern::OpRewritePattern;
288
289 LogicalResult matchAndRewrite(CompressOp op,
290 PatternRewriter &rewriter) const override {
291 auto inputs = op.getInputs();
292 auto size = inputs.size();
293
294 APInt value;
295
296 // compress(..., 0) -> compress(...) -- identity
297 if (matchPattern(inputs.back(), m_ConstantInt(&value)) && value.isZero()) {
298
299 // If only reducing by one row and contains zero - pass through operands
300 if (size - 1 == op.getNumResults()) {
301 rewriter.replaceOp(op, inputs.drop_back());
302 return success();
303 }
304
305 // Default create a compressor with fewer arguments
306 rewriter.replaceOpWithNewOp<CompressOp>(op, inputs.drop_back(),
307 op.getNumResults());
308 return success();
309 }
310
311 APInt value1, value2;
312 // compress(...c1, c2) -> compress(..., c1+c2)
313 assert(size >= 3 &&
314 "compress op has 3 or more operands ensured by a verifier");
315 if (matchPattern(inputs.back(), m_ConstantInt(&value1)) &&
316 matchPattern(inputs[size - 2], m_ConstantInt(&value2))) {
317
318 SmallVector<Value> newInputs(inputs.drop_back(2));
319 auto summedValue = value1 + value2;
320 auto constOp = hw::ConstantOp::create(rewriter, op.getLoc(), summedValue);
321 newInputs.push_back(constOp);
322 // If reducing by one row and constant folding - pass through operands
323 if (size - 1 == op.getNumResults()) {
324 rewriter.replaceOp(op, newInputs);
325 return success();
326 }
327
328 // Default create a compressor with fewer arguments
329 rewriter.replaceOpWithNewOp<CompressOp>(op, newInputs,
330 op.getNumResults());
331 return success();
332 }
333
334 return failure();
335 }
336};
337
338void CompressOp::getCanonicalizationPatterns(RewritePatternSet &results,
339 MLIRContext *context) {
342}
343
344//===----------------------------------------------------------------------===//
345// Partial Product Operation
346//===----------------------------------------------------------------------===//
347struct ReduceNumPartialProducts : public OpRewritePattern<PartialProductOp> {
348 using OpRewritePattern::OpRewritePattern;
349
350 // pp(concat(0,a), concat(0,b)) -> reduced number of results
351 LogicalResult matchAndRewrite(PartialProductOp op,
352 PatternRewriter &rewriter) const override {
353 auto operands = op.getOperands();
354 unsigned inputWidth = operands[0].getType().getIntOrFloatBitWidth();
355
356 // TODO: implement a constant multiplication for the PartialProductOp
357
358 auto op0NonZeroBits = calculateNonZeroBits(operands[0], op.getNumResults());
359 auto op1NonZeroBits = calculateNonZeroBits(operands[1], op.getNumResults());
360
361 if (failed(op0NonZeroBits) || failed(op1NonZeroBits))
362 return failure();
363
364 // Need the +1 for the carry-out
365 size_t maxNonZeroBits = std::max(*op0NonZeroBits, *op1NonZeroBits);
366
367 auto newPP = datapath::PartialProductOp::create(
368 rewriter, op.getLoc(), op.getOperands(), maxNonZeroBits);
369
370 auto zero = hw::ConstantOp::create(rewriter, op.getLoc(),
371 APInt::getZero(inputWidth));
372
373 // Collect newPP results and pad with zeros if needed
374 SmallVector<Value> newResults(newPP.getResults().begin(),
375 newPP.getResults().end());
376
377 newResults.append(op.getNumResults() - newResults.size(), zero);
378
379 rewriter.replaceOp(op, newResults);
380 return success();
381 }
382};
383
384struct SignedPartialProducts : public OpRewritePattern<PartialProductOp> {
385 using OpRewritePattern::OpRewritePattern;
386
387 // Based on the classical Baugh-Wooley algorithm for signed mulitplication.
388 // Paper: A Two's Complement Parallel Array Multiplication Algorithm
389 //
390 // Consider a p-bit by q-bit signed multiplier - producing a (p+q)-bit result:
391 // a_sign = a[p-1], a_mag = a[p-2:0],
392 // b_sign = b[q-1], b_mag = b[q-2:0]
393 // sext(a) * sext(b) = a_mag * b_mag [unsigned product]
394 // - 2^(p-1) * a_sign * b_mag [sign correction]
395 // - 2^(q-1) * b_sign * a_mag [sign correction]
396 // + 2^(p+q-2) * a_sign * b_sign [sign * sign]
397 //
398 // We implement optimizations to turn the subtractions into bitwise
399 // negations with constant corrections that can be folded together.
400 LogicalResult matchAndRewrite(PartialProductOp op,
401 PatternRewriter &rewriter) const override {
402 // Booth encoding will automatically handle signed multiplications
403 auto isSquarer = op.getLhs() == op.getRhs();
404 if (comb::shouldUseBoothEncoding(op.getLhs(), op.getRhs()) && !isSquarer)
405 return failure();
406
407 auto inputWidth = op.getLhs().getType().getIntOrFloatBitWidth();
408 Value lhsReplBits;
409 Value rhsReplBits;
410 if (!matchPattern(op.getLhs(), comb::m_SextBy(m_Any(&lhsReplBits))) ||
411 !matchPattern(op.getRhs(), comb::m_SextBy(m_Any(&rhsReplBits))))
412 return failure();
413
414 size_t lhsWidth =
415 inputWidth - lhsReplBits.getType().getIntOrFloatBitWidth();
416 size_t rhsWidth =
417 inputWidth - rhsReplBits.getType().getIntOrFloatBitWidth();
418 // Subtract 1 as will handle sign-bit separately
419 size_t maxRows = std::max(lhsWidth, rhsWidth) - 1;
420
421 // TODO: add support for different width inputs
422 // Need to have a sign bit in both inputs
423 if (lhsWidth != rhsWidth || lhsWidth <= 1 || rhsWidth <= 1)
424 return failure();
425
426 // No further reduction possible
427 if (maxRows >= op.getNumResults())
428 return failure();
429
430 // Pull off the sign bits
431 auto lhsBaseWidth = lhsWidth - 1;
432 auto rhsBaseWidth = rhsWidth - 1;
433 auto lhsSignBit = comb::ExtractOp::create(rewriter, op.getLoc(),
434 op.getLhs(), lhsBaseWidth, 1);
435 auto rhsSignBit = comb::ExtractOp::create(rewriter, op.getLoc(),
436 op.getRhs(), rhsBaseWidth, 1);
437 auto lhsBase = comb::ExtractOp::create(rewriter, op.getLoc(), op.getLhs(),
438 0, lhsBaseWidth);
439 auto rhsBase = comb::ExtractOp::create(rewriter, op.getLoc(), op.getRhs(),
440 0, rhsBaseWidth);
441
442 // Create the unsigned partial product of the unextended inputs
443 auto lhsBaseZext =
444 comb::createZExt(rewriter, op.getLoc(), lhsBase, inputWidth);
445 auto rhsBaseZext =
446 comb::createZExt(rewriter, op.getLoc(), rhsBase, inputWidth);
447 auto newPP = datapath::PartialProductOp::create(
448 rewriter, op.getLoc(), ValueRange{lhsBaseZext, rhsBaseZext}, maxRows);
449
450 // Optimization (similar for second sign correction), ext to (p+q)-bits:
451 // -2^(p-1)*sign(lhs)*rhsBase = ~((sign(lhs) * rhsBase) << (p-1)) + 1
452 // = (~(replicate(sign(lhs)) & rhsBase)) << (p-1)
453 // + (-1) << (p+q-2) [msb correction]
454 // + (1<<(p-1)) - 1 + 1 [lsb correction]
455
456 // Create ~(replicate(sign(lhs)) & rhsBase)
457 auto lhsSignReplicate = comb::ReplicateOp::create(rewriter, op.getLoc(),
458 lhsSignBit, rhsBaseWidth);
459 auto lhsSignAndRhs =
460 comb::AndOp::create(rewriter, op.getLoc(), lhsSignReplicate, rhsBase);
461 auto lhsSignCorrection =
462 comb::createOrFoldNot(rewriter, op.getLoc(), lhsSignAndRhs, true);
463
464 // zext({lhsSignCorrection, lhsBaseWidth{1'b0}})
465 auto alignLhsSignCorrection = zeroPad(
466 rewriter, op.getLoc(), lhsSignCorrection, inputWidth, lhsBaseWidth);
467
468 // Create ~(replicate(sign(rhs)) & lhsBase)
469 auto rhsSignReplicate = comb::ReplicateOp::create(rewriter, op.getLoc(),
470 rhsSignBit, lhsBaseWidth);
471 auto rhsSignAndLhs =
472 comb::AndOp::create(rewriter, op.getLoc(), rhsSignReplicate, lhsBase);
473 auto rhsSignCorrection =
474 comb::createOrFoldNot(rewriter, op.getLoc(), rhsSignAndLhs, true);
475
476 // zext({rhsSignCorrection, rhsBaseWidth{1'b0}})
477 auto alignRhsSignCorrection = zeroPad(
478 rewriter, op.getLoc(), rhsSignCorrection, inputWidth, rhsBaseWidth);
479
480 // 2^(p+q-2) * sign(lhs) * sign(rhs) = (sign(lhs) & sign(rhs)) << (p+q-2)
481 // Create sign(lhs) & sign(rhs)
482 auto signAnd =
483 comb::AndOp::create(rewriter, op.getLoc(), lhsSignBit, rhsSignBit);
484 // zext({sign(lhs) & sign(rhs), lhsBaseWidth+rhsBaseWidth{1'b0}})
485 auto alignSignAndZext = zeroPad(rewriter, op.getLoc(), signAnd, inputWidth,
486 lhsBaseWidth + rhsBaseWidth);
487
488 // Gather constant corrections together (once for each sign correction):
489 // (-1) << (p+q-2) + (1<<(p-1)) - 1 + 1
490 auto ones = APInt::getAllOnes(inputWidth);
491 auto lowerLhs = APInt::getOneBitSet(inputWidth, lhsBaseWidth);
492 auto lowerRhs = APInt::getOneBitSet(inputWidth, rhsBaseWidth);
493 auto msbCorrection = ones << (lhsBaseWidth + rhsBaseWidth);
494 auto correction = lowerLhs + lowerRhs + 2 * msbCorrection;
495
496 auto constantCorrection =
497 hw::ConstantOp::create(rewriter, op.getLoc(), correction);
498
499 auto zero = hw::ConstantOp::create(rewriter, op.getLoc(),
500 APInt::getZero(inputWidth));
501 // Collect newPP results and pad with zeros if needed
502 SmallVector<Value> newResults(newPP.getResults().begin(),
503 newPP.getResults().end());
504
505 // ~(replicate(sign(lhs)) & rhsBase) * 2^(p-1)
506 newResults.push_back(alignLhsSignCorrection);
507 // ~(replicate(sign(rhs)) & lhsBase) * 2^(q-1)
508 newResults.push_back(alignRhsSignCorrection);
509 // sign(lhs)*sign(rhs) * 2^(p+q-2)
510 newResults.push_back(alignSignAndZext);
511 // Constant correction
512 newResults.push_back(constantCorrection);
513 // Zero pad if necessary
514 newResults.append(op.getNumResults() - newResults.size(), zero);
515
516 rewriter.replaceOp(op, newResults);
517 return success();
518 }
519};
520
521struct PosPartialProducts : public OpRewritePattern<PartialProductOp> {
522 using OpRewritePattern::OpRewritePattern;
523
524 // pp(add(a,b),c) -> pos_pp(a,b,c)
525 LogicalResult matchAndRewrite(PartialProductOp op,
526 PatternRewriter &rewriter) const override {
527 auto width = op.getType(0).getIntOrFloatBitWidth();
528
529 assert(op.getNumOperands() == 2);
530
531 // Detect if any input is an AddOp
532 auto lhsAdder = op.getOperand(0).getDefiningOp<comb::AddOp>();
533 auto rhsAdder = op.getOperand(1).getDefiningOp<comb::AddOp>();
534 if ((lhsAdder && rhsAdder) || !(lhsAdder || rhsAdder))
535 return failure();
536 auto addInput = lhsAdder ? lhsAdder : rhsAdder;
537 auto otherInput = lhsAdder ? op.getOperand(1) : op.getOperand(0);
538
539 if (addInput->getNumOperands() != 2)
540 return failure();
541
542 Value addend0 = addInput->getOperand(0);
543 Value addend1 = addInput->getOperand(1);
544
545 rewriter.replaceOpWithNewOp<PosPartialProductOp>(
546 op, ValueRange{addend0, addend1, otherInput}, width);
547 return success();
548 }
549};
550
551void PartialProductOp::getCanonicalizationPatterns(RewritePatternSet &results,
552 MLIRContext *context) {
553 results
555 context);
556}
557
558//===----------------------------------------------------------------------===//
559// Pos Partial Product Operation
560//===----------------------------------------------------------------------===//
562 : public OpRewritePattern<PosPartialProductOp> {
563 using OpRewritePattern::OpRewritePattern;
564
565 // pos_pp(concat(0,a), concat(0,b), c) -> reduced number of results
566 LogicalResult matchAndRewrite(PosPartialProductOp op,
567 PatternRewriter &rewriter) const override {
568 unsigned inputWidth = op.getAddend0().getType().getIntOrFloatBitWidth();
569 auto addend0NonZero =
570 calculateNonZeroBits(op.getAddend0(), op.getNumResults());
571 auto addend1NonZero =
572 calculateNonZeroBits(op.getAddend1(), op.getNumResults());
573
574 if (failed(addend0NonZero) || failed(addend1NonZero))
575 return failure();
576
577 // Need the +1 for the carry-out
578 size_t maxNonZeroBits = std::max(*addend0NonZero, *addend1NonZero) + 1;
579
580 if (maxNonZeroBits >= op.getNumResults())
581 return failure();
582
583 auto newPP = datapath::PosPartialProductOp::create(
584 rewriter, op.getLoc(), op.getOperands(), maxNonZeroBits);
585
586 auto zero = hw::ConstantOp::create(rewriter, op.getLoc(),
587 APInt::getZero(inputWidth));
588
589 // Collect newPP results and pad with zeros if needed
590 SmallVector<Value> newResults(newPP.getResults().begin(),
591 newPP.getResults().end());
592
593 newResults.append(op.getNumResults() - newResults.size(), zero);
594
595 rewriter.replaceOp(op, newResults);
596 return success();
597 }
598};
599
600struct SignedPosPartialProducts : public OpRewritePattern<PosPartialProductOp> {
601 using OpRewritePattern::OpRewritePattern;
602
603 // Inspired by the classical Baugh-Wooley algorithm for signed mulitplication.
604 // Paper: A Two's Complement Parallel Array Multiplication Algorithm
605 //
606 // Consider a p-bit signed pos - producing a q-bit result:
607 // a_sign = a[p-1], a_mag = a[p-2:0],
608 // b_sign = b[p-1], b_mag = b[p-2:0]
609 // (sext(a) + sext(b)) * c = (a_mag + b_mag) * c [unsigned pos]
610 // - 2^(p-1) * (a_sign + b_sign) * c [sign correct]
611 //
612 // We implement optimizations to turn the subtractions into bitwise
613 // negations with constant corrections that can be folded together.
614 LogicalResult matchAndRewrite(PosPartialProductOp op,
615 PatternRewriter &rewriter) const override {
616
617 auto a = op.getAddend0();
618 auto b = op.getAddend1();
619 auto c = op.getMultiplicand();
620 auto loc = op.getLoc();
621 auto inputWidth = a.getType().getIntOrFloatBitWidth();
622 Value aReplBits;
623 Value bReplBits;
624 if (!matchPattern(a, comb::m_SextBy(m_Any(&aReplBits))) ||
625 !matchPattern(b, comb::m_SextBy(m_Any(&bReplBits))))
626 return failure();
627
628 size_t aWidth = inputWidth - aReplBits.getType().getIntOrFloatBitWidth();
629 size_t bWidth = inputWidth - bReplBits.getType().getIntOrFloatBitWidth();
630
631 // TODO: add support for different width inputs
632 // Need to have a sign bit in both inputs
633 if (aWidth != bWidth || aWidth <= 1 || bWidth <= 1)
634 return failure();
635
636 // Pull off the sign bits
637 auto baseWidth = aWidth - 1;
638 // No further reduction possible - already reduced to min partial products
639 // Need baseWidth rows + 2 for sign correction and constant correction
640 if (baseWidth + 2 >= op.getNumResults())
641 return failure();
642
643 auto aSign = comb::ExtractOp::create(rewriter, loc, a, baseWidth, 1);
644 auto bSign = comb::ExtractOp::create(rewriter, loc, b, baseWidth, 1);
645 auto aBase = comb::ExtractOp::create(rewriter, loc, a, 0, baseWidth);
646 auto bBase = comb::ExtractOp::create(rewriter, loc, b, 0, baseWidth);
647
648 // Create the unsigned pos partial product of the unextended inputs
649 auto aBaseZext = comb::createZExt(rewriter, loc, aBase, inputWidth);
650 auto bBaseZext = comb::createZExt(rewriter, loc, bBase, inputWidth);
651 auto newPP = datapath::PosPartialProductOp::create(
652 rewriter, loc, ValueRange{aBaseZext, bBaseZext, op.getMultiplicand()},
653 baseWidth);
654
655 // Optimization:
656 // -2^(p-1)*(a_sign + b_sign) * c =
657 // ~(((a_sign & b_sign)*2c | (a_sign ^ b_sign)*c) << (p-1)) + 1
658 // CARRY SAVE
659
660 auto cWidth = c.getType().getIntOrFloatBitWidth();
661 auto carry = rewriter.createOrFold<comb::AndOp>(loc, aSign, bSign);
662 auto save = rewriter.createOrFold<comb::XorOp>(loc, aSign, bSign);
663 auto one = hw::ConstantOp::create(rewriter, loc, APInt(cWidth, 1));
664 auto twoC = rewriter.createOrFold<comb::ShlOp>(loc, c, one);
665 auto replSave = rewriter.createOrFold<comb::ReplicateOp>(loc, save, cWidth);
666 auto replCarry =
667 rewriter.createOrFold<comb::ReplicateOp>(loc, carry, cWidth);
668 auto carryAnd = rewriter.createOrFold<comb::AndOp>(loc, replCarry, twoC);
669 auto saveAnd = rewriter.createOrFold<comb::AndOp>(loc, replSave, c);
670 auto ppRow = rewriter.createOrFold<comb::OrOp>(loc, carryAnd, saveAnd);
671 auto shiftBy =
672 hw::ConstantOp::create(rewriter, loc, APInt(cWidth, baseWidth));
673 auto ppRowShift = rewriter.createOrFold<comb::ShlOp>(loc, ppRow, shiftBy);
674 auto ppRowNot = comb::createOrFoldNot(rewriter, loc, ppRowShift);
675
676 // Collect newPP results and pad with zeros if needed
677 SmallVector<Value> newResults(newPP.getResults().begin(),
678 newPP.getResults().end());
679
680 // Can safely append rows as we know original operation had at least
681 // baseWidth + 2 rows
682 newResults.push_back(ppRowNot);
683 newResults.push_back(one); // Constant correction for the sign correction
684 // Zero pad if necessary
685 auto zero = hw::ConstantOp::create(rewriter, op.getLoc(),
686 APInt::getZero(inputWidth));
687 newResults.append(op.getNumResults() - newResults.size(), zero);
688
689 rewriter.replaceOp(op, newResults);
690 return success();
691 }
692};
693
694void PosPartialProductOp::getCanonicalizationPatterns(
695 RewritePatternSet &results, MLIRContext *context) {
697}
assert(baseType &&"element must be base type")
static bool areAllCompressorResultsSummed(ValueRange compressResults, ValueRange operands)
static FailureOr< size_t > calculateNonZeroBits(Value operand, size_t numResults)
static Value zeroPad(PatternRewriter &rewriter, Location loc, Value input, size_t targetWidth, size_t trailingZeros)
static std::unique_ptr< Context > context
create(low_bit, result_type, input=None)
Definition comb.py:187
create(data_type, value)
Definition hw.py:433
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
LogicalResult matchAndRewrite(CompressOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(comb::AddOp addOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(datapath::CompressOp compOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(PartialProductOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(PartialProductOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(PosPartialProductOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(CompressOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(PartialProductOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(PosPartialProductOp op, PatternRewriter &rewriter) const override