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

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

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

スポンサーリンク

以前にBunldeで構造化したデータを使ってレジスタインスタンスする方法を紹介したのだがその記事を書いたときにもう一つコレ出来たらいいなっと思いながら出来なかったことがあった。 それは”Bundleを使ってレジスタ郡を作る際に、各フィールド毎に任意の値で初期化出来ないか?”というものだった。 んでやっとそのやり方が分かったので、今日はそのやり方についてまとめようと思う。

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

やりたい事をまとめると以下のようになる。

  • MyBundleインスタンスすることで構造化したレジスタoutを作りたい
  • その際にMyBundleのフィールドa/bを以下の値で初期化したい。
    • out.a : 3.U
    • out.b : 5.U

これが出来ると例えば外からアクセスする1wordのレジスタの各ビットフィールドを全てBundleの要素として定義して、各フィールドに任意の初期値を入れたり出来る。 何となくChiselで書くと以下のような感じになるのだが???の部分をどうすれば上記の要件を満たす形でレジスタを生成できるのかについて考えていく。

class MyBundle extends Bundle {
  val a = UInt(2.W)
  val b = UInt(3.W)
}

class MyModule extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(5.W))
    val out = Output(new MyBundle)
    })

  // RegInitの???を工夫して
  // 初期化後の値を以下に設定したい
  // a = 3.U
  // b = 5.U
  val out = RegInit(???)

  out.a := op.in(1, 0)
  out.b := op.in(4, 2)

  io.out <> out
}

おさらい:Bundle + RegInitで作るレジスタを"0"で初期化する方法

と、その前におさらいから。これは以前に以下の記事でまとめた話。

www.tech-diningyo.info

MyBundleで作ったレジスタ0.Uで初期化するソースコード

次のようにすればBundleの各フィールドを"0"で初期化出来た。 初期化時の値はあえて0.Uにした。

class MyBundle extends Bundle {
  val a = UInt(2.W)
  val b = UInt(3.W)
}

class MyModule extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(5.W))
    val a = Output(UInt(2.W))
    val b = Output(UInt(3.W))
    })

  val out = RegInit(2.U.asTypeOf(new MyBundle))

  out.a := op.in(1, 0)
  out.b := op.in(4, 2)

  io.a := out.a
  io.b := out.b
}

生成されたRTL

以下のようにout_a/out_bが共に0.Uで初期化されているのがわかるかと思う。
#見にくいのでifdefは消してます。

module cmd18WrapperHelperMyModule( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input  [4:0] io_in, // @[:@6.4]
  output [1:0] io_out_a, // @[:@6.4]
  output [2:0] io_out_b // @[:@6.4]
);
  reg [1:0] out_a; // @[cmd18.sc 12:20:@15.4]
  reg [31:0] _RAND_0;
  reg [2:0] out_b; // @[cmd18.sc 12:20:@15.4]
  reg [31:0] _RAND_1;
  wire [1:0] _T_18; // @[cmd18.sc 14:17:@16.4]
  wire [2:0] _T_19; // @[cmd18.sc 15:17:@18.4]
  assign _T_18 = io_in[1:0]; // @[cmd18.sc 14:17:@16.4]
  assign _T_19 = io_in[4:2]; // @[cmd18.sc 15:17:@18.4]
  assign io_out_a = out_a;
  assign io_out_b = out_b;
  always @(posedge clock) begin
    if (reset) begin
      out_a <= 2'h0;
    end else begin
      out_a <= _T_18;
    end
    if (reset) begin
      out_b <= 3'h0;
    end else begin
      out_b <= _T_19;
    end
  end
endmodule

実は今回この初期化の方法を試しているときに気づいたのだが、この方法だと"0"で初期化する以外は意図通りには動かなかった。
上の見出しを「"0"で初期化する方法」と書いたのはそのため。。 例えば2.Uというような値で初期化してみると、out_aの初期値が2.Uにはならなかった。

各々をフィールドを任意の値で初期化する方法

以上の前置きを踏まえて、ここからが本題。 各種試してみて駄目だったことも含めて書くので、結論が見たい方は以下のページ内リンクで移動ください。

RegInitの実装

まずはRegInitの実装から確認してみた。RegInitobjectとなっており、以下の2つのapplyメソッドを持つ。

// Chisel3.core.Reg.scala の該当部分を抜粋
object RegInit {
  /** Returns a register pre-initialized (on reset) to the specified value.
    * Register type is inferred from the initializer.
    */
  def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    val model = (init.litArg match {
      // For e.g. Reg(init=UInt(0, k)), fix the Reg's width to k
      case Some(lit) if lit.forcedWidth => init.cloneTypeFull
      case _ => init match {
        case init: Bits => init.cloneTypeWidth(Width())
        case init => init.cloneTypeFull
      }
    }).asInstanceOf[T]
    RegInit(model, init)
  }

  /** Creates a register given an explicit type and an initialization (reset) value.
  */
  def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    if (compileOptions.declaredTypeMustBeUnbound) {
      requireIsChiselType(t, "reg type")
    }
    val reg = t.cloneTypeFull
    val clock = Node(Builder.forcedClock)
    val reset = Node(Builder.forcedReset)

    reg.bind(RegBinding(Builder.forcedUserModule))
    requireIsHardware(init, "reg initializer")
    pushCommand(DefRegInit(sourceInfo, reg, clock, reset, init.ref))
    reg
  }
}

通常している形RegInit(0.U(32.W))の形式が最初の方で、2つ目の方は実際にレジスタとしての情報を構築して設定しているような形となっている。
ここから先はあまり追えていないが、2つ目の各処理の名前から推測するとRegInitが宣言されたモジュールにTのデータを束縛しているような感じだろうか?
とりあえずRegInitに初期値を渡しても、戻ってくるのはTというのが分かったのでこれを元に実験してみよう。

MyBundleインスタンスRegInitに設定

いずれにしても”最終的にはTが戻り値になっている”ということはが分かったので、とりあえず自分で定義したBundleインスタンスしたものをRegInitに入れてみればいいのでは??という考えの元に試してみたのが以下のコードだ。

class MyBundle extends Bundle {
  val a = UInt(32.W)
  val b = UInt(32.W)
}

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

  val bundleObj = new MyBundle

  bundleObj.a := 1.U
  bundleObj.b := 2.U

  val out = RegInit(bundleObj)

  io <> out
}

早速試してみよう。

chisel3.core.Binding$ExpectedHardwareException: data to be connected 'chisel3.core.UInt@12' must be hardware, not a bare Chisel type
  chisel3.core.requireIsHardware$.apply(Binding.scala:31)
  chisel3.core.Data.connect(Data.scala:293)
  chisel3.core.Data.$colon$eq(Data.scala:365)
  $sess.cmd20Wrapper$Helper$MyModule.<init>(cmd20.sc:14)
  $sess.cmd20Wrapper$Helper$$anonfun$1.apply(cmd20.sc:23)
  $sess.cmd20Wrapper$Helper$$anonfun$1.apply(cmd20.sc:23)
  chisel3.core.Module$.do_apply(Module.scala:49)
  chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93)
  chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93)
  chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:297)
  chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:295)
  scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
  chisel3.internal.Builder$.build(Builder.scala:295)
  chisel3.Driver$.elaborate(Driver.scala:93)
  chisel3.Driver$.execute(Driver.scala:140)
  chisel3.Driver$.execute(Driver.scala:202)

渡すデータはハードウェアでないといかん!!とエラーになった。これがどこかで見かけた気もする”Chiselのデータ型とハードウェアの違いを意識しよう”って部分ですな。 先程の2つ目のapplyメソッドからも何となく推測できるのだが、"Chiselのハードウェア要素 == Chsielの型にハードウェアとしての付加情報を足したもの"と捉えておくのが良さそう。

MyBundleインスタンスをハードウェア要素にして渡そう

先の結果を受けて次に試すのは「じゃあハードウェアにして渡せばいいよね」という考えだ。コードは先ほどをそう変わりはなく、ただ単にMyBundleインスタンスWireでラップするだけ。

class MyBundle extends Bundle {
  val a = UInt(32.W)
  val b = UInt(32.W)
}

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

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

  bundleObj.a := 1.U
  bundleObj.b := 2.U

  val out = RegInit(bundleObj)

  io <> out
}
  • エラボレート結果

今度は正常にエラボレートが終了して、RTLが生成された。
なお、エラボレート後に最適化が行われているようで「値が変化しないからレジスタやめて固定値クリップ」されたRTLが生成された。 assign文の固定値を見てもらえれば分かる通り、bundleObjに設定した値がそのまま出力されており、意図した動作が得られた。

[info] [0.000] Elaborating design...
[info] [0.010] Done elaborating.
Total FIRRTL Compile Time: 25.4 ms
module cmd28WrapperHelperMyModule( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  output [31:0] io_a, // @[:@6.4]
  output [31:0] io_b // @[:@6.4]
);
  assign io_a = 32'h1;
  assign io_b = 32'h2;
endmodule

今日は力尽きたのでここまで。。
やりたいことは出来たのは出来たけどなんか野暮ったいので、次回は自分なりにこのコードをキレイにしてみようと思う。