FPGA開発日記

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

Diplomacyを使ってOCPバスを作成する方法の検討(2. PatternPusherを作る)

Diplomacyを使ってOCPのバスを作成するプロジェクト。 バスの定義があってもまずはウィジェットが無いと始まらない。 OCPウィジェットのPatternPusherを作成する。

PatternPusherはOCPのマスター(OCPClientNode)として働く。TileLinkにもPatternPusherがあるので、これを流用して改造を行う。とりあえず用意するのはWritePattern, ReadPattern, ReadExpectPatternだ。

  • WritePattern : OCPバスに対して書き込みを行う。
  • ReadPattern : OCPバスに対して読み込みを行う。
  • ReadExpectPattern : OCPバスに対して読み込みを行い、想定するデータと比較する。
case class WritePattern(address: BigInt, size: Int, data: BigInt) extends Pattern
{
  require (0 <= data && data < (BigInt(1) << (8 << size)))
  def bits(edge: OCPEdgeOut) = edge.Write(0.U, address.U, size.U, (data << (8*(address % edge.manager.beatBytes).toInt)).U)
}

case class ReadPattern(address: BigInt, size: Int) extends Pattern
{
  def bits(edge: OCPEdgeOut) = edge.Read(0.U, address.U, size.U)
}

case class ReadExpectPattern(address: BigInt, size: Int, data: BigInt) extends Pattern
{
  def bits(edge: OCPEdgeOut) = edge.Read(0.U, address.U, size.U)
  override def dataIn = Some(data)
}

次にOCPPaternPusherの定義を行う。これはTLPatternPusherと同様に作成する。

class OCPPatternPusher(name: String, pattern: Seq[Pattern])(implicit p: Parameters) extends LazyModule
{
  val node = OCPClientNode(Seq(OCPClientPortParameters(Seq(OCPClientParameters(name = name)))))

  lazy val module = new LazyModuleImp(this) {
    val io = IO(new Bundle {
      val run  = Input(Bool())
      val done = Output(Bool())
    })
...

まず、OCPClientNode型のノードを作成する。これがDiplomacyのノードにあたるものだ。OCPClientNodeおよびOCPManagerNodeの定義は以下のようになっている。

  • chisel-hw/src/main/scala/ocp/Nodes.scala
trait OCPFormatNode extends FormatNode[OCPEdgeIn, OCPEdgeOut]

case class OCPClientNode(portParams: Seq[OCPClientPortParameters])(implicit valName: ValName) extends SourceNode(OCPImp)(portParams) with OCPFormatNode

case class OCPManagerNode(portParams: Seq[OCPManagerPortParameters])(implicit valName: ValName) extends SinkNode(OCPImp)(portParams) with OCPFormatNode

長ったらしくて難しいのだが、

  • 基本はSourceNodeおよびSinkNodeから拡張しているということ。OCPFormatNodeをトレイトとして使っている。

SourceNodeおよびSinkNodeの引数となっているOCPImpは以下のように定義しており、ノードの定義となっている。今回は面倒なのでモニターの定義は省略した。

object OCPImp extends NodeImp[OCPClientPortParameters, OCPManagerPortParameters, OCPEdgeOut, OCPEdgeIn, OCPBundle]
{
  def edgeO(pd: OCPClientPortParameters, pu: OCPManagerPortParameters, p: Parameters, sourceInfo: SourceInfo) = new OCPEdgeOut(pd, pu, p, sourceInfo)
  def edgeI(pd: OCPClientPortParameters, pu: OCPManagerPortParameters, p: Parameters, sourceInfo: SourceInfo) = new OCPEdgeIn (pd, pu, p, sourceInfo)

  def bundleO(eo: OCPEdgeOut) = OCPBundle(eo.bundle)
  def bundleI(ei: OCPEdgeIn)  = OCPBundle(ei.bundle)

  def render(ei: OCPEdgeIn) = RenderedEdge(colour = "#000000" /* black */, label = (ei.manager.beatBytes * 8).toString)

  // override def monitor(bundle: OCPBundle, edge: OCPEdgeIn) {
  //   val monitor = Module(edge.params(OCPMonitorBuilder)(OCPMonitorArgs(edge)))
  //   monitor.io.in := bundle
  // }

  override def mixO(pd: OCPClientPortParameters, node: OutwardNode[OCPClientPortParameters, OCPManagerPortParameters, OCPBundle]): OCPClientPortParameters  =
    pd.copy(clients  = pd.clients.map  { c => c.copy (nodePath = node +: c.nodePath) })
  override def mixI(pu: OCPManagerPortParameters, node: InwardNode[OCPClientPortParameters, OCPManagerPortParameters, OCPBundle]): OCPManagerPortParameters =
    pu.copy(managers = pu.managers.map { m => m.copy (nodePath = node +: m.nodePath) })
}
  • edgeO : 新しいOCPEdgeOutノードを作成する。
  • edgiI : 新しいOCPEdgeInノードを作成する。

ぶっちゃけこれ以上は良く分からない。ひたすらTileLinkのノードを置き換えていっただけである...

次にOCPのノードの中で最も基本となるバッファを定義する。バッファの名前はOCPBufferである。

class OCPBuffer(
  a: BufferParams,
  c: BufferParams,
  d: BufferParams)(implicit p: Parameters) extends LazyModule
{
  def this(ace: BufferParams)(implicit p: Parameters) = this(ace, ace, ace)
  def this()(implicit p: Parameters) = this(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)
    }
  }
}

こちらもDiplomacyを使って定義している。中身は単純にノードの入力とノードの出力をすべてつなぎ合わせるだけである。複数ノードが接続されている場合はそれぞれペアにしてつないでいく。

次にオブジェクトの方を定義する。

object OCPBuffer
{
  def apply()                                   (implicit p: Parameters): OCPNode = apply(BufferParams.default)
  def apply(abcde: BufferParams)                (implicit p: Parameters): OCPNode = apply(abcde, abcde, abcde)
  def apply(
      a: BufferParams,
      c: BufferParams,
      d: BufferParams)(implicit p: Parameters): OCPNode =
  {
    val buffer = LazyModule(new OCPBuffer(a, c, d))
    buffer.node
  }
    ...

こちらも単純にapply()を定義している。これでOCPバッファが接続定義できるようになった。