Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion include/circt/Dialect/Synth/Transforms/SynthPasses.td
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ def LowerVariadic : Pass<"synth-lower-variadic", "hw::HWModuleOp"> {
ListOption<"opNames", "op-names", "std::string",
"Specify operation names to lower (empty means all)">,
Option<"timingAware", "timing-aware", "bool", "true",
"Lower operators with timing information">
"Lower operators with timing information">,
Option<"reuseSubsets", "reuse-subsets", "bool", /*default=*/"false",
"Reuse existing logic subsets to minimize area">
];
let dependentDialects = [
"circt::comb::CombDialect", "circt::hw::HWDialect",
Expand Down
111 changes: 110 additions & 1 deletion lib/Dialect/Synth/Transforms/LowerVariadic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@
#include "circt/Dialect/Synth/SynthOps.h"
#include "circt/Dialect/Synth/Transforms/SynthPasses.h"
#include "mlir/Analysis/TopologicalSortUtils.h"
#include "mlir/IR/Block.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Value.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
#include <iterator>
#include <vector>

#define DEBUG_TYPE "synth-lower-variadic"

Expand Down Expand Up @@ -93,6 +103,86 @@ static LogicalResult replaceWithBalancedTree(
return success();
}

using OperandKey = std::vector<std::pair<mlir::Value, bool>>;

// Struct for ordering the andInverterOp operations we have already seen
struct OperandPairLess {
bool operator()(const std::pair<mlir::Value, bool> &lhs,
const std::pair<mlir::Value, bool> &rhs) const {
if (lhs.first != rhs.first) {
auto lhsArg = llvm::dyn_cast<mlir::BlockArgument>(lhs.first);
auto rhsArg = llvm::dyn_cast<mlir::BlockArgument>(rhs.first);
if (lhsArg && rhsArg)
return lhsArg.getArgNumber() < rhsArg.getArgNumber();
if (lhsArg)
return true;
if (rhsArg)
return false;

auto *lhsOp = lhs.first.getDefiningOp();
auto *rhsOp = rhs.first.getDefiningOp();
return lhsOp->isBeforeInBlock(rhsOp);
}
return lhs.second < rhs.second;
}
};

// Ordering of a vector of andInverterOp operations
struct OperandKeyLess {
bool operator()(const OperandKey &lhs, const OperandKey &rhs) const {
// std::lexicographical_compare is the standard way to compare two vectors
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end(), OperandPairLess());
}
};

static OperandKey getSortedOperandKey(aig::AndInverterOp op) {
OperandKey key;
for (size_t i = 0, e = op.getNumOperands(); i < e; ++i) {
key.emplace_back(op.getOperand(i), op.isInverted(i));
}
std::sort(key.begin(), key.end(), OperandPairLess());
return key;
}

static void simplifyWithExistingOperations(
aig::AndInverterOp op, mlir::IRRewriter &rewriter,
std::map<OperandKey, Value, OperandKeyLess> &seenExpressions) {

if (op.getNumOperands() <= 2)
return;

OperandKey allOperands = getSortedOperandKey(op);
mlir::SmallVector<Value> newValues;
mlir::SmallVector<bool> newInversions;

for (auto it = allOperands.begin(); it != allOperands.end(); ++it) {
// Look at the remaining operands from 'it' to the end
OperandKey remaining(it, allOperands.end());

auto match = seenExpressions.find(remaining);
if (match != seenExpressions.end() && match->second != op.getResult()) {
newValues.push_back(match->second);
newInversions.push_back(false);

// We found a match that covers everything from 'it' to the end,
// so we can stop searching.
break;
}

// No match, add it to the new list of values and inversions.
newValues.push_back(it->first);
newInversions.push_back(it->second);
}

if (newValues.size() < allOperands.size()) {
rewriter.modifyOpInPlace(op, [&]() {
op.getOperation()->setOperands(newValues);
op.setInverted(newInversions);
});
}
}

void LowerVariadicPass::runOnOperation() {
// Topologically sort operations in graph regions to ensure operands are
// defined before uses.
Expand Down Expand Up @@ -131,6 +221,26 @@ void LowerVariadicPass::runOnOperation() {
mlir::IRRewriter rewriter(&getContext());
rewriter.setListener(analysis);

// To be used in simplifyWithExistingOperations.
std::map<OperandKey, Value, OperandKeyLess> seenExpressions;
// Simplify exising andInverterOps by reusing operations.
if (reuseSubsets) {
// First collect all the andInverterOp operations in the block.
for (auto &op : moduleOp.getBodyBlock()->getOperations()) {
if (auto andInverterOp = llvm::dyn_cast<aig::AndInverterOp>(op)) {
OperandKey key = getSortedOperandKey(andInverterOp);
seenExpressions[key] = andInverterOp.getResult();
}
}
// Now try to replace operations with subsets.
for (auto &op : moduleOp.getBodyBlock()->getOperations()) {
if (auto andInverterOp = llvm::dyn_cast<aig::AndInverterOp>(op)) {
simplifyWithExistingOperations(andInverterOp, rewriter,
seenExpressions);
}
}
}

// FIXME: Currently only top-level operations are lowered due to the lack of
// topological sorting in across nested regions.
for (auto &opRef :
Expand Down Expand Up @@ -158,7 +268,6 @@ void LowerVariadicPass::runOnOperation() {
});
if (failed(result))
return signalPassFailure();
continue;
}

// Handle commutative operations (and, or, xor, mul, add, etc.) using
Expand Down
8 changes: 6 additions & 2 deletions lib/Dialect/Synth/Transforms/SynthesisPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ static void addOpName(SmallVectorImpl<std::string> &ops) {
(ops.push_back(AllowedOpTy::getOperationName().str()), ...);
}
template <typename... OpToLowerTy>
static std::unique_ptr<Pass> createLowerVariadicPass(bool timingAware) {
static std::unique_ptr<Pass>
createLowerVariadicPass(bool timingAware, bool reuseSubsets = false) {
LowerVariadicOptions options;
addOpName<OpToLowerTy...>(options.opNames);
options.timingAware = timingAware;
options.reuseSubsets = reuseSubsets;
return createLowerVariadic(options);
}
void circt::synth::buildCombLoweringPipeline(
Expand Down Expand Up @@ -81,7 +83,9 @@ void circt::synth::buildCombLoweringPipeline(
if (options.targetIR.getValue() == TargetIR::AIG) {
// For AIG, lower variadic XoR since AIG cannot keep variadic
// representation.
pm.addPass(createLowerVariadicPass<comb::XorOp>(options.timingAware));
pm.addPass(createLowerVariadicPass<comb::XorOp>(
options.timingAware,
options.synthesisStrategy == OptimizationStrategyArea));
} else if (options.targetIR.getValue() == TargetIR::MIG) {
// For MIG, lower variadic And, Or, and Xor since MIG cannot keep variadic
// representation.
Expand Down
19 changes: 19 additions & 0 deletions test/Dialect/Synth/lower-variadic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,22 @@ hw.module @Issue9115(in %a : i16, in %b : i16, in %c : i16, in %d : i16, out pro
// COMMON-NEXT: comb.mul %c, %[[TMP]] : i16
hw.output %0 : i16
}

// RUN: circt-opt %s --synth-lower-variadic=reuse-subsets=true | FileCheck %s
// COMMON-LABEL: hw.module @SharingHeuristic
hw.module @SharingHeuristic(in %in0 : i1, in %in1 : i1, in %in2 : i1, in %in3 : i1, in %in4 : i1, out out1 : i1, out out2 : i1) {

// These represent the subset tree (out2)
// CHECK: %[[N0:.+]] = synth.aig.and_inv %in1, %in2
// CHECK: %[[N1:.+]] = synth.aig.and_inv %in3, %in4
// CHECK: %[[SUBSET_RES:.+]] = synth.aig.and_inv %[[N0]], %[[N1]]
%out2 = synth.aig.and_inv %in1, %in2, %in3, %in4 : i1

// out1 should now just use the SUBSET_RES directly
// CHECK: %[[OUT1_ROOT:.+]] = synth.aig.and_inv %in0, %[[SUBSET_RES]]
%out1 = synth.aig.and_inv %in0, %in1, %in2, %in3, %in4 : i1

// CHECK: hw.output %[[OUT1_ROOT]], %[[SUBSET_RES]]
hw.output %out1, %out2 : i1, i1
}

Loading