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