CIRCT 22.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/STLExtras.h"
17#include "llvm/Support/MathExtras.h"
18#include <algorithm>
19#include <cassert>
20
21using namespace circt;
22using llvm::APInt;
23
24//===----------------------------------------------------------------------===//
25// BinaryTruthTable
26//===----------------------------------------------------------------------===//
27
28llvm::APInt BinaryTruthTable::getOutput(const llvm::APInt &input) const {
29 assert(input.getBitWidth() == numInputs && "Input width mismatch");
30 return table.extractBits(numOutputs, input.getZExtValue() * numOutputs);
31}
32
33void BinaryTruthTable::setOutput(const llvm::APInt &input,
34 const llvm::APInt &output) {
35 assert(input.getBitWidth() == numInputs && "Input width mismatch");
36 assert(output.getBitWidth() == numOutputs && "Output width mismatch");
37 assert(input.getBitWidth() < 32 && "Input width too large");
38 unsigned offset = input.getZExtValue() * numOutputs;
39 for (unsigned i = 0; i < numOutputs; ++i)
40 table.setBitVal(offset + i, output[i]);
41}
42
44BinaryTruthTable::applyPermutation(ArrayRef<unsigned> permutation) const {
45 assert(permutation.size() == numInputs && "Permutation size mismatch");
47
48 for (unsigned i = 0; i < (1u << numInputs); ++i) {
49 llvm::APInt input(numInputs, i);
50 llvm::APInt permutedInput = input;
51
52 // Apply permutation
53 for (unsigned j = 0; j < numInputs; ++j) {
54 if (permutation[j] != j)
55 permutedInput.setBitVal(j, input[permutation[j]]);
56 }
57
58 result.setOutput(permutedInput, getOutput(input));
59 }
60
61 return result;
62}
63
66
67 for (unsigned i = 0; i < (1u << numInputs); ++i) {
68 llvm::APInt input(numInputs, i);
69
70 // Apply negation using bitwise XOR
71 llvm::APInt negatedInput = input ^ llvm::APInt(numInputs, mask);
72
73 result.setOutput(negatedInput, getOutput(input));
74 }
75
76 return result;
77}
78
80BinaryTruthTable::applyOutputNegation(unsigned negation) const {
82
83 for (unsigned i = 0; i < (1u << numInputs); ++i) {
84 llvm::APInt input(numInputs, i);
85 llvm::APInt output = getOutput(input);
86
87 // Apply negation using bitwise XOR
88 llvm::APInt negatedOutput = output ^ llvm::APInt(numOutputs, negation);
89
90 result.setOutput(input, negatedOutput);
91 }
92
93 return result;
94}
95
97 const BinaryTruthTable &other) const {
98 assert(numInputs == other.numInputs && numOutputs == other.numOutputs);
99 return table.ult(other.table);
100}
101
103 return numInputs == other.numInputs && numOutputs == other.numOutputs &&
104 table == other.table;
105}
106
107void BinaryTruthTable::dump(llvm::raw_ostream &os) const {
108 os << "TruthTable(" << numInputs << " inputs, " << numOutputs << " outputs, "
109 << "value=";
110 os << table << ")\n";
111
112 // Print header
113 for (unsigned i = 0; i < numInputs; ++i) {
114 os << "i" << i << " ";
115 }
116 for (unsigned i = 0; i < numOutputs; ++i) {
117 os << "o" << i << " ";
118 }
119 os << "\n";
120
121 // Print separator
122 for (unsigned i = 0; i < numInputs + numOutputs; ++i) {
123 os << "---";
124 }
125 os << "\n";
126
127 // Print truth table rows
128 for (unsigned i = 0; i < (1u << numInputs); ++i) {
129 // Print input values
130 for (unsigned j = 0; j < numInputs; ++j) {
131 os << ((i >> j) & 1) << " ";
132 }
133
134 // Print output values
135 llvm::APInt input(numInputs, i);
136 llvm::APInt output = getOutput(input);
137 os << "|";
138 for (unsigned j = 0; j < numOutputs; ++j) {
139 os << output[j] << " ";
140 }
141 os << "\n";
142 }
143}
144
145//===----------------------------------------------------------------------===//
146// NPNClass
147//===----------------------------------------------------------------------===//
148
149namespace {
150// Helper functions for permutation manipulation - kept as implementation
151// details
152
153/// Create an identity permutation of the given size.
154/// Result[i] = i for all i in [0, size).
155llvm::SmallVector<unsigned> identityPermutation(unsigned size) {
156 llvm::SmallVector<unsigned> identity(size);
157 for (unsigned i = 0; i < size; ++i)
158 identity[i] = i;
159 return identity;
160}
161
162/// Apply a permutation to a negation mask.
163/// Given a negation mask and a permutation, returns a new mask where
164/// the negation bits are reordered according to the permutation.
165unsigned permuteNegationMask(unsigned negationMask,
166 ArrayRef<unsigned> permutation) {
167 unsigned result = 0;
168 for (unsigned i = 0; i < permutation.size(); ++i) {
169 if (negationMask & (1u << i)) {
170 result |= (1u << permutation[i]);
171 }
172 }
173 return result;
174}
175
176/// Create the inverse of a permutation.
177/// If permutation[i] = j, then inverse[j] = i.
178llvm::SmallVector<unsigned> invertPermutation(ArrayRef<unsigned> permutation) {
179 llvm::SmallVector<unsigned> inverse(permutation.size());
180 for (unsigned i = 0; i < permutation.size(); ++i) {
181 inverse[permutation[i]] = i;
182 }
183 return inverse;
184}
185
186} // anonymous namespace
187
189 const NPNClass &targetNPN,
190 llvm::SmallVectorImpl<unsigned> &permutation) const {
191 assert(inputPermutation.size() == targetNPN.inputPermutation.size() &&
192 "NPN classes must have the same number of inputs");
194 "NPN classes must be equivalent for input mapping");
195
196 // Create inverse permutation for this NPN class
197 auto thisInverse = invertPermutation(inputPermutation);
198
199 // For each input position in the target NPN class, find the corresponding
200 // input position in this NPN class
201 permutation.reserve(targetNPN.inputPermutation.size());
202 for (unsigned i = 0; i < targetNPN.inputPermutation.size(); ++i) {
203 // Target input i maps to canonical position targetNPN.inputPermutation[i]
204 // We need the input in this NPN class that maps to the same canonical
205 // position
206 unsigned canonicalPos = targetNPN.inputPermutation[i];
207 permutation.push_back(thisInverse[canonicalPos]);
208 }
209}
210
212 NPNClass canonical(tt);
213 // Initialize permutation with identity
214 canonical.inputPermutation = identityPermutation(tt.numInputs);
215 assert(tt.numInputs <= 8 && "Inputs are too large");
216 // Try all possible tables and pick the lexicographically smallest.
217 // FIXME: The time complexity is O(n! * 2^(n + m)) where n is the number
218 // of inputs and m is the number of outputs. This is not scalable so
219 // semi-canonical forms should be used instead.
220 for (uint32_t negMask = 0; negMask < (1u << tt.numInputs); ++negMask) {
221 BinaryTruthTable negatedTT = tt.applyInputNegation(negMask);
222
223 // Try all possible permutations
224 auto permutation = identityPermutation(tt.numInputs);
225
226 do {
227 BinaryTruthTable permutedTT = negatedTT.applyPermutation(permutation);
228
229 // Permute the negation mask according to the permutation
230 unsigned currentNegMask = permuteNegationMask(negMask, permutation);
231
232 // Try all negation masks for the output
233 for (unsigned outputNegMask = 0; outputNegMask < (1u << tt.numOutputs);
234 ++outputNegMask) {
235 // Apply output negation
236 BinaryTruthTable candidateTT =
237 permutedTT.applyOutputNegation(outputNegMask);
238
239 // Create a new NPN class for the candidate
240 NPNClass candidate(candidateTT, permutation, currentNegMask,
241 outputNegMask);
242
243 // Check if this candidate is lexicographically smaller than the
244 // current canonical form
245 if (candidate.isLexicographicallySmaller(canonical))
246 canonical = std::move(candidate);
247 }
248 } while (std::next_permutation(permutation.begin(), permutation.end()));
249 }
250
251 return canonical;
252}
253
254void NPNClass::dump(llvm::raw_ostream &os) const {
255 os << "NPNClass:\n";
256 os << " Input permutation: [";
257 for (unsigned i = 0; i < inputPermutation.size(); ++i) {
258 if (i > 0)
259 os << ", ";
260 os << inputPermutation[i];
261 }
262 os << "]\n";
263 os << " Input negation: 0b";
264 for (int i = truthTable.numInputs - 1; i >= 0; --i) {
265 os << ((inputNegation >> i) & 1);
266 }
267 os << "\n";
268 os << " Output negation: 0b";
269 for (int i = truthTable.numOutputs - 1; i >= 0; --i) {
270 os << ((outputNegation >> i) & 1);
271 }
272 os << "\n";
273 os << " Canonical truth table:\n";
274 truthTable.dump(os);
275}
276
277/// Precomputed masks for variables in truth tables up to 6 variables (64 bits).
278///
279/// In a truth table, bit position i represents minterm i, where the binary
280/// representation of i gives the variable values. For example, with 3 variables
281/// (a,b,c), bit 5 (binary 101) represents minterm a=1, b=0, c=1.
282///
283/// These masks identify which minterms have a particular variable value:
284/// - Masks[0][var] = minterms where var=0 (for negative literal !var)
285/// - Masks[1][var] = minterms where var=1 (for positive literal var)
286static constexpr uint64_t kVarMasks[2][6] = {
287 {0x5555555555555555ULL, 0x3333333333333333ULL, 0x0F0F0F0F0F0F0F0FULL,
288 0x00FF00FF00FF00FFULL, 0x0000FFFF0000FFFFULL,
289 0x00000000FFFFFFFFULL}, // var=0 masks
290 {0xAAAAAAAAAAAAAAAAULL, 0xCCCCCCCCCCCCCCCCULL, 0xF0F0F0F0F0F0F0F0ULL,
291 0xFF00FF00FF00FF00ULL, 0xFFFF0000FFFF0000ULL,
292 0xFFFFFFFF00000000ULL}, // var=1 masks
293};
294
295/// Create a mask for a variable in the truth table.
296/// For positive=true: mask has 1s where var=1 in the truth table encoding
297/// For positive=false: mask has 1s where var=0 in the truth table encoding
298template <bool positive>
299static APInt createVarMaskImpl(unsigned numVars, unsigned varIndex) {
300 assert(numVars <= 64 && "Number of variables too large");
301 assert(varIndex < numVars && "Variable index out of bounds");
302 uint64_t numBits = 1u << numVars;
303
304 // Use precomputed table for small cases (up to 6 variables = 64 bits)
305 if (numVars <= 6) {
306 assert(varIndex < 6);
307 uint64_t maskValue = kVarMasks[positive][varIndex];
308 // Mask off bits beyond numBits
309 if (numBits < 64)
310 maskValue &= (1ULL << numBits) - 1;
311 return APInt(numBits, maskValue);
312 }
313
314 // For larger cases, use getSplat to create repeating pattern
315 // Pattern width is 2^(var+1) bits
316 uint64_t patternWidth = 1u << (varIndex + 1);
317 APInt pattern(patternWidth, 0);
318
319 // Fill the appropriate half of the pattern
320 uint64_t shift = 1u << varIndex;
321 if constexpr (positive) {
322 // Set upper half: bits [shift, patternWidth)
323 pattern.setBits(shift, patternWidth);
324 } else {
325 // Set lower half: bits [0, shift)
326 pattern.setBits(0, shift);
327 }
328
329 return APInt::getSplat(numBits, pattern);
330}
331
332APInt circt::createVarMask(unsigned numVars, unsigned varIndex, bool positive) {
333 if (positive)
334 return createVarMaskImpl<true>(numVars, varIndex);
335 return createVarMaskImpl<false>(numVars, varIndex);
336}
337
338/// Compute cofactor of a Boolean function for a given variable.
339///
340/// A cofactor of a function f with respect to variable x is the function
341/// obtained by fixing x to a constant value:
342/// - Negative cofactor f|x=0 : f with variable x set to 0
343/// - Positive cofactor f|x=1 : f with variable x set to 1
344///
345/// Cofactors are fundamental in Boolean function decomposition and the
346/// Shannon expansion: f = x'*f|x=0 + x*f|x=1
347///
348/// In truth table representation, cofactors are computed by selecting the
349/// subset of minterms where the variable has the specified value, then
350/// replicating that pattern across the full truth table width.
351///
352/// Returns: (negative cofactor, positive cofactor)
353std::pair<APInt, APInt>
354circt::computeCofactors(const APInt &f, unsigned numVars, unsigned var) {
355 assert(numVars <= 64 && "Number of variables too large");
356 assert(var < numVars && "Variable index out of bounds");
357 assert(f.getBitWidth() == (1u << numVars) && "Truth table size mismatch");
358 uint64_t numBits = 1u << numVars;
359 uint64_t shift = 1u << var;
360
361 // Build masks using getSplat to replicate bit patterns
362 // For var at position k, we need blocks of size 2^k where:
363 // - mask0 selects lower 2^k bits of each 2^(k+1)-bit block (var=0)
364 // - mask1 selects upper 2^k bits of each 2^(k+1)-bit block (var=1)
365 APInt blockPattern = APInt::getLowBitsSet(2 * shift, shift);
366 APInt mask0 = APInt::getSplat(numBits, blockPattern);
367 APInt mask1 = mask0.shl(shift);
368
369 // Extract bits for each cofactor
370 APInt selected0 = f & mask0;
371 APInt selected1 = f & mask1;
372
373 // Replicate the selected bits across the full truth table
374 APInt cof0 = selected0 | selected0.shl(shift);
375 APInt cof1 = selected1 | selected1.lshr(shift);
376
377 return {cof0, cof1};
378}
379
380//===----------------------------------------------------------------------===//
381// SOPForm
382//===----------------------------------------------------------------------===//
383
385 APInt tt(1 << numVars, 0);
386 for (const auto &cube : cubes) {
387 APInt cubeTT = ~APInt(1 << numVars, 0);
388 for (unsigned i = 0; i < numVars; ++i) {
389 if (cube.hasLiteral(i))
390 cubeTT &= createVarMask(numVars, i, !cube.isLiteralInverted(i));
391 }
392 tt |= cubeTT;
393 }
394 return tt;
395}
396
397#ifndef NDEBUG
398void SOPForm::dump(llvm::raw_ostream &os) const {
399 os << "SOPForm: " << numVars << " vars, " << cubes.size() << " cubes\n";
400 for (const auto &cube : cubes) {
401 os << " (";
402 for (unsigned i = 0; i < numVars; ++i) {
403 if (cube.mask & (1ULL << i)) {
404 os << ((cube.inverted & (1ULL << i)) ? "!" : "");
405 os << "x" << i << " ";
406 }
407 }
408 os << ")\n";
409 }
410}
411#endif
412//===----------------------------------------------------------------------===//
413// ISOP Extraction
414//===----------------------------------------------------------------------===//
415
416namespace {
417
418/// Minato-Morreale ISOP algorithm.
419///
420/// Computes an Irredundant Sum-of-Products (ISOP) cover for a Boolean function.
421///
422/// References:
423/// - Minato, "Fast Generation of Irredundant Sum-of-Products Forms from Binary
424/// Decision Diagrams", SASIMI 1992.
425/// - Morreale, "Recursive Operators for Prime Implicant and Irredundant Normal
426/// Form Determination", IEEE TC 1970.
427///
428/// Implementation inspired by lsils/kitty library:
429/// https://github.com/lsils/kitty/blob/master/include/kitty/isop.hpp
430/// distributed under MIT License.
431///
432/// The algorithm recursively decomposes the function using Shannon expansion:
433/// f = !x * f|x=0 + x * f|x=1
434///
435/// For ISOP, we partition minterms into three groups:
436/// - Minterms only in negative cofactor: must use !x literal
437/// - Minterms only in positive cofactor: must use x literal
438/// - Minterms in both cofactors: can omit x from the cube
439///
440/// Parameters:
441/// tt: The ON-set (minterms that must be covered)
442/// dc: The don't-care set (minterms that can optionally be covered)
443/// Invariant: tt is a subset of dc (ON-set is subset of care set)
444/// numVars: Total number of variables in the function
445/// varIndex: Current variable index to start search from (counts down)
446/// result: Output SOP form (cubes are accumulated here)
447///
448/// Returns: The actual cover computed (subset of dc that covers tt)
449///
450/// The maximum recursion depth is equal to the number of variables (one level
451/// per variable). For typical use cases with TruthTable (up to 6-8 variables),
452/// this is not a concern. Since truth tables require 2^numVars bits, the
453/// recursion depth is not a limiting factor.
454APInt isopImpl(const APInt &tt, const APInt &dc, unsigned numVars,
455 unsigned varIndex, SOPForm &result) {
456 assert((tt & ~dc).isZero() && "tt must be subset of dc");
457
458 // Base case: nothing to cover
459 if (tt.isZero())
460 return tt;
461
462 // Base case: function is tautology (all don't-cares)
463 if (dc.isAllOnes()) {
464 result.cubes.emplace_back();
465 return dc;
466 }
467
468 // Find highest variable in support (top-down from varIndex).
469 // NOTE: It is well known that the order of variable selection largely
470 // affects the size of the resulting ISOP. There are numerous studies on
471 // implementing heuristics for variable selection that could improve results,
472 // albeit at the cost of runtime. We may consider implementing such
473 // heuristics in the future.
474 int var = -1;
475 APInt negCof, posCof, negDC, posDC;
476 for (int i = varIndex - 1; i >= 0; --i) {
477 std::tie(negCof, posCof) = computeCofactors(tt, numVars, i);
478 std::tie(negDC, posDC) = computeCofactors(dc, numVars, i);
479 if (negCof != posCof || negDC != posDC) {
480 var = i;
481 break;
482 }
483 }
484 assert(var >= 0 && "No variable found in support");
485
486 // Recurse on minterms unique to negative cofactor (will get !var literal)
487 size_t negBegin = result.cubes.size();
488 APInt negCover = isopImpl(negCof & ~posDC, negDC, numVars, var, result);
489 size_t negEnd = result.cubes.size();
490
491 // Recurse on minterms unique to positive cofactor (will get var literal)
492 APInt posCover = isopImpl(posCof & ~negDC, posDC, numVars, var, result);
493 size_t posEnd = result.cubes.size();
494
495 // Recurse on shared minterms (no literal for this variable)
496 APInt remaining = (negCof & ~negCover) | (posCof & ~posCover);
497 APInt sharedCover = isopImpl(remaining, negDC & posDC, numVars, var, result);
498
499 // Compute total cover by restricting each sub-cover to its domain
500 APInt negMask = createVarMaskImpl<false>(numVars, var);
501 APInt totalCover = sharedCover | (negCover & negMask) | (posCover & ~negMask);
502
503 // Add literals to cubes from recursions
504 for (size_t i = negBegin; i < negEnd; ++i)
505 result.cubes[i].setLiteral(var, true);
506
507 for (size_t i = negEnd; i < posEnd; ++i)
508 result.cubes[i].setLiteral(var, false);
509
510 return totalCover;
511}
512
513} // namespace
514
515SOPForm circt::extractISOP(const APInt &truthTable, unsigned numVars) {
516 assert((1u << numVars) == truthTable.getBitWidth() &&
517 "Truth table size must match 2^numVars");
518 SOPForm sop(numVars);
519
520 if (numVars == 0 || truthTable.isZero())
521 return sop;
522
523 (void)isopImpl(truthTable, truthTable, numVars, numVars, sop);
524
525 return sop;
526}
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).
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.
Represents a boolean function as a truth table.
Definition TruthTable.h:39
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:42
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:40
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:41
Represents the canonical form of a boolean function under NPN equivalence.
Definition TruthTable.h:104
unsigned outputNegation
Output negation mask.
Definition TruthTable.h:108
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:105
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:147
llvm::SmallVector< unsigned > inputPermutation
Input permutation applied.
Definition TruthTable.h:106
unsigned inputNegation
Input negation mask.
Definition TruthTable.h:107
bool isLexicographicallySmaller(const NPNClass &other) const
Definition TruthTable.h:153
Represents a sum-of-products expression.
Definition TruthTable.h:250
unsigned numVars
Definition TruthTable.h:252
void dump(llvm::raw_ostream &os=llvm::errs()) const
Debug dump method for SOP forms.
llvm::SmallVector< Cube > cubes
Definition TruthTable.h:251
llvm::APInt computeTruthTable() const
Compute the truth table represented by this SOP form.