前回の記事ではChisel BootcampのModule2.4の学習を終えた。
今回はModule2.5の練習問題に取り組んでいく。
Module 2.5: FIRフィルタ:全てを一つに
モチベーション
まずはモチベーションから
これであなたはChiselの基礎についてを学んだ。さあその知識を使ってFIRフィルタを作ってみよう!FIRフィルタはデジタル信号処理において一般的なものだ。FIRフィルタはModule3において度々現れるものでもあるので、この演習をスキップせずにおいてほしい。もしFIRフィルタについて馴染みがなければ、信頼のおけるWikipediaの記事に目を通してみよう。
練習問題:FIRフィルタ
練習問題なので問題文をそのまま訳して載せていく。
これからデザインするFIRフィルタは以下の図のようなものだ。
基本的には各フィルタ係数と入力信号を要素ごとに乗算し足し合わせたものが出力とする(畳み込みとも呼ばれる処理だ)
式で書くと:
𝑦[𝑛]=𝑏0𝑥[𝑛]+𝑏1𝑥[𝑛−1]+𝑏2𝑥[𝑛−2]+...
上記のようになり、式中の要素は以下の意味を持つ
- 𝑦[𝑛]は 𝑛番目の出力信号
- 𝑥[𝑛]は入力信号
- 𝑏𝑖はフィルタ係数、もしくはインパルス応答
- 𝑛−1, 𝑛−2, ... は𝑛番目の要素を1, 2, ... と遅延したもの
8-bitの仕様
ここからは実際の設計するFIRフィルタの仕様とスケルトンの提示となる。
4つの重みをパラメータとするFIRフィルタを作ろう。モジュールのスケルトンと基本的なテストは既に与えられている。入力、出力ともに8-bitの符号なし整数であることに注意しよう。この演習ではシフトレジスタと同様に遅延した信号を保持するためのステートが必要になる。与えられたテスターを使用して自分の実装をチェックしてみよう。定数を入力とするレジスタは
ShiftRegister
のシフト値を1とするか、RegNext
を使用することで生成可能だ。注意:テストをパスさせるためにはレジスタは
0.U
で初期化される必要がある。
- スケルトンコード
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)) }) ??? }
- テスト
// Simple sanity check: a element with all zero coefficients should always produce zero Driver(() => new My4ElementFir(0, 0, 0, 0)) { c => new PeekPokeTester(c) { poke(c.io.in, 0) expect(c.io.out, 0) step(1) poke(c.io.in, 4) expect(c.io.out, 0) step(1) poke(c.io.in, 5) expect(c.io.out, 0) step(1) poke(c.io.in, 2) expect(c.io.out, 0) } }
上記のように、 まだ重みパラメータ 解答 - クリックすると開くので、見たくない場合は開かないように注意。
class My4ElementFir(b0: Int, b1: Int, b2: Int, b3: Int) extends Module {
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 z1 = RegInit(0.U)
val z2 = RegInit(0.U)
val z3 = RegInit(0.U)
z1 := io.in
z2 := z1
z3 := z2
io.out := io.in * UInt(b0) + z1 * UInt(b1) + z2 * UInt(b2) + z3 * UInt(b3)
}
// Simple sanity check: a element with all zero coefficients should always produce zero
Driver(() => new My4ElementFir(0, 0, 0, 0)) {
c => new PeekPokeTester(c) {
poke(c.io.in, 0)
expect(c.io.out, 0)
step(1)
poke(c.io.in, 4)
expect(c.io.out, 0)
step(1)
poke(c.io.in, 5)
expect(c.io.out, 0)
step(1)
poke(c.io.in, 2)
expect(c.io.out, 0)
}
}
RegInit
を使って愚直にレジスタに代入して計算してみた。b1
~b3
についてもUInt()
で変換している。この演習の答えではb1.U(8.W)
というふうにUInt
への変換を行っていた。
さて出来上がったFIRフィルタだが、入力するフィルタの係数を変化させることでよく知られるフィルタになる。設計したFIRフィルタが正しい処理を行っていればこれらのパラメータを使った残りのテストについても同様にパスできるはずだ。
シンプルな4点移動平均フィルタ
以下のテストのフィルタインスタンス部分のように係数を全て1にすると、4点移動平均フィルタになる。
// Simple 4-point moving average Driver(() => new My4ElementFir(1, 1, 1, 1)) { c => new PeekPokeTester(c) { poke(c.io.in, 1) expect(c.io.out, 1) // 1, 0, 0, 0 step(1) poke(c.io.in, 4) expect(c.io.out, 5) // 4, 1, 0, 0 step(1) poke(c.io.in, 3) expect(c.io.out, 8) // 3, 4, 1, 0 step(1) poke(c.io.in, 2) expect(c.io.out, 10) // 2, 3, 4, 1 step(1) poke(c.io.in, 7) expect(c.io.out, 16) // 7, 2, 3, 4 step(1) poke(c.io.in, 0) expect(c.io.out, 12) // 0, 7, 2, 3 } }
- 実行結果
[info] [0.000] Elaborating design... [deprecated] cmd6.sc:15 (4 calls): apply is deprecated: "use value.U" [warn] There were 1 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.008] Done elaborating. Total FIRRTL Compile Time: 27.5 ms Total FIRRTL Compile Time: 22.1 ms End of dependency graph Circuit state created [info] [0.000] SEED 1541249456529 test cmd6HelperMy4ElementFir Success: 6 tests passed in 10 cycles taking 0.011520 seconds [info] [0.010] RAN 5 CYCLES PASSED
ノンシンメトリック・フィルタ
// Nonsymmetric filter Driver(() => new My4ElementFir(1, 2, 3, 4)) { c => new PeekPokeTester(c) { poke(c.io.in, 1) expect(c.io.out, 1) // 1*1, 0*2, 0*3, 0*4 step(1) poke(c.io.in, 4) expect(c.io.out, 6) // 4*1, 1*2, 0*3, 0*4 step(1) poke(c.io.in, 3) expect(c.io.out, 14) // 3*1, 4*2, 1*3, 0*4 step(1) poke(c.io.in, 2) expect(c.io.out, 24) // 2*1, 3*2, 4*3, 1*4 step(1) poke(c.io.in, 7) expect(c.io.out, 36) // 7*1, 2*2, 3*3, 4*4 step(1) poke(c.io.in, 0) expect(c.io.out, 32) // 0*1, 7*2, 2*3, 3*4 } }
- 実行結果
[info] [0.000] Elaborating design... [deprecated] cmd6.sc:15 (4 calls): apply is deprecated: "use value.U" [warn] There were 1 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.021] Done elaborating. Total FIRRTL Compile Time: 28.8 ms Total FIRRTL Compile Time: 23.2 ms End of dependency graph Circuit state created [info] [0.000] SEED 1541249459324 test cmd6HelperMy4ElementFir Success: 6 tests passed in 10 cycles taking 0.012704 seconds [info] [0.009] RAN 5 CYCLES PASSED
とりあえず最初の演習が終わったので今日はここまで。
まだ残りとして
- IPXact
- FIRフィルタ・ジェネレータ
- DSPブロック
と続いていく。IPXactはそのままだと動かないし、DSPブロックはrocket-dsp-utilsのもので、今の自分からすると凝った作りになっているので後回しにする予定だ。 とりあえずFIRフィルタ・ジェネレータのみを実施して次のModuleに進むことにしようと思う。