FPGA開発日記

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

CIRCTの仕組みとMLIRを読み解く (FIRRTLのParse部分)

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