CIRCTではFIRRTLを受け取ってSystemVerilogをエクスポートするような機能が搭載されている。CIRCTではMLIRの技術が使用されており、MLIRの勉強するのにはもってこいだと思う。FIRRTLをParseしてMLIRを使って構築するフローが構築されているので、まずはこれを体験する。
$ ./circt-translate --help
OVERVIEW: CIRCT Translation Testing Tool
USAGE: circt-translate [options] <input file>
OPTIONS:
Color Options:
--color - Use colors in output (default=autodetect)
General options:
Translation to perform
--export-llhd-verilog - export-llhd-verilog
--export-quartus-tcl - export-quartus-tcl
--export-verilog - export-verilog
--import-firrtl - import-firrtl
--lowering-options=<value> - Style options
--mlir-disable-threading - Disabling multi-threading within MLIR
--mlir-elide-elementsattrs-if-larger=<uint> - Elide ElementsAttrs with "..." that have more elements than the given upper limit
--mlir-pretty-debuginfo - Print pretty debug info in MLIR output
--mlir-print-debuginfo - Print debug info in MLIR output
--mlir-print-elementsattrs-with-hex-if-larger=<long> - Print DenseElementsAttrs with a hex string that have more elements than the given upper limit (use -1 to disable)
--mlir-print-op-on-diagnostic - When a diagnostic is emitted on an operation, also print the operation as an attached note
--mlir-print-stacktrace-on-diagnostic - When a diagnostic is emitted, also print the stack trace as an attached note
-o=<filename> - Output filename
--split-input-file - Split the input file into pieces and process each chunk independently
--verify-diagnostics - Check that emitted diagnostics match expected-* lines on the corresponding line
まずは以下のようなFIRRTLファイルを作ってテストしてみる。
test/Dialect/FIRRTL/basic.fir
circuit basic :
module basic :
input b: UInt
output a : UInt
a <= b
これをコンパイルしてみると、MLIRが出力される。
$ ./circt-translate --import-firrtl ../../test/Dialect/FIRRTL/basic.fir
module {
firrtl.circuit "basic" {
firrtl.module @basic(%b: !firrtl.uint, %a: !firrtl.flip<uint>) {
firrtl.connect %a, %b : !firrtl.flip<uint>, !firrtl.uint
}
}
}
なるほど、これがMLIRのフォーマットとなる。まずはFIRRTLのParse部分を考えてみる。
まずはCIRCTのディレクトリの内部を探っている。FIRRTLをParseしているのはこの辺だと思われるので、これを確認していく。
$ ag ::parse FIRRTL/Import/FIRParser.cpp
300:ParseResult FIRParser::parseToken(FIRToken::Kind expectedToken,
/* ... 中略 ... */
2788:ParseResult FIRModuleParser::parseModule(unsigned indent) {
2854:ParseResult FIRCircuitParser::parseCircuit() {
ふむ、parseCircuit()からだが、これをどのようにMLIRに変換するのか見ていきたい。
circt/lib/Dialect/FIRRTL/Import/FIRParser.cpp
/// file ::= circuit /// circuit ::= 'circuit' id ':' info? INDENT module* DEDENT EOF /// ParseResult FIRCircuitParser::parseCircuit() { auto indent = getIndentation(); if (!indent.hasValue()) return emitError("'circuit' must be first token on its line"), failure(); unsigned circuitIndent = indent.getValue(); /* ... 中略 ... */
このparseCircuit()だが、MLIRの構築はこの辺りな予感がしている。
/* ... 中略 ... */ OpBuilder b(mlirModule.getBodyRegion()); // Create the top-level circuit op in the MLIR module. auto circuit = b.create<CircuitOp>(info.getLoc(), name, annotationVec); /* ... 中略 ... */
mlirModuleはFIRRTLのModuleユニット向けのクラスっぽいな。ModuleOpとして定義されている。
namespace { /// This class implements the outer level of the parser, including things /// like circuit and module. struct FIRCircuitParser : public FIRParser { explicit FIRCircuitParser(GlobalFIRParserState &state, ModuleOp mlirModule) : FIRParser(state), mlirModule(mlirModule) {} ParseResult parseCircuit(); private: ModuleOp mlirModule; };
このModuleOpはこれが相当しているのだろうか?これもおそらくBuiltinOps.tdから生成されているということかな?
circt/llvm/build/tools/mlir/include/mlir/IR/BuiltinOps.h.inc
class ModuleOp : public ::mlir::Op<ModuleOp, ::mlir::OpTrait::OneRegion, ::mlir::OpTrait::ZeroResult, ::mlir::OpTrait::ZeroSuccessor, ::mlir::OpTrait::ZeroOperands, ::mlir::OpTrait::AffineScope, ::mlir::OpTrait::IsIsolatedFromAbove, ::mlir::OpTrait::NoRegionArguments, ::mlir::OpTrait::SymbolTable, ::mlir::SymbolOpInterface::Trait, ::mlir::OpTrait::NoTerminator, ::mlir::OpTrait::SingleBlock, ::mlir::RegionKindInterface::Trait, ::mlir::OpTrait::HasOnlyGraphRegion> {
https://mlir.llvm.org/docs/Dialects/Builtin/#module-mlirmoduleop
circt/llvm/mlir/include/mlir/IR/BuiltinOps.td
//===----------------------------------------------------------------------===//
// ModuleOp
//===----------------------------------------------------------------------===//
def ModuleOp : Builtin_Op<"module", [
AffineScope, IsolatedFromAbove, NoRegionArguments, SymbolTable, Symbol]
# GraphRegionNoTerminator.traits> {
let summary = "A top level container operation";
let description = [{
A `module` represents a top-level container operation. It contains a single
[graph region](#control-flow-and-ssacfg-regions) containing a single block
which can contain any operations and does not have a terminator. Operations
within this region cannot implicitly capture values defined outside the module,
i.e. Modules are [IsolatedFromAbove](Traits.md#isolatedfromabove). Modules have
an optional [symbol name](SymbolsAndSymbolTables.md) which can be used to refer
to them in operations.
Example:
```mlir
module {
func @foo()
}