CIRCT 23.0.0git
Loading...
Searching...
No Matches
ExactSynthesis.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 SAT-based exact synthesis for small Boolean truth
10// tables.
11//
12// References:
13// "Practical exact synthesis", M. Soeken et al., DATE 2018
14//
15//===----------------------------------------------------------------------===//
16
23#include "mlir/IR/Builders.h"
24#include "mlir/IR/OperationSupport.h"
25#include "mlir/IR/PatternMatch.h"
26#include "mlir/Transforms/WalkPatternRewriteDriver.h"
27#include "llvm/ADT/ArrayRef.h"
28#include "llvm/ADT/STLExtras.h"
29#include "llvm/ADT/SmallVector.h"
30#include "llvm/Support/DebugLog.h"
31#include <array>
32#include <optional>
33#include <string>
34
35namespace circt {
36namespace synth {
37#define GEN_PASS_DEF_EXACTSYNTHESIS
38#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
39} // namespace synth
40} // namespace circt
41
42using namespace circt;
43using namespace circt::synth;
44using namespace mlir;
45
46#define DEBUG_TYPE "synth-exact-synthesis"
47
48namespace {
49
50static constexpr unsigned kMaxExactSynthesisInputs = 6;
51
52static SmallString<32> formatTruthTable(const APInt &truthTable) {
53 SmallString<32> text;
54 truthTable.toStringUnsigned(text, /*Radix=*/16);
55 return text;
56}
57
58using BooleanLogicConcept =
59 circt::synth::detail::BooleanLogicOpInterfaceInterfaceTraits::Concept;
60
61struct ExactCandidatePolicy {
62 bool mayUseConstantSource = true;
63 bool enumerateInputInversions = true;
64};
65
66//===----------------------------------------------------------------------===//
67// Exact network model
68//===----------------------------------------------------------------------===//
69
70class ExactNodeInfo;
71
72struct ExactSignalRef {
73 // Source 0 is the constant false value. Sources 1..numInputs are primary
74 // inputs. Later sources are the synthesized steps, in order.
75 unsigned source = 0;
76 bool inverted = false;
77};
78
79struct ExactNetworkStep {
80 const ExactNodeInfo *info = nullptr;
81 SmallVector<ExactSignalRef, 3> fanins;
82
83 unsigned getInversionMask() const;
84 bool operator<(const ExactNetworkStep &rhs) const;
85};
86
87struct ExactNetwork {
88 unsigned numInputs = 0;
89 SmallVector<ExactNetworkStep, 4> steps;
90 ExactSignalRef output;
91};
92
93using ExactCandidate = ExactNetworkStep;
94
95/// One primitive that the exact search may use.
96class ExactNodeInfo {
97public:
98 ExactNodeInfo(OperationName opName, unsigned arity, bool commutative,
99 const BooleanLogicConcept *iface,
100 ExactCandidatePolicy candidatePolicy)
101 : opName(opName), arity(arity), commutative(commutative), iface(iface),
102 candidatePolicy(candidatePolicy) {}
103
104 OperationName getOperationName() const { return opName; }
105 unsigned getArity() const { return arity; }
106 bool isCommutative() const { return commutative; }
107 bool mayUseConstantSource() const {
108 return candidatePolicy.mayUseConstantSource;
109 }
110 bool shouldEnumerateInputInversions() const {
111 return candidatePolicy.enumerateInputInversions;
112 }
113
114 /// Emit clauses for `selector => outLit == f(fanins...)`.
115 void emitConditionedCNF(
116 IncrementalSATSolver &solver, int selector, int outLit,
117 const ExactCandidate &candidate, unsigned minterm,
118 llvm::function_ref<int(unsigned source, unsigned minterm, bool inverted)>
119 getSourceLiteral) const;
120 Value materialize(OpBuilder &builder, Location loc, ArrayRef<Value> operands,
121 ArrayRef<bool> inverted) const {
122 return iface->createBooleanLogicOp(builder, loc, operands, inverted);
123 }
124
125private:
126 OperationName opName;
127 unsigned arity;
128 bool commutative;
129 const BooleanLogicConcept *iface;
130 ExactCandidatePolicy candidatePolicy;
131};
132
133struct ExactSynthesisPolicy {
134 SmallVector<ExactNodeInfo, 4> primitiveInfos;
135};
136
137static SmallString<64>
138formatPrimitiveSummary(const ExactSynthesisPolicy &policy) {
139 SmallString<64> text;
140 llvm::raw_svector_ostream os(text);
141 llvm::interleaveComma(policy.primitiveInfos, os, [&](const auto &info) {
142 os << info.getOperationName().getStringRef() << ":" << info.getArity();
143 });
144 return text;
145}
146
147unsigned ExactNetworkStep::getInversionMask() const {
148 unsigned mask = 0;
149 for (auto [index, fanin] : llvm::enumerate(fanins))
150 if (fanin.inverted)
151 mask |= 1u << index;
152 return mask;
153}
154
155bool ExactNetworkStep::operator<(const ExactNetworkStep &rhs) const {
156 if (fanins.size() != rhs.fanins.size())
157 return fanins.size() < rhs.fanins.size();
158 for (size_t i = 0, e = fanins.size(); i != e; ++i) {
159 if (fanins[i].source != rhs.fanins[i].source)
160 return fanins[i].source < rhs.fanins[i].source;
161 }
162 if (info != rhs.info) {
163 if (info->getOperationName() != rhs.info->getOperationName())
164 return info->getOperationName().getStringRef() <
165 rhs.info->getOperationName().getStringRef();
166 }
167 return getInversionMask() < rhs.getInversionMask();
168}
169
170static std::optional<ExactNetwork> synthesizeDirect(unsigned numInputs,
171 const APInt &target) {
172 ExactNetwork network;
173 network.numInputs = numInputs;
174 if (target.isZero()) {
175 network.output = {0, false};
176 return network;
177 }
178 if (target.isAllOnes()) {
179 network.output = {0, true};
180 return network;
181 }
182
183 for (unsigned input = 0; input != numInputs; ++input) {
184 APInt mask = circt::createVarMask(numInputs, input, true);
185 if (target == mask) {
186 network.output = {1 + input, false};
187 return network;
188 }
189 APInt invertedMask = mask;
190 invertedMask.flipAllBits();
191 if (target == invertedMask) {
192 network.output = {1 + input, true};
193 return network;
194 }
195 }
196 return std::nullopt;
197}
198
199//===----------------------------------------------------------------------===//
200// Enumeration
201//===----------------------------------------------------------------------===//
202
203/// Enumerates the primitive instances that may be placed at one SAT step.
204class ExactCandidateEnumerator {
205public:
206 void enumerate(const ExactSynthesisPolicy &policy, unsigned availableSources,
207 SmallVectorImpl<ExactCandidate> &candidates) const;
208
209private:
210 static void enumerateCommutativeOperandSources(
211 unsigned availableSources, unsigned arity, unsigned currentArity,
212 unsigned nextSource, SmallVectorImpl<unsigned> &sources,
213 llvm::function_ref<void(ArrayRef<unsigned>)> emit);
214
215 static void enumerateOrderedOperandSources(
216 unsigned availableSources, unsigned arity, unsigned firstSource,
217 unsigned currentArity, SmallVectorImpl<unsigned> &sources,
218 llvm::function_ref<void(ArrayRef<unsigned>)> emit);
219
220 static void
221 enumerateNodeCandidates(const ExactNodeInfo &info, unsigned availableSources,
222 SmallVectorImpl<ExactCandidate> &candidates);
223};
224
225//===----------------------------------------------------------------------===//
226// Materialization
227//===----------------------------------------------------------------------===//
228
229/// Lowers a solved exact network back into current Synth IR.
230class ExactNetworkMaterializer {
231public:
232 ExactNetworkMaterializer(OpBuilder &builder, Location loc,
233 ArrayRef<Value> inputs);
234
235 Value materialize(const ExactNetwork &network);
236
237private:
238 Value getConstant(bool value);
239 Value getRawSignal(ExactSignalRef signal, ArrayRef<Value> stepValues);
240 Value materializeInverter(Value input);
241
242 OpBuilder &builder;
243 Location loc;
244 ArrayRef<Value> inputs;
245 std::array<Value, 2> constValues;
246};
247
248//===----------------------------------------------------------------------===//
249// SAT search
250//===----------------------------------------------------------------------===//
251
252class GenericExactSATProblem {
253public:
254 GenericExactSATProblem(const ExactSynthesisPolicy &policy,
255 IncrementalSATSolver &solver, unsigned numInputs,
256 const APInt &target, unsigned numSteps);
257
258 std::optional<ExactNetwork> solve();
259
260private:
261 int newVar();
262 /// Return the SAT variable for one source under one concrete input pattern.
263 int getSourceValueVar(unsigned source, unsigned minterm) const;
264 /// Return that same variable as a literal, optionally negated.
265 int getSourceLiteral(unsigned source, unsigned minterm, bool inverted) const;
266
267 /// Build the SAT model for "can this truth table be implemented with exactly
268 /// `numSteps` internal nodes?".
269 bool buildEncoding();
270
271 /// Say what each step output must be for each candidate and each input
272 /// pattern.
273 void addCandidateSemanticsConstraints();
274
275 /// Force every step except the root to feed some later selected step.
276 void addUseAllStepsConstraints();
277
278 ExactNetwork decodeModel() const;
279
280 const ExactSynthesisPolicy &policy;
281 IncrementalSATSolver &solver;
282 unsigned numInputs;
283 APInt target;
284 unsigned numSteps;
285 unsigned numMinterms;
286 unsigned totalSources;
287 int rootInvertVar = 0;
288 SmallVector<SmallVector<int, 16>, 8> sourceValueVars;
289 SmallVector<SmallVector<ExactCandidate, 64>, 8> stepCandidates;
290 SmallVector<SmallVector<int, 64>, 8> stepSelectionVars;
291 ExactCandidateEnumerator enumerator;
292};
293
294void ExactNodeInfo::emitConditionedCNF(
295 IncrementalSATSolver &solver, int selector, int outLit,
296 const ExactCandidate &candidate, unsigned minterm,
297 llvm::function_ref<int(unsigned source, unsigned minterm, bool inverted)>
298 getSourceLiteral) const {
299 auto addConditionedClause = [&](ArrayRef<int> literals) {
300 SmallVector<int, 8> clause;
301 clause.reserve(literals.size() + 1);
302 clause.push_back(-selector);
303 clause.append(literals.begin(), literals.end());
304 solver.addClause(clause);
305 };
306
307 // Apply this candidate's edge inversions in the literals we pass to the
308 // primitive. Guard every primitive clause with the selector so unselected
309 // candidates do not constrain the step output.
310 SmallVector<int, 4> inputLits;
311 inputLits.reserve(getArity());
312 for (unsigned operand = 0; operand != getArity(); ++operand) {
313 const auto &fanin = candidate.fanins[operand];
314 inputLits.push_back(
315 getSourceLiteral(fanin.source, minterm, fanin.inverted));
316 }
317 iface->emitCNFWithoutInversion(outLit, inputLits, addConditionedClause,
318 [&] { return solver.newVar(); });
319}
320
321void ExactCandidateEnumerator::enumerate(
322 const ExactSynthesisPolicy &policy, unsigned availableSources,
323 SmallVectorImpl<ExactCandidate> &candidates) const {
324 candidates.clear();
325 for (const auto &info : policy.primitiveInfos)
326 enumerateNodeCandidates(info, availableSources, candidates);
327 llvm::sort(candidates);
328 LDBG() << "Enumerated " << candidates.size()
329 << " candidates with availableSources=" << availableSources << "\n";
330}
331
332void ExactCandidateEnumerator::enumerateCommutativeOperandSources(
333 unsigned availableSources, unsigned arity, unsigned currentArity,
334 unsigned nextSource, SmallVectorImpl<unsigned> &sources,
335 llvm::function_ref<void(ArrayRef<unsigned>)> emit) {
336 if (currentArity == arity) {
337 emit(sources);
338 return;
339 }
340
341 for (unsigned source = nextSource; source < availableSources; ++source) {
342 sources.push_back(source);
343 enumerateCommutativeOperandSources(
344 availableSources, arity, currentArity + 1, source + 1, sources, emit);
345 sources.pop_back();
346 }
347}
348
349void ExactCandidateEnumerator::enumerateOrderedOperandSources(
350 unsigned availableSources, unsigned arity, unsigned firstSource,
351 unsigned currentArity, SmallVectorImpl<unsigned> &sources,
352 llvm::function_ref<void(ArrayRef<unsigned>)> emit) {
353 if (currentArity == arity) {
354 emit(sources);
355 return;
356 }
357
358 // Ordered nodes such as DOT must keep operand order. They may also reuse the
359 // same source more than once.
360 for (unsigned source = firstSource; source < availableSources; ++source) {
361 sources.push_back(source);
362 enumerateOrderedOperandSources(availableSources, arity, firstSource,
363 currentArity + 1, sources, emit);
364 sources.pop_back();
365 }
366}
367
368void ExactCandidateEnumerator::enumerateNodeCandidates(
369 const ExactNodeInfo &info, unsigned availableSources,
370 SmallVectorImpl<ExactCandidate> &candidates) {
371 SmallVector<unsigned, 3> sources;
372 auto emitCandidate = [&](ArrayRef<unsigned> operandSources) {
373 unsigned numInversionMasks =
374 info.shouldEnumerateInputInversions() ? (1u << info.getArity()) : 1;
375 for (unsigned invMask = 0; invMask != numInversionMasks; ++invMask) {
376 ExactCandidate candidate;
377 candidate.info = &info;
378 for (auto [index, source] : llvm::enumerate(operandSources))
379 candidate.fanins.push_back(
380 {source, static_cast<bool>(invMask & (1u << index))});
381 candidates.push_back(std::move(candidate));
382 }
383 };
384
385 // At this point we only choose source numbers and edge inversions. The
386 // primitive's Boolean behavior is added later as CNF.
387 unsigned firstSource = info.mayUseConstantSource() ? 0 : 1;
388 if (info.isCommutative()) {
389 // Commutative nodes use sorted, distinct sources. This removes operand
390 // permutations and skips repeated-source cases. For the current
391 // commutative Synth primitives, repeated sources reduce to constants,
392 // projections, inversions, or distinct-source candidates with constants.
393 // FIXME: Derive this repeated-source pruning from the primitive truth table
394 // or make it an explicit primitive policy before adding more commutative
395 // primitives here.
396 enumerateCommutativeOperandSources(availableSources, info.getArity(),
397 /*currentArity=*/0, firstSource, sources,
398 emitCandidate);
399 return;
400 }
401
402 enumerateOrderedOperandSources(availableSources, info.getArity(), firstSource,
403 /*currentArity=*/0, sources, emitCandidate);
404}
405
406ExactNetworkMaterializer::ExactNetworkMaterializer(OpBuilder &builder,
407 Location loc,
408 ArrayRef<Value> inputs)
409 : builder(builder), loc(loc), inputs(inputs) {}
410
411Value ExactNetworkMaterializer::materialize(const ExactNetwork &network) {
412 SmallVector<Value, 4> stepValues;
413 stepValues.reserve(network.steps.size());
414 for (const auto &step : network.steps) {
415 assert(step.info && "network step must carry node info");
416 const auto &info = *step.info;
417 SmallVector<Value, 3> operands;
418 SmallVector<bool, 3> inverted;
419 operands.reserve(info.getArity());
420 inverted.reserve(info.getArity());
421 for (const auto &fanin : step.fanins) {
422 operands.push_back(getRawSignal(fanin, stepValues));
423 inverted.push_back(fanin.inverted);
424 }
425 stepValues.push_back(info.materialize(builder, loc, operands, inverted));
426 }
427
428 if (network.output.source == 0)
429 return getConstant(network.output.inverted);
430
431 Value result = getRawSignal(network.output, stepValues);
432 if (!network.output.inverted)
433 return result;
434 return materializeInverter(result);
435}
436
437Value ExactNetworkMaterializer::getConstant(bool value) {
438 if (constValues[value])
439 return constValues[value];
440 return constValues[value] =
441 hw::ConstantOp::create(builder, loc, APInt(1, value));
442}
443
444Value ExactNetworkMaterializer::getRawSignal(ExactSignalRef signal,
445 ArrayRef<Value> stepValues) {
446 if (signal.source == 0)
447 return getConstant(false);
448 if (signal.source <= inputs.size())
449 return inputs[signal.source - 1];
450
451 unsigned stepIndex = signal.source - (inputs.size() + 1);
452 assert(stepIndex < stepValues.size() && "invalid synthesized step index");
453 return stepValues[stepIndex];
454}
455
456Value ExactNetworkMaterializer::materializeInverter(Value input) {
457 return aig::AndInverterOp::create(builder, loc, input, true);
458}
459
460GenericExactSATProblem::GenericExactSATProblem(
461 const ExactSynthesisPolicy &policy, IncrementalSATSolver &solver,
462 unsigned numInputs, const APInt &target, unsigned numSteps)
463 : policy(policy), solver(solver), numInputs(numInputs), target(target),
464 numSteps(numSteps), numMinterms(1u << numInputs),
465 totalSources(1 + numInputs + numSteps) {}
466
467std::optional<ExactNetwork> GenericExactSATProblem::solve() {
468 LDBG() << "SAT solve start: inputs=" << numInputs << " steps=" << numSteps
469 << " minterms=" << numMinterms << " target=0x"
470 << formatTruthTable(target) << "\n";
471 if (!buildEncoding())
472 return std::nullopt;
473 auto result = solver.solve();
474 LDBG() << "SAT solve result: "
475 << (result == IncrementalSATSolver::kSAT ? "SAT"
476 : result == IncrementalSATSolver::kUNSAT ? "UNSAT"
477 : "UNKNOWN")
478 << "\n";
479 if (result != IncrementalSATSolver::kSAT)
480 return std::nullopt;
481 return decodeModel();
482}
483
484int GenericExactSATProblem::newVar() { return solver.newVar(); }
485
486int GenericExactSATProblem::getSourceValueVar(unsigned source,
487 unsigned minterm) const {
488 return sourceValueVars[source][minterm];
489}
490
491int GenericExactSATProblem::getSourceLiteral(unsigned source, unsigned minterm,
492 bool inverted) const {
493 int lit = getSourceValueVar(source, minterm);
494 return inverted ? -lit : lit;
495}
496
497bool GenericExactSATProblem::buildEncoding() {
498 // A minterm is one input assignment. For every source and every minterm,
499 // create one SAT variable that means "this source is true for this minterm".
500 sourceValueVars.resize(totalSources);
501 for (unsigned source = 0; source != totalSources; ++source) {
502 sourceValueVars[source].reserve(numMinterms);
503 for (unsigned minterm = 0; minterm != numMinterms; ++minterm)
504 sourceValueVars[source].push_back(newVar());
505 }
506
507 // Source 0 is always false, for every input pattern.
508 for (unsigned minterm = 0; minterm != numMinterms; ++minterm)
509 solver.addClause({-getSourceValueVar(0, minterm)});
510
511 // Fix each primary input source to the matching bit of the minterm number.
512 // For example, minterm 5 (0b101) makes input 0 and input 2 true.
513 for (unsigned input = 0; input != numInputs; ++input)
514 for (unsigned minterm = 0; minterm != numMinterms; ++minterm)
515 solver.addClause({((minterm >> input) & 1)
516 ? getSourceValueVar(1 + input, minterm)
517 : -getSourceValueVar(1 + input, minterm)});
518
519 // Each internal step chooses exactly one candidate primitive instance.
520 stepCandidates.resize(numSteps);
521 stepSelectionVars.resize(numSteps);
522 for (unsigned step = 0; step != numSteps; ++step) {
523 unsigned availableSources = 1 + numInputs + step;
524 enumerator.enumerate(policy, availableSources, stepCandidates[step]);
525 LDBG() << " step " << step << ": availableSources=" << availableSources
526 << " candidates=" << stepCandidates[step].size() << "\n";
527 if (stepCandidates[step].empty())
528 return false;
529
530 auto &selectionVars = stepSelectionVars[step];
531 selectionVars.reserve(stepCandidates[step].size());
532 for (size_t i = 0, e = stepCandidates[step].size(); i != e; ++i)
533 selectionVars.push_back(newVar());
535 selectionVars, [&](ArrayRef<int> clause) { solver.addClause(clause); },
536 [&] { return newVar(); });
537 }
538
539 // Add the primitive semantics and require every non-root step to feed a later
540 // selected step.
541 // TODO: Add symmetry breaking constraints to reduce the search space.
542 addCandidateSemanticsConstraints();
543 addUseAllStepsConstraints();
544
545 // The root is the last internal step with one optional output inversion. The
546 // inversion bit is shared by all minterms, so it chooses one global polarity.
547 unsigned rootSource = totalSources - 1;
548 rootInvertVar = newVar();
549 for (unsigned minterm = 0; minterm != numMinterms; ++minterm) {
550 int rootLit = getSourceValueVar(rootSource, minterm);
551 if (target[minterm]) {
552 // target = root xor rootInvert, with target fixed to true.
553 solver.addClause({rootLit, rootInvertVar});
554 solver.addClause({-rootLit, -rootInvertVar});
555 } else {
556 // target = root xor rootInvert, with target fixed to false.
557 solver.addClause({rootLit, -rootInvertVar});
558 solver.addClause({-rootLit, rootInvertVar});
559 }
560 }
561 return true;
562}
563
564void GenericExactSATProblem::addCandidateSemanticsConstraints() {
565 for (unsigned step = 0; step != numSteps; ++step) {
566 unsigned outSource = 1 + numInputs + step;
567 const auto &selectionVars = stepSelectionVars[step];
568 // If a candidate is selected, its output must match its primitive
569 // semantics for every minterm.
570 for (auto [candidateIndex, candidate] :
571 llvm::enumerate(stepCandidates[step]))
572 for (unsigned minterm = 0; minterm != numMinterms; ++minterm) {
573 assert(candidate.info && "candidate must carry node info");
574 candidate.info->emitConditionedCNF(
575 solver, selectionVars[candidateIndex],
576 getSourceValueVar(outSource, minterm), candidate, minterm,
577 [&](unsigned source, unsigned currentMinterm, bool inverted) {
578 return getSourceLiteral(source, currentMinterm, inverted);
579 });
580 }
581 }
582}
583
584void GenericExactSATProblem::addUseAllStepsConstraints() {
585 for (unsigned step = 0; step + 1 < numSteps; ++step) {
586 unsigned source = 1 + numInputs + step;
587 SmallVector<int, 32> users;
588 for (unsigned userStep = step + 1; userStep != numSteps; ++userStep)
589 for (auto [candidateIndex, candidate] :
590 llvm::enumerate(stepCandidates[userStep]))
591 if (llvm::any_of(candidate.fanins, [&](const ExactSignalRef &fanin) {
592 return fanin.source == source;
593 }))
594 users.push_back(stepSelectionVars[userStep][candidateIndex]);
595
596 // Without this, an area-bounded search could satisfy the target with dead
597 // logic that never reaches the root.
598 solver.addClause(users);
599 }
600}
601
602ExactNetwork GenericExactSATProblem::decodeModel() const {
603 ExactNetwork network;
604 network.numInputs = numInputs;
605 network.steps.reserve(numSteps);
606 for (unsigned step = 0; step != numSteps; ++step) {
607 const auto &selectionVars = stepSelectionVars[step];
608 const auto &candidates = stepCandidates[step];
609 for (size_t i = 0, e = selectionVars.size(); i != e; ++i) {
610 if (solver.val(selectionVars[i]) != selectionVars[i])
611 continue;
612 network.steps.push_back(candidates[i]);
613 break;
614 }
615 }
616 network.output = {1 + numInputs + numSteps - 1,
617 solver.val(rootInvertVar) == rootInvertVar};
618 LDBG() << "Decoded network with " << network.steps.size()
619 << " steps, rootSource=" << network.output.source
620 << " rootInvert=" << network.output.inverted << "\n";
621 return network;
622}
623
624static FailureOr<Value>
625exactSynthesizeAreaMinimized(OpBuilder &builder, Location loc, APInt truthTable,
626 ArrayRef<Value> operands,
627 const ExactSynthesisPolicy &policy,
628 StringRef satSolver, unsigned maxArea) {
629 ExactNetworkMaterializer materializer(builder, loc, operands);
630 unsigned numInputs = operands.size();
631 LDBG() << "Exact synthesis request: inputs=" << numInputs << " truthTable=0x"
632 << formatTruthTable(truthTable)
633 << " allowed-primitives=" << formatPrimitiveSummary(policy)
634 << " sat-solver=" << satSolver << "\n";
635
636 if (policy.primitiveInfos.empty())
637 return failure();
638
639 LDBG() << "Trying direct synthesis for target=0x"
640 << formatTruthTable(truthTable) << "\n";
641 auto network = synthesizeDirect(numInputs, truthTable);
642 if (network) {
643 LDBG() << "Using direct synthesis result\n";
644 return materializer.materialize(*network);
645 }
646
647 for (unsigned area = 1; area <= maxArea; ++area) {
648 LDBG() << "Trying area=" << area << "\n";
649 auto solver = createSATSolver(satSolver);
650 if (!solver)
651 return failure();
652 GenericExactSATProblem problem(policy, *solver, numInputs, truthTable,
653 area);
654 auto solved = problem.solve();
655 if (!solved) {
656 LDBG() << "Area " << area << " has no solution\n";
657 continue;
658 }
659 LDBG() << "Found solution at area=" << area << "\n";
660 return materializer.materialize(*solved);
661 }
662 LDBG() << "No exact solution found up to area limit " << maxArea << "\n";
663 return failure();
664}
665
666//===----------------------------------------------------------------------===//
667// Rewrite Pass
668//===----------------------------------------------------------------------===//
669
670struct ExactSynthesisStatistics {
671 unsigned numSolved = 0;
672 unsigned numUnsolved = 0;
673};
674
675struct ExactSynthesisPattern : public OpRewritePattern<comb::TruthTableOp> {
676 ExactSynthesisPattern(MLIRContext *context,
677 const ExactSynthesisPolicy &policy, StringRef satSolver,
678 unsigned maxInputs, unsigned maxArea,
679 ExactSynthesisStatistics &statistics)
680 : OpRewritePattern(context), policy(policy), satSolver(satSolver.str()),
681 maxInputs(maxInputs), maxArea(maxArea), statistics(statistics) {}
682
683 LogicalResult matchAndRewrite(comb::TruthTableOp op,
684 PatternRewriter &rewriter) const override {
685 if (op.getInputs().size() > maxInputs) {
686 ++statistics.numUnsolved;
687 return failure();
688 }
689
690 SmallVector<Value> operands;
691 operands.reserve(op.getInputs().size());
692 // comb.truth_table indexes the first operand as the most significant input
693 // bit. The exact synthesis truth-table utilities use input 0 as the least
694 // significant bit.
695 for (Value operand : llvm::reverse(op.getInputs()))
696 operands.push_back(operand);
697
698 APInt truthTable(op.getLookupTable().size(), 0);
699 for (size_t index = 0, e = op.getLookupTable().size(); index != e; ++index)
700 truthTable.setBitVal(index, op.getLookupTable()[index]);
701 auto result =
702 exactSynthesizeAreaMinimized(rewriter, op.getLoc(), truthTable,
703 operands, policy, satSolver, maxArea);
704 if (failed(result)) {
705 ++statistics.numUnsolved;
706 return failure();
707 }
708
709 rewriter.replaceOp(op, *result);
710 ++statistics.numSolved;
711 return success();
712 }
713
714private:
715 const ExactSynthesisPolicy &policy;
716 std::string satSolver;
717 unsigned maxInputs;
718 unsigned maxArea;
719 ExactSynthesisStatistics &statistics;
720};
721
722struct ExactSynthesisPass
723 : public circt::synth::impl::ExactSynthesisBase<ExactSynthesisPass> {
724 using ExactSynthesisBase::ExactSynthesisBase;
725
726 FailureOr<ExactSynthesisPolicy> parsePolicy(MLIRContext *context) const {
727 ExactSynthesisPolicy policy;
728
729 if (allowedOps.empty()) {
730 emitError(UnknownLoc::get(context))
731 << "synth-exact-synthesis requires at least one "
732 "'allowed-ops=name:arity' entry";
733 return failure();
734 }
735
736 for (const std::string &allowedOp : allowedOps) {
737 StringRef spelling = allowedOp;
738 auto parts = spelling.split(':');
739 StringRef name = parts.first.trim();
740 StringRef arityText = parts.second.trim();
741 auto registeredInfo = RegisteredOperationName::lookup(name, context);
742 if (!registeredInfo) {
743 emitError(UnknownLoc::get(context))
744 << "unknown allowed exact-synthesis op '" << name << "'";
745 return failure();
746 }
747 auto *iface = registeredInfo->getInterface<BooleanLogicOpInterface>();
748 if (!iface) {
749 emitError(UnknownLoc::get(context))
750 << "op '" << name << "' does not implement BooleanLogicOpInterface";
751 return failure();
752 }
753
754 unsigned arity = 0;
755 if (arityText.empty() || arityText.getAsInteger(10, arity)) {
756 emitError(UnknownLoc::get(context))
757 << "expected allowed exact-synthesis op in 'name:arity' form, "
758 "e.g. '"
759 << name << ":3'";
760 return failure();
761 }
762 if (arity < 2 || arity > kMaxExactSynthesisInputs) {
763 emitError(UnknownLoc::get(context))
764 << "unsupported arity " << arity << " for '" << name << "'";
765 return failure();
766 }
767 if (!iface->supportsNumInputs(arity)) {
768 emitError(UnknownLoc::get(context))
769 << "op '" << name << "' does not support exact-synthesis arity "
770 << arity;
771 return failure();
772 }
773 OperationName opName(*registeredInfo);
774 ExactCandidatePolicy candidatePolicy;
775 if (name == XorInverterOp::getOperationName() && arity == 2) {
776 // For binary XOR, constants and input inversions only change the result
777 // into a constant, projection, or complemented XOR. Direct synthesis,
778 // edge inversions, and root inversion already cover those cases.
779 candidatePolicy.mayUseConstantSource = false;
780 candidatePolicy.enumerateInputInversions = false;
781 }
782 if (llvm::any_of(policy.primitiveInfos, [&](const ExactNodeInfo &info) {
783 return info.getOperationName() == opName &&
784 info.getArity() == arity;
785 })) {
786 emitError(UnknownLoc::get(context))
787 << "duplicate allowed exact-synthesis op '" << spelling << "'";
788 return failure();
789 }
790 policy.primitiveInfos.emplace_back(opName, arity,
791 iface->areInputsPermutationInvariant(),
792 iface, candidatePolicy);
793 }
794 return policy;
795 }
796
797 LogicalResult initialize(MLIRContext *context) override {
798 if (maxInputs > kMaxExactSynthesisInputs) {
799 emitError(UnknownLoc::get(context))
800 << "maximum exact-synthesis input count is "
801 << kMaxExactSynthesisInputs;
802 return failure();
803 }
804
805 auto parsedPolicy = parsePolicy(context);
806 if (failed(parsedPolicy))
807 return failure();
808 policy = *parsedPolicy;
809
810 if (!createSATSolver(satSolver)) {
811 emitError(UnknownLoc::get(context))
812 << "unsupported or unavailable SAT solver '" << satSolver
813 << "' (expected auto, z3, or cadical)";
814
815 return failure();
816 }
817 return success();
818 }
819
820 void runOnOperation() override {
821 ExactSynthesisStatistics statistics;
822 RewritePatternSet patterns(&getContext());
823 patterns.add<ExactSynthesisPattern>(&getContext(), policy, satSolver,
824 maxInputs, maxArea, statistics);
825 walkAndApplyPatterns(getOperation(), std::move(patterns));
826 numSolved += statistics.numSolved;
827 numUnsolved += statistics.numUnsolved;
828 }
829
830private:
831 ExactSynthesisPolicy policy;
832};
833
834} // namespace
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static std::optional< APSInt > getConstant(Attribute operand)
Determine the value of a constant operand for the sake of constant folding.
static InstancePath empty
Abstract interface for incremental SAT solvers with an IPASIR-style API.
Definition SATSolver.h:23
virtual void addClause(llvm::ArrayRef< int > lits)
Add a complete clause in one call.
Definition SATSolver.h:53
virtual int newVar()=0
Add a fresh variable for safe incremental SAT solving.
virtual Result solve()=0
Solve under the previously added clauses and current assumptions.
create(data_type, value)
Definition hw.py:433
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void addExactlyOneClauses(llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause, llvm::function_ref< int()> newVar)
Emit clauses encoding that exactly one literal in inputLits is true.
llvm::APInt createVarMask(unsigned numVars, unsigned varIndex, bool positive)
Create a mask for a variable in the truth table.
std::unique_ptr< IncrementalSATSolver > createSATSolver(llvm::StringRef backend="auto")
Construct an incremental SAT solver using the requested backend.
Definition emit.py:1
Definition synth.py:1