前回の記事ではChisel BootcampではやっとModule3.1が終わりになった。
今回からはModule3.2に入り、Scalaのコレクション型を使ってChiselのジェネレータを書く方法を学んでいく。
Module 3.2: ジェネレータ:コレクション
モチベーション
まずはお決まりとなったモチベーションから。
ジェネレータには数多くのオブジェクトの変数を処理しなければならない場合がよく起きる。それはIOだったりモジュールだったり、テストベクタだったりと様々だ。コレクション型はこのような状況を処理するために重要な機能となる。このモジュールではScalaのコレクションとこれをどのようにしてChiselのジェネレータと一緒に使っていくかを紹介する。
ジェネレータとコレクション型
このモジュールでは以前のチャプターで出てきたFIRフィルタを使って学習を進めていく。
ということで、サンプルとなるFIRフィルタのコードを再度見てみよう。
class My4ElementFir(b0: Int, b1: Int, b2: Int, b3: Int) extends Module { val io = IO(new Bundle { val in = Input(UInt(8.W)) val out = Output(UInt(8.W)) }) val x_n1 = RegNext(io.in, 0.U) val x_n2 = RegNext(x_n1, 0.U) val x_n3 = RegNext(x_n2, 0.U) io.out := io.in * b0.U(8.W) + x_n1 * b1.U(8.W) + x_n2 * b2.U(8.W) + x_n3 * b3.U(8.W) }
この記述はフィルタの重みをインスタンス時に変更出来るという点ではジェネレータとなるのだが、ジェネレータとしてはそれ以外の機能は持っていない。
このFIRフィルタ・ジェネレータをN tap対応のジェネレータに変更するにはどうすればいいのか?ということを考えていく。
通常は以下のようなステップを踏むとうまく行くだろう。
- ScalaでN tap対応を行ったFIRフィルタを実装し、これをリファレンスモデルとする
- リファレンスデザインを使ってテストを再度構築し、テストが所望の機能を確認出来ることを確認する
- Chiselで書いた
My4ElementFir
をN tap対応版のジェネレータに変更する - 2.で作成したテストを使って3.のN tap対応版FIRフィルタの機能を確認する
例題:FIRフィルタのリファレンスモデル
以下がBootcampに載っているFIRフィルタのリファレンスモデルのコードとなる。
/** * A naive implementation of an FIR filter with an arbitrary number of taps. */ class ScalaFirFilter(taps: Seq[Int]) { var pseudoRegisters = List.fill(taps.length)(0) def poke(value: Int): Int = { pseudoRegisters = value :: pseudoRegisters.take(taps.length - 1) var accumulator = 0 for(i <- taps.indices) { accumulator += taps(i) * pseudoRegisters(i) } accumulator } }
このリファレンスモデルのポイントについてを見ていこう。
Seq
コード中にあるとおりクラスのパラメータtaps
がSeq[Int]
で定義されている。これにより、クラスのインスタンス時にtap数とフィルタのパラメータを任意に決めることが出来るようになる。
Registers
以下のコードはリファレンスモデルのFIRフィルタ中のレジスタに相当するコードとなる。
var pseudoRegisters = List.fill(taps.length)(0)
クラスのパラメータであるtaps
はSeq
型なのでlength
を参照することでtap数がわかる。これをList
に与えることでデータを格納する場所を用意し0で初期化している。
なので、上記のコードだけを実行すると以下のようにtaps数に与えた要素数を持つ領域が確保される。
val taps = 10 var pseudoRegisters = List.fill(taps)(0)
- 実行結果
taps: Int = 10 pseudoRegisters: List[Int] = List(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
poke
Chiselのテストに合わせてpoke
メソッドが用意されている。この関数を一回呼び出すと、リファレンスモデルで1cycleに相当する処理が行われる。
そのため、実際のFIRフィルタの計算処理はこの関数で実行されることになるので少し細かく見ていこう
レジスタのアップデート
以下のコードは入力された値を使ってレジスタをアップデートするコードとなる。
pseudoRegisters = value :: pseudoRegisters.take(taps.length - 1)
Scalaもあんまりわかってないので、これを見た瞬間に::
ってちょいちょい出てくるけど何だっけ??となってしまった。。
ということでこのコードだけ切り出して、少し遊んで見る。
val value = 100 var pseudoRegisters = List.fill(taps)(0) pseudoRegisters = value :: pseudoRegisters.take(taps - 1)
- 実行結果
value: Int = 100 pseudoRegisters: List[Int] = List(100, 0, 0, 0, 0, 0, 0, 0, 0, 0)
value
に入れた値が先頭に入った状態になった。
では次にtake
の引数を変更してみる。
val value = 100 var pseudoRegisters = List.fill(taps)(0) pseudoRegisters = value :: pseudoRegisters.take(taps - 5)
- 実行結果
value: Int = 100 pseudoRegisters: List[Int] = List(100, 0, 0, 0, 0, 0)
take
の引数を変更することで、処理後のリストのサイズが縮んだのがわかると思う。
そのため、元のコードでは以下の処理を行うことで、レジスタ用のリストの先頭に入力されたデータを入れるという処理を実現している。
take
でpseudoRegisters
の要素数をtaps.size - 1
に変更value ::
で1.のリストとvalue
を結合して新しいリストを作成
List.take
一応、List.take
の処理をScala Standard Libraryを見て確認しておこう。
def take(n: Int): List[A] Selects first n elements. n:the number of elements to take from this list. returns:a list consisting only of the first n elements of this list, or else the whole list, if it has less than n elements. If n is negative, returns an empty list.
書いてあるとおりで、take
を呼ぶと元のList
の先頭からn
個の要素を選んだリストが返却されるようだ。
::
??
もうひとつついでに調べておこう。調べるのは::
ってなんて呼べばいいの??という話。以下の記事が色々Scalaの記号についてをまとめてくださっているので非常にためになった。
あと、呼び方だが以下のstack overlflowの記事によるとcons operatorということになるみたい。
Scala Standard Libraryのページは以下のページで良さそう。
先のQiitaの記事にも書いてあるけど、Scalaの記号は検索しにくいことこの上ないな。。。。
出力の計算
以下のループ処理でFIRフィルタの計算を実行して、最後にaccumulator
をpoke
関数のブロック式の値とすることで出力している。
var accumulator = 0 for(i <- taps.indices) { accumulator += taps(i) * pseudoRegisters(i) }
これは特に難しいことはなく、indices
を呼ぶことでList
のインデックスを取得してtaps
に入っているフィルタの係数とpseudoRegisters
の値を計算して積算していくだけとなっている。
List.indices
一応備忘の意味も込めてindices
の処理も確認しておく。
def indices: Range Produces the range of all indices of this sequence. returns a Range value from 0 to one less than the length of this sequence.
リファレンスモデルの中身を見るだけでそこそこの分量になってしまったので、ここで一回区切ることにしよう。