ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Rocket ChipのGeneratorのソースの解析メモ(3) - TestHarnessの中身

スポンサーリンク

Rocket Chip環境の仕組み解析メモの垂れ流し記事。前回のgenerateFirrtlで実際にChiselのモジュールとして回路化されるTestHarnessを見ていく。

TestHarness

前回の記事の終わりの方で書いたとおり"emulator"ディレクトリの下でmakeコマンドを実行した際に構築されるChiselのモジュールは

  • src/main/scala/system/TestHarness.scalaの中のTestHarness()クラス

であった。

でこのクラスの中身だが、以下のような物になっている。

class TestHarness()(implicit p: Parameters) extends Module {
  val io = new Bundle {
    val success = Bool(OUTPUT)
  }

  val dut = Module(LazyModule(new ExampleRocketSystem).module)
  dut.reset := reset | dut.debug.ndreset

  dut.dontTouchPorts()
  dut.tieOffInterrupts()
  dut.connectSimAXIMem()
  dut.connectSimAXIMMIO()
  dut.l2_frontend_bus_axi4.foreach(_.tieoff)
  Debug.connectDebug(dut.debug, clock, reset, io.success)
}

ものすごく、、、、、シンプル。とてもこれだけであのRTLが生成されるとは思えない。 またRocket Chipの売りである”Genratorへ渡すパラメータクラスだけで、構成が変更できる”という部分が単純に見ただけだとわからない。

そのカスタマイズ性を作り出しているのがLazyModuleの力ということになる。とりあえずLazyModule自体については置いておくことにする。。

で上記の中で実際にやっているのは、以下のような処理だけ。

  // ExampleRocketSystemのインスタンス
  val dut = Module(LazyModule(new ExampleRocketSystem).module)

  // リセットの接続
  dut.reset := reset | dut.debug.ndreset

  // dutのDon't touch指定
  dut.dontTouchPorts()

  // 割り込みの0クリップ
  dut.tieOffInterrupts()

  // シミュレーション用のAXIメモリの接続
  dut.connectSimAXIMem()

  // シミュレーション用のAXI-MMIOメモリの接続
  dut.connectSimAXIMMIO()

  // L2 Frontendバスとnodeの接続
  dut.l2_frontend_bus_axi4.foreach(_.tieoff)

  // デバッグ関連の接続
  Debug.connectDebug(dut.debug, clock, reset, io.success)

正直、まだ追いきれてないけど上記の各接続処理の中身も見ておく。

dontTouchPorts

このメソッドはtrait DontTouchの中に定義されているもの。

  /** Marks every port as don't touch
    *
    * @note This method can only be called after the Module has been fully constructed
    *   (after Module(...))
    */
  def dontTouchPorts(): this.type = {
    self.getModulePorts.foreach(dontTouch(_))
    self
  }

self.dontTouchPortsとした際のselfTestHarnessdutになる。

dut内部のモジュールのポートをforeachで処理してdontTouchに渡して処理する。最終的にはChiselのdontTouchが呼ばれるようになっている。

tieOffInterrupts

/** Common io name and methods for propagating or tying off the port bundle */
trait HasExtInterruptsBundle {
  val interrupts: UInt

  def tieOffInterrupts(dummy: Int = 1) {
    interrupts := UInt(0)
  }
}

interruptsを宣言だけしておいて、それにUInt(0)を設定している。

connectSimAXIMem

このメソッドはtrait CanHaveMasterAXI4MemPortModuleImpの中に定義されているもの。

  val mem_axi4 = outer.memAXI4Node.map(x => IO(HeterogeneousBag.fromNode(x.in)))
  (mem_axi4 zip outer.memAXI4Node) foreach { case (io, node) =>
    (io zip node.in).foreach { case (io, (bundle, _)) => io <> bundle }
  }

  def connectSimAXIMem() {
    (mem_axi4 zip outer.memAXI4Node).foreach { case (io, node) =>
      (io zip node.in).foreach { case (io, (_, edge)) =>
        val mem = LazyModule(new SimAXIMem(edge, size = p(ExtMem).get.master.size))
        Module(mem.module).io.axi4.head <> io
      }
    }
  }

val mem_axi4にRocket Chip側のIO情報が入っていて、それをconnectSimAXIMemで接続しているような感じに見える。

connectSimAXIMMIO

このメソッドはtrait CanHaveMasterAXI4MMIOPortModuleImpの中に定義されているもの。

  val mmio_axi4 = IO(HeterogeneousBag.fromNode(outer.mmioAXI4Node.in))

  (mmio_axi4 zip outer.mmioAXI4Node.in) foreach { case (io, (bundle, _)) => io <> bundle }

  def connectSimAXIMMIO() {
    (mmio_axi4 zip outer.mmioAXI4Node.in) foreach { case (io, (_, edge)) =>
      // test harness size capped to 4KB (ignoring p(ExtMem).get.master.size)
      val mmio_mem = LazyModule(new SimAXIMem(edge, size = 4096))
      Module(mmio_mem.module).io.axi4.head <> io
    }
  }

これに関しては先程のconnectSimAXIMemとほぼ一緒。

dut.l2_frontend_bus_axi4.foreach(_.tieoff)

この処理の中のl2_frontend_bus_axi4は以下。

/** Actually generates the corresponding IO in the concrete Module */
trait CanHaveSlaveAXI4PortModuleImp extends LazyModuleImp {
  val outer: CanHaveSlaveAXI4Port
  val l2_frontend_bus_axi4 = IO(HeterogeneousBag.fromNode(outer.l2FrontendAXI4Node.out).flip)
  (outer.l2FrontendAXI4Node.out zip l2_frontend_bus_axi4) foreach { case ((bundle, _), io) => bundle <> io }
}

l2_frontend_bus_axi4自体は先程のconnectSimAXIMemmem_axi4と同じ形に見える。 実際にはouterl2FrontendAXI4Node.outというポートを反転したものに対して_.tieoffを実施、なのだがtieoffが見つからない。

名前からだと先ほどのtieOffInterruptsと一緒な気がするのだが。。。

Debug.connectDebug

これは引数の各種ポートと各種テストモジュールを接続する感じの処理。

  • src/main/scla/devices/debug/Periphery.scala
  def connectDebug(
      debug: DebugIO,
      c: Clock,
      r: Bool,
      out: Bool,
      tckHalfPeriod: Int = 2,
      cmdDelay: Int = 2,
      psd: PSDTestMode = new PSDTestMode().fromBits(0.U))
      (implicit p: Parameters): Unit =  {
    debug.clockeddmi.foreach { d =>
      val dtm = Module(new SimDTM).connect(c, r, d, out)
    }
    debug.systemjtag.foreach { sj =>
      val jtag = Module(new SimJTAG(tickDelay=3)).connect(sj.jtag, c, r, ~r, out)
      sj.reset := r
      sj.mfr_id := p(JtagDTMKey).idcodeManufId.U(11.W)
    }
    debug.psd.foreach { _ <> psd }
    debug.disableDebug.foreach { x => x := Bool(false) }
  }

ということでざっとTestHarnessの中身について、でした。Rocket Chip自体はExampleRocketSystemで完結していて、それにシミュレーションようにメモリやらデバッグ用のモジュールを接続する処理をしたもの、という感じ。