前回はChiselのBundle
へのアクセスを簡単にする方法を紹介した。
今回はいま実装しているHWの処理の関係で算術右シフトが必要になった時に、どうすれば良いのか悩んだので、その話をまとめておく。
Chiselで算術右シフト
説明は不要とは思うが、一応算術シフトについてのおさらいから。
ご存知のようにシフト演算には
- 論理シフト
- 算術シフト
の2つが存在している。 これらのシフトの処理に置いて右シフトした際に挙動が変わってきて、以下の違いが存在する。
- 論理シフト:シフトして空いた上位ビットは0で埋める
- 算術シフト:シフトして空いた上位ビットは元の値の符号ビットの値で埋める
例を見ておくと以下のようになる。 32bitの値"0xdeadbeaf"を右に8bitをシフトした際に、それぞれのシフト処理の結果は以下のようになる。
- 論理シフト:
0xdeadbeaf >> 8 = 0x00deadbe
- 算術シフト:
0xdeadbeaf >> 8 = 0xffdeadbe
Chiselではどーする??
Verilogだと>>>
とかで実装できるけどChiselだとどうすればいいんだと考えた挙句Fill
とか使って埋めたりしないといけないのか??とか右往左往したのだが、答えはもっとシンプルでSInt
に変換したうえでシフトをすればよかった。
算術右シフトの挙動確認コード
サンプルコードはこちら↓
import chisel3._ import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester} /** * UInt/SIntに右ビットシフトを適用した場合の挙動を確認するモジュール */ class ShiftTestModule extends Module { val io = IO(new Bundle { val uintIn = Input(UInt(32.W)) val uintOut = Output(UInt(32.W)) val sintOutFromUIntIn = Output(SInt(32.W)) val sintOut = Output(SInt(32.W)) val sintToUIntOut = Output(UInt(32.W)) }) io.uintOut := BigInt("deadbeaf", 16).asUInt() >> 8 // UIntの値を右シフト io.sintOut := (0xdeadbeaf.S >> BigInt(8)) // SIntの値を右シフト io.sintToUIntOut := (0xdeadbeaf.S >> 8.U).asUInt() // SIntの値を右シフトした後にUIntに変換 io.sintOutFromUIntIn := io.uintIn.asTypeOf(SInt(32.W)) >> 8 // シフト量はInt/BigInt/UIntならOK }
説明はコード中に書いたものがほぼほぼ全て。。
テストコード
早速動かしてみよう。テストコードは各出力を個々に確認する形にした。
class UIntAndSIntShiftTester extends ChiselFlatSpec { behavior of "ShiftTest" it should "UIntの右シフトは論理シフトになる" in { Driver.execute(Array(""), () => new ShiftTestModule) { c => new PeekPokeTester(c) { println(s"0x${peek(c.io.uintOut).toInt.toHexString}") expect(c.io.uintOut, 0xdeadbe) } } } it should "SIntの右シフトは算術シフトとなり、空いた上位ビットは符号ビット拡張が行われる" in { Driver.execute(Array(""), () => new ShiftTestModule) { c => new PeekPokeTester(c) { println(s"0x${peek(c.io.sintOut).toInt.toHexString}") expect(c.io.sintOut, 0xffdeadbe) } } } it should "SIntを右にシフトしたものをUIntに変換すると、符号ビットが0にセットされる" in { Driver.execute(Array(""), () => new ShiftTestModule) { c => new PeekPokeTester(c) { println(s"0x${peek(c.io.sintToUIntOut).toInt.toHexString}") expect(c.io.sintToUIntOut, 0xfdeadbe) } } } it should "UIntの入力をSIntに変換してシフトすれば算術シフトになる" in { Driver.execute(Array(""), () => new ShiftTestModule) { c => new PeekPokeTester(c) { poke(c.io.uintIn, 0xdeadbeafL) println(s"0x${peek(c.io.sintToUIntOut).toInt.toHexString}") expect(c.io.sintToUIntOut, 0xfdeadbe) } } } }
- 実行結果
実行すると以下のように全ての期待値が一致することを確認できた。
[IJ]sbt:uintAndSIntShift> test ~警告出てたけどそこは省略(リフレクションのやつ)~ [info] Done compiling. [info] [0.001] Elaborating design... [info] [0.136] Done elaborating. Total FIRRTL Compile Time: 264.8 ms Total FIRRTL Compile Time: 85.1 ms file loaded in 0.156806441 seconds, 11 symbols, 7 statements [info] [0.001] SEED 1554619069085 [info] [0.002] 0xdeadbe test ShiftTestModule Success: 1 tests passed in 5 cycles in 0.014059 seconds 355.65 Hz [info] [0.003] RAN 0 CYCLES PASSED [info] [0.000] Elaborating design... [info] [0.054] Done elaborating. Total FIRRTL Compile Time: 21.4 ms Total FIRRTL Compile Time: 25.5 ms file loaded in 0.030837271 seconds, 11 symbols, 7 statements [info] [0.000] SEED 1554619070023 [info] [0.000] 0xffdeadbe test ShiftTestModule Success: 1 tests passed in 5 cycles in 0.001987 seconds 2516.52 Hz [info] [0.001] RAN 0 CYCLES PASSED [info] [0.000] Elaborating design... [info] [0.004] Done elaborating. Total FIRRTL Compile Time: 19.4 ms Total FIRRTL Compile Time: 21.0 ms file loaded in 0.025568511 seconds, 11 symbols, 7 statements [info] [0.000] SEED 1554619070143 [info] [0.000] 0x7fdeadbe [info] [0.002] EXPECT AT 0 io_sintToUIntOut got 2145299902 expected 266251710 FAIL test ShiftTestModule Success: 0 tests passed in 5 cycles in 0.004120 seconds 1213.59 Hz [info] [0.002] RAN 0 CYCLES FAILED FIRST AT CYCLE 0 [info] [0.000] Elaborating design... [info] [0.004] Done elaborating. Total FIRRTL Compile Time: 16.9 ms Total FIRRTL Compile Time: 19.7 ms file loaded in 0.02338843 seconds, 11 symbols, 7 statements [info] [0.000] SEED 1554619070204 [info] [0.000] 0x7fdeadbe [info] [0.001] EXPECT AT 0 io_sintToUIntOut got 2145299902 expected 266251710 FAIL test ShiftTestModule Success: 0 tests passed in 5 cycles in 0.002296 seconds 2177.73 Hz [info] [0.001] RAN 0 CYCLES FAILED FIRST AT CYCLE 0 [info] UIntAndSIntShiftTester: [info] ShiftTest [info] - should UIntの右シフトは論理シフトになる [info] - should SIntの右シフトは算術シフトとなり、空いた上位ビットは符号ビット拡張が行われる [info] - should SIntを右にシフトしたものをUIntに変換すると、符号ビットが0にセットされる [info] - should UIntの入力をSIntに変換してシフトすれば算術シフトになる [info] ScalaTest [info] Run completed in 1 second, 361 milliseconds. [info] Total number of tests run: 4 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 4, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 4, Failed 0, Errors 0, Passed 4 [success] Total time: 4 s, completed 2019/04/07 15:37:50
ちょっとだけ実装を覗いてみる
多分この辺がその実装なはず。
Bits.scalaのBits
の実装部分(L.231(Chiselのバージョンは3.1.7))
演算子の定義が行われていて、中身はマクロ。実体はdo_>>
になってるっぽいがBits
では実装されておらず、各型のクラスで実装される。
/** Shift right operation */ // REVIEW TODO: redundant final def >> (that: BigInt): Bits = macro SourceInfoWhiteboxTransform.thatArg
Bits.scalaのSInt
の実装部分(L.683あたりから)
3つ用意されており、それぞれ受け取る型が異なっている。これがあるのでサンプルの例の様にシフト量に用いる型がInt
/BigInt
/UInt
なら使用出来る。
override def do_>> (that: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = binop(sourceInfo, SInt(this.width.shiftRight(that)), ShiftRightOp, validateShiftAmount(that)) override def do_>> (that: BigInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = this >> that.toInt override def do_>> (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = binop(sourceInfo, SInt(this.width), DynamicShiftRightOp, that)
ここから先はFIRRTLの世界になるみたいで、正直まだあんまり把握していないのでここまででストップ。
でもどうやらFIRRTLで対応する要素に変換されるみたい。なので実際にどう計算されているかはFIRRTL側の話。もう少しFIRRTL側の調査もしていかないとなぁ。。。
とりあえず今日のネタはここまで。 因みに今回のサンプルで記載したコードは以下に動く形にしたものが登録されてるので興味があれば試してみてくださいm(_ _)m