Rocket Chipの解析の話の4回目。
今回は"make"に渡す引数の一つDefaultConfig
の中身でああるConfig
やParameters
について。
この辺の話はすでに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
に渡されるようになる。
そのDefaultConfig
はfreechips.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
のメンバx
にthis
をy
に右辺のParameters
のインスタンスを入れていくので++
でParameters
を接続した場合、以下の画像のような感じで++
で結合したParameters
が入れ子になっていく。
これ使う時にどうすんのよ??って思ったのだが、実際に使う際には以下のような形で所望のパラメータが入った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
を使って定義がされていた。
- tile/Core.scala
case object XLen extends Field[Int]
この感じだと、自分でモジュールを作ってつなげたい場合にはField[T]
とそれを検索するためのmatch
式を含むConfig
の派生クラスがあれば良さそう。
ということで、ざっくりParameters
の仕組みを追ってみた話でした。