FPGA開発日記

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

ChiselとDiplomacyを使ってオリジナルデザインを作成してみる (1. 開発環境の構築)

久しぶりにChiselで何か作ってみたくなった。Chiselを使うならDiplomacyを使わないと意味ないだろ!ということで久しぶりにDiplomacyの資料を取り出して読み直している。少し新しいデザインを作りながら、Diplomacyの作り方を復習している。

f:id:msyksphinz:20201003014732p:plain

Diplomacyを使ったTileLinkスレーブノードの作成

Diplomacyを使ったスレーブノードには、以下の2レイヤが必要となる。

  1. LazyModuleを使ったDiplomacyノードを持つクラス
  2. Moduleを使ったDiplomacyノードに接続される実際のクラス

  3. chisel-hw/src/main/scala/TLUnitTest/TLSlaveReg.scala

class TLSlaveReg(
  address: AddressSet,
  parentLogicalTreeNode: Option[LogicalTreeNode] = None,
  beatBytes: Int = 4,
  val devName: Option[String] = None,
  val dtsCompat: Option[Seq[String]] = None
)(implicit p: Parameters) extends LazyModule
...

実際のノードは以下のように作成する。TLManagerNodeなので、スレーブデバイスとして動作させるわけだ。

  • chisel-hw/src/main/scala/TLUnitTest/TLSlaveReg.scala
  val node = TLManagerNode(Seq(TLManagerPortParameters(
    Seq(TLManagerParameters(
      address            = List(address),
      resources          = device.reg("TLSlaveReg"),
      regionType         = RegionType.IDEMPOTENT,
      executable         = false,
      supportsGet        = TransferSizes(1, beatBytes),
...

実際のノードはLazyModuleImplを使って実装される。LazyModuleImplには、nodeからのDiplomacyノードを引き込む形になっている。

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

inは実際のノードの配線を示し、edgeはノードのパラメータなどの情報が入っている。

  • inの使い方は、実際にTileLinkのノードを引き込んで制御するために使う。
  lazy val module = new LazyModuleImp(this) {
    val (in, edge) = node.in(0)
...
    val a = in.a
    val d = in.d
...
    val a_read  = a.bits.opcode === TLMessages.Get
    val a_write = a.bits.opcode =/= TLMessages.Get
    val a_extra = Cat(a.bits.source, a.bits.size)

    in.a.ready := true.B

    when (a.fire && a_write) { printf("A.Write Addr = %x, Data = %x", a.bits.address, a.bits.data) }
    when (a.fire && a_read ) { printf("A.Read  Addr = %x", a.bits.address) }

    when (a_write) {
      counter := counter + a.bits.data
    }
  • edgeの使い方は、TileLink接続ノードのパラメータを参照するときに使う。
  lazy val module = new LazyModuleImp(this) {
    val (in, edge) = node.in(0)

    val baseEnd = 0
    val (sizeEnd,   sizeOff)   = (edge.bundle.sizeBits   + baseEnd, baseEnd)
    val (sourceEnd, sourceOff) = (edge.bundle.sourceBits + sizeEnd, sizeEnd)
...
    d.bits := edge.AccessAck(
      toSource    = a_extra(sourceEnd-1, sourceOff),
      lgSize      = a_extra(sizeEnd-1, sizeOff))

実際に接続してみよう。TLOriginalSlave.scalaを作成して接続してみる。

slavereg0slavereg1を作成した。LazyModuleインスタンス化して、AddressSetをそれぞれ0x000, 0x400, 0x400, 0x800を作成した。

  • chisel-hw/src/main/scala/TLUnitTest/TLOriginalSlave.scala
class TLOriginalSlave(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
...
  val xbar      = LazyModule(new TLXbar)
  val slavereg0 = LazyModule(new TLSlaveReg(AddressSet(0x000, 0x3ff), beatBytes = ramBeatBytes))
  val slavereg1 = LazyModule(new TLSlaveReg(AddressSet(0x400, 0x3ff), beatBytes = ramBeatBytes))

  pushers.zip(model).map{ case (pusher, model) =>
    xbar.node := model.node := pusher.node
  }
  slavereg0.node := xbar.node
  slavereg1.node := xbar.node
...

これでVerilogを生成すると、オリジナルのスレーブノードが接続されたVerilogファイルを生成できた。一応シミュレーションも動いている。もう少しデバッグを進めたいかな。

TLSlaveRegs

以下のようにしてMakefileを作成し、自動的にVerilogが生成されるようにした。

PROJECT ?= freechips.rocketchip.unittest
CONFIG  ?= TLOriginalUnitTestConfig
CONFIG_FIR ?= $(PROJECT).$(CONFIG).fir

JAVA_HEAP_SIZE ?= 8G
JAVA_ARGS ?= -Xmx$(JAVA_HEAP_SIZE) -Xss8M -XX:MaxPermSize=256M

export JAVA_ARGS

include ../../Makefrag-verilator

tilelink: TestHarness.sv
    mkdir -p $(generated_dir_debug)/$(long_name)
    $(VERILATOR) $(VERILATOR_FLAGS) -Mdir $(generated_dir_debug)/$(long_name) \
    -o $(abspath $(sim_dir))/$@ $(verilog) $(cppfiles) -LDFLAGS "$(LDFLAGS)" \
    -CFLAGS "-I$(generated_dir_debug) -include $(model_header_debug)"
    $(MAKE) VM_PARALLEL_BUILDS=1 -C $(generated_dir_debug)/$(long_name) -f V$(MODEL).mk
    ./$@
$ make tilelink CONFIG=TLOriginalSlaveTestConfig