From 1d1591345595893e3d17b58da0d1e2bd1c3bab77 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 17 Oct 2024 14:22:10 +0900 Subject: [PATCH 1/2] [AIG] Add an AIG dialect --- docs/Dialects/AIG.md | 33 ++ include/circt/Conversion/CombToAIG.h | 25 ++ include/circt/Conversion/Passes.h | 1 + include/circt/Conversion/Passes.td | 12 + include/circt/Dialect/AIG/AIG.td | 25 ++ include/circt/Dialect/AIG/AIGDialect.h | 22 ++ include/circt/Dialect/AIG/AIGOps.h | 31 ++ include/circt/Dialect/AIG/AIGOps.td | 120 +++++++ include/circt/Dialect/AIG/AIGPasses.h | 40 +++ include/circt/Dialect/AIG/AIGPasses.td | 41 +++ include/circt/Dialect/AIG/CMakeLists.txt | 9 + include/circt/Dialect/CMakeLists.txt | 1 + include/circt/InitAllDialects.h | 2 + include/circt/InitAllPasses.h | 2 + lib/CAPI/Conversion/CMakeLists.txt | 1 + lib/Conversion/CMakeLists.txt | 1 + lib/Conversion/CombToAIG/CMakeLists.txt | 20 ++ lib/Conversion/CombToAIG/CombToAIG.cpp | 117 +++++++ lib/Dialect/AIG/AIGDialect.cpp | 35 +++ lib/Dialect/AIG/AIGOps.cpp | 192 ++++++++++++ lib/Dialect/AIG/CMakeLists.txt | 17 + lib/Dialect/AIG/Transforms/CMakeLists.txt | 14 + .../AIG/Transforms/GreedyCutDecomp.cpp | 292 ++++++++++++++++++ lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp | 159 ++++++++++ lib/Dialect/AIG/Transforms/LowerVariadic.cpp | 112 +++++++ .../AIG/Transforms/LowerWordToBits.cpp | 91 ++++++ lib/Dialect/CMakeLists.txt | 1 + test/CMakeLists.txt | 1 + test/Conversion/CombToAIG/comb-to-aig.mlir | 9 + test/Dialect/AIG/canonicalizer.mlir | 42 +++ test/Dialect/AIG/greedy-decomp.mlir | 26 ++ test/Dialect/AIG/lower-cut-to-lut.mlir | 15 + test/Dialect/AIG/lower-variadic.mlir | 11 + test/Dialect/AIG/lower-word-to-bits.mlir | 14 + test/Dialect/AIG/round-trip.mlir | 21 ++ test/circt-synth/basic.mlir | 46 +++ test/circt-synth/commandline.mlir | 3 + test/circt-synth/lut-size.mlir | 24 ++ test/lit.cfg.py | 4 +- tools/CMakeLists.txt | 1 + tools/circt-synth/CMakeLists.txt | 16 + tools/circt-synth/circt-synth.cpp | 219 +++++++++++++ 42 files changed, 1866 insertions(+), 2 deletions(-) create mode 100644 docs/Dialects/AIG.md create mode 100644 include/circt/Conversion/CombToAIG.h create mode 100644 include/circt/Dialect/AIG/AIG.td create mode 100644 include/circt/Dialect/AIG/AIGDialect.h create mode 100644 include/circt/Dialect/AIG/AIGOps.h create mode 100644 include/circt/Dialect/AIG/AIGOps.td create mode 100644 include/circt/Dialect/AIG/AIGPasses.h create mode 100644 include/circt/Dialect/AIG/AIGPasses.td create mode 100644 include/circt/Dialect/AIG/CMakeLists.txt create mode 100644 lib/Conversion/CombToAIG/CMakeLists.txt create mode 100644 lib/Conversion/CombToAIG/CombToAIG.cpp create mode 100644 lib/Dialect/AIG/AIGDialect.cpp create mode 100644 lib/Dialect/AIG/AIGOps.cpp create mode 100644 lib/Dialect/AIG/CMakeLists.txt create mode 100644 lib/Dialect/AIG/Transforms/CMakeLists.txt create mode 100644 lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp create mode 100644 lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp create mode 100644 lib/Dialect/AIG/Transforms/LowerVariadic.cpp create mode 100644 lib/Dialect/AIG/Transforms/LowerWordToBits.cpp create mode 100644 test/Conversion/CombToAIG/comb-to-aig.mlir create mode 100644 test/Dialect/AIG/canonicalizer.mlir create mode 100644 test/Dialect/AIG/greedy-decomp.mlir create mode 100644 test/Dialect/AIG/lower-cut-to-lut.mlir create mode 100644 test/Dialect/AIG/lower-variadic.mlir create mode 100644 test/Dialect/AIG/lower-word-to-bits.mlir create mode 100644 test/Dialect/AIG/round-trip.mlir create mode 100644 test/circt-synth/basic.mlir create mode 100644 test/circt-synth/commandline.mlir create mode 100644 test/circt-synth/lut-size.mlir create mode 100644 tools/circt-synth/CMakeLists.txt create mode 100644 tools/circt-synth/circt-synth.cpp diff --git a/docs/Dialects/AIG.md b/docs/Dialects/AIG.md new file mode 100644 index 000000000000..765197da1226 --- /dev/null +++ b/docs/Dialects/AIG.md @@ -0,0 +1,33 @@ +# 'aig' Dialect + +This document outlines the rationale of the AIG dialect, a dialect used for representing and transforming And-Inverter Graphs. + +[TOC] + +## Rationale + +# Why use the AIG dialect instead of the `comb` dialect? + +And-Inverter Graphs have proven to be a scalable approach for logic synthesis, serving as the underlying data structure for ABC, one of the most performant open-source logic synthesis tools. + +While it's technically possible to represent `aig.and_inv` using a combination of `comb.and`, `comb.xor`, and `hw.constant`, the ability to represent everything with `aig.and_inv` offers significant advantages. This unified representation simplifies complex analyses such as path retiming and area analysis, as well as logic mappings. Moreover, it allows for direct application of existing AIG research results and tools, further enhancing its utility in the synthesis process. + +# Operations +## aig.and_inv + +The `aig.and_inv` operation directly represents an And-Node in the AIG. Traditionally, an And-Node in AIG has two operands. However, `aig.and_inv` extends this concept by allowing variadic operands and non-i1 integer types. Although the final stage of the pipeline requires lowering everything to i1-binary operands, it's more efficient to progressively lower the variadic multibit operations. + +Variadic operands have demonstrated their utility in low-level optimizations within the `comb` dialect. Furthermore, in synthesis, it's common practice to re-balance the logic path. Variadic operands enable the compiler to select more efficient solutions without the need to traverse binary trees multiple times. + +The ability to represent multibit operations during synthesis is crucial for scalable logic optimization. This approach enables a form of vectorization, allowing for batch processing of logic synthesis when multibit operations are constructed in a similar manner. Such vectorization can significantly improve the efficiency and performance of the synthesis process. + +## aig.cut + +The `aig.cut` operation represents a "cut" in the logic tree. This operation possesses the `IsolatedAbove` trait and contains a single block. Its input operands represent the input edges, while the returned value represents the output edges. + +This operation proves particularly useful for progressive LUT mapping. For instance, a k-input cut can be readily mapped to a k-input LUT. Consequently, the subsequent stages of the pipeline can concentrate on replacing combinational logic with k-input Cut operations, simplifying the overall process. + + +## Operations + +[include "Dialects/AIGOps.md"] diff --git a/include/circt/Conversion/CombToAIG.h b/include/circt/Conversion/CombToAIG.h new file mode 100644 index 000000000000..27d529a39dc2 --- /dev/null +++ b/include/circt/Conversion/CombToAIG.h @@ -0,0 +1,25 @@ +//===- CombToAIG.h - Comb to AIG dialect conversion ---------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_COMBTOAIG_H +#define CIRCT_CONVERSION_COMBTOAIG_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { +namespace hw { +class HWModuleOp; +} + +#define GEN_PASS_DECL_CONVERTCOMBTOAIG +#include "circt/Conversion/Passes.h.inc" + +} // namespace circt + +#endif // CIRCT_CONVERSION_COMBTOARITH_H diff --git a/include/circt/Conversion/Passes.h b/include/circt/Conversion/Passes.h index 9d6b2d2aacc2..887da277f8aa 100644 --- a/include/circt/Conversion/Passes.h +++ b/include/circt/Conversion/Passes.h @@ -19,6 +19,7 @@ #include "circt/Conversion/CalyxNative.h" #include "circt/Conversion/CalyxToFSM.h" #include "circt/Conversion/CalyxToHW.h" +#include "circt/Conversion/CombToAIG.h" #include "circt/Conversion/CombToArith.h" #include "circt/Conversion/CombToSMT.h" #include "circt/Conversion/ConvertToArcs.h" diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 791c998bfd32..e1d13bb191c6 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -790,4 +790,16 @@ def LowerSimToSV: Pass<"lower-sim-to-sv", "mlir::ModuleOp"> { ]; } +//===----------------------------------------------------------------------===// +// ConvertCombToAIG +//===----------------------------------------------------------------------===// + +def ConvertCombToAIG: Pass<"convert-comb-to-aig", "hw::HWModuleOp"> { + let summary = "Lower comb ops to aig ops."; + let dependentDialects = [ + "circt::comb::CombDialect", + "circt::aig::AIGDialect", + ]; +} + #endif // CIRCT_CONVERSION_PASSES_TD diff --git a/include/circt/Dialect/AIG/AIG.td b/include/circt/Dialect/AIG/AIG.td new file mode 100644 index 000000000000..62cb778b5691 --- /dev/null +++ b/include/circt/Dialect/AIG/AIG.td @@ -0,0 +1,25 @@ +//===- AIG.td - AIG Definitions ----------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_AIG_DIALECT_TD +#define CIRCT_AIG_DIALECT_TD + +include "mlir/IR/DialectBase.td" +include "mlir/IR/OpBase.td" + +def AIG_Dialect : Dialect { + let name = "aig"; + let cppNamespace = "::circt::aig"; + let summary = "Representation of AIGs"; + + let usePropertiesForAttributes = 0; +} + +include "circt/Dialect/AIG/AIGOps.td" + +#endif // CIRCT_AIG_DIALECT_TD diff --git a/include/circt/Dialect/AIG/AIGDialect.h b/include/circt/Dialect/AIG/AIGDialect.h new file mode 100644 index 000000000000..acd0f712ae7a --- /dev/null +++ b/include/circt/Dialect/AIG/AIGDialect.h @@ -0,0 +1,22 @@ +//===- AIGDialect.h - AIG Definitions --------------------------*- C++ -*-===// +// +// 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 defines the AIG CIRCT dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_AIG_AIGDIALECT_H +#define CIRCT_DIALECT_AIG_AIGDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/AIG/AIGDialect.h.inc" + +#endif // CIRCT_DIALECT_AIG_AIGDIALECT_H diff --git a/include/circt/Dialect/AIG/AIGOps.h b/include/circt/Dialect/AIG/AIGOps.h new file mode 100644 index 000000000000..ff36bd69919c --- /dev/null +++ b/include/circt/Dialect/AIG/AIGOps.h @@ -0,0 +1,31 @@ +//===- AIGOps.h - AIG Op Definitions ---------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_AIG_AIGOPS_H +#define CIRCT_DIALECT_AIG_AIGOPS_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Dialect.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/Operation.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#include "circt/Dialect/AIG/AIGDialect.h" + +#define GET_OP_CLASSES +#include "circt/Dialect/AIG/AIG.h.inc" + +#endif // CIRCT_DIALECT_AIG_AIGOPS_H diff --git a/include/circt/Dialect/AIG/AIGOps.td b/include/circt/Dialect/AIG/AIGOps.td new file mode 100644 index 000000000000..ca6e99da8f3d --- /dev/null +++ b/include/circt/Dialect/AIG/AIGOps.td @@ -0,0 +1,120 @@ +//===- AIGOps.td - AIG Op Definitions -------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// AIG Ops are defined in tablegen. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_AIG_OPS_TD +#define CIRCT_DIALECT_AIG_OPS_TD + +include "circt/Dialect/AIG/AIG.td" +include "mlir/IR/OpBase.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + + +class AIGOp traits = []> : + Op; + +def AndInverterOp : AIGOp<"and_inv", [SameOperandsAndResultType, Pure]> { + let summary = "AIG dialect AND operation"; + let description = [{ + The `aig.and_inv` operation represents an And-Inverter in the AIG dialect. + Unlike comb.and, operands can be inverted respectively. + + Example: + ```mlir + %r1 = aig.and_inv %a, %b: i3 + %r2 = aig.and_inv not %a, %b, not %c : i3 + %r3 = aig.and_inv not %a : i3 + ``` + }]; + // TODO: Restrict to HWIntegerType. + let arguments = (ins Variadic:$inputs, DenseBoolArrayAttr:$inverted); + let results = (outs AnyType:$result); + + // NOTE: Custom assembly format is needed to pretty print the `inverted` + // attribute. + let hasCustomAssemblyFormat = 1; + + let builders = [ + OpBuilder<(ins "Value":$input, CArg<"bool", "false">:$invert), [{ + SmallVector inverted {invert}; + return build($_builder, $_state, {input}, inverted); + }]>, + OpBuilder<(ins "Value":$lhs, "Value":$rhs, CArg<"bool", "false">:$invertLhs, + CArg<"bool", "false">:$invertRhs), [{ + SmallVector inverted {invertLhs, invertRhs}; + return build($_builder, $_state, {lhs, rhs}, inverted); + }]> + ]; + + let extraClassDeclaration = [{ + // Evaluate the operation with the given input values. + APInt evaluate(ArrayRef inputs); + + // Check if the input is inverted. + bool isInverted(size_t idx) { + return getInverted()[idx]; + } + }]; + let hasFolder = 1; + let hasCanonicalizeMethod = 1; +} + +def CutOp : AIGOp<"cut", [IsolatedFromAbove, SingleBlock]> { + let summary = "AIG dialect Cut operation"; + let description = [{ + The `aig.cut` operation represents a cut in the And-Inverter-Graph. + This operation is variadic and can take multiple inputs and outputs, + which corresponds to the input and output edges in AIG conceptually. + + ```mlir + %0, %1 = aig.cut %a, %b, %c, %d : (i1, i1, i1, i1) -> (i1, i1) { + ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1): + %0 = aig.and_inv not %arg0, %arg1 : i1 + %1 = aig.and_inv %arg1, %arg3 : i1 + aig.output %0, %1 : i1 + } + ``` + + }]; + let arguments = (ins Variadic:$inputs); + let results = (outs Variadic:$results); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ + $inputs attr-dict `:` functional-type($inputs, $results) $body + }]; + + let builders = [ + OpBuilder<(ins + CArg<"TypeRange", "{}">:$resultTypes, + CArg<"ValueRange", "{}">:$inputs, + CArg<"std::function", "{}">:$ctor)> + ]; + + let extraClassDeclaration = [{ + Block *getBodyBlock() { return &getBody().front(); } + }]; +} + +def OutputOp : AIGOp<"output", [Terminator, + ReturnLike, ParentOneOf<["CutOp"]>]> { + let summary = "AIG dialect Output operation"; + let description = [{ + The `aig.output` operation represents out edges of a cut. + }]; + let arguments = (ins Variadic:$outputs); + let assemblyFormat = [{ + attr-dict ($outputs^ `:` qualified(type($outputs)))? + }]; +} + +#endif // CIRCT_DIALECT_AIG_OPS_TD diff --git a/include/circt/Dialect/AIG/AIGPasses.h b/include/circt/Dialect/AIG/AIGPasses.h new file mode 100644 index 000000000000..4226bc35711b --- /dev/null +++ b/include/circt/Dialect/AIG/AIGPasses.h @@ -0,0 +1,40 @@ +//===- AIGPasses.h - AIG dialect passes -----------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_AIG_AIGPASSES_H +#define CIRCT_DIALECT_AIG_AIGPASSES_H + +#include "mlir/Pass/Pass.h" +#include +#include + +namespace mlir { +class Pass; +} // namespace mlir + +#include "circt/Dialect/AIG/AIGPassesEnums.h.inc" + +namespace circt { +namespace aig { + +#define GEN_PASS_DECL +#include "circt/Dialect/AIG/AIGPasses.h.inc" + +std::unique_ptr createLowerCutToLUTPass(); +std::unique_ptr createLowerVariadicPass(); +std::unique_ptr createLowerWordToBitsPass(); +std::unique_ptr +createGreedyCutDecompPass(const GreedyCutDecompOptions &options = {}); + +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/AIG/AIGPasses.h.inc" + +} // namespace aig +} // namespace circt + +#endif // CIRCT_DIALECT_AIG_AIGPASSES_H diff --git a/include/circt/Dialect/AIG/AIGPasses.td b/include/circt/Dialect/AIG/AIGPasses.td new file mode 100644 index 000000000000..8c570245c17f --- /dev/null +++ b/include/circt/Dialect/AIG/AIGPasses.td @@ -0,0 +1,41 @@ +//===- ArcPasses.td - Arc dialect passes -------------------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCPASSES_TD +#define CIRCT_DIALECT_ARC_ARCPASSES_TD + +include "mlir/Pass/PassBase.td" + +def LowerCutToLUT : Pass<"aig-lower-cut-to-lut", "hw::HWModuleOp"> { + let summary = "Lower a cut to a LUT"; + let dependentDialects = ["comb::CombDialect"]; + let constructor = "circt::aig::createLowerCutToLUTPass()"; +} + +def LowerVariadic : Pass<"aig-lower-variadic", "hw::HWModuleOp"> { + let summary = "Lower variadic AndInverter operations to binary AndInverter"; + let constructor = "circt::aig::createLowerVariadicPass()"; +} + +def LowerWordToBits : Pass<"aig-lower-word-to-bits", "hw::HWModuleOp"> { + let summary = "Lower multi-bit AIG operations to single-bit ones"; + let dependentDialects = ["comb::CombDialect"]; + let constructor = "circt::aig::createLowerWordToBitsPass()"; +} + +def GreedyCutDecomp : Pass<"aig-greedy-cut-decomp", "hw::HWModuleOp"> { + let summary = "Decompose AIGs into k-feasible Cuts using a greedy algorithm"; + let dependentDialects = ["comb::CombDialect"]; + let constructor = "circt::aig::createGreedyCutDecompPass()"; + let options = [ + Option<"cutSizes", "cut-sizes", "uint32_t", "6", + "The sizes of the cuts to decompose">, + ]; +} + +#endif // CIRCT_DIALECT_ARC_ARCPASSES_TD diff --git a/include/circt/Dialect/AIG/CMakeLists.txt b/include/circt/Dialect/AIG/CMakeLists.txt new file mode 100644 index 000000000000..3bd74131f1c2 --- /dev/null +++ b/include/circt/Dialect/AIG/CMakeLists.txt @@ -0,0 +1,9 @@ +add_circt_dialect(AIG aig) +add_circt_dialect_doc(AIG aig) + +set(LLVM_TARGET_DEFINITIONS AIGPasses.td) +mlir_tablegen(AIGPasses.h.inc -gen-pass-decls) +mlir_tablegen(AIGPassesEnums.h.inc -gen-enum-decls) +mlir_tablegen(AIGPassesEnums.cpp.inc -gen-enum-defs) +add_public_tablegen_target(CIRCTAIGTransformsIncGen) +add_circt_doc(AIGPasses AIGPasses -gen-pass-doc) diff --git a/include/circt/Dialect/CMakeLists.txt b/include/circt/Dialect/CMakeLists.txt index 96a7b1013375..798409471773 100644 --- a/include/circt/Dialect/CMakeLists.txt +++ b/include/circt/Dialect/CMakeLists.txt @@ -6,6 +6,7 @@ ## ##===----------------------------------------------------------------------===// +add_subdirectory(AIG) add_subdirectory(Arc) add_subdirectory(Calyx) add_subdirectory(Comb) diff --git a/include/circt/InitAllDialects.h b/include/circt/InitAllDialects.h index 763e63293918..3212a01c154d 100644 --- a/include/circt/InitAllDialects.h +++ b/include/circt/InitAllDialects.h @@ -14,6 +14,7 @@ #ifndef CIRCT_INITALLDIALECTS_H_ #define CIRCT_INITALLDIALECTS_H_ +#include "circt/Dialect/AIG/AIGDialect.h" #include "circt/Dialect/Arc/ArcDialect.h" #include "circt/Dialect/Calyx/CalyxDialect.h" #include "circt/Dialect/Comb/CombDialect.h" @@ -51,6 +52,7 @@ namespace circt { inline void registerAllDialects(mlir::DialectRegistry ®istry) { // clang-format off registry.insert< + aig::AIGDialect, arc::ArcDialect, calyx::CalyxDialect, chirrtl::CHIRRTLDialect, diff --git a/include/circt/InitAllPasses.h b/include/circt/InitAllPasses.h index 173e3adafbb6..36ae439f2970 100644 --- a/include/circt/InitAllPasses.h +++ b/include/circt/InitAllPasses.h @@ -16,6 +16,7 @@ #include "circt/Conversion/ExportVerilog.h" #include "circt/Conversion/Passes.h" +#include "circt/Dialect/AIG/AIGPasses.h" #include "circt/Dialect/Arc/ArcPasses.h" #include "circt/Dialect/Calyx/CalyxPasses.h" #include "circt/Dialect/Comb/CombPasses.h" @@ -57,6 +58,7 @@ inline void registerAllPasses() { registerBMCTransformsPasses(); // Standard Passes + aig::registerPasses(); arc::registerPasses(); calyx::registerPasses(); comb::registerPasses(); diff --git a/lib/CAPI/Conversion/CMakeLists.txt b/lib/CAPI/Conversion/CMakeLists.txt index 385ede3bef18..8f6176f027a6 100644 --- a/lib/CAPI/Conversion/CMakeLists.txt +++ b/lib/CAPI/Conversion/CMakeLists.txt @@ -7,6 +7,7 @@ add_circt_public_c_api_library(CIRCTCAPIConversion CIRCTCalyxToFSM CIRCTCalyxToHW CIRCTCalyxNative + CIRCTCombToAIG CIRCTCombToArith CIRCTCombToLLVM CIRCTCombToSMT diff --git a/lib/Conversion/CMakeLists.txt b/lib/Conversion/CMakeLists.txt index 04bc5bfa9d71..bcb890e5c900 100644 --- a/lib/Conversion/CMakeLists.txt +++ b/lib/Conversion/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(CalyxToHW) add_subdirectory(CombToArith) add_subdirectory(CombToLLVM) add_subdirectory(CombToSMT) +add_subdirectory(CombToAIG) add_subdirectory(ConvertToArcs) add_subdirectory(DCToHW) add_subdirectory(ExportChiselInterface) diff --git a/lib/Conversion/CombToAIG/CMakeLists.txt b/lib/Conversion/CombToAIG/CMakeLists.txt new file mode 100644 index 000000000000..38d6832bd55c --- /dev/null +++ b/lib/Conversion/CombToAIG/CMakeLists.txt @@ -0,0 +1,20 @@ +add_circt_conversion_library(CIRCTCombToAIG + CombToAIG.cpp + + ADDITIONAL_HEADER_DIRS + ${CIRCT_MAIN_INCLUDE_DIR}/circt/Conversion/CombToAIG + + DEPENDS + CIRCTConversionPassIncGen + + LINK_LIBS PUBLIC + CIRCTHW + CIRCTComb + CIRCTAIG + CIRCTSupport + CIRCTTransforms + MLIRIR + MLIRPass + MLIRSupport + MLIRTransforms +) diff --git a/lib/Conversion/CombToAIG/CombToAIG.cpp b/lib/Conversion/CombToAIG/CombToAIG.cpp new file mode 100644 index 000000000000..6d95c831d544 --- /dev/null +++ b/lib/Conversion/CombToAIG/CombToAIG.cpp @@ -0,0 +1,117 @@ +//===- CombToAIG.cpp - Comb to AIG Conversion Pass ------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/CombToAIG.h" +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +namespace circt { +#define GEN_PASS_DEF_CONVERTCOMBTOAIG +#include "circt/Conversion/Passes.h.inc" +} // namespace circt + +using namespace circt; +using namespace comb; + +//===----------------------------------------------------------------------===// +// Conversion patterns +//===----------------------------------------------------------------------===// + +namespace { + +/// Lower a comb::AndOp operation to aig::AndInverterOp +struct CombAndOpConversion : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(AndOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector nonInverts(adaptor.getInputs().size(), false); + rewriter.replaceOpWithNewOp(op, adaptor.getInputs(), + nonInverts); + return success(); + } +}; + +/// Lower a comb::OrOp operation to aig::AndInverterOp with invert flags +struct CombOrOpConversion : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(OrOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Implement Or using And and invert flags: a | b = ~(~a & ~b) + SmallVector allInverts(adaptor.getInputs().size(), true); + auto andOp = rewriter.create( + op.getLoc(), adaptor.getInputs(), allInverts); + rewriter.replaceOpWithNewOp(op, andOp, + /*invert=*/true); + return success(); + } +}; + +/// Lower a comb::XorOp operation to AIG operations +struct CombXorOpConversion : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(XorOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Xor using And with invert flags: a ^ b = (a | b) & (~a | ~b) + // (a | b) = ~(~a & ~b) + // (~a | ~b) = ~(a & b) + auto inputs = adaptor.getInputs(); + SmallVector allInverts(inputs.size(), true); + SmallVector allNotInverts(inputs.size(), false); + + // a | b = ~(~a & ~b) + auto notAAndNotB = + rewriter.create(op.getLoc(), inputs, allInverts); + auto aAndB = + rewriter.create(op.getLoc(), inputs, allNotInverts); + + rewriter.replaceOpWithNewOp(op, notAAndNotB, aAndB, + /*lhs_invert=*/true, + /*rhs_invert=*/true); + return success(); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// Convert Comb to AIG pass +//===----------------------------------------------------------------------===// + +namespace { +struct ConvertCombToAIGPass + : public impl::ConvertCombToAIGBase { + void runOnOperation() override; +}; +} // namespace + +static void populateCombToAIGConversionPatterns(RewritePatternSet &patterns) { + patterns.add( + patterns.getContext()); +} + +void ConvertCombToAIGPass::runOnOperation() { + ConversionTarget target(getContext()); + target.addIllegalDialect(); + target.addLegalDialect(); + + RewritePatternSet patterns(&getContext()); + populateCombToAIGConversionPatterns(patterns); + + if (failed(mlir::applyPartialConversion(getOperation(), target, + std::move(patterns)))) + return signalPassFailure(); +} diff --git a/lib/Dialect/AIG/AIGDialect.cpp b/lib/Dialect/AIG/AIGDialect.cpp new file mode 100644 index 000000000000..0acb1e1b8b2a --- /dev/null +++ b/lib/Dialect/AIG/AIGDialect.cpp @@ -0,0 +1,35 @@ +//===- AIGDialect.cpp - Implement the AIG dialect -----------------------===// +// +// 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 AIG dialect. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGDialect.h" +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/DialectImplementation.h" + +using namespace circt; +using namespace aig; + +//===----------------------------------------------------------------------===// +// Dialect specification. +//===----------------------------------------------------------------------===// + +void AIGDialect::initialize() { + // Register operations. + addOperations< +#define GET_OP_LIST +#include "circt/Dialect/AIG/AIG.cpp.inc" + >(); +} + +#include "circt/Dialect/AIG/AIGDialect.cpp.inc" diff --git a/lib/Dialect/AIG/AIGOps.cpp b/lib/Dialect/AIG/AIGOps.cpp new file mode 100644 index 000000000000..0a62bcbffb97 --- /dev/null +++ b/lib/Dialect/AIG/AIGOps.cpp @@ -0,0 +1,192 @@ +//===- LoopScheduleOps.cpp - LoopSchedule CIRCT Operations ------*- C++ -*-===// +// +// 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 implement the AIG ops. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/PatternMatch.h" + +using namespace mlir; +using namespace circt; +using namespace circt::aig; + +#define GET_OP_CLASSES +#include "circt/Dialect/AIG/AIG.cpp.inc" + +OpFoldResult AndInverterOp::fold(FoldAdaptor adaptor) { + if (getNumOperands() == 1 && !isInverted(0)) + return getOperand(0); + return {}; +} + +LogicalResult AndInverterOp::canonicalize(AndInverterOp op, + PatternRewriter &rewriter) { + SmallDenseMap seen; + SmallVector uniqueValues; + SmallVector uniqueInverts; + + APInt constValue = + APInt::getAllOnes(op.getResult().getType().getIntOrFloatBitWidth()); + + bool invertedConstFound = false; + size_t numConstInputs = 0; + + for (auto [value, inverted] : llvm::zip(op.getInputs(), op.getInverted())) { + if (auto constOp = value.getDefiningOp()) { + numConstInputs++; + if (inverted) { + constValue &= ~constOp.getValue(); + invertedConstFound = true; + } else { + constValue &= constOp.getValue(); + } + continue; + } + + auto it = seen.find(value); + if (it == seen.end()) { + seen.insert({value, inverted}); + uniqueValues.push_back(value); + uniqueInverts.push_back(inverted); + } else if (it->second != inverted) { + // replace with const 0 + rewriter.replaceOpWithNewOp( + op, APInt::getZero(value.getType().getIntOrFloatBitWidth())); + return success(); + } + } + + // If the constant is zero, we can just replace with zero. + if (constValue.isZero()) { + rewriter.replaceOpWithNewOp(op, constValue); + return success(); + } + + // No change. + if (uniqueValues.size() == op.getInputs().size() || + (!constValue.isAllOnes() && !invertedConstFound && + uniqueValues.size() + 1 == op.getInputs().size())) + return failure(); + + if (!constValue.isAllOnes()) { + auto constOp = rewriter.create(op.getLoc(), constValue); + uniqueInverts.push_back(false); + uniqueValues.push_back(constOp); + } + + // It means the input is reduced to all ones. + if (uniqueValues.size() == 0) { + rewriter.replaceOpWithNewOp(op, constValue); + return success(); + } + + // build new op with reduced input values + rewriter.replaceOpWithNewOp(op, uniqueValues, + uniqueInverts); + return success(); +} + +mlir::ParseResult AndInverterOp::parse(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + SmallVector operands; + SmallVector inverts; + auto loc = parser.getCurrentLocation(); + + while (true) { + if (succeeded(parser.parseOptionalKeyword("not"))) { + inverts.push_back(true); + } else { + inverts.push_back(false); + } + operands.push_back(OpAsmParser::UnresolvedOperand()); + + if (parser.parseOperand(operands.back())) + return failure(); + if (parser.parseOptionalComma()) + break; + } + + if (parser.parseOptionalAttrDict(result.attributes)) + return mlir::failure(); + + if (parser.parseColon()) + return mlir::failure(); + + mlir::Type resultRawType{}; + llvm::ArrayRef resultTypes(&resultRawType, 1); + + { + mlir::Type type; + if (parser.parseCustomTypeWithFallback(type)) + return mlir::failure(); + resultRawType = type; + } + + result.addTypes(resultTypes); + result.addAttribute("inverted", + parser.getBuilder().getDenseBoolArrayAttr(inverts)); + if (parser.resolveOperands(operands, resultTypes[0], loc, result.operands)) + return mlir::failure(); + return mlir::success(); +} + +void AndInverterOp::print(mlir::OpAsmPrinter &odsPrinter) { + odsPrinter << ' '; + llvm::interleaveComma(llvm::zip(getInverted(), getInputs()), odsPrinter, + [&](auto &&pair) { + auto [invert, input] = pair; + if (invert) { + odsPrinter << "not "; + } + odsPrinter << input; + }); + llvm::SmallVector elidedAttrs; + elidedAttrs.push_back("inverted"); + odsPrinter.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs); + odsPrinter << ' ' << ":"; + odsPrinter << ' '; + { + auto type = getResult().getType(); + if (auto validType = llvm::dyn_cast(type)) + odsPrinter.printStrippedAttrOrType(validType); + else + odsPrinter << type; + } +} + +APInt AndInverterOp::evaluate(ArrayRef inputs) { + assert(inputs.size() == getNumOperands() && + "Expected as many inputs as operands"); + assert(inputs.size() != 0 && "Expected non-empty input list"); + APInt result = APInt::getAllOnes(inputs.front().getBitWidth()); + for (auto [idx, input] : llvm::enumerate(inputs)) { + if (isInverted(idx)) + result &= ~input; + else + result &= input; + } + return result; +} + +void CutOp::build(OpBuilder &builder, OperationState &result, + TypeRange resultTypes, ValueRange inputs, + std::function ctor) { + OpBuilder::InsertionGuard guard(builder); + + auto *block = builder.createBlock(result.addRegion()); + result.addTypes(resultTypes); + result.addOperands(inputs); + for (auto input : inputs) + block->addArgument(input.getType(), input.getLoc()); + + if (ctor) + ctor(); +} diff --git a/lib/Dialect/AIG/CMakeLists.txt b/lib/Dialect/AIG/CMakeLists.txt new file mode 100644 index 000000000000..6b6d4ca0766c --- /dev/null +++ b/lib/Dialect/AIG/CMakeLists.txt @@ -0,0 +1,17 @@ +add_circt_dialect_library(CIRCTAIG + AIGOps.cpp + AIGDialect.cpp + + ADDITIONAL_HEADER_DIRS + ${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/AIG + + LINK_LIBS PUBLIC + MLIRIR + CIRCTHW + + DEPENDS + CIRCTHW + MLIRAIGIncGen +) + +add_subdirectory(Transforms) \ No newline at end of file diff --git a/lib/Dialect/AIG/Transforms/CMakeLists.txt b/lib/Dialect/AIG/Transforms/CMakeLists.txt new file mode 100644 index 000000000000..17cf02ddc6b4 --- /dev/null +++ b/lib/Dialect/AIG/Transforms/CMakeLists.txt @@ -0,0 +1,14 @@ +add_circt_dialect_library(CIRCTAIGTransforms + GreedyCutDecomp.cpp + LowerCutToLUT.cpp + LowerVariadic.cpp + LowerWordToBits.cpp + + DEPENDS + CIRCTAIGTransformsIncGen + + LINK_LIBS PUBLIC + CIRCTAIG + CIRCTComb + CIRCTHW +) diff --git a/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp b/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp new file mode 100644 index 000000000000..89e9cf760021 --- /dev/null +++ b/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp @@ -0,0 +1,292 @@ +//===- GreedyCutDecomp.cpp ---------------------------------------------===// +// +// 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 pass performs cut decomposition on AIGs based on a naive greedy +// algorithm. We first convert all `aig.and_inv` to `aig.cut` that has a single +// operation and then try to merge cut operations on inputs. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Analysis/TopologicalSortUtils.h" +#include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" +#define DEBUG_TYPE "aig-greedy-cut-decomp" + +namespace circt { +namespace aig { +#define GEN_PASS_DEF_GREEDYCUTDECOMP +#include "circt/Dialect/AIG/AIGPasses.h.inc" +} // namespace aig +} // namespace circt + +using namespace circt; +using namespace aig; + +//===----------------------------------------------------------------------===// +// Conversion patterns +//===----------------------------------------------------------------------===// + +namespace { + +struct AndInverterOpToCutPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(aig::AndInverterOp op, + PatternRewriter &rewriter) const override { + if (isa(op->getParentOp())) + return failure(); + + auto cutOp = rewriter.create( + op.getLoc(), op.getResult().getType(), op.getInputs(), [&]() { + auto result = rewriter.create( + op.getLoc(), op.getResult().getType(), + rewriter.getBlock()->getArguments(), op.getInvertedAttr()); + rewriter.create(op.getLoc(), ValueRange{result}); + }); + + rewriter.replaceOp(op, cutOp); + return success(); + } +}; + +static aig::CutOp mergeCuts(Location loc, MutableArrayRef cuts, + ArrayRef inputs, Value output, + PatternRewriter &rewriter) { + if (!mlir::computeTopologicalSorting(cuts)) + return {}; + + assert(cuts.size() >= 2); + + DenseMap valueToNewValue, inputsToBlockArg; + auto cutOp = + rewriter.create(loc, output.getType(), inputs, [&]() { + for (auto [i, input] : llvm::enumerate(inputs)) + inputsToBlockArg[input] = rewriter.getBlock()->getArgument(i); + + for (auto [i, cut] : llvm::enumerate(cuts)) { + auto cutOp = cast(cut); + assert(cutOp.getNumResults() == 1); + for (auto [arg, input] : llvm::zip( + cutOp.getBodyBlock()->getArguments(), cutOp.getInputs())) { + auto it = inputsToBlockArg.find(input); + if (it != inputsToBlockArg.end()) { + rewriter.replaceAllUsesWith(arg, it->second); + } else { + auto cutOp = dyn_cast(input.getDefiningOp()); + assert(cutOp && cutOp.getNumResults() == 1); + rewriter.replaceAllUsesWith(arg, valueToNewValue.at(input)); + } + } + + assert(cutOp.getNumResults() == 1); + valueToNewValue[cutOp.getResult(0)] = + cutOp.getBodyBlock()->getTerminator()->getOperand(0); + } + }); + + rewriter.replaceAllUsesWith(output, cutOp.getResult(0)); + { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToEnd(cutOp.getBodyBlock()); + rewriter.create(loc, ValueRange{valueToNewValue.at(output)}); + } + + for (auto oldCut : llvm::reverse(cuts)) { + auto *oldCutBlock = cast(oldCut).getBodyBlock(); + auto oldCutOutput = oldCutBlock->getTerminator(); + oldCutOutput->erase(); + // Erase arguments before inlining. Arguments are already replaced. + oldCutBlock->eraseArguments([](BlockArgument block) { return true; }); + rewriter.inlineBlockBefore(oldCutBlock, cutOp.getBodyBlock(), + cutOp.getBodyBlock()->begin()); + rewriter.eraseOp(oldCut); + } + + return cutOp; +} + +struct MergeCutPattern : public OpRewritePattern { + MergeCutPattern(MLIRContext *context, unsigned cutLimit) + : OpRewritePattern(context), cutLimit(cutLimit) {} + const unsigned cutLimit; + + LogicalResult matchAndRewrite(aig::CutOp op, + PatternRewriter &rewriter) const override { + if (op.getNumOperands() >= cutLimit) + return failure(); + + SmallVector inputCuts; + { + SetVector inputCutsSet; + inputCutsSet.insert(op); + for (auto cut : op.getOperands()) { + if (auto cutOp = cut.getDefiningOp(); + cutOp && cutOp.getNumResults() == 1) + inputCutsSet.insert(cutOp); + } + + inputCuts = std::move(inputCutsSet.takeVector()); + } + + if (inputCuts.size() <= 1) + return failure(); + + SmallVector inputs; + inputs.reserve(inputCuts.size()); + + // This is naive implementation of the cut emuration of the local inputs. + // FIXME: This is really dumb algorithm, but it is just a proof of concept. + + LLVM_DEBUG(llvm::dbgs() << "Trying to merge " << op << "\n"); + for (unsigned i = (1 << (inputCuts.size() - 1)) - 1; i != 0; --i) { + auto checkSubsetMerge = [&](unsigned i) -> LogicalResult { + SetVector inValues; + llvm::SmallDenseSet cutSet; + SmallVector outValues; + + for (unsigned j = 0; j < inputCuts.size(); ++j) { + if (i & (1 << j)) { + cutSet.insert(inputCuts[j]); + for (auto in : inputCuts[j].getInputs()) + inValues.insert(in); + outValues.push_back(inputCuts[j].getResult(0)); + LLVM_DEBUG(llvm::dbgs() << "Added " << inputCuts[j] << "\n"); + } + } + + Value singleOutput; + for (auto out : outValues) { + // Users of cuts must be closed under the cut set. + bool isClosed = + llvm::all_of(out.getUsers(), [&cutSet](Operation *user) { + if (auto cutOp = dyn_cast(user)) + return cutSet.contains(cutOp); + return false; + }); + inValues.remove(out); + if (!isClosed) { + if (singleOutput) { + LLVM_DEBUG(llvm::dbgs() << "Not closed\n"); + return failure(); + } + + singleOutput = out; + } + } + + if (!singleOutput || inValues.size() > cutLimit) { + LLVM_DEBUG(llvm::dbgs() << "Limit exceeded\n"); + return failure(); + } + + SmallVector subsetCuts; + for (unsigned j = 0; j < inputCuts.size(); ++j) { + if (i & (1 << j)) + subsetCuts.push_back(inputCuts[j]); + } + + // Ok, let's merge the cuts. + auto cutOp = mergeCuts(op.getLoc(), subsetCuts, inValues.takeVector(), + singleOutput, rewriter); + if (!cutOp) { + LLVM_DEBUG(llvm::dbgs() << "Failed to merge\n"); + return failure(); + } + return success(); + }; + + // Always enable a bit for 0 (the original cut). + auto result = checkSubsetMerge((i << 1) | 1); + if (succeeded(result)) + return result; + } + return failure(); + } +}; + +struct SinkConstantPattern : public mlir::OpRewritePattern { + using mlir::OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(aig::CutOp op, + PatternRewriter &rewriter) const override { + SmallVector oldInputs, oldArgs; + auto *block = op.getBodyBlock(); + { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToStart(block); + BitVector eraseArgs(block->getNumArguments()); + bool changed = false; + for (auto [i, in] : llvm::enumerate(op.getInputs())) { + if (auto constOp = in.getDefiningOp()) { + eraseArgs.set(i); + auto cloned = rewriter.clone(*constOp); + rewriter.replaceAllUsesWith(block->getArgument(i), + cloned->getResult(0)); + changed = true; + } else { + oldInputs.push_back(in); + oldArgs.push_back(block->getArgument(i)); + } + } + if (!changed) + return failure(); + block->eraseArguments(eraseArgs); + } + + auto newCut = rewriter.create(op.getLoc(), op.getResultTypes(), + oldInputs, [&]() {}); + + for (auto [newArg, oldArg] : + llvm::zip(newCut.getBodyBlock()->getArguments(), oldArgs)) + rewriter.replaceAllUsesWith(oldArg, newArg); + + // Erase arguments before inlining. Arguments are already replaced. + block->eraseArguments([](BlockArgument arg) { return true; }); + rewriter.inlineBlockBefore(block, newCut.getBodyBlock(), + newCut.getBodyBlock()->begin()); + rewriter.replaceOp(op, newCut); + return success(); + } +}; +} // namespace + +//===----------------------------------------------------------------------===// +// Greedy Cut Decomposition Pass +//===----------------------------------------------------------------------===// + +namespace { +struct GreedyCutDecompPass + : public impl::GreedyCutDecompBase { + using GreedyCutDecompBase::GreedyCutDecompBase; + void runOnOperation() override; + using GreedyCutDecompBase::cutSizes; +}; +} // namespace + +void GreedyCutDecompPass::runOnOperation() { + RewritePatternSet patterns(&getContext()); + patterns.add( + patterns.getContext()); + patterns.add(patterns.getContext(), cutSizes.getValue()); + mlir::FrozenRewritePatternSet frozen(std::move(patterns)); + mlir::GreedyRewriteConfig config; + + config.useTopDownTraversal = true; + + if (failed( + mlir::applyPatternsAndFoldGreedily(getOperation(), frozen, config))) + return signalPassFailure(); +} + +std::unique_ptr +aig::createGreedyCutDecompPass(const GreedyCutDecompOptions &options) { + return std::make_unique(options); +} diff --git a/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp b/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp new file mode 100644 index 000000000000..244988f716cd --- /dev/null +++ b/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp @@ -0,0 +1,159 @@ +//===- LowerCutToLUT.cpp --------------------------------------------------===// +// +// 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 pass lowers `aig.cut` to `comb.truth_table` with k inputs where +// k is the size of the cut (= operand inputs size). +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Transforms/DialectConversion.h" + +#define DEBUG_TYPE "aig-lower-cut-to-lut" + +namespace circt { +namespace aig { +#define GEN_PASS_DEF_LOWERCUTTOLUT +#include "circt/Dialect/AIG/AIGPasses.h.inc" +} // namespace aig +} // namespace circt + +using namespace circt; +using namespace aig; + +//===----------------------------------------------------------------------===// +// Conversion patterns +//===----------------------------------------------------------------------===// + +namespace { + +struct CutToLUTPattern : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CutOp op, + PatternRewriter &rewriter) const override; +}; + +LogicalResult +CutToLUTPattern::matchAndRewrite(CutOp cutOp, PatternRewriter &rewriter) const { + if (cutOp.getNumResults() == 0) { + rewriter.eraseOp(cutOp); + return success(); + } + + auto lutWidth = cutOp.getNumOperands(); + // 2. Lower the cut to a LUT. We can get a truth table by evaluating the cut + // body with every possible combination of the input values. + uint32_t tableSize = 1 << lutWidth; + DenseMap mapping; + auto &body = cutOp.getBodyRegion().front(); + for (uint32_t i = 0; i < lutWidth; i++) { + APInt value(tableSize, 0); + for (uint32_t j = 0; j < tableSize; j++) { + // Make sure the order of the bits is correct. + value.setBitVal(j, (j >> i) & 1); + } + mapping[body.getArgument(i)] = std::move(value); + } + + // Evaluate the cut body. Update `mapping` along the way. + for (auto &op : body.getOperations()) { + if (auto constOp = dyn_cast(&op)) { + mapping[constOp.getResult()] = + APInt(tableSize, constOp.getValue().getZExtValue()); + } else if (auto AndInverterOp = dyn_cast(&op)) { + // TODO: Avoid this copy. + SmallVector inputs; + for (auto input : AndInverterOp.getInputs()) + inputs.push_back(mapping[input]); + mapping[AndInverterOp.getResult()] = AndInverterOp.evaluate(inputs); + } else if (auto outputOp = dyn_cast(&op)) { + assert(outputOp.getOutputs().size() == 1 && "expected single output"); + auto value = mapping.at(outputOp.getOutputs().front()); + LLVM_DEBUG(llvm::dbgs() << "value: " << value << "\n"); + SmallVector bits; + bits.reserve(tableSize); + for (uint32_t i = 0; i < tableSize; i++) + bits.push_back(value[i]); + auto truthTable = rewriter.create( + op.getLoc(), cutOp.getOperands(), rewriter.getBoolArrayAttr(bits)); + rewriter.replaceOp(cutOp, truthTable); + return success(); + } else { + return op.emitError("unsupported operation in Eval: ") << op; + } + } + + // It should not reach here. + return failure(); +} + +} // namespace + +//===----------------------------------------------------------------------===// +// Convert Comb to AIG pass +//===----------------------------------------------------------------------===// + +static void populateLowerCutToLUTPatterns(RewritePatternSet &patterns) { + patterns.add(patterns.getContext()); +} + +namespace { +struct LowerCutToLUTPass : public impl::LowerCutToLUTBase { + void runOnOperation() override; +}; +} // namespace + +void LowerCutToLUTPass::runOnOperation() { + auto i1Type = IntegerType::get(&getContext(), 1); + auto result = getOperation().walk([&](aig::CutOp cutOp) -> WalkResult { + // 1. Check if the cut can be lowered to a LUT. + + // Check if the cut has a single output. + if (cutOp.getNumResults() != 1) + return cutOp.emitError("expected single output"); + + // Check if every type is i1. + for (auto operand : cutOp.getOperands()) { + if (operand.getType() != i1Type) + return cutOp.emitError("expected i1 type"); + } + + for (auto result : cutOp.getResults()) { + if (result.getType() != i1Type) + return cutOp.emitError("expected i1 type"); + } + + uint32_t lutWidth = cutOp.getNumOperands(); + if (lutWidth >= 32) + return cutOp.emitError("Cut width is too large to fit in a LUT"); + + return WalkResult::advance(); + }); + + if (result.wasInterrupted()) + return signalPassFailure(); + + ConversionTarget target(getContext()); + target.addLegalDialect(); + target.addIllegalOp(); + + RewritePatternSet patterns(&getContext()); + populateLowerCutToLUTPatterns(patterns); + + if (failed(mlir::applyPartialConversion(getOperation(), target, + std::move(patterns)))) + return signalPassFailure(); +} + +std::unique_ptr aig::createLowerCutToLUTPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/AIG/Transforms/LowerVariadic.cpp b/lib/Dialect/AIG/Transforms/LowerVariadic.cpp new file mode 100644 index 000000000000..d8821a3ae2d1 --- /dev/null +++ b/lib/Dialect/AIG/Transforms/LowerVariadic.cpp @@ -0,0 +1,112 @@ +//===- LowerVariadic.cpp ---------------------------------------------===// +// +// 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 pass lowers variadic AndInverter operations to binary AndInverter +// operations. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +#define DEBUG_TYPE "aig-lower-variadic" + +namespace circt { +namespace aig { +#define GEN_PASS_DEF_LOWERVARIADIC +#include "circt/Dialect/AIG/AIGPasses.h.inc" +} // namespace aig +} // namespace circt + +using namespace circt; +using namespace aig; + +//===----------------------------------------------------------------------===// +// Conversion patterns +//===----------------------------------------------------------------------===// + +namespace { +static Value lowerFullyAssociativeOp(AndInverterOp op, OperandRange operands, + ArrayRef inverts, + PatternRewriter &rewriter) { + Value lhs, rhs; + switch (operands.size()) { + case 0: + assert(0 && "cannot be called with empty operand range"); + break; + case 1: + if (inverts[0]) + return rewriter.create(op.getLoc(), operands[0], true); + else + return operands[0]; + case 2: + lhs = operands[0]; + rhs = operands[1]; + return rewriter.create(op.getLoc(), lhs, rhs, inverts[0], + inverts[1]); + default: + auto firstHalf = operands.size() / 2; + lhs = lowerFullyAssociativeOp(op, operands.take_front(firstHalf), + inverts.take_front(firstHalf), rewriter); + rhs = lowerFullyAssociativeOp(op, operands.drop_front(firstHalf), + inverts.drop_front(firstHalf), rewriter); + return rewriter.create(op.getLoc(), lhs, rhs); + } +} + +struct VariadicOpConversion : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(AndInverterOp op, + PatternRewriter &rewriter) const override { + if (op.getInputs().size() <= 2) + return failure(); + + // TODO: This is naive implementation that creates a balanced binary tree. + // We can improve by analyzing the dataflow and creating a tree that + // improves the critical path or area. + auto result = lowerFullyAssociativeOp(op, op.getOperands(), + op.getInverted(), rewriter); + rewriter.replaceOp(op, result); + return success(); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// Convert Comb to AIG pass +//===----------------------------------------------------------------------===// + +static void populateLowerVariadicPatterns(RewritePatternSet &patterns) { + patterns.add(patterns.getContext()); +} + +namespace { +struct LowerVariadicPass : public impl::LowerVariadicBase { + void runOnOperation() override; +}; +} // namespace + +void LowerVariadicPass::runOnOperation() { + RewritePatternSet patterns(&getContext()); + populateLowerVariadicPatterns(patterns); + mlir::FrozenRewritePatternSet frozen(std::move(patterns)); + mlir::GreedyRewriteConfig config; + + if (failed( + mlir::applyPatternsAndFoldGreedily(getOperation(), frozen, config))) + return signalPassFailure(); +} + +std::unique_ptr aig::createLowerVariadicPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp b/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp new file mode 100644 index 000000000000..3f4a49a8c09d --- /dev/null +++ b/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp @@ -0,0 +1,91 @@ +//===- LowerWordToBits.cpp ---------------------------------------------===// +// +// 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 pass lowers multi-bit AIG operations to single-bit ones. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/AIG/AIGOps.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +#define DEBUG_TYPE "aig-lower-word-to-bits" + +namespace circt { +namespace aig { +#define GEN_PASS_DEF_LOWERWORDTOBITS +#include "circt/Dialect/AIG/AIGPasses.h.inc" +} // namespace aig +} // namespace circt + +using namespace circt; +using namespace aig; + +//===----------------------------------------------------------------------===// +// Rewrite patterns +//===----------------------------------------------------------------------===// + +namespace { + +struct WordRewritePattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(AndInverterOp op, + PatternRewriter &rewriter) const override { + if (op.getType().getIntOrFloatBitWidth() <= 1) + return failure(); + + SmallVector result; + for (size_t i = 0; i < op.getType().getIntOrFloatBitWidth(); i++) { + SmallVector operands; + for (auto operand : op.getOperands()) { + auto operandBits = + rewriter.create(op.getLoc(), operand, i, 1); + operands.push_back(operandBits); + } + auto andInverter = rewriter.create(op.getLoc(), operands, + op.getInvertedAttr()); + result.push_back(andInverter); + } + + auto concat = rewriter.create(op.getLoc(), result); + rewriter.replaceOp(op, concat); + return success(); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// Lower Word to Bits pass +//===----------------------------------------------------------------------===// + +namespace { +struct LowerWordToBitsPass + : public impl::LowerWordToBitsBase { + void runOnOperation() override; +}; +} // namespace + +void LowerWordToBitsPass::runOnOperation() { + RewritePatternSet patterns(&getContext()); + patterns.add(&getContext()); + + mlir::FrozenRewritePatternSet frozenPatterns(std::move(patterns)); + mlir::GreedyRewriteConfig config; + + if (failed(mlir::applyPatternsAndFoldGreedily(getOperation(), frozenPatterns, + config))) + return signalPassFailure(); +} + +std::unique_ptr aig::createLowerWordToBitsPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/CMakeLists.txt b/lib/Dialect/CMakeLists.txt index 82ff4d0d835e..eb30485abf79 100644 --- a/lib/Dialect/CMakeLists.txt +++ b/lib/Dialect/CMakeLists.txt @@ -9,6 +9,7 @@ ## ##===----------------------------------------------------------------------===// +add_subdirectory(AIG) add_subdirectory(Arc) add_subdirectory(Calyx) add_subdirectory(Comb) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0661355d1b98..3bc76409af63 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -26,6 +26,7 @@ set(CIRCT_TEST_DEPENDS circt-dis circt-lec circt-opt + circt-synth circt-test circt-translate circt-reduce diff --git a/test/Conversion/CombToAIG/comb-to-aig.mlir b/test/Conversion/CombToAIG/comb-to-aig.mlir new file mode 100644 index 000000000000..de740b40e192 --- /dev/null +++ b/test/Conversion/CombToAIG/comb-to-aig.mlir @@ -0,0 +1,9 @@ +// RUN: circt-opt %s --convert-comb-to-aig | FileCheck %s + +// CHECK-LABEL: @test +hw.module @test(in %arg0: i32, in %arg1: i32, in %arg2: i32, in %arg3: i32, in %arg4: i1, out out: i32) { + // CHECK-NEXT: %0 = aig.and_inv not %arg0, not %arg1, not %arg2, not %arg3 : i32 + // CHECK-NEXT: %1 = aig.and_inv not %0 : i32 + %0 = comb.or %arg0, %arg1, %arg2, %arg3 : i32 + hw.output %0 : i32 +} \ No newline at end of file diff --git a/test/Dialect/AIG/canonicalizer.mlir b/test/Dialect/AIG/canonicalizer.mlir new file mode 100644 index 000000000000..2147673a20c6 --- /dev/null +++ b/test/Dialect/AIG/canonicalizer.mlir @@ -0,0 +1,42 @@ +// RUN: circt-opt %s --canonicalize | FileCheck %s + +// CHECK-LABEL: @And +hw.module @And(in %a: i4, in %b: i4, out o1: i4, out o2: i4, + out o3: i4, out o4: i4, out o5: i4, out o6: i4, out o7: i4, + out o8: i4) { + // CHECK-NEXT: %c0_i4 = hw.constant 0 : i4 + // CHECK-NEXT: %c-1_i4 = hw.constant -1 : i4 + // CHECK-NEXT: %c5_i4 = hw.constant 5 : i4 + // CHECK-NEXT: %[[TMP1:.+]] = aig.and_inv %a, %c5_i4 : i4 + // CHECK-NEXT: %[[TMP2:.+]] = aig.and_inv %a, %b : i4 + // CHECK-NEXT: hw.output %c0_i4, %[[TMP1]], %a, %a, %c0_i4, %[[TMP2]], %c0_i4, %c-1_i4 : i4, i4, i4, i4, i4, i4, i4, i4 + %c0 = hw.constant 0 : i4 + %c2 = hw.constant 2 : i4 + %c8 = hw.constant 7 : i4 + %c15 = hw.constant 15 : i4 + + // a & 0 -> 0 + %0 = aig.and_inv %a, %c0 : i4 + + // a & 7 & ~2 -> a & 5 + %1 = aig.and_inv %a, %c8, not %c2 : i4 + + // a & 15 -> a + %2 = aig.and_inv %a, %c15 : i4 + + // a & ~0 -> a + %3 = aig.and_inv %a, not %c0 : i4 + + // a & ~15 -> 0 + %4 = aig.and_inv %a, not %c15 : i4 + + // a & a & b -> a & b + %5 = aig.and_inv %a, %b : i4 + + // a & ~a & b -> 0 + %6 = hw.constant 0 : i4 + + // 15 & 15 -> 15 + %7 = hw.constant -1 : i4 + hw.output %0, %1, %2, %3, %4, %5, %6, %7 : i4, i4, i4, i4, i4, i4, i4, i4 +} diff --git a/test/Dialect/AIG/greedy-decomp.mlir b/test/Dialect/AIG/greedy-decomp.mlir new file mode 100644 index 000000000000..9827b0394dea --- /dev/null +++ b/test/Dialect/AIG/greedy-decomp.mlir @@ -0,0 +1,26 @@ +// RUN: circt-opt %s --aig-greedy-cut-decomp | FileCheck %s + +// CHECK-LABEL: @variadic +hw.module @variadic(in %a : i1, in %b : i1, in %c : i1, in %d : i1, in %e : i1, in %f : i1, in %g : i1, out and6 : i1) { + %0 = aig.and_inv %b, %c : i1 + %1 = aig.and_inv %a, %0 : i1 + %2 = aig.and_inv %d, %e : i1 + %3 = aig.and_inv %f, %g : i1 + %4 = aig.and_inv %2, %3 : i1 + %5 = aig.and_inv %1, %4 : i1 + hw.output %5 : i1 +} +// CHECK-NEXT: %0 = aig.cut %a, %b, %c : (i1, i1, i1) -> i1 { +// CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1, %arg2: i1): +// CHECK-NEXT: %2 = aig.and_inv %arg1, %arg2 : i1 +// CHECK-NEXT: %3 = aig.and_inv %arg0, %2 : i1 +// CHECK-NEXT: aig.output %3 : i1 +// CHECK-NEXT: } +// CHECK-NEXT: %1 = aig.cut %0, %d, %e, %f, %g : (i1, i1, i1, i1, i1) -> i1 { +// CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1): +// CHECK-NEXT: %2 = aig.and_inv %arg1, %arg2 : i1 +// CHECK-NEXT: %3 = aig.and_inv %arg3, %arg4 : i1 +// CHECK-NEXT: %4 = aig.and_inv %2, %3 : i1 +// CHECK-NEXT: %5 = aig.and_inv %arg0, %4 : i1 +// CHECK-NEXT: aig.output %5 : i1 +// CHECK-NEXT: } diff --git a/test/Dialect/AIG/lower-cut-to-lut.mlir b/test/Dialect/AIG/lower-cut-to-lut.mlir new file mode 100644 index 000000000000..71e5514784c5 --- /dev/null +++ b/test/Dialect/AIG/lower-cut-to-lut.mlir @@ -0,0 +1,15 @@ +// RUN: circt-opt %s --aig-lower-cut-to-lut | FileCheck %s +// CHECK: hw.module @Cut +hw.module @Cut(in %a: i1, in %b: i1, in %c: i1, in %d: i1, out e: i1) { + // CHECK-NEXT: %0 = comb.truth_table %a, %b, %c, %d + // CHECK-SAME: -> [false, false, false, false, false, true, true, true, + // CHECK-SAME: false, false, false, false, false, false, false, false] + %0 = aig.cut %a, %b, %c, %d : (i1, i1, i1, i1) -> (i1) { + ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1): + %1 = aig.and_inv not %arg0, not %arg1 : i1 + %2 = aig.and_inv %arg2, not %arg3 : i1 + %3 = aig.and_inv not %1, %2 : i1 + aig.output %3 : i1 + } + hw.output %0 : i1 +} diff --git a/test/Dialect/AIG/lower-variadic.mlir b/test/Dialect/AIG/lower-variadic.mlir new file mode 100644 index 000000000000..df1dca9279eb --- /dev/null +++ b/test/Dialect/AIG/lower-variadic.mlir @@ -0,0 +1,11 @@ +// RUN: circt-opt %s --aig-lower-variadic | FileCheck %s +// CHECK: hw.module @Basic +hw.module @Basic(in %a: i2, in %b: i2, in %c: i2, in %d: i2, in %e: i2, out f: i2) { + // CHECK: %0 = aig.and_inv not %a, %b : i2 + // CHECK-NEXT: %1 = aig.and_inv not %d, %e : i2 + // CHECK-NEXT: %2 = aig.and_inv %c, %1 : i2 + // CHECK-NEXT: %3 = aig.and_inv %0, %2 : i2 + // CHECK-NEXT: hw.output %3 : i2 + %0 = aig.and_inv not %a, %b, %c, not %d, %e : i2 + hw.output %0 : i2 +} diff --git a/test/Dialect/AIG/lower-word-to-bits.mlir b/test/Dialect/AIG/lower-word-to-bits.mlir new file mode 100644 index 000000000000..9202a8c6eb98 --- /dev/null +++ b/test/Dialect/AIG/lower-word-to-bits.mlir @@ -0,0 +1,14 @@ +// RUN: circt-opt %s --aig-lower-word-to-bits | FileCheck %s +// CHECK: hw.module @Basic +hw.module @Basic(in %a: i2, in %b: i2, out f: i2) { + // CHECK-NEXT: %0 = comb.extract %a from 0 : (i2) -> i1 + // CHECK-NEXT: %1 = comb.extract %b from 0 : (i2) -> i1 + // CHECK-NEXT: %2 = aig.and_inv not %0, %1 : i1 + // CHECK-NEXT: %3 = comb.extract %a from 1 : (i2) -> i1 + // CHECK-NEXT: %4 = comb.extract %b from 1 : (i2) -> i1 + // CHECK-NEXT: %5 = aig.and_inv not %3, %4 : i1 + // CHECK-NEXT: %6 = comb.concat %2, %5 : i1, i1 + // CHECK-NEXT: hw.output %6 : i2 + %0 = aig.and_inv not %a, %b : i2 + hw.output %0 : i2 +} diff --git a/test/Dialect/AIG/round-trip.mlir b/test/Dialect/AIG/round-trip.mlir new file mode 100644 index 000000000000..78f9e6bd8574 --- /dev/null +++ b/test/Dialect/AIG/round-trip.mlir @@ -0,0 +1,21 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +// CHECK-LABEL: @And +// CHECK-NEXT: aig.and_inv %b, %b : i4 +// CHECK-NEXT: aig.and_inv %b, not %b : i4 +// CHECK-NEXT: aig.and_inv not %a, not %a : i1 +hw.module @And(in %a: i1, in %b: i4) { + %0 = aig.and_inv %b, %b : i4 + %1 = aig.and_inv %b, not %b : i4 + %2 = aig.and_inv not %a, not %a : i1 +} + +hw.module @Cut(in %a: i1, in %b: i1, out c: i1, out d: i1) { + %0, %1 = aig.cut %a, %b : (i1, i1) -> (i1, i1) { + ^bb0(%arg0: i1, %arg1: i1): + %c = aig.and_inv %arg0, not %arg1 : i1 + %d = aig.and_inv %arg0, %arg1 : i1 + aig.output %c, %d : i1, i1 + } + hw.output %0, %1 : i1, i1 +} diff --git a/test/circt-synth/basic.mlir b/test/circt-synth/basic.mlir new file mode 100644 index 000000000000..82c96020f9e3 --- /dev/null +++ b/test/circt-synth/basic.mlir @@ -0,0 +1,46 @@ +// RUN: circt-synth %s | FileCheck %s + +// CHECK-LABEL: @and +hw.module @and(in %a: i1, in %b: i1, out and: i1) { + %0 = comb.and %a, %b : i1 + // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, false, false, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @or +hw.module @or(in %a: i1, in %b: i1, out or: i1) { + %0 = comb.or %a, %b : i1 + // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, true, true, true] + hw.output %0 : i1 +} + +// CHECK-LABEL: @xor +hw.module @xor(in %a: i1, in %b: i1, out xor: i1) { + %0 = comb.xor %a, %b : i1 + // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, true, true, false] + hw.output %0 : i1 +} + +// CHECK-LABEL: @multibit +hw.module @multibit(in %a: i2, in %b: i2, out and: i2) { + %0 = comb.and %a, %b : i2 + // CHCK-NEXT: %0 = comb.extract %a from 0 : (i2) -> i1 + // CHCK-NEXT: %1 = comb.extract %b from 0 : (i2) -> i1 + // CHCK-NEXT: %2 = comb.truth_table %0, %1 -> [false, false, false, true] + // CHCK-NEXT: %3 = comb.extract %a from 1 : (i2) -> i1 + // CHCK-NEXT: %4 = comb.extract %b from 1 : (i2) -> i1 + // CHCK-NEXT: %5 = comb.truth_table %3, %4 -> [false, false, false, true] + // CHCK-NEXT: %6 = comb.concat %2, %5 : i1, i1 + hw.output %0 : i2 +} + +// CHECK-LABEL: @variadic +hw.module @variadic(in %a: i1, in %b: i1, in %c: i1, + in %d: i1, in %e: i1, in %f: i1, out and6: i1) { + %0 = comb.and %a, %b, %c, %d, %e, %f : i1 + // CHECK-NEXT: %0 = comb.truth_table %a, %b, %c, %d, %e, %f -> [ + // CHECK-COUNT-63: false + // CHECK-SAME: true + // CHECK-SAME: ] + hw.output %0 : i1 +} \ No newline at end of file diff --git a/test/circt-synth/commandline.mlir b/test/circt-synth/commandline.mlir new file mode 100644 index 000000000000..9673e7c89a2a --- /dev/null +++ b/test/circt-synth/commandline.mlir @@ -0,0 +1,3 @@ +// RUN: circt-synth --help | FileCheck %s + +// CHECK: OVERVIEW: Logic synthesis tool diff --git a/test/circt-synth/lut-size.mlir b/test/circt-synth/lut-size.mlir new file mode 100644 index 000000000000..222d8d947089 --- /dev/null +++ b/test/circt-synth/lut-size.mlir @@ -0,0 +1,24 @@ +// RUN: circt-synth %s --lut-size=2 | FileCheck %s --check-prefix=LUT-2 +// RUN: circt-synth %s --lut-size=4 | FileCheck %s --check-prefix=LUT-4 +// RUN: circt-synth %s --lut-size=6 | FileCheck %s --check-prefix=LUT-6 + +// LUT-2-LABEL: @variadic +// LUT-4-LABEL: @variadic +// LUT-6-LABEL: @variadic +hw.module @variadic(in %a: i1, in %b: i1, in %c: i1, + in %d: i1, in %e: i1, in %f: i1, out and6: i1) { + // LUT-2-NEXT: %0 = comb.truth_table %b, %c -> [false, false, false, true] + // LUT-2-NEXT: %1 = comb.truth_table %a, %0 -> [false, false, false, true] + // LUT-2-NEXT: %2 = comb.truth_table %e, %f -> [false, false, false, true] + // LUT-2-NEXT: %3 = comb.truth_table %d, %2 -> [false, false, false, true] + // LUT-2-NEXT: %4 = comb.truth_table %1, %3 -> [false, false, false, true] + // LUT-2-NEXT: hw.output %4 + // LUT-4-NEXT: %0 = comb.truth_table %a, %b, %c -> [false, false, false, false, false, false, false, true] + // LUT-4-NEXT: %1 = comb.truth_table %0, %d, %e, %f -> [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true] + // LUT-4-NEXT: hw.output %1 + // LUT-6-NEXT: %0 = comb.truth_table + // LUT-6-NEXT: hw.output %0 + %0 = comb.and %a, %b, %c, %d, %e, %f : i1 + + hw.output %0 : i1 +} diff --git a/test/lit.cfg.py b/test/lit.cfg.py index 479f97da8a1c..86eb72502280 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -60,8 +60,8 @@ tools = [ 'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test', 'circt-capi-firrtl-test', 'circt-capi-firtool-test', 'circt-dis', - 'circt-lec', 'circt-reduce', 'circt-test', 'circt-translate', 'firtool', - 'hlstool', 'om-linker', 'ibistool' + 'circt-lec', 'circt-reduce', 'circt-test', 'circt-translate', 'circt-synth', + 'firtool', 'hlstool', 'om-linker', 'ibistool' ] if "CIRCT_OPT_CHECK_IR_ROUNDTRIP" in os.environ: diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 738513fc547c..a91b167fa521 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(circt-lsp-server) add_subdirectory(circt-opt) add_subdirectory(circt-reduce) add_subdirectory(circt-rtl-sim) +add_subdirectory(circt-synth) add_subdirectory(circt-test) add_subdirectory(circt-translate) add_subdirectory(firtool) diff --git a/tools/circt-synth/CMakeLists.txt b/tools/circt-synth/CMakeLists.txt new file mode 100644 index 000000000000..c4ec428bad40 --- /dev/null +++ b/tools/circt-synth/CMakeLists.txt @@ -0,0 +1,16 @@ +add_circt_tool(circt-synth circt-synth.cpp) +target_link_libraries(circt-synth + PRIVATE + CIRCTCombToAIG + CIRCTAIGTransforms + CIRCTAIG + CIRCTComb + CIRCTHW + CIRCTSupport + MLIRIR + MLIRParser + LLVMSupport +) + +llvm_update_compile_flags(circt-synth) +mlir_check_all_link_libraries(circt-synth) diff --git a/tools/circt-synth/circt-synth.cpp b/tools/circt-synth/circt-synth.cpp new file mode 100644 index 000000000000..b08050b230a3 --- /dev/null +++ b/tools/circt-synth/circt-synth.cpp @@ -0,0 +1,219 @@ +//===- circt-synth.cpp - The circt-synth driver -----------------*- C++ -*-===// +// +// 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 initiliazes the 'circt-synth' tool, which performs logic +/// synthesis. Currently, it only performs backend-agnostic FPGA synthesis, +/// mapping core dialects into FPGA-specific primitives, such as LUTs. +/// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/CombToAIG.h" +#include "circt/Dialect/AIG/AIGDialect.h" +#include "circt/Dialect/AIG/AIGPasses.h" +#include "circt/Dialect/Comb/CombDialect.h" +#include "circt/Dialect/HW/HWDialect.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Support/Passes.h" +#include "circt/Support/Version.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/OwningOpRef.h" +#include "mlir/Parser/Parser.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Support/FileUtilities.h" +#include "mlir/Support/LogicalResult.h" +#include "mlir/Transforms/Passes.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/ToolOutputFile.h" + +namespace cl = llvm::cl; + +using namespace mlir; +using namespace circt; + +//===----------------------------------------------------------------------===// +// Command-line options declaration +//===----------------------------------------------------------------------===// + +static cl::OptionCategory mainCategory("circt-synth Options"); + +static cl::opt inputFilename(cl::Positional, cl::Required, + cl::desc("Specify an input file"), + cl::value_desc("filename"), + cl::cat(mainCategory)); + +static cl::opt outputFilename("o", cl::desc("Output filename"), + cl::value_desc("filename"), + cl::init("-"), + cl::cat(mainCategory)); + +static cl::opt + verifyPasses("verify-each", + cl::desc("Run the verifier after each transformation pass"), + cl::init(true), cl::cat(mainCategory)); + +static cl::opt + verbosePassExecutions("verbose-pass-executions", + cl::desc("Log executions of toplevel module passes"), + cl::init(false), cl::cat(mainCategory)); +// Options to control early-out from pipeline. +enum Until { UntilAIG, UntilAIGOpt, UntilAIGLowering, UntilEnd }; + +static auto runUntilValues = llvm::cl::values( + clEnumValN(UntilAIG, "aig", "Conversion of modules to AIG"), + clEnumValN(UntilAIGOpt, "aig-opt", "AIG optimizations"), + clEnumValN(UntilAIGLowering, "aig-lowering", + "AIG lowering to lut-mappable forms"), + clEnumValN(UntilEnd, "all", "After AIG to Comb lowering")); + +static llvm::cl::opt runUntilBefore( + "until-before", llvm::cl::desc("Stop pipeline before a specified point"), + runUntilValues, llvm::cl::init(UntilEnd), llvm::cl::cat(mainCategory)); +static llvm::cl::opt runUntilAfter( + "until-after", llvm::cl::desc("Stop pipeline after a specified point"), + runUntilValues, llvm::cl::init(UntilEnd), llvm::cl::cat(mainCategory)); + +// LUT-k parameter. This needs to be unifined to a more fine-grained target +// architecture information. +static cl::opt lutSize("lut-size", + cl::desc("Size of LUT to use for mapping"), + cl::init(6), cl::cat(mainCategory)); + +//===----------------------------------------------------------------------===// +// Main Tool Logic +//===----------------------------------------------------------------------===// + +static bool untilReached(Until until) { + return until >= runUntilBefore || until > runUntilAfter; +} + +//===----------------------------------------------------------------------===// +// Tool implementation +//===-----------------------------------------------------------------------===// + +static void populateSynthesisPipeline(PassManager &pm) { + auto &mpm = pm.nest(); + mpm.addPass(createConvertCombToAIG()); + mpm.addPass(createCSEPass()); + if (untilReached(UntilAIG)) + return; + + mpm.addPass(createSimpleCanonicalizerPass()); + mpm.addPass(createCSEPass()); + + // TODO: Add balancing, rewriting, FRAIG conversion, etc. + if (untilReached(UntilAIGOpt)) + return; + + mpm.addPass(aig::createLowerVariadicPass()); + mpm.addPass(aig::createLowerWordToBitsPass()); + mpm.addPass(createCSEPass()); + mpm.addPass(createSimpleCanonicalizerPass()); + + // TODO: Add LUT mapping, etc. + if (untilReached(UntilAIGLowering)) + return; + + mpm.addPass(createCSEPass()); + aig::GreedyCutDecompOptions options; + options.cutSizes = lutSize; + mpm.addPass(aig::createGreedyCutDecompPass(options)); + mpm.addPass(aig::createLowerCutToLUTPass()); +} + +/// This functions initializes the various components of the tool and +/// orchestrates the work to be done. +static LogicalResult executeSynthesis(MLIRContext &context) { + // Create the timing manager we use to sample execution times. + DefaultTimingManager tm; + applyDefaultTimingManagerCLOptions(tm); + auto ts = tm.getRootScope(); + + OwningOpRef module; + { + auto parserTimer = ts.nest("Parse MLIR input"); + // Parse the provided input files. + module = parseSourceFile(inputFilename, &context); + } + if (!module) + return failure(); + // Create the output directory or output file depending on our mode. + std::optional> outputFile; + std::string errorMessage; + // Create an output file. + outputFile.emplace(openOutputFile(outputFilename, &errorMessage)); + if (!outputFile.value()) { + llvm::errs() << errorMessage << "\n"; + return failure(); + } + + PassManager pm(&context); + pm.enableVerifier(verifyPasses); + pm.enableTiming(ts); + if (failed(applyPassManagerCLOptions(pm))) + return failure(); + + if (verbosePassExecutions) + pm.addInstrumentation( + std::make_unique>( + "circt-synth")); + populateSynthesisPipeline(pm); + if (failed(pm.run(module.get()))) + return failure(); + + auto timer = ts.nest("Print MLIR output"); + OpPrintingFlags printingFlags; + module->print(outputFile.value()->os(), printingFlags); + outputFile.value()->keep(); + return success(); +} + +/// The entry point for the `circt-synth` tool: +/// configures and parses the command-line options, +/// registers all dialects within a MLIR context, +/// and calls the `executeSynthesis` function to do the actual work. +int main(int argc, char **argv) { + llvm::InitLLVM y(argc, argv); + + // Hide default LLVM options, other than for this tool. + // MLIR options are added below. + cl::HideUnrelatedOptions(mainCategory); + + // Register any pass manager command line options. + registerMLIRContextCLOptions(); + registerPassManagerCLOptions(); + registerDefaultTimingManagerCLOptions(); + registerAsmPrinterCLOptions(); + cl::AddExtraVersionPrinter( + [](llvm::raw_ostream &os) { os << circt::getCirctVersion() << '\n'; }); + + // Parse the command-line options provided by the user. + cl::ParseCommandLineOptions(argc, argv, "Logic synthesis tool\n\n"); + + // Set the bug report message to indicate users should file issues on + // llvm/circt and not llvm/llvm-project. + llvm::setBugReportMsg(circt::circtBugReportMsg); + + // Register the supported CIRCT dialects and create a context to work with. + DialectRegistry registry; + registry.insert(); + MLIRContext context(registry); + + // Setup of diagnostic handling. + llvm::SourceMgr sourceMgr; + SourceMgrDiagnosticHandler sourceMgrHandler(sourceMgr, &context); + // Avoid printing a superfluous note on diagnostic emission. + context.printOpOnDiagnostic(false); + + // Perform the synthesis; using `exit` to avoid the slow + // teardown of the MLIR context. + exit(failed(executeSynthesis(context))); +} From fea2f26b201bda13be0974fd1afa6963dedb5f6f Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Sat, 26 Oct 2024 01:49:07 +0900 Subject: [PATCH 2/2] Address comments --- include/circt/Conversion/CombToAIG.h | 4 +- include/circt/Dialect/AIG/AIG.td | 5 +- include/circt/Dialect/AIG/AIGDialect.h | 2 +- include/circt/Dialect/AIG/AIGOps.h | 2 +- include/circt/Dialect/AIG/AIGOps.td | 17 +++-- include/circt/Dialect/AIG/AIGPasses.h | 6 -- include/circt/Dialect/AIG/AIGPasses.td | 12 ++-- lib/Conversion/CombToAIG/CombToAIG.cpp | 3 + lib/Dialect/AIG/AIGDialect.cpp | 3 +- lib/Dialect/AIG/AIGOps.cpp | 66 ++++++++----------- lib/Dialect/AIG/CMakeLists.txt | 3 +- .../AIG/Transforms/GreedyCutDecomp.cpp | 41 ++++-------- lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp | 20 ++---- lib/Dialect/AIG/Transforms/LowerVariadic.cpp | 23 ++----- .../AIG/Transforms/LowerWordToBits.cpp | 9 +-- test/Conversion/CombToAIG/comb-to-aig.mlir | 17 +++-- test/Dialect/AIG/canonicalizer.mlir | 13 ++-- test/Dialect/AIG/greedy-decomp.mlir | 20 +++--- test/Dialect/AIG/lower-cut-to-lut.mlir | 3 +- test/Dialect/AIG/lower-variadic.mlir | 10 +-- test/Dialect/AIG/lower-word-to-bits.mlir | 16 ++--- test/Dialect/AIG/round-trip.mlir | 15 ++++- test/circt-synth/basic.mlir | 28 ++++---- test/circt-synth/lut-size.mlir | 22 +++---- tools/circt-synth/circt-synth.cpp | 20 +++--- 25 files changed, 175 insertions(+), 205 deletions(-) diff --git a/include/circt/Conversion/CombToAIG.h b/include/circt/Conversion/CombToAIG.h index 27d529a39dc2..960414f8d107 100644 --- a/include/circt/Conversion/CombToAIG.h +++ b/include/circt/Conversion/CombToAIG.h @@ -1,4 +1,4 @@ -//===- CombToAIG.h - Comb to AIG dialect conversion ---------*- C++ -*-===// +//===- CombToAIG.h - Comb to AIG dialect conversion --------------- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -22,4 +22,4 @@ class HWModuleOp; } // namespace circt -#endif // CIRCT_CONVERSION_COMBTOARITH_H +#endif // CIRCT_CONVERSION_COMBTOAIG_H diff --git a/include/circt/Dialect/AIG/AIG.td b/include/circt/Dialect/AIG/AIG.td index 62cb778b5691..ff04617316a7 100644 --- a/include/circt/Dialect/AIG/AIG.td +++ b/include/circt/Dialect/AIG/AIG.td @@ -1,4 +1,4 @@ -//===- AIG.td - AIG Definitions ----------*- tablegen -*-===// +//===- AIG.td - AIG Definitions ----------------------------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,14 +10,11 @@ #define CIRCT_AIG_DIALECT_TD include "mlir/IR/DialectBase.td" -include "mlir/IR/OpBase.td" def AIG_Dialect : Dialect { let name = "aig"; let cppNamespace = "::circt::aig"; let summary = "Representation of AIGs"; - - let usePropertiesForAttributes = 0; } include "circt/Dialect/AIG/AIGOps.td" diff --git a/include/circt/Dialect/AIG/AIGDialect.h b/include/circt/Dialect/AIG/AIGDialect.h index acd0f712ae7a..f8622359e106 100644 --- a/include/circt/Dialect/AIG/AIGDialect.h +++ b/include/circt/Dialect/AIG/AIGDialect.h @@ -1,4 +1,4 @@ -//===- AIGDialect.h - AIG Definitions --------------------------*- C++ -*-===// +//===- AIGDialect.h - AIG Definitions ---------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/include/circt/Dialect/AIG/AIGOps.h b/include/circt/Dialect/AIG/AIGOps.h index ff36bd69919c..0a298cd71a7c 100644 --- a/include/circt/Dialect/AIG/AIGOps.h +++ b/include/circt/Dialect/AIG/AIGOps.h @@ -1,4 +1,4 @@ -//===- AIGOps.h - AIG Op Definitions ---------------------------*- C++ -*-===// +//===- AIGOps.h - AIG Op Definitions ----------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/include/circt/Dialect/AIG/AIGOps.td b/include/circt/Dialect/AIG/AIGOps.td index ca6e99da8f3d..ddfd2bb6bd9f 100644 --- a/include/circt/Dialect/AIG/AIGOps.td +++ b/include/circt/Dialect/AIG/AIGOps.td @@ -27,7 +27,7 @@ def AndInverterOp : AIGOp<"and_inv", [SameOperandsAndResultType, Pure]> { let summary = "AIG dialect AND operation"; let description = [{ The `aig.and_inv` operation represents an And-Inverter in the AIG dialect. - Unlike comb.and, operands can be inverted respectively. + Unlike `comb.and`, operands can be inverted respectively. Example: ```mlir @@ -81,27 +81,30 @@ def CutOp : AIGOp<"cut", [IsolatedFromAbove, SingleBlock]> { ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1): %0 = aig.and_inv not %arg0, %arg1 : i1 %1 = aig.and_inv %arg1, %arg3 : i1 - aig.output %0, %1 : i1 - } + aig.output %0, %1 : i1 } ``` }]; + + // TODO: Restrict to HWIntegerType. let arguments = (ins Variadic:$inputs); let results = (outs Variadic:$results); - let regions = (region SizedRegion<1>:$body); + let regions = (region SizedRegion<1>:$bodyRegion); let assemblyFormat = [{ - $inputs attr-dict `:` functional-type($inputs, $results) $body + $inputs attr-dict `:` functional-type($inputs, $results) $bodyRegion }]; let builders = [ OpBuilder<(ins CArg<"TypeRange", "{}">:$resultTypes, CArg<"ValueRange", "{}">:$inputs, - CArg<"std::function", "{}">:$ctor)> + CArg<"std::function", "{}">:$ctor)> ]; + let hasVerifier = 1; + let extraClassDeclaration = [{ - Block *getBodyBlock() { return &getBody().front(); } + Block *getBodyBlock() { return getBody(); } }]; } diff --git a/include/circt/Dialect/AIG/AIGPasses.h b/include/circt/Dialect/AIG/AIGPasses.h index 4226bc35711b..1bac79ec0979 100644 --- a/include/circt/Dialect/AIG/AIGPasses.h +++ b/include/circt/Dialect/AIG/AIGPasses.h @@ -25,12 +25,6 @@ namespace aig { #define GEN_PASS_DECL #include "circt/Dialect/AIG/AIGPasses.h.inc" -std::unique_ptr createLowerCutToLUTPass(); -std::unique_ptr createLowerVariadicPass(); -std::unique_ptr createLowerWordToBitsPass(); -std::unique_ptr -createGreedyCutDecompPass(const GreedyCutDecompOptions &options = {}); - #define GEN_PASS_REGISTRATION #include "circt/Dialect/AIG/AIGPasses.h.inc" diff --git a/include/circt/Dialect/AIG/AIGPasses.td b/include/circt/Dialect/AIG/AIGPasses.td index 8c570245c17f..368de261ee0f 100644 --- a/include/circt/Dialect/AIG/AIGPasses.td +++ b/include/circt/Dialect/AIG/AIGPasses.td @@ -1,4 +1,4 @@ -//===- ArcPasses.td - Arc dialect passes -------------------*- tablegen -*-===// +//===- AIGPasses.td - AIG dialect passes -------------------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,36 +6,32 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_ARC_ARCPASSES_TD -#define CIRCT_DIALECT_ARC_ARCPASSES_TD +#ifndef CIRCT_DIALECT_AIG_AIGPASSES_TD +#define CIRCT_DIALECT_AIG_AIGPASSES_TD include "mlir/Pass/PassBase.td" def LowerCutToLUT : Pass<"aig-lower-cut-to-lut", "hw::HWModuleOp"> { let summary = "Lower a cut to a LUT"; let dependentDialects = ["comb::CombDialect"]; - let constructor = "circt::aig::createLowerCutToLUTPass()"; } def LowerVariadic : Pass<"aig-lower-variadic", "hw::HWModuleOp"> { let summary = "Lower variadic AndInverter operations to binary AndInverter"; - let constructor = "circt::aig::createLowerVariadicPass()"; } def LowerWordToBits : Pass<"aig-lower-word-to-bits", "hw::HWModuleOp"> { let summary = "Lower multi-bit AIG operations to single-bit ones"; let dependentDialects = ["comb::CombDialect"]; - let constructor = "circt::aig::createLowerWordToBitsPass()"; } def GreedyCutDecomp : Pass<"aig-greedy-cut-decomp", "hw::HWModuleOp"> { let summary = "Decompose AIGs into k-feasible Cuts using a greedy algorithm"; let dependentDialects = ["comb::CombDialect"]; - let constructor = "circt::aig::createGreedyCutDecompPass()"; let options = [ Option<"cutSizes", "cut-sizes", "uint32_t", "6", "The sizes of the cuts to decompose">, ]; } -#endif // CIRCT_DIALECT_ARC_ARCPASSES_TD +#endif // CIRCT_DIALECT_AIG_AIGPASSES_TD diff --git a/lib/Conversion/CombToAIG/CombToAIG.cpp b/lib/Conversion/CombToAIG/CombToAIG.cpp index 6d95c831d544..ab0ccea944d4 100644 --- a/lib/Conversion/CombToAIG/CombToAIG.cpp +++ b/lib/Conversion/CombToAIG/CombToAIG.cpp @@ -65,7 +65,10 @@ struct CombXorOpConversion : OpConversionPattern { LogicalResult matchAndRewrite(XorOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + if (op.getNumOperands() != 2) + return failure(); // Xor using And with invert flags: a ^ b = (a | b) & (~a | ~b) + // (a | b) = ~(~a & ~b) // (~a | ~b) = ~(a & b) auto inputs = adaptor.getInputs(); diff --git a/lib/Dialect/AIG/AIGDialect.cpp b/lib/Dialect/AIG/AIGDialect.cpp index 0acb1e1b8b2a..ff5b0df17a7c 100644 --- a/lib/Dialect/AIG/AIGDialect.cpp +++ b/lib/Dialect/AIG/AIGDialect.cpp @@ -1,4 +1,4 @@ -//===- AIGDialect.cpp - Implement the AIG dialect -----------------------===// +//===- AIGDialect.cpp - Implement the AIG dialect -------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -12,7 +12,6 @@ #include "circt/Dialect/AIG/AIGDialect.h" #include "circt/Dialect/AIG/AIGOps.h" -#include "circt/Dialect/HW/HWOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/DialectImplementation.h" diff --git a/lib/Dialect/AIG/AIGOps.cpp b/lib/Dialect/AIG/AIGOps.cpp index 0a62bcbffb97..e0bdf9697539 100644 --- a/lib/Dialect/AIG/AIGOps.cpp +++ b/lib/Dialect/AIG/AIGOps.cpp @@ -1,4 +1,4 @@ -//===- LoopScheduleOps.cpp - LoopSchedule CIRCT Operations ------*- C++ -*-===// +//===- AIGOps.cpp - AIG Dialect Operations ----------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -101,11 +101,7 @@ mlir::ParseResult AndInverterOp::parse(mlir::OpAsmParser &parser, auto loc = parser.getCurrentLocation(); while (true) { - if (succeeded(parser.parseOptionalKeyword("not"))) { - inverts.push_back(true); - } else { - inverts.push_back(false); - } + inverts.push_back(succeeded(parser.parseOptionalKeyword("not"))); operands.push_back(OpAsmParser::UnresolvedOperand()); if (parser.parseOperand(operands.back())) @@ -114,28 +110,17 @@ mlir::ParseResult AndInverterOp::parse(mlir::OpAsmParser &parser, break; } - if (parser.parseOptionalAttrDict(result.attributes)) - return mlir::failure(); - - if (parser.parseColon()) - return mlir::failure(); - - mlir::Type resultRawType{}; - llvm::ArrayRef resultTypes(&resultRawType, 1); - - { - mlir::Type type; - if (parser.parseCustomTypeWithFallback(type)) - return mlir::failure(); - resultRawType = type; - } + mlir::Type type; + if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() || + parser.parseCustomTypeWithFallback(type)) + return failure(); - result.addTypes(resultTypes); + result.addTypes({type}); result.addAttribute("inverted", parser.getBuilder().getDenseBoolArrayAttr(inverts)); - if (parser.resolveOperands(operands, resultTypes[0], loc, result.operands)) - return mlir::failure(); - return mlir::success(); + if (parser.resolveOperands(operands, type, loc, result.operands)) + return failure(); + return success(); } void AndInverterOp::print(mlir::OpAsmPrinter &odsPrinter) { @@ -148,18 +133,9 @@ void AndInverterOp::print(mlir::OpAsmPrinter &odsPrinter) { } odsPrinter << input; }); - llvm::SmallVector elidedAttrs; - elidedAttrs.push_back("inverted"); + llvm::SmallVector elidedAttrs{"inverted"}; odsPrinter.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs); - odsPrinter << ' ' << ":"; - odsPrinter << ' '; - { - auto type = getResult().getType(); - if (auto validType = llvm::dyn_cast(type)) - odsPrinter.printStrippedAttrOrType(validType); - else - odsPrinter << type; - } + odsPrinter << " : " << getResult().getType(); } APInt AndInverterOp::evaluate(ArrayRef inputs) { @@ -178,7 +154,7 @@ APInt AndInverterOp::evaluate(ArrayRef inputs) { void CutOp::build(OpBuilder &builder, OperationState &result, TypeRange resultTypes, ValueRange inputs, - std::function ctor) { + std::function ctor) { OpBuilder::InsertionGuard guard(builder); auto *block = builder.createBlock(result.addRegion()); @@ -188,5 +164,19 @@ void CutOp::build(OpBuilder &builder, OperationState &result, block->addArgument(input.getType(), input.getLoc()); if (ctor) - ctor(); + ctor(block->getArguments()); +} + +LogicalResult CutOp::verify() { + if (getInputs().size() != getBodyBlock()->getNumArguments()) + return emitOpError("the number of inputs and the number of block arguments " + "do not match. Expected ") + << getInputs().size() << " but got " + << getBodyBlock()->getNumArguments(); + if (getNumResults() != getBodyBlock()->getTerminator()->getNumOperands()) + return emitOpError("the number of results and the number of terminator " + "operands do not match. Expected ") + << getNumResults() << " but got " + << getBodyBlock()->getTerminator()->getNumOperands(); + return success(); } diff --git a/lib/Dialect/AIG/CMakeLists.txt b/lib/Dialect/AIG/CMakeLists.txt index 6b6d4ca0766c..3f785288f30a 100644 --- a/lib/Dialect/AIG/CMakeLists.txt +++ b/lib/Dialect/AIG/CMakeLists.txt @@ -10,8 +10,7 @@ add_circt_dialect_library(CIRCTAIG CIRCTHW DEPENDS - CIRCTHW MLIRAIGIncGen ) -add_subdirectory(Transforms) \ No newline at end of file +add_subdirectory(Transforms) diff --git a/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp b/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp index 89e9cf760021..636a5560a042 100644 --- a/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp +++ b/lib/Dialect/AIG/Transforms/GreedyCutDecomp.cpp @@ -1,4 +1,4 @@ -//===- GreedyCutDecomp.cpp ---------------------------------------------===// +//===- GreedyCutDecomp.cpp ------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// // // This pass performs cut decomposition on AIGs based on a naive greedy -// algorithm. We first convert all `aig.and_inv` to `aig.cut` that has a single +// algorithm. We first convert all `aig.and_inv` to `aig.cut` that have a single // operation and then try to merge cut operations on inputs. // //===----------------------------------------------------------------------===// @@ -19,6 +19,7 @@ #include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/Transforms/DialectConversion.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" + #define DEBUG_TYPE "aig-greedy-cut-decomp" namespace circt { @@ -42,14 +43,15 @@ struct AndInverterOpToCutPattern : public OpRewritePattern { LogicalResult matchAndRewrite(aig::AndInverterOp op, PatternRewriter &rewriter) const override { - if (isa(op->getParentOp())) + if (op->getParentOfType()) return failure(); auto cutOp = rewriter.create( - op.getLoc(), op.getResult().getType(), op.getInputs(), [&]() { + op.getLoc(), op.getResult().getType(), op.getInputs(), + [&](Block::BlockArgListType args) { auto result = rewriter.create( - op.getLoc(), op.getResult().getType(), - rewriter.getBlock()->getArguments(), op.getInvertedAttr()); + op.getLoc(), op.getResult().getType(), args, + op.getInvertedAttr()); rewriter.create(op.getLoc(), ValueRange{result}); }); @@ -67,10 +69,10 @@ static aig::CutOp mergeCuts(Location loc, MutableArrayRef cuts, assert(cuts.size() >= 2); DenseMap valueToNewValue, inputsToBlockArg; - auto cutOp = - rewriter.create(loc, output.getType(), inputs, [&]() { + auto cutOp = rewriter.create( + loc, output.getType(), inputs, [&](Block::BlockArgListType args) { for (auto [i, input] : llvm::enumerate(inputs)) - inputsToBlockArg[input] = rewriter.getBlock()->getArgument(i); + inputsToBlockArg[input] = args[i]; for (auto [i, cut] : llvm::enumerate(cuts)) { auto cutOp = cast(cut); @@ -103,7 +105,7 @@ static aig::CutOp mergeCuts(Location loc, MutableArrayRef cuts, for (auto oldCut : llvm::reverse(cuts)) { auto *oldCutBlock = cast(oldCut).getBodyBlock(); auto oldCutOutput = oldCutBlock->getTerminator(); - oldCutOutput->erase(); + rewriter.eraseOp(oldCutOutput); // Erase arguments before inlining. Arguments are already replaced. oldCutBlock->eraseArguments([](BlockArgument block) { return true; }); rewriter.inlineBlockBefore(oldCutBlock, cutOp.getBodyBlock(), @@ -241,18 +243,8 @@ struct SinkConstantPattern : public mlir::OpRewritePattern { block->eraseArguments(eraseArgs); } - auto newCut = rewriter.create(op.getLoc(), op.getResultTypes(), - oldInputs, [&]() {}); - - for (auto [newArg, oldArg] : - llvm::zip(newCut.getBodyBlock()->getArguments(), oldArgs)) - rewriter.replaceAllUsesWith(oldArg, newArg); - - // Erase arguments before inlining. Arguments are already replaced. - block->eraseArguments([](BlockArgument arg) { return true; }); - rewriter.inlineBlockBefore(block, newCut.getBodyBlock(), - newCut.getBodyBlock()->begin()); - rewriter.replaceOp(op, newCut); + rewriter.modifyOpInPlace( + op, [&]() { op.getInputsMutable().assign(oldInputs); }); return success(); } }; @@ -285,8 +277,3 @@ void GreedyCutDecompPass::runOnOperation() { mlir::applyPatternsAndFoldGreedily(getOperation(), frozen, config))) return signalPassFailure(); } - -std::unique_ptr -aig::createGreedyCutDecompPass(const GreedyCutDecompOptions &options) { - return std::make_unique(options); -} diff --git a/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp b/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp index 244988f716cd..f57b44691d10 100644 --- a/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp +++ b/lib/Dialect/AIG/Transforms/LowerCutToLUT.cpp @@ -55,9 +55,9 @@ CutToLUTPattern::matchAndRewrite(CutOp cutOp, PatternRewriter &rewriter) const { uint32_t tableSize = 1 << lutWidth; DenseMap mapping; auto &body = cutOp.getBodyRegion().front(); - for (uint32_t i = 0; i < lutWidth; i++) { + for (uint32_t i = 0; i < lutWidth; ++i) { APInt value(tableSize, 0); - for (uint32_t j = 0; j < tableSize; j++) { + for (uint32_t j = 0; j < tableSize; ++j) { // Make sure the order of the bits is correct. value.setBitVal(j, (j >> i) & 1); } @@ -69,12 +69,12 @@ CutToLUTPattern::matchAndRewrite(CutOp cutOp, PatternRewriter &rewriter) const { if (auto constOp = dyn_cast(&op)) { mapping[constOp.getResult()] = APInt(tableSize, constOp.getValue().getZExtValue()); - } else if (auto AndInverterOp = dyn_cast(&op)) { + } else if (auto andInverterOp = dyn_cast(&op)) { // TODO: Avoid this copy. SmallVector inputs; - for (auto input : AndInverterOp.getInputs()) + for (auto input : andInverterOp.getInputs()) inputs.push_back(mapping[input]); - mapping[AndInverterOp.getResult()] = AndInverterOp.evaluate(inputs); + mapping[andInverterOp.getResult()] = andInverterOp.evaluate(inputs); } else if (auto outputOp = dyn_cast(&op)) { assert(outputOp.getOutputs().size() == 1 && "expected single output"); auto value = mapping.at(outputOp.getOutputs().front()); @@ -127,10 +127,8 @@ void LowerCutToLUTPass::runOnOperation() { return cutOp.emitError("expected i1 type"); } - for (auto result : cutOp.getResults()) { - if (result.getType() != i1Type) - return cutOp.emitError("expected i1 type"); - } + if (cutOp.getResult(0).getType() != i1Type) + return cutOp.emitError("expected i1 type"); uint32_t lutWidth = cutOp.getNumOperands(); if (lutWidth >= 32) @@ -153,7 +151,3 @@ void LowerCutToLUTPass::runOnOperation() { std::move(patterns)))) return signalPassFailure(); } - -std::unique_ptr aig::createLowerCutToLUTPass() { - return std::make_unique(); -} diff --git a/lib/Dialect/AIG/Transforms/LowerVariadic.cpp b/lib/Dialect/AIG/Transforms/LowerVariadic.cpp index d8821a3ae2d1..52e3ccf5bdbe 100644 --- a/lib/Dialect/AIG/Transforms/LowerVariadic.cpp +++ b/lib/Dialect/AIG/Transforms/LowerVariadic.cpp @@ -1,4 +1,4 @@ -//===- LowerVariadic.cpp ---------------------------------------------===// +//===- LowerVariadic.cpp --------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -13,9 +13,7 @@ #include "circt/Dialect/AIG/AIGOps.h" #include "circt/Dialect/AIG/AIGPasses.h" -#include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" -#include "mlir/Transforms/DialectConversion.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #define DEBUG_TYPE "aig-lower-variadic" @@ -38,7 +36,6 @@ namespace { static Value lowerFullyAssociativeOp(AndInverterOp op, OperandRange operands, ArrayRef inverts, PatternRewriter &rewriter) { - Value lhs, rhs; switch (operands.size()) { case 0: assert(0 && "cannot be called with empty operand range"); @@ -49,16 +46,14 @@ static Value lowerFullyAssociativeOp(AndInverterOp op, OperandRange operands, else return operands[0]; case 2: - lhs = operands[0]; - rhs = operands[1]; - return rewriter.create(op.getLoc(), lhs, rhs, inverts[0], - inverts[1]); + return rewriter.create(op.getLoc(), operands[0], operands[1], + inverts[0], inverts[1]); default: auto firstHalf = operands.size() / 2; - lhs = lowerFullyAssociativeOp(op, operands.take_front(firstHalf), - inverts.take_front(firstHalf), rewriter); - rhs = lowerFullyAssociativeOp(op, operands.drop_front(firstHalf), - inverts.drop_front(firstHalf), rewriter); + auto lhs = lowerFullyAssociativeOp(op, operands.take_front(firstHalf), + inverts.take_front(firstHalf), rewriter); + auto rhs = lowerFullyAssociativeOp(op, operands.drop_front(firstHalf), + inverts.drop_front(firstHalf), rewriter); return rewriter.create(op.getLoc(), lhs, rhs); } } @@ -106,7 +101,3 @@ void LowerVariadicPass::runOnOperation() { mlir::applyPatternsAndFoldGreedily(getOperation(), frozen, config))) return signalPassFailure(); } - -std::unique_ptr aig::createLowerVariadicPass() { - return std::make_unique(); -} diff --git a/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp b/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp index 3f4a49a8c09d..97c28c15707a 100644 --- a/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp +++ b/lib/Dialect/AIG/Transforms/LowerWordToBits.cpp @@ -1,4 +1,4 @@ -//===- LowerWordToBits.cpp ---------------------------------------------===// +//===- LowerWordToBits.cpp ------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -55,8 +55,7 @@ struct WordRewritePattern : public OpRewritePattern { result.push_back(andInverter); } - auto concat = rewriter.create(op.getLoc(), result); - rewriter.replaceOp(op, concat); + rewriter.replaceOpWithNewOp(op, result); return success(); } }; @@ -85,7 +84,3 @@ void LowerWordToBitsPass::runOnOperation() { config))) return signalPassFailure(); } - -std::unique_ptr aig::createLowerWordToBitsPass() { - return std::make_unique(); -} diff --git a/test/Conversion/CombToAIG/comb-to-aig.mlir b/test/Conversion/CombToAIG/comb-to-aig.mlir index de740b40e192..550bae1569b0 100644 --- a/test/Conversion/CombToAIG/comb-to-aig.mlir +++ b/test/Conversion/CombToAIG/comb-to-aig.mlir @@ -1,9 +1,16 @@ // RUN: circt-opt %s --convert-comb-to-aig | FileCheck %s // CHECK-LABEL: @test -hw.module @test(in %arg0: i32, in %arg1: i32, in %arg2: i32, in %arg3: i32, in %arg4: i1, out out: i32) { - // CHECK-NEXT: %0 = aig.and_inv not %arg0, not %arg1, not %arg2, not %arg3 : i32 - // CHECK-NEXT: %1 = aig.and_inv not %0 : i32 +hw.module @test(in %arg0: i32, in %arg1: i32, in %arg2: i32, in %arg3: i32, out out0: i32, out out1: i32, out out2: i32) { + // CHECK-NEXT: %[[OR_TMP:.+]] = aig.and_inv not %arg0, not %arg1, not %arg2, not %arg3 : i32 + // CHECK-NEXT: %[[OR:.+]] = aig.and_inv not %0 : i32 + // CHECK-NEXT: %[[AND:.+]] = aig.and_inv %arg0, %arg1, %arg2, %arg3 : i32 + // CHECK-NEXT: %[[XOR_NOT_AND:.+]] = aig.and_inv not %arg0, not %arg1 : i32 + // CHECK-NEXT: %[[XOR_AND:.+]] = aig.and_inv %arg0, %arg1 : i32 + // CHECK-NEXT: %[[XOR:.+]] = aig.and_inv not %[[XOR_NOT_AND]], not %[[XOR_AND]] : i32 + // CHECK-NEXT: hw.output %[[OR]], %[[AND]], %[[XOR]] : i32, i32, i32 %0 = comb.or %arg0, %arg1, %arg2, %arg3 : i32 - hw.output %0 : i32 -} \ No newline at end of file + %1 = comb.and %arg0, %arg1, %arg2, %arg3 : i32 + %2 = comb.xor %arg0, %arg1 : i32 + hw.output %0, %1, %2 : i32, i32, i32 +} diff --git a/test/Dialect/AIG/canonicalizer.mlir b/test/Dialect/AIG/canonicalizer.mlir index 2147673a20c6..6d00aa68ba56 100644 --- a/test/Dialect/AIG/canonicalizer.mlir +++ b/test/Dialect/AIG/canonicalizer.mlir @@ -4,22 +4,22 @@ hw.module @And(in %a: i4, in %b: i4, out o1: i4, out o2: i4, out o3: i4, out o4: i4, out o5: i4, out o6: i4, out o7: i4, out o8: i4) { - // CHECK-NEXT: %c0_i4 = hw.constant 0 : i4 // CHECK-NEXT: %c-1_i4 = hw.constant -1 : i4 + // CHECK-NEXT: %c0_i4 = hw.constant 0 : i4 // CHECK-NEXT: %c5_i4 = hw.constant 5 : i4 // CHECK-NEXT: %[[TMP1:.+]] = aig.and_inv %a, %c5_i4 : i4 // CHECK-NEXT: %[[TMP2:.+]] = aig.and_inv %a, %b : i4 // CHECK-NEXT: hw.output %c0_i4, %[[TMP1]], %a, %a, %c0_i4, %[[TMP2]], %c0_i4, %c-1_i4 : i4, i4, i4, i4, i4, i4, i4, i4 %c0 = hw.constant 0 : i4 %c2 = hw.constant 2 : i4 - %c8 = hw.constant 7 : i4 + %c7 = hw.constant 7 : i4 %c15 = hw.constant 15 : i4 // a & 0 -> 0 %0 = aig.and_inv %a, %c0 : i4 // a & 7 & ~2 -> a & 5 - %1 = aig.and_inv %a, %c8, not %c2 : i4 + %1 = aig.and_inv %a, %c7, not %c2 : i4 // a & 15 -> a %2 = aig.and_inv %a, %c15 : i4 @@ -31,12 +31,13 @@ hw.module @And(in %a: i4, in %b: i4, out o1: i4, out o2: i4, %4 = aig.and_inv %a, not %c15 : i4 // a & a & b -> a & b - %5 = aig.and_inv %a, %b : i4 + %5 = aig.and_inv %a, %a, %b : i4 // a & ~a & b -> 0 - %6 = hw.constant 0 : i4 + %6 = aig.and_inv %a, not %a, %b : i4 // 15 & 15 -> 15 - %7 = hw.constant -1 : i4 + %7 = aig.and_inv %c15, %c15 : i4 + hw.output %0, %1, %2, %3, %4, %5, %6, %7 : i4, i4, i4, i4, i4, i4, i4, i4 } diff --git a/test/Dialect/AIG/greedy-decomp.mlir b/test/Dialect/AIG/greedy-decomp.mlir index 9827b0394dea..0a5e36811215 100644 --- a/test/Dialect/AIG/greedy-decomp.mlir +++ b/test/Dialect/AIG/greedy-decomp.mlir @@ -10,17 +10,17 @@ hw.module @variadic(in %a : i1, in %b : i1, in %c : i1, in %d : i1, in %e : i1, %5 = aig.and_inv %1, %4 : i1 hw.output %5 : i1 } -// CHECK-NEXT: %0 = aig.cut %a, %b, %c : (i1, i1, i1) -> i1 { +// CHECK-NEXT: %[[RES0:.+]] = aig.cut %a, %b, %c : (i1, i1, i1) -> i1 { // CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1, %arg2: i1): -// CHECK-NEXT: %2 = aig.and_inv %arg1, %arg2 : i1 -// CHECK-NEXT: %3 = aig.and_inv %arg0, %2 : i1 -// CHECK-NEXT: aig.output %3 : i1 +// CHECK-NEXT: %[[RES2:.+]] = aig.and_inv %arg1, %arg2 : i1 +// CHECK-NEXT: %[[RES3:.+]] = aig.and_inv %arg0, %[[RES2]] : i1 +// CHECK-NEXT: aig.output %[[RES3]] : i1 // CHECK-NEXT: } -// CHECK-NEXT: %1 = aig.cut %0, %d, %e, %f, %g : (i1, i1, i1, i1, i1) -> i1 { +// CHECK-NEXT: %[[RES1:.+]] = aig.cut %0, %d, %e, %f, %g : (i1, i1, i1, i1, i1) -> i1 { // CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1, %arg4: i1): -// CHECK-NEXT: %2 = aig.and_inv %arg1, %arg2 : i1 -// CHECK-NEXT: %3 = aig.and_inv %arg3, %arg4 : i1 -// CHECK-NEXT: %4 = aig.and_inv %2, %3 : i1 -// CHECK-NEXT: %5 = aig.and_inv %arg0, %4 : i1 -// CHECK-NEXT: aig.output %5 : i1 +// CHECK-NEXT: %[[RES2:.+]] = aig.and_inv %arg1, %arg2 : i1 +// CHECK-NEXT: %[[RES3:.+]] = aig.and_inv %arg3, %arg4 : i1 +// CHECK-NEXT: %[[RES4:.+]] = aig.and_inv %[[RES2]], %[[RES3]] : i1 +// CHECK-NEXT: %[[RES5:.+]] = aig.and_inv %arg0, %[[RES4]] : i1 +// CHECK-NEXT: aig.output %[[RES5]] : i1 // CHECK-NEXT: } diff --git a/test/Dialect/AIG/lower-cut-to-lut.mlir b/test/Dialect/AIG/lower-cut-to-lut.mlir index 71e5514784c5..f15cddcfb10d 100644 --- a/test/Dialect/AIG/lower-cut-to-lut.mlir +++ b/test/Dialect/AIG/lower-cut-to-lut.mlir @@ -1,9 +1,10 @@ // RUN: circt-opt %s --aig-lower-cut-to-lut | FileCheck %s // CHECK: hw.module @Cut hw.module @Cut(in %a: i1, in %b: i1, in %c: i1, in %d: i1, out e: i1) { - // CHECK-NEXT: %0 = comb.truth_table %a, %b, %c, %d + // CHECK-NEXT: %[[RES0:.+]] = comb.truth_table %a, %b, %c, %d // CHECK-SAME: -> [false, false, false, false, false, true, true, true, // CHECK-SAME: false, false, false, false, false, false, false, false] + // CHECK-NEXT: hw.output %[[RES0]] : i1 %0 = aig.cut %a, %b, %c, %d : (i1, i1, i1, i1) -> (i1) { ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1): %1 = aig.and_inv not %arg0, not %arg1 : i1 diff --git a/test/Dialect/AIG/lower-variadic.mlir b/test/Dialect/AIG/lower-variadic.mlir index df1dca9279eb..29e755fa20fd 100644 --- a/test/Dialect/AIG/lower-variadic.mlir +++ b/test/Dialect/AIG/lower-variadic.mlir @@ -1,11 +1,11 @@ // RUN: circt-opt %s --aig-lower-variadic | FileCheck %s // CHECK: hw.module @Basic hw.module @Basic(in %a: i2, in %b: i2, in %c: i2, in %d: i2, in %e: i2, out f: i2) { - // CHECK: %0 = aig.and_inv not %a, %b : i2 - // CHECK-NEXT: %1 = aig.and_inv not %d, %e : i2 - // CHECK-NEXT: %2 = aig.and_inv %c, %1 : i2 - // CHECK-NEXT: %3 = aig.and_inv %0, %2 : i2 - // CHECK-NEXT: hw.output %3 : i2 + // CHECK: %[[RES0:.+]] = aig.and_inv not %a, %b : i2 + // CHECK-NEXT: %[[RES1:.+]] = aig.and_inv not %d, %e : i2 + // CHECK-NEXT: %[[RES2:.+]] = aig.and_inv %c, %[[RES1]] : i2 + // CHECK-NEXT: %[[RES3:.+]] = aig.and_inv %[[RES0]], %[[RES2]] : i2 + // CHECK-NEXT: hw.output %[[RES3]] : i2 %0 = aig.and_inv not %a, %b, %c, not %d, %e : i2 hw.output %0 : i2 } diff --git a/test/Dialect/AIG/lower-word-to-bits.mlir b/test/Dialect/AIG/lower-word-to-bits.mlir index 9202a8c6eb98..98d2a77dc274 100644 --- a/test/Dialect/AIG/lower-word-to-bits.mlir +++ b/test/Dialect/AIG/lower-word-to-bits.mlir @@ -1,14 +1,14 @@ // RUN: circt-opt %s --aig-lower-word-to-bits | FileCheck %s // CHECK: hw.module @Basic hw.module @Basic(in %a: i2, in %b: i2, out f: i2) { - // CHECK-NEXT: %0 = comb.extract %a from 0 : (i2) -> i1 - // CHECK-NEXT: %1 = comb.extract %b from 0 : (i2) -> i1 - // CHECK-NEXT: %2 = aig.and_inv not %0, %1 : i1 - // CHECK-NEXT: %3 = comb.extract %a from 1 : (i2) -> i1 - // CHECK-NEXT: %4 = comb.extract %b from 1 : (i2) -> i1 - // CHECK-NEXT: %5 = aig.and_inv not %3, %4 : i1 - // CHECK-NEXT: %6 = comb.concat %2, %5 : i1, i1 - // CHECK-NEXT: hw.output %6 : i2 + // CHECK-NEXT: %[[RES0:.+]] = comb.extract %a from 0 : (i2) -> i1 + // CHECK-NEXT: %[[RES1:.+]] = comb.extract %b from 0 : (i2) -> i1 + // CHECK-NEXT: %[[RES2:.+]] = aig.and_inv not %[[RES0]], %[[RES1]] : i1 + // CHECK-NEXT: %[[RES3:.+]] = comb.extract %a from 1 : (i2) -> i1 + // CHECK-NEXT: %[[RES4:.+]] = comb.extract %b from 1 : (i2) -> i1 + // CHECK-NEXT: %[[RES5:.+]] = aig.and_inv not %[[RES3]], %[[RES4]] : i1 + // CHECK-NEXT: %[[RES6:.+]] = comb.concat %[[RES2]], %[[RES5]] : i1, i1 + // CHECK-NEXT: hw.output %[[RES6]] : i2 %0 = aig.and_inv not %a, %b : i2 hw.output %0 : i2 } diff --git a/test/Dialect/AIG/round-trip.mlir b/test/Dialect/AIG/round-trip.mlir index 78f9e6bd8574..1cce29084a3e 100644 --- a/test/Dialect/AIG/round-trip.mlir +++ b/test/Dialect/AIG/round-trip.mlir @@ -1,15 +1,24 @@ // RUN: circt-opt %s | circt-opt | FileCheck %s // CHECK-LABEL: @And -// CHECK-NEXT: aig.and_inv %b, %b : i4 -// CHECK-NEXT: aig.and_inv %b, not %b : i4 -// CHECK-NEXT: aig.and_inv not %a, not %a : i1 +// CHECK-NEXT: %[[RES0:.+]] = aig.and_inv %b, %b : i4 +// CHECK-NEXT: %[[RES1:.+]] = aig.and_inv %b, not %b : i4 +// CHECK-NEXT: %[[RES2:.+]] = aig.and_inv not %a, not %a : i1 hw.module @And(in %a: i1, in %b: i4) { %0 = aig.and_inv %b, %b : i4 %1 = aig.and_inv %b, not %b : i4 %2 = aig.and_inv not %a, not %a : i1 } +// CHECK-LABEL: @Cut +// CHECK-NEXT: %[[RES:.+]]:2 = aig.cut %a, %b : (i1, i1) -> (i1, i1) { +// CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1): +// CHECK-NEXT: %[[C:.+]] = aig.and_inv %arg0, not %arg1 : i1 +// CHECK-NEXT: %[[D:.+]] = aig.and_inv %arg0, %arg1 : i1 +// CHECK-NEXT: aig.output %[[C]], %[[D]] : i1, i1 +// CHECK-NEXT: } +// CHECK-NEXT: hw.output %[[RES]]#0, %[[RES]]#1 : i1, i1 + hw.module @Cut(in %a: i1, in %b: i1, out c: i1, out d: i1) { %0, %1 = aig.cut %a, %b : (i1, i1) -> (i1, i1) { ^bb0(%arg0: i1, %arg1: i1): diff --git a/test/circt-synth/basic.mlir b/test/circt-synth/basic.mlir index 82c96020f9e3..fa287e70771d 100644 --- a/test/circt-synth/basic.mlir +++ b/test/circt-synth/basic.mlir @@ -3,34 +3,37 @@ // CHECK-LABEL: @and hw.module @and(in %a: i1, in %b: i1, out and: i1) { %0 = comb.and %a, %b : i1 - // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, false, false, true] + // CHECK-NEXT: %[[RESULT:.+]] = comb.truth_table %a, %b -> [false, false, false, true] + // CHECK-NEXT: hw.output %[[RESULT:.+]] : i1 hw.output %0 : i1 } // CHECK-LABEL: @or hw.module @or(in %a: i1, in %b: i1, out or: i1) { %0 = comb.or %a, %b : i1 - // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, true, true, true] + // CHECK-NEXT: %[[RESULT:.+]] = comb.truth_table %a, %b -> [false, true, true, true] + // CHECK-NEXT: hw.output %[[RESULT:.+]] : i1 hw.output %0 : i1 } // CHECK-LABEL: @xor hw.module @xor(in %a: i1, in %b: i1, out xor: i1) { %0 = comb.xor %a, %b : i1 - // CHECK-NEXT: %0 = comb.truth_table %a, %b -> [false, true, true, false] + // CHECK-NEXT: %[[RESULT:.+]] = comb.truth_table %a, %b -> [false, true, true, false] + // CHECK-NEXT: hw.output %[[RESULT:.+]] : i1 hw.output %0 : i1 } // CHECK-LABEL: @multibit hw.module @multibit(in %a: i2, in %b: i2, out and: i2) { %0 = comb.and %a, %b : i2 - // CHCK-NEXT: %0 = comb.extract %a from 0 : (i2) -> i1 - // CHCK-NEXT: %1 = comb.extract %b from 0 : (i2) -> i1 - // CHCK-NEXT: %2 = comb.truth_table %0, %1 -> [false, false, false, true] - // CHCK-NEXT: %3 = comb.extract %a from 1 : (i2) -> i1 - // CHCK-NEXT: %4 = comb.extract %b from 1 : (i2) -> i1 - // CHCK-NEXT: %5 = comb.truth_table %3, %4 -> [false, false, false, true] - // CHCK-NEXT: %6 = comb.concat %2, %5 : i1, i1 + // CHECK-NEXT: %[[EXTRACT0:.+]] = comb.extract %a from 0 : (i2) -> i1 + // CHECK-NEXT: %[[EXTRACT1:.+]] = comb.extract %b from 0 : (i2) -> i1 + // CHECK-NEXT: %[[LUT0:.+]] = comb.truth_table %[[EXTRACT0]], %[[EXTRACT1]] -> [false, false, false, true] + // CHECK-NEXT: %[[EXTRACT2:.+]] = comb.extract %a from 1 : (i2) -> i1 + // CHECK-NEXT: %[[EXTRACT3:.+]] = comb.extract %b from 1 : (i2) -> i1 + // CHECK-NEXT: %[[LUT1:.+]] = comb.truth_table %[[EXTRACT2]], %[[EXTRACT3]] -> [false, false, false, true] + // CHECK-NEXT: %[[CONCAT:.+]] = comb.concat %[[LUT0]], %[[LUT1]] : i1, i1 hw.output %0 : i2 } @@ -38,9 +41,10 @@ hw.module @multibit(in %a: i2, in %b: i2, out and: i2) { hw.module @variadic(in %a: i1, in %b: i1, in %c: i1, in %d: i1, in %e: i1, in %f: i1, out and6: i1) { %0 = comb.and %a, %b, %c, %d, %e, %f : i1 - // CHECK-NEXT: %0 = comb.truth_table %a, %b, %c, %d, %e, %f -> [ + // CHECK-NEXT: %[[RESULT:.+]] = comb.truth_table %a, %b, %c, %d, %e, %f -> [ // CHECK-COUNT-63: false // CHECK-SAME: true // CHECK-SAME: ] + // CHECK-NEXT: hw.output %[[RESULT:.+]] : i1 hw.output %0 : i1 -} \ No newline at end of file +} diff --git a/test/circt-synth/lut-size.mlir b/test/circt-synth/lut-size.mlir index 222d8d947089..0bed5afd3367 100644 --- a/test/circt-synth/lut-size.mlir +++ b/test/circt-synth/lut-size.mlir @@ -7,17 +7,17 @@ // LUT-6-LABEL: @variadic hw.module @variadic(in %a: i1, in %b: i1, in %c: i1, in %d: i1, in %e: i1, in %f: i1, out and6: i1) { - // LUT-2-NEXT: %0 = comb.truth_table %b, %c -> [false, false, false, true] - // LUT-2-NEXT: %1 = comb.truth_table %a, %0 -> [false, false, false, true] - // LUT-2-NEXT: %2 = comb.truth_table %e, %f -> [false, false, false, true] - // LUT-2-NEXT: %3 = comb.truth_table %d, %2 -> [false, false, false, true] - // LUT-2-NEXT: %4 = comb.truth_table %1, %3 -> [false, false, false, true] - // LUT-2-NEXT: hw.output %4 - // LUT-4-NEXT: %0 = comb.truth_table %a, %b, %c -> [false, false, false, false, false, false, false, true] - // LUT-4-NEXT: %1 = comb.truth_table %0, %d, %e, %f -> [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true] - // LUT-4-NEXT: hw.output %1 - // LUT-6-NEXT: %0 = comb.truth_table - // LUT-6-NEXT: hw.output %0 + // LUT-2-NEXT: %[[LUT0:.+]] = comb.truth_table %b, %c -> [false, false, false, true] + // LUT-2-NEXT: %[[LUT1:.+]] = comb.truth_table %a, %[[LUT0]] -> [false, false, false, true] + // LUT-2-NEXT: %[[LUT2:.+]] = comb.truth_table %e, %f -> [false, false, false, true] + // LUT-2-NEXT: %[[LUT3:.+]] = comb.truth_table %d, %[[LUT2]] -> [false, false, false, true] + // LUT-2-NEXT: %[[LUT4:.+]] = comb.truth_table %[[LUT1]], %[[LUT3]] -> [false, false, false, true] + // LUT-2-NEXT: hw.output %[[LUT4]] + // LUT-4-NEXT: %[[LUT0:.+]] = comb.truth_table %a, %b, %c -> [false, false, false, false, false, false, false, true] + // LUT-4-NEXT: %[[LUT1:.+]] = comb.truth_table %[[LUT0]], %d, %e, %f -> [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true] + // LUT-4-NEXT: hw.output %[[LUT1]] + // LUT-6-NEXT: %[[LUT0:.+]] = comb.truth_table + // LUT-6-NEXT: hw.output %[[LUT0]] %0 = comb.and %a, %b, %c, %d, %e, %f : i1 hw.output %0 : i1 diff --git a/tools/circt-synth/circt-synth.cpp b/tools/circt-synth/circt-synth.cpp index b08050b230a3..ee19b582d2a4 100644 --- a/tools/circt-synth/circt-synth.cpp +++ b/tools/circt-synth/circt-synth.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// /// -/// This file initiliazes the 'circt-synth' tool, which performs logic +/// This file initializes the 'circt-synth' tool, which performs logic /// synthesis. Currently, it only performs backend-agnostic FPGA synthesis, /// mapping core dialects into FPGA-specific primitives, such as LUTs. /// @@ -44,7 +44,7 @@ using namespace circt; static cl::OptionCategory mainCategory("circt-synth Options"); -static cl::opt inputFilename(cl::Positional, cl::Required, +static cl::opt inputFilename(cl::Positional, cl::init("-"), cl::desc("Specify an input file"), cl::value_desc("filename"), cl::cat(mainCategory)); @@ -80,8 +80,8 @@ static llvm::cl::opt runUntilAfter( "until-after", llvm::cl::desc("Stop pipeline after a specified point"), runUntilValues, llvm::cl::init(UntilEnd), llvm::cl::cat(mainCategory)); -// LUT-k parameter. This needs to be unifined to a more fine-grained target -// architecture information. +// LUT-k parameter. This needs to be extended to provide more fine-grained +// target architecture information. static cl::opt lutSize("lut-size", cl::desc("Size of LUT to use for mapping"), cl::init(6), cl::cat(mainCategory)); @@ -96,7 +96,7 @@ static bool untilReached(Until until) { //===----------------------------------------------------------------------===// // Tool implementation -//===-----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// static void populateSynthesisPipeline(PassManager &pm) { auto &mpm = pm.nest(); @@ -112,8 +112,8 @@ static void populateSynthesisPipeline(PassManager &pm) { if (untilReached(UntilAIGOpt)) return; - mpm.addPass(aig::createLowerVariadicPass()); - mpm.addPass(aig::createLowerWordToBitsPass()); + mpm.addPass(aig::createLowerVariadic()); + mpm.addPass(aig::createLowerWordToBits()); mpm.addPass(createCSEPass()); mpm.addPass(createSimpleCanonicalizerPass()); @@ -124,11 +124,11 @@ static void populateSynthesisPipeline(PassManager &pm) { mpm.addPass(createCSEPass()); aig::GreedyCutDecompOptions options; options.cutSizes = lutSize; - mpm.addPass(aig::createGreedyCutDecompPass(options)); - mpm.addPass(aig::createLowerCutToLUTPass()); + mpm.addPass(aig::createGreedyCutDecomp(options)); + mpm.addPass(aig::createLowerCutToLUT()); } -/// This functions initializes the various components of the tool and +/// This function initializes the various components of the tool and /// orchestrates the work to be done. static LogicalResult executeSynthesis(MLIRContext &context) { // Create the timing manager we use to sample execution times.