FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

CIRCTのcirct-translateの内部構造を解析する (FIRParserの解析)

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*の部分は繰り返しで表現されている。

f:id:msyksphinz:20200808203925p:plain

次は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;
...

これも構文解析のフローを起こしてみた。これも直感と同様の構造になっている。

f:id:msyksphinz:20200808203944p:plain

この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に変換するのかを引き続き解析していこう。

f:id:msyksphinz:20200808204006p:plain