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

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

ChiselのBoringUtilsの使い方を確認

スポンサーリンク

ChiselのBorringUtilsについての確認を行ったので、メモ書き。

BoringUtils

この話は既にmsyksphinzさんがブログでまとめてくださっているので、そちらも合わせてどうぞ。
このオブジェクトを使用すると、通常はIO()で包んで宣言したポート以外の信号に、別のモジュールからアクセスができるようになる。

class MyModule extends Module {
  val io = IO(new Bundle)

  // BoringUtilsを使うとw_wireに
  // 外部のモジュールからアクセスできる
  val w_wire = WireDefault(0.U)
}

このBorringUtilsには以下の2つの使い方が定義されているが、実現できることはどちらも一緒だ。

  • Hierarchical Boring
  • Non-hierarchical Boring

この2つを簡単なサンプルで見てみようと思う。なお、このBorringUtilsは現状experimental扱いなので、動かないことがあるかも??
自分が確認に使ったバージョンはChisel 3.2.0で、本記事に掲載しているコードは正常に動作することが確認できている。

Hierarchical Boring

Hierarchical Boringはアクセスしたいモジュールに手を入れること無く、外部のモジュールからアクセスを行う方法だ。イメージ的にはVerilog-HDLの階層参照に近い感じ(なのでHierarchicalか)。

この使い方をする際にはBoringUtilsオブジェクトに定義されているboreメソッドを使う。

使用方法は第一引数に参照したい信号のパスを与えて、第2引数にはSeqの中に参照したい信号を受け取る信号を与える様にすればOK。
実際にやってみると、以下のようになる。コード中にあるようにこのオブジェクトを使用する際にはchisel3.experimental.BoringUtilsのインポートが必要な点に注意が必要だ。

import chisel3._
import chisel3.util.experimental.BoringUtils

/**
 * BoringUtilsの動作確認用バンドル
 */
class BoringUtilsBundle extends Bundle {
  val uint = UInt(8.W)
  val vecOfBool = Vec(2, Bool())
  val bundle = new Bundle {
    val a = Bool()
  }
}

/**
 * Hierarchical Boringのサンプル
 */
class ObjBoringUtils1 extends Module {
  val io = IO(new Bundle {
    val uint = Output(UInt(8.W))
    val bundle = Output(new BoringUtilsBundle)
  })

  val m_sub = Module(new ObjBoringUtilsSub1)
  // 予め値を決定しておかないと、エラーになるためWireDefaultを使う
  val w_sub_uint =  WireDefault(0.U(8.W))
  val w_sub_bundle = WireDefault(0.U.asTypeOf(new BoringUtilsBundle))

  // boreの第2引数はSeqでのラップが必要な点に注意
  BoringUtils.bore(m_sub.w_uint, Seq(w_sub_uint))
  BoringUtils.bore(m_sub.w_bundle, Seq(w_sub_bundle))

  io.uint := w_sub_uint
  io.bundle := w_sub_bundle
}

/**
 * 確認用のサブモジュール
 */
class ObjBoringUtilsSub1 extends Module {
  val io = IO(new Bundle {})

  val w_uint = WireDefault(255.U)
  val w_bundle = Wire(new BoringUtilsBundle)

  w_bundle.uint := 0x80.U
  w_bundle.vecOfBool(0) := false.B
  w_bundle.vecOfBool(1) := true.B
  w_bundle.bundle.a := true.B
}

コメントにも記載したが、受け取る側の信号は事前に他の値が入っていないとエラーが発生する。別の言い方をするとWireで宣言してboreメソッド以外で値を格納していない場合には、エラーが発生する。

動作確認は、本記事の最後でまとめて行うので後回し。

Non-hierarchical Boring

Non-hierarchical Boringは階層構造関係なくChiselのモジュール間でやり取りしたい信号を、登録の際に与えるIDを使うことで参照できるようにする。 こちらの方法を使う場合にはBoringUtilsに定義されているaddSource/addSinkメソッドを使用する。

簡単なサンプルを作ってみると次のようになる。

/**
 * Non-hierarchical Boringのサンプル
 */
class ObjBoringUtils2 extends Module {
  val io = IO(new Bundle {
    val uint = Output(UInt(8.W))
    val bundle = Output(new BoringUtilsBundle)
  })

  val m_sub = Module(new ObjBoringUtilsSub2(0x80))
  val w_sub_uint =  WireDefault(0.U(8.W))
  val w_sub_bundle = WireDefault(0.U.asTypeOf(new BoringUtilsBundle))

  m_sub.io.in := 0xf0.U

  // BoringUtils.addSourceで登録した信号を定義時の"name"で
  // 参照して紐付ける
  BoringUtils.addSink(w_sub_uint, "w_sub_uint")
  BoringUtils.addSink(w_sub_bundle, "w_sub_bundle" )

  io.uint := w_sub_uint
  io.bundle := w_sub_bundle
}

/**
 * 確認用のサブモジュール
 */
class ObjBoringUtilsSub2(n: Int) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
  })

  val w_uint = WireDefault(io.in)
  val w_bundle = Wire(new BoringUtilsBundle)

  // 別の階層からアクセスしたい信号をaddSourceで登録
  BoringUtils.addSource(w_uint, "w_sub_uint") // 参照時には"w_sub_uint"を指定する
  BoringUtils.addSource(w_bundle, "w_sub_bundle", true)

  w_bundle.uint := n.U
  w_bundle.vecOfBool(0) := false.B
  w_bundle.vecOfBool(1) := true.B
  w_bundle.bundle.a := true.B
}

テストで確認

上記で作成したサンプルを簡単なテストで確認してみた。

import chisel3.iotesters._

/**
 * ObjBoringUtilsのテスト
 */
class ObjBoringUtilsTester extends ChiselFlatSpec {

  val dutName = "ObjBoringUtils"

  "ObjBoringUtils1" should s"Hierarchical Boringで参照した信号を確認できる" in {
    Driver.execute(Array(""), () => new ObjBoringUtils1) {
      c => new PeekPokeTester(c) {
        expect(c.io.uint, 0xff)
        expect(c.io.bundle.uint, 0x80)
        expect(c.io.bundle.vecOfBool(0), false)
        expect(c.io.bundle.vecOfBool(1), true)
        expect(c.io.bundle.bundle.a, true)
      }
    } should be (true)
  }

  "ObjBoringUtils2" should s"Non-hierarchical Boringで参照した信号を確認できる" in {
    Driver.execute(Array(""), () => new ObjBoringUtils2) {
      c => new PeekPokeTester(c) {
        expect(c.io.uint, 0xf0)
        expect(c.io.bundle.uint, 0x80)
        expect(c.io.bundle.vecOfBool(0), false)
        expect(c.io.bundle.vecOfBool(1), true)
        expect(c.io.bundle.bundle.a, true)
      }
    } should be (true)
  }
}

上記のテストを実行すると、次のようにテストがPASSし、各モジュール内でインスタンスしたサブモジュール内の信号が参照できていることが確認できた。

[info] ObjBoringUtilsTester:
[info] ObjBoringUtils1
[info] - should Hierarchical Boringで参照した信号を確認できる
[info] ObjBoringUtils2
[info] - should Non-hierarchical Boringで参照した信号を確認できる

これを使用するとChiselのテスト機能を使ってテストを実行する際に、テストターゲットをインスタンスするテストベンチのトップ階層を挟んでおくだけで、所望の信号にアクセスするパスを作ることができる。
そのため内部の信号の期待値比較を行う場合にデバッグ用のポートを作成&トップ階層まで引き上げる作業が不要になるのが便利。