mirror of https://github.com/llvm/circt.git
[AIG] Add AndInverterOp (#7738)
This commit adds `aig.and_inv` op that represents an And-Inverter in AIG. DenseBoolArray `inverted` field specifies if each operand is inverted. This commit also adds a basic canonicalizer for it.
This commit is contained in:
parent
7195bf3450
commit
c2c00c69e0
|
@ -15,8 +15,73 @@
|
|||
|
||||
include "circt/Dialect/AIG/AIG.td"
|
||||
include "mlir/IR/OpBase.td"
|
||||
include "mlir/Interfaces/InferTypeOpInterface.td"
|
||||
include "mlir/Interfaces/SideEffectInterfaces.td"
|
||||
|
||||
class AIGOp<string mnemonic, list<Trait> traits = []> :
|
||||
Op<AIG_Dialect, mnemonic, traits>;
|
||||
|
||||
def AndInverterOp : AIGOp<"and_inv", [SameOperandsAndResultType, Pure]> {
|
||||
let summary = "AIG dialect AND operation";
|
||||
let description = [{
|
||||
The `aig.and_inv` operation represents an And-Inverter in the AIG dialect.
|
||||
Unlike `comb.and`, operands can be inverted respectively.
|
||||
|
||||
Example:
|
||||
```mlir
|
||||
%r1 = aig.and_inv %a, %b: i3
|
||||
%r2 = aig.and_inv not %a, %b, not %c : i3
|
||||
%r3 = aig.and_inv not %a : i3
|
||||
```
|
||||
|
||||
Traditionally, an And-Node in AIG has two operands. However, `aig.and_inv`
|
||||
extends this concept by allowing variadic operands and non-i1 integer types.
|
||||
Although the final stage of the synthesis pipeline requires lowering
|
||||
everything to i1-binary operands, it's more efficient to progressively lower
|
||||
the variadic multibit operations.
|
||||
|
||||
Variadic operands have demonstrated their utility in low-level optimizations
|
||||
within the `comb` dialect. Furthermore, in synthesis, it's common practice
|
||||
to re-balance the logic path. Variadic operands enable the compiler to
|
||||
select more efficient solutions without the need to traverse binary trees
|
||||
multiple times.
|
||||
|
||||
The ability to represent multibit operations during synthesis is crucial for
|
||||
scalability. This approach enables a form of vectorization, allowing for
|
||||
batch processing of logic synthesis when multibit operations are constructed
|
||||
in a similar manner.
|
||||
}];
|
||||
// TODO: Restrict to HWIntegerType.
|
||||
let arguments = (ins Variadic<AnyType>:$inputs, DenseBoolArrayAttr:$inverted);
|
||||
let results = (outs AnyType:$result);
|
||||
|
||||
// NOTE: Custom assembly format is needed to pretty print the `inverted`
|
||||
// attribute.
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
|
||||
let builders = [
|
||||
OpBuilder<(ins "Value":$input, CArg<"bool", "false">:$invert), [{
|
||||
SmallVector<bool> inverted {invert};
|
||||
return build($_builder, $_state, {input}, inverted);
|
||||
}]>,
|
||||
OpBuilder<(ins "Value":$lhs, "Value":$rhs, CArg<"bool", "false">:$invertLhs,
|
||||
CArg<"bool", "false">:$invertRhs), [{
|
||||
SmallVector<bool> inverted {invertLhs, invertRhs};
|
||||
return build($_builder, $_state, {lhs, rhs}, inverted);
|
||||
}]>
|
||||
];
|
||||
|
||||
let extraClassDeclaration = [{
|
||||
// Evaluate the operation with the given input values.
|
||||
APInt evaluate(ArrayRef<APInt> inputs);
|
||||
|
||||
// Check if the input is inverted.
|
||||
bool isInverted(size_t idx) {
|
||||
return getInverted()[idx];
|
||||
}
|
||||
}];
|
||||
let hasFolder = 1;
|
||||
let hasCanonicalizeMethod = 1;
|
||||
}
|
||||
|
||||
#endif // CIRCT_DIALECT_AIG_OPS_TD
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
//===- AIGOps.cpp - AIG Dialect Operations ----------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implement the AIG ops.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/Dialect/AIG/AIGOps.h"
|
||||
#include "circt/Dialect/HW/HWOps.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace circt;
|
||||
using namespace circt::aig;
|
||||
|
||||
#define GET_OP_CLASSES
|
||||
#include "circt/Dialect/AIG/AIG.cpp.inc"
|
||||
|
||||
OpFoldResult AndInverterOp::fold(FoldAdaptor adaptor) {
|
||||
if (getNumOperands() == 1 && !isInverted(0))
|
||||
return getOperand(0);
|
||||
return {};
|
||||
}
|
||||
|
||||
LogicalResult AndInverterOp::canonicalize(AndInverterOp op,
|
||||
PatternRewriter &rewriter) {
|
||||
SmallDenseMap<Value, bool> seen;
|
||||
SmallVector<Value> uniqueValues;
|
||||
SmallVector<bool> uniqueInverts;
|
||||
|
||||
APInt constValue =
|
||||
APInt::getAllOnes(op.getResult().getType().getIntOrFloatBitWidth());
|
||||
|
||||
bool invertedConstFound = false;
|
||||
size_t numConstInputs = 0;
|
||||
|
||||
for (auto [value, inverted] : llvm::zip(op.getInputs(), op.getInverted())) {
|
||||
if (auto constOp = value.getDefiningOp<hw::ConstantOp>()) {
|
||||
numConstInputs++;
|
||||
if (inverted) {
|
||||
constValue &= ~constOp.getValue();
|
||||
invertedConstFound = true;
|
||||
} else {
|
||||
constValue &= constOp.getValue();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = seen.find(value);
|
||||
if (it == seen.end()) {
|
||||
seen.insert({value, inverted});
|
||||
uniqueValues.push_back(value);
|
||||
uniqueInverts.push_back(inverted);
|
||||
} else if (it->second != inverted) {
|
||||
// replace with const 0
|
||||
rewriter.replaceOpWithNewOp<hw::ConstantOp>(
|
||||
op, APInt::getZero(value.getType().getIntOrFloatBitWidth()));
|
||||
return success();
|
||||
}
|
||||
}
|
||||
|
||||
// If the constant is zero, we can just replace with zero.
|
||||
if (constValue.isZero()) {
|
||||
rewriter.replaceOpWithNewOp<hw::ConstantOp>(op, constValue);
|
||||
return success();
|
||||
}
|
||||
|
||||
// No change.
|
||||
if (uniqueValues.size() == op.getInputs().size() ||
|
||||
(!constValue.isAllOnes() && !invertedConstFound &&
|
||||
uniqueValues.size() + 1 == op.getInputs().size()))
|
||||
return failure();
|
||||
|
||||
if (!constValue.isAllOnes()) {
|
||||
auto constOp = rewriter.create<hw::ConstantOp>(op.getLoc(), constValue);
|
||||
uniqueInverts.push_back(false);
|
||||
uniqueValues.push_back(constOp);
|
||||
}
|
||||
|
||||
// It means the input is reduced to all ones.
|
||||
if (uniqueValues.size() == 0) {
|
||||
rewriter.replaceOpWithNewOp<hw::ConstantOp>(op, constValue);
|
||||
return success();
|
||||
}
|
||||
|
||||
// build new op with reduced input values
|
||||
rewriter.replaceOpWithNewOp<aig::AndInverterOp>(op, uniqueValues,
|
||||
uniqueInverts);
|
||||
return success();
|
||||
}
|
||||
|
||||
ParseResult AndInverterOp::parse(OpAsmParser &parser, OperationState &result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> operands;
|
||||
SmallVector<bool> inverts;
|
||||
auto loc = parser.getCurrentLocation();
|
||||
|
||||
while (true) {
|
||||
inverts.push_back(succeeded(parser.parseOptionalKeyword("not")));
|
||||
operands.push_back(OpAsmParser::UnresolvedOperand());
|
||||
|
||||
if (parser.parseOperand(operands.back()))
|
||||
return failure();
|
||||
if (parser.parseOptionalComma())
|
||||
break;
|
||||
}
|
||||
|
||||
Type type;
|
||||
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||
|
||||
parser.parseCustomTypeWithFallback(type))
|
||||
return failure();
|
||||
|
||||
result.addTypes({type});
|
||||
result.addAttribute("inverted",
|
||||
parser.getBuilder().getDenseBoolArrayAttr(inverts));
|
||||
if (parser.resolveOperands(operands, type, loc, result.operands))
|
||||
return failure();
|
||||
return success();
|
||||
}
|
||||
|
||||
void AndInverterOp::print(OpAsmPrinter &odsPrinter) {
|
||||
odsPrinter << ' ';
|
||||
llvm::interleaveComma(llvm::zip(getInverted(), getInputs()), odsPrinter,
|
||||
[&](auto &&pair) {
|
||||
auto [invert, input] = pair;
|
||||
if (invert) {
|
||||
odsPrinter << "not ";
|
||||
}
|
||||
odsPrinter << input;
|
||||
});
|
||||
odsPrinter.printOptionalAttrDict((*this)->getAttrs(), {"inverted"});
|
||||
odsPrinter << " : " << getResult().getType();
|
||||
}
|
||||
|
||||
APInt AndInverterOp::evaluate(ArrayRef<APInt> inputs) {
|
||||
assert(inputs.size() == getNumOperands() &&
|
||||
"Expected as many inputs as operands");
|
||||
assert(inputs.size() != 0 && "Expected non-empty input list");
|
||||
APInt result = APInt::getAllOnes(inputs.front().getBitWidth());
|
||||
for (auto [idx, input] : llvm::enumerate(inputs)) {
|
||||
if (isInverted(idx))
|
||||
result &= ~input;
|
||||
else
|
||||
result &= input;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
add_circt_dialect_library(CIRCTAIG
|
||||
AIGDialect.cpp
|
||||
AIGOps.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/AIG
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// RUN: circt-opt %s --canonicalize | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: @And
|
||||
hw.module @And(in %a: i4, in %b: i4, out o1: i4, out o2: i4,
|
||||
out o3: i4, out o4: i4, out o5: i4, out o6: i4, out o7: i4,
|
||||
out o8: i4) {
|
||||
// CHECK-NEXT: %c-1_i4 = hw.constant -1 : i4
|
||||
// CHECK-NEXT: %c0_i4 = hw.constant 0 : i4
|
||||
// CHECK-NEXT: %c5_i4 = hw.constant 5 : i4
|
||||
// CHECK-NEXT: %[[TMP1:.+]] = aig.and_inv %a, %c5_i4 : i4
|
||||
// CHECK-NEXT: %[[TMP2:.+]] = aig.and_inv %a, %b : i4
|
||||
// CHECK-NEXT: hw.output %c0_i4, %[[TMP1]], %a, %a, %c0_i4, %[[TMP2]], %c0_i4, %c-1_i4 : i4, i4, i4, i4, i4, i4, i4, i4
|
||||
%c0 = hw.constant 0 : i4
|
||||
%c2 = hw.constant 2 : i4
|
||||
%c7 = hw.constant 7 : i4
|
||||
%c15 = hw.constant 15 : i4
|
||||
|
||||
// a & 0 -> 0
|
||||
%0 = aig.and_inv %a, %c0 : i4
|
||||
|
||||
// a & 7 & ~2 -> a & 5
|
||||
%1 = aig.and_inv %a, %c7, not %c2 : i4
|
||||
|
||||
// a & 15 -> a
|
||||
%2 = aig.and_inv %a, %c15 : i4
|
||||
|
||||
// a & ~0 -> a
|
||||
%3 = aig.and_inv %a, not %c0 : i4
|
||||
|
||||
// a & ~15 -> 0
|
||||
%4 = aig.and_inv %a, not %c15 : i4
|
||||
|
||||
// a & a & b -> a & b
|
||||
%5 = aig.and_inv %a, %a, %b : i4
|
||||
|
||||
// a & ~a & b -> 0
|
||||
%6 = aig.and_inv %a, not %a, %b : i4
|
||||
|
||||
// 15 & 15 -> 15
|
||||
%7 = aig.and_inv %c15, %c15 : i4
|
||||
|
||||
hw.output %0, %1, %2, %3, %4, %5, %6, %7 : i4, i4, i4, i4, i4, i4, i4, i4
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// RUN: circt-opt %s | circt-opt | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: @And
|
||||
// CHECK-NEXT: %[[RES0:.+]] = aig.and_inv %b, %b : i4
|
||||
// CHECK-NEXT: %[[RES1:.+]] = aig.and_inv %b, not %b : i4
|
||||
// CHECK-NEXT: %[[RES2:.+]] = aig.and_inv not %a, not %a : i1
|
||||
hw.module @And(in %a: i1, in %b: i4) {
|
||||
%0 = aig.and_inv %b, %b : i4
|
||||
%1 = aig.and_inv %b, not %b : i4
|
||||
%2 = aig.and_inv not %a, not %a : i1
|
||||
}
|
Loading…
Reference in New Issue