[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:
Fabian Schuiki 2023-03-12 16:39:31 -07:00 committed by GitHub
parent 3d27bf4ab2
commit c37e320a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 310 additions and 1 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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.

View File

@ -1,3 +1,4 @@
add_subdirectory(arcilator)
add_subdirectory(circt-as)
add_subdirectory(circt-dis)
add_subdirectory(circt-lsp-server)

View File

@ -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)

View File

@ -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));
}