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