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

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

Chiselの文法 - 入門編〜その2:Scala/Chiselの基本的なデータ型〜

スポンサーリンク

前回の続きでChiselの文法入門編その2
自分の理解ではChiselでハードウェアを実装する場合に、以下の3つの要素についての区別を理解する必要があると思っています。

  1. Scalaの型
  2. Chiselの型
  3. Ciiselのハードウェアの要素

これは言い換えると、以下の2つの境界線を明確に区別する必要があるということだと考えています。

  1. ScalaとChiselの境界線
  2. Chiselの”型”とChiselの”ハードウェア要素”の境界線

一回だと結構なボリュームになるので今回はまずScalaの型とChiselの型について見ていきます

Chisel入門編〜その2:Scala/Chiselの基本的なデータ型〜

Scalaの型

まずはScalaの型から。
とは言っても、ものすごく簡単に。
Chiselで使うScalaの基本の型は大体以下になると思います。

  • Boolean : Scalaのブール型
  • Int : Scalaの符号付き整数型(32bit)
  • Long : Scalaの符号付き整数型(64bit)
  • BigInt : Scala多倍長整数型(って訳されるのを初めて知った)

あとは期待値の生成時にFloatやらDoubleも使うかもですが、とりあえず置いときます。
また、ハードウェアの構築時にはコレクション型も使うと思います。

  • Seq : いわゆる配列(他にもList/Arrayなどありますが、使い分けはそこまで気にしなくてもいいかも??)
  • Map : キーと値がペアになったデータ構造。ハッシュ(Perl)とかディクショナリ(Python)とか呼ばれるアレ。

この辺を使ったハードウェアの記述はまた別の記事で。。。

Chiselの型

上記のScalaの型に対して、Chiselの型がどうなっているかを見ていきます。

Bool

Bool型は名前からも推測できるとは思いますがScalaBooleanに対応するChiselの型です。
宣言は以下のようになります。

val a = Bool()  // ChiselのBool型の変数
val b = true.B  // 定数値trueのChisel表現
val c = false.B // 定数値falseのChisel表現

上記はa/bとcで若干区別が必要になります(後述の”Chiselのハードウェア要素”参照)
当たり前の話ですがBool値なので1bitの信号が生成されることになりRTLに変換した際には1bitの信号にマッピングされます。
上記のtrue.BScalaの暗黙の型変換という機能によって、Chiselに実装されている所定のメソッドが呼び出されることによりBooleanBoolの変換が実行されています。

  • core/package.scala(このファイルはChiselの中のもの)
    implicit class fromBooleanToLiteral(boolean: Boolean) {
      /** Boolean to Bool conversion, recommended style for constants.
        */
      def B: Bool = Bool.Lit(boolean)  // scalastyle:ignore method.name

      /** Boolean to Bool conversion, recommended style for variables.
        */
      def asBool(): Bool = Bool.Lit(boolean)
    }

SIntとUInt

この2つは符号あり/なしの違いがあるだけなので、まとめて扱います。

宣言の仕方は以下のいずれかになります。

val a = [UInt or SInt](<ビット幅の宣言>)
val b = <定数値表現>.[U or S][(<ビット幅宣言>)]

上記の<ビット幅宣言>は以下のようにScalaInt型の整数に.Wを付与することを意味します。
例えば8bitにしたい場合は8.Wとなります。

実際にやってみると以下のような感じです。

// 符号なし整数
val uintA = UInt(32.W)
val uintB = 0x100.U         
val uintC = "b1010".U(32.W) // 2進数指定も可能
// 符号付き整数
val sintA = SInt(32.W)
val sintB = -100.S
val sintC = "o1010".U(32.W) // 8進数指定も可能

因みに定数値を設定する際に同時にビット幅指定を行う場合(上記のuintBのようなケース)において、指定したビット幅が定数値のビット幅に満たない場合はエラーが発生します。

chisel3.core.Bool@b
[error] (run-main-2a) java.lang.IllegalArgumentException: requirement failed:
The literal value -100 was elaborated with a specified width of 2 bits,
but at least 8 bits are required.

またこちらについてもBoolの場合と同様の仕組みでChiselの型への変換が行われます。

    // これはInt → UInt/SIntへの変換用のimplicit classで
    // String → UInt/SInt用のクラスも存在する
    implicit class fromBigIntToLiteral(bigint: BigInt) {
      /** Int to UInt conversion, recommended style for constants.
        */
      def U: UInt = UInt.Lit(bigint, Width())  // scalastyle:ignore method.name
      /** Int to SInt conversion, recommended style for constants.
        */
      def S: SInt = SInt.Lit(bigint, Width())  // scalastyle:ignore method.name

モジュール化して試してみる

ここまでのサンプルコードを実行できるようにモジュール化したものが以下になります。
ここで2つのプリント文が使用されていますが、以下のような違いあります。

  • println : Scalaのプリント出力関数
    • Scalaの関数なので、エラボレート時にSaclaのval自体の情報を出力する
  • printf : Chiselのプリント出力関数

    • Chiselのハードウェア要素の値を出力する
    • ”ハードウェア要素の中身”なのでシミュレーション実行時にのみ動作する
  • src/main/scala/TestChiselTypes.scala

import chisel3._

/**
  * Chiselのモジュール基本形
  * このモジュールは入力"in"を出力"out"に接続しスルーするだけ
  */
class TestChiselTypes extends Module {
  val io = IO(new Bundle {
    val in = Input(Bool())
    val out = Output(Bool())
  })

  val boolA = Bool()
  val boolB = true.B
  val boolC = false.B

  // printlnを使うとエラボレート時に型の情報が出力される
  println(boolA)
  println(boolB)
  println(boolC)

  // printfでシミューション実行時の値を出力可能
  // printf("boolA : %d\n", boolA) // これはエラーになる(詳しくは次の記事で)
  printf("boolB : %d\n", boolB)
  printf("boolC : %d\n", boolC)

  val uintA = UInt(32.W)
  val uintB = 0x100.U // "h100".Uでも可
  val uintC = "b1010".U(32.W)

  val sintA = SInt(32.W)
  val sintB = -100.S
  val sintC = "o1010".U(32.W)

  println(uintA)
  println(uintB)
  println(s" - bit width = ${uintB.getWidth}")
  println(uintC)
  println(s" - bit width = ${uintC.getWidth}")
  println(sintA)
  println(sintB)
  println(sintC)

  // printf("uintA : %d\n", uintA) // これもエラーになる(詳しくは次の記事で)
  printf("uintB : %d\n", boolB)
  printf("uintC : %d\n", boolC)
  // printf("sintA : %d\n", sintA) // これはエラーになる(詳しくは次の記事で)
  printf("sintB : %d\n", sintB)
  printf("sintB : %b\n", sintB.asUInt()) // .Uの変数版で<変数名>.asUInt()でキャスト出来る
  printf("sintC : %d\n", sintC)

  // 信号の接続
  io.out := io.in
}

上記を実行するためのメイン関数は以下のようにしました。
シミュレーション時の出力も取得するために先ほどのメイン関数とは異なり、こちらではchisel3.iotesters.Driverで評価した後に、先ほどと同様にchisel3.DriverによりRTLを生成する形にしています。

import chisel3._
import chisel3.iotesters._

/**
  * TestChiselTypesのエラボレート
  */
object ElaborateTestChiselTypes extends App{

  /*
   * 各Chiselのデータ型に設定された値を確認するために
   * テスト用のDriverを使用する
   */
  iotesters.Driver.execute(Array(
    "--target-dir=test_run_dir/chisel-basic",
    "--top-name=TestChiselTypes"
  ),
  () => new TestChiselTypes) {
    c => new PeekPokeTester(c) {
      step(1)
    }
  }

  // RTL生成用
  chisel3.Driver.execute(Array(
    "--target-dir=rtl/chisel-basic"
  ),
  () => new TestChiselTypes
  )
}

このElaborateTestChiselTypesを実行すると以下のような出力が得られます。
#警告と2つ目のDriver.executeの実行結果は重複するので削除し、ログ中にコメントを付与しています。

runMain ElaborateTestChiselTypes
[info] Compiling 1 Scala source to /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/target/scala-2.11/classes ...
[info] Done compiling.
[warn] Multiple main classes detected.  Run 'show discoveredMainClasses' to see the list
[info] Packaging /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/target/scala-2.11/chiselbasic_2.11-2.0.jar ...
[info] Done packaging.
[info] Running ElaborateTestChiselTypes
[info] [0.001] Elaborating design...
// TestChiselTypes内のprinln関数の出力
chisel3.core.Bool@11
chisel3.core.Bool@12
chisel3.core.Bool@13
chisel3.core.UInt@1a
chisel3.core.UInt@1b
 - bit width = 9     // bit幅が9。9なの??と思ったが確かに処理上こうなる(IR.scalaのdef minWidth参照)
chisel3.core.UInt@1c
 - bit width = 32
chisel3.core.SInt@1d
chisel3.core.SInt@1e
chisel3.core.UInt@17
[info] [0.982] Done elaborating.
Total FIRRTL Compile Time: 283.2 ms
Total FIRRTL Compile Time: 121.4 ms
file loaded in 0.189576433 seconds, 12 symbols, 2 statements
// TestChiselTypes内のprintf関数の出力
[info] [0.001] SEED 1558837588719
boolB : 1
boolC : 0
uintB : 1
uintC : 0
sintB : -100      // 負数になっている
sintB : 10011100  // ビット表現で見ると、最上位ビットが0x1になり2の補数表現になっているのがわかる&ビット幅も適切になる
sintC : 520
test TestChiselTypes Success: 0 tests passed in 6 cycles in 0.015464 seconds 387.99 Hz
[info] [0.003] RAN 1 CYCLES PASSED
[success] Total time: 3 s, completed 2019/05/26 11:26:30

ここまでChiselの基本のデータ型の挙動についてを確認してきました。簡単にまとめると以下のようになります。

  • Chiselの基本となるデータ型には以下がある。
    • Bool : ブール型。宣言は以下の通り。
      • val a = Bool() ※厳密には不十分(詳細は次の記事で)
      • val a = true.B or false.B
        • .Bを付けることでScalaBooleanをChiselのBoolに変換が出来る
    • UInt/SInt : 符号なし(あり)整数型。宣言は以下の通り。
      • val a = UInt(32.W) / val a = SInt(8.W)Boolと同じく厳密には不十分(詳細は次の記事で)
      • val a = 0.U / val a = -10.S(32.W)
        • .U/.Sを付けることでScalaIntをChiselのUInt/SIntに変換が出来る
        • ビット幅を指定した場合は指定のビット幅を持ち、指定がない場合はInt型のビット幅から自動的に計算される

その3に続く。

www.tech-diningyo.info