CIRCTの解析続き。CIRCTのツールの一つににcirct-translate
というツールがあり、これが各種ハードウェア記述言語からMLIRへの変換、そしてターゲットコードへの変換を司っているらしい。
circt-translate.cpp
のソースコードを眺めてみても良いが、まずはFIRRTLをParseするためのFIRParser
のソースコードを読んでいこう。
circt/lib/FIRParser/FIRParser.cpp
FIRParser.cpp
には、FIRParser
をパスとして登録するためのルーチンが定義されている。これもおそらくcirct-translate.cpp
内でオプションに応じて実行されるものと思われる。
circt/lib/FIRParser/FIRParser.cpp
void circt::registerFIRParserTranslation() { static TranslateToMLIRRegistration fromFIR( "parse-fir", [](llvm::SourceMgr &sourceMgr, MLIRContext *context) { return parseFIRFile(sourceMgr, context); }); }
この中でparseFIRFile()
が呼び出されている。これはその名の通りの関数だ。
// Parse the specified .fir file into the specified MLIR context. OwningModuleRef circt::parseFIRFile(SourceMgr &sourceMgr, MLIRContext *context, FIRParserOptions options) { auto sourceBuf = sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID()); // This is the result module we are parsing into. OwningModuleRef module(ModuleOp::create( FileLineColLoc::get(sourceBuf->getBufferIdentifier(), /*line=*/0, /*column=*/0, context))); GlobalFIRParserState state(sourceMgr, context, options); if (FIRCircuitParser(state, *module).parseCircuit()) return nullptr; ...
ファイルを読み込むと、FIRCircuitParser().parseCircuit()
が呼び出されることが分かる。parseCircuit()
は、LLVMのAsmParser
を読んだことがあるので何となくわかる。
最初のコメントにも書いてある通り、parseCircuit()
は以下の構文をParseする仕組みになっている。
circuit ::= 'circuit' id ':' info? INDENT module* DEDENT EOF
/// 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(); LocWithInfo info(getToken().getLoc(), this); StringAttr name; ...
構文解析の方法は単純で、上から順番にparseしており、module*
の部分は繰り返しで表現されている。
次はmodule
を構文解析するための関数だ。parseModule
を見てみる。parseModule
は以下の構文に従うコードをParseする。
module ::= 'module' id ':' info? INDENT portlist simple_stmt_block
/// module ::= 'module' id ':' info? INDENT portlist simple_stmt_block /// DEDENT /// ParseResult FIRModuleParser::parseModule(unsigned indent) { LocWithInfo info(getToken().getLoc(), this); StringAttr name; SmallVector<PortInfoAndLoc, 4> portListAndLoc; ...
これも構文解析のフローを起こしてみた。これも直感と同様の構造になっている。
このparseModule()
の中で呼び出されているparsePortList()
も同様に解析してみた。parsePortList
は以下の構文に従うコードをParseする。
portlist ::= port* port ::= dir id ':' type info? NEWLINE dir ::= 'input' | 'output'
ParseResult FIRModuleParser::parsePortList(SmallVectorImpl<PortInfoAndLoc> &result, unsigned indent) { // Parse any ports. while (getToken().isAny(FIRToken::kw_input, FIRToken::kw_output) && // Must be nested under the module. getIndentation() > indent) { bool isOutput = getToken().is(FIRToken::kw_output); ...
これらの方式でparseをしていくという訳だ。これらの情報を前提に、どのようにVerilogに変換するのかを引き続き解析していこう。