FPGA開発日記

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

TileLinkのDiplomacyデザインの解析 (1. AddressDecoderによるアドレス重複チェック)

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))
...
f:id:msyksphinz:20200108233715p:plain:w400
TLManyMasterManySlaveのデザイン
[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が実行されるまでは以下のようなフローを通る。

  1. TLManyMasterManySlaveでモジュールがインスタンス化される。modulelazy valなのでここで実際に変数がインスタンス化される。
  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 {
...
  1. 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)