CIRCT 23.0.0git
Loading...
Searching...
No Matches
TruthTable.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 truth table utilities.
10//
11//===----------------------------------------------------------------------===//
12
14
15#include "llvm/ADT/APInt.h"
16#include "llvm/ADT/BitVector.h"
17#include "llvm/ADT/STLExtras.h"
18#include "llvm/Support/MathExtras.h"
19#include <algorithm>
20#include <array>
21#include <cassert>
22
23using namespace circt;
24using llvm::APInt;
25
26//===----------------------------------------------------------------------===//
27// BinaryTruthTable
28//===----------------------------------------------------------------------===//
29
30namespace {
31
32static llvm::APInt
33expandTruthTableToInputSpaceSmall(const llvm::APInt &tt,
34 ArrayRef<unsigned> mapping,
35 unsigned numExpandedInputs) {
36 assert(numExpandedInputs <= 6 && "small fast path requires <= 6 inputs");
37
38 unsigned numOrigInputs = mapping.size();
39 unsigned expandedSize = 1U << numExpandedInputs;
40 // `kVarMasks[i]` marks rows where expanded input `i` is 1. For example,
41 // `kVarMasks[0] = 0b1010...` and `kVarMasks[1] = 0b1100...`, matching the
42 // standard packed truth-table row order where input 0 is the LSB.
43 static constexpr uint64_t kVarMasks[6] = {
44 0xAAAAAAAAAAAAAAAAULL, // input 0: 1010...
45 0xCCCCCCCCCCCCCCCCULL, // input 1: 1100...
46 0xF0F0F0F0F0F0F0F0ULL, // input 2: 11110000...
47 0xFF00FF00FF00FF00ULL, // input 3: 8 zeros, 8 ones
48 0xFFFF0000FFFF0000ULL, // input 4: 16 zeros, 16 ones
49 0xFFFFFFFF00000000ULL // input 5: 32 zeros, 32 ones
50 };
51
52 uint64_t sizeMask = llvm::maskTrailingOnes<uint64_t>(expandedSize);
53 unsigned origSize = 1U << numOrigInputs;
54 uint64_t origMask = llvm::maskTrailingOnes<uint64_t>(origSize);
55 uint64_t origTT = tt.getZExtValue() & origMask;
56
57 // `constrainMasks[i][b]` is the set of expanded rows where original input
58 // `i`, after remapping into the expanded space, observes bit value `b`.
59 std::array<std::array<uint64_t, 2>, 6> constrainMasks{};
60 for (unsigned i = 0; i < numOrigInputs; ++i) {
61 uint64_t posMask = kVarMasks[mapping[i]] & sizeMask;
62 constrainMasks[i][0] = (~posMask) & sizeMask;
63 constrainMasks[i][1] = posMask;
64 }
65
66 uint64_t activePatterns = origTT;
67 bool useComplementResult = false;
68 uint64_t result = 0;
69 // If the original truth table has more 1s than 0s, it's more efficient to
70 // iterate over the 0 patterns.
71 if (static_cast<unsigned>(llvm::popcount(origTT)) > (origSize / 2)) {
72 activePatterns = (~origTT) & origMask;
73 useComplementResult = true;
74 result = sizeMask;
75 }
76
77 while (activePatterns) {
78 unsigned origIdx = llvm::countr_zero(activePatterns);
79 activePatterns &= activePatterns - 1;
80
81 uint64_t pattern = sizeMask;
82 for (unsigned i = 0; i < numOrigInputs; ++i)
83 pattern &= constrainMasks[i][(origIdx >> i) & 1U];
84
85 if (useComplementResult)
86 result &= ~pattern;
87 else
88 result |= pattern;
89 }
90
91 return llvm::APInt(expandedSize, result);
92}
93
94static llvm::APInt
95expandTruthTableToInputSpaceGeneric(const llvm::APInt &tt,
96 ArrayRef<unsigned> mapping,
97 unsigned numExpandedInputs) {
98 unsigned numOrigInputs = mapping.size();
99 unsigned expandedSize = 1U << numExpandedInputs;
100
101 llvm::APInt result = llvm::APInt::getZero(expandedSize);
102 for (unsigned expandedIdx = 0; expandedIdx < expandedSize; ++expandedIdx) {
103 unsigned origIdx = 0;
104 for (unsigned i = 0; i < numOrigInputs; ++i)
105 if ((expandedIdx >> mapping[i]) & 1U)
106 origIdx |= 1U << i;
107 if (tt[origIdx])
108 result.setBit(expandedIdx);
109 }
110
111 return result;
112}
113
114} // namespace
115
116llvm::APInt BinaryTruthTable::getOutput(const llvm::APInt &input) const {
117 assert(input.getBitWidth() == numInputs && "Input width mismatch");
118 return table.extractBits(numOutputs, input.getZExtValue() * numOutputs);
119}
120
121void BinaryTruthTable::setOutput(const llvm::APInt &input,
122 const llvm::APInt &output) {
123 assert(input.getBitWidth() == numInputs && "Input width mismatch");
124 assert(output.getBitWidth() == numOutputs && "Output width mismatch");
125 assert(input.getBitWidth() < 32 && "Input width too large");
126 unsigned offset = input.getZExtValue() * numOutputs;
127 for (unsigned i = 0; i < numOutputs; ++i)
128 table.setBitVal(offset + i, output[i]);
129}
130
132BinaryTruthTable::applyPermutation(ArrayRef<unsigned> permutation) const {
133 assert(permutation.size() == numInputs && "Permutation size mismatch");
135
136 for (unsigned i = 0; i < (1u << numInputs); ++i) {
137 llvm::APInt input(numInputs, i);
138 llvm::APInt permutedInput = input;
139
140 // Apply permutation
141 for (unsigned j = 0; j < numInputs; ++j) {
142 if (permutation[j] != j)
143 permutedInput.setBitVal(j, input[permutation[j]]);
144 }
145
146 result.setOutput(permutedInput, getOutput(input));
147 }
148
149 return result;
150}
151
154
155 for (unsigned i = 0; i < (1u << numInputs); ++i) {
156 llvm::APInt input(numInputs, i);
157
158 // Apply negation using bitwise XOR
159 llvm::APInt negatedInput = input ^ llvm::APInt(numInputs, mask);
160
161 result.setOutput(negatedInput, getOutput(input));
162 }
163
164 return result;
165}
166
170
171 for (unsigned i = 0; i < (1u << numInputs); ++i) {
172 llvm::APInt input(numInputs, i);
173 llvm::APInt output = getOutput(input);
174
175 // Apply negation using bitwise XOR
176 llvm::APInt negatedOutput = output ^ llvm::APInt(numOutputs, negation);
177
178 result.setOutput(input, negatedOutput);
179 }
180
181 return result;
182}
183
185 const BinaryTruthTable &other) const {
186 assert(numInputs == other.numInputs && numOutputs == other.numOutputs);
187 return table.ult(other.table);
188}
189
191 return numInputs == other.numInputs && numOutputs == other.numOutputs &&
192 table == other.table;
193}
194
195void BinaryTruthTable::dump(llvm::raw_ostream &os) const {
196 os << "TruthTable(" << numInputs << " inputs, " << numOutputs << " outputs, "
197 << "value=";
198 os << table << ")\n";
199
200 // Print header
201 for (unsigned i = 0; i < numInputs; ++i) {
202 os << "i" << i << " ";
203 }
204 for (unsigned i = 0; i < numOutputs; ++i) {
205 os << "o" << i << " ";
206 }
207 os << "\n";
208
209 // Print separator
210 for (unsigned i = 0; i < numInputs + numOutputs; ++i) {
211 os << "---";
212 }
213 os << "\n";
214
215 // Print truth table rows
216 for (unsigned i = 0; i < (1u << numInputs); ++i) {
217 // Print input values
218 for (unsigned j = 0; j < numInputs; ++j) {
219 os << ((i >> j) & 1) << " ";
220 }
221
222 // Print output values
223 llvm::APInt input(numInputs, i);
224 llvm::APInt output = getOutput(input);
225 os << "|";
226 for (unsigned j = 0; j < numOutputs; ++j) {
227 os << output[j] << " ";
228 }
229 os << "\n";
230 }
231}
232
233llvm::APInt
235 ArrayRef<unsigned> mapping,
236 unsigned numExpandedInputs) {
237 unsigned numOrigInputs = mapping.size();
238 unsigned expandedSize = 1U << numExpandedInputs;
239
240 if (numOrigInputs == numExpandedInputs) {
241 bool isIdentity = true;
242 for (unsigned i = 0; i < numOrigInputs && isIdentity; ++i)
243 isIdentity = mapping[i] == i;
244 if (isIdentity)
245 return tt.zext(expandedSize);
246 }
247
248 if (tt.isZero())
249 return llvm::APInt::getZero(expandedSize);
250 if (tt.isAllOnes())
251 return llvm::APInt::getAllOnes(expandedSize);
252
253 if (numExpandedInputs <= 6)
254 return expandTruthTableToInputSpaceSmall(tt, mapping, numExpandedInputs);
255 return expandTruthTableToInputSpaceGeneric(tt, mapping, numExpandedInputs);
256}
257
258//===----------------------------------------------------------------------===//
259// NPNClass
260//===----------------------------------------------------------------------===//
261
262namespace {
263// Helper functions for permutation manipulation - kept as implementation
264// details
265
266/// Create an identity permutation of the given size.
267/// Result[i] = i for all i in [0, size).
268llvm::SmallVector<unsigned> identityPermutation(unsigned size) {
269 llvm::SmallVector<unsigned> identity(size);
270 for (unsigned i = 0; i < size; ++i)
271 identity[i] = i;
272 return identity;
273}
274
275/// Apply a permutation to a negation mask.
276/// Given a negation mask and a permutation, returns a new mask where
277/// the negation bits are reordered according to the permutation.
278unsigned permuteNegationMask(unsigned negationMask,
279 ArrayRef<unsigned> permutation) {
280 unsigned result = 0;
281 for (unsigned i = 0; i < permutation.size(); ++i)
282 if (negationMask & (1u << permutation[i]))
283 result |= (1u << i);
284 return result;
285}
286
287/// Create the inverse of a permutation.
288/// If permutation[i] = j, then inverse[j] = i.
289llvm::SmallVector<unsigned> invertPermutation(ArrayRef<unsigned> permutation) {
290 llvm::SmallVector<unsigned> inverse(permutation.size());
291 for (unsigned i = 0; i < permutation.size(); ++i) {
292 inverse[permutation[i]] = i;
293 }
294 return inverse;
295}
296
297llvm::SmallVector<unsigned>
298expandInputPermutation(const std::array<uint8_t, 4> &permutation) {
299 llvm::SmallVector<unsigned> result;
300 result.reserve(permutation.size());
301 for (uint8_t index : permutation)
302 result.push_back(index);
303 return result;
304}
305
306struct NPNTransform4 {
307 // Maps each output minterm in the transformed table back to the minterm to
308 // read from the source table.
309 std::array<uint8_t, 16> outputToSource = {};
310 // Inverse mapping used when reconstructing an original table from a chosen
311 // canonical representative.
312 std::array<uint8_t, 16> inverseOutputToSource = {};
313 std::array<uint8_t, 4> inputPermutation = {};
314 uint8_t inputNegation = 0;
315 bool outputNegation = false;
316};
317
318uint16_t applyNPNTransform4(uint16_t truthTable,
319 const std::array<uint8_t, 16> &outputToSource,
320 bool outputNegation) {
321 uint16_t result = 0;
322 for (unsigned output = 0; output != 16; ++output) {
323 unsigned bit = (truthTable >> outputToSource[output]) & 1u;
324 if (outputNegation)
325 bit ^= 1u;
326 result |= static_cast<uint16_t>(bit << output);
327 }
328 return result;
329}
330
331void buildCanonicalOrderNPNTransforms4(
332 llvm::SmallVectorImpl<NPNTransform4> &transforms) {
333 transforms.clear();
334 transforms.reserve(24 * 16 * 2);
335
336 // Enumerate the full 4-input NPN group in a deterministic order so table
337 // construction picks stable representatives and encodings.
338 for (unsigned negMask = 0; negMask != 16; ++negMask) {
339 std::array<unsigned, 4> permutation = {0, 1, 2, 3};
340 do {
341 std::array<unsigned, 4> inversePermutation = {};
342 for (unsigned i = 0; i != 4; ++i)
343 inversePermutation[permutation[i]] = i;
344
345 uint8_t currentNegMask = permuteNegationMask(negMask, permutation);
346 for (unsigned outputNegation = 0; outputNegation != 2; ++outputNegation) {
347 NPNTransform4 transform;
348 transform.inputNegation = currentNegMask;
349 transform.outputNegation = outputNegation;
350 for (unsigned i = 0; i != 4; ++i)
351 transform.inputPermutation[i] = permutation[i];
352
353 for (unsigned output = 0; output != 16; ++output) {
354 unsigned source = 0;
355 for (unsigned input = 0; input != 4; ++input) {
356 unsigned bit = (output >> inversePermutation[input]) & 1u;
357 bit ^= (negMask >> input) & 1u;
358 source |= bit << input;
359 }
360 transform.outputToSource[output] = source;
361 transform.inverseOutputToSource[source] = output;
362 }
363 transforms.push_back(transform);
364 }
365 } while (std::next_permutation(permutation.begin(), permutation.end()));
366 }
367}
368
369void collectNPN4Representatives(ArrayRef<NPNTransform4> transforms,
370 llvm::SmallVectorImpl<uint16_t> &reps) {
371 llvm::BitVector seen(1u << 16, false);
372 reps.clear();
373
374 for (unsigned seed = 0; seed != (1u << 16); ++seed) {
375 if (seen.test(seed))
376 continue;
377
378 // Walk the full NPN orbit of this truth table and pick the numerically
379 // smallest member as the canonical representative.
380 uint16_t representative = seed;
381 for (const auto &transform : transforms) {
382 uint16_t member = applyNPNTransform4(seed, transform.outputToSource,
383 transform.outputNegation);
384 seen.set(member);
385 representative = std::min(representative, member);
386 }
387 reps.push_back(representative);
388 }
389}
390
391} // anonymous namespace
392
394 llvm::SmallVectorImpl<uint16_t> &representatives) {
395 llvm::SmallVector<NPNTransform4, 24 * 16 * 2> transforms;
396 buildCanonicalOrderNPNTransforms4(transforms);
397 collectNPN4Representatives(transforms, representatives);
398}
399
401 llvm::SmallVector<uint16_t, 222> representatives;
403
404 llvm::SmallVector<NPNTransform4, 24 * 16 * 2> transforms;
405 buildCanonicalOrderNPNTransforms4(transforms);
406
407 llvm::BitVector initialized(entries4.size(), false);
408 auto isBetterEntry = [&](const Entry4 &candidate, const Entry4 &current) {
409 // Multiple transforms can map the same function to the same
410 // representative. Pick a deterministic encoding for the stored witness.
411 if (candidate.representative != current.representative)
412 return candidate.representative < current.representative;
413 if (candidate.inputNegation != current.inputNegation)
414 return candidate.inputNegation < current.inputNegation;
415 return candidate.outputNegation < current.outputNegation;
416 };
417
418 for (uint16_t representative : representatives) {
419 for (const auto &transform : transforms) {
420 // Starting from the canonical representative, populate every equivalent
421 // member with the transform needed to recover the representative.
422 uint16_t member =
423 applyNPNTransform4(representative, transform.inverseOutputToSource,
424 transform.outputNegation);
425
426 Entry4 candidate;
427 candidate.representative = representative;
428 candidate.inputPermutation = transform.inputPermutation;
429 candidate.inputNegation = transform.inputNegation;
430 candidate.outputNegation = transform.outputNegation;
431
432 if (!initialized.test(member) ||
433 isBetterEntry(candidate, entries4[member])) {
434 entries4[member] = candidate;
435 initialized.set(member);
436 }
437 }
438 }
439
440 assert(initialized.all() && "expected to populate all 4-input NPN entries");
441}
442
443bool NPNTable::lookup(const BinaryTruthTable &tt, NPNClass &result) const {
444 if (tt.numInputs != 4 || tt.numOutputs != 1)
445 return false;
446
447 const auto &entry = entries4[tt.table.getZExtValue()];
448 result = NPNClass(BinaryTruthTable(4, 1, APInt(16, entry.representative)),
449 expandInputPermutation(entry.inputPermutation),
450 entry.inputNegation, entry.outputNegation);
451 return true;
452}
453
455 const NPNClass &targetNPN,
456 llvm::SmallVectorImpl<unsigned> &permutation) const {
457 assert(inputPermutation.size() == targetNPN.inputPermutation.size() &&
458 "NPN classes must have the same number of inputs");
460 "NPN classes must be equivalent for input mapping");
461
462 // Create inverse permutation for this NPN class
463 auto thisInverse = invertPermutation(inputPermutation);
464
465 // For each input position in the target NPN class, find the corresponding
466 // input position in this NPN class
467 permutation.reserve(targetNPN.inputPermutation.size());
468 for (unsigned i = 0; i < targetNPN.inputPermutation.size(); ++i) {
469 // Target input i maps to canonical position targetNPN.inputPermutation[i]
470 // We need the input in this NPN class that maps to the same canonical
471 // position
472 unsigned canonicalPos = targetNPN.inputPermutation[i];
473 permutation.push_back(thisInverse[canonicalPos]);
474 }
475}
476
478 NPNClass canonical(tt);
479 // Initialize permutation with identity
480 canonical.inputPermutation = identityPermutation(tt.numInputs);
481 assert(tt.numInputs <= 8 && "Inputs are too large");
482 // Try all possible tables and pick the lexicographically smallest.
483 // FIXME: The time complexity is O(n! * 2^(n + m)) where n is the number
484 // of inputs and m is the number of outputs. This is not scalable so
485 // semi-canonical forms should be used instead.
486 for (uint32_t negMask = 0; negMask < (1u << tt.numInputs); ++negMask) {
487 BinaryTruthTable negatedTT = tt.applyInputNegation(negMask);
488
489 // Try all possible permutations
490 auto permutation = identityPermutation(tt.numInputs);
491
492 do {
493 BinaryTruthTable permutedTT = negatedTT.applyPermutation(permutation);
494
495 // Permute the negation mask according to the permutation
496 unsigned currentNegMask = permuteNegationMask(negMask, permutation);
497
498 // Try all negation masks for the output
499 for (unsigned outputNegMask = 0; outputNegMask < (1u << tt.numOutputs);
500 ++outputNegMask) {
501 // Apply output negation
502 BinaryTruthTable candidateTT =
503 permutedTT.applyOutputNegation(outputNegMask);
504
505 // Create a new NPN class for the candidate
506 NPNClass candidate(candidateTT, permutation, currentNegMask,
507 outputNegMask);
508
509 // Check if this candidate is lexicographically smaller than the
510 // current canonical form
511 if (candidate.isLexicographicallySmaller(canonical))
512 canonical = std::move(candidate);
513 }
514 } while (std::next_permutation(permutation.begin(), permutation.end()));
515 }
516
517 return canonical;
518}
519
520void NPNClass::dump(llvm::raw_ostream &os) const {
521 os << "NPNClass:\n";
522 os << " Input permutation: [";
523 for (unsigned i = 0; i < inputPermutation.size(); ++i) {
524 if (i > 0)
525 os << ", ";
526 os << inputPermutation[i];
527 }
528 os << "]\n";
529 os << " Input negation: 0b";
530 for (int i = truthTable.numInputs - 1; i >= 0; --i) {
531 os << ((inputNegation >> i) & 1);
532 }
533 os << "\n";
534 os << " Output negation: 0b";
535 for (int i = truthTable.numOutputs - 1; i >= 0; --i) {
536 os << ((outputNegation >> i) & 1);
537 }
538 os << "\n";
539 os << " Canonical truth table:\n";
540 truthTable.dump(os);
541}
542
543/// Precomputed masks for variables in truth tables up to 6 variables (64 bits).
544///
545/// In a truth table, bit position i represents minterm i, where the binary
546/// representation of i gives the variable values. For example, with 3 variables
547/// (a,b,c), bit 5 (binary 101) represents minterm a=1, b=0, c=1.
548///
549/// These masks identify which minterms have a particular variable value:
550/// - Masks[0][var] = minterms where var=0 (for negative literal !var)
551/// - Masks[1][var] = minterms where var=1 (for positive literal var)
552static constexpr uint64_t kVarMasks[2][6] = {
553 {0x5555555555555555ULL, 0x3333333333333333ULL, 0x0F0F0F0F0F0F0F0FULL,
554 0x00FF00FF00FF00FFULL, 0x0000FFFF0000FFFFULL,
555 0x00000000FFFFFFFFULL}, // var=0 masks
556 {0xAAAAAAAAAAAAAAAAULL, 0xCCCCCCCCCCCCCCCCULL, 0xF0F0F0F0F0F0F0F0ULL,
557 0xFF00FF00FF00FF00ULL, 0xFFFF0000FFFF0000ULL,
558 0xFFFFFFFF00000000ULL}, // var=1 masks
559};
560
561/// Create a mask for a variable in the truth table.
562/// For positive=true: mask has 1s where var=1 in the truth table encoding
563/// For positive=false: mask has 1s where var=0 in the truth table encoding
564template <bool positive>
565static APInt createVarMaskImpl(unsigned numVars, unsigned varIndex) {
566 assert(numVars <= 64 && "Number of variables too large");
567 assert(varIndex < numVars && "Variable index out of bounds");
568 uint64_t numBits = 1u << numVars;
569
570 // Use precomputed table for small cases (up to 6 variables = 64 bits)
571 if (numVars <= 6) {
572 assert(varIndex < 6);
573 uint64_t maskValue = kVarMasks[positive][varIndex];
574 // Mask off bits beyond numBits
575 if (numBits < 64)
576 maskValue &= (1ULL << numBits) - 1;
577 return APInt(numBits, maskValue);
578 }
579
580 // For larger cases, use getSplat to create repeating pattern
581 // Pattern width is 2^(var+1) bits
582 uint64_t patternWidth = 1u << (varIndex + 1);
583 APInt pattern(patternWidth, 0);
584
585 // Fill the appropriate half of the pattern
586 uint64_t shift = 1u << varIndex;
587 if constexpr (positive) {
588 // Set upper half: bits [shift, patternWidth)
589 pattern.setBits(shift, patternWidth);
590 } else {
591 // Set lower half: bits [0, shift)
592 pattern.setBits(0, shift);
593 }
594
595 return APInt::getSplat(numBits, pattern);
596}
597
598APInt circt::createVarMask(unsigned numVars, unsigned varIndex, bool positive) {
599 if (positive)
600 return createVarMaskImpl<true>(numVars, varIndex);
601 return createVarMaskImpl<false>(numVars, varIndex);
602}
603
604/// Compute cofactor of a Boolean function for a given variable.
605///
606/// A cofactor of a function f with respect to variable x is the function
607/// obtained by fixing x to a constant value:
608/// - Negative cofactor f|x=0 : f with variable x set to 0
609/// - Positive cofactor f|x=1 : f with variable x set to 1
610///
611/// Cofactors are fundamental in Boolean function decomposition and the
612/// Shannon expansion: f = x'*f|x=0 + x*f|x=1
613///
614/// In truth table representation, cofactors are computed by selecting the
615/// subset of minterms where the variable has the specified value, then
616/// replicating that pattern across the full truth table width.
617///
618/// Returns: (negative cofactor, positive cofactor)
619std::pair<APInt, APInt>
620circt::computeCofactors(const APInt &f, unsigned numVars, unsigned var) {
621 assert(numVars <= 64 && "Number of variables too large");
622 assert(var < numVars && "Variable index out of bounds");
623 assert(f.getBitWidth() == (1u << numVars) && "Truth table size mismatch");
624 uint64_t numBits = 1u << numVars;
625 uint64_t shift = 1u << var;
626
627 // Build masks using getSplat to replicate bit patterns
628 // For var at position k, we need blocks of size 2^k where:
629 // - mask0 selects lower 2^k bits of each 2^(k+1)-bit block (var=0)
630 // - mask1 selects upper 2^k bits of each 2^(k+1)-bit block (var=1)
631 APInt blockPattern = APInt::getLowBitsSet(2 * shift, shift);
632 APInt mask0 = APInt::getSplat(numBits, blockPattern);
633 APInt mask1 = mask0.shl(shift);
634
635 // Extract bits for each cofactor
636 APInt selected0 = f & mask0;
637 APInt selected1 = f & mask1;
638
639 // Replicate the selected bits across the full truth table
640 APInt cof0 = selected0 | selected0.shl(shift);
641 APInt cof1 = selected1 | selected1.lshr(shift);
642
643 return {cof0, cof1};
644}
645
646//===----------------------------------------------------------------------===//
647// SOPForm
648//===----------------------------------------------------------------------===//
649
651 APInt tt(1 << numVars, 0);
652 for (const auto &cube : cubes) {
653 APInt cubeTT = ~APInt(1 << numVars, 0);
654 for (unsigned i = 0; i < numVars; ++i) {
655 if (cube.hasLiteral(i))
656 cubeTT &= createVarMask(numVars, i, !cube.isLiteralInverted(i));
657 }
658 tt |= cubeTT;
659 }
660 return tt;
661}
662
663#ifndef NDEBUG
664void SOPForm::dump(llvm::raw_ostream &os) const {
665 os << "SOPForm: " << numVars << " vars, " << cubes.size() << " cubes\n";
666 for (const auto &cube : cubes) {
667 os << " (";
668 for (unsigned i = 0; i < numVars; ++i) {
669 if (cube.mask & (1ULL << i)) {
670 os << ((cube.inverted & (1ULL << i)) ? "!" : "");
671 os << "x" << i << " ";
672 }
673 }
674 os << ")\n";
675 }
676}
677#endif
678//===----------------------------------------------------------------------===//
679// ISOP Extraction
680//===----------------------------------------------------------------------===//
681
682namespace {
683
684/// Minato-Morreale ISOP algorithm.
685///
686/// Computes an Irredundant Sum-of-Products (ISOP) cover for a Boolean function.
687///
688/// References:
689/// - Minato, "Fast Generation of Irredundant Sum-of-Products Forms from Binary
690/// Decision Diagrams", SASIMI 1992.
691/// - Morreale, "Recursive Operators for Prime Implicant and Irredundant Normal
692/// Form Determination", IEEE TC 1970.
693///
694/// Implementation inspired by lsils/kitty library:
695/// https://github.com/lsils/kitty/blob/master/include/kitty/isop.hpp
696/// distributed under MIT License.
697///
698/// The algorithm recursively decomposes the function using Shannon expansion:
699/// f = !x * f|x=0 + x * f|x=1
700///
701/// For ISOP, we partition minterms into three groups:
702/// - Minterms only in negative cofactor: must use !x literal
703/// - Minterms only in positive cofactor: must use x literal
704/// - Minterms in both cofactors: can omit x from the cube
705///
706/// Parameters:
707/// tt: The ON-set (minterms that must be covered)
708/// dc: The don't-care set (minterms that can optionally be covered)
709/// Invariant: tt is a subset of dc (ON-set is subset of care set)
710/// numVars: Total number of variables in the function
711/// varIndex: Current variable index to start search from (counts down)
712/// result: Output SOP form (cubes are accumulated here)
713///
714/// Returns: The actual cover computed (subset of dc that covers tt)
715///
716/// The maximum recursion depth is equal to the number of variables (one level
717/// per variable). For typical use cases with TruthTable (up to 6-8 variables),
718/// this is not a concern. Since truth tables require 2^numVars bits, the
719/// recursion depth is not a limiting factor.
720APInt isopImpl(const APInt &tt, const APInt &dc, unsigned numVars,
721 unsigned varIndex, SOPForm &result) {
722 assert((tt & ~dc).isZero() && "tt must be subset of dc");
723
724 // Base case: nothing to cover
725 if (tt.isZero())
726 return tt;
727
728 // Base case: function is tautology (all don't-cares)
729 if (dc.isAllOnes()) {
730 result.cubes.emplace_back();
731 return dc;
732 }
733
734 // Find highest variable in support (top-down from varIndex).
735 // NOTE: It is well known that the order of variable selection largely
736 // affects the size of the resulting ISOP. There are numerous studies on
737 // implementing heuristics for variable selection that could improve results,
738 // albeit at the cost of runtime. We may consider implementing such
739 // heuristics in the future.
740 int var = -1;
741 APInt negCof, posCof, negDC, posDC;
742 for (int i = varIndex - 1; i >= 0; --i) {
743 std::tie(negCof, posCof) = computeCofactors(tt, numVars, i);
744 std::tie(negDC, posDC) = computeCofactors(dc, numVars, i);
745 if (negCof != posCof || negDC != posDC) {
746 var = i;
747 break;
748 }
749 }
750 assert(var >= 0 && "No variable found in support");
751
752 // Recurse on minterms unique to negative cofactor (will get !var literal)
753 size_t negBegin = result.cubes.size();
754 APInt negCover = isopImpl(negCof & ~posDC, negDC, numVars, var, result);
755 size_t negEnd = result.cubes.size();
756
757 // Recurse on minterms unique to positive cofactor (will get var literal)
758 APInt posCover = isopImpl(posCof & ~negDC, posDC, numVars, var, result);
759 size_t posEnd = result.cubes.size();
760
761 // Recurse on shared minterms (no literal for this variable)
762 APInt remaining = (negCof & ~negCover) | (posCof & ~posCover);
763 APInt sharedCover = isopImpl(remaining, negDC & posDC, numVars, var, result);
764
765 // Compute total cover by restricting each sub-cover to its domain
766 APInt negMask = createVarMaskImpl<false>(numVars, var);
767 APInt totalCover = sharedCover | (negCover & negMask) | (posCover & ~negMask);
768
769 // Add literals to cubes from recursions
770 for (size_t i = negBegin; i < negEnd; ++i)
771 result.cubes[i].setLiteral(var, true);
772
773 for (size_t i = negEnd; i < posEnd; ++i)
774 result.cubes[i].setLiteral(var, false);
775
776 return totalCover;
777}
778
779} // namespace
780
781SOPForm circt::extractISOP(const APInt &truthTable, unsigned numVars) {
782 assert((1u << numVars) == truthTable.getBitWidth() &&
783 "Truth table size must match 2^numVars");
784 SOPForm sop(numVars);
785
786 if (numVars == 0 || truthTable.isZero())
787 return sop;
788
789 (void)isopImpl(truthTable, truthTable, numVars, numVars, sop);
790
791 return sop;
792}
assert(baseType &&"element must be base type")
RewritePatternSet pattern
static APInt createVarMaskImpl(unsigned numVars, unsigned varIndex)
Create a mask for a variable in the truth table.
static constexpr uint64_t kVarMasks[2][6]
Precomputed masks for variables in truth tables up to 6 variables (64 bits).
std::array< Entry4, 1u<< 16 > entries4
Definition TruthTable.h:183
bool lookup(const BinaryTruthTable &tt, NPNClass &result) const
Returns false if the given truth table shape is unsupported.
llvm::APInt expandTruthTableToInputSpace(const llvm::APInt &tt, ArrayRef< unsigned > inputMapping, unsigned numExpandedInputs)
Expand a truth table to a larger input space using the given input mapping.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
SOPForm extractISOP(const llvm::APInt &truthTable, unsigned numVars)
Extract ISOP (Irredundant Sum-of-Products) from a truth table.
std::pair< llvm::APInt, llvm::APInt > computeCofactors(const llvm::APInt &f, unsigned numVars, unsigned varIndex)
Compute cofactor of a Boolean function for a given variable.
llvm::APInt createVarMask(unsigned numVars, unsigned varIndex, bool positive)
Create a mask for a variable in the truth table.
void collectCanonicalNPN4Representatives(llvm::SmallVectorImpl< uint16_t > &representatives)
Collect all canonical 4-input single-output NPN representatives in ascending truth-table order.
Represents a boolean function as a truth table.
Definition TruthTable.h:41
BinaryTruthTable applyOutputNegation(unsigned negation) const
Apply output negation to create a new truth table.
llvm::APInt table
Truth table data as a packed bit vector.
Definition TruthTable.h:44
BinaryTruthTable applyInputNegation(unsigned mask) const
Apply input negation to create a new truth table.
unsigned numInputs
Number of inputs for this boolean function.
Definition TruthTable.h:42
void dump(llvm::raw_ostream &os=llvm::errs()) const
Debug dump method for truth tables.
bool isLexicographicallySmaller(const BinaryTruthTable &other) const
Check if this truth table is lexicographically smaller than another.
bool operator==(const BinaryTruthTable &other) const
Equality comparison for truth tables.
llvm::APInt getOutput(const llvm::APInt &input) const
Get the output value for a given input combination.
BinaryTruthTable applyPermutation(ArrayRef< unsigned > permutation) const
Apply input permutation to create a new truth table.
void setOutput(const llvm::APInt &input, const llvm::APInt &output)
Set the output value for a given input combination.
unsigned numOutputs
Number of outputs for this boolean function.
Definition TruthTable.h:43
Represents the canonical form of a boolean function under NPN equivalence.
Definition TruthTable.h:106
unsigned outputNegation
Output negation mask.
Definition TruthTable.h:110
static NPNClass computeNPNCanonicalForm(const BinaryTruthTable &tt)
Compute the canonical NPN form for a given truth table.
void getInputPermutation(const NPNClass &targetNPN, llvm::SmallVectorImpl< unsigned > &permutation) const
Get input permutation from this NPN class to another equivalent NPN class.
BinaryTruthTable truthTable
Canonical truth table.
Definition TruthTable.h:107
void dump(llvm::raw_ostream &os=llvm::errs()) const
Debug dump method for NPN classes.
bool equivalentOtherThanPermutation(const NPNClass &other) const
Equality comparison for NPN classes.
Definition TruthTable.h:149
llvm::SmallVector< unsigned > inputPermutation
Input permutation applied.
Definition TruthTable.h:108
unsigned inputNegation
Input negation mask.
Definition TruthTable.h:109
bool isLexicographicallySmaller(const NPNClass &other) const
Definition TruthTable.h:155
std::array< uint8_t, 4 > inputPermutation
Definition TruthTable.h:178
Represents a sum-of-products expression.
Definition TruthTable.h:285
unsigned numVars
Definition TruthTable.h:287
void dump(llvm::raw_ostream &os=llvm::errs()) const
Debug dump method for SOP forms.
llvm::SmallVector< Cube > cubes
Definition TruthTable.h:286
llvm::APInt computeTruthTable() const
Compute the truth table represented by this SOP form.