前回のChisel-Bootcampの学習ではChiselの異なる方同士の接続の法則についてを見ていった。
今回も引き続きModule3.6を見ていく。今日はジェネリクスとChiselの型の階層について。
ジェネリクス
ジェネリクスについて、、、とか書いておいて何なんだけど、よく知らんのよね、この概念自体を。。 ということで毎度お世話になります、Wikipediaさん。
総称型(generic type)、あるいはパラメタ付型(parametric type)とは、型付けされたプログラミング言語においてデータ型の定義とそれを参照する式(型式)の一部にパラメタを許すことによって類似した構造を持つ複数のデータ型を一括して定義して、それらを選択利用する仕組みである。
https://ja.wikipedia.org/wiki/%E7%B7%8F%E7%A7%B0%E5%9E%8B
なるほど、わからん。 ということで探した別の方の記事がこちら
あれだ、ScalaDocとか見てるとよく見るやつ。
abstract class ReadyValidIO[+T <: Data](gen: T) extends Bundle
あんまり深くは突っ込まないで、そこに指定してある型しか入らんのだろーな、位に考えてた。 とりあえずBootcampのサンプルを見ていこう。
サンプルコードその1
これは紹介するのに丁度いいコード、、ということらしい。 各々のコメント部分に書いてあるのが、Seqの中に含まれているデータの型を示している。
val seq1 = Seq("1", "2", "3") // Type is Seq[String] val seq2 = Seq(1, 2, 3) // Type is Seq[Int] val seq3 = Seq(1, "2", true) // Type is Seq[Any]
サンプルコードその2
上記は生成時にデータを入れており、それを元にコンパイラが型推論出来るが、そうは行かないケースもあるというのが次の例。
このケースでは、空のSeq
を作ることになるので型の指定をしないとval default
の型が定まらずに以降の処理が出来ない、ということらしい。
//val default = Seq() // Error! val default = Seq[String]() // ユーザーはコンパイラにデフォルトの型がSeq[String]と教えてやる必要がある Seq(1, "2", true).foldLeft(default){ (strings, next) => next match { case s: String => strings ++ Seq(s) case _ => strings } }
- 実行結果
default: Seq[String] = List() res2_1: Seq[String] = List("2")
サンプルコードその3
次がジェネリクスを使った例。Scalaでは型パラメータが正しい名称っぽい??
def time[T](block: => T): T = { val t0 = System.nanoTime() val result = block val t1 = System.nanoTime() val timeMillis = (t1 - t0) / 1000000.0 println(s"Block took $timeMillis milliseconds!") result } // 1から100万までを足し合わせる val int = time { (1 to 1000000).reduce(_ + _) } println(s"Add 1 through a million is $int") // 100万以下の数字から16進文字列に直した時にbeafを含む最大の数字を見つける val string = time { (1 to 1000000).map(_.toHexString).filter(_.contains("beef")).last } println(s"The largest number under a million that has beef: $string")
- 実行結果
Block took 15.082196 milliseconds! Add 1 through a million is 1784293664 Block took 105.76495 milliseconds! The largest number under a million that has beef: ebeef
動きを見てると、T
を返す匿名関数をもらって、T
を結果として返す、、ということになる、、はず。
ここはもう少しきちんと見なきゃだなぁ。。
Chiselの型の階層
このジェネリクスの考えをChiselでどのように使うのか、というのがここからの話。 うまく使っていくためには、Chiselの基本的な型がどのような構造をしているのかを知っておく必要があるとのこと。 文字で書くと、直感的でないので図にしたものをペタっと。
見てもらうとわかる通りChiselの設計で使用されるUInt
やBool
、Bundle
などなどは全てData
から派生したものになっている。
このData
にChiselでハードウェアを記述する際に使用する:=
のようなメソッドが定義されている。
以下はData
のコードから抜粋したもの
abstract class Data extends HasId with NamedComponent { ~略~ final def := (that: Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = this.connect(that)(sourceInfo, connectionCompileOptions) final def <> (that: Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = this.bulkConnect(that)(sourceInfo, connectionCompileOptions)
このようなData
から派生したデータをReg
やWire
なのでChiselのハードウェアを構成する要素に入れることで、そのData
の情報に基づいて、レジスタを実体化することが出来るようになっている。
object Reg { import chisel3.core.{Binding, CompileOptions} import chisel3.internal.sourceinfo.SourceInfo import chisel3.internal.throwException // Passthrough for chisel3.core.Reg // TODO: make val Reg = chisel3.core.Reg once we eliminate the legacy Reg constructor def apply[T <: Data](t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = chisel3.core.Reg(t)
ぶっちゃけ、このことだけ知ってればそれなりにChiselのライブラリをどう使うかは結構わかるような気もする。
以前はVec
を使ってレジスタを複数個まとめて定義するときにVec(Reg(UInt(8.W)))
じゃないの??とか思ったりもしたんだけど、上記のVec
がData
から派生していることを抑えておけばReg(Vec(4, UInt(8.W)))
になるというのは頷ける話。
例題:ジェネリクスを使ったシフトレジスタ
最後にここまでの話をまとめた例題がBootcampに載っているので、それを見て今日はお終いにしよう。
class ShiftRegisterIO[T <: Data](gen: T, n: Int) extends Bundle { require (n >= 0, "Shift register must have non-negative shift") val in = Input(gen.cloneType) val out = Output(Vec(n + 1, gen.cloneType)) // + 1 because in is included in out override def cloneType: this.type = (new ShiftRegisterIO(gen, n)).asInstanceOf[this.type] } class ShiftRegister[T <: Data](gen: T, n: Int) extends Module { val io = IO(new ShiftRegisterIO(gen, n)) io.out.foldLeft(io.in) { case (in, out) => out := in RegNext(in) } } class ShiftRegisterTester[T <: Bits](c: ShiftRegister[T]) extends PeekPokeTester(c) { println(s"Testing ShiftRegister of type ${c.io.in} and depth ${c.io.out.length}") for (i <- 0 until 10) { poke(c.io.in, i) println(s"$i: ${peek(c.io.out)}") step(1) } } Driver(() => new ShiftRegister(UInt(4.W), 5)) { c => new ShiftRegisterTester(c) } Driver(() => new ShiftRegister(SInt(6.W), 3)) { c => new ShiftRegisterTester(c) }
これまでBootcampを通して作ってきたモジュールと大きく異なるのはクラスの宣言時にジェネリクスを使っており、使う際にChiselのどの型で生成するかを決定できるようになっていること。
この例ではUInt
とSInt
の切り替えのみの例になっているが、Bundle
もChiselのData
から派生しているため、これをうまく使うことでいろいろパラメタライズの幅が広がりそう。
そのへんをうまく使ってるのがChiselの各種ライブラリということだな。
- 実行結果
[info] [0.001] Elaborating design... [info] [0.059] Done elaborating. Total FIRRTL Compile Time: 712.0 ms Total FIRRTL Compile Time: 29.8 ms End of dependency graph Circuit state created [info] [0.001] SEED 1552313783108 [info] [0.003] Testing ShiftRegister of type chisel3.core.UInt@25 and depth 6 [info] [0.007] 0: Vector(0, 0, 0, 0, 0, 0) [info] [0.009] 1: Vector(1, 0, 0, 0, 0, 0) [info] [0.016] 2: Vector(2, 1, 0, 0, 0, 0) [info] [0.018] 3: Vector(3, 2, 1, 0, 0, 0) [info] [0.019] 4: Vector(4, 3, 2, 1, 0, 0) [info] [0.021] 5: Vector(5, 4, 3, 2, 1, 0) [info] [0.023] 6: Vector(6, 5, 4, 3, 2, 1) [info] [0.025] 7: Vector(7, 6, 5, 4, 3, 2) [info] [0.027] 8: Vector(8, 7, 6, 5, 4, 3) [info] [0.028] 9: Vector(9, 8, 7, 6, 5, 4) test cmd4HelperShiftRegister Success: 0 tests passed in 15 cycles taking 0.048256 seconds [info] [0.031] RAN 10 CYCLES PASSED [info] [0.000] Elaborating design... [info] [0.010] Done elaborating. Total FIRRTL Compile Time: 26.6 ms Total FIRRTL Compile Time: 15.6 ms End of dependency graph Circuit state created [info] [0.000] SEED 1552313784311 [info] [0.001] Testing ShiftRegister of type chisel3.core.SInt@1f and depth 4 [info] [0.002] 0: Vector(0, 28, 28, 28) [info] [0.004] 1: Vector(1, 0, 28, 28) [info] [0.005] 2: Vector(2, 1, 0, 28) [info] [0.006] 3: Vector(3, 2, 1, 0) [info] [0.007] 4: Vector(4, 3, 2, 1) [info] [0.009] 5: Vector(5, 4, 3, 2) [info] [0.010] 6: Vector(6, 5, 4, 3) [info] [0.011] 7: Vector(7, 6, 5, 4) [info] [0.013] 8: Vector(8, 7, 6, 5) [info] [0.015] 9: Vector(9, 8, 7, 6) test cmd4HelperShiftRegister Success: 0 tests passed in 15 cycles taking 0.016668 seconds [info] [0.016] RAN 10 CYCLES PASSED
概念はこれまでのChisel-Bootcampの学習やChiselのソースコードを見ていてわかってきているが、使いこなしてパラメタライズの幅を広げるにはもう少し自分で書いてみるしか無いぁ、、、というのが正直なところ。とりあえずこういう機能があって、いろいろ出来そうということを踏まえて自分で実装する際の選択肢の一つとして試していかねば。