mirror of https://github.com/llvm/circt.git
471 lines
17 KiB
C++
471 lines
17 KiB
C++
//===- kanagawatool.cpp - utility for working with the Kanagawa 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 'kanagawatool', which composes together a variety of
|
|
// CIRCT libraries that can be used to realise an Kanagawa-based lowering flow.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
|
|
#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
|
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
|
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
|
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
|
#include "mlir/IR/AsmState.h"
|
|
#include "mlir/IR/BuiltinOps.h"
|
|
#include "mlir/Parser/Parser.h"
|
|
#include "mlir/Pass/Pass.h"
|
|
#include "mlir/Pass/PassInstrumentation.h"
|
|
#include "mlir/Pass/PassManager.h"
|
|
#include "mlir/Support/FileUtilities.h"
|
|
#include "mlir/Support/Timing.h"
|
|
#include "mlir/Support/ToolUtilities.h"
|
|
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
|
|
#include "mlir/Transforms/Passes.h"
|
|
|
|
#include "llvm/Support/Chrono.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/InitLLVM.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/SourceMgr.h"
|
|
#include "llvm/Support/ToolOutputFile.h"
|
|
|
|
#include "circt/Conversion/ExportVerilog.h"
|
|
#include "circt/Conversion/Passes.h"
|
|
#include "circt/Dialect/DC/DCDialect.h"
|
|
#include "circt/Dialect/DC/DCPasses.h"
|
|
#include "circt/Dialect/ESI/ESIDialect.h"
|
|
#include "circt/Dialect/ESI/ESIPasses.h"
|
|
#include "circt/Dialect/Kanagawa/KanagawaDialect.h"
|
|
#include "circt/Dialect/Kanagawa/KanagawaOps.h"
|
|
#include "circt/Dialect/Kanagawa/KanagawaPassPipelines.h"
|
|
#include "circt/Dialect/Kanagawa/KanagawaPasses.h"
|
|
#include "circt/Dialect/Pipeline/PipelineDialect.h"
|
|
#include "circt/Dialect/Pipeline/PipelinePasses.h"
|
|
#include "circt/Dialect/SV/SVDialect.h"
|
|
#include "circt/Dialect/SV/SVPasses.h"
|
|
#include "circt/Dialect/Seq/SeqDialect.h"
|
|
#include "circt/Dialect/Seq/SeqPasses.h"
|
|
#include "circt/Support/LoweringOptions.h"
|
|
#include "circt/Support/LoweringOptionsParser.h"
|
|
#include "circt/Support/Version.h"
|
|
#include "circt/Transforms/Passes.h"
|
|
|
|
using namespace llvm;
|
|
using namespace mlir;
|
|
using namespace circt;
|
|
using namespace kanagawa;
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Tool options
|
|
// --------------------------------------------------------------------------
|
|
|
|
static cl::OptionCategory mainCategory("kanagawatool Options");
|
|
|
|
static cl::opt<std::string> inputFilename(cl::Positional,
|
|
cl::desc("<input file>"),
|
|
cl::init("-"), cl::cat(mainCategory));
|
|
|
|
static cl::opt<std::string> outputFilename(
|
|
"o", cl::desc("Output filename, or directory for split output"),
|
|
cl::value_desc("filename"), cl::init("-"), cl::cat(mainCategory));
|
|
|
|
static cl::opt<bool>
|
|
splitInputFile("split-input-file",
|
|
cl::desc("Split the input file into pieces and process each "
|
|
"chunk independently"),
|
|
cl::init(false), cl::Hidden, cl::cat(mainCategory));
|
|
|
|
static cl::opt<bool>
|
|
verifyDiagnostics("verify-diagnostics",
|
|
cl::desc("Check that emitted diagnostics match "
|
|
"expected-* lines on the corresponding line"),
|
|
cl::init(false), cl::Hidden, cl::cat(mainCategory));
|
|
|
|
static cl::opt<bool>
|
|
verbosePassExecutions("verbose-pass-executions",
|
|
cl::desc("Log executions of toplevel module passes"),
|
|
cl::init(false), cl::cat(mainCategory));
|
|
|
|
static cl::opt<bool>
|
|
verifyPasses("verify-each",
|
|
cl::desc("Run the verifier after each transformation pass"),
|
|
cl::init(true), cl::cat(mainCategory));
|
|
|
|
static cl::opt<bool>
|
|
allowUnregisteredDialects("allow-unregistered-dialects",
|
|
cl::desc("Allow unknown dialects in the input"),
|
|
cl::init(false), cl::Hidden,
|
|
cl::cat(mainCategory));
|
|
|
|
enum OutputFormatKind {
|
|
OutputLoweredKanagawa,
|
|
OutputIR,
|
|
OutputVerilog,
|
|
OutputSplitVerilog
|
|
};
|
|
|
|
static cl::opt<OutputFormatKind> outputFormat(
|
|
cl::desc("Specify output format:"),
|
|
cl::values(
|
|
clEnumValN(OutputLoweredKanagawa, "post-kanagawa-ir",
|
|
"Emit IR after Kanagawa constructs have been lowered away"),
|
|
clEnumValN(OutputIR, "ir", "Emit pre-emission IR"),
|
|
clEnumValN(OutputVerilog, "verilog", "Emit Verilog"),
|
|
clEnumValN(OutputSplitVerilog, "split-verilog",
|
|
"Emit Verilog (one file per module; specify "
|
|
"directory with -o=<dir>)")),
|
|
cl::init(OutputVerilog), cl::cat(mainCategory));
|
|
|
|
static cl::opt<bool>
|
|
traceIVerilog("sv-trace-iverilog",
|
|
cl::desc("Add tracing to an iverilog simulated module"),
|
|
cl::init(false), cl::cat(mainCategory));
|
|
|
|
enum FlowKind { HiKanagawa, LoKanagawa };
|
|
static cl::opt<FlowKind> flowKind(
|
|
cl::desc("Specify flow kind:"),
|
|
cl::values(clEnumValN(HiKanagawa, "hi", "High-level Kanagawa flow"),
|
|
clEnumValN(LoKanagawa, "lo", "Low-level Kanagawa flow")),
|
|
cl::Required, cl::cat(mainCategory));
|
|
|
|
static LoweringOptionsOption loweringOptions(mainCategory);
|
|
|
|
// --------------------------------------------------------------------------
|
|
// (Configurable) pass pipelines
|
|
// --------------------------------------------------------------------------
|
|
|
|
/// Create a simple canonicalizer pass.
|
|
static std::unique_ptr<Pass> createSimpleCanonicalizerPass() {
|
|
mlir::GreedyRewriteConfig config;
|
|
config.setUseTopDownTraversal(true);
|
|
config.setRegionSimplificationLevel(
|
|
mlir::GreedySimplifyRegionLevel::Disabled);
|
|
return mlir::createCanonicalizerPass(config);
|
|
}
|
|
|
|
static void loadHighLevelControlflowTransformsPipeline(OpPassManager &pm) {
|
|
pm.addPass(mlir::createLowerAffinePass());
|
|
pm.addPass(mlir::createSCFToControlFlowPass());
|
|
pm.addPass(createSimpleCanonicalizerPass());
|
|
}
|
|
|
|
static void loadHandshakeTransformsPipeline(OpPassManager &pm) {
|
|
// Make the CFG a binary tree by inserting merge blocks.
|
|
pm.addPass(circt::createInsertMergeBlocksPass());
|
|
|
|
// Perform dataflow conversion
|
|
pm.nest<kanagawa::DesignOp>().nest<kanagawa::ClassOp>().addPass(
|
|
kanagawa::createConvertCFToHandshakePass());
|
|
// Canonicalize - necessary after handshake conversion to clean up a lot of
|
|
// stuff e.g. simple branches.
|
|
pm.addPass(createSimpleCanonicalizerPass());
|
|
pm.nest<handshake::FuncOp>().addPass(
|
|
handshake::createHandshakeInsertBuffersPass("all", 2));
|
|
}
|
|
|
|
static void loadDCTransformsPipeline(OpPassManager &pm) {
|
|
pm.nest<kanagawa::DesignOp>().nest<ClassOp>().addPass(
|
|
kanagawa::createConvertHandshakeToDCPass());
|
|
pm.addPass(createSimpleCanonicalizerPass());
|
|
pm.nest<kanagawa::DesignOp>()
|
|
.nest<ClassOp>()
|
|
.nest<DataflowMethodOp>()
|
|
.addPass(dc::createDCMaterializeForksSinksPass());
|
|
// pm.nest<ClassOp>().addPass(circt::createDCToHWPass());
|
|
}
|
|
|
|
static void loadESILoweringPipeline(OpPassManager &pm) {
|
|
pm.addPass(circt::esi::createESIPortLoweringPass());
|
|
pm.addPass(circt::esi::createESIPhysicalLoweringPass());
|
|
pm.addPass(circt::esi::createESItoHWPass());
|
|
}
|
|
|
|
static void loadHWLoweringPipeline(OpPassManager &pm) {
|
|
pm.addPass(createSimpleCanonicalizerPass());
|
|
pm.nest<hw::HWModuleOp>().addPass(circt::seq::createLowerSeqHLMemPass());
|
|
pm.addPass(seq::createHWMemSimImplPass());
|
|
pm.addPass(circt::createLowerSeqToSVPass());
|
|
pm.nest<hw::HWModuleOp>().addPass(sv::createHWCleanupPass());
|
|
pm.addPass(mlir::createCSEPass());
|
|
|
|
// Legalize unsupported operations within the modules.
|
|
pm.nest<hw::HWModuleOp>().addPass(sv::createHWLegalizeModulesPass());
|
|
pm.addPass(createSimpleCanonicalizerPass());
|
|
|
|
// Tidy up the IR to improve verilog emission quality.
|
|
auto &modulePM = pm.nest<hw::HWModuleOp>();
|
|
modulePM.addPass(sv::createPrettifyVerilogPass());
|
|
}
|
|
|
|
static void loadSchedulingPipeline(OpPassManager &pm) {
|
|
// Inject operator library
|
|
pm.addPass(kanagawa::createAddOperatorLibraryPass());
|
|
|
|
// Map any arith operators to comb
|
|
pm.nest<kanagawa::DesignOp>()
|
|
.nest<kanagawa::ClassOp>()
|
|
.nest<kanagawa::DataflowMethodOp>()
|
|
.nest<kanagawa::IsolatedStaticBlockOp>()
|
|
.addPass(circt::createMapArithToCombPass());
|
|
|
|
// Prepare for scheduling
|
|
pm.nest<kanagawa::DesignOp>()
|
|
.nest<kanagawa::ClassOp>()
|
|
.nest<kanagawa::DataflowMethodOp>()
|
|
.nest<kanagawa::IsolatedStaticBlockOp>()
|
|
.addPass(kanagawa::createPrepareSchedulingPass());
|
|
|
|
// Schedule!
|
|
pm.nest<kanagawa::DesignOp>()
|
|
.nest<kanagawa::ClassOp>()
|
|
.nest<kanagawa::DataflowMethodOp>()
|
|
.nest<kanagawa::IsolatedStaticBlockOp>()
|
|
.addPass(pipeline::createScheduleLinearPipelinePass());
|
|
}
|
|
|
|
static void loadPipelineLoweringPipeline(OpPassManager &pm) {
|
|
pm.addPass(pipeline::createExplicitRegsPass());
|
|
pm.addPass(createPipelineToHWPass());
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Tool driver code
|
|
// --------------------------------------------------------------------------
|
|
|
|
static void loadLowLevelPassPipeline(
|
|
PassManager &pm, ModuleOp module,
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
|
loadPipelineLoweringPipeline(pm);
|
|
loadESILoweringPipeline(pm);
|
|
loadHWLoweringPipeline(pm);
|
|
if (traceIVerilog)
|
|
pm.addPass(circt::sv::createSVTraceIVerilogPass());
|
|
|
|
if (loweringOptions.getNumOccurrences())
|
|
loweringOptions.setAsAttribute(module);
|
|
if (outputFormat == OutputVerilog) {
|
|
pm.addPass(createExportVerilogPass((*outputFile)->os()));
|
|
} else if (outputFormat == OutputSplitVerilog) {
|
|
pm.addPass(createExportSplitVerilogPass(outputFilename));
|
|
}
|
|
}
|
|
|
|
static void loadKanagawaHiFlow(
|
|
PassManager &pm, ModuleOp module,
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
|
if (verbosePassExecutions)
|
|
llvm::errs() << "[kanagawatool] Will run high-level Kanagawa flow\n";
|
|
|
|
loadHighLevelControlflowTransformsPipeline(pm);
|
|
loadKanagawaHighLevelPassPipeline(pm);
|
|
if (outputFormat != OutputLoweredKanagawa) {
|
|
loadHandshakeTransformsPipeline(pm);
|
|
loadSchedulingPipeline(pm);
|
|
loadDCTransformsPipeline(pm);
|
|
if (outputFormat != OutputLoweredKanagawa)
|
|
loadLowLevelPassPipeline(pm, module, outputFile);
|
|
}
|
|
}
|
|
|
|
static void loadKanagawaLoFlow(
|
|
PassManager &pm, ModuleOp module,
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
|
if (verbosePassExecutions)
|
|
llvm::errs() << "[kanagawatool] Will run low-level Kanagawa flow\n";
|
|
loadKanagawaLowLevelPassPipeline(pm);
|
|
|
|
if (outputFormat != OutputLoweredKanagawa)
|
|
loadLowLevelPassPipeline(pm, module, outputFile);
|
|
}
|
|
|
|
/// Process a single buffer of the input.
|
|
static LogicalResult processBuffer(
|
|
MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
|
// Parse the input.
|
|
mlir::OwningOpRef<mlir::ModuleOp> module;
|
|
llvm::sys::TimePoint<> parseStartTime;
|
|
if (verbosePassExecutions) {
|
|
llvm::errs() << "[kanagawatool] Running MLIR parser\n";
|
|
parseStartTime = llvm::sys::TimePoint<>::clock::now();
|
|
}
|
|
auto parserTimer = ts.nest("MLIR Parser");
|
|
module = parseSourceFile<ModuleOp>(sourceMgr, &context);
|
|
|
|
if (!module)
|
|
return failure();
|
|
|
|
if (verbosePassExecutions) {
|
|
auto elpased = std::chrono::duration<double>(
|
|
llvm::sys::TimePoint<>::clock::now() - parseStartTime) /
|
|
std::chrono::seconds(1);
|
|
llvm::errs() << "[kanagawatool] -- Done in "
|
|
<< llvm::format("%.3f", elpased) << " sec\n";
|
|
}
|
|
|
|
// Apply any pass manager command line options.
|
|
PassManager pm(&context);
|
|
pm.enableVerifier(verifyPasses);
|
|
pm.enableTiming(ts);
|
|
if (failed(applyPassManagerCLOptions(pm)))
|
|
return failure();
|
|
|
|
pm.addPass(createSimpleCanonicalizerPass());
|
|
if (flowKind == HiKanagawa)
|
|
loadKanagawaHiFlow(pm, module.get(), outputFile);
|
|
else if (flowKind == LoKanagawa)
|
|
loadKanagawaLoFlow(pm, module.get(), outputFile);
|
|
|
|
// Go execute!
|
|
if (failed(pm.run(module.get())))
|
|
return failure();
|
|
if (outputFormat != OutputVerilog || outputFormat == OutputSplitVerilog)
|
|
module->print((*outputFile)->os());
|
|
|
|
return success();
|
|
|
|
// We intentionally "leak" the Module into the MLIRContext instead of
|
|
// deallocating it. There is no need to deallocate it right before process
|
|
// exit.
|
|
(void)module.release();
|
|
return success();
|
|
}
|
|
|
|
/// Process a single split of the input. This allocates a source manager and
|
|
/// creates a regular or verifying diagnostic handler, depending on whether
|
|
/// the user set the verifyDiagnostics option.
|
|
static LogicalResult processInputSplit(
|
|
MLIRContext &context, TimingScope &ts,
|
|
std::unique_ptr<llvm::MemoryBuffer> buffer,
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
|
llvm::SourceMgr sourceMgr;
|
|
sourceMgr.AddNewSourceBuffer(std::move(buffer), llvm::SMLoc());
|
|
if (!verifyDiagnostics) {
|
|
SourceMgrDiagnosticHandler sourceMgrHandler(sourceMgr, &context);
|
|
return processBuffer(context, ts, sourceMgr, outputFile);
|
|
}
|
|
|
|
SourceMgrDiagnosticVerifierHandler sourceMgrHandler(sourceMgr, &context);
|
|
context.printOpOnDiagnostic(false);
|
|
(void)processBuffer(context, ts, sourceMgr, outputFile);
|
|
return sourceMgrHandler.verify();
|
|
}
|
|
|
|
/// Process the entire input provided by the user, splitting it up if the
|
|
/// corresponding option was specified.
|
|
static LogicalResult
|
|
processInput(MLIRContext &context, TimingScope &ts,
|
|
std::unique_ptr<llvm::MemoryBuffer> input,
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
|
if (!splitInputFile)
|
|
return processInputSplit(context, ts, std::move(input), outputFile);
|
|
|
|
return splitAndProcessBuffer(
|
|
std::move(input),
|
|
[&](std::unique_ptr<MemoryBuffer> buffer, raw_ostream &) {
|
|
return processInputSplit(context, ts, std::move(buffer), outputFile);
|
|
},
|
|
llvm::outs());
|
|
}
|
|
|
|
static LogicalResult executeKanagawatool(MLIRContext &context) {
|
|
if (allowUnregisteredDialects)
|
|
context.allowUnregisteredDialects();
|
|
|
|
// Create the timing manager we use to sample execution times.
|
|
DefaultTimingManager tm;
|
|
applyDefaultTimingManagerCLOptions(tm);
|
|
auto ts = tm.getRootScope();
|
|
|
|
// Set up the input file.
|
|
std::string errorMessage;
|
|
auto input = openInputFile(inputFilename, &errorMessage);
|
|
if (!input) {
|
|
llvm::errs() << errorMessage << "\n";
|
|
return failure();
|
|
}
|
|
|
|
std::optional<std::unique_ptr<llvm::ToolOutputFile>> outputFile;
|
|
if (outputFormat != OutputSplitVerilog) {
|
|
outputFile.emplace(openOutputFile(outputFilename, &errorMessage));
|
|
if (!*outputFile) {
|
|
llvm::errs() << errorMessage << "\n";
|
|
return failure();
|
|
}
|
|
}
|
|
|
|
// Process the input.
|
|
if (failed(processInput(context, ts, std::move(input), outputFile)))
|
|
return failure();
|
|
|
|
// If the result succeeded and we're emitting a file, close it.
|
|
if (outputFile.has_value())
|
|
(*outputFile)->keep();
|
|
|
|
return success();
|
|
}
|
|
|
|
/// Main driver for kanagawatool command. This sets up LLVM and MLIR, and
|
|
/// parses command line options before passing off to 'executeKanagawatool'.
|
|
/// This is set up so we can `exit(0)` at the end of the program to avoid
|
|
/// teardown of the MLIRContext and modules inside of it (reducing compile
|
|
/// time).
|
|
int main(int argc, char **argv) {
|
|
InitLLVM y(argc, argv);
|
|
|
|
// Set the bug report message to indicate users should file issues on
|
|
// llvm/circt and not llvm/llvm-project.
|
|
setBugReportMsg(circtBugReportMsg);
|
|
|
|
// 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();
|
|
|
|
// Parse pass names in main to ensure static initialization completed.
|
|
cl::ParseCommandLineOptions(argc, argv, "CIRCT Kanagawa tool\n");
|
|
|
|
DialectRegistry registry;
|
|
// Register MLIR dialects.
|
|
registry.insert<mlir::memref::MemRefDialect>();
|
|
registry.insert<mlir::func::FuncDialect>();
|
|
registry.insert<mlir::arith::ArithDialect>();
|
|
registry.insert<mlir::cf::ControlFlowDialect>();
|
|
registry.insert<mlir::scf::SCFDialect>();
|
|
|
|
// Register MLIR passes.
|
|
mlir::registerCSEPass();
|
|
mlir::registerSCCPPass();
|
|
mlir::registerInlinerPass();
|
|
mlir::registerCanonicalizerPass();
|
|
|
|
// Register CIRCT dialects.
|
|
registry
|
|
.insert<hw::HWDialect, comb::CombDialect, seq::SeqDialect, sv::SVDialect,
|
|
handshake::HandshakeDialect, kanagawa::KanagawaDialect,
|
|
dc::DCDialect, esi::ESIDialect, pipeline::PipelineDialect>();
|
|
|
|
// Do the guts of the kanagawatool process.
|
|
MLIRContext context(registry);
|
|
auto result = executeKanagawatool(context);
|
|
|
|
// Use "exit" instead of return'ing to signal completion. This avoids
|
|
// invoking the MLIRContext destructor, which spends a bunch of time
|
|
// deallocating memory etc which process exit will do for us.
|
|
exit(failed(result));
|
|
}
|