diff --git a/include/circt/Dialect/Comb/Passes.td b/include/circt/Dialect/Comb/Passes.td index 7f5920aeb068..6bcc3de8752e 100644 --- a/include/circt/Dialect/Comb/Passes.td +++ b/include/circt/Dialect/Comb/Passes.td @@ -63,4 +63,15 @@ def AssumeTwoValued : Pass<"comb-assume-two-valued"> { }]; } + +def SimplifyTruthTable : Pass<"comb-simplify-tt"> { + let summary = "Simplify truth tables that depend on 1 or less inputs"; + let description = [{ + Simplifies truth tables that are constant or depend only on single input. + For truth tables that depend on no input, reduce them to hw.constant. For + truth tables that depend on single input, reduce to identity of input or + negation of input. + }]; +} + #endif // CIRCT_DIALECT_COMB_PASSES_TD diff --git a/lib/Dialect/Comb/Transforms/CMakeLists.txt b/lib/Dialect/Comb/Transforms/CMakeLists.txt index 01d6125994d8..4d00477d2464 100644 --- a/lib/Dialect/Comb/Transforms/CMakeLists.txt +++ b/lib/Dialect/Comb/Transforms/CMakeLists.txt @@ -4,6 +4,7 @@ add_circt_dialect_library(CIRCTCombTransforms IntRangeAnnotations.cpp BalanceMux.cpp AssumeTwoValued.cpp + SimplifyTruthTable.cpp DEPENDS CIRCTCombTransformsIncGen diff --git a/lib/Dialect/Comb/Transforms/SimplifyTruthTable.cpp b/lib/Dialect/Comb/Transforms/SimplifyTruthTable.cpp new file mode 100644 index 000000000000..d8d9152c8f04 --- /dev/null +++ b/lib/Dialect/Comb/Transforms/SimplifyTruthTable.cpp @@ -0,0 +1,146 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the SimplifyTruthTable pass, which simplifies truth +// tables that depend on one or fewer inputs. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/Comb/CombPasses.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Support/Naming.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/Transforms/WalkPatternRewriteDriver.h" +#include "llvm/Support/LogicalResult.h" + +using namespace circt; +using namespace comb; + +namespace circt { +namespace comb { +#define GEN_PASS_DEF_SIMPLIFYTRUTHTABLE +#include "circt/Dialect/Comb/Passes.h.inc" +} // namespace comb +} // namespace circt + +namespace { + +// Helper to check if operation is trivially recursive +static bool isOpTriviallyRecursive(Operation *op) { + return llvm::any_of(op->getOperands(), [op](auto operand) { + return operand.getDefiningOp() == op; + }); +} + +// Pattern to simplify truth tables +struct SimplifyTruthTable : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TruthTableOp op, + PatternRewriter &rewriter) const override { + if (isOpTriviallyRecursive(op)) + return failure(); + + const auto inputs = op.getInputs(); + const auto table = op.getLookupTable(); + size_t numInputs = inputs.size(); + size_t tableSize = table.size(); + + if (numInputs <= 1) + return failure(); + + // Check if all table entries are the same (constant output) + bool allSame = llvm::all_equal(table); + if (allSame) { + bool firstValue = cast(table[0]).getValue(); + auto constOp = + hw::ConstantOp::create(rewriter, op.getLoc(), APInt(1, firstValue)); + replaceOpAndCopyNamehint(rewriter, op, constOp); + return success(); + } + + // Detect if the truth table depends only on one of the inputs. + // For each input bit, we test whether flipping only that input bit changes + // the output value of the truth table at any point. + SmallVector dependsOn(numInputs, false); + int dependentInput = -1; + unsigned numDependencies = 0; + + for (size_t idx = 0; idx < tableSize; ++idx) { + bool currentValue = cast(table[idx]).getValue(); + + for (size_t bitPos = 0; bitPos < numInputs; ++bitPos) { + // Skip if we already know this input matters + if (dependsOn[bitPos]) + continue; + + // Calculate the index of the entry with the bit in question flipped + size_t bitPositionInTable = numInputs - 1 - bitPos; + size_t flippedIdx = idx ^ (1ull << bitPositionInTable); + bool flippedValue = cast(table[flippedIdx]).getValue(); + + // If flipping this bit changes the output, this input is a dependency + if (currentValue != flippedValue) { + dependsOn[bitPos] = true; + dependentInput = bitPos; + numDependencies++; + + // Exit early if we already found more than one dependency + if (numDependencies > 1) + break; + } + } + + // Exit early from outer loop if we found more than one dependency + if (numDependencies > 1) + break; + } + + // Only simplify if exactly one input dependency found + if (numDependencies != 1) + return failure(); + + // Determine if the truth table is identity or inverted by checking the + // output when the dependent input is 1 (all other inputs at 0) + size_t bitPositionInTable = numInputs - 1 - dependentInput; + size_t idxWhen1 = 1ull << bitPositionInTable; + bool isIdentity = cast(table[idxWhen1]).getValue(); + + // Replace with the input or a simpler truth table for negation + Value input = inputs[dependentInput]; + if (isIdentity) { + // Identity case: just replace with the input directly + replaceOpAndCopyNamehint(rewriter, op, input); + } else { + // Inverted case: replace with a single-input truth table for negation + // This avoids introducing comb.xor, which is useful for LUT mapping + replaceOpWithNewOpAndCopyNamehint( + rewriter, op, ValueRange{input}, + rewriter.getBoolArrayAttr({true, false})); + } + return success(); + } +}; + +class SimplifyTruthTablePass + : public impl::SimplifyTruthTableBase { +public: + using SimplifyTruthTableBase::SimplifyTruthTableBase; + void runOnOperation() override; +}; + +} // namespace + +void SimplifyTruthTablePass::runOnOperation() { + Operation *op = getOperation(); + MLIRContext *context = op->getContext(); + RewritePatternSet patterns(context); + patterns.add(context); + walkAndApplyPatterns(op, std::move(patterns)); +} diff --git a/test/Dialect/Comb/simplify-truth-table.mlir b/test/Dialect/Comb/simplify-truth-table.mlir new file mode 100644 index 000000000000..9604864ce2de --- /dev/null +++ b/test/Dialect/Comb/simplify-truth-table.mlir @@ -0,0 +1,100 @@ +// RUN: circt-opt %s --comb-simplify-tt | FileCheck %s + +// CHECK-LABEL: @truth_table_constant_true +hw.module @truth_table_constant_true(in %a: i1, in %b: i1, out out: i1) { + // Truth table that is always true (all ones) + // CHECK-NEXT: [[TRUE:%.+]] = hw.constant true + // CHECK-NEXT: hw.output [[TRUE]] + %0 = comb.truth_table %a, %b -> [true, true, true, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_constant_false +hw.module @truth_table_constant_false(in %a: i1, in %b: i1, out out: i1) { + // Truth table that is always false (all zeros) + // CHECK-NEXT: [[FALSE:%.+]] = hw.constant false + // CHECK-NEXT: hw.output [[FALSE]] + %0 = comb.truth_table %a, %b -> [false, false, false, false] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_identity +hw.module @truth_table_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) { + // Truth table that depends only on %a + // Pattern: [0,0,0,0,1,1,1,1] means output follows first input + // CHECK-NEXT: hw.output %a + %0 = comb.truth_table %a, %b, %c -> [false, false, false, false, true, true, true, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_inverted +hw.module @truth_table_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) { + // Truth table that depends only on %a (inverted) + // Pattern: [1,1,1,1,0,0,0,0] means output is NOT of first input + // Should simplify to single-input truth table for negation + // CHECK-NEXT: [[NOT:%.+]] = comb.truth_table %a -> [true, false] + // CHECK-NEXT: hw.output [[NOT]] + %0 = comb.truth_table %a, %b, %c -> [true, true, true, true, false, false, false, false] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_middle_input_identity +hw.module @truth_table_middle_input_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) { + // Truth table that depends only on %b (middle input, identity) + // Pattern: [0,0,1,1,0,0,1,1] means output follows second input + // CHECK-NEXT: hw.output %b + %0 = comb.truth_table %a, %b, %c -> [false, false, true, true, false, false, true, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_middle_input_inverted +hw.module @truth_table_middle_input_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) { + // Truth table that depends only on %b (middle input, inverted) + // Pattern: [1,1,0,0,1,1,0,0] means output is NOT of second input + // Should simplify to single-input truth table for negation + // CHECK-NEXT: [[NOT:%.+]] = comb.truth_table %b -> [true, false] + // CHECK-NEXT: hw.output [[NOT]] + %0 = comb.truth_table %a, %b, %c -> [true, true, false, false, true, true, false, false] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_last_input_identity +hw.module @truth_table_last_input_identity(in %a: i1, in %b: i1, in %c: i1, out out: i1) { + // Truth table that depends only on %c (last input, identity) + // Pattern: [0,1,0,1,0,1,0,1] means output follows third input + // CHECK-NEXT: hw.output %c + %0 = comb.truth_table %a, %b, %c -> [false, true, false, true, false, true, false, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_last_input_inverted +hw.module @truth_table_last_input_inverted(in %a: i1, in %b: i1, in %c: i1, out out: i1) { + // Truth table that depends only on %c (last input, inverted) + // Pattern: [1,0,1,0,1,0,1,0] means output is NOT of third input + // Should simplify to single-input truth table for negation + // CHECK-NEXT: [[NOT:%.+]] = comb.truth_table %c -> [true, false] + // CHECK-NEXT: hw.output [[NOT]] + %0 = comb.truth_table %a, %b, %c -> [true, false, true, false, true, false, true, false] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_two_input_non_foldable +hw.module @truth_table_two_input_non_foldable(in %a: i1, in %b: i1, out out: i1) { + // Truth table depends on both inputs, Should not be canonicalized + // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, false, false, true] + // CHECK-NEXT: hw.output %0 + %0 = comb.truth_table %a, %b -> [false, false, false, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @truth_table_with_extract_operations +hw.module @truth_table_with_extract_operations(in %c: i3, out out: i1) { + // Truth table depends only on first input (%2 = LSB of %c) + // CHECK: [[TMP:%.+]] = comb.extract %c from 0 + // CHECK: hw.output [[TMP]] + %0 = comb.extract %c from 2 : (i3) -> i1 + %1 = comb.extract %c from 1 : (i3) -> i1 + %2 = comb.extract %c from 0 : (i3) -> i1 + %3 = comb.truth_table %2, %0, %1 -> [false, false, false, false, true, true, true, true] + hw.output %3 : i1 +}