CIRCT 24.0.0git
Loading...
Searching...
No Matches
CutRewriter.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a DAG-based boolean matching cut rewriting algorithm for
10// applications like technology/LUT mapping and combinational logic
11// optimization. The algorithm uses priority cuts and NPN
12// (Negation-Permutation-Negation) canonical forms to efficiently match cuts
13// against rewriting patterns.
14//
15// References:
16// "Combinational and Sequential Mapping with Priority Cuts", Alan Mishchenko,
17// Sungmin Cho, Satrajit Chatterjee and Robert Brayton, ICCAD 2007
18// "Improvements to technology mapping for LUT-based FPGAs", Alan Mishchenko,
19// Satrajit Chatterjee and Robert Brayton, FPGA 2006
20//
21//===----------------------------------------------------------------------===//
22
24
29#include "circt/Support/LLVM.h"
32#include "mlir/Analysis/TopologicalSortUtils.h"
33#include "mlir/IR/Builders.h"
34#include "mlir/IR/Operation.h"
35#include "mlir/IR/RegionKindInterface.h"
36#include "mlir/IR/Value.h"
37#include "mlir/IR/ValueRange.h"
38#include "mlir/IR/Visitors.h"
39#include "mlir/Support/LLVM.h"
40#include "llvm/ADT/APInt.h"
41#include "llvm/ADT/Bitset.h"
42#include "llvm/ADT/DenseMap.h"
43#include "llvm/ADT/MapVector.h"
44#include "llvm/ADT/STLExtras.h"
45#include "llvm/ADT/ScopeExit.h"
46#include "llvm/ADT/SetVector.h"
47#include "llvm/ADT/SmallVector.h"
48#include "llvm/ADT/TypeSwitch.h"
49#include "llvm/ADT/iterator.h"
50#include "llvm/Support/Debug.h"
51#include "llvm/Support/ErrorHandling.h"
52#include "llvm/Support/LogicalResult.h"
53#include <algorithm>
54#include <functional>
55#include <memory>
56#include <optional>
57#include <string>
58
59#define DEBUG_TYPE "synth-cut-rewriter"
60
61using namespace circt;
62using namespace circt::synth;
63
64//===----------------------------------------------------------------------===//
65// LogicNetwork
66//===----------------------------------------------------------------------===//
67
68uint32_t LogicNetwork::getOrCreateIndex(Value value) {
69 auto [it, inserted] = valueToIndex.try_emplace(value, gates.size());
70 if (inserted) {
71 indexToValue.push_back(value);
72 gates.emplace_back(); // Will be filled in later
73 }
74 return it->second;
75}
76
77uint32_t LogicNetwork::getIndex(Value value) const {
78 const auto it = valueToIndex.find(value);
79 assert(it != valueToIndex.end() &&
80 "Value not found in LogicNetwork - use getOrCreateIndex or check with "
81 "hasIndex first");
82 return it->second;
83}
84
85bool LogicNetwork::hasIndex(Value value) const {
86 return valueToIndex.contains(value);
87}
88
89Value LogicNetwork::getValue(uint32_t index) const {
90 // Index 0 and 1 are reserved for constants, they have no associated Value
91 if (index == kConstant0 || index == kConstant1)
92 return Value();
93
94 assert(index < indexToValue.size() &&
95 "Index out of bounds in LogicNetwork::getValue");
96 return indexToValue[index];
97}
98
99void LogicNetwork::getValues(ArrayRef<uint32_t> indices,
100 SmallVectorImpl<Value> &values) const {
101 values.clear();
102 values.reserve(indices.size());
103 for (uint32_t idx : indices)
104 values.push_back(getValue(idx));
105}
106
107uint32_t LogicNetwork::addPrimaryInput(Value value) {
108 const uint32_t index = getOrCreateIndex(value);
110 return index;
111}
112
113uint32_t LogicNetwork::addGate(Operation *op, LogicNetworkGate::Kind kind,
114 Value result, ArrayRef<Signal> operands) {
115 const uint32_t index = getOrCreateIndex(result);
116 gates[index] = LogicNetworkGate(op, kind, operands);
117 return index;
118}
119
120LogicalResult LogicNetwork::buildFromBlock(Block *block) {
121 // Pre-size vectors to reduce reallocations (rough estimate)
122 const size_t estimatedSize =
123 block->getArguments().size() + block->getOperations().size();
124 indexToValue.reserve(estimatedSize);
125 gates.reserve(estimatedSize);
126
127 auto handleSingleInputGate = [&](Operation *op, Value result,
128 const Signal &inputSignal) {
129 if (!inputSignal.isInverted()) {
130 // Non-inverted buffer: directly alias the result to the input
131 valueToIndex[result] = inputSignal.getIndex();
132 return;
133 }
134 // Inverted operation: create a NOT gate
135 addGate(op, LogicNetworkGate::Identity, result, {inputSignal});
136 };
137
138 // Ensure all block arguments are indexed as primary inputs first
139 for (Value arg : block->getArguments()) {
140 if (!hasIndex(arg))
141 addPrimaryInput(arg);
142 }
143
144 auto handleOtherResults = [&](Operation *op) {
145 for (Value result : op->getResults()) {
146 if (result.getType().isInteger(1) && !hasIndex(result))
147 addPrimaryInput(result);
148 }
149 };
150
151 auto getInvertibleSignal = [&](auto op, unsigned index) {
152 return getOrCreateSignal(op.getOperand(index), op.isInverted(index));
153 };
154
155 auto handleInvertibleBinaryGate = [&](auto logicOp,
157 // The cut rewriter only has dedicated nodes for single-bit unary/binary
158 // gates. Wider or variadic forms stay as opaque cut inputs for now.
159 const auto inputs = logicOp.getInputs();
160 if (inputs.size() == 1) {
161 const Signal inputSignal = getInvertibleSignal(logicOp, 0);
162 handleSingleInputGate(logicOp, logicOp.getResult(), inputSignal);
163 return success();
164 }
165 if (inputs.size() == 2) {
166 const Signal lhsSignal = getInvertibleSignal(logicOp, 0);
167 const Signal rhsSignal = getInvertibleSignal(logicOp, 1);
168 addGate(logicOp, kind, {lhsSignal, rhsSignal});
169 }
170 // Variadic gates with >2 inputs are treated as primary
171 // inputs for now.
172 handleOtherResults(logicOp);
173 return success();
174 };
175
176 auto handleInvertibleTernaryGate = [&](auto logicOp,
178 if (!logicOp.getType().isInteger(1)) {
179 handleOtherResults(logicOp);
180 return success();
181 }
182 const Signal aSignal = getInvertibleSignal(logicOp, 0);
183 const Signal bSignal = getInvertibleSignal(logicOp, 1);
184 const Signal cSignal = getInvertibleSignal(logicOp, 2);
185 addGate(logicOp, kind, {aSignal, bSignal, cSignal});
186 return success();
187 };
188
189 // Process operations in topological order
190 for (Operation &op : block->getOperations()) {
191 LogicalResult result =
192 llvm::TypeSwitch<Operation *, LogicalResult>(&op)
193 .Case<aig::AndInverterOp>([&](aig::AndInverterOp andOp) {
194 return handleInvertibleBinaryGate(andOp, LogicNetworkGate::And2);
195 })
196 .Case<synth::XorInverterOp>([&](synth::XorInverterOp xorOp) {
197 return handleInvertibleBinaryGate(xorOp, LogicNetworkGate::Xor2);
198 })
199 .Case<synth::MuxInverterOp>([&](synth::MuxInverterOp muxOp) {
200 return handleInvertibleTernaryGate(muxOp, LogicNetworkGate::Mux3);
201 })
202 .Case<synth::DotOp>([&](synth::DotOp dotOp) {
203 return handleInvertibleTernaryGate(dotOp, LogicNetworkGate::Dot3);
204 })
205 .Case<synth::MajorityOp>([&](synth::MajorityOp majOp) {
206 return handleInvertibleTernaryGate(majOp, LogicNetworkGate::Maj3);
207 })
208 .Case<synth::OneHotOp>([&](synth::OneHotOp oneHotOp) {
209 return handleInvertibleTernaryGate(oneHotOp,
211 })
212 .Case<synth::GambleOp>([&](synth::GambleOp gambleOp) {
213 return handleInvertibleTernaryGate(gambleOp,
215 })
216 .Case<comb::XorOp>([&](comb::XorOp xorOp) {
217 if (xorOp->getNumOperands() != 2) {
218 handleOtherResults(xorOp);
219 return success();
220 }
221 const Signal lhsSignal =
222 getOrCreateSignal(xorOp.getOperand(0), false);
223 const Signal rhsSignal =
224 getOrCreateSignal(xorOp.getOperand(1), false);
225 addGate(xorOp, LogicNetworkGate::Xor2, {lhsSignal, rhsSignal});
226 return success();
227 })
228 .Case<hw::ConstantOp>([&](hw::ConstantOp constOp) {
229 Value result = constOp.getResult();
230 if (!result.getType().isInteger(1)) {
231 handleOtherResults(constOp);
232 return success();
233 }
234 uint32_t constIdx =
235 constOp.getValue().isZero() ? kConstant0 : kConstant1;
236 valueToIndex[result] = constIdx;
237 return success();
238 })
239 .Case<synth::ChoiceOp>([&](synth::ChoiceOp choiceOp) {
240 if (!choiceOp.getType().isInteger(1)) {
241 handleOtherResults(choiceOp);
242 return success();
243 }
244 addGate(choiceOp, LogicNetworkGate::Choice, choiceOp.getResult(),
245 {});
246 return success();
247 })
248 .Default([&](Operation *defaultOp) {
249 handleOtherResults(defaultOp);
250 return success();
251 });
252
253 if (failed(result))
254 return result;
255 }
256
257 return success();
258}
259
261 valueToIndex.clear();
262 indexToValue.clear();
263 gates.clear();
264 // Re-add the constant nodes (index 0 = const0, index 1 = const1)
265 gates.emplace_back(nullptr, LogicNetworkGate::Constant);
266 gates.emplace_back(nullptr, LogicNetworkGate::Constant);
267 // Placeholders for constants in indexToValue
268 indexToValue.push_back(Value()); // const0
269 indexToValue.push_back(Value()); // const1
270}
271
272//===----------------------------------------------------------------------===//
273// Helper functions
274//===----------------------------------------------------------------------===//
275
276// Return true if the gate at the given index is always a cut input.
277static bool isAlwaysCutInput(const LogicNetwork &network, uint32_t index) {
278 const auto &gate = network.getGate(index);
279 return gate.isAlwaysCutInput();
280}
281
282// Return true if the new area/delay is better than the old area/delay in the
283// context of the given strategy.
284static bool compareDelayAndArea(OptimizationStrategy strategy, double newArea,
285 ArrayRef<DelayType> newDelay, double oldArea,
286 ArrayRef<DelayType> oldDelay) {
287 if (strategy == OptimizationStrategyArea)
288 return newArea < oldArea || (newArea == oldArea && newDelay < oldDelay);
289 if (strategy == OptimizationStrategyTiming)
290 return newDelay < oldDelay || (newDelay == oldDelay && newArea < oldArea);
291 llvm_unreachable("Unknown mapping strategy");
292}
293
294LogicalResult circt::synth::topologicallySortLogicNetwork(Operation *topOp) {
295 const auto isOperationReady = [](Value value, Operation *op) -> bool {
296 // Topologically sort AIG ops and dataflow ops. Other operations
297 // can be scheduled.
299 };
300
301 if (failed(topologicallySortGraphRegionBlocks(topOp, isOperationReady)))
302 return emitError(topOp->getLoc(),
303 "failed to sort operations topologically");
304 return success();
305}
306
307/// Get the truth table for operations within a block.
308FailureOr<BinaryTruthTable> circt::synth::getTruthTable(ValueRange values,
309 Block *block) {
311 for (Value arg : block->getArguments())
312 inputArgs.insert(arg);
313
314 if (inputArgs.empty())
315 return BinaryTruthTable();
316
317 const int64_t numInputs = inputArgs.size();
318 const int64_t numOutputs = values.size();
319 if (LLVM_UNLIKELY(numOutputs != 1 || numInputs >= maxTruthTableInputs)) {
320 if (numOutputs == 0)
321 return BinaryTruthTable(numInputs, 0);
322 if (numInputs >= maxTruthTableInputs)
323 return mlir::emitError(values.front().getLoc(),
324 "Truth table is too large");
325 return mlir::emitError(values.front().getLoc(),
326 "Multiple outputs are not supported yet");
327 }
328
329 // Create a map to evaluate the operation
330 DenseMap<Value, APInt> eval;
331 for (uint32_t i = 0; i < numInputs; ++i)
332 eval[inputArgs[i]] = circt::createVarMask(numInputs, i, true);
333
334 // Simulate the operations in the block
335 for (Operation &op : block->without_terminator()) {
336 if (op.getNumResults() != 1 ||
337 hw::getBitWidth(op.getResult(0).getType()) != 1)
338 return op.emitError("Unsupported operation for truth table simulation");
339
340 if (auto choiceOp = dyn_cast<synth::ChoiceOp>(&op)) {
341 auto it = eval.find(choiceOp.getInputs().front());
342 if (it == eval.end())
343 return choiceOp.emitError("Input value not found in evaluation map");
344 eval[choiceOp.getResult()] = it->second;
345 } else if (auto logicOp = dyn_cast<BooleanLogicOpInterface>(&op)) {
346 for (auto value : logicOp.getInputs())
347 if (!eval.contains(value))
348 return logicOp->emitError("Input value not found in evaluation map");
349
350 eval[logicOp.getResult()] =
351 logicOp.evaluateBooleanLogic([&](unsigned i) -> const APInt & {
352 return eval.find(logicOp.getInput(i))->second;
353 });
354 } else if (auto xorOp = dyn_cast<comb::XorOp>(&op)) {
355 // TODO: Define Xor as Synth op.
356 auto it = eval.find(xorOp.getOperand(0));
357 if (it == eval.end())
358 return xorOp.emitError("Input value not found in evaluation map");
359 llvm::APInt result = it->second;
360 for (unsigned i = 1; i < xorOp.getNumOperands(); ++i) {
361 it = eval.find(xorOp.getOperand(i));
362 if (it == eval.end())
363 return xorOp.emitError("Input value not found in evaluation map");
364 result ^= it->second;
365 }
366 eval[xorOp.getResult()] = result;
367 } else if (auto constantOp = dyn_cast<hw::ConstantOp>(&op)) {
368 auto tableSize = 1ULL << numInputs;
369 eval[constantOp.getResult()] = constantOp.getValue().isZero()
370 ? llvm::APInt::getZero(tableSize)
371 : llvm::APInt::getAllOnes(tableSize);
372 } else {
373 return op.emitError("Unsupported operation for truth table simulation");
374 }
375 }
376
377 return BinaryTruthTable(numInputs, 1, eval[values[0]]);
378}
379
380//===----------------------------------------------------------------------===//
381// Cut
382//===----------------------------------------------------------------------===//
383
384bool Cut::isTrivialCut() const {
385 // A cut is a trivial cut if it has no root (rootIndex == 0 sentinel)
386 // and only one input
387 return rootIndex == 0 && inputs.size() == 1;
388}
389
390const NPNClass &Cut::getNPNClass() const { return getNPNClass(nullptr); }
391
392const NPNClass &Cut::getNPNClass(const NPNTable *npnTable) const {
393 if (npnClass)
394 return *npnClass;
395
396 const auto &truthTable = *getTruthTable();
397 NPNClass canonicalForm;
398 if (!npnTable || !npnTable->lookup(truthTable, canonicalForm))
400
401 npnClass.emplace(std::move(canonicalForm));
402 return *npnClass;
403}
404
406 const NPNTable *npnTable, const NPNClass &patternNPN,
407 SmallVectorImpl<unsigned> &permutedIndices) const {
408 const auto &npnClass = getNPNClass(npnTable);
409 npnClass.getInputPermutation(patternNPN, permutedIndices);
410}
411
412LogicalResult
414 SmallVectorImpl<DelayType> &results) const {
415 results.reserve(getInputSize());
416 const auto &network = enumerator.getLogicNetwork();
417
418 // Compute arrival times for each input.
419 for (auto inputIndex : inputs) {
420 if (isAlwaysCutInput(network, inputIndex)) {
421 // If the input is a primary input, it has no delay.
422 results.push_back(0);
423 continue;
424 }
425 auto *cutSet = enumerator.getCutSet(inputIndex);
426 assert(cutSet && "Input must have a valid cut set");
427
428 // If there is no matching pattern, it means it's not possible to use the
429 // input in the cut rewriting. Return empty vector to indicate failure.
430 auto *bestCut = cutSet->getBestMatchedCut();
431 if (!bestCut)
432 return failure();
433
434 const auto &matchedPattern = *bestCut->getMatchedPattern();
435
436 // Get the value for result number lookup
437 mlir::Value inputValue = network.getValue(inputIndex);
438 // Otherwise, the cut input is an op result. Get the arrival time
439 // from the matched pattern.
440 results.push_back(matchedPattern.getArrivalTime(
441 cast<mlir::OpResult>(inputValue).getResultNumber()));
442 }
443
444 return success();
445}
446
447void Cut::dump(llvm::raw_ostream &os, const LogicNetwork &network) const {
448 os << "// === Cut Dump ===\n";
449 os << "Cut with " << getInputSize() << " inputs";
450 if (rootIndex != 0) {
451 auto *rootOp = network.getGate(rootIndex).getOperation();
452 if (rootOp)
453 os << " and root: " << *rootOp;
454 }
455 os << "\n";
456
457 if (isTrivialCut()) {
458 mlir::Value inputVal = network.getValue(inputs[0]);
459 os << "Primary input cut: " << inputVal << "\n";
460 return;
461 }
462
463 os << "Inputs (indices): \n";
464 for (auto [idx, inputIndex] : llvm::enumerate(inputs)) {
465 mlir::Value inputVal = network.getValue(inputIndex);
466 os << " Input " << idx << " (index " << inputIndex << "): " << inputVal
467 << "\n";
468 }
469
470 if (rootIndex != 0) {
471 os << "\nRoot operation: \n";
472 if (auto *rootOp = network.getGate(rootIndex).getOperation())
473 rootOp->print(os);
474 os << "\n";
475 }
476
477 auto &npnClass = getNPNClass();
478 npnClass.dump(os);
479
480 os << "// === Cut End ===\n";
481}
482
483unsigned Cut::getInputSize() const { return inputs.size(); }
484
485unsigned Cut::getOutputSize(const LogicNetwork &network) const {
486 if (rootIndex == 0)
487 return 1; // Trivial cut has 1 output
488 auto *rootOp = network.getGate(rootIndex).getOperation();
489 return rootOp ? rootOp->getNumResults() : 1;
490}
491
492/// Simulate a gate and return its truth table.
493static inline llvm::APInt applyGateSemantics(LogicNetworkGate::Kind kind,
494 const llvm::APInt &a) {
495 switch (kind) {
497 return a;
498 default:
499 llvm_unreachable("Unsupported unary operation for truth table computation");
500 }
501}
502
503static inline llvm::APInt applyGateSemantics(LogicNetworkGate::Kind kind,
504 const llvm::APInt &a,
505 const llvm::APInt &b) {
506 switch (kind) {
508 return a & b;
510 return a ^ b;
511 default:
512 llvm_unreachable(
513 "Unsupported binary operation for truth table computation");
514 }
515}
516
517static inline llvm::APInt applyGateSemantics(LogicNetworkGate::Kind kind,
518 const llvm::APInt &a,
519 const llvm::APInt &b,
520 const llvm::APInt &c) {
521 switch (kind) {
523 return evaluateMuxLogic(a, b, c);
525 return evaluateMajorityLogic(a, b, c);
527 return evaluateDotLogic(a, b, c);
529 return evaluateOneHotLogic(a, b, c);
531 return evaluateGambleLogic(a, b, c);
532 default:
533 llvm_unreachable(
534 "Unsupported ternary operation for truth table computation");
535 }
536}
537
538namespace {
539
540// Helper class to build a merged truth table for a cut based on its operand
541// cuts
542struct MergedTruthTableBuilder {
543 MergedTruthTableBuilder(ArrayRef<uint32_t> mergedInputs,
544 ArrayRef<const Cut *> operandCuts)
545 : mergedInputs(mergedInputs), numMergedInputs(mergedInputs.size()),
546 operandCuts(operandCuts) {
547 assert(llvm::is_sorted(mergedInputs) && "merged inputs must be sorted");
548 assert(llvm::adjacent_find(mergedInputs) == mergedInputs.end() &&
549 "merged inputs must be unique");
550 }
551
552 ArrayRef<uint32_t> mergedInputs;
553 unsigned numMergedInputs;
554 ArrayRef<const Cut *> operandCuts;
555
556 std::optional<unsigned> findMergedInputPosition(uint32_t operandIdx) const {
557 auto *it = llvm::find(mergedInputs, operandIdx);
558 if (it == mergedInputs.end())
559 return std::nullopt;
560 return static_cast<unsigned>(std::distance(mergedInputs.begin(), it));
561 }
562
563 const Cut *findOperandCut(uint32_t operandIdx) const {
564 for (const Cut *cut : operandCuts) {
565 if (!cut)
566 continue;
567 uint32_t cutOutput =
568 cut->isTrivialCut() ? cut->inputs[0] : cut->getRootIndex();
569 if (cutOutput == operandIdx)
570 return cut;
571 }
572 return nullptr;
573 }
574
575 void getInputMapping(const Cut *cut,
576 SmallVectorImpl<unsigned> &mapping) const {
577 mapping.clear();
578 mapping.reserve(cut->inputs.size());
579 for (uint32_t idx : cut->inputs) {
580 auto *it = llvm::find(mergedInputs, idx);
581 assert(it != mergedInputs.end() &&
582 "cut input must exist in merged inputs");
583 mapping.push_back(static_cast<unsigned>(it - mergedInputs.begin()));
584 }
585 }
586
587 llvm::APInt expandCutTruthTable(const Cut *cut) const {
588 const auto &cutTT = *cut->getTruthTable();
589 SmallVector<unsigned, 8> inputMapping;
590 getInputMapping(cut, inputMapping);
592 cutTT.table, inputMapping, numMergedInputs);
593 }
594
595 llvm::APInt expandOperand(uint32_t operandIdx, bool isInverted) const {
596 llvm::APInt result(1, 0);
597 if (operandIdx == LogicNetwork::kConstant0) {
598 result = llvm::APInt::getZero(1U << numMergedInputs);
599 } else if (operandIdx == LogicNetwork::kConstant1) {
600 result = llvm::APInt::getAllOnes(1U << numMergedInputs);
601 } else if (auto pos = findMergedInputPosition(operandIdx)) {
602 // Direct cut inputs already live in the merged input space.
603 result = circt::createVarMask(numMergedInputs, *pos, true);
604 } else if (const Cut *cut = findOperandCut(operandIdx)) {
605 // Internal operands reuse the operand cut truth table after expanding it
606 // to this root cut's merged input space.
607 result = expandCutTruthTable(cut);
608 } else {
609 llvm_unreachable("Operand not found in cuts or merged inputs");
610 }
611
612 if (isInverted)
613 result.flipAllBits();
614 return result;
615 }
616
617 BinaryTruthTable computeForGate(const LogicNetworkGate &rootGate) const {
618 auto getEdgeTT = [&](unsigned edgeIdx) {
619 const auto &edge = rootGate.edges[edgeIdx];
620 return expandOperand(edge.getIndex(), edge.isInverted());
621 };
622
623 switch (rootGate.getKind()) {
626 return BinaryTruthTable(
627 numMergedInputs, 1,
628 applyGateSemantics(rootGate.getKind(), getEdgeTT(0), getEdgeTT(1)));
634 return BinaryTruthTable(numMergedInputs, 1,
635 applyGateSemantics(rootGate.getKind(),
636 getEdgeTT(0), getEdgeTT(1),
637 getEdgeTT(2)));
639 return BinaryTruthTable(
640 numMergedInputs, 1,
641 applyGateSemantics(rootGate.getKind(), getEdgeTT(0)));
642 default:
643 llvm_unreachable("Unsupported operation for truth table computation");
644 }
645 }
646};
647
648} // namespace
649
651 if (isTrivialCut()) {
652 assert(truthTable && "trivial cuts should have their truth table pre-set");
653 return;
654 }
655
656 assert(!operandCuts.empty() &&
657 "non-trivial cuts must carry operand cuts for truth table expansion");
658
659 const auto &rootGate = network.getGate(rootIndex);
660 truthTable.emplace(
661 MergedTruthTableBuilder(inputs, operandCuts).computeForGate(rootGate));
662}
663
664bool Cut::dominates(const Cut &other) const {
665 return dominates(other.inputs, other.signature);
666}
667
668bool Cut::dominates(ArrayRef<uint32_t> otherInputs, uint64_t otherSig) const {
669
670 if (getInputSize() > otherInputs.size())
671 return false;
672
673 if ((signature & otherSig) != signature)
674 return false;
675
676 return llvm::includes(otherInputs, inputs);
677}
678
679Cut Cut::getTrivialCut(uint32_t index) {
680 Cut cut;
681 cut.inputs.push_back(index);
682 // The truth table for a trivial cut is just the identity function on its
683 // single input.
684 cut.setTruthTable(BinaryTruthTable(1, 1, llvm::APInt(2, 2)));
685 cut.setSignature(1ULL << (index % 64)); // Set signature bit for this input
686 return cut;
687}
688
689//===----------------------------------------------------------------------===//
690// MatchedPattern
691//===----------------------------------------------------------------------===//
692
693ArrayRef<DelayType> MatchedPattern::getArrivalTimes() const {
694 assert(pattern && "Pattern must be set to get arrival time");
695 return arrivalTimes;
696}
697
699 assert(pattern && "Pattern must be set to get arrival time");
700 return arrivalTimes[index];
701}
702
704 assert(pattern && "Pattern must be set to get the pattern");
705 return pattern;
706}
707
709 assert(pattern && "Pattern must be set to get area");
710 return area;
711}
712
713//===----------------------------------------------------------------------===//
714// CutSet
715//===----------------------------------------------------------------------===//
716
718
719unsigned CutSet::size() const { return cuts.size(); }
720
721void CutSet::addCut(Cut *cut) {
722 assert(!isFrozen && "Cannot add cuts to a frozen cut set");
723 cuts.push_back(cut);
724}
725
726ArrayRef<Cut *> CutSet::getCuts() const { return cuts; }
727
728// Remove duplicate cuts and non-minimal cuts. A cut is non-minimal if there
729// exists another cut that is a subset of it.
730static void removeDuplicateAndNonMinimalCuts(SmallVectorImpl<Cut *> &cuts) {
731 auto dumpInputs = [](llvm::raw_ostream &os,
732 const llvm::SmallVectorImpl<uint32_t> &inputs) {
733 os << "{";
734 llvm::interleaveComma(inputs, os);
735 os << "}";
736 };
737 // Sort by size, then lexicographically by inputs. This enables cheap exact
738 // duplicate elimination and tighter candidate filtering for subset checks.
739 llvm::stable_sort(cuts, [](const Cut *a, const Cut *b) {
740 if (a->getInputSize() != b->getInputSize())
741 return a->getInputSize() < b->getInputSize();
742 return std::lexicographical_compare(a->inputs.begin(), a->inputs.end(),
743 b->inputs.begin(), b->inputs.end());
744 });
745
746 // Group kept cuts by input size so subset checks only visit smaller cuts.
747 unsigned maxCutSize = cuts.empty() ? 0 : cuts.back()->getInputSize();
748 llvm::SmallVector<llvm::SmallVector<Cut *, 4>, 16> keptBySize(maxCutSize + 1);
749
750 // Compact kept cuts in-place.
751 unsigned uniqueCount = 0;
752 for (Cut *cut : cuts) {
753 unsigned cutSize = cut->getInputSize();
754
755 // Fast exact duplicate check: with lexicographic sort, duplicates are
756 // adjacent among cuts with equal size.
757 if (uniqueCount > 0) {
758 Cut *lastKept = cuts[uniqueCount - 1];
759 if (lastKept->getInputSize() == cutSize &&
760 lastKept->inputs == cut->inputs)
761 continue;
762 }
763
764 bool isDominated = false;
765 for (unsigned existingSize = 1; existingSize < cutSize && !isDominated;
766 ++existingSize) {
767 for (const Cut *existingCut : keptBySize[existingSize]) {
768 if (!existingCut->dominates(*cut))
769 continue;
770
771 LLVM_DEBUG({
772 llvm::dbgs() << "Dropping non-minimal cut ";
773 dumpInputs(llvm::dbgs(), cut->inputs);
774 llvm::dbgs() << " due to subset ";
775 dumpInputs(llvm::dbgs(), existingCut->inputs);
776 llvm::dbgs() << "\n";
777 });
778 isDominated = true;
779 break;
780 }
781 }
782
783 if (isDominated)
784 continue;
785
786 cuts[uniqueCount++] = cut;
787 keptBySize[cutSize].push_back(cut);
788 }
789
790 LLVM_DEBUG(llvm::dbgs() << "Original cuts: " << cuts.size()
791 << " Unique cuts: " << uniqueCount << "\n");
792
793 // Resize the cuts vector to the number of surviving cuts.
794 cuts.resize(uniqueCount);
795}
796
798 const CutRewriterOptions &options,
799 llvm::function_ref<std::optional<MatchedPattern>(const Cut &)> matchCut,
800 const LogicNetwork &logicNetwork) {
801
802 // Remove duplicate/non-minimal cuts first so all follow-up work only runs on
803 // survivors.
805
806 // Compute truth tables lazily, then match cuts to collect timing/area data.
807 for (Cut *cut : cuts) {
808 if (!cut->getTruthTable().has_value())
809 cut->computeTruthTableFromOperands(logicNetwork);
810
811 assert(cut->getInputSize() <= options.maxCutInputSize &&
812 "Cut input size exceeds maximum allowed size");
813
814 if (auto matched = matchCut(*cut))
815 cut->setMatchedPattern(std::move(*matched));
816 }
817
818 // Sort cuts by priority to select the most promising ones.
819 // Priority is determined by the optimization strategy:
820 // - Trivial cuts (direct connections) have highest priority
821 // - Among matched cuts, compare by area/delay based on the strategy
822 // - Matched cuts are preferred over unmatched cuts
823 // See "Combinational and Sequential Mapping with Priority Cuts" by Mishchenko
824 // et al., ICCAD 2007 for more details.
825 // TODO: Use a priority queue instead of sorting for better performance.
826
827 // Partition the cuts into trivial and non-trivial cuts.
828 auto *trivialCutsEnd =
829 std::stable_partition(cuts.begin(), cuts.end(),
830 [](const Cut *cut) { return cut->isTrivialCut(); });
831
832 auto isBetterCut = [&options](const Cut *a, const Cut *b) {
833 assert(!a->isTrivialCut() && !b->isTrivialCut() &&
834 "Trivial cuts should have been excluded");
835 const auto &aMatched = a->getMatchedPattern();
836 const auto &bMatched = b->getMatchedPattern();
837
838 if (aMatched && bMatched)
839 return compareDelayAndArea(
840 options.strategy, aMatched->getArea(), aMatched->getArrivalTimes(),
841 bMatched->getArea(), bMatched->getArrivalTimes());
842
843 if (static_cast<bool>(aMatched) != static_cast<bool>(bMatched))
844 return static_cast<bool>(aMatched);
845
846 return a->getInputSize() < b->getInputSize();
847 };
848 std::stable_sort(trivialCutsEnd, cuts.end(), isBetterCut);
849
850 // Keep only the top-K cuts to bound growth.
851 if (cuts.size() > options.maxCutSizePerRoot)
852 cuts.resize(options.maxCutSizePerRoot);
853
854 // Select the best cut from the remaining candidates.
855 bestCut = nullptr;
856 for (Cut *cut : cuts) {
857 const auto &currentMatch = cut->getMatchedPattern();
858 if (!currentMatch)
859 continue;
860 bestCut = cut;
861 break;
862 }
863
864 LLVM_DEBUG({
865 llvm::dbgs() << "Finalized cut set with " << cuts.size() << " cuts and "
866 << (bestCut
867 ? "matched pattern to " + bestCut->getMatchedPattern()
868 ->getPattern()
869 ->getPatternName()
870 : "no matched pattern")
871 << "\n";
872 });
873
874 isFrozen = true; // Mark the cut set as frozen
875}
876
877//===----------------------------------------------------------------------===//
878// CutRewritePattern
879//===----------------------------------------------------------------------===//
880
882 SmallVectorImpl<NPNClass> &matchingNPNClasses) const {
883 return false;
884}
885
886//===----------------------------------------------------------------------===//
887// CutRewritePatternSet
888//===----------------------------------------------------------------------===//
889
891 llvm::SmallVector<std::unique_ptr<CutRewritePattern>, 4> patterns)
892 : patterns(std::move(patterns)) {
893 // Initialize the NPN to pattern map
894 for (auto &pattern : this->patterns) {
895 SmallVector<NPNClass, 2> npnClasses;
896 auto result = pattern->useTruthTableMatcher(npnClasses);
897 if (result) {
898 for (auto npnClass : npnClasses) {
899 // Create a NPN class from the truth table
900 npnToPatternMap[{npnClass.truthTable.table,
901 npnClass.truthTable.numInputs}]
902 .push_back(std::make_pair(std::move(npnClass), pattern.get()));
903 }
904 } else {
905 // If the pattern does not provide NPN classes, we use a special key
906 // to indicate that it should be considered for all cuts.
907 nonNPNPatterns.push_back(pattern.get());
908 }
909 }
910}
911
912//===----------------------------------------------------------------------===//
913// CutEnumerator
914//===----------------------------------------------------------------------===//
915
919
921 CutSet *cutSet = cutSetAllocator.create();
922 auto [cutSetPtr, inserted] = cutSets.try_emplace(index, cutSet);
923 assert(inserted && "Cut set already exists for this index");
924 return cutSetPtr->second;
925}
926
928 cutSets.clear();
929 processingOrder.clear();
930 logicNetwork.clear();
931 cutAllocator.DestroyAll();
932 cutSetAllocator.DestroyAll();
933}
934
935LogicalResult CutEnumerator::visitLogicOp(uint32_t nodeIndex) {
936 const auto &gate = logicNetwork.getGate(nodeIndex);
937 auto *logicOp = gate.getOperation();
938 assert(logicOp && logicOp->getNumResults() == 1 &&
939 "Logic operation must have a single result");
940
941 if (gate.getKind() == LogicNetworkGate::Choice) {
942 auto choiceOp = cast<synth::ChoiceOp>(logicOp);
943 auto *resultCutSet = createNewCutSet(nodeIndex);
944 Cut *primaryInputCut = cutAllocator.create(Cut::getTrivialCut(nodeIndex));
945 processingOrder.push_back(nodeIndex);
946 resultCutSet->addCut(primaryInputCut);
947
948 for (Value operand : choiceOp.getInputs()) {
949 auto *operandCutSet = getCutSet(logicNetwork.getIndex(operand));
950 if (!operandCutSet)
951 return logicOp->emitError("Failed to get cut set for choice operand");
952
953 // Choice nodes do not introduce new logic. They forward each non-trivial
954 // operand cut as an equivalent alternative for the same root.
955 for (const Cut *operandCut : operandCutSet->getCuts()) {
956 if (operandCut->isTrivialCut())
957 continue;
958
959 resultCutSet->addCut(cutAllocator.create(
960 nodeIndex, operandCut->inputs, operandCut->getSignature(),
961 ArrayRef<const Cut *>{operandCut}, *operandCut->getTruthTable()));
962 }
963 }
964
965 // Finalize cut set: remove duplicates, limit size, and match patterns
966 resultCutSet->finalize(options, matchCut, logicNetwork);
967 return success();
968 }
969
970 unsigned numFanins = gate.getNumFanins();
971
972 // Validate operation constraints
973 // TODO: Variadic operations and non-single-bit results can be supported
974 if (numFanins > 3)
975 return logicOp->emitError("Cut enumeration supports at most 3 operands, "
976 "found: ")
977 << numFanins;
978 if (!logicOp->getOpResult(0).getType().isInteger(1))
979 return logicOp->emitError()
980 << "Supported logic operations must have a single bit "
981 "result type but found: "
982 << logicOp->getResult(0).getType();
983
984 // A vector to hold cut sets for each operand along with their max cut input
985 // size.
986 SmallVector<std::pair<const CutSet *, unsigned>, 2> operandCutSets;
987 operandCutSets.reserve(numFanins);
988
989 // Collect cut sets for each fanin (using LogicNetwork edges)
990 for (unsigned i = 0; i < numFanins; ++i) {
991 uint32_t faninIndex = gate.edges[i].getIndex();
992 auto *operandCutSet = getCutSet(faninIndex);
993 if (!operandCutSet)
994 return logicOp->emitError("Failed to get cut set for fanin index ")
995 << faninIndex;
996
997 // Find the largest cut size among the operand's cuts for sorting heuristic
998 // later.
999 unsigned maxInputCutSize = 0;
1000 for (auto *cut : operandCutSet->getCuts())
1001 maxInputCutSize = std::max(maxInputCutSize, cut->getInputSize());
1002 operandCutSets.push_back(std::make_pair(operandCutSet, maxInputCutSize));
1003 }
1004
1005 // Create the trivial cut for this node's output
1006 Cut *primaryInputCut = cutAllocator.create(Cut::getTrivialCut(nodeIndex));
1007
1008 auto *resultCutSet = createNewCutSet(nodeIndex);
1009 processingOrder.push_back(nodeIndex);
1010 resultCutSet->addCut(primaryInputCut);
1011
1012 // Sort operand cut sets by their largest cut size in descending order. This
1013 // heuristic improves efficiency of the k-way merge when generating cuts for
1014 // the current node by maximizing the chance of early pruning when the merged
1015 // cut exceeds the input size limit.
1016 llvm::stable_sort(operandCutSets,
1017 [](const std::pair<const CutSet *, unsigned> &a,
1018 const std::pair<const CutSet *, unsigned> &b) {
1019 return a.second > b.second;
1020 });
1021
1022 // Cache maxCutInputSize to avoid repeated access
1023 unsigned maxInputSize = options.maxCutInputSize;
1024
1025 // This lambda generates nested loops at runtime to iterate over all
1026 // combinations of cuts from N operands
1027 auto enumerateCutCombinations = [&](auto &&self, unsigned operandIdx,
1028 SmallVector<const Cut *, 3> &cutPtrs,
1029 uint64_t currentSig) -> void {
1030 // Base case: all operands processed, create merged cut
1031 if (operandIdx == numFanins) {
1032 // Efficient k-way merge: inputs are sorted, so dedup and constant
1033 // filtering can be done while merging. Abort early once we exceed the
1034 // cut-size limit to avoid building doomed merged cuts.
1035 SmallVector<uint32_t, 6> mergedInputs;
1036 auto appendMergedInput = [&](uint32_t value) {
1037 if (value == LogicNetwork::kConstant0 ||
1038 value == LogicNetwork::kConstant1)
1039 return true;
1040 if (!mergedInputs.empty() && mergedInputs.back() == value)
1041 return true;
1042 mergedInputs.push_back(value);
1043 return mergedInputs.size() <= maxInputSize;
1044 };
1045
1046 if (numFanins == 1) {
1047 // Single input: copy while filtering constants.
1048 mergedInputs.reserve(
1049 std::min<size_t>(cutPtrs[0]->inputs.size(), maxInputSize));
1050 for (uint32_t value : cutPtrs[0]->inputs)
1051 if (!appendMergedInput(value))
1052 return;
1053 } else if (numFanins == 2) {
1054 // Two-way merge (common case for AND gates)
1055 const auto &inputs0 = cutPtrs[0]->inputs;
1056 const auto &inputs1 = cutPtrs[1]->inputs;
1057 mergedInputs.reserve(
1058 std::min<size_t>(inputs0.size() + inputs1.size(), maxInputSize));
1059
1060 unsigned i = 0, j = 0;
1061 while (i < inputs0.size() || j < inputs1.size()) {
1062 uint32_t next;
1063 if (j == inputs1.size() ||
1064 (i < inputs0.size() && inputs0[i] <= inputs1[j])) {
1065 next = inputs0[i++];
1066 if (j < inputs1.size() && inputs1[j] == next)
1067 ++j;
1068 } else {
1069 next = inputs1[j++];
1070 }
1071 if (!appendMergedInput(next))
1072 return;
1073 }
1074 } else {
1075 // Three-way merge (for MAJ/MUX gates)
1076 const SmallVectorImpl<uint32_t> &inputs0 = cutPtrs[0]->inputs;
1077 const SmallVectorImpl<uint32_t> &inputs1 = cutPtrs[1]->inputs;
1078 const SmallVectorImpl<uint32_t> &inputs2 = cutPtrs[2]->inputs;
1079 mergedInputs.reserve(std::min<size_t>(
1080 inputs0.size() + inputs1.size() + inputs2.size(), maxInputSize));
1081
1082 unsigned i = 0, j = 0, k = 0;
1083 while (i < inputs0.size() || j < inputs1.size() || k < inputs2.size()) {
1084 // Find minimum among available elements
1085 uint32_t minVal = UINT32_MAX;
1086 if (i < inputs0.size())
1087 minVal = std::min(minVal, inputs0[i]);
1088 if (j < inputs1.size())
1089 minVal = std::min(minVal, inputs1[j]);
1090 if (k < inputs2.size())
1091 minVal = std::min(minVal, inputs2[k]);
1092
1093 // Advance all iterators pointing to minVal (handles duplicates)
1094 if (i < inputs0.size() && inputs0[i] == minVal)
1095 i++;
1096 if (j < inputs1.size() && inputs1[j] == minVal)
1097 j++;
1098 if (k < inputs2.size() && inputs2[k] == minVal)
1099 k++;
1100
1101 if (!appendMergedInput(minVal))
1102 return;
1103 }
1104 }
1105
1106 // Create the merged cut.
1107 Cut *mergedCut = cutAllocator.create(nodeIndex, mergedInputs, currentSig,
1108 ArrayRef<const Cut *>(cutPtrs));
1109 resultCutSet->addCut(mergedCut);
1110
1111 LLVM_DEBUG({
1112 if (mergedCut->inputs.size() >= 4) {
1113 llvm::dbgs() << "Generated cut for node " << nodeIndex;
1114 if (logicOp)
1115 llvm::dbgs() << " (" << logicOp->getName() << ")";
1116 llvm::dbgs() << " inputs=";
1117 llvm::interleaveComma(mergedCut->inputs, llvm::dbgs());
1118 llvm::dbgs() << "\n";
1119 }
1120 });
1121 return;
1122 }
1123
1124 // Recursive case: iterate over cuts for current operand
1125 const CutSet *currentCutSet = operandCutSets[operandIdx].first;
1126 for (const Cut *cut : currentCutSet->getCuts()) {
1127 uint64_t cutSig = cut->getSignature();
1128 uint64_t newSig = currentSig | cutSig;
1129 if (static_cast<unsigned>(llvm::popcount(newSig)) > maxInputSize)
1130 continue; // Early rejection based on signature
1131
1132 cutPtrs.push_back(cut);
1133
1134 // Recurse to next operand
1135 self(self, operandIdx + 1, cutPtrs, newSig);
1136
1137 cutPtrs.pop_back();
1138 }
1139 };
1140
1141 // Start the recursion with empty cut pointer list and zero signature
1142 SmallVector<const Cut *, 3> cutPtrs;
1143 cutPtrs.reserve(numFanins);
1144 enumerateCutCombinations(enumerateCutCombinations, 0, cutPtrs, 0ULL);
1145
1146 // Finalize cut set: remove duplicates, limit size, and match patterns
1147 resultCutSet->finalize(options, matchCut, logicNetwork);
1148
1149 return success();
1150}
1151
1153 Operation *topOp,
1154 llvm::function_ref<std::optional<MatchedPattern>(const Cut &)> matchCut) {
1155 LLVM_DEBUG(llvm::dbgs() << "Enumerating cuts for module: " << topOp->getName()
1156 << "\n");
1157 // Topologically sort the logic network
1158 if (failed(topologicallySortLogicNetwork(topOp)))
1159 return failure();
1160
1161 // Store the pattern matching function for use during cut finalization
1162 this->matchCut = matchCut;
1163
1164 // Build the flat logic network representation for efficient simulation
1165 auto &block = topOp->getRegion(0).getBlocks().front();
1166 if (failed(logicNetwork.buildFromBlock(&block)))
1167 return failure();
1168
1169 for (const auto &[index, gate] : llvm::enumerate(logicNetwork.getGates())) {
1170 // Skip non-logic gates.
1171 if (!gate.isLogicGate())
1172 continue;
1173
1174 // Ensure cut set exists for each logic gate
1175 if (failed(visitLogicOp(index)))
1176 return failure();
1177 }
1178
1179 LLVM_DEBUG(llvm::dbgs() << "Cut enumeration completed successfully\n");
1180 return success();
1181}
1182
1183const CutSet *CutEnumerator::getCutSet(uint32_t index) {
1184 // Check if cut set already exists
1185 auto it = cutSets.find(index);
1186 if (it == cutSets.end()) {
1187 // Create new cut set for an unprocessed value (primary input or other)
1188 CutSet *cutSet = cutSetAllocator.create();
1189 Cut *trivialCut = cutAllocator.create(Cut::getTrivialCut(index));
1190 cutSet->addCut(trivialCut);
1191 auto [newIt, inserted] = cutSets.insert({index, cutSet});
1192 assert(inserted && "Cut set already exists for this index");
1193 it = newIt;
1194 }
1195
1196 return it->second;
1197}
1198
1199/// Generate a human-readable name for a value used in test output.
1200/// This function creates meaningful names for values to make debug output
1201/// and test results more readable and understandable.
1202static StringRef
1203getTestVariableName(Value value, DenseMap<OperationName, unsigned> &opCounter) {
1204 if (auto *op = value.getDefiningOp()) {
1205 // Handle values defined by operations
1206 // First, check if the operation already has a name hint attribute
1207 if (auto name = op->getAttrOfType<StringAttr>("sv.namehint"))
1208 return name.getValue();
1209
1210 // For single-result operations, generate a unique name based on operation
1211 // type
1212 if (op->getNumResults() == 1) {
1213 auto opName = op->getName();
1214 auto count = opCounter[opName]++;
1215
1216 // Create a unique name by appending a counter to the operation name
1217 SmallString<16> nameStr;
1218 nameStr += opName.getStringRef();
1219 nameStr += "_";
1220 nameStr += std::to_string(count);
1221
1222 // Store the generated name as a hint attribute for future reference
1223 auto nameAttr = StringAttr::get(op->getContext(), nameStr);
1224 op->setAttr("sv.namehint", nameAttr);
1225 return nameAttr;
1226 }
1227
1228 // Multi-result operations or other cases get a generic name
1229 return "<unknown>";
1230 }
1231
1232 // Handle block arguments
1233 auto blockArg = cast<BlockArgument>(value);
1234 auto hwOp =
1235 dyn_cast<circt::hw::HWModuleOp>(blockArg.getOwner()->getParentOp());
1236 if (!hwOp)
1237 return "<unknown>";
1238
1239 // Return the formal input name from the hardware module
1240 return hwOp.getInputName(blockArg.getArgNumber());
1241}
1242
1244 DenseMap<OperationName, unsigned> opCounter;
1245 for (auto index : processingOrder) {
1246 auto it = cutSets.find(index);
1247 if (it == cutSets.end())
1248 continue;
1249 auto &cutSet = *it->second;
1250 mlir::Value value = logicNetwork.getValue(index);
1251 llvm::outs() << getTestVariableName(value, opCounter) << " "
1252 << cutSet.getCuts().size() << " cuts:";
1253 for (const Cut *cut : cutSet.getCuts()) {
1254 llvm::outs() << " {";
1255 llvm::interleaveComma(cut->inputs, llvm::outs(), [&](uint32_t inputIdx) {
1256 mlir::Value inputVal = logicNetwork.getValue(inputIdx);
1257 llvm::outs() << getTestVariableName(inputVal, opCounter);
1258 });
1259 auto &pattern = cut->getMatchedPattern();
1260 llvm::outs() << "}"
1261 << "@t" << cut->getTruthTable()->table.getZExtValue() << "d";
1262 if (pattern) {
1263 llvm::outs() << *std::max_element(pattern->getArrivalTimes().begin(),
1264 pattern->getArrivalTimes().end());
1265 } else {
1266 llvm::outs() << "0";
1267 }
1268 }
1269 llvm::outs() << "\n";
1270 }
1271 llvm::outs() << "Cut enumeration completed successfully\n";
1272}
1273
1274//===----------------------------------------------------------------------===//
1275// CutRewriter
1276//===----------------------------------------------------------------------===//
1277
1278LogicalResult CutRewriter::run(Operation *topOp) {
1279 LLVM_DEBUG({
1280 llvm::dbgs() << "Starting Cut Rewriter\n";
1281 llvm::dbgs() << "Mode: "
1282 << (OptimizationStrategyArea == options.strategy ? "area"
1283 : "timing")
1284 << "\n";
1285 llvm::dbgs() << "Max input size: " << options.maxCutInputSize << "\n";
1286 llvm::dbgs() << "Max cut size: " << options.maxCutSizePerRoot << "\n";
1287 });
1288
1289 // Currently we don't support patterns with multiple outputs.
1290 // So check that.
1291 // TODO: This must be removed when we support multiple outputs.
1292 for (auto &pattern : patterns.patterns) {
1293 if (pattern->getNumOutputs() > 1) {
1294 return mlir::emitError(pattern->getLoc(),
1295 "Cut rewriter does not support patterns with "
1296 "multiple outputs yet");
1297 }
1298 }
1299
1300 // First sort the operations topologically to ensure we can process them
1301 // in a valid order.
1302 if (failed(topologicallySortLogicNetwork(topOp)))
1303 return failure();
1304
1305 // Enumerate cuts for all nodes (initial delay-oriented selection)
1306 if (failed(enumerateCuts(topOp)))
1307 return failure();
1308
1309 // Dump cuts if testing priority cuts.
1310 if (options.testPriorityCuts) {
1311 cutEnumerator.dump();
1312 return success();
1313 }
1314
1315 // Select best cuts and perform mapping
1316 if (failed(runBottomUpRewrite(topOp)))
1317 return failure();
1318
1319 return success();
1320}
1321
1322LogicalResult CutRewriter::enumerateCuts(Operation *topOp) {
1323 LLVM_DEBUG(llvm::dbgs() << "Enumerating cuts...\n");
1324
1325 return cutEnumerator.enumerateCuts(
1326 topOp, [&](const Cut &cut) -> std::optional<MatchedPattern> {
1327 // Match the cut against the patterns
1328 return patternMatchCut(cut);
1329 });
1330}
1331
1332ArrayRef<std::pair<NPNClass, const CutRewritePattern *>>
1334 if (patterns.npnToPatternMap.empty())
1335 return {};
1336
1337 auto &npnClass = cut.getNPNClass(options.npnTable);
1338 auto it = patterns.npnToPatternMap.find(
1339 {npnClass.truthTable.table, npnClass.truthTable.numInputs});
1340 if (it == patterns.npnToPatternMap.end())
1341 return {};
1342 return it->getSecond();
1343}
1344
1345std::optional<MatchedPattern> CutRewriter::patternMatchCut(const Cut &cut) {
1346 if (cut.isTrivialCut())
1347 return {};
1348
1349 const auto &network = cutEnumerator.getLogicNetwork();
1350 const CutRewritePattern *bestPattern = nullptr;
1351 SmallVector<DelayType, 4> inputArrivalTimes;
1352 SmallVector<DelayType, 1> bestArrivalTimes;
1353 double bestArea = 0.0;
1354 inputArrivalTimes.reserve(cut.getInputSize());
1355 bestArrivalTimes.reserve(cut.getOutputSize(network));
1356
1357 // Compute arrival times for each input.
1358 if (failed(cut.getInputArrivalTimes(cutEnumerator, inputArrivalTimes)))
1359 return {};
1360
1361 auto computeArrivalTimeAndPickBest =
1362 [&](const CutRewritePattern *pattern, const MatchResult &matchResult,
1363 llvm::function_ref<unsigned(unsigned)> mapIndex) {
1364 SmallVector<DelayType, 1> outputArrivalTimes;
1365 // Compute the maximum delay for each output from inputs.
1366 for (unsigned outputIndex = 0, outputSize = cut.getOutputSize(network);
1367 outputIndex < outputSize; ++outputIndex) {
1368 // Compute the arrival time for this output.
1369 DelayType outputArrivalTime = 0;
1370 auto delays = matchResult.getDelays();
1371 for (unsigned inputIndex = 0, inputSize = cut.getInputSize();
1372 inputIndex < inputSize; ++inputIndex) {
1373 // Map pattern input i to cut input through NPN transformations
1374 unsigned cutOriginalInput = mapIndex(inputIndex);
1375 outputArrivalTime =
1376 std::max(outputArrivalTime,
1377 delays[outputIndex * inputSize + inputIndex] +
1378 inputArrivalTimes[cutOriginalInput]);
1379 }
1380
1381 outputArrivalTimes.push_back(outputArrivalTime);
1382 }
1383
1384 // Update the arrival time
1385 if (!bestPattern ||
1386 compareDelayAndArea(options.strategy, matchResult.area,
1387 outputArrivalTimes, bestArea,
1388 bestArrivalTimes)) {
1389 LLVM_DEBUG({
1390 llvm::dbgs() << "== Matched Pattern ==============\n";
1391 llvm::dbgs() << "Matching cut: \n";
1392 cut.dump(llvm::dbgs(), network);
1393 llvm::dbgs() << "Found better pattern: "
1394 << pattern->getPatternName();
1395 llvm::dbgs() << " with area: " << matchResult.area;
1396 llvm::dbgs() << " and input arrival times: ";
1397 for (unsigned i = 0; i < inputArrivalTimes.size(); ++i) {
1398 llvm::dbgs() << " " << inputArrivalTimes[i];
1399 }
1400 llvm::dbgs() << " and arrival times: ";
1401
1402 for (auto arrivalTime : outputArrivalTimes) {
1403 llvm::dbgs() << " " << arrivalTime;
1404 }
1405 llvm::dbgs() << "\n";
1406 llvm::dbgs() << "== Matched Pattern End ==============\n";
1407 });
1408
1409 bestArrivalTimes = std::move(outputArrivalTimes);
1410 bestArea = matchResult.area;
1411 bestPattern = pattern;
1412 }
1413 };
1414
1415 for (auto &[patternNPN, pattern] : getMatchingPatternsFromTruthTable(cut)) {
1416 assert(patternNPN.truthTable.numInputs == cut.getInputSize() &&
1417 "Pattern input size must match cut input size");
1418 auto matchResult = pattern->match(cutEnumerator, cut);
1419 if (!matchResult)
1420 continue;
1421 auto &cutNPN = cut.getNPNClass(options.npnTable);
1422
1423 // Get the input mapping from pattern's NPN class to cut's NPN class
1424 SmallVector<unsigned> inputMapping;
1425 cutNPN.getInputPermutation(patternNPN, inputMapping);
1426 computeArrivalTimeAndPickBest(pattern, *matchResult,
1427 [&](unsigned i) { return inputMapping[i]; });
1428 }
1429
1430 for (const CutRewritePattern *pattern : patterns.nonNPNPatterns) {
1431 if (auto matchResult = pattern->match(cutEnumerator, cut))
1432 computeArrivalTimeAndPickBest(pattern, *matchResult,
1433 [&](unsigned i) { return i; });
1434 }
1435
1436 if (!bestPattern)
1437 return {}; // No matching pattern found
1438
1439 return MatchedPattern(bestPattern, std::move(bestArrivalTimes), bestArea);
1440}
1441
1442LogicalResult CutRewriter::runBottomUpRewrite(Operation *top) {
1443 LLVM_DEBUG(llvm::dbgs() << "Performing cut-based rewriting...\n");
1444 const auto &network = cutEnumerator.getLogicNetwork();
1445 const auto &cutSets = cutEnumerator.getCutSets();
1446 auto processingOrder = cutEnumerator.getProcessingOrder();
1447
1448 // Note: Don't clear cutEnumerator yet - we need it during rewrite
1449 UnusedOpPruner pruner;
1450 PatternRewriter rewriter(top->getContext());
1451
1452 // Process in reverse topological order
1453 for (auto index : llvm::reverse(processingOrder)) {
1454 auto it = cutSets.find(index);
1455 if (it == cutSets.end())
1456 continue;
1457
1458 mlir::Value value = network.getValue(index);
1459 auto &cutSet = *it->second;
1460
1461 if (value.use_empty()) {
1462 if (auto *op = value.getDefiningOp())
1463 pruner.eraseNow(op);
1464 continue;
1465 }
1466
1467 if (isAlwaysCutInput(network, index)) {
1468 // If the value is a primary input, skip it
1469 LLVM_DEBUG(llvm::dbgs() << "Skipping inputs: " << value << "\n");
1470 continue;
1471 }
1472
1473 LLVM_DEBUG(llvm::dbgs() << "Cut set for value: " << value << "\n");
1474 auto *bestCut = cutSet.getBestMatchedCut();
1475 if (!bestCut) {
1476 if (options.allowNoMatch)
1477 continue; // No matching pattern found, skip this value
1478 return emitError(value.getLoc(), "No matching cut found for value: ")
1479 << value;
1480 }
1481
1482 // Get the root operation from LogicNetwork
1483 auto *rootOp = network.getGate(bestCut->getRootIndex()).getOperation();
1484 rewriter.setInsertionPoint(rootOp);
1485 const auto &matchedPattern = bestCut->getMatchedPattern();
1486 auto result = matchedPattern->getPattern()->rewrite(rewriter, cutEnumerator,
1487 *bestCut);
1488 if (failed(result))
1489 return failure();
1490
1491 rewriter.replaceOp(rootOp, *result);
1492 cutEnumerator.noteCutRewritten();
1493
1494 if (options.attachDebugTiming) {
1495 auto array = rewriter.getI64ArrayAttr(matchedPattern->getArrivalTimes());
1496 (*result)->setAttr("test.arrival_times", array);
1497 }
1498 }
1499
1500 // Clear the enumerator after rewriting is complete
1501 cutEnumerator.clear();
1502 return success();
1503}
assert(baseType &&"element must be base type")
static llvm::APInt applyGateSemantics(LogicNetworkGate::Kind kind, const llvm::APInt &a)
Simulate a gate and return its truth table.
static void removeDuplicateAndNonMinimalCuts(SmallVectorImpl< Cut * > &cuts)
static bool isAlwaysCutInput(const LogicNetwork &network, uint32_t index)
static StringRef getTestVariableName(Value value, DenseMap< OperationName, unsigned > &opCounter)
Generate a human-readable name for a value used in test output.
static bool compareDelayAndArea(OptimizationStrategy strategy, double newArea, ArrayRef< DelayType > newDelay, double oldArea, ArrayRef< DelayType > oldDelay)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
Precomputed NPN canonicalization table for 4-input single-output functions.
Definition TruthTable.h:168
bool lookup(const BinaryTruthTable &tt, NPNClass &result) const
Returns false if the given truth table shape is unsupported.
Cut enumeration engine for combinational logic networks.
LogicalResult visitLogicOp(uint32_t nodeIndex)
Visit a combinational logic operation and generate cuts.
llvm::SmallVector< uint32_t > processingOrder
Indices in processing order.
const LogicNetwork & getLogicNetwork() const
Get the logic network (read-only).
CutSet * createNewCutSet(uint32_t index)
Create a new cut set for an index.
TrackedSpecificBumpPtrAllocator< CutSet > cutSetAllocator
const CutRewriterOptions & options
Configuration options for cut enumeration.
LogicalResult enumerateCuts(Operation *topOp, llvm::function_ref< std::optional< MatchedPattern >(const Cut &)> matchCut=[](const Cut &) { return std::nullopt;})
Enumerate cuts for all nodes in the given module.
CutEnumeratorStats stats
Statistics for cut enumeration (number of cuts allocated, etc.).
CutEnumerator(const CutRewriterOptions &options)
Constructor for cut enumerator.
void clear()
Clear all cut sets and reset the enumerator.
llvm::function_ref< std::optional< MatchedPattern >(const Cut &)> matchCut
Function to match cuts against available patterns.
llvm::DenseMap< uint32_t, CutSet * > cutSets
Maps indices to their associated cut sets.
const CutSet * getCutSet(uint32_t index)
Get the cut set for a specific index.
LogicNetwork logicNetwork
Flat logic network representation used during enumeration/rewrite.
TrackedSpecificBumpPtrAllocator< Cut > cutAllocator
Typed bump allocators for fast allocation with destructors.
SmallVector< const CutRewritePattern *, 4 > nonNPNPatterns
Patterns that use custom matching logic instead of NPN lookup.
DenseMap< std::pair< APInt, unsigned >, SmallVector< std::pair< NPNClass, const CutRewritePattern * > > > npnToPatternMap
Fast lookup table mapping NPN canonical forms to matching patterns.
CutRewritePatternSet(llvm::SmallVector< std::unique_ptr< CutRewritePattern >, 4 > patterns)
Constructor that takes ownership of the provided patterns.
const CutRewriterOptions & options
Configuration options.
ArrayRef< std::pair< NPNClass, const CutRewritePattern * > > getMatchingPatternsFromTruthTable(const Cut &cut) const
Find patterns that match a cut's truth table.
std::optional< MatchedPattern > patternMatchCut(const Cut &cut)
Match a cut against available patterns and compute arrival time.
LogicalResult enumerateCuts(Operation *topOp)
Enumerate cuts for all nodes in the given module.
LogicalResult run(Operation *topOp)
Execute the complete cut-based rewriting algorithm.
CutEnumerator cutEnumerator
LogicalResult runBottomUpRewrite(Operation *topOp)
Perform the actual circuit rewriting using selected patterns.
Manages a collection of cuts for a single logic node using priority cuts algorithm.
Cut * getBestMatchedCut() const
Get the cut associated with the best matched pattern.
void addCut(Cut *cut)
Add a new cut to this set using bump allocator.
unsigned size() const
Get the number of cuts in this set.
llvm::SmallVector< Cut *, 12 > cuts
Collection of cuts for this node.
ArrayRef< Cut * > getCuts() const
Get read-only access to all cuts in this set.
bool isFrozen
Whether cut set is finalized.
void finalize(const CutRewriterOptions &options, llvm::function_ref< std::optional< MatchedPattern >(const Cut &)> matchCut, const LogicNetwork &logicNetwork)
Finalize the cut set by removing duplicates and selecting the best pattern.
Represents a cut in the combinational logic network.
static Cut getTrivialCut(uint32_t index)
Create a trivial cut for a value.
std::optional< NPNClass > npnClass
Cached NPN canonical form for this cut.
uint64_t signature
Signature bitset for fast cut size estimation.
uint64_t getSignature() const
Get the signature of this cut.
void dump(llvm::raw_ostream &os, const LogicNetwork &network) const
std::optional< MatchedPattern > matchedPattern
const std::optional< MatchedPattern > & getMatchedPattern() const
Get the matched pattern for this cut.
void setTruthTable(BinaryTruthTable tt)
Set the truth table directly (used for incremental computation).
unsigned getOutputSize(const LogicNetwork &network) const
Get the number of outputs from root operation.
const std::optional< BinaryTruthTable > & getTruthTable() const
Get the truth table for this cut.
const NPNClass & getNPNClass() const
Get the NPN canonical form for this cut.
llvm::SmallVector< uint32_t, 6 > inputs
External inputs to this cut (cut boundary).
LogicalResult getInputArrivalTimes(CutEnumerator &enumerator, SmallVectorImpl< DelayType > &results) const
Get arrival times for each input of this cut.
void computeTruthTableFromOperands(const LogicNetwork &network)
Compute truth table using fast incremental method from operand cuts.
llvm::SmallVector< const Cut *, 3 > operandCuts
Operand cuts used to create this cut (for lazy TT computation).
std::optional< BinaryTruthTable > truthTable
Cached truth table for this cut.
void setSignature(uint64_t sig)
Set the signature of this cut.
bool dominates(const Cut &other) const
Check if this cut dominates another (i.e., this cut's inputs are a subset of the other's inputs).
void getPermutatedInputIndices(const NPNTable *npnTable, const NPNClass &patternNPN, SmallVectorImpl< unsigned > &permutedIndices) const
Get the permutated inputs for this cut based on the given pattern NPN.
unsigned getInputSize() const
Get the number of inputs to this cut.
void setMatchedPattern(MatchedPattern pattern)
Matched pattern for this cut.
bool isTrivialCut() const
Check if this cut represents a trivial cut.
uint32_t rootIndex
Root index in LogicNetwork (0 indicates no root for a trivial cut).
Flat logic network representation for efficient cut enumeration.
llvm::SmallVector< LogicNetworkGate > gates
Vector of all gates in the network.
bool hasIndex(Value value) const
Check if a value has been indexed.
static constexpr uint32_t kConstant0
Special constant indices.
uint32_t getOrCreateIndex(Value value)
Get or create an index for a value.
llvm::DenseMap< Value, uint32_t > valueToIndex
Map from MLIR Value to network index.
uint32_t getIndex(Value value) const
Get the raw index for a value.
void getValues(ArrayRef< uint32_t > indices, SmallVectorImpl< Value > &values) const
Fill values for the given raw indices.
uint32_t addPrimaryInput(Value value)
Add a primary input to the network.
Value getValue(uint32_t index) const
Get the value for a given raw index.
LogicalResult buildFromBlock(Block *block)
Build the logic network from a region/block in topological order.
Signal getOrCreateSignal(Value value, bool inverted)
Get or create a Signal for a value.
static constexpr uint32_t kConstant1
llvm::SmallVector< Value > indexToValue
Map from network index to MLIR Value.
void clear()
Clear the network and reset to initial state.
const LogicNetworkGate & getGate(uint32_t index) const
Get the gate at a given index.
uint32_t addGate(Operation *op, LogicNetworkGate::Kind kind, Value result, llvm::ArrayRef< Signal > operands={})
Add a gate with explicit result value and operand signals.
Represents a cut that has been successfully matched to a rewriting pattern.
double area
Area cost of this pattern.
DelayType getArrivalTime(unsigned outputIndex) const
Get the arrival time of signals through this pattern.
ArrayRef< DelayType > getArrivalTimes() const
const CutRewritePattern * pattern
The matched library pattern.
double getArea() const
Get the area cost of using this pattern.
const CutRewritePattern * getPattern() const
Get the library pattern that was matched.
SmallVector< DelayType, 1 > arrivalTimes
Arrival times of outputs from this pattern.
llvm::APInt expandTruthTableToInputSpace(const llvm::APInt &tt, ArrayRef< unsigned > inputMapping, unsigned numExpandedInputs)
Expand a truth table to a larger input space using the given input mapping.
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition HWTypes.cpp:122
OptimizationStrategy
Optimization strategy.
Definition SynthPasses.h:24
@ OptimizationStrategyArea
Optimize for minimal area.
Definition SynthPasses.h:25
@ OptimizationStrategyTiming
Optimize for minimal critical path delay.
Definition SynthPasses.h:26
FailureOr< BinaryTruthTable > getTruthTable(ValueRange values, Block *block)
Get the truth table for operations within a block.
int64_t DelayType
Definition CutRewriter.h:40
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...
Definition SynthOps.cpp:543
static constexpr unsigned maxTruthTableInputs
Maximum number of inputs supported for truth table generation.
Definition CutRewriter.h:45
T evaluateDotLogic(const T &x, const T &y, const T &z)
Evaluate the Boolean function x ^ (z | (x & y)).
Definition SynthOps.h:135
T evaluateGambleLogic(const T &a, const T &b, const T &c)
Definition SynthOps.h:173
T evaluateMajorityLogic(const T &a, const T &b, const T &c)
Definition SynthOps.h:140
T evaluateMuxLogic(const T &a, const T &b, const T &c)
Definition SynthOps.h:168
LogicalResult topologicallySortLogicNetwork(mlir::Operation *op)
bool isLogicNetworkOp(mlir::Operation *op)
T evaluateOneHotLogic(const T &a, const T &b, const T &c)
Definition SynthOps.h:162
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::APInt createVarMask(unsigned numVars, unsigned varIndex, bool positive)
Create a mask for a variable in the truth table.
Represents a boolean function as a truth table.
Definition TruthTable.h:41
Represents the canonical form of a boolean function under NPN equivalence.
Definition TruthTable.h:106
static NPNClass computeNPNCanonicalForm(const BinaryTruthTable &tt)
Compute the canonical NPN form for a given truth table.
void getInputPermutation(const NPNClass &targetNPN, llvm::SmallVectorImpl< unsigned > &permutation) const
Get input permutation from this NPN class to another equivalent NPN class.
Utility that tracks operations that have potentially become unused and allows them to be cleaned up a...
void eraseNow(Operation *op)
Erase an operation immediately, and remove it from the set of ops to be removed later.
Base class for cut rewriting patterns used in combinational logic optimization.
virtual StringRef getPatternName() const
Get the name of this pattern. Used for debugging.
virtual bool useTruthTableMatcher(SmallVectorImpl< NPNClass > &matchingNPNClasses) const
Specify truth tables that this pattern can match.
virtual std::optional< MatchResult > match(CutEnumerator &enumerator, const Cut &cut) const =0
Check if a cut matches this pattern and compute area/delay metrics.
Configuration options for the cut-based rewriting algorithm.
unsigned maxCutInputSize
Maximum number of inputs allowed for any cut.
unsigned maxCutSizePerRoot
Maximum number of cuts to maintain per logic node.
OptimizationStrategy strategy
Optimization strategy (area vs. timing).
Represents a single gate/node in the flat logic network.
Signal edges[3]
Fanin edges (up to 3 inputs).
Operation * getOperation() const
Get the operation pointer (nullptr for constants).
bool isAlwaysCutInput() const
Check if this should always be a cut input (PI or constant).
Kind getKind() const
Get the kind of this gate.
@ Xor2
XOR gate (2-input).
@ And2
AND gate (2-input, aig::AndInverterOp).
@ Dot3
Ordered DOT gate (3-input, synth.dot).
@ OneHot3
OneHot gate (3-input, synth.onehot).
@ Identity
Identity gate (used for 1-input inverter).
@ Gamble3
Ordered Gamble gate (3-input, synth.gamble).
@ Mux3
Ordered MUX gate (3-input, synth.mux_inv).
@ Maj3
Reserved 3-input gate kind.
@ PrimaryInput
Primary input to the network.
@ Choice
Choice node (synth.choice).
@ Constant
Constant 0/1 node (index 0 = const0, index 1 = const1).
Result of matching a cut against a pattern.
Edge representation in the logic network.
Definition CutRewriter.h:75