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