CIRCT 24.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.
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
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 int val(int v) const =0
Return the satisfying assignment for variable v from the last SAT result.
virtual Result solve()=0
Solve under the previously added clauses and current assumptions.
create(data_type, value)
Definition hw.py:433
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