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

View File

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

View File

@ -4,4 +4,5 @@ add_subdirectory(ExportVerilog)
add_subdirectory(Dialect)
add_subdirectory(Firtool)
add_subdirectory(RtgTool)
add_subdirectory(Synthesis)
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(Scheduling)
add_subdirectory(Support)
add_subdirectory(Synthesis)
add_subdirectory(Target)
add_subdirectory(Tools)
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
CIRCTAIG
CIRCTAIGToComb
CIRCTAIGTransforms
CIRCTAIGAnalysis
CIRCTComb
CIRCTCombToAIG
CIRCTDebug
CIRCTEmit
CIRCTHW
CIRCTHWTransforms
CIRCTLTL
CIRCTOM
CIRCTSeq
CIRCTSim
CIRCTSupport
CIRCTSV
CIRCTSynthesis
CIRCTTransforms
CIRCTVerif
MLIRBytecodeWriter

View File

@ -12,7 +12,6 @@
//===----------------------------------------------------------------------===//
#include "circt/Conversion/AIGToComb.h"
#include "circt/Conversion/CombToAIG.h"
#include "circt/Dialect/AIG/AIGDialect.h"
#include "circt/Dialect/AIG/AIGPasses.h"
#include "circt/Dialect/AIG/Analysis/LongestPathAnalysis.h"
@ -22,7 +21,6 @@
#include "circt/Dialect/Emit/EmitDialect.h"
#include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWPasses.h"
#include "circt/Dialect/LTL/LTLDialect.h"
#include "circt/Dialect/OM/OMDialect.h"
#include "circt/Dialect/SV/SVDialect.h"
@ -31,12 +29,14 @@
#include "circt/Dialect/Verif/VerifDialect.h"
#include "circt/Support/Passes.h"
#include "circt/Support/Version.h"
#include "circt/Synthesis/SynthesisPipeline.h"
#include "circt/Transforms/Passes.h"
#include "mlir/Bytecode/BytecodeWriter.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/OwningOpRef.h"
#include "mlir/Parser/Parser.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Pass/PassRegistry.h"
#include "mlir/Support/FileUtilities.h"
#include "mlir/Support/LogicalResult.h"
#include "mlir/Transforms/Passes.h"
@ -51,6 +51,7 @@ namespace cl = llvm::cl;
using namespace mlir;
using namespace circt;
using namespace synthesis;
//===----------------------------------------------------------------------===//
// Command-line options declaration
@ -90,7 +91,6 @@ static cl::opt<bool>
cl::desc("Allow unknown dialects in the input"),
cl::init(false), cl::cat(mainCategory));
// Options to control early-out from pipeline.
enum Until { UntilAIGLowering, UntilEnd };
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));
static cl::opt<bool>
continueOnFailure("ignore-abc-failures",
ignoreAbcFailures("ignore-abc-failures",
cl::desc("Continue on ABC failure instead of aborting"),
cl::init(false), cl::cat(mainCategory));
@ -152,6 +152,17 @@ static bool untilReached(Until until) {
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
//===----------------------------------------------------------------------===//
@ -161,60 +172,24 @@ static void partiallyLegalizeCombToAIG(SmallVectorImpl<std::string> &ops) {
(ops.push_back(AllowedOpTy::getOperationName().str()), ...);
}
static void populateSynthesisPipeline(PassManager &pm) {
auto pipeline = [](OpPassManager &mpm) {
// Add the AIG to Comb at the scope exit if requested.
auto addAIGToComb = llvm::make_scope_exit([&]() {
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());
// Add a default synthesis pipeline and analysis.
static void populateCIRCTSynthPipeline(PassManager &pm) {
auto pipeline = [](OpPassManager &pm) {
circt::synthesis::buildAIGLoweringPipeline(pm);
if (untilReached(UntilAIGLowering))
return;
mpm.addPass(createSimpleCanonicalizerPass());
mpm.addPass(createCSEPass());
mpm.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.
mpm.addPass(aig::createLowerWordToBits());
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;
circt::synthesis::AIGOptimizationPipelineOptions options;
options.abcCommands = abcCommands;
options.abcPath.setValue(abcPath);
options.ignoreAbcFailures.setValue(ignoreAbcFailures);
circt::synthesis::buildAIGOptimizationPipeline(pm, options);
};
if (topName.empty()) {
pipeline(pm.nest<hw::HWModuleOp>());
} else {
pm.addPass(circt::createHierarchicalRunner(topName, pipeline));
}
nestOrAddToHierarchicalRunner(pm, pipeline, topName);
// Run analysis if requested.
if (!outputLongestPath.empty()) {
circt::aig::PrintLongestPathAnalysisOptions options;
options.outputFile = outputLongestPath;
@ -223,7 +198,14 @@ static void populateSynthesisPipeline(PassManager &pm) {
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.
@ -285,7 +267,7 @@ static LogicalResult executeSynthesis(MLIRContext &context) {
pm.addInstrumentation(
std::make_unique<VerbosePassInstrumentation<mlir::ModuleOp>>(
"circt-synth"));
populateSynthesisPipeline(pm);
populateCIRCTSynthPipeline(pm);
if (!topName.empty()) {
// Set a top module name for the longest path analysis.
@ -320,6 +302,7 @@ int main(int argc, char **argv) {
registerPassManagerCLOptions();
registerDefaultTimingManagerCLOptions();
registerAsmPrinterCLOptions();
cl::AddExtraVersionPrinter(
[](llvm::raw_ostream &os) { os << circt::getCirctVersion() << '\n'; });