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