PSoC5LP Prototyping Kit (19) --- DFB と Filter (11)

Filter コンポーネントで作成される IIR バイクアッド・フィルタの計算プログラムでは、2 次セクションの伝達関数
\qquad\qquad H(z)\, = \,\frac{\normalsize \,a_0\, +\, a_1 \cdot z^{-1}\, + \,a_2 \cdot z^{-2}\,}{\normalsize \,\,1\, -\, b_1 \cdot z^{-1}\,-\,b_2 \cdot z^{-2}}
の分子多項式の計算をまず行い、ついで分母多項式の計算を行なう「直接型 I」と呼ばれる方式で行なっています。 図示したものを下に示します。

2 次ディジタル・フィルタでは、必要な遅延要素の最小値は 2 となり、最小値の 2 個の遅延要素だけで実現したものは「標準型」と呼ばれています。
「直接型」では遅延要素が 4 個となり、余計な遅延要素が必要になっていますが、この 2 次セクションを縦続接続すると下の図のように、隣り合うセクション同士で遅延要素を共有できて、遅延要素の数を減らせます。


上の図では、2 次セクション 2 段の構成であり、標準型では遅延要素 4 個必要なのに対して、遅延要素 6 個で実現されています。
一般に 2 次セクションの段数を m とすると、必要な遅延要素の個数は (2 × m + 2) となります。 つまり、標準型と比較して常に 2 個だけ余分に遅延要素が必要になります。
上の図ではデータ RAM はアドレス 0 から割り付けられるものとし、A[・] は遅延要素として使われているデータ RAM A の要素を示し、B[・] はタップ係数を格納しているデータ RAM B の要素を示しています。
2 次セクション当たりタップ係数としてデータ RAM B は 5 ワード使い、遅延要素としてデータ RAM A は 2 ワード使います。 そして全体として、余分なデータ RAM A を 4 ワード、余分なデータ RAM B を 1 ワード使います。
この 2 次セクション 1 段分の計算を IIR バイクアッド・フィルタ計算プログラムでは次の 10 命令で実行します。 この例では、フィルタ・チャネル B として定義しています。

ChB_biquad:
  acu(write, write) addr(TEMP) dmux(sa, sa) alu(hold) mac(hold)
  acu(read, read) addr(ROUND) dmux(sra, sra) alu(hold) mac(clra)
  acu(read, read) addr(TEMP) dmux(sa, sra) alu(hold) mac(macc)
  acu(incr, incr) dmux(sra, sra) alu(seta) mac(macc)
  acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold) write(da)
  acu(incr, incr) dmux(sra, sra) alu(hold) mac(macc)
  acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold) write(da)
  acu(incr, incr) dmux(sra, sra) alu(hold) mac(macc)
  acu(incr, incr) dmux(sra, sra) alu(hold) mac(macc)
  acu(subf, incr) dmux(srm, srm) alu(add) mac(hold) jmpl(acubeq, ChB_finish)

この 10 命令の内訳は、

  • 計算結果の丸めのための定数ロード — 1 命令
  • ACU 内の REG A / B の退避/復旧 — 2 命令
  • 遅延要素間のデータ転送 — 2 命令
  • 遅延要素のデータとタップ係数との乗算 — 5 命令

となっています。 前段の 2 次セクションの計算結果は ALU に入っています。
まず、次の最初の 3 行で丸めの定数ロード、REG A / B の退避 / 復旧を行なっています。

  acu(write, write) addr(TEMP) dmux(sa, sa) alu(hold) mac(hold)
  acu(read, read) addr(ROUND) dmux(sra, sra) alu(hold) mac(clra)
  acu(read, read) addr(TEMP) dmux(sa, sra) alu(hold) mac(macc)

上の 3 行の主目的は、2 行目に書かれている丸めのための定数を ACURAM の ROUND エントリでポイントされるデータ RAM から読み出すことですが、そのために REG A / B レジスタが変更されてしまうので、ACURAM の TEMP エントリでポイントされるメモリに退避 / 復旧するためのコードが 1 行目と 3 行目についています。
TEMP と ROUND については下のように定義されています。

area acu
  TEMP: dw 0x7E7E
  ROUND:dw 0x7F7F

area data_a
  org 127
  dw 2048

area data_b
  org 127
  dw 2048

ここで、area acu の中で dw 0x7E7E のように記述されているのは、16 進定数でアドレスを定義する場合の書き方で、上位 8 ビットが ACURAM A 側、下位 8 ビットが ACURAM B 側を示します。
RAM アドレスは 7 ビットなので、そのまま自然に隙間なく並べると全体では 14 ビットとなり、 A / B の区切りが 7 ビット目に来て 16 進表示では読みにくくなるため、あえて区切り位置を 8 ビット目にしているようです。
area data_a の最初の行の org 127 は、アセンブラのディレクティブで、CLC (Current Location Couter) を指定の値にセットします。 次の行の dw 2048 が丸めのための定数になります。
3行目の

  acu(read, read) addr(TEMP) dmux(sa, sra) alu(hold) mac(macc)

が最初のタップ係数 a0 に対する計算ですが、dmux(sa, sra) となっているので、A サイドのデータ・サンプル側はデータ RAM からではなく、シフタを通して ALU から供給されます。 (A[0] は使われません)
B サイドのタップ係数は、5 個 1 組の内の最初の値 (B[0]) が使われます。
4 行目の

  acu(incr, incr) dmux(sra, sra) alu(seta) mac(macc)

でタップ係数 a1 に関する計算 (B[1] × A[1]) が行なわれ、データ RAM A[1] の値が ALU にセットされます。
5 行目の

  acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold) write(da)

で、ALU に保持されている値が (シフタ経由で) データ RAM A[1] に書き戻されますが、このとき、ALU 出力はパイプライン・ディレイにより 4 行目でロードした値ではなく、ロード前の値である前段の計算結果が書き込まれると思われます。
6 行目で、

  acu(incr, incr) dmux(sra, sra) alu(hold) mac(macc)

でタップ係数 a2 に関する計算 (B[2] × A[2]) を行い、7 行目で、

  acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold) write(da)

により、今度はロード後の ALU の値 (書き戻し前の A[1] の値) が A[2] に書き込まれます。
これで分子側の多項式の計算および遅延要素間のデータの転送が終了しました。
8 行目、9 行目

  acu(incr, incr) dmux(sra, sra) alu(hold) mac(macc)
  acu(incr, incr) dmux(sra, sra) alu(hold) mac(macc)

でタップ係数 b1、b2 に関する計算 (B[3] × A[3]、B[4] × A[4]) を行います。
10 行目の

  acu(subf, incr) dmux(srm, srm) alu(add) mac(hold) jmpl(acubeq, ChB_finish)

では、IIR 計算の前準備で FREG に「2」がロードされているので、acu(subf, incr) により、REG A は 2 引き戻されて A[2] を指すようになり、REG B はインクリメントされて B[5] を指すようになります。
初段の 2 次セクションで A[0] が使われなかったのと同様に、2 段目の 2 次セクションの計算では前段の遅延要素である A[2] の内容には一切変更を加えません
jmpl(acubeq, ChB_finish) 命令により、REG B が MREG B に等しくなるまでループを続けます。 2 次セクションの数をカウントしているわけではないので、タップ係数の総数が 5 の倍数になっていないとうまく終了条件を検出できません。
Filter コンポーネントではカスタム係数をインポートする際に 5 の倍数になっていないとエラーになりますが、dfb.v2 ファイルに手を加えてフィルタ係数の差し替えなどを行なう場合には注意する必要があります。
IIR フィルタ自体の「計算」は終了しても、最終段の遅延要素 2 個はシフトされないまま残っているので、後処理のブロック ChB_finish で後始末を行います。

ChB_finish:
  acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold) shift(L, 0)
  acu(incr, hold) addr(0) dmux(sra, sa) alu(seta) mac(hold) write(bus)
  acu(hold, hold) dmux(sra, sra) alu(seta) mac(hold) write(da)
  acu(incr, hold) dmux(sa, sa) alu(hold) mac(hold) write(da) jmp(eob, ChB_EXIT)

1 行目の shift(L, 0) はシフタのシフト量を設定する命令ですが、0 ビット左シフトということで意味がありません。
Filter コンポーネントではフィルタ・ゲインを -40 dB から 40 dB の範囲で選べるので、ゲイン調整の一部をシフタに負担させるのかも知れません。
2 行目では、addr(0) write(bus) でフィルタ結果をホールディング・レジスタ B に書き込みながら、 acu(incr, hold) dmux(sra, sa) alu(seta) によりデータ RAM A から ALU に最終段の出力側の z-1 の項のデータを読み込んでいます。
3 行目では write(da) で z-1 の項に対応するデータ RAM A にフィルタ結果のデータを書き込んでいます。
4 行目の acu(incr, hold) dmux(sa, sa) write(da) により、ALU に読み込まれた z-1 の項のデータを z-2 の項に書き込んでいます。