Skip to content

Commit 5ff85a1

Browse files
committed
[Comb] Add canonicalization for comb.truth_table
1 parent 68bd267 commit 5ff85a1

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed

include/circt/Dialect/Comb/Combinational.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ def TruthTableOp : CombOp<"truth_table", [Pure]> {
330330
}];
331331

332332
let hasVerifier = 1;
333+
let hasCanonicalizeMethod = 1;
333334
}
334335

335336
def ReverseOp : CombOp<"reverse", [

lib/Dialect/Comb/CombFolds.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3291,3 +3291,89 @@ LogicalResult ICmpOp::canonicalize(ICmpOp op, PatternRewriter &rewriter) {
32913291

32923292
return failure();
32933293
}
3294+
3295+
//===----------------------------------------------------------------------===//
3296+
// TruthTableOp
3297+
//===----------------------------------------------------------------------===//
3298+
3299+
// Canonicalize truth tables by folding to constant when output does not depend
3300+
// on any of the inputs or simplifying when the output depends on only a single
3301+
// input.
3302+
LogicalResult TruthTableOp::canonicalize(TruthTableOp op,
3303+
PatternRewriter &rewriter) {
3304+
if (isOpTriviallyRecursive(op))
3305+
return failure();
3306+
3307+
const auto inputs = op.getInputs();
3308+
const auto table = op.getLookupTable();
3309+
size_t numInputs = inputs.size();
3310+
size_t tableSize = table.size();
3311+
3312+
if (numInputs == 0 || tableSize == 0 || tableSize != (1ull << numInputs))
3313+
return failure();
3314+
3315+
// Check if the table can be folded to just a constant.
3316+
bool firstValue = cast<BoolAttr>(table[0]).getValue();
3317+
bool allSame = llvm::all_of(table, [firstValue](Attribute attr) {
3318+
return cast<BoolAttr>(attr).getValue() == firstValue;
3319+
});
3320+
if (allSame) {
3321+
auto constOp =
3322+
hw::ConstantOp::create(rewriter, op.getLoc(), APInt(1, firstValue));
3323+
replaceOpAndCopyNamehint(rewriter, op, constOp);
3324+
return success();
3325+
}
3326+
3327+
// Detect if the truth table depends only on one of the inputs.
3328+
// For each input bit, we test whether flipping only that input bit changes
3329+
// the output value of the truth table at any point. For this, iterate over
3330+
// all table entries and compute the index of the entry where just that input
3331+
// bit is inverted.
3332+
SmallVector<bool> dependsOn(numInputs, false);
3333+
int dependentInput = -1;
3334+
unsigned numDependencies = 0;
3335+
3336+
for (size_t idx = 0; idx < tableSize; ++idx) {
3337+
bool currentValue = cast<BoolAttr>(table[idx]).getValue();
3338+
3339+
for (size_t bitPos = 0; bitPos < numInputs; ++bitPos) {
3340+
// Skip if we already know this input matters.
3341+
if (dependsOn[bitPos])
3342+
continue;
3343+
3344+
// Calculate the index of the entry with the bit in question flipped.
3345+
size_t bitPositionInTable = numInputs - 1 - bitPos;
3346+
size_t flippedIdx = idx ^ (1ull << bitPositionInTable);
3347+
bool flippedValue = cast<BoolAttr>(table[flippedIdx]).getValue();
3348+
3349+
// If flipping this bit changes the output, this input is a dependency.
3350+
if (currentValue != flippedValue) {
3351+
dependsOn[bitPos] = true;
3352+
dependentInput = bitPos;
3353+
numDependencies++;
3354+
3355+
// Exit early if we already found more than one dependency.
3356+
if (numDependencies > 1)
3357+
break;
3358+
}
3359+
}
3360+
3361+
// Exit early from outer loop if we found more than one dependency.
3362+
if (numDependencies > 1)
3363+
break;
3364+
}
3365+
3366+
if (numDependencies != 1)
3367+
return failure();
3368+
3369+
// Determine if the truth table is identity or inverted by checking the output
3370+
// when the dependent input is 1 (all other inputs at 0).
3371+
size_t idxWhen1 = 1ull << (numInputs - 1 - dependentInput);
3372+
bool isIdentity = cast<BoolAttr>(table[idxWhen1]).getValue();
3373+
3374+
// Replace with the input or its negation.
3375+
Value input = inputs[dependentInput];
3376+
Value replacement = isIdentity ? input : createOrFoldNot(op.getLoc(), input, rewriter);
3377+
replaceOpAndCopyNamehint(rewriter, op, replacement);
3378+
return success();
3379+
}

test/Dialect/Comb/canonicalization.mlir

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,3 +1960,103 @@ hw.module @issue9403(in %sel: i1, out out1: ui1) {
19601960
%mux1 = comb.mux %sel, %true, %false : ui1
19611961
hw.output %mux1 : ui1
19621962
}
1963+
1964+
1965+
// CHECK-LABEL: @truth_table_constant_true
1966+
hw.module @truth_table_constant_true(in %a: i1, in %b: i1, out out: i1) {
1967+
// Truth table that is always true (all ones)
1968+
// CHECK-NEXT: [[TRUE:%.+]] = hw.constant true
1969+
// CHECK-NEXT: hw.output [[TRUE]]
1970+
%0 = comb.truth_table %a, %b -> [true, true, true, true]
1971+
hw.output %0 : i1
1972+
}
1973+
1974+
// CHECK-LABEL: @truth_table_constant_false
1975+
hw.module @truth_table_constant_false(in %a: i1, in %b: i1, out out: i1) {
1976+
// Truth table that is always false (all zeros)
1977+
// CHECK-NEXT: [[FALSE:%.+]] = hw.constant false
1978+
// CHECK-NEXT: hw.output [[FALSE]]
1979+
%0 = comb.truth_table %a, %b -> [false, false, false, false]
1980+
hw.output %0 : i1
1981+
}
1982+
1983+
// CHECK-LABEL: @truth_table_identity
1984+
hw.module @truth_table_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
1985+
// Truth table that depends only on %a
1986+
// Pattern: [0,0,0,0,1,1,1,1] means output follows first input
1987+
// CHECK-NEXT: hw.output %a
1988+
%0 = comb.truth_table %a, %b, %c -> [false, false, false, false, true, true, true, true]
1989+
hw.output %0 : i1
1990+
}
1991+
1992+
// CHECK-LABEL: @truth_table_inverted
1993+
hw.module @truth_table_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
1994+
// Truth table that depends only on %a (inverted)
1995+
// Pattern: [1,1,1,1,0,0,0,0] means output is NOT of first input
1996+
// CHECK-NEXT: %true = hw.constant true
1997+
// CHECK-NEXT: [[NOT:%.+]] = comb.xor %a, %true
1998+
// CHECK-NEXT: hw.output [[NOT]]
1999+
%0 = comb.truth_table %a, %b, %c -> [true, true, true, true, false, false, false, false]
2000+
hw.output %0 : i1
2001+
}
2002+
2003+
// CHECK-LABEL: @truth_table_middle_input_identity
2004+
hw.module @truth_table_middle_input_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
2005+
// Truth table that depends only on %b (middle input, identity)
2006+
// Pattern: [0,0,1,1,0,0,1,1] means output follows second input
2007+
// CHECK-NEXT: hw.output %b
2008+
%0 = comb.truth_table %a, %b, %c -> [false, false, true, true, false, false, true, true]
2009+
hw.output %0 : i1
2010+
}
2011+
2012+
// CHECK-LABEL: @truth_table_middle_input_inverted
2013+
hw.module @truth_table_middle_input_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
2014+
// Truth table that depends only on %b (middle input, inverted)
2015+
// Pattern: [1,1,0,0,1,1,0,0] means output is NOT of second input
2016+
// CHECK-NEXT: %true = hw.constant true
2017+
// CHECK-NEXT: [[NOT:%.+]] = comb.xor %b, %true
2018+
// CHECK-NEXT: hw.output [[NOT]]
2019+
%0 = comb.truth_table %a, %b, %c -> [true, true, false, false, true, true, false, false]
2020+
hw.output %0 : i1
2021+
}
2022+
2023+
// CHECK-LABEL: @truth_table_last_input_identity
2024+
hw.module @truth_table_last_input_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
2025+
// Truth table that depends only on %c (last input, identity)
2026+
// Pattern: [0,1,0,1,0,1,0,1] means output follows third input
2027+
// CHECK-NEXT: hw.output %c
2028+
%0 = comb.truth_table %a, %b, %c -> [false, true, false, true, false, true, false, true]
2029+
hw.output %0 : i1
2030+
}
2031+
2032+
// CHECK-LABEL: @truth_table_last_input_inverted
2033+
hw.module @truth_table_last_input_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
2034+
// Truth table that depends only on %c (last input, inverted)
2035+
// Pattern: [1,0,1,0,1,0,1,0] means output is NOT of third input
2036+
// CHECK-NEXT: %true = hw.constant true
2037+
// CHECK-NEXT: [[NOT:%.+]] = comb.xor %c, %true
2038+
// CHECK-NEXT: hw.output [[NOT]]
2039+
%0 = comb.truth_table %a, %b, %c -> [true, false, true, false, true, false, true, false]
2040+
hw.output %0 : i1
2041+
}
2042+
2043+
// CHECK-LABEL: @truth_table_two_input_non_foldable
2044+
hw.module @truth_table_two_input_non_foldable(in %a: i1, in %b: i1, out out: i1) {
2045+
// Truth table depends on both inputs, Should not be canonicalized
2046+
// CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, false, false, true]
2047+
// CHECK-NEXT: hw.output %0
2048+
%0 = comb.truth_table %a, %b -> [false, false, false, true]
2049+
hw.output %0 : i1
2050+
}
2051+
2052+
// CHECK-LABEL: @truth_table_with_extract_operations
2053+
hw.module @truth_table_with_extract_operations(in %c: i3, out out: i1) {
2054+
// Truth table depends only on first input (%2 = LSB of %c)
2055+
// CHECK: [[TMP:%.+]] = comb.extract %c from 0
2056+
// CHECK: hw.output [[TMP]]
2057+
%0 = comb.extract %c from 2 : (i3) -> i1
2058+
%1 = comb.extract %c from 1 : (i3) -> i1
2059+
%2 = comb.extract %c from 0 : (i3) -> i1
2060+
%3 = comb.truth_table %2, %0, %1 -> [false, false, false, false, true, true, true, true]
2061+
hw.output %3 : i1
2062+
}

0 commit comments

Comments
 (0)