FPGA開発日記

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

Diplomacyを使ってOCPバスを作成する (10. OCP Bufferの動作確認)

Diplomacyを使って独自OCPバスの作成を行っているのだが、どうも上手く行かない。いきなりXbarを作るのはさすがに難易度が高かったか。とりあえずまずはBufferだけを作って動作を確認する。

Bufferは、単純に信号を右から左に渡すだけの機能だ。ただ渡すだけでなく、スライサを挿入したりすることもできる。

chipyard.readthedocs.io

Bufferの実装だが、内部は単純に右から左に信号を渡すだけとなっている。

  • rocketchip/src/main/scala/tilelink/Buffer.scala
class TLBuffer(
  a: BufferParams,
  b: BufferParams,
  c: BufferParams,
  d: BufferParams,
  e: BufferParams)(implicit p: Parameters) extends LazyModule
{
  def this(ace: BufferParams, bd: BufferParams)(implicit p: Parameters) = this(ace, bd, ace, bd, ace)
  def this(abcde: BufferParams)(implicit p: Parameters) = this(abcde, abcde)
  def this()(implicit p: Parameters) = this(BufferParams.default)

  val node = new TLBufferNode(a, b, c, d, e)

  lazy val module = new LazyModuleImp(this) {
    (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) =>
      out.a <> a(in .a)
      in .d <> d(out.d)

      if (edgeOut.manager.anySupportAcquireB && edgeOut.client.anySupportProbe) {
        in .b <> b(out.b)
        out.c <> c(in .c)
        out.e <> e(in .e)
      } else {
        in.b.valid := Bool(false)
        in.c.ready := Bool(true)
        in.e.ready := Bool(true)
        out.b.ready := Bool(true)
        out.c.valid := Bool(false)
        out.e.valid := Bool(false)
      }
    }
  }
}

TL-ULの場合は単純にout.a <> a(in .a)in .d <> d(out.d)が有効で、このadはTLBufferのインスタンス化時に渡されているパラメータだ。BufferParamsというのは、

  • rocketchip/src/main/scala/diplomacy/Parameters.scala
case class BufferParams(depth: Int, flow: Boolean, pipe: Boolean)
{
  require (depth >= 0, "Buffer depth must be >= 0")
  def isDefined = depth > 0
  def latency = if (isDefined && !flow) 1 else 0

  def apply[T <: Data](x: DecoupledIO[T]) =
    if (isDefined) Queue(x, depth, flow=flow, pipe=pipe)
    else x

  def sq[T <: Data](x: DecoupledIO[T]) =
    if (!isDefined) x else {
      val sq = Module(new ShiftQueue(x.bits, depth, flow=flow, pipe=pipe))
      sq.io.enq <> x
      sq.io.deq
    }

  override def toString() = "BufferParams:%d%s%s".format(depth, if (flow) "F" else "", if (pipe) "P" else "")

}

このように、単純にa(io:DecoupledIO)という形式で呼ばれた場合、depthが1以上の場合はQueueが挿入され、そうでない場合はそのまま渡される。

同様に、OCPの実装にもBufferを作ってみよう。

class OCPBuffer(
  a: BufferCmdParams,
  c: BufferParams,
  d: BufferParams)(implicit p: Parameters) extends LazyModule
{
  def this(cmd: BufferCmdParams, ace: BufferParams)(implicit p: Parameters) = this(cmd, ace, ace)
  def this()(implicit p: Parameters) = this(BufferCmdParams.default, BufferParams.default)

  val node = new OCPBufferNode(a, c, d)

  lazy val module = new LazyModuleImp(this) {
    (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) =>
      out.cmd  <> a(in .cmd )
      out.data <> c(in .data)
      in .resp <> d(out.resp)
    }
  }
}

同様にすべてのチャネルを接続したが、どうだろうか?BufferCmdParamsは、DecoupledIOではなくCmdReadyIO用に作った特別なパラメータで、

case class BufferCmdParams(depth: Int, flow: Boolean, pipe: Boolean)
{
  require (depth >= 0, "Buffer depth must be >= 0")
  def isDefined = depth > 0
  def latency = if (isDefined && !flow) 1 else 0

  def apply[T <: Data](x: CmdReadyIO[T]) =
    if (isDefined) CmdQueue(x, depth, flow=flow, pipe=pipe)
    else x

  override def toString() = "BufferCmdParams:%d%s%s".format(depth, if (flow) "F" else "", if (pipe) "P" else "")
}

としてこちらも単純に信号を受け渡す仕組みにした。

これでFIRを生成してみたのだが、どうもCmdReadyIOが上手く接続してくれない。Ready信号が消えてしまっている?

  val ram0 = LazyModule(new OCPRAM(AddressSet(0x000, 0x3ff)))
  val buffer = LazyModule(new OCPBuffer())

  ram0.node := buffer.node := pusher.node

Bufferを使用しないとシミュレーションには成功するので、何かがおかしい。

  ram0.node := pusher.node

いろいろ確認すると、CmdQueueの実装が誤っていたことが分かったので修正。再度シミュレーションを実行すると上手く動作した。

f:id:msyksphinz:20200222130028p:plain
OCP Bufferを間に挟んだ場合のRTLシミュレーション