mirror of https://github.com/llvm/circt.git
[Arc] Add arcilator convenience tool (#4700)
Add the `arcilator` convenience tool to make experimenting with the dialect easier. The intended pass sequence performs the full conversion from a circuit cut along port boundaries (through modules) to a circuit cut along the state elements (through arcs). The tool simply executes this pass pipeline. Co-authored-by: Martin Erhart <maerhart@outlook.com> Co-authored-by: Zachary Yedidia <zyedidia@gmail.com>
This commit is contained in:
parent
3d27bf4ab2
commit
c37e320a60
|
@ -18,6 +18,7 @@ configure_lit_site_cfg(
|
|||
set(CIRCT_TEST_DEPENDS
|
||||
FileCheck count not
|
||||
split-file
|
||||
arcilator
|
||||
circt-capi-ir-test
|
||||
circt-as
|
||||
circt-dis
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// RUN: arcilator %s | FileCheck %s
|
||||
|
||||
// CHECK: arc.define @[[XOR_ARC:.+]](
|
||||
// CHECK-NEXT: comb.xor
|
||||
// CHECK-NEXT: arc.output
|
||||
// CHECK-NEXT: }
|
||||
|
||||
// CHECK: arc.define @[[ADD_ARC:.+]](
|
||||
// CHECK-NEXT: comb.add
|
||||
// CHECK-NEXT: arc.output
|
||||
// CHECK-NEXT: }
|
||||
|
||||
// CHECK: arc.define @[[MUL_ARC:.+]](
|
||||
// CHECK-NEXT: comb.mul
|
||||
// CHECK-NEXT: arc.output
|
||||
// CHECK-NEXT: }
|
||||
|
||||
// CHECK-LABEL: hw.module @Top
|
||||
hw.module @Top(%clock: i1, %i0: i4, %i1: i4) -> (out: i4) {
|
||||
// CHECK-NOT: hw.instance
|
||||
// CHECK-DAG: [[T0:%.+]] = arc.state @[[ADD_ARC]](%i0, %i1) lat 0
|
||||
%0 = comb.add %i0, %i1 : i4
|
||||
// CHECK-DAG: [[T3:%.+]] = arc.state @[[XOR_ARC]]([[T0]], %i0) clock %clock lat 1
|
||||
// CHECK-DAG: [[T4:%.+]] = arc.state @[[XOR_ARC]]([[T0]], %i1) clock %clock lat 1
|
||||
%1 = comb.xor %0, %i0 : i4
|
||||
%2 = comb.xor %0, %i1 : i4
|
||||
%3 = seq.compreg %1, %clock : i4
|
||||
%4 = seq.compreg %2, %clock : i4
|
||||
// CHECK-DAG: [[T5:%.+]] = arc.state @[[MUL_ARC]]([[T3]], [[T4]]) lat 0
|
||||
%5 = comb.mul %3, %4 : i4
|
||||
// CHECK-DAG: [[K:%.+]] = hw.constant 6 :
|
||||
// CHECK-DAG: [[T6:%.+]] = arc.state @[[ADD_ARC]]([[T5]], [[K]]) clock %clock lat 1
|
||||
%6 = hw.instance "child" @Child(clock: %clock: i1, a: %5: i4) -> (z: i4)
|
||||
// CHECK-DAG: hw.output [[T6]]
|
||||
hw.output %6 : i4
|
||||
}
|
||||
|
||||
// CHECK-NOT: hw.module private @Child
|
||||
hw.module private @Child(%clock: i1, %a: i4) -> (z: i4) {
|
||||
%c6_i4 = hw.constant 6 : i4
|
||||
%0 = comb.add %a, %c6_i4 : i4
|
||||
%1 = seq.compreg %0, %clock : i4
|
||||
hw.output %1 : i4
|
||||
}
|
|
@ -57,7 +57,8 @@ tool_dirs = [
|
|||
]
|
||||
tools = [
|
||||
'firtool', 'circt-as', 'circt-dis', 'circt-opt', 'circt-reduce',
|
||||
'circt-translate', 'circt-capi-ir-test', 'esi-tester', 'hlstool'
|
||||
'circt-translate', 'circt-capi-ir-test', 'esi-tester', 'hlstool',
|
||||
'arcilator'
|
||||
]
|
||||
|
||||
# Enable Verilator if it has been detected.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
add_subdirectory(arcilator)
|
||||
add_subdirectory(circt-as)
|
||||
add_subdirectory(circt-dis)
|
||||
add_subdirectory(circt-lsp-server)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
set(LLVM_LINK_COMPONENTS Support)
|
||||
|
||||
add_llvm_tool(arcilator arcilator.cpp)
|
||||
target_link_libraries(arcilator
|
||||
PRIVATE
|
||||
CIRCTArc
|
||||
CIRCTArcTransforms
|
||||
CIRCTConvertToArcs
|
||||
CIRCTSupport
|
||||
MLIRParser
|
||||
)
|
||||
|
||||
llvm_update_compile_flags(arcilator)
|
||||
mlir_check_all_link_libraries(arcilator)
|
|
@ -0,0 +1,248 @@
|
|||
//===- arcilator.cpp - An experimental circuit simulator ------------------===//
|
||||
//
|
||||
// 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 'arcilator' compiler, which converts HW designs into
|
||||
// a corresponding LLVM-based software model.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/Dialect/Arc/Dialect.h"
|
||||
#include "circt/Dialect/Arc/Ops.h"
|
||||
#include "circt/Dialect/Arc/Passes.h"
|
||||
#include "circt/InitAllDialects.h"
|
||||
#include "circt/InitAllPasses.h"
|
||||
#include "circt/Support/Version.h"
|
||||
#include "mlir/Bytecode/BytecodeReader.h"
|
||||
#include "mlir/Bytecode/BytecodeWriter.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.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/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 <iostream>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace mlir;
|
||||
using namespace circt;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Command Line Arguments
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
static cl::OptionCategory mainCategory("arcilator 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"),
|
||||
cl::value_desc("filename"),
|
||||
cl::init("-"),
|
||||
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>
|
||||
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>
|
||||
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));
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Main Tool Logic
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Create a simple canonicalizer pass.
|
||||
static std::unique_ptr<Pass> createSimpleCanonicalizerPass() {
|
||||
mlir::GreedyRewriteConfig config;
|
||||
config.useTopDownTraversal = true;
|
||||
config.enableRegionSimplification = false;
|
||||
return mlir::createCanonicalizerPass(config);
|
||||
}
|
||||
|
||||
/// Populate a pass manager with the arc simulator pipeline for the given
|
||||
/// command line options.
|
||||
static void populatePipeline(PassManager &pm) {
|
||||
// Restructure the input from a `hw.module` hierarchy to a collection of arcs.
|
||||
pm.addPass(createConvertToArcsPass());
|
||||
pm.addPass(arc::createDedupPass());
|
||||
pm.addPass(arc::createInlineModulesPass());
|
||||
pm.addPass(createCSEPass());
|
||||
pm.addPass(createSimpleCanonicalizerPass());
|
||||
}
|
||||
|
||||
static LogicalResult
|
||||
processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
|
||||
Optional<std::unique_ptr<llvm::ToolOutputFile>> &outputFile) {
|
||||
mlir::OwningOpRef<mlir::ModuleOp> module;
|
||||
{
|
||||
auto parserTimer = ts.nest("Parse MLIR input");
|
||||
module = parseSourceFile<ModuleOp>(sourceMgr, &context);
|
||||
}
|
||||
if (!module)
|
||||
return failure();
|
||||
|
||||
PassManager pm(&context);
|
||||
pm.enableVerifier(verifyPasses);
|
||||
pm.enableTiming(ts);
|
||||
applyPassManagerCLOptions(pm);
|
||||
populatePipeline(pm);
|
||||
|
||||
if (failed(pm.run(module.get())))
|
||||
return failure();
|
||||
|
||||
{
|
||||
auto outputTimer = ts.nest("Print MLIR output");
|
||||
module->print(outputFile.value()->os());
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
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 executeArcilator(MLIRContext &context) {
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Create the output directory or output file depending on our mode.
|
||||
Optional<std::unique_ptr<llvm::ToolOutputFile>> outputFile;
|
||||
// Create an output file.
|
||||
outputFile.emplace(openOutputFile(outputFilename, &errorMessage));
|
||||
if (!outputFile.value()) {
|
||||
llvm::errs() << errorMessage << "\n";
|
||||
return failure();
|
||||
}
|
||||
|
||||
// Register our dialects.
|
||||
context.loadDialect<hw::HWDialect, comb::CombDialect, seq::SeqDialect,
|
||||
sv::SVDialect, arc::ArcDialect>();
|
||||
|
||||
// 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.value()->keep();
|
||||
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Main driver for the command. This sets up LLVM and MLIR, and parses command
|
||||
/// line options before passing off to 'executeArcilator'. 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);
|
||||
|
||||
// Hide default LLVM options, other than for this tool.
|
||||
// MLIR options are added below.
|
||||
cl::HideUnrelatedOptions(mainCategory);
|
||||
|
||||
// Register passes before parsing command-line options, so that they are
|
||||
// available for use with options like `--mlir-print-ir-before`.
|
||||
{
|
||||
// MLIR transforms:
|
||||
// Don't use registerTransformsPasses, pulls in too much.
|
||||
registerCSEPass();
|
||||
registerCanonicalizerPass();
|
||||
registerStripDebugInfoPass();
|
||||
|
||||
// Dialect passes:
|
||||
arc::registerPasses();
|
||||
}
|
||||
|
||||
// Register any pass manager command line options.
|
||||
registerMLIRContextCLOptions();
|
||||
registerPassManagerCLOptions();
|
||||
registerDefaultTimingManagerCLOptions();
|
||||
registerAsmPrinterCLOptions();
|
||||
cl::AddExtraVersionPrinter(
|
||||
[](raw_ostream &os) { os << getCirctVersion() << '\n'; });
|
||||
|
||||
// Parse pass names in main to ensure static initialization completed.
|
||||
cl::ParseCommandLineOptions(argc, argv, "MLIR-based circuit simulator\n");
|
||||
|
||||
MLIRContext context;
|
||||
|
||||
auto result = executeArcilator(context);
|
||||
|
||||
// Use "exit" instead of returning 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));
|
||||
}
|
Loading…
Reference in New Issue