diff --git a/include/circt/Dialect/AIG/AIGOps.td b/include/circt/Dialect/AIG/AIGOps.td index b2b668ee9fd4..c9f191784be6 100644 --- a/include/circt/Dialect/AIG/AIGOps.td +++ b/include/circt/Dialect/AIG/AIGOps.td @@ -22,7 +22,7 @@ include "mlir/Interfaces/SideEffectInterfaces.td" class AIGOp traits = []> : Op; -def AndInverterOp : AIGOp<"and_inv", [SameOperandsAndResultType, Pure]> { +def AndInverterOp : AIGOp<"and_inv", [SameOperandsAndResultType, Pure, Commutative]> { let summary = "AIG dialect AND operation"; let description = [{ The `aig.and_inv` operation represents an And-Inverter in the AIG dialect. diff --git a/include/circt/Dialect/AIG/AIGPasses.td b/include/circt/Dialect/AIG/AIGPasses.td index 4950aa4a3b2d..126024cc5485 100644 --- a/include/circt/Dialect/AIG/AIGPasses.td +++ b/include/circt/Dialect/AIG/AIGPasses.td @@ -15,9 +15,17 @@ def LowerVariadic : Pass<"aig-lower-variadic", "hw::HWModuleOp"> { let summary = "Lower variadic AndInverter operations to binary AndInverter"; } +def BalanceVariadic : Pass<"aig-balance-variadic", "hw::HWModuleOp"> { + let summary = "Lower variadic AndInverter operations to binary AndInverter"; +} + def LowerWordToBits : Pass<"aig-lower-word-to-bits", "hw::HWModuleOp"> { let summary = "Lower multi-bit AndInverter to single-bit ones"; let dependentDialects = ["comb::CombDialect"]; } +def MaximumAndCover : Pass<"maximum-and-cover", "hw::HWModuleOp"> { + let summary = "Maximum And Cover"; +} + #endif // CIRCT_DIALECT_AIG_AIGPASSES_TD diff --git a/include/circt/Dialect/AIG/Analysis/OpDepthAnalysis.h b/include/circt/Dialect/AIG/Analysis/OpDepthAnalysis.h new file mode 100644 index 000000000000..ad9a9b4c8242 --- /dev/null +++ b/include/circt/Dialect/AIG/Analysis/OpDepthAnalysis.h @@ -0,0 +1,50 @@ +//===- OpDepthAnalysis.h - operation depth analyses -----------------------===// +// +// 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 header file defines AIG operation depth analysis. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_ANALYSIS_OPDEPTH_ANALYSIS_H +#define CIRCT_ANALYSIS_OPDEPTH_ANALYSIS_H + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/HW/HWOps.h" + +namespace mlir { +class AnalysisManager; +} // namespace mlir +namespace circt { +namespace aig { +namespace analysis { + +class OpDepthAnalysis { +public: + OpDepthAnalysis(hw::HWModuleOp moduleOp, mlir::AnalysisManager &am); + + /// Get the depth of operations of a specific name + size_t getOpDepth(AndInverterOp op) const { return opDepths.at(op); } + + const DenseMap &getOpDepthMap() const { + return opDepths; + } + + size_t updateLevel(AndInverterOp op, bool isRoot = false); + + SmallVector getPOs(); + +private: + DenseMap opDepths; + hw::HWModuleOp module; +}; + +} // namespace analysis +} // namespace aig +} // namespace circt + +#endif // CIRCT_ANALYSIS_OPDEPTH_ANALYSIS_H diff --git a/integration_test/circt-synth/aig-balancing-lec.mlir b/integration_test/circt-synth/aig-balancing-lec.mlir new file mode 100644 index 000000000000..5ca143146987 --- /dev/null +++ b/integration_test/circt-synth/aig-balancing-lec.mlir @@ -0,0 +1,33 @@ +// REQUIRES: libz3 +// REQUIRES: circt-lec-jit + +// RUN: circt-opt %s --cse --convert-aig-to-comb -o %t1.mlir +// RUN: circt-opt %s --maximum-and-cover --aig-balance-variadic --cse --convert-aig-to-comb -o %t2.mlir + +// RUN: circt-lec %t1.mlir %t2.mlir -c1=aig -c2=aig --shared-libs=%libz3 | FileCheck %s --check-prefix=COMB_AIG +// COMB_AIG: c1 == c2 +hw.module @aig(in %a: i1, in %b: i1, in %c: i1, in %d: i1, out o1: i1, out o2: i1, out o3: i1) { + %0 = aig.and_inv %a, %b : i1 + %1 = aig.and_inv %0, %c : i1 + %2 = aig.and_inv %b, %c : i1 + %3 = aig.and_inv %2, %d : i1 + + %4 = aig.and_inv %c, %d : i1 + %5 = aig.and_inv %b, %4 : i1 + %6 = aig.and_inv %a, %5 : i1 + + hw.output %1, %3, %6 : i1, i1, i1 +} + +// RUN: circt-lec %t1.mlir %t2.mlir -c1=aig2 -c2=aig2 --shared-libs=%libz3 | FileCheck %s --check-prefix=COMB_AIG2 +// COMB_AIG2: c1 == c2 +hw.module @aig2(in %a: i1, in %b: i1, in %c: i1, in %d: i1, in %e: i1, in %f: i1, in %g: i1, out o1: i1) { + %1 = aig.and_inv %a, not %b : i1 + %2 = aig.and_inv %d, not %e : i1 + %3 = aig.and_inv not %2, %f : i1 + %4 = aig.and_inv not %c, %3 : i1 + %5 = aig.and_inv %1, not %4 : i1 + %6 = aig.and_inv %5, %g : i1 + + hw.output %6 : i1 +} diff --git a/lib/Dialect/AIG/Analysis/CMakeLists.txt b/lib/Dialect/AIG/Analysis/CMakeLists.txt new file mode 100644 index 000000000000..3c4354634cb9 --- /dev/null +++ b/lib/Dialect/AIG/Analysis/CMakeLists.txt @@ -0,0 +1,6 @@ +add_circt_dialect_library(CIRCTAIGAnalysis + OpDepthAnalysis.cpp + + LINK_LIBS PUBLIC + CIRCTAIG +) diff --git a/lib/Dialect/AIG/Analysis/OpDepthAnalysis.cpp b/lib/Dialect/AIG/Analysis/OpDepthAnalysis.cpp new file mode 100644 index 000000000000..ebc163365f9e --- /dev/null +++ b/lib/Dialect/AIG/Analysis/OpDepthAnalysis.cpp @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 defines the op depth (level) analysis. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/Analysis/OpDepthAnalysis.h" + +using namespace circt; +using namespace aig; +using namespace analysis; + +OpDepthAnalysis::OpDepthAnalysis(hw::HWModuleOp moduleOp, + mlir::AnalysisManager &am) + : module(moduleOp) {} + +SmallVector OpDepthAnalysis::getPOs() { + SmallVector po; + for (auto op : module.getOps()) { + bool isPO = false; + for (auto *user : op->getUsers()) { + if (!isa(user)) { + isPO = true; + break; + } + } + if (isPO) + po.push_back(op); + } + return po; +} + +size_t OpDepthAnalysis::updateLevel(AndInverterOp op, bool isRoot) { + if (!isRoot) { + if (auto it = opDepths.find(op); it != opDepths.end()) { + return it->second; + } + } + + /// PI is level 0, so the minimum level of an AndInverterOp is 1 + size_t maxDepth = 1; + for (auto fanin : op.getOperands()) { + if (auto faninOp = fanin.getDefiningOp()) { + size_t faninDepth = updateLevel(faninOp); + maxDepth = std::max(maxDepth, faninDepth + 1); + } + } + opDepths[op] = maxDepth; + return maxDepth; +} diff --git a/lib/Dialect/AIG/CMakeLists.txt b/lib/Dialect/AIG/CMakeLists.txt index 16c312ec2240..075d04c25e05 100644 --- a/lib/Dialect/AIG/CMakeLists.txt +++ b/lib/Dialect/AIG/CMakeLists.txt @@ -14,3 +14,4 @@ add_circt_dialect_library(CIRCTAIG ) add_subdirectory(Transforms) +add_subdirectory(Analysis) diff --git a/lib/Dialect/AIG/Transforms/BalanceVariadic.cpp b/lib/Dialect/AIG/Transforms/BalanceVariadic.cpp new file mode 100644 index 000000000000..5a99f81332aa --- /dev/null +++ b/lib/Dialect/AIG/Transforms/BalanceVariadic.cpp @@ -0,0 +1,177 @@ +//===----------------------------------------------------------------------===// +// +// 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 pass lowers variadic AndInverter operations to balanced binary +// AndInverter operations. +// +//===----------------------------------------------------------------------===// +#include "llvm/ADT/PriorityQueue.h" + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/AIG/Analysis/OpDepthAnalysis.h" + +#define DEBUG_TYPE "aig-balance-variadic" + +namespace circt { +namespace aig { +#define GEN_PASS_DEF_BALANCEVARIADIC +#include "circt/Dialect/AIG/AIGPasses.h.inc" +} // namespace aig +} // namespace circt + +using namespace circt; +using namespace aig; + +namespace { +/// For wrapping Value and complement information into one object +struct Signal { + Value value; + bool complement; + + Signal() = default; + Signal(Value v, bool complement) : value(v), complement(complement) {} + + bool isComplement() const { return complement; } + Value getValue() const { return value; } +}; + +struct BalanceVariadicDriver { + BalanceVariadicDriver(mlir::IRRewriter &rewriter, + aig::analysis::OpDepthAnalysis *opDepthAnalysis) + : rewriter(rewriter), opDepthAnalysis(opDepthAnalysis) {} + + struct PairSorter { + bool operator()(const std::pair &lhs, + const std::pair &rhs) const { + // First compare by level (higher level = lower priority) + if (lhs.first != rhs.first) + return lhs.first > rhs.first; + + // If levels are equal, compare by argnumber or result number for + // deterministic ordering + auto *lop = lhs.second.getValue().getDefiningOp(); + auto *rop = rhs.second.getValue().getDefiningOp(); + if (lop && rop) { + return !lop->isBeforeInBlock(rop); + } + + if (!lop && rop) + return false; + + if (lop && !rop) + return true; + + BlockArgument larg = cast(lhs.second.getValue()); + BlockArgument rarg = cast(rhs.second.getValue()); + return larg.getArgNumber() > rarg.getArgNumber(); + } + }; + + using NodeLevelHeap = + llvm::PriorityQueue, + std::vector>, PairSorter>; + + void balanceVariadicAndInverterOp(AndInverterOp op) { + rewriter.setInsertionPoint(op); + + NodeLevelHeap sortByLevel; + for (auto [fanin, inverted] : + llvm::zip(op.getOperands(), op.getInverted())) { + auto faninOp = fanin.getDefiningOp(); + size_t level = faninOp ? opDepthAnalysis->updateLevel(faninOp, true) : 0; + sortByLevel.push({level, Signal(fanin, inverted)}); + } + + // extract the top two elements with minimum level + // and replace them with a new AndInverterOp + while (sortByLevel.size() > 1) { + auto [llv, lhs] = sortByLevel.top(); + sortByLevel.pop(); + auto [rlv, rhs] = sortByLevel.top(); + sortByLevel.pop(); + + auto balanced = rewriter.create( + op.getLoc(), lhs.getValue(), rhs.getValue(), lhs.isComplement(), + rhs.isComplement()); + + size_t level = std::max(llv, rlv) + 1; + sortByLevel.push({level, Signal(balanced, false)}); + } + + switch (sortByLevel.size()) { + case 0: + break; + case 1: { + auto signal = sortByLevel.top().second; + Value val = signal.getValue(); + if (signal.isComplement()) { + // TODO: push inverter to use AndInverterOp (fanout) + val = rewriter.create(op.getLoc(), val, true); + } + rewriter.replaceOp(op, val); + break; + } + default: + assert(0); // should not happen + } + } + + void balanceRecursive(AndInverterOp op) { + if (visited.count(op)) + return; + + visited.insert(op); + assert(!op->use_empty()); + + for (auto fanin : op.getOperands()) { + auto faninOp = fanin.getDefiningOp(); + if (faninOp) { + balanceRecursive(faninOp); + } + } + + if (op.getOperands().size() <= 2) + return; + + balanceVariadicAndInverterOp(op); + } + + void balancing() { + // Balance each variadic AndInverterOp in reverse topological order + // Will ignore dangling internal AIG nodes + for (AndInverterOp po : opDepthAnalysis->getPOs()) { + balanceRecursive(po); + } + } + +private: + DenseSet visited; + mlir::IRRewriter &rewriter; + aig::analysis::OpDepthAnalysis *opDepthAnalysis; +}; + +struct BalanceVariadicPass + : public impl::BalanceVariadicBase { + void runOnOperation() override; +}; +} // namespace + +//===----------------------------------------------------------------------===// +// Balance Variadic pass +//===----------------------------------------------------------------------===// +void BalanceVariadicPass::runOnOperation() { + auto *opDepthAnalysis = &getAnalysis(); + + auto module = getOperation(); + MLIRContext *ctx = module->getContext(); + mlir::IRRewriter rewriter(ctx); + + BalanceVariadicDriver driver(rewriter, opDepthAnalysis); + driver.balancing(); +} diff --git a/lib/Dialect/AIG/Transforms/CMakeLists.txt b/lib/Dialect/AIG/Transforms/CMakeLists.txt index 045734e122b6..eb3393897f2a 100644 --- a/lib/Dialect/AIG/Transforms/CMakeLists.txt +++ b/lib/Dialect/AIG/Transforms/CMakeLists.txt @@ -1,6 +1,8 @@ add_circt_dialect_library(CIRCTAIGTransforms LowerVariadic.cpp LowerWordToBits.cpp + BalanceVariadic.cpp + MaximumAndCover.cpp DEPENDS CIRCTAIGPassesIncGen @@ -9,4 +11,5 @@ add_circt_dialect_library(CIRCTAIGTransforms CIRCTAIG CIRCTComb CIRCTHW + CIRCTAIGAnalysis ) diff --git a/lib/Dialect/AIG/Transforms/MaximumAndCover.cpp b/lib/Dialect/AIG/Transforms/MaximumAndCover.cpp new file mode 100644 index 000000000000..a82509e024dd --- /dev/null +++ b/lib/Dialect/AIG/Transforms/MaximumAndCover.cpp @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// 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 pass lowers variadic AndInverter operations to binary AndInverter +// operations. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +#define DEBUG_TYPE "aig-balancing" + +namespace circt { +namespace aig { +#define GEN_PASS_DEF_MAXIMUMANDCOVER +#include "circt/Dialect/AIG/AIGPasses.h.inc" +} // namespace aig +} // namespace circt + +using namespace circt; +using namespace aig; + +namespace { +struct MaximumAndCover : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(AndInverterOp op, + PatternRewriter &rewriter) const override { + // multi-fanout node cannot be collapsed + auto hasMultiFanout = [](AndInverterOp op) { + return !(op->hasOneUse() || op->use_empty()); + }; + + llvm::SmallVector newFanins; + llvm::SmallVector newInverts; + + for (auto [input, inverted] : + llvm::zip(op.getOperands(), op.getInverted())) { + auto andOp = input.getDefiningOp(); + if (inverted || !andOp || hasMultiFanout(op)) { + newFanins.push_back(input); + newInverts.push_back(inverted); + continue; + } + + // Collect fanin node's fanins into current node + for (auto [fanin, faninInverted] : + llvm::zip(andOp.getOperands(), andOp.getInverted())) { + newFanins.push_back(fanin); + newInverts.push_back(faninInverted); + } + } + + if (newFanins.size() != op.getOperands().size()) { + rewriter.replaceOp(op, rewriter.createOrFold( + op.getLoc(), newFanins, newInverts)); + } + + return failure(); + } +}; + +struct AIGMaximumAndCoverPass + : public impl::MaximumAndCoverBase { + void runOnOperation() override; +}; +} // namespace + +void AIGMaximumAndCoverPass::runOnOperation() { + RewritePatternSet patterns(&getContext()); + patterns.add(&getContext()); + + mlir::FrozenRewritePatternSet frozenPatterns(std::move(patterns)); + + if (failed(mlir::applyPatternsGreedily(getOperation(), frozenPatterns))) + return signalPassFailure(); +} diff --git a/test/Dialect/AIG/balance-variadic.mlir b/test/Dialect/AIG/balance-variadic.mlir new file mode 100644 index 000000000000..5a60bbdb484d --- /dev/null +++ b/test/Dialect/AIG/balance-variadic.mlir @@ -0,0 +1,17 @@ +// RUN: circt-opt %s --aig-balance-variadic | FileCheck %s +// CHECK-LABEL: @Tree1 +hw.module @Tree1(in %a: i1, in %b: i1, in %c: i1, in %d: i1, in %e: i1, in %f: i1, in %g: i1, out o1: i1) { + // CHECK-NEXT: %[[AND_INV0:.+]] = aig.and_inv %d, not %e : i1 + // CHECK-NEXT: %[[AND_INV1:.+]] = aig.and_inv not %c, %f : i1 + // CHECK-NEXT: %[[AND_INV2:.+]] = aig.and_inv not %[[AND_INV0]], %1 : i1 + // CHECK-NEXT: %[[AND_INV3:.+]] = aig.and_inv %a, not %b : i1 + // CHECK-NEXT: %[[AND_INV4:.+]] = aig.and_inv %g, %[[AND_INV3]] : i1 + // CHECK-NEXT: %[[AND_INV5:.+]] = aig.and_inv not %[[AND_INV2]], %[[AND_INV4]] : i1 + // CHECK-NEXT: hw.output %[[AND_INV5]] : i1 + + %0 = aig.and_inv %d, not %e : i1 + %1 = aig.and_inv not %c, not %0, %f : i1 + %2 = aig.and_inv %a, not %b, not %1, %g : i1 + + hw.output %2 : i1 +} diff --git a/test/Dialect/AIG/balancing.mlir b/test/Dialect/AIG/balancing.mlir new file mode 100644 index 000000000000..8476a500b307 --- /dev/null +++ b/test/Dialect/AIG/balancing.mlir @@ -0,0 +1,34 @@ +// RUN: circt-opt %s --maximum-and-cover --aig-balance-variadic | FileCheck %s +// CHECK-LABEL: @Tree1 +hw.module @Tree1(in %a: i1, in %b: i1, in %c: i1, in %d: i1, in %e: i1, in %f: i1, in %g: i1, out o1: i1) { + // CHECK-NEXT: %[[AND_INV0:.+]] = aig.and_inv %d, not %e : i1 + // CHECK-NEXT: %[[AND_INV1:.+]] = aig.and_inv not %c, %f : i1 + // CHECK-NEXT: %[[AND_INV2:.+]] = aig.and_inv not %[[AND_INV0]], %[[AND_INV1]] : i1 + // CHECK-NEXT: %[[AND_INV3:.+]] = aig.and_inv %a, not %b : i1 + // CHECK-NEXT: %[[AND_INV4:.+]] = aig.and_inv %g, %[[AND_INV3]] : i1 + // CHECK-NEXT: %[[AND_INV5:.+]] = aig.and_inv not %[[AND_INV2]], %[[AND_INV4]] : i1 + // CHECK-NEXT: hw.output %[[AND_INV5]] : i1 + + %1 = aig.and_inv %a, not %b : i1 + %2 = aig.and_inv %d, not %e : i1 + %3 = aig.and_inv not %2, %f : i1 + %4 = aig.and_inv not %c, %3 : i1 + %5 = aig.and_inv %1, not %4 : i1 + %6 = aig.and_inv %5, %g : i1 + + hw.output %6 : i1 +} + +// CHECK-LABEL: @Tree2 +hw.module @Tree2(in %a: i1, in %b: i1, in %c: i1, in %d: i1, out o1: i1) { + // CHECK-NEXT: %[[AND_INV0:.+]] = aig.and_inv %a, %b : i1 + // CHECK-NEXT: %[[AND_INV1:.+]] = aig.and_inv %c, %d : i1 + // CHECK-NEXT: %[[AND_INV2:.+]] = aig.and_inv %[[AND_INV0]], %[[AND_INV1]] : i1 + // CHECK-NEXT: hw.output %[[AND_INV2]] : i1 + + %1 = aig.and_inv %a, %b : i1 + %2 = aig.and_inv %c, %1 : i1 + %3 = aig.and_inv %d, %2 : i1 + + hw.output %3 : i1 +} diff --git a/test/Dialect/AIG/maximum-and-cover.mlir b/test/Dialect/AIG/maximum-and-cover.mlir new file mode 100644 index 000000000000..32dffcc04a9d --- /dev/null +++ b/test/Dialect/AIG/maximum-and-cover.mlir @@ -0,0 +1,17 @@ +// RUN: circt-opt %s --maximum-and-cover | FileCheck %s +// CHECK-LABEL: @Tree1 +hw.module @Tree1(in %a: i1, in %b: i1, in %c: i1, in %d: i1, in %e: i1, in %f: i1, in %g: i1, out o1: i1) { + // CHECK-NEXT: %[[AND_INV0:.+]] = aig.and_inv %d, not %e : i1 + // CHECK-NEXT: %[[AND_INV1:.+]] = aig.and_inv not %c, not %[[AND_INV0]], %f : i1 + // CHECK-NEXT: %[[AND_INV2:.+]] = aig.and_inv %a, not %b, not %[[AND_INV1]], %g : i1 + // CHECK-NEXT: hw.output %[[AND_INV2]] : i1 + + %1 = aig.and_inv %a, not %b : i1 + %2 = aig.and_inv %d, not %e : i1 + %3 = aig.and_inv not %2, %f : i1 + %4 = aig.and_inv not %c, %3 : i1 + %5 = aig.and_inv %1, not %4 : i1 + %6 = aig.and_inv %5, %g : i1 + + hw.output %6 : i1 +}