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 auto targetInverse = invertPermutation(targetNPN.inputPermutation);
488
489 // For each input position in the target NPN class, find the corresponding
490 // input position in this NPN class
491 permutation.clear();
492 permutation.reserve(targetNPN.inputPermutation.size());
493 for (unsigned targetInput = 0;
494 targetInput < targetNPN.inputPermutation.size(); ++targetInput) {
495 // `inputPermutation[canonicalPos]` is the original input at that canonical
496 // position. Find the canonical position of the target input and return the
497 // input in this NPN class at the same canonical position.
498 unsigned canonicalPos = targetInverse[targetInput];
499 permutation.push_back(inputPermutation[canonicalPos]);
500 }
501}
502
504 NPNClass canonical(tt);
505 // Initialize permutation with identity
506 canonical.inputPermutation = identityPermutation(tt.numInputs);
507 assert(tt.numInputs <= 8 && "Inputs are too large");
508 // Try all possible tables and pick the lexicographically smallest.
509 // FIXME: The time complexity is O(n! * 2^(n + m)) where n is the number
510 // of inputs and m is the number of outputs. This is not scalable so
511 // semi-canonical forms should be used instead.
512 for (uint32_t negMask = 0; negMask < (1u << tt.numInputs); ++negMask) {
513 BinaryTruthTable negatedTT = tt.applyInputNegation(negMask);
514
515 // Try all possible permutations
516 auto permutation = identityPermutation(tt.numInputs);
517
518 do {
519 BinaryTruthTable permutedTT = negatedTT.applyPermutation(permutation);
520
521 // Permute the negation mask according to the permutation
522 unsigned currentNegMask = permuteNegationMask(negMask, permutation);
523
524 // Try all negation masks for the output
525 for (unsigned outputNegMask = 0; outputNegMask < (1u << tt.numOutputs);
526 ++outputNegMask) {
527 // Apply output negation
528 BinaryTruthTable candidateTT =
529 permutedTT.applyOutputNegation(outputNegMask);
530
531 // Create a new NPN class for the candidate
532 NPNClass candidate(candidateTT, permutation, currentNegMask,
533 outputNegMask);
534
535 // Check if this candidate is lexicographically smaller than the
536 // current canonical form
537 if (candidate.isLexicographicallySmaller(canonical))
538 canonical = std::move(candidate);
539 }
540 } while (std::next_permutation(permutation.begin(), permutation.end()));
541 }
542
543 return canonical;
544}
545
546void NPNClass::dump(llvm::raw_ostream &os) const {
547 os << "NPNClass:\n";
548 os << " Input permutation: [";
549 for (unsigned i = 0; i < inputPermutation.size(); ++i) {
550 if (i > 0)
551 os << ", ";
552 os << inputPermutation[i];
553 }
554 os << "]\n";
555 os << " Input negation: 0b";
556 for (int i = truthTable.numInputs - 1; i >= 0; --i) {
557 os << ((inputNegation >> i) & 1);
558 }
559 os << "\n";
560 os << " Output negation: 0b";
561 for (int i = truthTable.numOutputs - 1; i >= 0; --i) {
562 os << ((outputNegation >> i) & 1);
563 }
564 os << "\n";
565 os << " Canonical truth table:\n";
566 truthTable.dump(os);
567}
568
569/// Precomputed masks for variables in truth tables up to 6 variables (64 bits).
570///
571/// In a truth table, bit position i represents minterm i, where the binary
572/// representation of i gives the variable values. For example, with 3 variables
573/// (a,b,c), bit 5 (binary 101) represents minterm a=1, b=0, c=1.
574///
575/// These masks identify which minterms have a particular variable value:
576/// - Masks[0][var] = minterms where var=0 (for negative literal !var)
577/// - Masks[1][var] = minterms where var=1 (for positive literal var)
578static constexpr uint64_t kVarMasks[2][6] = {
579 {0x5555555555555555ULL, 0x3333333333333333ULL, 0x0F0F0F0F0F0F0F0FULL,
580 0x00FF00FF00FF00FFULL, 0x0000FFFF0000FFFFULL,
581 0x00000000FFFFFFFFULL}, // var=0 masks
582 {0xAAAAAAAAAAAAAAAAULL, 0xCCCCCCCCCCCCCCCCULL, 0xF0F0F0F0F0F0F0F0ULL,
583 0xFF00FF00FF00FF00ULL, 0xFFFF0000FFFF0000ULL,
584 0xFFFFFFFF00000000ULL}, // var=1 masks
585};
586
587/// Create a mask for a variable in the truth table.
588/// For positive=true: mask has 1s where var=1 in the truth table encoding
589/// For positive=false: mask has 1s where var=0 in the truth table encoding
590template <bool positive>
591static APInt createVarMaskImpl(unsigned numVars, unsigned varIndex) {
592 assert(numVars <= 64 && "Number of variables too large");
593 assert(varIndex < numVars && "Variable index out of bounds");
594 uint64_t numBits = 1u << numVars;
595
596 // Use precomputed table for small cases (up to 6 variables = 64 bits)
597 if (numVars <= 6) {
598 assert(varIndex < 6);
599 uint64_t maskValue = kVarMasks[positive][varIndex];
600 // Mask off bits beyond numBits
601 if (numBits < 64)
602 maskValue &= (1ULL << numBits) - 1;
603 return APInt(numBits, maskValue);
604 }
605
606 // For larger cases, use getSplat to create repeating pattern
607 // Pattern width is 2^(var+1) bits
608 uint64_t patternWidth = 1u << (varIndex + 1);
609 APInt pattern(patternWidth, 0);
610
611 // Fill the appropriate half of the pattern
612 uint64_t shift = 1u << varIndex;
613 if constexpr (positive) {
614 // Set upper half: bits [shift, patternWidth)
615 pattern.setBits(shift, patternWidth);
616 } else {
617 // Set lower half: bits [0, shift)
618 pattern.setBits(0, shift);
619 }
620
621 return APInt::getSplat(numBits, pattern);
622}
623
624APInt circt::createVarMask(unsigned numVars, unsigned varIndex, bool positive) {
625 if (positive)
626 return createVarMaskImpl<true>(numVars, varIndex);
627 return createVarMaskImpl<false>(numVars, varIndex);
628}
629
630/// Compute cofactor of a Boolean function for a given variable.
631///
632/// A cofactor of a function f with respect to variable x is the function
633/// obtained by fixing x to a constant value:
634/// - Negative cofactor f|x=0 : f with variable x set to 0
635/// - Positive cofactor f|x=1 : f with variable x set to 1
636///
637/// Cofactors are fundamental in Boolean function decomposition and the
638/// Shannon expansion: f = x'*f|x=0 + x*f|x=1
639///
640/// In truth table representation, cofactors are computed by selecting the
641/// subset of minterms where the variable has the specified value, then
642/// replicating that pattern across the full truth table width.
643///
644/// Returns: (negative cofactor, positive cofactor)
645std::pair<APInt, APInt>
646circt::computeCofactors(const APInt &f, unsigned numVars, unsigned var) {
647 assert(numVars <= 64 && "Number of variables too large");
648 assert(var < numVars && "Variable index out of bounds");
649 assert(f.getBitWidth() == (1u << numVars) && "Truth table size mismatch");
650 uint64_t numBits = 1u << numVars;
651 uint64_t shift = 1u << var;
652
653 // Build masks using getSplat to replicate bit patterns
654 // For var at position k, we need blocks of size 2^k where:
655 // - mask0 selects lower 2^k bits of each 2^(k+1)-bit block (var=0)
656 // - mask1 selects upper 2^k bits of each 2^(k+1)-bit block (var=1)
657 APInt blockPattern = APInt::getLowBitsSet(2 * shift, shift);
658 APInt mask0 = APInt::getSplat(numBits, blockPattern);
659 APInt mask1 = mask0.shl(shift);
660
661 // Extract bits for each cofactor
662 APInt selected0 = f & mask0;
663 APInt selected1 = f & mask1;
664
665 // Replicate the selected bits across the full truth table
666 APInt cof0 = selected0 | selected0.shl(shift);
667 APInt cof1 = selected1 | selected1.lshr(shift);
668
669 return {cof0, cof1};
670}
671
672//===----------------------------------------------------------------------===//
673// SOPForm
674//===----------------------------------------------------------------------===//
675
677 APInt tt(1 << numVars, 0);
678 for (const auto &cube : cubes) {
679 APInt cubeTT = ~APInt(1 << numVars, 0);
680 for (unsigned i = 0; i < numVars; ++i) {
681 if (cube.hasLiteral(i))
682 cubeTT &= createVarMask(numVars, i, !cube.isLiteralInverted(i));
683 }
684 tt |= cubeTT;
685 }
686 return tt;
687}
688
689#ifndef NDEBUG
690void SOPForm::dump(llvm::raw_ostream &os) const {
691 os << "SOPForm: " << numVars << " vars, " << cubes.size() << " cubes\n";
692 for (const auto &cube : cubes) {
693 os << " (";
694 for (unsigned i = 0; i < numVars; ++i) {
695 if (cube.mask & (1ULL << i)) {
696 os << ((cube.inverted & (1ULL << i)) ? "!" : "");
697 os << "x" << i << " ";
698 }
699 }
700 os << ")\n";
701 }
702}
703#endif
704//===----------------------------------------------------------------------===//
705// ISOP Extraction
706//===----------------------------------------------------------------------===//
707
708namespace {
709
710/// Minato-Morreale ISOP algorithm.
711///
712/// Computes an Irredundant Sum-of-Products (ISOP) cover for a Boolean function.
713///
714/// References:
715/// - Minato, "Fast Generation of Irredundant Sum-of-Products Forms from Binary
716/// Decision Diagrams", SASIMI 1992.
717/// - Morreale, "Recursive Operators for Prime Implicant and Irredundant Normal
718/// Form Determination", IEEE TC 1970.
719///
720/// Implementation inspired by lsils/kitty library:
721/// https://github.com/lsils/kitty/blob/master/include/kitty/isop.hpp
722/// distributed under MIT License.
723///
724/// The algorithm recursively decomposes the function using Shannon expansion:
725/// f = !x * f|x=0 + x * f|x=1
726///
727/// For ISOP, we partition minterms into three groups:
728/// - Minterms only in negative cofactor: must use !x literal
729/// - Minterms only in positive cofactor: must use x literal
730/// - Minterms in both cofactors: can omit x from the cube
731///
732/// Parameters:
733/// tt: The ON-set (minterms that must be covered)
734/// dc: The don't-care set (minterms that can optionally be covered)
735/// Invariant: tt is a subset of dc (ON-set is subset of care set)
736/// numVars: Total number of variables in the function
737/// varIndex: Current variable index to start search from (counts down)
738/// result: Output SOP form (cubes are accumulated here)
739///
740/// Returns: The actual cover computed (subset of dc that covers tt)
741///
742/// The maximum recursion depth is equal to the number of variables (one level
743/// per variable). For typical use cases with TruthTable (up to 6-8 variables),
744/// this is not a concern. Since truth tables require 2^numVars bits, the
745/// recursion depth is not a limiting factor.
746APInt isopImpl(const APInt &tt, const APInt &dc, unsigned numVars,
747 unsigned varIndex, SOPForm &result) {
748 assert((tt & ~dc).isZero() && "tt must be subset of dc");
749
750 // Base case: nothing to cover
751 if (tt.isZero())
752 return tt;
753
754 // Base case: function is tautology (all don't-cares)
755 if (dc.isAllOnes()) {
756 result.cubes.emplace_back();
757 return dc;
758 }
759
760 // Find highest variable in support (top-down from varIndex).
761 // NOTE: It is well known that the order of variable selection largely
762 // affects the size of the resulting ISOP. There are numerous studies on
763 // implementing heuristics for variable selection that could improve results,
764 // albeit at the cost of runtime. We may consider implementing such
765 // heuristics in the future.
766 int var = -1;
767 APInt negCof, posCof, negDC, posDC;
768 for (int i = varIndex - 1; i >= 0; --i) {
769 std::tie(negCof, posCof) = computeCofactors(tt, numVars, i);
770 std::tie(negDC, posDC) = computeCofactors(dc, numVars, i);
771 if (negCof != posCof || negDC != posDC) {
772 var = i;
773 break;
774 }
775 }
776 assert(var >= 0 && "No variable found in support");
777
778 // Recurse on minterms unique to negative cofactor (will get !var literal)
779 size_t negBegin = result.cubes.size();
780 APInt negCover = isopImpl(negCof & ~posDC, negDC, numVars, var, result);
781 size_t negEnd = result.cubes.size();
782
783 // Recurse on minterms unique to positive cofactor (will get var literal)
784 APInt posCover = isopImpl(posCof & ~negDC, posDC, numVars, var, result);
785 size_t posEnd = result.cubes.size();
786
787 // Recurse on shared minterms (no literal for this variable)
788 APInt remaining = (negCof & ~negCover) | (posCof & ~posCover);
789 APInt sharedCover = isopImpl(remaining, negDC & posDC, numVars, var, result);
790
791 // Compute total cover by restricting each sub-cover to its domain
792 APInt negMask = createVarMaskImpl<false>(numVars, var);
793 APInt totalCover = sharedCover | (negCover & negMask) | (posCover & ~negMask);
794
795 // Add literals to cubes from recursions
796 for (size_t i = negBegin; i < negEnd; ++i)
797 result.cubes[i].setLiteral(var, true);
798
799 for (size_t i = negEnd; i < posEnd; ++i)
800 result.cubes[i].setLiteral(var, false);
801
802 return totalCover;
803}
804
805} // namespace
806
807SOPForm circt::extractISOP(const APInt &truthTable, unsigned numVars) {
808 assert((1u << numVars) == truthTable.getBitWidth() &&
809 "Truth table size must match 2^numVars");
810 SOPForm sop(numVars);
811
812 if (numVars == 0 || truthTable.isZero())
813 return sop;
814
815 (void)isopImpl(truthTable, truthTable, numVars, numVars, sop);
816
817 return sop;
818}
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.