[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:
Hideto Ueno 2024-10-26 17:45:29 +09:00 committed by GitHub
parent 7195bf3450
commit c2c00c69e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 271 additions and 0 deletions

View File

@ -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

151
lib/Dialect/AIG/AIGOps.cpp Normal file
View File

@ -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;
}

View File

@ -1,5 +1,6 @@
add_circt_dialect_library(CIRCTAIG
AIGDialect.cpp
AIGOps.cpp
ADDITIONAL_HEADER_DIRS
${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/AIG

View File

@ -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
}

View File

@ -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
}