前回の記事ではChisel BootcampはChiselで作成した4-tapのFIRフィルタ・ジェネレータをN-tap版FIRフィルタ・ジェネレータに変換する最後のフェイズとしてN-tap版のFIRフィルタ・ジェネレータのテストを作成した。
前回の終わりに書いたとおり、今日の記事ではChiselのコレクション型であるVec
についてを見ていく。
Module 3.2: ジェネレータ:コレクション
Chiselのコレクション型
このChiselのコレクション型の例としてModule3.1の前回までの記事で作成してきたN-tap版のFIRフィルタ・ジェネレータを一部変更したものが紹介されているので、まずはコードを見てみよう。
例題: 実行時のフィルタ係数変更機能を備えたFIRフィルタ・ジェネレータ
以下がそのコードとなる。
大きな違いはクラスのパラメータとして定義されていたフィルタ係数がModulesのIO宣言に移動したことにある。
class MyManyDynamicElementVecFir(length: Int) extends Module { val io = IO(new Bundle { val in = Input(UInt(8.W)) val out = Output(UInt(8.W)) val consts = Input(Vec(length, UInt(8.W))) }) // Reference solution val regs = RegInit(Vec.fill(length - 1)(0.U(8.W))) for(i <- 0 until length - 1) { if(i == 0) regs(i) := io.in else regs(i) := regs(i - 1) } val muls = Wire(Vec(length, UInt(8.W))) for(i <- 0 until length) { if(i == 0) muls(i) := io.in * io.consts(i) else muls(i) := regs(i - 1) * io.consts(i) } val scan = Wire(Vec(length, UInt(8.W))) for(i <- 0 until length) { if(i == 0) scan(i) := muls(i) else scan(i) := muls(i) + scan(i - 1) } io.out := scan(length - 1) }
そのフィルタ係数にまつわるIO宣言は以下の部分で、Chiselのコレクション型であるVec
が使用されている。
val consts = Input(Vec(length, UInt(8.W)))
Vec
の説明部分をBootcampから抜粋する。
Vec
はscalaのコレクション型が持つ多くのメソッドをサポートするが、Vec
に格納できるのはChiselのハードウェアの構成要素のみである。Vec
はScalaのコレクション型動作しない状況下において使うべきだが、そのような状況は基本的には以下の2つのケースがある。
Bundle
の中にコレクション型が含まれるようなケース。典型的な例はBundle
がIOとして使われる場合となる。- ハードウェアの構成要素を利用してコレクション型にアクセスする場合(レジスタファイルなどがこれに相当する)
一応、上記の2つについてを確認しておこう。
1.Bundle
にコレクション型を使用する場合
典型例として紹介されているIOのBundleの中で、というのは頷けるのだがいまいちピンとこないのでいくつか試してみる。
Bundle
の中にSeq
class MyBundleError() extends Bundle { val seqVal = Seq(10) } val myBundle = new MyBundleError()
これはエラーにはならない。
defined class MyBundleError myBundle: MyBundleError = ammonite.$sess.cmd20$Helper$MyBundleError@0
IO
の中のBundle
にSeq
class MyModuelIOBundle() extends Module { val io = IO(new Bundle { val inSeq = Input(Seq(0, 0, 0, 0)) }) }
こちらはChiselのModuleの中のIOにSeq
を適用した例になる。
これを実行すると 以下の様にエラーが発生した。
cmd22.sc:3: inferred type arguments [Seq[Int]] do not conform to method apply's type parameter bounds [T <: chisel3.core.Data] val inSeq = Input(Seq(0, 0, 0, 0)) ^cmd22.sc:3: type mismatch; found : Seq[Int] required: T val inSeq = Input(Seq(0, 0, 0, 0)) ^Compilation Failed
[T <: chisel3.core.Data]
と書いてあるので、このコンテキストではChiselの型しか入れることが出来ないということになるようだ。
IO
の中のBundle
にVec
では先の例のSeq
をVec
に変更してみよう。
class MyModuelIOBundle() extends Module { val io = IO(new Bundle { val inVec = Input(Vec(4, UInt(8.W))) }) }
これを実行してみる。
defined class MyModuelIOBundle
今度はエラーなく正常にコンパイルが終了した。
このモジュールをVerilogに変換してみると以下のようにVec
がポートに分解されたものが出力される。
module cmd3HelperMyModuelIOBundle( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [7:0] io_inVec_0, // @[:@6.4] input [7:0] io_inVec_1, // @[:@6.4] input [7:0] io_inVec_2, // @[:@6.4] input [7:0] io_inVec_3 // @[:@6.4] ); initial begin end endmodule
2. ハードウェアの構成要素を用いてコレクション型にアクセスする場合
”レジスタファイル”と書いてあるのでこれは割とわかりやすい。Vec
に格納したReg
に対してInput
で与えられるアドレスを使ってアクセスするような場合だ。
class MyModuelIOBundle() extends Module { val io = IO(new Bundle { val inAddr = Input(UInt(8.W)) val inWren = Input(Bool()) val inWrdata = Input(UInt(8.W)) val out = Output(UInt(8.W)) }) val vecA = Reg(Vec(8, UInt(8.W))) when(io.inWren) { vecA(io.inAddr) := io.inWrdata } io.out := vecA(io.inAddr) } println(getVerilog(new MyModuelIOBundle))
上記はVec
で確保したReg
のどれに値を書き込む or 値を出力するという選択をInput
から入ってくるアドレスで行っている。
これは特にエラーも発生せずに実行が可能だ。
defined class MyModuelIOBundle
Seq
に確保したレジスタの場合
一方で以下の様に、、とは言ってもあんまりいい例では無いのだが、ScalaのSeq
に入れたReg
に対してChiselのデータであるInput
を使って”どちらのレジスタを選択するか”というコードを書くとエラーとなる。
class MyModuelIOBundle() extends Module { val io = IO(new Bundle { val inAddr = Input(UInt(8.W)) val inWren = Input(Bool()) val inWrdata = Input(UInt(8.W)) val out = Output(UInt(8.W)) }) val seqA = Seq(Reg(UInt(8.W)), Reg(UInt(8.W))) when(io.inWren) { seqA(io.inAddr) := io.inWrdata } io.out := seqA(io.inAddr) }
これを実行すると、以下の様に”型の不一致”でエラーとなる。
cmd21.sc:12: type mismatch; found : chisel3.core.UInt required: Int seqA(io.inAddr) := io.inWrdata ^cmd21.sc:15: type mismatch; found : chisel3.core.UInt required: Int io.out := seqA(io.inAddr) ^Compilation Failed
テストの実行
最後にVec
を使って作ったFIRフィルタのテストコードを紹介しておしまいにする。こちらは取り立てて新しい要素は無いので説明は割愛。
val goldenModel = new ScalaFirFilter(Seq(1, 1, 1, 1)) Driver(() => new MyManyDynamicElementVecFir(4)) { c => new PeekPokeTester(c) { poke(c.io.consts(0), 1) poke(c.io.consts(1), 1) poke(c.io.consts(2), 1) poke(c.io.consts(3), 1) for(i <- 0 until 100) { val input = scala.util.Random.nextInt(8) val goldenModelResult = goldenModel.poke(input) poke(c.io.in, input) expect(c.io.out, goldenModelResult, s"i $i, input $input, gm $goldenModelResult, ${peek(c.io.out)}") step(1) } } }
以下が上記のテストコードの実行結果
[info] [0.000] Elaborating design... [deprecated] cmd38.sc:9 (1 calls): fill is deprecated: "Vec.fill(n)(gen) is deprecated, use VecInit(Seq.fill(n)(gen)) instead" [deprecated] cmd38.sc:9 (1 calls): do_apply is deprecated: "Vec(elts) is deprecated, use VecInit(elts) instead" [warn] There were 2 deprecated function(s) used. These may stop compiling in a future release, you are encouraged to fix these issues. [warn] Line numbers for deprecations reported by Chisel may be inaccurate, enable scalac compiler deprecation warnings by either: [warn] In the sbt interactive console, enter: [warn] set scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation") [warn] or, in your build.sbt, add the line: [warn] scalacOptions := Seq("-unchecked", "-deprecation") [info] [0.013] Done elaborating. Total FIRRTL Compile Time: 28.5 ms Total FIRRTL Compile Time: 11.5 ms End of dependency graph Circuit state created [info] [0.000] SEED 1543675701775 test cmd38HelperMyManyDynamicElementVecFir Success: 100 tests passed in 105 cycles taking 0.055636 seconds [info] [0.052] RAN 100 CYCLES PASSED
これでModule3.2を最後にするつもりだったが練習問題が残ってしまった。
ということで、次も引き続きModule3.2の続きを。。