前回のChiselの記事ではChisel-bootcampのModule3.5のコンパニオン・オブジェクトとケース・クラスについてを勉強した。
今回はちょっとChisel-Bootcampから離れてChiselを試していたハマったUIntの大きい定数値の扱いについてを紹介していく。
ChiselのUIntで大きな定数を使おうとしてハマった話
最近になってやっとある程度思い通りにChiselでの実装が出来るようになってきて、ちょっとずつ作りたいモジュールを実装を始めている。 その際にChiselデバッグ用のポートを作ろうとして、とりあえず挙動を調べるためのコードを書いていた時にUIntの大きめの定数値(32bitでMSBが0x1になるような数字)でハマった時のことを書いておこうと思う。
先に書いておくと、もちろん解決策は存在していてChiselは普通に32bitの範囲内はもちろんそれ以上の定数も扱えます。
一応何をどうしたらエラーになって、、、的な部分から書いていきますので、とりあえず解決策のみを見たい方はこの記事の後半部分にジャンプしてください。
何がしたかったのか??
では、状況の説明から。
やろうとしたのは以下のようにChiselのModule
内で作ったポートに32bitの大きなデータを入れようとしただけなんだ。
因みにこの話はChiselでオプションポートを扱う楽な方法を模索していた時に、適当に32bitの固定値だし0xdeadbeaf
だな、と入れてみたら予期せぬエラーに出くわした、という経緯。
なので以下のコードはエラーが起きるケースをシンプルに再現したコード。
class MyModule extends Module { val io = IO(new Bundle { val out = Output(UInt(32.W)) }) io.out := 0xdeadbeaf.asUInt(32.W) }
説明する必要も無い気もしますが、一応説明。
io.out
にScalaのIntのデータ0xdeadbeaf
をasUInt
で32bitのUInt
に変換してChiselのData
として接続する
というだけ。
上記のコードはScalaの文法的には何もおかしな部分は無いので、クラスの定義だけだと普通にビルドが終わる。
以下のメッセージは上記のclassの定義だけをコンパイルした時のもの(== Chiselのエラボレートは動いていない状態)で何事もなくMyModule
クラスが定義されたとjupyter-notebook上にメッセージが出る。
defined class MyModule
でもエラボレートするとエラーが起きるんだ
エラボレートしてVerilogのRTLとか作ろうとするとエラーになるんですよ、このコード。 ということで実際にやってみた際のメッセージが以下。
[info] [0.000] Elaborating design... java.lang.IllegalArgumentException: requirement failed: UInt literal -559038801 is negative scala.Predef$.require(Predef.scala:224) chisel3.internal.firrtl.ULit.<init>(IR.scala:79) chisel3.core.UIntFactory$class.Lit(Bits.scala:573) chisel3.core.UInt$.Lit(Bits.scala:590) chisel3.core.package$fromBigIntToLiteral.asUInt(package.scala:48) ammonite.$sess.cmd2$Helper$MyModule.<init>(cmd2.sc:6) ammonite.$sess.cmd3$Helper$$anonfun$1.apply(cmd3.sc:1) ammonite.$sess.cmd3$Helper$$anonfun$1.apply(cmd3.sc:1) 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) ammonite.$file.dummy.source.load$minusivy$Helper.getVerilog(Main.sc:9) ammonite.$sess.cmd3$Helper.<init>(cmd3.sc:1) ammonite.$sess.cmd3$.<init>(cmd3.sc:7) ammonite.$sess.cmd3$.<clinit>(cmd3.sc:-1)
見てお分かりの通り"UInt
の値が負数になってる!!"と怒られます。エラーメッセージの-559038801
はお気づきの通り0xdeadbeaf
を32bitのInt
として解釈した時の数字。
発生する理由はそのままズバリでScalaにはunsigned
に相当する型が無いため0xdeadbeaf
と書くとInt
型になるのでMSBが符号ビットとして解釈されて、そのデータがChiselのUInt
に入るため”負数を入力してくれるな!!!”とお叱りが入るわけです。
一応これを確認したコードが以下のコード(jupyter-notebook上であれば、以下のようにval
だけ定義してもエラーにはなりません)
- MSBを除く全ビットが0x1の場合はOK
val a = 0x7fffffff.U // OK a: UInt = chisel3.core.UInt@0
- MSBに0x1が立つとNG
val a = 0x80000000.U // OK java.lang.IllegalArgumentException: requirement failed: UInt literal -2147483648 is negative scala.Predef$.require(Predef.scala:224) chisel3.internal.firrtl.ULit.<init>(IR.scala:79) chisel3.core.UIntFactory$class.Lit(Bits.scala:573) chisel3.core.UInt$.Lit(Bits.scala:590) chisel3.core.package$fromBigIntToLiteral.U(package.scala:29) ammonite.$sess.cmd7$Helper.<init>(cmd7.sc:1) ammonite.$sess.cmd7$.<init>(cmd7.sc:7) ammonite.$sess.cmd7$.<clinit>(cmd7.sc:-1)
どうずれば解決できるのか
冒頭に書いたとおり解決策はあります。今回みたいな符号なしの32bitのデータはもちろん、64bitやそれ以上の定数もきちんと使えるようになってます。じゃないとSHA1みたいなハッシュの初期値とかをわざわざ符号付きにして扱うとかいう訳わからんことに。今回の件もそのデータを扱う部分をSInt
に変えるとエラーは起きません。起きませんが、望んでいるのはもちろんそういうことじゃないです。
ということで解決方法を紹介していきます。なお以下に記載する内容はChiselのgithubのissuesに載っている内容なので、必要に応じて以下もご覧ください。
解決策1:ScalaのLong
を使う(64bit未満のデータ限定)
これはScala側での対処でScalaのInt
でダメならLong
にすればいい!!という解決策で以下のようにやると64bitまでのデータならこれで乗り切れます。
val a = 0x80000000L.U // OK:定数値の末尾にLongを示す"L"を入れるだけ。 a: UInt = chisel3.core.UInt@0
ScalaのLong
は64bitなので、先の32bitの例と同様にMSBに0x1が立つような符号なしデータはエラーが発生します。
- エラーは起きない
val a = 0x7fffffffffffffffL.U // OK a: UInt = chisel3.core.UInt@0
- MSBに0x1が立つとNG@64bit
val a = 0x8000000000000000L.U // NG java.lang.IllegalArgumentException: requirement failed: UInt literal -9223372036854775808 is negative scala.Predef$.require(Predef.scala:224) chisel3.internal.firrtl.ULit.<init>(IR.scala:79) chisel3.core.UIntFactory$class.Lit(Bits.scala:573) chisel3.core.UInt$.Lit(Bits.scala:590) chisel3.core.package$fromBigIntToLiteral.U(package.scala:29) ammonite.$sess.cmd5$Helper.<init>(cmd5.sc:1) ammonite.$sess.cmd5$.<init>(cmd5.sc:7) ammonite.$sess.cmd5$.<clinit>(cmd5.sc:-1)
実は割とこれで乗りきれるケースは多いはず。定数値にL
を付けるだけだし。
解決策2:ScalaのBigInt
に変換してからChiselのUInt
に渡す
これもScala側での対処。ScalaのBigInt
は任意精度の整数になるので、引っかかる定数データを変換してから渡すという方法。
コードは以下のようにBigInt
で16進数文字列を基数16で変換してからUInt
に渡す。
val b = BigInt("deadbeaf", 16).U // ScalaのBigIntで変換してからUIntに渡す b: UInt = chisel3.core.UInt@0
因みにBigIntはBigInt(0x10)
といった基数を指定した数値を使った変換も出来るが、これを使うとうまくいかなかった。
val b = BigInt(0xdeadbeaf).U // ScalaのBigIntで変換してからUIntに渡す:でもエラー java.lang.IllegalArgumentException: requirement failed: UInt literal -559038801 is negative scala.Predef$.require(Predef.scala:224) chisel3.internal.firrtl.ULit.<init>(IR.scala:79) chisel3.core.UIntFactory$class.Lit(Bits.scala:573) chisel3.core.UInt$.Lit(Bits.scala:590) chisel3.core.package$fromBigIntToLiteral.U(package.scala:29) ammonite.$sess.cmd14$Helper.<init>(cmd14.sc:1) ammonite.$sess.cmd14$.<init>(cmd14.sc:7) ammonite.$sess.cmd14$.<clinit>(cmd14.sc:-1)
解決策3:Chiselの型変換でBigInt
→UInt
に変換
こちらは全部Chiselで解決する方法(Chisel側でInt
→BitInt
→UInt
の変換をしてくれる)。
この方法は基数として指定できる文字が2通り用意されている。
val c = "hdeadbeaf".U // 16進文字列定数を直接UIntに渡す(hが16進の基数表記) val d = "xdeadbeaf".U // 16進文字列定数を直接UIntに渡す(xも16進の基数表記) c: UInt = chisel3.core.UInt@0 d: UInt = chisel3.core.UInt@0
上記の2つはChiselのソースコード的にはcore/packages.scalaに定義されているfromBigIntToLiteral
の中の関数になる。
ということでまとめると以下になる。
val a = 0xdeadbeafL.U // 64bit未満のデータ限定 val b = BigInt("deadbeaf", 16).U // ScalaのBigIntで変換してからUIntに渡す val c = "hdeadbeaf".U // 16進文字列定数を直接UIntに渡す(hが16進の基数表記) val d = "xdeadbeaf".U // 16進文字列定数を直接UIntに渡す(xも16進の基数表記)
BigInt
に変換に変換する手法なら諸々引っかからずに済む。ただBigInt
を呼ぶのは面倒なのでc
/d
で書くのが良さそう。普段から全部統一するかは悩みどころ(0で初期化とか)