[circt-synth] [Synthesis] Add synthesis pipeline and refactor the lib structure (#8681)

This commit introduces a standardized synthesis pipeline and restructures the codebase:

* Creates a new SynthesisPipeline class to define the default synthesis pipeline. This pipeline serves both circt-synth and is exposed through the C API for Python bindings.
* Added a dedicated Synthesis directory under `lib/` to house synthesis-related code. This architectural change is aimed to promote synthesis capabilities to a first-class component within CIRCT rather than limiting it to the circt-synth tool.
This commit is contained in:
Hideto Ueno 2025-07-10 16:16:11 -07:00 committed by GitHub
parent dd7b402d96
commit 1fb38a58ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 292 additions and 59 deletions

View File

@ -0,0 +1,24 @@
//===----------------------------------------------------------------------===//
//
// 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_C_SYNTHESIS_H
#define CIRCT_C_SYNTHESIS_H
#include "mlir-c/IR.h"
#ifdef __cplusplus
extern "C" {
#endif
MLIR_CAPI_EXPORTED void registerSynthesisPipeline(void);
#ifdef __cplusplus
}
#endif
#endif // CIRCT_C_SYNTHESIS_H

View File

@ -0,0 +1,58 @@
//===----------------------------------------------------------------------===//
//
// 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 default synthesis pipeline.
//
//===----------------------------------------------------------------------===//
#ifndef LIB_SYNTHESIS_SYNTHESISPIPELINE_H
#define LIB_SYNTHESIS_SYNTHESISPIPELINE_H
#include "circt-c/Synthesis.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Pass/PassOptions.h"
#include <string>
#include <vector>
//===----------------------------------------------------------------------===//
// Pipeline Options
//===----------------------------------------------------------------------===//
namespace circt {
namespace synthesis {
/// Options for the aig optimization pipeline.
struct AIGOptimizationPipelineOptions
: public mlir::PassPipelineOptions<AIGOptimizationPipelineOptions> {
PassOptions::ListOption<std::string> abcCommands{
*this, "abc-commands", llvm::cl::desc("ABC passes to run")};
PassOptions::Option<std::string> abcPath{
*this, "abc-path", llvm::cl::desc("Path to ABC"), llvm::cl::init("abc")};
PassOptions::Option<bool> ignoreAbcFailures{
*this, "ignore-abc-failures",
llvm::cl::desc("Continue on ABC failure instead of aborting"),
llvm::cl::init(false)};
};
//===----------------------------------------------------------------------===//
// Pipeline Functions
//===----------------------------------------------------------------------===//
/// Populate the synthesis pipelines.
void buildAIGLoweringPipeline(mlir::OpPassManager &pm);
void buildAIGOptimizationPipeline(
mlir::OpPassManager &pm, const AIGOptimizationPipelineOptions &options);
/// Register the synthesis pipelines.
void registerSynthesisPipeline();
} // namespace synthesis
} // namespace circt
#endif // LIB_SYNTHESIS_SYNTHESISPIPELINE_H

View File

@ -0,0 +1,36 @@
# REQUIRES: bindings_python
# RUN: %PYTHON% %s | FileCheck %s
import circt
from circt.dialects import hw, comb
from circt.ir import Context, Location, Module, InsertionPoint, IntegerType
from circt.passmanager import PassManager
with Context() as ctx, Location.unknown():
circt.register_dialects(ctx)
m = Module.create()
with InsertionPoint(m.body):
i4 = IntegerType.get_signless(4)
# Create a module with comb.mul
def build_module(module):
a, b = module.entry_block.arguments
hw.OutputOp([comb.mul([a, b])])
hw.HWModuleOp(
name="foo",
input_ports=[("a", i4), ("b", i4)],
output_ports=[("out", i4)],
body_builder=build_module,
)
# Check that the synthesis pipeline is registered.
pm = PassManager.parse(
"builtin.module(hw.module(synthesis-aig-lowering-pipeline, "
"synthesis-aig-optimization-pipeline))")
pm.run(m.operation)
# CHECK: hw.module @foo(
# CHECK-NOT: comb.mul
# CHECK: aig.and_inv
print(m.operation)

View File

@ -26,6 +26,7 @@
#include "circt-c/Dialect/OM.h" #include "circt-c/Dialect/OM.h"
#include "circt-c/Dialect/Pipeline.h" #include "circt-c/Dialect/Pipeline.h"
#include "circt-c/Dialect/RTG.h" #include "circt-c/Dialect/RTG.h"
#include "circt-c/Synthesis.h"
#include "circt-c/Transforms.h" #include "circt-c/Transforms.h"
#ifdef CIRCT_INCLUDE_TESTS #ifdef CIRCT_INCLUDE_TESTS
#include "circt-c/Dialect/RTGTest.h" #include "circt-c/Dialect/RTGTest.h"
@ -63,6 +64,7 @@ static void registerPasses() {
registerHandshakePasses(); registerHandshakePasses();
registerKanagawaPasses(); registerKanagawaPasses();
registerPipelinePasses(); registerPipelinePasses();
registerSynthesisPipeline();
mlirRegisterCIRCTConversionPasses(); mlirRegisterCIRCTConversionPasses();
mlirRegisterCIRCTTransformsPasses(); mlirRegisterCIRCTTransformsPasses();
mlirRegisterTransformsCSE(); mlirRegisterTransformsCSE();

View File

@ -55,6 +55,7 @@ set(PYTHON_BINDINGS_LINK_LIBS
CIRCTCAPISV CIRCTCAPISV
CIRCTCAPIVerif CIRCTCAPIVerif
CIRCTCAPITransforms CIRCTCAPITransforms
CIRCTCAPISynthesis
MLIRCAPIIndex MLIRCAPIIndex
MLIRCAPISMT MLIRCAPISMT
MLIRCAPIExportSMTLIB MLIRCAPIExportSMTLIB

View File

@ -4,4 +4,5 @@ add_subdirectory(ExportVerilog)
add_subdirectory(Dialect) add_subdirectory(Dialect)
add_subdirectory(Firtool) add_subdirectory(Firtool)
add_subdirectory(RtgTool) add_subdirectory(RtgTool)
add_subdirectory(Synthesis)
add_subdirectory(Transforms) add_subdirectory(Transforms)

View File

@ -0,0 +1,7 @@
add_circt_public_c_api_library(CIRCTCAPISynthesis
SynthesisPipeline.cpp
LINK_LIBS PUBLIC
CIRCTSynthesis
MLIRCAPIIR
)

View File

@ -0,0 +1,15 @@
//===----------------------------------------------------------------------===//
//
// 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-c/Synthesis.h"
#include "circt/Synthesis/SynthesisPipeline.h"
void registerSynthesisPipeline() {
circt::synthesis::registerSynthesisPipeline();
}

View File

@ -11,6 +11,7 @@ add_subdirectory(Firtool)
add_subdirectory(Reduce) add_subdirectory(Reduce)
add_subdirectory(Scheduling) add_subdirectory(Scheduling)
add_subdirectory(Support) add_subdirectory(Support)
add_subdirectory(Synthesis)
add_subdirectory(Target) add_subdirectory(Target)
add_subdirectory(Tools) add_subdirectory(Tools)
add_subdirectory(Transforms) add_subdirectory(Transforms)

View File

@ -0,0 +1,13 @@
add_circt_library(CIRCTSynthesis
SynthesisPipeline.cpp
LINK_LIBS PUBLIC
CIRCTAIGTransforms
CIRCTCombToAIG
CIRCTHWTransforms
CIRCTSupport
MLIRIR
MLIRSupport
MLIRTransforms
)

View File

@ -0,0 +1,95 @@
//===----------------------------------------------------------------------===//
//
// 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 default synthesis pipeline from core dialect to AIG.
//
//===----------------------------------------------------------------------===//
#include "circt/Synthesis/SynthesisPipeline.h"
#include "circt/Conversion/CombToAIG.h"
#include "circt/Dialect/AIG/AIGPasses.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWPasses.h"
#include "circt/Support/Passes.h"
#include "circt/Transforms/Passes.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/SmallVector.h"
using namespace mlir;
using namespace circt;
using namespace circt::synthesis;
//===----------------------------------------------------------------------===//
// Pipeline Implementation
//===----------------------------------------------------------------------===//
/// Helper function to populate additional legal ops for partial legalization.
template <typename... AllowedOpTy>
static void partiallyLegalizeCombToAIG(SmallVectorImpl<std::string> &ops) {
(ops.push_back(AllowedOpTy::getOperationName().str()), ...);
}
void circt::synthesis::buildAIGLoweringPipeline(OpPassManager &pm) {
{
// Partially legalize Comb to AIG, run CSE and canonicalization.
circt::ConvertCombToAIGOptions convOptions;
partiallyLegalizeCombToAIG<comb::AndOp, comb::OrOp, comb::XorOp,
comb::MuxOp, comb::ICmpOp, hw::ArrayGetOp,
hw::ArraySliceOp, hw::ArrayCreateOp,
hw::ArrayConcatOp, hw::AggregateConstantOp>(
convOptions.additionalLegalOps);
pm.addPass(circt::createConvertCombToAIG(convOptions));
}
pm.addPass(createCSEPass());
pm.addPass(createSimpleCanonicalizerPass());
pm.addPass(circt::hw::createHWAggregateToCombPass());
pm.addPass(circt::createConvertCombToAIG());
pm.addPass(createCSEPass());
pm.addPass(createSimpleCanonicalizerPass());
pm.addPass(createCSEPass());
}
void circt::synthesis::buildAIGOptimizationPipeline(
OpPassManager &pm, const AIGOptimizationPipelineOptions &options) {
pm.addPass(aig::createLowerVariadic());
// TODO: LowerWordToBits is not scalable for large designs. Change to
// conditionally enable the pass once the rest of the pipeline was able
// to handle multibit operands properly.
pm.addPass(aig::createLowerWordToBits());
pm.addPass(createCSEPass());
pm.addPass(createSimpleCanonicalizerPass());
if (!options.abcCommands.empty()) {
aig::ABCRunnerOptions abcOptions;
abcOptions.abcPath = options.abcPath;
abcOptions.abcCommands.assign(options.abcCommands.begin(),
options.abcCommands.end());
abcOptions.continueOnFailure = options.ignoreAbcFailures;
pm.addPass(aig::createABCRunner(abcOptions));
}
// TODO: Add balancing, rewriting, FRAIG conversion, etc.
}
//===----------------------------------------------------------------------===//
// Pipeline Registration
//===----------------------------------------------------------------------===//
void circt::synthesis::registerSynthesisPipeline() {
PassPipelineRegistration<EmptyPipelineOptions>(
"synthesis-aig-lowering-pipeline",
"The default pipeline for until AIG lowering", buildAIGLoweringPipeline);
PassPipelineRegistration<AIGOptimizationPipelineOptions>(
"synthesis-aig-optimization-pipeline",
"The default pipeline for AIG optimization pipeline",
buildAIGOptimizationPipeline);
}

View File

@ -3,20 +3,17 @@ target_link_libraries(circt-synth
PRIVATE PRIVATE
CIRCTAIG CIRCTAIG
CIRCTAIGToComb CIRCTAIGToComb
CIRCTAIGTransforms
CIRCTAIGAnalysis CIRCTAIGAnalysis
CIRCTComb CIRCTComb
CIRCTCombToAIG
CIRCTDebug CIRCTDebug
CIRCTEmit CIRCTEmit
CIRCTHW CIRCTHW
CIRCTHWTransforms
CIRCTLTL CIRCTLTL
CIRCTOM CIRCTOM
CIRCTSeq CIRCTSeq
CIRCTSim CIRCTSim
CIRCTSupport
CIRCTSV CIRCTSV
CIRCTSynthesis
CIRCTTransforms CIRCTTransforms
CIRCTVerif CIRCTVerif
MLIRBytecodeWriter MLIRBytecodeWriter

View File

@ -12,7 +12,6 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "circt/Conversion/AIGToComb.h" #include "circt/Conversion/AIGToComb.h"
#include "circt/Conversion/CombToAIG.h"
#include "circt/Dialect/AIG/AIGDialect.h" #include "circt/Dialect/AIG/AIGDialect.h"
#include "circt/Dialect/AIG/AIGPasses.h" #include "circt/Dialect/AIG/AIGPasses.h"
#include "circt/Dialect/AIG/Analysis/LongestPathAnalysis.h" #include "circt/Dialect/AIG/Analysis/LongestPathAnalysis.h"
@ -22,7 +21,6 @@
#include "circt/Dialect/Emit/EmitDialect.h" #include "circt/Dialect/Emit/EmitDialect.h"
#include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWPasses.h"
#include "circt/Dialect/LTL/LTLDialect.h" #include "circt/Dialect/LTL/LTLDialect.h"
#include "circt/Dialect/OM/OMDialect.h" #include "circt/Dialect/OM/OMDialect.h"
#include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/SV/SVDialect.h"
@ -31,12 +29,14 @@
#include "circt/Dialect/Verif/VerifDialect.h" #include "circt/Dialect/Verif/VerifDialect.h"
#include "circt/Support/Passes.h" #include "circt/Support/Passes.h"
#include "circt/Support/Version.h" #include "circt/Support/Version.h"
#include "circt/Synthesis/SynthesisPipeline.h"
#include "circt/Transforms/Passes.h" #include "circt/Transforms/Passes.h"
#include "mlir/Bytecode/BytecodeWriter.h" #include "mlir/Bytecode/BytecodeWriter.h"
#include "mlir/IR/Diagnostics.h" #include "mlir/IR/Diagnostics.h"
#include "mlir/IR/OwningOpRef.h" #include "mlir/IR/OwningOpRef.h"
#include "mlir/Parser/Parser.h" #include "mlir/Parser/Parser.h"
#include "mlir/Pass/PassManager.h" #include "mlir/Pass/PassManager.h"
#include "mlir/Pass/PassRegistry.h"
#include "mlir/Support/FileUtilities.h" #include "mlir/Support/FileUtilities.h"
#include "mlir/Support/LogicalResult.h" #include "mlir/Support/LogicalResult.h"
#include "mlir/Transforms/Passes.h" #include "mlir/Transforms/Passes.h"
@ -51,6 +51,7 @@ namespace cl = llvm::cl;
using namespace mlir; using namespace mlir;
using namespace circt; using namespace circt;
using namespace synthesis;
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Command-line options declaration // Command-line options declaration
@ -90,7 +91,6 @@ static cl::opt<bool>
cl::desc("Allow unknown dialects in the input"), cl::desc("Allow unknown dialects in the input"),
cl::init(false), cl::cat(mainCategory)); cl::init(false), cl::cat(mainCategory));
// Options to control early-out from pipeline.
enum Until { UntilAIGLowering, UntilEnd }; enum Until { UntilAIGLowering, UntilEnd };
static auto runUntilValues = llvm::cl::values( static auto runUntilValues = llvm::cl::values(
@ -140,7 +140,7 @@ static cl::opt<std::string> abcPath("abc-path", cl::desc("Path to ABC"),
cl::cat(mainCategory)); cl::cat(mainCategory));
static cl::opt<bool> static cl::opt<bool>
continueOnFailure("ignore-abc-failures", ignoreAbcFailures("ignore-abc-failures",
cl::desc("Continue on ABC failure instead of aborting"), cl::desc("Continue on ABC failure instead of aborting"),
cl::init(false), cl::cat(mainCategory)); cl::init(false), cl::cat(mainCategory));
@ -152,6 +152,17 @@ static bool untilReached(Until until) {
return until >= runUntilBefore || until > runUntilAfter; return until >= runUntilBefore || until > runUntilAfter;
} }
static void
nestOrAddToHierarchicalRunner(OpPassManager &pm,
std::function<void(OpPassManager &pm)> pipeline,
const std::string &topName) {
if (topName.empty()) {
pipeline(pm.nest<hw::HWModuleOp>());
} else {
pm.addPass(circt::createHierarchicalRunner(topName, pipeline));
}
}
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Tool implementation // Tool implementation
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -161,60 +172,24 @@ static void partiallyLegalizeCombToAIG(SmallVectorImpl<std::string> &ops) {
(ops.push_back(AllowedOpTy::getOperationName().str()), ...); (ops.push_back(AllowedOpTy::getOperationName().str()), ...);
} }
static void populateSynthesisPipeline(PassManager &pm) { // Add a default synthesis pipeline and analysis.
auto pipeline = [](OpPassManager &mpm) { static void populateCIRCTSynthPipeline(PassManager &pm) {
// Add the AIG to Comb at the scope exit if requested. auto pipeline = [](OpPassManager &pm) {
auto addAIGToComb = llvm::make_scope_exit([&]() { circt::synthesis::buildAIGLoweringPipeline(pm);
if (convertToComb) {
mpm.addPass(circt::createConvertAIGToComb());
mpm.addPass(createCSEPass());
}
});
{
// Partially legalize Comb to AIG, run CSE and canonicalization.
circt::ConvertCombToAIGOptions options;
partiallyLegalizeCombToAIG<comb::AndOp, comb::OrOp, comb::XorOp,
comb::MuxOp, comb::ICmpOp, hw::ArrayGetOp,
hw::ArraySliceOp, hw::ArrayCreateOp,
hw::ArrayConcatOp, hw::AggregateConstantOp>(
options.additionalLegalOps);
mpm.addPass(circt::createConvertCombToAIG(options));
}
mpm.addPass(createCSEPass());
mpm.addPass(createSimpleCanonicalizerPass());
mpm.addPass(circt::hw::createHWAggregateToCombPass());
mpm.addPass(circt::createConvertCombToAIG());
mpm.addPass(createCSEPass());
if (untilReached(UntilAIGLowering)) if (untilReached(UntilAIGLowering))
return; return;
mpm.addPass(createSimpleCanonicalizerPass());
mpm.addPass(createCSEPass()); circt::synthesis::AIGOptimizationPipelineOptions options;
mpm.addPass(aig::createLowerVariadic()); options.abcCommands = abcCommands;
// TODO: LowerWordToBits is not scalable for large designs. Change to options.abcPath.setValue(abcPath);
// conditionally enable the pass once the rest of the pipeline was able options.ignoreAbcFailures.setValue(ignoreAbcFailures);
// to handle multibit operands properly.
mpm.addPass(aig::createLowerWordToBits()); circt::synthesis::buildAIGOptimizationPipeline(pm, options);
mpm.addPass(createCSEPass());
mpm.addPass(createSimpleCanonicalizerPass());
if (!abcCommands.empty()) {
aig::ABCRunnerOptions options;
options.abcPath = abcPath;
options.abcCommands.assign(abcCommands.begin(), abcCommands.end());
options.continueOnFailure = continueOnFailure;
mpm.addPass(aig::createABCRunner(options));
}
// TODO: Add balancing, rewriting, FRAIG conversion, etc.
if (untilReached(UntilEnd))
return;
}; };
if (topName.empty()) { nestOrAddToHierarchicalRunner(pm, pipeline, topName);
pipeline(pm.nest<hw::HWModuleOp>());
} else {
pm.addPass(circt::createHierarchicalRunner(topName, pipeline));
}
// Run analysis if requested.
if (!outputLongestPath.empty()) { if (!outputLongestPath.empty()) {
circt::aig::PrintLongestPathAnalysisOptions options; circt::aig::PrintLongestPathAnalysisOptions options;
options.outputFile = outputLongestPath; options.outputFile = outputLongestPath;
@ -223,7 +198,14 @@ static void populateSynthesisPipeline(PassManager &pm) {
pm.addPass(circt::aig::createPrintLongestPathAnalysis(options)); pm.addPass(circt::aig::createPrintLongestPathAnalysis(options));
} }
// TODO: Add LUT mapping, etc. if (convertToComb)
nestOrAddToHierarchicalRunner(
pm,
[&](OpPassManager &pm) {
pm.addPass(circt::createConvertAIGToComb());
pm.addPass(createCSEPass());
},
topName);
} }
/// Check output stream before writing bytecode to it. /// Check output stream before writing bytecode to it.
@ -285,7 +267,7 @@ static LogicalResult executeSynthesis(MLIRContext &context) {
pm.addInstrumentation( pm.addInstrumentation(
std::make_unique<VerbosePassInstrumentation<mlir::ModuleOp>>( std::make_unique<VerbosePassInstrumentation<mlir::ModuleOp>>(
"circt-synth")); "circt-synth"));
populateSynthesisPipeline(pm); populateCIRCTSynthPipeline(pm);
if (!topName.empty()) { if (!topName.empty()) {
// Set a top module name for the longest path analysis. // Set a top module name for the longest path analysis.
@ -320,6 +302,7 @@ int main(int argc, char **argv) {
registerPassManagerCLOptions(); registerPassManagerCLOptions();
registerDefaultTimingManagerCLOptions(); registerDefaultTimingManagerCLOptions();
registerAsmPrinterCLOptions(); registerAsmPrinterCLOptions();
cl::AddExtraVersionPrinter( cl::AddExtraVersionPrinter(
[](llvm::raw_ostream &os) { os << circt::getCirctVersion() << '\n'; }); [](llvm::raw_ostream &os) { os << circt::getCirctVersion() << '\n'; });