FPGA開発日記

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

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

Diplomacyを使ってOCPのバスを作成するプロジェクト。PatternPusherウィジェットの次はSRAMを作成する。SRAMを定義することでクライアントとマネージャの両方を定義することで、やっと回路を構築することができるようになる。OCPのSRAMを定義してみよう。

SRAMはスレーブノードなので、OCPManegerNodeを使用することになる。TileLinkのSRAMをまずはそのまま流用する。

  • chisel-hw/src/main/scala/ocp/SRAM.scala
class OCPRAM(
    address: AddressSet,
    parentLogicalTreeNode: Option[LogicalTreeNode] = None,
    cacheable: Boolean = true,
    executable: Boolean = true,
    atomics: Boolean = false,
    beatBytes: Int = 4,
    ecc: ECCParams = ECCParams(),
    val devName: Option[String] = None,
    val dtsCompat: Option[Seq[String]] = None
  )(implicit p: Parameters) extends DiplomaticSRAM(address, beatBytes, devName, dtsCompat)
{
  val node = OCPManagerNode(Seq(OCPManagerPortParameters(
    Seq(OCPManagerParameters(
      address            = List(address),
      resources          = device.reg("mem"),
      regionType         = if (cacheable) RegionType.UNCACHED else RegionType.IDEMPOTENT,
      executable         = executable,
      supportsGet        = TransferSizes(1, beatBytes),
      supportsPutPartial = TransferSizes(1, beatBytes),
      supportsPutFull    = TransferSizes(1, beatBytes),
      supportsArithmetic = if (atomics) TransferSizes(1, beatBytes) else TransferSizes.none,
      supportsLogical    = if (atomics) TransferSizes(1, beatBytes) else TransferSizes.none,
      fifoId             = Some(0))), // requests are handled in order
    beatBytes  = beatBytes,
    minLatency = 1))) // no bypass needed for this device

supportsGetとか全然使わないけど、とりあえずそのままTileLinkから持ってきて定義している。

中身の実装は以下のようになっている。ガバガバで全然OCPのプロトコルを守っていないが(それどころか連続でリクエストを持ってこられると普通にデータを失ってしまう)、まずはVerilogが生成できるところまで持って行きたい。

  lazy val module = new LazyModuleImp(this) {
    val (in, edge) = node.in(0)

    val sram = Reg(Vec(256, UInt(32.W)))
    val cmd_addr = Reg(UInt(8.W))

    when (in.cmd.valid && in.cmd.bits.mcmd === OCPMessages.Write) {
      cmd_addr := in.cmd.bits.address(7, 0)
    }

    when (in.cmd.valid && in.cmd.bits.mcmd === OCPMessages.Read) {
      in.resp.valid := true.B
      in.resp.bits.data := sram(in.cmd.bits.address)
    } .otherwise {
      in.resp.valid := false.B
    }

    when (in.data.valid) {
      sram(cmd_addr) := in.data.bits.data
    }

    in.cmd.ready := true.B
    in.data.ready := true.B
  }

スレーブノードを作成したので、次にマスターとスレーブを接続してOCPタイルを作ってみる。

  • chisel-hw/src/main/scala/ocp/OCPUnitTest.scala
class OCPUnitTest()(implicit p: Parameters) extends LazyModule {
  val pusher = LazyModule(new OCPPatternPusher("pat1", Seq(
    new WritePattern(0x100, 0x2, 0x012345678L),
    new WritePattern(0x500, 0x2, 0x0abcdef01L),
    new ReadExpectPattern(0x100, 0x2, 0x012345678L),
    new ReadExpectPattern(0x500, 0x2, 0x0abcdef01L)
  )))
  val ram = LazyModule(new OCPRAM(AddressSet(0x0, 0x3ff)))

  ram.node := pusher.node

  lazy val module = new LazyModuleImp(this) with UnitTestModule {
    pusher.module.io.run := true.B
    io.finished := pusher.module.io.done
  }
}

単純にOCPPatternPusherOCPSRAMを接続しただけである。GraphMLを生成してみよう。

f:id:msyksphinz:20200121003459p:plain
OCPマスターとスレーブを接続した図。

良さそうだ。しかし、Verilogを生成してみるとバスごと消されてしまっていた。。。これは要解析。

module OCPUnitTest(
  input   clock,
  input   reset,
  output  io_finished
);
  wire  pusher_clock; // @[OCPUnitTest.scala 18:26]
  wire  pusher_reset; // @[OCPUnitTest.scala 18:26]
  wire  pusher_io_done; // @[OCPUnitTest.scala 18:26]
  OCPPatternPusher pusher ( // @[OCPUnitTest.scala 18:26]
    .clock(pusher_clock),
    .reset(pusher_reset),
    .io_done(pusher_io_done)
  );
  assign io_finished = pusher_io_done; // @[OCPUnitTest.scala 30:17]
  assign pusher_clock = clock;
  assign pusher_reset = reset;