[Ibis] Add ibistool (#6206)

Adds `ibistool` - a tool for driving Ibis lowerings. The tool has two
modes - low-level and high-level Ibis lowering.
Alongside this, introduce a set of Ibis pass pipelines which other users
may load to ensure that they're lowering ibis constructs in the
standard order.
This commit is contained in:
Morten Borup Petersen 2023-09-28 10:14:20 +02:00 committed by GitHub
parent c8ab1e1163
commit 6b73873825
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 729 additions and 16 deletions

View File

@ -0,0 +1,29 @@
//===- IbisPassPipelines.h - Ibis pass pipelines -----------------*- 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_IBIS_IBISPASSPIPELINES_H
#define CIRCT_DIALECT_IBIS_IBISPASSPIPELINES_H
#include "circt/Dialect/Ibis/IbisPasses.h"
#include "mlir/Pass/PassManager.h"
#include <memory>
#include <optional>
namespace circt {
namespace ibis {
// Loads a pass pipeline to transform low-level Ibis constructs.
void loadIbisLowLevelPassPipeline(mlir::PassManager &pm);
// Loads a pass pipeline to transform high-level Ibis constructs.
void loadIbisHighLevelPassPipeline(mlir::PassManager &pm);
} // namespace ibis
} // namespace circt
#endif // CIRCT_DIALECT_IBIS_IBISPASSPIPELINES_H

View File

@ -21,16 +21,17 @@ namespace ibis {
#define GEN_PASS_DECL_IBISTUNNELING #define GEN_PASS_DECL_IBISTUNNELING
#include "circt/Dialect/Ibis/IbisPasses.h.inc" #include "circt/Dialect/Ibis/IbisPasses.h.inc"
std::unique_ptr<Pass> createCallPrepPass(); std::unique_ptr<mlir::Pass> createCallPrepPass();
std::unique_ptr<Pass> createContainerizePass(); std::unique_ptr<mlir::Pass> createContainerizePass();
std::unique_ptr<Pass> createTunnelingPass(const IbisTunnelingOptions & = {}); std::unique_ptr<mlir::Pass>
std::unique_ptr<Pass> createPortrefLoweringPass(); createTunnelingPass(const IbisTunnelingOptions & = {});
std::unique_ptr<Pass> createCleanSelfdriversPass(); std::unique_ptr<mlir::Pass> createPortrefLoweringPass();
std::unique_ptr<Pass> createContainersToHWPass(); std::unique_ptr<mlir::Pass> createCleanSelfdriversPass();
std::unique_ptr<Pass> createArgifyBlocksPass(); std::unique_ptr<mlir::Pass> createContainersToHWPass();
std::unique_ptr<Pass> createReblockPass(); std::unique_ptr<mlir::Pass> createArgifyBlocksPass();
std::unique_ptr<Pass> createInlineSBlocksPass(); std::unique_ptr<mlir::Pass> createReblockPass();
std::unique_ptr<Pass> createConvertCFToHandshakePass(); std::unique_ptr<mlir::Pass> createInlineSBlocksPass();
std::unique_ptr<mlir::Pass> createConvertCFToHandshakePass();
/// Generate the code for registering passes. /// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION #define GEN_PASS_REGISTRATION

View File

@ -10,6 +10,7 @@ set(CIRCT_INTEGRATION_TEST_DEPENDS
esi-collateral esi-collateral
firtool firtool
hlstool hlstool
ibistool
handshake-runner handshake-runner
) )

View File

@ -1,8 +1,4 @@
// RUN: circt-opt --ibis-containerize --ibis-tunneling --ibis-lower-portrefs \ // RUN: ibistool -lo %s
// RUN: --canonicalize --ibis-clean-selfdrivers --canonicalize \
// RUN: --ibis-convert-containers-to-hw --pipeline-explicit-regs \
// RUN: --lower-pipeline-to-hw --lower-seq-to-sv \
// RUN: --export-verilog -o %t_lo.mlir %s > %t.sv
// A class hierarchy with a shared parent, and accessing between the children // A class hierarchy with a shared parent, and accessing between the children

View File

@ -9,6 +9,7 @@ add_circt_dialect_library(CIRCTIbisTransforms
IbisReblockPass.cpp IbisReblockPass.cpp
IbisInlineSBlocksPass.cpp IbisInlineSBlocksPass.cpp
IbisConvertCFToHandshake.cpp IbisConvertCFToHandshake.cpp
IbisPassPipelines.cpp
DEPENDS DEPENDS
CIRCTIbisTransformsIncGen CIRCTIbisTransformsIncGen

View File

@ -0,0 +1,61 @@
//===- IbisPassPipelines.cpp - Ibis pass pipelines ------------------------===//
//
// 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/Dialect/Ibis/IbisPassPipelines.h"
#include "circt/Dialect/Ibis/IbisOps.h"
#include "circt/Transforms/Passes.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "mlir/Transforms/Passes.h"
using namespace mlir;
using namespace circt;
using namespace ibis;
/// Create a simple canonicalizer pass.
static std::unique_ptr<Pass> createSimpleCanonicalizerPass() {
mlir::GreedyRewriteConfig config;
config.useTopDownTraversal = true;
config.enableRegionSimplification = false;
return mlir::createCanonicalizerPass(config);
}
void circt::ibis::loadIbisLowLevelPassPipeline(mlir::PassManager &pm) {
pm.addPass(createContainerizePass());
pm.addPass(createTunnelingPass(IbisTunnelingOptions{"", ""}));
pm.addPass(createPortrefLoweringPass());
pm.addPass(createSimpleCanonicalizerPass());
pm.addPass(createCleanSelfdriversPass());
pm.addPass(createContainersToHWPass());
}
void circt::ibis::loadIbisHighLevelPassPipeline(mlir::PassManager &pm) {
pm.nest<ibis::MethodOp>().addPass(ibis::createInlineSBlocksPass());
pm.addPass(mlir::createMem2Reg());
// TODO @mortbopet: Add a verification pass to ensure that there are no more
// memref.alloca's - we want all memories to be mem2reg'able, unless they are
// member variable accesses.
// - just add it as an illegal op.
// Now, perform SSA maximizations.
pm.addPass(circt::createMaximizeSSAPass());
// SSA maximal form achieved. Reconstruct the Ibis sblocks.
pm.nest<ibis::MethodOp>().addPass(ibis::createReblockPass());
pm.addPass(ibis::createArgifyBlocksPass());
pm.addPass(createSimpleCanonicalizerPass());
// Make the CFG a binary tree by inserting merge blocks.
pm.addPass(circt::createInsertMergeBlocksPass());
// Perform dataflow conversion
pm.nest<ibis::ClassOp>().addPass(ibis::createConvertCFToHandshakePass());
// Canonicalize - necessary after handshake conversion to clean up a lot of
// stuff e.g. simple branches.
pm.addPass(createSimpleCanonicalizerPass());
}

View File

@ -29,6 +29,7 @@ set(CIRCT_TEST_DEPENDS
handshake-runner handshake-runner
firtool firtool
hlstool hlstool
ibistool
om-linker om-linker
) )

View File

@ -0,0 +1,66 @@
// RUN: ibistool --hi --post-ibis-ir %s | FileCheck %s
// CHECK-LABEL: ibis.class @ToHandshake {
// CHECK: %[[VAL_0:.*]] = ibis.this @ToHandshake
// CHECK: ibis.method.df @foo(%[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: none) -> (i32, none) {
// CHECK: %[[VAL_5:.*]] = handshake.constant %[[VAL_4]] {value = 2 : i32} : i32
// CHECK: %[[VAL_6:.*]] = handshake.constant %[[VAL_4]] {value = 1 : index} : index
// CHECK: %[[VAL_7:.*]] = handshake.constant %[[VAL_4]] {value = 0 : i32} : i32
// CHECK: %[[VAL_8:.*]] = handshake.buffer [1] seq %[[VAL_9:.*]] {initValues = [0]} : i1
// CHECK: %[[VAL_10:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_4]], %[[VAL_11:.*]]] : i1, none
// CHECK: %[[VAL_12:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_1]], %[[VAL_13:.*]]] : i1, index
// CHECK: %[[VAL_14:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_7]], %[[VAL_15:.*]]] : i1, i32
// CHECK: %[[VAL_16:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_2]], %[[VAL_17:.*]]] : i1, index
// CHECK: %[[VAL_18:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_5]], %[[VAL_19:.*]]] : i1, i32
// CHECK: %[[VAL_20:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_6]], %[[VAL_21:.*]]] : i1, index
// CHECK: %[[VAL_22:.*]] = handshake.mux %[[VAL_8]] {{\[}}%[[VAL_7]], %[[VAL_23:.*]]] : i1, i32
// CHECK: %[[VAL_9]] = arith.cmpi slt, %[[VAL_12]], %[[VAL_16]] : index
// CHECK: %[[VAL_24:.*]], %[[VAL_25:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_12]] : index
// CHECK: %[[VAL_26:.*]], %[[VAL_27:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_14]] : i32
// CHECK: %[[VAL_17]], %[[VAL_28:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_16]] : index
// CHECK: %[[VAL_19]], %[[VAL_29:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_18]] : i32
// CHECK: %[[VAL_21]], %[[VAL_30:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_20]] : index
// CHECK: %[[VAL_23]], %[[VAL_31:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_22]] : i32
// CHECK: %[[VAL_32:.*]], %[[VAL_33:.*]] = handshake.cond_br %[[VAL_9]], %[[VAL_10]] : none
// CHECK: %[[VAL_34:.*]] = arith.index_cast %[[VAL_24]] : index to i32
// CHECK: %[[VAL_35:.*]] = arith.remsi %[[VAL_26]], %[[VAL_19]] : i32
// CHECK: %[[VAL_36:.*]] = arith.cmpi eq, %[[VAL_35]], %[[VAL_23]] : i32
// CHECK: %[[VAL_37:.*]], %[[VAL_38:.*]] = handshake.cond_br %[[VAL_36]], %[[VAL_26]] : i32
// CHECK: %[[VAL_39:.*]], %[[VAL_40:.*]] = handshake.cond_br %[[VAL_36]], %[[VAL_32]] : none
// CHECK: %[[VAL_41:.*]], %[[VAL_42:.*]] = handshake.cond_br %[[VAL_36]], %[[VAL_34]] : i32
// CHECK: %[[VAL_43:.*]] = arith.addi %[[VAL_37]], %[[VAL_41]] : i32
// CHECK: %[[VAL_44:.*]] = arith.subi %[[VAL_38]], %[[VAL_42]] : i32
// CHECK: %[[VAL_15]] = handshake.mux %[[VAL_45:.*]] {{\[}}%[[VAL_44]], %[[VAL_43]]] : index, i32
// CHECK: %[[VAL_11]], %[[VAL_45]] = handshake.control_merge %[[VAL_40]], %[[VAL_39]] : none, index
// CHECK: %[[VAL_13]] = arith.addi %[[VAL_24]], %[[VAL_21]] : index
// CHECK: ibis.return %[[VAL_27]], %[[VAL_33]] : i32, none
// CHECK: }
// CHECK: }
ibis.class @ToHandshake {
%this = ibis.this @ToHandshake
ibis.method @foo(%a: index, %b: index, %c : i1) -> i32 {
%sum = memref.alloca () : memref<i32>
%c0_i32 = arith.constant 0 : i32
memref.store %c0_i32, %sum[] : memref<i32>
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : i32
%c0 = arith.constant 0 : i32
scf.for %i = %a to %b step %c1 {
%acc = memref.load %sum[] : memref<i32>
%i_i32 = arith.index_cast %i : index to i32
%rem = arith.remsi %acc, %c2 : i32
%cond = arith.cmpi eq, %rem, %c0 : i32
%res = scf.if %cond -> (i32) {
%v = arith.addi %acc, %i_i32 : i32
scf.yield %v : i32
} else {
%v = arith.subi %acc, %i_i32 : i32
scf.yield %v : i32
}
memref.store %res, %sum[] : memref<i32>
}
%res = memref.load %sum[] : memref<i32>
ibis.return %res : i32
}
}

View File

@ -0,0 +1,75 @@
// RUN: ibistool %s --lo --post-ibis-ir | FileCheck %s --check-prefix=CHECK-POST-IBIS
// RUN: ibistool %s --lo --ir | FileCheck %s --check-prefix=CHECK-IR
// RUN: ibistool %s --lo --verilog | FileCheck %s --check-prefix=CHECK-VERILOG
// CHECK-POST-IBIS-LABEL: hw.module @A_B(
// CHECK-POST-IBIS-SAME: %[[VAL_0:.*]]: !seq.clock, %[[VAL_1:.*]]: i1) -> (p_out: i1) {
// CHECK-POST-IBIS: %[[VAL_2:.*]] = seq.compreg %[[VAL_1]], %[[VAL_0]] : i1
// CHECK-POST-IBIS: hw.output %[[VAL_2]] : i1
// CHECK-POST-IBIS: }
// CHECK-POST-IBIS-LABEL: hw.module @A(
// CHECK-POST-IBIS-SAME: %[[VAL_0:.*]]: i1, %[[VAL_1:.*]]: !seq.clock) -> (out: i1) {
// CHECK-POST-IBIS: %[[VAL_2:.*]] = hw.instance "A_B" @A_B(p_clk: %[[VAL_1]]: !seq.clock, p_in: %[[VAL_0]]: i1) -> (p_out: i1)
// CHECK-POST-IBIS: hw.output %[[VAL_2]] : i1
// CHECK-POST-IBIS: }
// CHECK-IR-LABEL: hw.module @A_B(
// CHECK-IR-SAME: %[[VAL_0:.*]]: i1, %[[VAL_1:.*]]: i1) -> (p_out: i1) {
// CHECK-IR: %[[VAL_2:.*]] = sv.reg : !hw.inout<i1>
// CHECK-IR: %[[VAL_3:.*]] = sv.read_inout %[[VAL_2]] : !hw.inout<i1>
// CHECK-IR: sv.alwaysff(posedge %[[VAL_0]]) {
// CHECK-IR: sv.passign %[[VAL_2]], %[[VAL_1]] : i1
// CHECK-IR: }
// CHECK-IR: hw.output %[[VAL_3]] : i1
// CHECK-IR: }
// CHECK-IR-LABEL: hw.module @A(
// CHECK-IR-SAME: %[[VAL_0:.*]]: i1, %[[VAL_1:.*]]: i1) -> (out: i1) {
// CHECK-IR: %[[VAL_2:.*]] = hw.instance "A_B" @A_B(p_clk: %[[VAL_1]]: i1, p_in: %[[VAL_0]]: i1) -> (p_out: i1)
// CHECK-IR: hw.output %[[VAL_2]] : i1
// CHECK-IR: }
// CHECK-VERILOG-LABEL: module A_B(
// CHECK-VERILOG: input p_clk,
// CHECK-VERILOG: p_in,
// CHECK-VERILOG: output p_out
// CHECK-VERILOG: );
// CHECK-VERILOG: reg r;
// CHECK-VERILOG: always_ff @(posedge p_clk)
// CHECK-VERILOG: r <= p_in;
// CHECK-VERILOG: assign p_out = r;
// CHECK-VERILOG: endmodule
// CHECK-VERILOG-LABEL: module A(
// CHECK-VERILOG: input in,
// CHECK-VERILOG: clk,
// CHECK-VERILOG: output out
// CHECK-VERILOG: );
// CHECK-VERILOG: A_B A_B (
// CHECK-VERILOG: .p_clk (clk),
// CHECK-VERILOG: .p_in (in),
// CHECK-VERILOG: .p_out (out)
// CHECK-VERILOG: );
// CHECK-VERILOG: endmodule
ibis.class @A {
%this = ibis.this @A
ibis.port.input @in : i1
ibis.port.output @out : i1
ibis.port.input @clk : !seq.clock
ibis.container@B {
%B_this = ibis.this @B
%parent = ibis.path [
#ibis.step<parent : !ibis.scoperef<@A>>
]
%a_in = ibis.get_port %parent, @in : !ibis.scoperef<@A> -> !ibis.portref<out i1>
%a_clk = ibis.get_port %parent, @clk : !ibis.scoperef<@A> -> !ibis.portref<out !seq.clock>
%a_out = ibis.get_port %parent, @out : !ibis.scoperef<@A> -> !ibis.portref<in i1>
%in = ibis.port.read %a_in : !ibis.portref<out i1>
%clk = ibis.port.read %a_clk : !ibis.portref<out !seq.clock>
%r = seq.compreg %in, %clk: i1
ibis.port.write %a_out, %r : !ibis.portref<in i1>
}
}

View File

@ -58,7 +58,7 @@ tool_dirs = [
tools = [ tools = [
'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test', 'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test',
'circt-capi-firrtl-test', 'circt-dis', 'circt-opt', 'circt-reduce', 'circt-capi-firrtl-test', 'circt-dis', 'circt-opt', 'circt-reduce',
'circt-translate', 'firtool', 'hlstool', 'om-linker' 'circt-translate', 'firtool', 'hlstool', 'om-linker', 'ibistool'
] ]
# Enable Verilator if it has been detected. # Enable Verilator if it has been detected.

View File

@ -15,3 +15,4 @@ add_subdirectory(llhd-sim)
add_subdirectory(om-linker) add_subdirectory(om-linker)
add_subdirectory(py-split-input-file) add_subdirectory(py-split-input-file)
add_subdirectory(hlstool) add_subdirectory(hlstool)
add_subdirectory(ibistool)

View File

@ -0,0 +1,47 @@
set(LLVM_LINK_COMPONENTS
Support
)
add_circt_tool(ibistool
ibistool.cpp
)
llvm_update_compile_flags(ibistool)
target_link_libraries(ibistool
PRIVATE
CIRCTExportChiselInterface
CIRCTExportVerilog
CIRCTHandshake
CIRCTHandshakeToHW
CIRCTHandshakeTransforms
CIRCTHW
CIRCTHWTransforms
CIRCTSeq
CIRCTSeqToSV
CIRCTSeqTransforms
CIRCTCFToHandshake
CIRCTSV
CIRCTSVTransforms
CIRCTIbis
CIRCTIbisTransforms
CIRCTTransforms
CIRCTPipelineOps
CIRCTPipelineToHW
CIRCTDCToHW
CIRCTHandshakeToDC
CIRCTPipelineTransforms
CIRCTDC
CIRCTDCTransforms
CIRCTESI
MLIRIR
MLIRLLVMDialect
MLIRMemRefDialect
MLIROptLib
MLIRParser
MLIRControlFlowDialect
MLIRSupport
MLIRTransforms
MLIRSCFToControlFlow
)

434
tools/ibistool/ibistool.cpp Normal file
View File

@ -0,0 +1,434 @@
//===- ibistool.cpp - The ibistool utility for working with the Ibis 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 'ibistool', which composes together a variety of
// CIRCT libraries that can be used to realise an Ibis-based lowering flow.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/Passes.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/Ibis/IbisDialect.h"
#include "circt/Dialect/Ibis/IbisPassPipelines.h"
#include "circt/Dialect/Ibis/IbisPasses.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"
#include <iostream>
using namespace llvm;
using namespace mlir;
using namespace circt;
using namespace ibis;
// --------------------------------------------------------------------------
// Tool options
// --------------------------------------------------------------------------
static cl::OptionCategory mainCategory("ibistool 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 {
OutputLoweredIbis,
OutputIR,
OutputVerilog,
OutputSplitVerilog
};
static cl::opt<OutputFormatKind> outputFormat(
cl::desc("Specify output format:"),
cl::values(
clEnumValN(OutputLoweredIbis, "post-ibis-ir",
"Emit IR after Ibis 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 { HiIbis, LoIbis };
static cl::opt<FlowKind>
flowKind(cl::desc("Specify flow kind:"),
cl::values(clEnumValN(HiIbis, "hi", "High-level Ibis flow"),
clEnumValN(LoIbis, "lo", "Low-level Ibis 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.useTopDownTraversal = true;
config.enableRegionSimplification = false;
return mlir::createCanonicalizerPass(config);
}
static void loadHighLevelControlflowTransformsPipeline(OpPassManager &pm) {
pm.addPass(mlir::createLowerAffinePass());
pm.addPass(mlir::createConvertSCFToCFPass());
pm.addPass(createSimpleCanonicalizerPass());
}
static void loadHandshakeTransformsPipeline(OpPassManager &pm) {
pm.addPass(circt::createCFToHandshakePass(
/*sourceConstants=*/false,
/*disableTaskPipelining=*/false));
pm.addPass(createSimpleCanonicalizerPass());
pm.nest<handshake::FuncOp>().addPass(
handshake::createHandshakeMaterializeForksSinksPass());
pm.addPass(createSimpleCanonicalizerPass());
pm.nest<handshake::FuncOp>().addPass(
handshake::createHandshakeInsertBuffersPass("all", 2));
pm.addPass(createSimpleCanonicalizerPass());
}
static void loadDCTransformsPipeline(OpPassManager &pm) {
pm.addPass(circt::createHandshakeToDCPass());
pm.addPass(createSimpleCanonicalizerPass());
pm.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(sv::createHWMemSimImplPass(false, false));
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 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 loadIbisHiFlow(
PassManager &pm, ModuleOp module,
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
if (verbosePassExecutions)
llvm::errs() << "[ibistool] Will run high-level Ibis flow\n";
loadHighLevelControlflowTransformsPipeline(pm);
loadIbisHighLevelPassPipeline(pm);
if (outputFormat != OutputLoweredIbis) {
loadHandshakeTransformsPipeline(pm);
loadDCTransformsPipeline(pm);
if (outputFormat != OutputLoweredIbis)
loadLowLevelPassPipeline(pm, module, outputFile);
}
}
static void loadIbisLoFlow(
PassManager &pm, ModuleOp module,
std::optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
if (verbosePassExecutions)
llvm::errs() << "[ibistool] Will run low-level Ibis flow\n";
loadIbisLowLevelPassPipeline(pm);
if (outputFormat != OutputLoweredIbis)
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() << "[ibistool] 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() << "[ibistool] -- 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 == HiIbis)
loadIbisHiFlow(pm, module.get(), outputFile);
else if (flowKind == LoIbis)
loadIbisLoFlow(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 executeIbistool(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 ibistool command. This sets up LLVM and MLIR, and parses
/// command line options before passing off to 'executeIbistool'. 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 Ibis 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, ibis::IbisDialect,
dc::DCDialect, esi::ESIDialect, pipeline::PipelineDialect>();
// Do the guts of the ibistool process.
MLIRContext context(registry);
auto result = executeIbistool(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));
}