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() }