-
Notifications
You must be signed in to change notification settings - Fork 366
[Datapath] Operator definitions and canonicalization patterns #8647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… builder and assemblyFormat
…explicit reduction factor
…ingle compressor tree and removing zeros
let arguments = (ins Variadic<HWIntegerType>:$inputs); | ||
let results = (outs Variadic<HWIntegerType>:$results); | ||
|
||
let hasCustomAssemblyFormat = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use normal assembly format if possible for maintainability? I think functional-type is not that bad or you can also use custom-directives to print operand types nicely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it really valuable to see to make it easy to count how many rows were in my compressor - will revisit if custom directives can achieve this - using functional-type involved a lot of counting (if I recall)
PatternRewriter &rewriter) const override { | ||
// Get operands of the AddOp | ||
auto operands = compOp.getOperands(); | ||
SmallVector<Value, 8> processedCompressorResults; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you use SmallSetVector and replace llvm::is_contained
with SmallSetVector::contains
? That should be as efficient as SmallVector + llvm::is_contained for small sizes and robust for large sets.
} | ||
|
||
// Construct partial product array from two operands | ||
def PartialProductOp : DatapathOp<"pp", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: can we use more verbose name than pp
such as partial_prod
or partial_product
?
The first step in a multiplication is to generate partial products, which | ||
when summed, yield the product of the two operands. The partial | ||
product operator does not specify an implementation, only that summing the | ||
results will yield the product of the two operands. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you briefly describe the semantics of op here? Especially correspondence of number/bit position, column/row and operands.
// pp(concat(0,a), concat(0,b)) -> reduce number of results | ||
for (Value operand : operands) { | ||
// If the extracted bits are all known, then return the result. | ||
auto knownBits = comb::computeKnownBits(operand); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not blocking but computeKnownBits is very expensive API (#4690) to use in canonicalizer so we might need to revisit this later.
Have now updated the datapath operator formatting to avoid custom assembly formats |
@@ -23,5 +23,85 @@ include "circt/Dialect/HW/HWTypes.td" | |||
class DatapathOp<string mnemonic, list<Trait> traits = []> : | |||
Op<DatapathDialect, mnemonic, traits>; | |||
|
|||
//===----------------------------------------------------------------------===// | |||
|
|||
// Compress an array of bitvectors to a smaller set of bitvectors (at least 2). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: why not add this to the description (thus also the documentation on the website) instead?
|
||
// Compress an array of bitvectors to a smaller set of bitvectors (at least 2). | ||
def CompressOp : DatapathOp<"compress", | ||
[Pure, SameTypeOperands, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super-nit: this indentation looks a bit weird
let results = (outs Variadic<HWIntegerType>:$results); | ||
|
||
let assemblyFormat = | ||
"$inputs attr-dict `:` custom<CompressFormat>(type($inputs), type($results))"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
80 cols?
]; | ||
} | ||
|
||
// Construct partial product array from two operands |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: comment seems redundant with the summary
and could thus be removed
|
||
// Construct partial product array from two operands | ||
def PartialProductOp : DatapathOp<"partial_product", | ||
[Pure, SameTypeOperands, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: weird indentation
|
||
// let hasCustomAssemblyFormat = true; | ||
let assemblyFormat = | ||
"$multiplicand `,` $multiplier attr-dict `:` functional-type(operands, results)"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
80 cols?
lib/Dialect/Datapath/DatapathOps.cpp
Outdated
if (getNumOperands() < 3) | ||
return emitOpError("Requires 3 or more arguments - otherwise use add"); | ||
|
||
if (getNumResults() >= getNumOperands()) | ||
return emitOpError("Must reduce the number of operands by at least 1"); | ||
|
||
if (getNumResults() < 2) | ||
return emitOpError("Must produce at least 2 results"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the consensus is that these error strings shouldn't start with an uppercase letter (because it's not the start of a sentence and not even the start of the whole error message)
|
||
Example: | ||
```mlir | ||
%0:2 = datapath.compress %a, %b, %c : 3 x i16 -> (i16, i16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since all operands and result types are always the same, why is this not just
%0:2 = datapath.compress %a, %b, %c : 3 x i16 -> (i16, i16) | |
%0:2 = datapath.compress %a, %b, %c : i16 |
It would remove alll the redundant information and the entire custom parser and printer could go away. It would also be more consistent with how things are done in other CIRCT dialects (e.g. most comb
ops).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My ideal format would be
%0:2 = datapath.compress %a, %b, %c : 3 x i16
Reasoning - when these compressors become large - it is valuable to be able to quickly read off how many rows the compressor is summing - without this annotation you need to count the number of arguments
Problems: have had great difficulties understanding how assemblyFormat works... So if anyone is able to help achieve the above that would be super helpful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd argue that you can easily use your editor/IDE (e.g., vim) to give you this information by counting the number of %
minus 1 in that line instead of annotating these things in the IR.
If you really want this format, you can significantly reduce what the custom directive is doing. It should just print num-operands x
and in the parser it should parse a number and x
and just throw it away as it is redundant anyway. The operands and type should be part of the assemblyFormat
not the directive and with the right traits (the ones you already added should be enough) it will automatically infer the other operands and all result types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maerhart - do you have an example of how to print type($inputs[0[) as the comb example just uses the result - which in this case we have variadic inputs and outputs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sorry! This is trickier than I thought. One way I have seen people work around this is to define one extra operand or result outside of the variadic and use that as the anchor for the type but that leads to other annoyances, so I'd probably rather avoid that and pay the cost of the custom parser/printer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's also no way to get the number of results from the %0:2 =
prefix (which I guess is the reason you kept the (i16, i16)
). So I guess there are two options:
- Custom parser/printer for something like
%0:2 = datapath.compress %a, %b, %c : 2 x i16
- Use the ODS
functional-type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok how about the following that just involves one custom-directive and prints the type only once - I accept there is replication in the 2 but given the additional code required to get %0:2 this is perhaps more maintainable?
%0:2 = datapath.compress %a, %b, %c : i16 [3 -> 2]
test/Dialect/Datapath/errors.mlir
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to there regression tests for the errors, we also have at least one test per operation (often in a file called basic.mlir
) where circt-opt is only invoked with the round-trip option.
} | ||
}; | ||
|
||
struct FoldAddIntoCompress : public OpRewritePattern<comb::AddOp> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: it often helps readability a lot to add a typical example of an application of a canonicalization to the docsting of the pattern struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have added comments of basic examples and where we may have mutliple folds e.g. in Compress constant fold - have mimicked the CombFolds commenting style
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than several nits 👍
|
||
// Only fold if we have constructed a larger compressor than what was | ||
// already there | ||
if (!(shouldFold)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!(shouldFold)) | |
if (!shouldFold) |
test/Dialect/Datapath/basic.mlir
Outdated
// CHECK-NEXT: datapath.partial_product %a, %b : (i3, i3) -> (i3, i3, i3) | ||
%0:3 = datapath.partial_product %a, %b : (i3, i3) -> (i3, i3, i3) | ||
hw.output %0#0, %0#1, %0#2 : i3, i3, i3 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: insert new line
} | |
} | |
test/Dialect/Datapath/basic.mlir
Outdated
@@ -0,0 +1,15 @@ | |||
// RUN: circt-opt %s | circt-opt | FileCheck %s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: You ca ncheck with -verify-roundtrip
// RUN: circt-opt %s | circt-opt | FileCheck %s | |
// RUN: circt-opt %s -verify-roundtrip| FileCheck %s |
auto newCompressOp = rewriter.create<CompressOp>( | ||
op.getLoc(), inputs.drop_back(), op.getNumResults()); | ||
|
||
rewriter.replaceOp(op, newCompressOp.getResults()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work?
auto newCompressOp = rewriter.create<CompressOp>( | |
op.getLoc(), inputs.drop_back(), op.getNumResults()); | |
rewriter.replaceOp(op, newCompressOp.getResults()); | |
rewriter.replaceOpWithNewOp<CompressOp>( | |
op, inputs.drop_back(), op.getNumResults()); |
while (newResults.size() < op.getNumResults()) | ||
newResults.push_back(zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while (newResults.size() < op.getNumResults()) | |
newResults.push_back(zero); | |
newResults.append(op.getNumResults() - newResults.size(), zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this - was looking for a neater way to do this!
//===----------------------------------------------------------------------===// | ||
// Partial Product Operation | ||
//===----------------------------------------------------------------------===// | ||
struct ConstantFoldPartialProduct : public OpRewritePattern<PartialProductOp> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This canonicalization pattern looks like width narrowing. MLIR has folder API for constant folding so can we rename this pattern to avoid confusion?
Have now addressed the nits and still awaiting commit access approval so will need someone else to merge for me if satisfied please? |
Building on the boiler plate definition adding two operators
datapath.compress
- compressor tree circuitdatapath.partial_product
- partial product generation circuitThe key idea is to view datapath operators as generators of circuits that satisfy some contract, for example in the case of the
datapath.compress
summing it's results is equivalent to summing it's inputs. This allows us to defer implementing these critical circuits until later in the synthesis flow.In a simple example, we can fold a*b+c using the datapath dialect to remove a carry-propagate adder:
Which is equivalent to:
This is the first in a series of PRs to add datapath synthesis capabilities that will include the following (implemented but not merged):