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

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

Chisel Bootcamp - Module2.5 (1) - Chiselで書くFIRフィルタ

スポンサーリンク

前回の記事ではChisel BootcampのModule2.4の学習を終えた。

www.tech-diningyo.info

今回はModule2.5の練習問題に取り組んでいく。

Module 2.5: FIRフィルタ:全てを一つに

モチベーション

まずはモチベーションから

これであなたはChiselの基礎についてを学んだ。さあその知識を使ってFIRフィルタを作ってみよう!FIRフィルタはデジタル信号処理において一般的なものだ。FIRフィルタはModule3において度々現れるものでもあるので、この演習をスキップせずにおいてほしい。もしFIRフィルタについて馴染みがなければ、信頼のおけるWikipediaの記事に目を通してみよう。

練習問題:FIRフィルタ

練習問題なので問題文をそのまま訳して載せていく。

これからデザインするFIRフィルタは以下の図のようなものだ。

f:id:diningyo-kpuku-jougeki:20181103215907j:plain

基本的には各フィルタ係数と入力信号を要素ごとに乗算し足し合わせたものが出力とする(畳み込みとも呼ばれる処理だ)

式で書くと:

𝑦[𝑛]=𝑏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に進むことにしようと思う。