ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Rocket ChipのGeneratorのソースの解析メモ(1)

スポンサーリンク

Rocket Chip環境の仕組み解析メモの垂れ流し記事。とりあえずRTL生成時のざっくりした流れを追ったメモを元に少しだけ手直し。

Rocket ChipのRTL生成フロー(全体の流れ)

少し前に書いた記事(IntelliJ IDEA上でRocket Chipのエミュレータをビルドする)で記載したが、Rocket ChipのREADMEに従ってエミュレータを生成する際に実行するmakeコマンドの中では以下のようなsbtコマンドが発行されていた。

runMain freechips.rocketchip.system.Generator ./emulator/generated-src freechips.rocketchip.system TestHarness freechips.rocketchip.system DefaultConfig

上記はrunMainを使ってfreechips.rocketchip.system.Generatorのメインオブジェクトを実行する処理に相当するので"src"ディレクトリ中のデータから

  • rocketchip/system/Generator.scala

の中にあるオブジェクトを見てやればいいことになる。 ということで早速中身を確認していく。

src/main/system/Generator.scala

そこそこ長いので不要な部分は端折りながら。

オブジェクトの宣言

冒頭は当たり前だけどGeneratorオブジェクトの宣言。ここではGeneratorAppというクラスを継承することでScalaのメインオブジェクトとして宣言する形になっている。 GeneratorAppについてはまた後ほど。 冒頭に実行するテストスイートについての情報が定義されている。

/** A Generator for platforms containing Rocket Subsystemes */
object Generator extends GeneratorApp {

  // 各アーキごとの追加テストリスト
  val rv64RegrTestNames = LinkedHashSet(
        "rv64ud-v-fcvt",
        // 略
        "rv64si-p-dirty")

  val rv32RegrTestNames = LinkedHashSet(
      "rv32mi-p-ma_addr",
      // 略
      "rv32ui-p-sll")

テストスイートの構築メソッド(addTestSuites)

以下はテストスイートの構築メソッド。 コンフィグで指定されたパラメータから

  • xLen
  • エクステンション
  • モード

の情報を抽出しそれに対応するテストをTestGeneration.addSuitesを使って追加している。

  override def addTestSuites {
    import DefaultTestSuites._
    val xlen = params(XLen)
    // TODO: for now only generate tests for the first core in the first subsystem
    params(RocketTilesKey).headOption.map { tileParams =>
      val coreParams = tileParams.core
      val vm = coreParams.useVM
      val env = if (vm) List("p","v") else List("p")

      // FPUのオプションに従ってテストを追加
      coreParams.fpu foreach { case cfg =>
        if (xlen == 32) {
          TestGeneration.addSuites(env.map(rv32uf))
          if (cfg.fLen >= 64)
            TestGeneration.addSuites(env.map(rv32ud))
        } else {
          TestGeneration.addSuite(rv32udBenchmarks)
          TestGeneration.addSuites(env.map(rv64uf))
          if (cfg.fLen >= 64)
            TestGeneration.addSuites(env.map(rv64ud))
        }
      }

      // A-extension(Atomimc)のテストに関する処理
      if (coreParams.useAtomics) {
        if (tileParams.dcache.flatMap(_.scratch).isEmpty)
          TestGeneration.addSuites(env.map(if (xlen == 64) rv64ua else rv32ua))
        else
          TestGeneration.addSuites(env.map(if (xlen == 64) rv64uaSansLRSC else rv32uaSansLRSC))
      }

      // C-extensionのテストに関する処理
      if (coreParams.useCompressed) TestGeneration.addSuites(env.map(if (xlen == 64) rv64uc else rv32uc))
      val (rvi, rvu) =
        if (xlen == 64) ((if (vm) rv64i else rv64pi), rv64u)
        else            ((if (vm) rv32i else rv32pi), rv32u)

      TestGeneration.addSuites(rvi.map(_("p")))
      TestGeneration.addSuites((if (vm) List("v") else List()).flatMap(env => rvu.map(_(env))))
      TestGeneration.addSuite(benchmarks)
      TestGeneration.addSuite(new RegressionTestSuite(if (xlen == 64) rv64RegrTestNames else rv32RegrTestNames))
    }
  }

エミュレータの生成処理

以下がGeneratorのメイン処理。 longNameを構築した後、

  1. FIRRTLの生成
  2. アノテーションファイルの生成
  3. Makefragsの生成
  4. ROMの生成
  5. アーティファクトの生成(memmap.jsonとかが該当する)

を行う。

  val longName = names.configProject + "." + names.configs
  generateFirrtl
  generateAnno
  generateTestSuiteMakefrags
  generateROMs
  generateArtefacts
}

この中のgenerateFirrtlを読み解ければ、どのようにしてRocket Chipが構築されていくのかがわかりそう。 残りの4つは中を見る限り、生成したRocket Chipの情報を元に各種ファイルを出力するだけの模様。 とりあえず以下にそのコードを載せておく。

  • generateAnno
    • circuitの中の変数をJSON形式でシリアライズして出力
    • 生成されるのは"*.anno.json"でBlackBoxの情報やメモリマップの情報が出力される
  def generateAnno {
    val annotationFile = new File(td, s"$longName.anno.json")
    val af = new FileWriter(annotationFile)
    af.write(JsonProtocol.serialize(circuit.annotations.map(_.toFirrtl)))
    af.close()
  }
  • generateTestSuiteMakefrags
    • ここで先ほど紹介したsaddTestSuitesが呼び出される。
    • 出力されるのは"<出力先ディレクトリ>/*.d"で中身はmake時のターゲット情報が記載される
  /** Output software test Makefrags, which provide targets for integration testing. */
  def generateTestSuiteMakefrags {
    addTestSuites
    writeOutputFile(td, s"$longName.d", TestGeneration.generateMakefrag) // Subsystem-specific test suites
  }
  • generateROMs
    • *.rom.confが出力される
    • エミュレータの生成時にはファイルの中身は空。
  def generateROMs {
    writeOutputFile(td, s"$longName.rom.conf", enumerateROMs(circuit))
  }
  • generateArtefacts
    • 先ほど書いた*.memmap.jsonが出力される
    • 処理的にはforeachの処理なので、他にも出力されそうだが詳細は未調査
  /** Output files created as a side-effect of elaboration */
  def generateArtefacts {
    ElaborationArtefacts.files.foreach { case (extension, contents) =>
      writeOutputFile(td, s"$longName.$extension", contents ())
    }
  }

Rocket Chip環境が凄いのはREADMEに書いてあるmake一発で作成できるDefaultConfig以外のコンフィグもこのジェネレータ・オブジェクトのみで生成できる点だと思う。 なのでメインの処理となるgenerateFirrtlについての調査を継続中です。ただLazyModuleが。。。。やってることはわかってきたけど自分で使おうとすると。。。