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

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

Chisel Bootcamp - Module3.2(5) - Chiselのコレクション型"Vec"

スポンサーリンク

前回の記事ではChisel BootcampはChiselで作成した4-tapのFIRフィルタ・ジェネレータをN-tap版FIRフィルタ・ジェネレータに変換する最後のフェイズとしてN-tap版のFIRフィルタ・ジェネレータのテストを作成した。

www.tech-diningyo.info

前回の終わりに書いたとおり、今日の記事では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から抜粋する。

Vecscalaのコレクション型が持つ多くのメソッドをサポートするが、Vecに格納できるのはChiselのハードウェアの構成要素のみである。VecScalaのコレクション型動作しない状況下において使うべきだが、そのような状況は基本的には以下の2つのケースがある。

  1. Bundleの中にコレクション型が含まれるようなケース。典型的な例はBundleがIOとして使われる場合となる。
  2. ハードウェアの構成要素を利用してコレクション型にアクセスする場合(レジスタファイルなどがこれに相当する)

一応、上記の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の中のBundleSeq
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の中のBundleVec

では先の例のSeqVecに変更してみよう。

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に確保したレジスタの場合

一方で以下の様に、、とは言ってもあんまりいい例では無いのだが、ScalaSeqに入れた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の続きを。。