Diplomacyを使ったバスの設計では、Chiselの機能を使って設計段階で様々な問題を事前にチェックすることができる。例えば、スレーブノード(Managerノード)のアドレスマップが被っているかどうかをチェックすることができる。
例えば以下のデザインで、TLRAMのアドレスマップが被っており(0x0 - 0x400)、これをコンパイル時にチェックすることができる。以下のようなエラーが出てくるのがそれだ。
chisel-hw/src/main/scala/TLUnitTest/TLManyMasterManySlave.scala
class TLManyMasterManySlave(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule { val pusher0 = LazyModule(new TLPatternPusher("pat1", Seq( new WritePattern(0x100, 0x2, 0x012345678L), ... val ram0 = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff), beatBytes = ramBeatBytes)) val ram1 = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff), beatBytes = ramBeatBytes)) ...
[error] at java.base/java.lang.reflect.Method.invoke(Method.java:566) [error] Caused by: java.lang.IllegalArgumentException: requirement failed: Ports cannot overlap: AddressSet(0x0, 0x3ff) AddressSet(0x0, 0x3ff) [error] at scala.Predef$.require(Predef.scala:277) [error] at freechips.rocketchip.diplomacy.AddressDecoder$.$anonfun$apply$4(AddressDecoder.scala:30) [error] at freechips.rocketchip.diplomacy.AddressDecoder$.$anonfun$apply$4$adapted(AddressDecoder.scala:29) [error] at scala.collection.immutable.List.foreach(List.scala:389)
requirement failed: Ports cannot overlap: AddressSet(0x0, 0x3ff) AddressSet(0x0, 0x3ff)
というエラーが表示される。これがどのような経路で出てきたかというと。Xbar.scala
の実装から出てきたわけだ。
chisel-hw/rocketchip/src/main/scala/diplomacy/AddressDecoder.scala
object AddressDecoder { type Port = Seq[AddressSet] ... } else { // Verify the user did not give us an impossible problem nonEmptyPorts.combinations(2).foreach { case Seq(x, y) => x.foreach { a => y.foreach { b => require (!a.overlaps(b), s"Ports cannot overlap: $a $b") } } }
これはつまりXbar
に接続されているすべてのノードの組み合わせを作成して、そのアドレスマップが被っていないかどうかを確認するわけだ。そのためにcombinations
を使用している。
ちなみに、このAddressDecoder
が実行されるまでは以下のようなフローを通る。
val lazy_dut = LazyModule(new TLManyMasterManySlave(ramBeatBytes, txns)) val dut = Module(lazy_dut.module) ... class TLManyMasterManySlave(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule { ... lazy val module = new LazyModuleImp(this) with UnitTestModule { ...
LazyModuleImpl
により実際にモジュールがインスタンス化される。
class LazyModuleImp(val wrapper: LazyModule) extends MultiIOModule with LazyModuleImpLike { val (auto, dangles) = instantiate() } ... sealed trait LazyModuleImpLike extends RawModule { protected[diplomacy] def instantiate() = { val childDangles = wrapper.children.reverse.flatMap { c => implicit val sourceInfo = c.info val mod = Module(c.module) mod.finishInstantiate() mod.dangles } ...
ここでc.module
は以下のLazyModuleがインスタンス化される。
freechips.rocketchip.tilelink.TLPatternPusher@7a2ef887 freechips.rocketchip.tilelink.TLPatternPusher@219e6439 freechips.rocketchip.tilelink.TLRAMModel@70463050 freechips.rocketchip.tilelink.TLRAMModel@4a8d3a7f freechips.rocketchip.tilelink.TLXbar@403537fe
この5つのモジュールはどのようにして認識されているかというと、LazyModule
をインスタンス化したときにchildren
メンバ変数にこれらのモジュールが登録されているわけだ。
val model0 = LazyModule(new TLRAMModel("SRAMSimple")) val model1 = LazyModule(new TLRAMModel("SRAMSimple")) val xbar = LazyModule(new TLXbar) val ram0 = LazyModule(new TLRAM(AddressSet(0x000, 0x3ff), beatBytes = ramBeatBytes)) val ram1 = LazyModule(new TLRAM(AddressSet(0x400, 0x3ff), beatBytes = ramBeatBytes))
rocketchip/src/main/scala/diplomacy/LazyModule.scala
def apply[T <: LazyModule](bc: T)(implicit valName: ValName, sourceInfo: SourceInfo): T = {
...
scope = bc.parent
bc.info = sourceInfo
...
メンバ変数scope
に、登録するモジュールbc
の親に当たるモジュールが登録されている。つまりscope
はそのLazyModule
の親クラスが登録されている。このメンバ変数を起点に、children
が探索されるわけだ。
abstract class LazyModule()(implicit val p: Parameters) ... def parents: Seq[LazyModule] = parent match { case None => Nil case Some(x) => x +: x.parents } LazyModule.scope = Some(this) parent.foreach(p => p.children = this :: p.children)
TLXbar
をインスタンス化されたときにAddressDecoder
によるアドレス領域チェックが実行される。
rocketchip/src/main/scala/tilelink/Xbar.scala
lazy val module = new LazyModuleImp(this) { ... // Based on input=>output connectivity, create per-input minimal address decode circuits val requiredAC = (connectAIO ++ connectCIO).distinct val outputPortFns: Map[Vector[Boolean], Seq[UInt => Bool]] = requiredAC.map { connectO => val port_addrs = edgesOut.map(_.manager.managers.flatMap(_.address)) val routingMask = AddressDecoder(filter(port_addrs, connectO)) val route_addrs = port_addrs.map(seq => AddressSet.unify(seq.map(_.widen(~routingMask)).distinct))
ここでAddressDecoder
が呼ばれる。println
を使ってアドレスマップの組み合わせを確認してみる。テストとして下記のようにスレーブを3つ定義した場合の様子を確認してみる。
val ram0 = LazyModule(new TLRAM(AddressSet(0x000, 0x3ff), beatBytes = ramBeatBytes)) val ram1 = LazyModule(new TLRAM(AddressSet(0x400, 0x3ff), beatBytes = ramBeatBytes)) val ram2 = LazyModule(new TLRAM(AddressSet(0x800, 0x3ff), beatBytes = ramBeatBytes)) ... ram0.node := xbar.node ram1.node := xbar.node ram2.node := xbar.node
Compare AddressSet(0x0, 0x3ff) and AddressSet(0x400, 0x3ff) Compare AddressSet(0x0, 0x3ff) and AddressSet(0x800, 0x3ff) Compare AddressSet(0x400, 0x3ff) and AddressSet(0x800, 0x3ff)