17#include "mlir/Analysis/TopologicalSortUtils.h"
18#include "mlir/IR/BuiltinAttributes.h"
19#include "mlir/IR/Matchers.h"
20#include "mlir/IR/OpDefinition.h"
21#include "mlir/IR/PatternMatch.h"
22#include "mlir/IR/Value.h"
23#include "mlir/Interfaces/CallInterfaces.h"
24#include "mlir/Interfaces/FunctionImplementation.h"
25#include "llvm/ADT/APInt.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/ADT/SmallVector.h"
28#include "llvm/Support/Casting.h"
29#include "llvm/Support/LogicalResult.h"
34using namespace circt::synth::aig;
36using namespace matchers;
39#include "circt/Dialect/Synth/Synth.cpp.inc"
43inline llvm::KnownBits applyInversion(llvm::KnownBits value,
bool inverted) {
45 std::swap(value.Zero, value.One);
49template <
typename SubType>
50struct ComplementMatcher {
52 ComplementMatcher(SubType lhs) : lhs(std::move(lhs)) {}
53 bool match(Operation *op) {
54 auto boolOp = dyn_cast<BooleanLogicOpInterface>(op);
55 return boolOp && boolOp.getInputs().size() == 1 && boolOp.isInverted(0) &&
56 lhs.match(op->getOperand(0));
60template <
typename SubType>
61static inline ComplementMatcher<SubType>
m_Complement(
const SubType &subExpr) {
62 return ComplementMatcher<SubType>(subExpr);
67LogicalResult ChoiceOp::verify() {
68 if (getNumOperands() < 1)
69 return emitOpError(
"requires at least one operand");
73OpFoldResult ChoiceOp::fold(FoldAdaptor adaptor) {
74 if (adaptor.getInputs().size() == 1)
88LogicalResult ChoiceOp::canonicalize(ChoiceOp op, PatternRewriter &rewriter) {
89 llvm::SetVector<Value> worklist;
92 auto addToWorklist = [&](ChoiceOp choice) ->
bool {
93 if (choice->getBlock() == op->getBlock() && visitedChoices.insert(choice)) {
94 worklist.insert(choice.getInputs().begin(), choice.getInputs().end());
102 bool mergedOtherChoices =
false;
105 for (
unsigned i = 0; i < worklist.size(); ++i) {
106 Value val = worklist[i];
107 if (
auto defOp = val.getDefiningOp<synth::ChoiceOp>()) {
109 if (addToWorklist(defOp))
110 mergedOtherChoices =
true;
113 for (Operation *user : val.getUsers()) {
114 if (
auto userChoice = llvm::dyn_cast<synth::ChoiceOp>(user)) {
115 if (addToWorklist(userChoice)) {
116 mergedOtherChoices =
true;
122 llvm::SmallVector<mlir::Value> finalOperands;
123 for (Value v : worklist) {
124 if (!visitedChoices.contains(v.getDefiningOp())) {
125 finalOperands.push_back(v);
129 if (!mergedOtherChoices && finalOperands.size() == op.getInputs().size())
130 return llvm::failure();
132 auto newChoice = synth::ChoiceOp::create(rewriter, op->getLoc(), op.getType(),
134 for (Operation *visited : visitedChoices.takeVector())
135 rewriter.replaceOp(visited, newChoice);
137 for (
auto value : newChoice.getInputs())
138 rewriter.replaceAllUsesExcept(value, newChoice.getResult(), newChoice);
147bool AndInverterOp::areInputsPermutationInvariant() {
return true; }
149OpFoldResult AndInverterOp::fold(FoldAdaptor adaptor) {
150 if (getNumOperands() == 1 && !isInverted(0))
151 return getOperand(0);
153 auto inputs = adaptor.getInputs();
154 if (inputs.size() == 2)
155 if (
auto intAttr = dyn_cast_or_null<IntegerAttr>(inputs[1])) {
156 auto value = intAttr.getValue();
160 return IntegerAttr::get(
161 IntegerType::get(getContext(), value.getBitWidth()), value);
162 if (value.isAllOnes()) {
166 return getOperand(0);
172LogicalResult AndInverterOp::canonicalize(AndInverterOp op,
173 PatternRewriter &rewriter) {
175 SmallVector<Value> uniqueValues;
176 SmallVector<bool> uniqueInverts;
179 APInt::getAllOnes(op.getResult().getType().getIntOrFloatBitWidth());
181 bool invertedConstFound =
false;
182 bool flippedFound =
false;
184 for (
auto [value, inverted] :
llvm::zip(op.getInputs(), op.getInverted())) {
185 bool newInverted = inverted;
188 constValue &= ~constOp.getValue();
189 invertedConstFound =
true;
191 constValue &= constOp.getValue();
196 if (
auto andInverterOp = value.getDefiningOp<synth::aig::AndInverterOp>()) {
197 if (andInverterOp.getInputs().size() == 1 &&
198 andInverterOp.isInverted(0)) {
199 value = andInverterOp.getOperand(0);
200 newInverted = andInverterOp.isInverted(0) ^ inverted;
205 auto it = seen.find(value);
206 if (it == seen.end()) {
207 seen.insert({value, newInverted});
208 uniqueValues.push_back(value);
209 uniqueInverts.push_back(newInverted);
210 }
else if (it->second != newInverted) {
213 op, APInt::getZero(value.getType().getIntOrFloatBitWidth()));
219 if (constValue.isZero()) {
225 if ((uniqueValues.size() == op.getInputs().size() && !flippedFound) ||
226 (!constValue.isAllOnes() && !invertedConstFound &&
227 uniqueValues.size() + 1 == op.getInputs().size()))
230 if (!constValue.isAllOnes()) {
232 uniqueInverts.push_back(
false);
233 uniqueValues.push_back(constOp);
237 if (uniqueValues.empty()) {
243 replaceOpWithNewOpAndCopyNamehint<synth::aig::AndInverterOp>(
244 rewriter, op, uniqueValues, uniqueInverts);
248APInt AndInverterOp::evaluateBooleanLogicWithoutInversion(
249 llvm::ArrayRef<APInt> inputs) {
250 assert(!inputs.empty() &&
"expected non-empty input list");
251 APInt result = APInt::getAllOnes(inputs.front().getBitWidth());
252 for (
const APInt &input : inputs)
257bool AndInverterOp::supportsNumInputs(
unsigned numInputs) {
258 return numInputs >= 1;
261llvm::KnownBits AndInverterOp::computeKnownBits(
262 llvm::function_ref<
const llvm::KnownBits &(
unsigned)> getInputKnownBits) {
263 assert(getNumOperands() > 0 &&
"Expected non-empty input list");
265 auto width = getInputKnownBits(0).getBitWidth();
266 llvm::KnownBits result(width);
267 result.One = APInt::getAllOnes(width);
268 result.Zero = APInt::getZero(width);
270 for (
auto [i, inverted] :
llvm::enumerate(getInverted()))
271 result &= applyInversion(getInputKnownBits(i), inverted);
276int64_t AndInverterOp::getLogicDepthCost() {
277 return llvm::Log2_64_Ceil(getNumOperands());
280std::optional<uint64_t> AndInverterOp::getLogicAreaCost() {
281 int64_t bitWidth = hw::getBitWidth(getType());
284 return static_cast<uint64_t
>(getNumOperands() - 1) * bitWidth;
287void AndInverterOp::emitCNFWithoutInversion(
288 int outVar, llvm::ArrayRef<int> inputVars,
289 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
290 llvm::function_ref<
int()> newVar) {
299bool XorInverterOp::areInputsPermutationInvariant() {
return true; }
301OpFoldResult XorInverterOp::fold(FoldAdaptor adaptor) {
303 if (getNumOperands() == 1 && !isInverted(0))
304 return getOperand(0);
306 auto inputs = adaptor.getInputs();
307 if (inputs.size() == 2)
308 if (
auto intAttr = dyn_cast_or_null<IntegerAttr>(inputs[1])) {
309 auto value = intAttr.getValue();
314 return getOperand(0);
319LogicalResult XorInverterOp::canonicalize(XorInverterOp op,
320 PatternRewriter &rewriter) {
327 APInt::getZero(op.getResult().getType().getIntOrFloatBitWidth());
329 bool constFound =
false;
330 bool changed =
false;
332 for (
auto [value, inverted] :
llvm::zip(op.getInputs(), op.getInverted())) {
333 Value currentValue = value;
334 bool newInverted = inverted;
338 if (
auto constOp = currentValue.getDefiningOp<
hw::ConstantOp>()) {
339 APInt val = constOp.getValue();
350 matchPattern(currentValue,
m_Complement(m_Any(&matchedVal)))) {
351 currentValue = matchedVal;
358 if (activeOperands.count(currentValue)) {
361 if (activeOperands[currentValue] != newInverted)
362 constValue.flipAllBits();
363 activeOperands.erase(currentValue);
366 activeOperands[currentValue] = newInverted;
372 if (!changed && !constFound && activeOperands.size() == op.getInputs().size())
377 if (!constValue.isZero()) {
378 if (constValue.isAllOnes() && !activeOperands.empty()) {
380 activeOperands.back().second = !activeOperands.back().second;
382 if (op.getInputs().size() == 2 && !op.getInverted()[1] &&
383 activeOperands.size() == 1)
386 activeOperands.insert({constOp,
false});
390 if (activeOperands.empty()) {
392 op, APInt::getZero(op.getResult().getType().getIntOrFloatBitWidth()));
397 XorInverterOp::create(rewriter, op.getLoc(),
398 activeOperands.getArrayRef()));
402APInt XorInverterOp::evaluateBooleanLogicWithoutInversion(
403 llvm::ArrayRef<APInt> inputs) {
404 assert(!inputs.empty() &&
"expected non-empty input list");
405 APInt result = APInt::getZero(inputs.front().getBitWidth());
406 for (
const APInt &input : inputs)
411bool XorInverterOp::supportsNumInputs(
unsigned numInputs) {
412 return numInputs >= 1;
415llvm::KnownBits XorInverterOp::computeKnownBits(
416 llvm::function_ref<
const llvm::KnownBits &(
unsigned)> getInputKnownBits) {
417 assert(getNumOperands() > 0 &&
"Expected non-empty input list");
419 llvm::KnownBits result(getInputKnownBits(0).
getBitWidth());
420 for (
auto [i, inverted] :
llvm::enumerate(getInverted()))
421 result ^= applyInversion(getInputKnownBits(i), inverted);
425int64_t XorInverterOp::getLogicDepthCost() {
426 return llvm::Log2_64_Ceil(getNumOperands());
429std::optional<uint64_t> XorInverterOp::getLogicAreaCost() {
430 int64_t bitWidth = hw::getBitWidth(getType());
433 return static_cast<uint64_t
>(getNumOperands() - 1) * bitWidth;
436void XorInverterOp::emitCNFWithoutInversion(
437 int outVar, llvm::ArrayRef<int> inputVars,
438 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
439 llvm::function_ref<
int()> newVar) {
447void DotOp::emitCNFWithoutInversion(
448 int outVar, llvm::ArrayRef<int> inputVars,
449 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
450 llvm::function_ref<
int()> newVar) {
451 assert(inputVars.size() == 3 &&
"expected one SAT variable per operand");
452 int andVar = newVar();
453 int orVar = newVar();
466void MajorityOp::emitCNFWithoutInversion(
467 int outVar, llvm::ArrayRef<int> inputVars,
468 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
469 llvm::function_ref<
int()> newVar) {
470 assert(inputVars.size() == 3 &&
"expected exactly three inputs");
485 Location loc, ValueRange operands, ArrayRef<bool> inverts,
486 PatternRewriter &rewriter,
487 llvm::function_ref<Value(Value,
bool)> createUnary,
488 llvm::function_ref<Value(Value, Value,
bool,
bool)> createBinary) {
489 switch (operands.size()) {
491 assert(0 &&
"cannot be called with empty operand range");
494 return inverts[0] ? createUnary(operands[0],
true) : operands[0];
496 return createBinary(operands[0], operands[1], inverts[0], inverts[1]);
498 auto firstHalf = operands.size() / 2;
500 inverts.take_front(firstHalf),
501 rewriter, createUnary, createBinary);
503 inverts.drop_front(firstHalf),
504 rewriter, createUnary, createBinary);
505 return createBinary(lhs, rhs,
false,
false);
510template <
typename OpTy>
512 PatternRewriter &rewriter) {
513 if (op.getInputs().size() <= 2)
516 op.getLoc(), op.getOperands(), op.getInverted(), rewriter,
517 [&](Value input,
bool invert) {
518 return OpTy::create(rewriter, op.getLoc(), input, invert);
520 [&](Value lhs, Value rhs,
bool invertLhs,
bool invertRhs) {
521 return OpTy::create(rewriter, op.getLoc(), lhs, rhs, invertLhs,
530 patterns.add(lowerVariadicAndInverterOpConversion<aig::AndInverterOp>);
535 patterns.add(lowerVariadicAndInverterOpConversion<XorInverterOp>);
539 return isa<synth::BooleanLogicOpInterface, synth::ChoiceOp,
comb::ExtractOp,
545 llvm::function_ref<
bool(mlir::Value, mlir::Operation *)> isOperandReady) {
547 auto walkResult = op->walk([&](Region *region) {
549 dyn_cast<mlir::RegionKindInterface>(region->getParentOp());
551 regionKindOp.hasSSADominance(region->getRegionNumber()))
552 return WalkResult::advance();
555 for (
auto &block : *region) {
556 if (!mlir::sortTopologically(&block, isOperandReady))
557 return WalkResult::interrupt();
559 return WalkResult::advance();
562 return success(!walkResult.wasInterrupted());
569void OneHotOp::emitCNFWithoutInversion(
570 int outVar, llvm::ArrayRef<int> inputVars,
571 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
572 llvm::function_ref<
int()> newVar) {
573 assert(inputVars.size() == 3 &&
"expected exactly three inputs");
576 int parity = newVar();
580 int allSet = newVar();
591void MuxInverterOp::emitCNFWithoutInversion(
592 int outVar, llvm::ArrayRef<int> inputVars,
593 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
594 llvm::function_ref<
int()> newVar) {
595 assert(inputVars.size() == 3 &&
"expected exactly three inputs");
597 int cond = inputVars[0];
598 int trueValue = inputVars[1];
599 int falseValue = inputVars[2];
616void GambleOp::emitCNFWithoutInversion(
617 int outVar, llvm::ArrayRef<int> inputVars,
618 llvm::function_ref<
void(llvm::ArrayRef<int>)> addClause,
619 llvm::function_ref<
int()> newVar) {
620 assert(inputVars.size() == 3 &&
"expected exactly three inputs");
623 int allSet = newVar();
627 int orSet = newVar();
638ParseResult CutRewritePatternOp::parse(OpAsmParser &parser,
639 OperationState &result) {
641 SmallVector<OpAsmParser::Argument> entryArgs;
642 SmallVector<Type> resultTypes;
643 SmallVector<DictionaryAttr> resultAttrs;
644 bool isVariadic =
false;
646 if (function_interface_impl::parseFunctionSignatureWithArguments(
647 parser,
false, entryArgs, isVariadic, resultTypes,
651 auto inputTypes = llvm::map_to_vector(
652 entryArgs, [](
auto &arg) -> Type {
return arg.type; });
654 parser.getBuilder().getFunctionType(inputTypes, resultTypes);
656 result.addAttribute(getFunctionTypeAttrName(result.name),
657 TypeAttr::get(functionType));
658 if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
661 return parser.parseRegion(*result.addRegion(), entryArgs,
665void CutRewritePatternOp::print(OpAsmPrinter &p) {
666 auto functionType = getFunctionType();
667 call_interface_impl::printFunctionSignature(
668 p, functionType.getInputs(), {},
false,
669 functionType.getResults(), {}, &getBody(),
672 p.printOptionalAttrDictWithKeyword((*this)->getAttrs(),
673 {getFunctionTypeAttrName()});
676 p.printRegion(getBody(),
false,
680LogicalResult CutRewritePatternOp::verify() {
681 auto functionType = getFunctionType();
683 if (functionType.getNumResults() != 1)
684 return emitError() <<
"requires exactly one result";
686 for (
auto type : functionType.getInputs())
687 if (!type.isInteger(1))
688 return emitError() <<
"argument type must be i1, but got " << type;
690 for (
auto type : functionType.getResults())
691 if (!type.isInteger(1))
692 return emitError() <<
"result type must be i1, but got " << type;
695 auto *terminator = this->getBody().front().getTerminator();
696 if (terminator->getOperands().size() != functionType.getNumResults())
697 return emitError() <<
"result type doesn't match with the terminator";
699 for (
auto [lhs, rhs] :
llvm::zip(terminator->getOperands().getTypes(),
700 functionType.getResults()))
702 return emitError() << rhs <<
" is expected but got " << lhs;
704 auto blockArgs = this->getBody().front().getArguments();
705 if (blockArgs.size() != functionType.getNumInputs())
706 return emitError() <<
"operand type doesn't match with the block arg";
708 for (
auto [blockArg, inputType] :
709 llvm::zip(blockArgs, functionType.getInputs()))
710 if (blockArg.getType() != inputType)
711 return emitError() << inputType <<
" is expected but got "
712 << blockArg.getType();
714 auto cost = getCost();
716 auto arcs = cost.getArcs();
717 if (arcs.size() != functionType.getNumResults() * functionType.getNumInputs())
718 return emitError() <<
"mapping cost arcs must match the number of results "
721 if (
auto inputCaps = cost.getInputCaps())
722 if (inputCaps.size() != functionType.getNumInputs())
724 <<
"input_caps size must match the number of arguments";
assert(baseType &&"element must be base type")
static ComplementMatcher< SubType > m_Complement(const SubType &subExpr)
LogicalResult lowerVariadicAndInverterOpConversion(OpTy op, PatternRewriter &rewriter)
static Value lowerVariadicInvertibleOp(Location loc, ValueRange operands, ArrayRef< bool > inverts, PatternRewriter &rewriter, llvm::function_ref< Value(Value, bool)> createUnary, llvm::function_ref< Value(Value, Value, bool, bool)> createBinary)
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
void populateVariadicXorInverterLoweringPatterns(mlir::RewritePatternSet &patterns)
LogicalResult topologicallySortGraphRegionBlocks(mlir::Operation *op, llvm::function_ref< bool(mlir::Value, mlir::Operation *)> isOperandReady)
This function performs a topological sort on the operations within each block of graph regions in the...
bool isLogicNetworkOp(mlir::Operation *op)
void populateVariadicAndInverterLoweringPatterns(mlir::RewritePatternSet &patterns)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void addAndClauses(int outVar, llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause)
Emit clauses encoding outVar <=> and(inputLits).
void addXorClauses(int outVar, int lhsLit, int rhsLit, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause)
Emit clauses encoding outVar <=> (lhsLit xor rhsLit).
void replaceOpAndCopyNamehint(PatternRewriter &rewriter, Operation *op, Value newValue)
A wrapper of PatternRewriter::replaceOp to propagate "sv.namehint" attribute.
void addOrClauses(int outVar, llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause)
Emit clauses encoding outVar <=> or(inputLits).
void addParityClauses(int outVar, llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause, llvm::function_ref< int()> newVar)
Emit clauses encoding outVar <=> parity(inputLits).