FPGA開発日記

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

Diplomacyを使ってOCPバスを作成する方法の検討

DiplomacyにはTileLinkのウィジェット、AMBA(AXI4, APB, AHB)のウィジェットがあり、これらを自由に組み合わせてタイルを構成することができる。これがDiplomacyのメリットであり、これらの部品を使ってアジャイルにSoCバスを構成することができる。

これまででTileLink/AXI4のウィジェットを使ってDiplomacyの実験を行ってきたが、より深くDiplomacyについて学んでいきたい。このために、例としてOCP(Open Core Protocol)のDiplomacyウィジェットを作るためにはどうすればよいのか考えてみたいと思う。OCPの仕様書は以下のウェブサイトに公開されており、誰でも入手することができる。

https://www.accellera.org/downloads/standards/ocp/files

OCPの実装をしなくなって久しいので仕様を若干忘れてしまっているのだが、基本的に以下のチャネルを作れば良いと理解している。

  • コマンドチャネル : マスターからスレーブに対してコマンドを送信する。Read/Writeなどのコマンドや、アドレスなどが含まれる。
  • 書き込みデータチャネル : マスターからスレーブに対して書き込みデータを送信する。このチャネルはコマンドチャネルとは非同期に動作することができる。
  • レスポンスデータチャネル : スレーブからマスターに対して読み込みデータを送信する。このチャネルはコマンドチャネル・データチャネルとは非同期に動作することができる。

TileLinkチャネルはABCDEの5種類が定義されているが、OCPの場合は5つだ。これにはコヒーレントの動作が含まれていないのだが、とりあえず簡単なものから作り込んでいきたい。

OCPバンドルの作成

新しいDiplomacyの作成方法についてガイドラインなどあるわけがないので試行錯誤になるのだが、TileLinkのバス設計のディレクトリを見ていると、まずはバンドルの定義が必要に思える。

  • src/main/scala/ocp/Bundles.scala

ここにOCPバンドルの基本的な定義を作り込んでいくのから始めればよいのかな?まずはメッセージの定義だ。

package freechips.rocketchip.ocp   // OCPバンドルのパッケージを作った。

// OCPメッセージの定義
object OCPMessages
{
  def Write        = 1.U(3.W)
  def Read         = 2.U(3.W)
  def ReadEx       = 3.U(3.W)
  def ReadLinked   = 4.U(3.W)
  def WriteNonPost = 5.U(3.W)
  def WriteCond    = 6.U(3.W)
  def Broadcast    = 7.U(3.W)

  def DataValid = 1.U(2.W)
  def ReqFail   = 2.U(2.W)
  def RespError = 3.U(2.W)
...

次にチャネルを定義する。Cmd / Data / Resp を定義する。

  • コマンドチャネル
final class OCPBundleCmd(params: OCPBundleParameters)
  extends OCPBundleBase(params) with OCPAddrChannel
{
  val channelName = "'Cmd' channel"
  // fixed fields during multibeat:
  val mcmd    = UInt(3.W)
  val mtagid  = UInt(params.tagidBits.W) // from
  val address = UInt(params.addressBits.W) // to
}
  • データチャネル
final class OCPBundleData(params: OCPBundleParameters)
  extends OCPBundleBase(params) with OCPAddrChannel
{
  val channelName = "'Data' channel"
  // fixed fields during multibeat:
  val mdatatagid = UInt(params.tagidBits.W ) // to
  val data       = UInt((params.dataBits).W )
}
  • レスポンスチャネル
final class OCPBundleResp(params: OCPBundleParameters)
  extends OCPBundleBase(params) with OCPDataChannel
{
  val channelName = "'Resp' channel"

  // fixed fields during multibeat:
  val mresptagid = UInt(params.tagidBits.W) // to
  val data       = UInt(params.dataBits.W )
}

バンドルとしてはこれらを合わせて以下のようにOCPBundleを定義した。

class OCPBundle(val params: OCPBundleParameters) extends Record
{
  // Emulate a Bundle with elements abcde or ad depending on params.hasBCE

  private val optCmd  = Some (Decoupled(new OCPBundleCmd (params)))
  private val optData = Some (Decoupled(new OCPBundleData(params)))
  private val optResp = Some (Decoupled(new OCPBundleResp(params)).flip)

  def cmd : DecoupledIO[OCPBundleCmd ] = optCmd. getOrElse(Wire(Decoupled(new OCPBundleCmd(params))))
  def data: DecoupledIO[OCPBundleData] = optData.getOrElse(Wire(Decoupled(new OCPBundleData(params))))
  def resp: DecoupledIO[OCPBundleResp] = optResp.getOrElse(Wire(Decoupled(new OCPBundleResp(params))))

  override def cloneType: this.type = (new OCPBundle(params)).asInstanceOf[this.type]
  val elements = ListMap("cmd" -> cmd, "data" -> data, "resp" -> resp)

  def tieoff() {
    cmd.ready.dir match {
      case INPUT =>
        cmd.ready  := false.B
        data.ready := false.B
        resp.valid := false.B
      case OUTPUT =>
        cmd.valid  := false.B
        data.valid := false.B
        resp.ready := false.B
      case _ =>
    }
  }
}

次にOCPと対応するパラメータを定義する。上記の実装にはOCPBundleParametersというものが含まれているが、これを作り込んでいくのがParameters.scalaだ。

  • src/main/scala/ocp/Parameters.scala
case class OCPBundleParameters(
  addressBits: Int,
  dataBits:    Int,
  tagidBits:   Int,
  sizeBits:    Int)
{
  // Chisel has issues with 0-width wires
  require (addressBits >= 1)
  require (dataBits    >= 8)
  require (tagidBits   >= 1)
  require (isPow2(dataBits))

  val addrLoBits = log2Up(dataBits/8)

  def union(x: OCPBundleParameters) =
    OCPBundleParameters(
      max(addressBits, x.addressBits),
      max(dataBits,    x.dataBits),
      max(tagidBits,   x.tagidBits),
      max(sizeBits,    x.sizeBits)
    )
}

object OCPBundleParameters
{
  val emptyBundleParams = OCPBundleParameters(
    addressBits = 1,
    dataBits    = 8,
    tagidBits  = 1,
    sizeBits    = 1
  )

  def union(x: Seq[OCPBundleParameters]) = x.foldLeft(emptyBundleParams)((x,y) => x.union(y))

  def apply(client: OCPClientPortParameters, manager: OCPManagerPortParameters) =
    new OCPBundleParameters(
      addressBits = log2Up(manager.maxAddress + 1),
      dataBits    = manager.beatBytes * 8,
      tagidBits   = log2Up(client.endTagidId),
      sizeBits    = log2Up(log2Ceil(max(client.maxTransfer, manager.maxTransfer))+1)
    )
}

OCPClientPortParametersOCPManagerPortParametersも作り込んでいこう。これらのパラメータはバスのパラメータとしてコンフィギャラブルな要素となる。