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

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

Rocket ChipのGeneratorのソースの解析メモ(4) - Config、Parameters、Field

スポンサーリンク

Rocket Chipの解析の話の4回目。 今回は"make"に渡す引数の一つDefaultConfigの中身でああるConfigParametersについて。 この辺の話はすでにmsyksphinzさんのFPGA開発日記で解説してくれているけど、解析の関係上で扱っておくべきだと思うのでざっと見ていく。

Rocket Chipのコンフィグレーションについて

前回のRocket Chipネタの記事ではRocket Chip環境で"make"コマンドを実行した際に freechips.rocketchip.system.TestHarnessクラスが実体化していると述べた。

このTestHarnessの中でLazyModuleを使ったシステムを構築する際にp: Parametersが渡されている。

  • system/TestHarness.scala から抜粋
class TestHarness()(implicit p: Parameters) extends Module {
  val io = new Bundle {
    val success = Bool(OUTPUT)
  }

  val dut = Module(LazyModule(new ExampleRocketSystem).module)

"emulator"ディレクトリの下でmakeを実行した際には前々回あたりで解析したgenerateFirrtlの実行の際にDefaultConfigというパラメータクラスがTestHarnessに渡されるようになる。 そのDefaultConfigfreechips.rocketchip.systemパッケージの下にあるConfigs.scalaに以下のように定義されている。

class DefaultConfig extends Config(new WithNBigCores(1) ++ new BaseConfig)

雰囲気的にお察しいただけると思うが継承したConfigクラスの中で

  • WithNBigCores
  • BaseConfig

が結合されているような感じだ。(この辺の仕組みは冒頭で記載したmsyksphinzさんの記事も併せてどうぞ)

ここで使われている++Parametersクラスのメソッドで、以下のように自分自身(this)と右辺のParametersクラスのインスタンスChainParametersクラスの引数として使用している。

  • config/Config.scala から抜粋
abstract class Parameters extends View {
  final def ++ (x: Parameters): Parameters =
    new ChainParameters(this, x)

このChainParametersも同じ"Config.scala"に定義されている。

private class ChainParameters(x: Parameters, y: Parameters) extends Parameters {
  def chain[T](site: View, tail: View, pname: Field[T]) = x.chain(site, new ChainView(y, tail), pname)
}

お気づきになったと思うが、上記のように++メソッドではChainParametersのメンバxthisyに右辺のParametersインスタンスを入れていくので++Parametersを接続した場合、以下の画像のような感じで++で結合したParameters入れ子になっていく。

f:id:diningyo-kpuku-jougeki:20190819213015p:plain
DefaultConfigの中身

これ使う時にどうすんのよ??って思ったのだが、実際に使う際には以下のような形で所望のパラメータが入ったParametersクラスのインスタンスを引っ張り出すような形で使用する。

  • subsystem/BaseSubsystem.scala から抜粋
/** Base Subsystem class with no peripheral devices or ports added */
abstract class BaseSubsystem(implicit p: Parameters) extends BareSubsystem {
  override val module: BaseSubsystemModuleImp[BaseSubsystem]

  // These are wrappers around the standard buses available in all subsytems, where
  // peripherals, tiles, ports, and other masters and slaves can attach themselves.
  val ibus = new InterruptBusWrapper()
  val sbus = LazyModule(p(BuildSystemBus)(p))
  // p(PeripheryBusKey) を実行するとParametersの中から
  // PeripheryBusKeyにマッチしたインスタンスとしてPeripheryBusParamsが帰ってくる
  val pbus = LazyModule(new PeripheryBus(p(PeripheryBusKey)))

このp(hoge)ParametersクラスのスーパークラスであるViewクラスのapplyメソッドが実体で、その中でfindメソッドを呼び出してhogeにマッチするParametersを返す仕組みとなっている。

abstract class View {
  final def apply[T](pname: Field[T]): T = apply(pname, this)
  final def apply[T](pname: Field[T], site: View): T = {
    val out = find(pname, site)
    require (out.isDefined, s"Key ${pname} is not defined in Parameters")
    out.get
  }

上記のp(PeripheryBusKey)の場合は以下の様に定義されたcase objectをキーとして検索し、その中のPeripheryBusParamsを取得する感じ。

case object PeripheryBusKey extends Field[PeripheryBusParams]

このfindを追っていくと最終的には以下のようなthisというメソッドに行き着く。

class Config(p: Parameters) extends Parameters {
  def this(f: (View, View, View) => PartialFunction[Any,Any]) = this(Parameters(f))

このthisは各派生クラスで実装されており、上記で例に上げたPeripheryBusKeyの場合はBaseBubSystemConfigクラスのthisメソッド内に実装されているmatch式の一つのケースにたどり着いてPeripheryBusParamsが返却されるようになっている。

class BaseSubsystemConfig extends Config ((site, here, up) => {
  // Tile parameters
  case PgLevels => if (site(XLen) == 64) 3 /* Sv39 */ else 2 /* Sv32 */
  case XLen => 64 // Applies to all cores
  case MaxHartIdBits => log2Up(site(RocketTilesKey).size)
  // Interconnect parameters
  case SystemBusKey => SystemBusParams(
    beatBytes = site(XLen)/8,
    blockBytes = site(CacheBlockBytes))
  case ControlBusKey => PeripheryBusParams(
    beatBytes = site(XLen)/8,
    blockBytes = site(CacheBlockBytes),
    errorDevice = Some(DevNullParams(List(AddressSet(0x3000, 0xfff)), maxAtomic=site(XLen)/8, maxTransfer=4096)),
    replicatorMask = site(MultiChipMaskKey))
  // ここにマッチするとPeripheryBusParamsが返ってくる
  case PeripheryBusKey => PeripheryBusParams(
    beatBytes = site(XLen)/8,
    blockBytes = site(CacheBlockBytes))

このような形でConfig/Parameters/Fieldを各設定項目ごとに定義しておき、それを++で組み上げていくことによってRocket Chipのパラメータが構築されている。 今回これを追うまで全く気づいてなかったけどRISC-VのXLENというパラメータすらFieldを使って定義がされていた。

case object XLen extends Field[Int]

この感じだと、自分でモジュールを作ってつなげたい場合にはField[T]とそれを検索するためのmatch式を含むConfigの派生クラスがあれば良さそう。

ということで、ざっくりParametersの仕組みを追ってみた話でした。