CIRCT 24.0.0git
Loading...
Searching...
No Matches
FunctionalReduction.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 pass implements FunctionalReduction (Functionally Reduced And-Inverter
10// Graph) optimization. It identifies and merges functionally equivalent nodes
11// through simulation-based candidate detection followed by SAT-based
12// verification.
13//
14//===----------------------------------------------------------------------===//
15
23#include "mlir/IR/Attributes.h"
24#include "mlir/IR/Builders.h"
25#include "mlir/IR/BuiltinOps.h"
26#include "mlir/IR/PatternMatch.h"
27#include "mlir/Pass/Pass.h"
28#include "mlir/Support/LogicalResult.h"
29#include "llvm/ADT/APInt.h"
30#include "llvm/ADT/ArrayRef.h"
31#include "llvm/ADT/DenseMap.h"
32#include "llvm/ADT/DenseSet.h"
33#include "llvm/ADT/MapVector.h"
34#include "llvm/ADT/STLFunctionalExtras.h"
35#include "llvm/ADT/SmallVector.h"
36#include "llvm/ADT/StringRef.h"
37#include "llvm/ADT/TypeSwitch.h"
38#include "llvm/Support/Debug.h"
39#include <random>
40
41#define DEBUG_TYPE "synth-functional-reduction"
42
43static constexpr llvm::StringLiteral kTestClassAttrName =
44 "synth.test.fc_equiv_class";
45
46namespace circt {
47namespace synth {
48#define GEN_PASS_DEF_FUNCTIONALREDUCTION
49#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
50} // namespace synth
51} // namespace circt
52
53using namespace circt;
54using namespace circt::synth;
55
56namespace {
57enum class EquivResult { Proved, Disproved, Unknown };
58
59class FunctionalReductionSATBuilder {
60public:
61 FunctionalReductionSATBuilder(IncrementalSATSolver &solver,
62 llvm::DenseMap<Value, int> &satVars,
63 llvm::DenseSet<Value> &encodedValues);
64
65 // If inverted, negates rhs in the SAT encoding to check lhs == NOT(rhs).
66 EquivResult verify(Value lhs, Value rhs, bool inverted);
67
68private:
69 int getOrCreateVar(Value value);
70 // Create a fresh SAT variable for an intermediate Boolean subexpression that
71 // does not correspond to an MLIR value.
72 int createAuxVar();
73 SmallVector<int> getOperandVars(ValueRange operands);
74 void encodeValue(Value value);
75
77 llvm::DenseMap<Value, int> &satVars;
78 llvm::DenseSet<Value> &encodedValues;
79};
80
81static bool isFunctionalReductionSimulatableOp(Operation *op) {
82 return isa<BooleanLogicOpInterface, comb::AndOp, comb::OrOp, comb::XorOp>(op);
83}
84
85EquivResult FunctionalReductionSATBuilder::verify(Value lhs, Value rhs,
86 bool inverted) {
87 encodeValue(lhs);
88 encodeValue(rhs);
89
90 int lhsVar = getOrCreateVar(lhs);
91 int rhsVar = getOrCreateVar(rhs);
92
93 if (inverted)
94 rhsVar = -rhsVar;
95 // Check the two halves of the XOR miter separately. If either assignment is
96 // satisfiable, the solver found a distinguishing input pattern.
97 solver.assume(lhsVar);
98 solver.assume(-rhsVar);
99 auto result = solver.solve();
100 if (result == IncrementalSATSolver::kSAT)
101 return EquivResult::Disproved;
102 if (result != IncrementalSATSolver::kUNSAT)
103 return EquivResult::Unknown;
104
105 solver.assume(-lhsVar);
106 solver.assume(rhsVar);
107 result = solver.solve();
108 if (result == IncrementalSATSolver::kSAT)
109 return EquivResult::Disproved;
110 if (result != IncrementalSATSolver::kUNSAT)
111 return EquivResult::Unknown;
112
113 return EquivResult::Proved;
114}
115
116int FunctionalReductionSATBuilder::getOrCreateVar(Value value) {
117 auto it = satVars.find(value);
118 assert(it != satVars.end() && "SAT variable must be preallocated");
119 return it->second;
120}
121
122int FunctionalReductionSATBuilder::createAuxVar() { return solver.newVar(); }
123
124SmallVector<int>
125FunctionalReductionSATBuilder::getOperandVars(ValueRange operands) {
126 SmallVector<int> vars;
127 vars.reserve(operands.size());
128 for (auto operand : operands)
129 vars.push_back(getOrCreateVar(operand));
130 return vars;
131}
132
133void FunctionalReductionSATBuilder::encodeValue(Value value) {
134 SmallVector<std::pair<Value, bool>> worklist;
135 worklist.push_back({value, false});
136
137 while (!worklist.empty()) {
138 auto [current, readyToEncode] = worklist.pop_back_val();
139 if (encodedValues.contains(current))
140 continue;
141
142 Operation *op = current.getDefiningOp();
143 if (!op) {
144 encodedValues.insert(current);
145 continue;
146 }
147
148 APInt constantValue;
149 if (matchPattern(current, mlir::m_ConstantInt(&constantValue))) {
150 encodedValues.insert(current);
151 solver.addClause({constantValue.isZero() ? -getOrCreateVar(current)
152 : getOrCreateVar(current)});
153 continue;
154 }
155
156 if (!isFunctionalReductionSimulatableOp(op)) {
157 // Unsupported operations remain unconstrained, just like block
158 // arguments. Since we only prove equivalence from UNSAT, omitting these
159 // clauses may miss a proof but cannot create a false proof.
160 encodedValues.insert(current);
161 continue;
162 }
163
164 if (!readyToEncode) {
165 worklist.push_back({current, true});
166 for (auto input : op->getOperands()) {
167 assert(input.getType().isInteger(1) &&
168 "only i1 inputs should be simulated or encoded");
169 if (!encodedValues.contains(input))
170 worklist.push_back({input, false});
171 }
172 continue;
173 }
174
175 encodedValues.insert(current);
176 int outVar = getOrCreateVar(current);
177 auto addClause = [&](llvm::ArrayRef<int> clause) {
178 solver.addClause(clause);
179 };
180
181 TypeSwitch<Operation *>(op)
182 .Case<BooleanLogicOpInterface>([&](auto logicOp) {
183 auto inputVars = getOperandVars(logicOp.getInputs());
184 logicOp.emitCNF(outVar, inputVars, addClause,
185 [&]() { return createAuxVar(); });
186 })
187 .Case<comb::AndOp>([&](auto andOp) {
188 auto inputLits = getOperandVars(andOp.getInputs());
189 circt::addAndClauses(outVar, inputLits, addClause);
190 })
191 .Case<comb::OrOp>([&](auto orOp) {
192 auto inputLits = getOperandVars(orOp.getInputs());
193 circt::addOrClauses(outVar, inputLits, addClause);
194 })
195 .Case<comb::XorOp>([&](auto xorOp) {
196 auto inputLits = getOperandVars(xorOp.getInputs());
197 circt::addParityClauses(outVar, inputLits, addClause,
198 [&]() { return createAuxVar(); });
199 })
200 .Default(
201 [](Operation *) { llvm_unreachable("unexpected supported op"); });
202 }
203}
204
205//===----------------------------------------------------------------------===//
206// Core Functional Reduction Implementation
207//===----------------------------------------------------------------------===//
208
209class FunctionalReductionSolver {
210public:
211 FunctionalReductionSolver(hw::HWModuleOp module, unsigned numPatterns,
212 unsigned seed, bool testTransformation,
213 std::unique_ptr<IncrementalSATSolver> satSolver)
214 : module(module), numPatterns(numPatterns), seed(seed),
215 testTransformation(testTransformation),
216 satSolver(std::move(satSolver)) {}
217
218 ~FunctionalReductionSolver() = default;
219
220 /// Run the Functional Reduction algorithm and return statistics.
221 struct Stats {
222 unsigned numEquivClasses = 0;
223 unsigned numProvedEquiv = 0;
224 unsigned numDisprovedEquiv = 0;
225 unsigned numUnknown = 0;
226 unsigned numMergedNodes = 0;
227 };
228 mlir::FailureOr<Stats> run();
229
230private:
231 // Phase 1: Collect i1 values and run simulation
232 void collectValues();
233 void runSimulation();
234 llvm::APInt simulateValue(Value v);
235
236 // Phase 2: Build equivalence classes from simulation
237 void buildEquivalenceClasses();
238
239 // Phase 3: SAT-based verification with per-class solver
240 void verifyCandidates();
241 void initializeSATState();
242
243 // Phase 4: Merge equivalent nodes
244 void mergeEquivalentNodes();
245
246 // Test transformation helpers.
247 static Attribute getTestEquivClass(Value value);
248 static bool matchesTestEquivClass(Value lhs, Value rhs);
249 EquivResult verifyEquivalence(Value lhs, Value rhs, bool inverted);
250
251 // Module being processed
252 hw::HWModuleOp module;
253
254 // Configuration
255 unsigned numPatterns;
256 unsigned seed;
257 bool testTransformation;
258
259 // Primary inputs (block arguments or results of unknown operations treated as
260 // inputs)
261 SmallVector<Value> primaryInputs;
262
263 // All i1 values in topological order
264 SmallVector<Value> allValues;
265
266 // Simulation signatures: value -> APInt simulation result
267 llvm::DenseMap<Value, llvm::APInt> simSignatures;
268
269 // Equivalence candidates: groups of values with identical or inverted
270 // simulation signatures, tracked with an inversion flag
271 SmallVector<SmallVector<std::pair<Value, bool>>> equivCandidates;
272
273 // Proven equivalences: representative -> proven equivalent members with
274 // inversion flag indicating whether the member is inverted relative to
275 // representative
277 provenEquivalences;
278
279 std::unique_ptr<IncrementalSATSolver> satSolver;
280 std::unique_ptr<FunctionalReductionSATBuilder> satBuilder;
281 llvm::DenseMap<Value, int> satVars;
282 llvm::DenseSet<Value> encodedValues;
283 Stats stats;
284};
285
286FunctionalReductionSATBuilder::FunctionalReductionSATBuilder(
287 IncrementalSATSolver &solver, llvm::DenseMap<Value, int> &satVars,
288 llvm::DenseSet<Value> &encodedValues)
289 : solver(solver), satVars(satVars), encodedValues(encodedValues) {}
290
291Attribute FunctionalReductionSolver::getTestEquivClass(Value value) {
292 Operation *op = value.getDefiningOp();
293 if (!op)
294 return {};
295 return op->getAttr(kTestClassAttrName);
296}
297
298bool FunctionalReductionSolver::matchesTestEquivClass(Value lhs, Value rhs) {
299 Attribute lhsClass = getTestEquivClass(lhs);
300 Attribute rhsClass = getTestEquivClass(rhs);
301 return lhsClass && rhsClass && lhsClass == rhsClass;
302}
303
304EquivResult FunctionalReductionSolver::verifyEquivalence(Value lhs, Value rhs,
305 bool inverted) {
306
307 if (testTransformation) {
308 if (matchesTestEquivClass(lhs, rhs))
309 return EquivResult::Proved;
310 return EquivResult::Unknown;
311 }
312 assert(satBuilder && "SAT builder must be initialized before verification");
313 // SAT-based equivalence checking builds a miter for the two candidate nodes
314 // and proves that no input assignment can make them differ.
315 return satBuilder->verify(lhs, rhs, inverted);
316}
317
318void FunctionalReductionSolver::initializeSATState() {
319 assert(satSolver && "SAT solver must be initialized before SAT state setup");
320
321 satVars.clear();
322 encodedValues.clear();
323 satVars.reserve(allValues.size());
324 for (auto [index, value] : llvm::enumerate(allValues))
325 satVars[value] = index + 1;
326 satSolver->reserveVars(allValues.size());
327
328 satBuilder = std::make_unique<FunctionalReductionSATBuilder>(
329 *satSolver, satVars, encodedValues);
330}
331
332//===----------------------------------------------------------------------===//
333// Phase 1: Collect values and run simulation
334//===----------------------------------------------------------------------===//
335
336void FunctionalReductionSolver::collectValues() {
337
338 // Seed zero constants so nodes can be merged
339 // if input IR does not contain constants already.
340 OpBuilder builder(module.getContext());
341 builder.setInsertionPointToStart(module.getBodyBlock());
342 auto i1Type = builder.getIntegerType(1);
343 hw::ConstantOp::create(builder, module.getLoc(), i1Type, 0);
344
345 // Collect block arguments (primary inputs) that are i1
346 for (auto arg : module.getBodyBlock()->getArguments()) {
347 if (arg.getType().isInteger(1)) {
348 primaryInputs.push_back(arg);
349 allValues.push_back(arg);
350 }
351 }
352
353 // Walk operations and collect i1 results
354 // - AIG operations: add to allValues for simulation
355 // - Unknown operations: treat as inputs (assign random patterns)
356 module.walk([&](Operation *op) {
357 for (auto result : op->getResults()) {
358 if (!result.getType().isInteger(1))
359 continue;
360
361 allValues.push_back(result);
362 if (!op->hasTrait<OpTrait::ConstantLike>() &&
363 !isFunctionalReductionSimulatableOp(op)) {
364 // Unknown operations - treat as primary inputs
365 primaryInputs.push_back(result);
366 }
367 }
368 });
369
370 LLVM_DEBUG(llvm::dbgs() << "FunctionalReduction: Collected "
371 << primaryInputs.size()
372 << " primary inputs (including unknown ops) and "
373 << allValues.size() << " total i1 values\n");
374}
375
376void FunctionalReductionSolver::runSimulation() {
377 // Calculate number of 64-bit words needed for numPatterns bits
378 unsigned numWords = numPatterns / 64;
379
380 // Create seeded random number generator for deterministic patterns
381 std::mt19937_64 rng(seed);
382
383 for (auto input : primaryInputs) {
384 // Generate random words using seeded RNG
385 SmallVector<uint64_t> words(numWords);
386 for (auto &word : words)
387 word = rng();
388
389 // Construct APInt directly from words
390 llvm::APInt pattern(numPatterns, words);
391 simSignatures[input] = pattern;
392 }
393
394 // Propagate simulation through the circuit in topological order
395 for (auto value : allValues) {
396 if (simSignatures.count(value))
397 continue; // Already computed (primary input)
398
399 simSignatures[value] = simulateValue(value);
400 }
401
402 LLVM_DEBUG({
403 llvm::dbgs() << "FunctionalReduction: Simulation complete with "
404 << numPatterns << " patterns\n";
405 });
406}
407
408llvm::APInt FunctionalReductionSolver::simulateValue(Value v) {
409 Operation *op = v.getDefiningOp();
410 if (!op)
411 return simSignatures.at(v);
412 return llvm::TypeSwitch<Operation *, llvm::APInt>(op)
413 .Case<BooleanLogicOpInterface>([&](auto op) {
414 return op.evaluateBooleanLogic([&](unsigned i) -> const APInt & {
415 return simSignatures.at(op.getInput(i));
416 });
417 })
418 .Case<comb::AndOp>([&](auto op) {
419 APInt result = APInt::getAllOnes(numPatterns);
420 for (auto input : op.getInputs())
421 result &= simSignatures.at(input);
422 return result;
423 })
424 .Case<comb::OrOp>([&](auto op) {
425 APInt result = APInt::getZero(numPatterns);
426 for (auto input : op.getInputs())
427 result |= simSignatures.at(input);
428 return result;
429 })
430 .Case<comb::XorOp>([&](auto op) {
431 APInt result = APInt::getZero(numPatterns);
432 for (auto input : op.getInputs())
433 result ^= simSignatures.at(input);
434 return result;
435 })
436 .Case([&](hw::ConstantOp op) {
437 return op.getValue().isZero() ? APInt::getZero(numPatterns)
438 : APInt::getAllOnes(numPatterns);
439 })
440 .Default([&](Operation *) {
441 // Unknown operation - treat as input (already assigned a random
442 // pattern)
443 return simSignatures.at(v);
444 });
445}
446
447//===----------------------------------------------------------------------===//
448// Phase 2: Build equivalence classes from simulation
449//===----------------------------------------------------------------------===//
450
451void FunctionalReductionSolver::buildEquivalenceClasses() {
452 // Map from canonical signature to list of {value, inverted pairs}
453 // Inverted signals share the same canonical signature since inversion
454 // is zero cost in synthesis
456 for (auto value : allValues) {
457 auto signature = simSignatures.at(value);
458 bool inverted = false;
459 if (signature.isNegative()) {
460 inverted = true;
461 signature.flipAllBits();
462 }
463 sigGroups[signature].push_back({value, inverted});
464 }
465
466 // Build equivalence candidates for groups with >1 member.
467 // Re-normalize so inverted is relative to representative (first member)
468 for (auto &[hash, members] : sigGroups) {
469 if (members.size() <= 1)
470 continue;
471 bool repInverted = members.front().second;
472 for (auto &[_, inv] : members)
473 inv ^= repInverted;
474 equivCandidates.push_back(std::move(members));
475 }
476 stats.numEquivClasses = equivCandidates.size();
477
478 LLVM_DEBUG(llvm::dbgs() << "FunctionalReduction: Built "
479 << equivCandidates.size()
480 << " equivalence candidates\n");
481}
482
483//===----------------------------------------------------------------------===//
484// Phase 3: SAT-based verification with per-class solvers
485//
486// For each equivalence class candidates, verify each member against the
487// representative using a SAT solver.
488//===----------------------------------------------------------------------===//
489
490void FunctionalReductionSolver::verifyCandidates() {
491 LLVM_DEBUG(
492 llvm::dbgs() << "FunctionalReduction: Starting SAT verification with "
493 << equivCandidates.size() << " equivalence classes\n");
494
495 for (auto &members : equivCandidates) {
496 if (members.empty())
497 continue;
498 auto [representative, repInversion] = members.front();
499 assert(!repInversion && "representative must not be inverted");
500 (void)repInversion;
501 auto &provenMembers = provenEquivalences[representative];
502 // Representative is the canonical node for this class. Members can be
503 // inverted relative to the representative, tracked by the inversion flag
504 for (auto [member, inverted] :
505 llvm::ArrayRef<std::pair<Value, bool>>(members).drop_front()) {
506 EquivResult result = verifyEquivalence(representative, member, inverted);
507 if (result == EquivResult::Proved) {
508 stats.numProvedEquiv++;
509 provenMembers.push_back({member, inverted});
510 } else if (result == EquivResult::Disproved) {
511 stats.numDisprovedEquiv++;
512 // TODO: Refine equivalence classes based on counterexamples from SAT
513 // solver
514 } else {
515 stats.numUnknown++;
516 }
517 }
518 }
519
520 LLVM_DEBUG(
521 llvm::dbgs() << "FunctionalReduction: SAT verification complete. Proved "
522 << stats.numProvedEquiv << " equivalences\n");
523}
524
525//===----------------------------------------------------------------------===//
526// Phase 4: Merge equivalent nodes
527//===----------------------------------------------------------------------===//
528
529void FunctionalReductionSolver::mergeEquivalentNodes() {
530 if (provenEquivalences.empty())
531 return;
532
533 // Build all replacement IR first, then perform use rewrites in a second
534 // phase. This keeps `isBeforeInBlock` queries anchored to the final block
535 // order instead of an order that is still being mutated by insertion.
536 struct PlannedMember {
537 Value original;
538 bool inverted;
539 aig::AndInverterOp operandInverter;
540 };
541 struct MergeRewritePlan {
542 Value representative;
543 SmallVector<PlannedMember> members;
544 // Members which are at risk of reaching their representative
545 SmallVector<PlannedMember> reachableMembers;
546 synth::ChoiceOp choice;
547 aig::AndInverterOp choiceNot;
548 };
549
550 mlir::OpBuilder builder(module.getContext());
551 auto replaceDominatedUses =
552 [](Value from, Value to,
553 llvm::function_ref<bool(Operation *)> shouldReplaceOwner) {
554 auto *defOp = to.getDefiningOp();
555 assert(defOp && "replacement value must be defined by an operation");
556 from.replaceUsesWithIf(to, [&](OpOperand &use) {
557 auto *user = use.getOwner();
558 // Restrict rewrites to uses after the replacement value's definition
559 // in the same block so merging cannot introduce use-before-def edges
560 // or SSA cycles.
561 return shouldReplaceOwner(user) &&
562 user->getBlock() == defOp->getBlock();
563 });
564 };
565
566 DenseSet<Value> reachable;
567 auto visitFrom = [&](Value start) {
568 SmallVector<Value> stack;
569 stack.push_back(start);
570 while (!stack.empty()) {
571 Value current = stack.pop_back_val();
572 if (!reachable.insert(current).second)
573 continue;
574 for (Operation *user : current.getUsers())
575 if (isLogicNetworkOp(user))
576 for (Value result : user->getResults())
577 stack.push_back(result);
578 }
579 };
580
581 SmallVector<MergeRewritePlan> rewritePlans;
582 rewritePlans.reserve(provenEquivalences.size());
583 for (auto provenEquivSet : provenEquivalences) {
584 auto &[representative, members] = provenEquivSet;
585 if (members.empty())
586 continue;
587 // Mark all values reachable from representative before checking members.
588 visitFrom(representative);
589
590 // Greedily filter for members that can create a cycle with representative
591 SmallVector<std::pair<Value, bool>> safeMembers;
592 SmallVector<PlannedMember> plannedReachable;
593 for (auto [member, inverted] : members) {
594 if (reachable.count(member)) {
595 plannedReachable.push_back({member, inverted, {}});
596 continue;
597 }
598 visitFrom(member); // Visit users
599 safeMembers.push_back({member, inverted});
600 }
601
602 if (safeMembers.empty())
603 continue;
604
605 builder.setInsertionPointAfterValue(safeMembers.back().first);
606
607 SmallVector<Value> operands;
608 operands.reserve(safeMembers.size() + 1);
609 operands.push_back(representative);
610
611 SmallVector<PlannedMember> plannedMembers;
612 plannedMembers.reserve(safeMembers.size());
613 bool hasInvertedMember = false;
614 for (auto [member, inverted] : safeMembers) {
615 auto &planned =
616 plannedMembers.emplace_back(PlannedMember{member, inverted, {}});
617 if (!inverted) {
618 operands.push_back(member);
619 continue;
620 }
621 hasInvertedMember = true;
622 // If the member is inverted relative to the representative, we
623 // create an inverter for the choice operand
624 planned.operandInverter =
625 aig::AndInverterOp::create(builder, member.getLoc(), member, true);
626 operands.push_back(planned.operandInverter.getResult());
627 }
628
629 auto choice = synth::ChoiceOp::create(builder, representative.getLoc(),
630 representative.getType(), operands);
631
632 // If there is an inverted member, we need to create an inverter for the
633 // choice result as well
634 auto choiceNot = !hasInvertedMember
635 ? nullptr
636 : aig::AndInverterOp::create(builder, choice.getLoc(),
637 choice, true);
638
639 stats.numMergedNodes += safeMembers.size() + 1;
640 rewritePlans.push_back({representative, std::move(plannedMembers),
641 std::move(plannedReachable), choice, choiceNot});
642 }
643
644 for (auto &plan : rewritePlans) {
645 auto replaceValue = [&](const PlannedMember &member) {
646 if (member.inverted)
647 replaceDominatedUses(member.original, plan.choiceNot,
648 [&](Operation *user) {
649 // Do not rewrite the freshly created operand
650 // inverter or the choice result inverter. This
651 // avoids creating an immediate cycle when
652 // merging an inverted node into its
653 // representative.
654 return user != member.operandInverter &&
655 user != plan.choiceNot.getOperation();
656 });
657 else
658 replaceDominatedUses(member.original, plan.choice,
659 [&](Operation *user) {
660 return user != plan.choice.getOperation();
661 });
662 };
663
664 replaceDominatedUses(
665 plan.representative, plan.choice,
666 [&](Operation *user) { return user != plan.choice.getOperation(); });
667 for (const auto &member : plan.members)
668 replaceValue(member);
669
670 // Reachable members are redundant here so either replace their uses with
671 // choice or erase if they have no uses left.
672 for (auto &member : plan.reachableMembers) {
673 member.original.replaceUsesWithIf(plan.choice, [&](OpOperand &use) {
674 auto *user = use.getOwner();
675 return user->getBlock() == plan.choice->getBlock();
676 });
677 if (member.original.use_empty())
678 member.original.getDefiningOp()->erase();
679 }
680 }
681
682 LLVM_DEBUG(llvm::dbgs() << "FunctionalReduction: Merged "
683 << stats.numMergedNodes << " nodes\n");
684}
685
686//===----------------------------------------------------------------------===//
687// Main Functional Reduction algorithm
688//===----------------------------------------------------------------------===//
689
690mlir::FailureOr<FunctionalReductionSolver::Stats>
691FunctionalReductionSolver::run() {
692 LLVM_DEBUG(
693 llvm::dbgs() << "FunctionalReduction: Starting functional reduction with "
694 << numPatterns << " simulation patterns\n");
695
696 if (!testTransformation && !satSolver) {
697 module->emitError()
698 << "FunctionalReduction requires a SAT solver, but none is "
699 "available in this build";
700 return failure();
701 }
702
703 // Topologically sort the values
705 module->emitError()
706 << "FunctionalReduction: Failed to topologically sort logic network";
707 return failure();
708 }
709
710 // Phase 1: Collect values and run simulation
711 collectValues();
712 if (allValues.empty()) {
713 LLVM_DEBUG(llvm::dbgs()
714 << "FunctionalReduction: No i1 values to process\n");
715 return stats;
716 }
717
718 runSimulation();
719
720 // Phase 2: Build equivalence classes
721 buildEquivalenceClasses();
722 if (equivCandidates.empty()) {
723 LLVM_DEBUG(llvm::dbgs()
724 << "FunctionalReduction: No equivalence candidates found\n");
725 return stats;
726 }
727
728 // Phase 3: SAT-based verification
729 if (!testTransformation)
730 initializeSATState();
731 verifyCandidates();
732
733 // Phase 4: Merge equivalent nodes
734 mergeEquivalentNodes();
735
736 // Re-sort after merging to restore topological order after choice insertion.
738 module->emitError()
739 << "FunctionalReduction: Failed to topologically sort logic network";
740 return failure();
741 }
742
743 LLVM_DEBUG(llvm::dbgs() << "FunctionalReduction: Complete. Stats:\n"
744 << " Equivalence classes: " << stats.numEquivClasses
745 << "\n"
746 << " Proved: " << stats.numProvedEquiv << "\n"
747 << " Disproved: " << stats.numDisprovedEquiv << "\n"
748 << " Unknown (limit): " << stats.numUnknown << "\n"
749 << " Merged: " << stats.numMergedNodes << "\n");
750
751 return stats;
752}
753
754//===----------------------------------------------------------------------===//
755// Pass implementation
756//===----------------------------------------------------------------------===//
757
758struct FunctionalReductionPass
759 : public circt::synth::impl::FunctionalReductionBase<
760 FunctionalReductionPass> {
761 using FunctionalReductionBase::FunctionalReductionBase;
762 void updateStats(const FunctionalReductionSolver::Stats &stats) {
763 numEquivClasses += stats.numEquivClasses;
764 numProvedEquiv += stats.numProvedEquiv;
765 numDisprovedEquiv += stats.numDisprovedEquiv;
766 numUnknown += stats.numUnknown;
767 numMergedNodes += stats.numMergedNodes;
768 }
769
770 void runOnOperation() override {
771 auto module = getOperation();
772 LLVM_DEBUG(llvm::dbgs() << "Running FunctionalReduction pass on "
773 << module.getName() << "\n");
774
775 if (numRandomPatterns == 0 || (numRandomPatterns & 63U) != 0) {
776 module.emitError()
777 << "'num-random-patterns' must be a positive multiple of 64";
778 return signalPassFailure();
779 }
780 if (conflictLimit < -1) {
781 module.emitError()
782 << "'conflict-limit' must be greater than or equal to -1";
783 return signalPassFailure();
784 }
785
786 std::unique_ptr<IncrementalSATSolver> satSolver;
787 if (!testTransformation) {
788 satSolver = createSATSolver(this->satSolver);
789 if (!satSolver) {
790 module.emitError() << "unsupported or unavailable SAT solver '"
791 << this->satSolver
792 << "' (expected auto, z3, or cadical)";
793 return signalPassFailure();
794 }
795 satSolver->setConflictLimit(static_cast<int>(conflictLimit));
796 }
797
798 FunctionalReductionSolver fcSolver(module, numRandomPatterns, seed,
799 testTransformation,
800 std::move(satSolver));
801 auto stats = fcSolver.run();
802 if (failed(stats))
803 return signalPassFailure();
804 updateStats(*stats);
805 if (stats->numMergedNodes == 0)
806 markAllAnalysesPreserved();
807 }
808};
809
810} // namespace
assert(baseType &&"element must be base type")
static constexpr llvm::StringLiteral kTestClassAttrName
static Block * getBodyBlock(FModuleLike mod)
RewritePatternSet pattern
Abstract interface for incremental SAT solvers with an IPASIR-style API.
Definition SATSolver.h:23
create(data_type, value)
Definition hw.py:433
LogicalResult topologicallySortLogicNetwork(mlir::Operation *op)
bool isLogicNetworkOp(mlir::Operation *op)
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 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).
std::unique_ptr< IncrementalSATSolver > createSATSolver(llvm::StringRef backend="auto")
Construct an incremental SAT solver using the requested backend.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
Definition synth.py:1