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

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

ChiselのBundleをRegInitに渡すときに各フィールドを任意の値で初期化する方法(2)

スポンサーリンク

今日は昨日の続き。
昨日はBundleRegInitに渡すときにBundle内の各フィールドを任意の値で初期化する方法について考えた。
目的の処理は出来ることは分かったのだが、記述がイマイチなので今日はそれを少し整えてみようと思う。

www.tech-diningyo.info

引き続き「Bundleをインスタンスして作るRegInitで各フィールドを任意の値で初期化する方法」

昨日のコードは以下のようなものだった。 この方法でもRegInit部分のエラボレートは通り、RTLが生成できることが確認できた。
レジスタになってることを確認するためライト用の入力端子も追加 & 各クラスの名前を修正

やり方をまとめると

  1. WireでラップしてChiselのハードウェア要素位にしたBundleRegInit0インスタンスを生成
  2. そのインスタンスに初期値を設定
  3. 初期値を設定したBundleRegInit0インスタンスRegInitに渡す

ことで、所望の処理が出来る。

import chisel3._

/**
  * RegInit用のBundleサンプルその1
  */
class BundleRegInit0 extends Bundle {
  val a = UInt(4.W)
  val b = UInt(4.W)
}

/**
  * Bundleで作るRegInitの各フィールドを任意の値で初期化
  */
class SampleBundleRegInit0 extends Module {

  val io = IO(new Bundle {
    val en = Input(Bool())
    val data = Input(UInt(8.W))
    val out = Output(new BundleRegInit0)
  })

  val bundleObj = Wire(new BundleRegInit0) // Wireでラップする

  // 初期値を設定
  bundleObj.a := 1.U
  bundleObj.b := 2.U

  // 初期値設定済みのWireで初期化してレジスタを作成
  val out = RegInit(bundleObj)

  when (io.en) {
    out.a := io.data
    out.b := io.data
  }

  io.out <> out
}

もう少し整理してみる

ただこれだと、初期値の記述をModule内にべた書きで”なんだかなーー”という感じなのでもう少し修正してみた。
変更点はMyBundle側に初期化用のメソッドinitを追加して、それを呼び出すようにしたことだ。
# あとちゃんとレジスタになっていることを確認するためにio.en/io.dataを書き込むように変更

import chisel3._

/**
  * RegInit用のBundleサンプルその2
  */
class BundleRegInit1 extends Bundle {
  val a = UInt(4.W)
  val b = UInt(4.W)

  /**
    * 初期化用メソッド
    * @return 初期化後のインスタンス
    */
  def init(): BundleRegInit1 = {
    a := 2.U
    b := 3.U
    this
  }
}

/**
  * Bundleで作るRegInitの各フィールドを任意の値で初期化
  */
class SampleBundleRegInit1 extends Module {

  val io = IO(new Bundle {
    val en = Input(Bool())
    val data = Input(UInt(8.W))
    val out = Output(new BundleRegInit1)
  })

  val out = RegInit(Wire(new BundleRegInit1).init())

  when (io.en) {
    out.a := io.data
    out.b := io.data
  }

  io.out <> out
}

少し整理したバージョンのエラボレート結果

以下のようになった。initで設定している値がout_a/out_bに入っているのがわかると思う。

module SampleBundleRegInit1(
  input        clock,
  input        reset,
  input        io_en,
  input  [7:0] io_data,
  output [3:0] io_out_a,
  output [3:0] io_out_b
);
  reg [3:0] out_a; // @[BundleRegInit1.scala 34:20]
  reg [3:0] out_b; // @[BundleRegInit1.scala 34:20]
  wire [7:0] _GEN_0; // @[BundleRegInit1.scala 36:16]
  wire [7:0] _GEN_1; // @[BundleRegInit1.scala 36:16]
  assign _GEN_0 = io_en ? io_data : {{4'd0}, out_a}; // @[BundleRegInit1.scala 36:16]
  assign _GEN_1 = io_en ? io_data : {{4'd0}, out_b}; // @[BundleRegInit1.scala 36:16]
  assign io_out_a = out_a; // @[BundleRegInit1.scala 41:10]
  assign io_out_b = out_b; // @[BundleRegInit1.scala 41:10]

  always @(posedge clock) begin
    if (reset) begin
      out_a <= 4'h2;
    end else begin
      out_a <= _GEN_0[3:0];
    end
    if (reset) begin
      out_b <= 4'h3;
    end else begin
      out_b <= _GEN_1[3:0];
    end
  end
endmodule

Chiselの方の初期化部分がちょっと野暮ったいが最初の例と比較するとこっちのほうが良いかな、と思う。

コンパニオン・オブジェクト作ってすっきりさせる

更にスッキリさせたければコンパニオン・オブジェクト作って初期化済みのWireを返す形にすればOK。

import chisel3._

/**
  * RegInit用のBundleサンプルその3
  */
class BundleRegInit2 extends Bundle {
  val a = UInt(4.W)
  val b = UInt(4.W)

  /**
    * 初期値を設定する
    * @return 初期化後の自クラス
    */
  def init(valA: Int = 3, valB: Int = 4): BundleRegInit2 = {
    a := valA.U
    b := valB.U
    this
  }

  /**
    * ライト
    * @param data 書き込む値
    */
  def write(data: UInt): Unit = {
    a := data(3, 0)
    b := data(7, 4)
  }
}

/**
  * class BundleRegInit2 のコンパニオン・オブジェクト
  */
object BundleRegInit2 {
  /**
    * initを呼んで初期値を設定
    * @return 設定後のインスタンス
    */
  def apply(): BundleRegInit2 = {
    val m = Wire(new BundleRegInit2())
    m.init()
  }

  /**
    * 生成時に引数の値で初期化
    * @param data 初期化時に設定する値
    * @return 設定後のインスタンス
    */
  def apply(data: UInt): BundleRegInit2 = {
    val m = Wire(new BundleRegInit2())
    m.write(data)
    m
  }
}

/**
  * Bundleで作るRegInitの各フィールドを任意の値で初期化
  */
class SampleBundleRegInit2 extends Module {

  val io = IO(new Bundle {
    val en = Input(Bool())
    val data = Input(UInt(32.W))
    val out1 = Output(new BundleRegInit0)
    val out2 = Output(new BundleRegInit2)
  })

  val out1 = RegInit(BundleRegInit2())
  val out2 = RegInit(BundleRegInit2("h77".U))

  when (io.en) {
    out1.write(io.data)
    out2.write(io.data)
  }

  io.out1 <> out1
  io.out2 <> out2
}

このバージョンでRTLを生成するとしたものがgistにあるので興味があればご確認ください。

BundleとRegInitで作る初期化済みレジスタ(コンパニオン・オブジェクト版)から生成したRTL · GitHub

全部ソースみたいよーという方は、以下にテストも含めて置いてあるのでそちらもどーぞ。

github.com

これで整理したバージョンについての紹介はお終い。
今のところ使い途で思い浮かんでいるのは、外部からアクセスするレジスタを32bit単位でまとめたりするようケース。値を保持するフィールドをBundle側に実装して、init/write/readを作っておき、Arrayとかをうまく使うとレジスタへのアクセス部分(デコードやらセレクタ)をいい感じに書けるかなーとか。これについても追ってまとめてみようと思う。