Skip to content

Commit f3d2d67

Browse files
committed
[Comb] Added Truth Table Simplification Pass
1 parent 33cc4ec commit f3d2d67

File tree

4 files changed

+245
-0
lines changed

4 files changed

+245
-0
lines changed

include/circt/Dialect/Comb/Passes.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,15 @@ def AssumeTwoValued : Pass<"comb-assume-two-valued"> {
6363
}];
6464
}
6565

66+
67+
def SimplifyTruthTable : Pass<"comb-simplify-tt"> {
68+
let summary = "Simplify truth tables that depend on 1 or less inputs";
69+
let description = [{
70+
Simplifies truth tables that are constant or depend only on single input.
71+
For truth tables that depend on no input, reduce them to hw.constant. For
72+
truth tables that depend on single input, reduce to identity of input or
73+
negation of input.
74+
}];
75+
}
76+
6677
#endif // CIRCT_DIALECT_COMB_PASSES_TD

lib/Dialect/Comb/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_circt_dialect_library(CIRCTCombTransforms
44
IntRangeAnnotations.cpp
55
BalanceMux.cpp
66
AssumeTwoValued.cpp
7+
SimplifyTruthTable.cpp
78

89
DEPENDS
910
CIRCTCombTransformsIncGen
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#include "circt/Dialect/Comb/CombOps.h"
2+
#include "circt/Dialect/Comb/CombPasses.h"
3+
#include "circt/Dialect/HW/HWOps.h"
4+
#include "circt/Support/Naming.h"
5+
#include "mlir/IR/PatternMatch.h"
6+
#include "mlir/Transforms/WalkPatternRewriteDriver.h"
7+
#include "llvm/Support/LogicalResult.h"
8+
9+
using namespace circt;
10+
using namespace comb;
11+
12+
namespace circt {
13+
namespace comb {
14+
#define GEN_PASS_DEF_SIMPLIFYTRUTHTABLE
15+
#include "circt/Dialect/Comb/Passes.h.inc"
16+
} // namespace comb
17+
} // namespace circt
18+
19+
namespace {
20+
21+
// Helper to check if operation is trivially recursive
22+
static bool isOpTriviallyRecursive(Operation *op) {
23+
return llvm::any_of(op->getOperands(), [op](auto operand) {
24+
return operand.getDefiningOp() == op;
25+
});
26+
}
27+
28+
// Pattern to simplify truth tables
29+
struct SimplifyTruthTable : public OpRewritePattern<TruthTableOp> {
30+
using OpRewritePattern::OpRewritePattern;
31+
32+
LogicalResult matchAndRewrite(TruthTableOp op,
33+
PatternRewriter &rewriter) const override {
34+
if (isOpTriviallyRecursive(op))
35+
return failure();
36+
37+
const auto inputs = op.getInputs();
38+
const auto table = op.getLookupTable();
39+
size_t numInputs = inputs.size();
40+
size_t tableSize = table.size();
41+
42+
if (numInputs <= 1)
43+
return failure();
44+
45+
// Check if all table entries are the same (constant output)
46+
bool allSame = llvm::all_equal(table);
47+
if (allSame) {
48+
bool firstValue = cast<BoolAttr>(table[0]).getValue();
49+
auto constOp =
50+
hw::ConstantOp::create(rewriter, op.getLoc(), APInt(1, firstValue));
51+
replaceOpAndCopyNamehint(rewriter, op, constOp);
52+
return success();
53+
}
54+
55+
// Detect if the truth table depends only on one of the inputs.
56+
// For each input bit, we test whether flipping only that input bit changes
57+
// the output value of the truth table at any point.
58+
SmallVector<bool> dependsOn(numInputs, false);
59+
int dependentInput = -1;
60+
unsigned numDependencies = 0;
61+
62+
for (size_t idx = 0; idx < tableSize; ++idx) {
63+
bool currentValue = cast<BoolAttr>(table[idx]).getValue();
64+
65+
for (size_t bitPos = 0; bitPos < numInputs; ++bitPos) {
66+
// Skip if we already know this input matters
67+
if (dependsOn[bitPos])
68+
continue;
69+
70+
// Calculate the index of the entry with the bit in question flipped
71+
size_t bitPositionInTable = numInputs - 1 - bitPos;
72+
size_t flippedIdx = idx ^ (1ull << bitPositionInTable);
73+
bool flippedValue = cast<BoolAttr>(table[flippedIdx]).getValue();
74+
75+
// If flipping this bit changes the output, this input is a dependency
76+
if (currentValue != flippedValue) {
77+
dependsOn[bitPos] = true;
78+
dependentInput = bitPos;
79+
numDependencies++;
80+
81+
// Exit early if we already found more than one dependency
82+
if (numDependencies > 1)
83+
break;
84+
}
85+
}
86+
87+
// Exit early from outer loop if we found more than one dependency
88+
if (numDependencies > 1)
89+
break;
90+
}
91+
92+
// Only simplify if exactly one input dependency found
93+
if (numDependencies != 1)
94+
return failure();
95+
96+
// Determine if the truth table is identity or inverted by checking the
97+
// output when the dependent input is 1 (all other inputs at 0)
98+
size_t bitPositionInTable = numInputs - 1 - dependentInput;
99+
size_t idxWhen1 = 1ull << bitPositionInTable;
100+
bool isIdentity = cast<BoolAttr>(table[idxWhen1]).getValue();
101+
102+
// Replace with the input or a simpler truth table for negation
103+
Value input = inputs[dependentInput];
104+
if (isIdentity) {
105+
// Identity case: just replace with the input directly
106+
replaceOpAndCopyNamehint(rewriter, op, input);
107+
} else {
108+
// Inverted case: replace with a single-input truth table for negation
109+
// This avoids introducing comb.xor, which is useful for LUT mapping
110+
replaceOpWithNewOpAndCopyNamehint<TruthTableOp>(
111+
rewriter, op, ValueRange{input},
112+
rewriter.getBoolArrayAttr({true, false}));
113+
}
114+
return success();
115+
}
116+
};
117+
118+
class SimplifyTruthTablePass
119+
: public impl::SimplifyTruthTableBase<SimplifyTruthTablePass> {
120+
public:
121+
using SimplifyTruthTableBase::SimplifyTruthTableBase;
122+
void runOnOperation() override;
123+
};
124+
125+
} // namespace
126+
127+
void SimplifyTruthTablePass::runOnOperation() {
128+
Operation *op = getOperation();
129+
MLIRContext *context = op->getContext();
130+
RewritePatternSet patterns(context);
131+
patterns.add<SimplifyTruthTable>(context);
132+
walkAndApplyPatterns(op, std::move(patterns));
133+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// RUN: circt-opt %s --comb-simplify-tt | FileCheck %s
2+
3+
// CHECK-LABEL: @truth_table_constant_true
4+
hw.module @truth_table_constant_true(in %a: i1, in %b: i1, out out: i1) {
5+
// Truth table that is always true (all ones)
6+
// CHECK-NEXT: [[TRUE:%.+]] = hw.constant true
7+
// CHECK-NEXT: hw.output [[TRUE]]
8+
%0 = comb.truth_table %a, %b -> [true, true, true, true]
9+
hw.output %0 : i1
10+
}
11+
12+
// CHECK-LABEL: @truth_table_constant_false
13+
hw.module @truth_table_constant_false(in %a: i1, in %b: i1, out out: i1) {
14+
// Truth table that is always false (all zeros)
15+
// CHECK-NEXT: [[FALSE:%.+]] = hw.constant false
16+
// CHECK-NEXT: hw.output [[FALSE]]
17+
%0 = comb.truth_table %a, %b -> [false, false, false, false]
18+
hw.output %0 : i1
19+
}
20+
21+
// CHECK-LABEL: @truth_table_identity
22+
hw.module @truth_table_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
23+
// Truth table that depends only on %a
24+
// Pattern: [0,0,0,0,1,1,1,1] means output follows first input
25+
// CHECK-NEXT: hw.output %a
26+
%0 = comb.truth_table %a, %b, %c -> [false, false, false, false, true, true, true, true]
27+
hw.output %0 : i1
28+
}
29+
30+
// CHECK-LABEL: @truth_table_inverted
31+
hw.module @truth_table_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
32+
// Truth table that depends only on %a (inverted)
33+
// Pattern: [1,1,1,1,0,0,0,0] means output is NOT of first input
34+
// Should simplify to single-input truth table for negation
35+
// CHECK-NEXT: [[NOT:%.+]] = comb.truth_table %a -> [true, false]
36+
// CHECK-NEXT: hw.output [[NOT]]
37+
%0 = comb.truth_table %a, %b, %c -> [true, true, true, true, false, false, false, false]
38+
hw.output %0 : i1
39+
}
40+
41+
// CHECK-LABEL: @truth_table_middle_input_identity
42+
hw.module @truth_table_middle_input_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
43+
// Truth table that depends only on %b (middle input, identity)
44+
// Pattern: [0,0,1,1,0,0,1,1] means output follows second input
45+
// CHECK-NEXT: hw.output %b
46+
%0 = comb.truth_table %a, %b, %c -> [false, false, true, true, false, false, true, true]
47+
hw.output %0 : i1
48+
}
49+
50+
// CHECK-LABEL: @truth_table_middle_input_inverted
51+
hw.module @truth_table_middle_input_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
52+
// Truth table that depends only on %b (middle input, inverted)
53+
// Pattern: [1,1,0,0,1,1,0,0] means output is NOT of second input
54+
// Should simplify to single-input truth table for negation
55+
// CHECK-NEXT: [[NOT:%.+]] = comb.truth_table %b -> [true, false]
56+
// CHECK-NEXT: hw.output [[NOT]]
57+
%0 = comb.truth_table %a, %b, %c -> [true, true, false, false, true, true, false, false]
58+
hw.output %0 : i1
59+
}
60+
61+
// CHECK-LABEL: @truth_table_last_input_identity
62+
hw.module @truth_table_last_input_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
63+
// Truth table that depends only on %c (last input, identity)
64+
// Pattern: [0,1,0,1,0,1,0,1] means output follows third input
65+
// CHECK-NEXT: hw.output %c
66+
%0 = comb.truth_table %a, %b, %c -> [false, true, false, true, false, true, false, true]
67+
hw.output %0 : i1
68+
}
69+
70+
// CHECK-LABEL: @truth_table_last_input_inverted
71+
hw.module @truth_table_last_input_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) {
72+
// Truth table that depends only on %c (last input, inverted)
73+
// Pattern: [1,0,1,0,1,0,1,0] means output is NOT of third input
74+
// Should simplify to single-input truth table for negation
75+
// CHECK-NEXT: [[NOT:%.+]] = comb.truth_table %c -> [true, false]
76+
// CHECK-NEXT: hw.output [[NOT]]
77+
%0 = comb.truth_table %a, %b, %c -> [true, false, true, false, true, false, true, false]
78+
hw.output %0 : i1
79+
}
80+
81+
// CHECK-LABEL: @truth_table_two_input_non_foldable
82+
hw.module @truth_table_two_input_non_foldable(in %a: i1, in %b: i1, out out: i1) {
83+
// Truth table depends on both inputs, Should not be canonicalized
84+
// CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, false, false, true]
85+
// CHECK-NEXT: hw.output %0
86+
%0 = comb.truth_table %a, %b -> [false, false, false, true]
87+
hw.output %0 : i1
88+
}
89+
90+
// CHECK-LABEL: @truth_table_with_extract_operations
91+
hw.module @truth_table_with_extract_operations(in %c: i3, out out: i1) {
92+
// Truth table depends only on first input (%2 = LSB of %c)
93+
// CHECK: [[TMP:%.+]] = comb.extract %c from 0
94+
// CHECK: hw.output [[TMP]]
95+
%0 = comb.extract %c from 2 : (i3) -> i1
96+
%1 = comb.extract %c from 1 : (i3) -> i1
97+
%2 = comb.extract %c from 0 : (i3) -> i1
98+
%3 = comb.truth_table %2, %0, %1 -> [false, false, false, false, true, true, true, true]
99+
hw.output %3 : i1
100+
}

0 commit comments

Comments
 (0)