dsPIC33FJ64GP802 (5) --- DSP 命令と DSP ライブラリ (3)
DSP ライブラリの FIR() 関数を使う前に、下のようなデータを用意しておく必要があります。
DSP ライブラリのドキュメントによると、FIR フィルタのタップ長を M、入出力サンプル数を N とすると、FIR() 関数の実行に必要なサイクル数は、C 言語での関数呼び出し/リターンのオーバーヘッドを含めて、
(タップ係数が X-データ・メモリ上にある場合)
61 + N*(4 + M)
(タップ係数がプログラム・メモリ上にある場合)
68 + N*(9 + M)
となっています。
上の式の定数項 (61 および 68) は FIR() 関数呼び出し 1 回あたりの固定オーバーヘッドであり、かなり大きな値となっています。
関数に入力サンプルを 1 個与えて呼び出し、結果の出力サンプルを 1 個受け取るのではオーバーヘッドが大きすぎて効率的ではないので、関数には複数の入力サンプルを格納したデータ配列を与え、複数の出力サンプルを得る方式になっています。
上の式を入出力サンプル数 N で割って、サンプルひとつ当たりのサイクル数に変換すると、
61/N + (4 + M)
68/N + (9 + M)
となり、N を 68 以上に選べば固定オーバーヘッドの寄与分はサンプルひとつ当たり 1 サイクル以下とすることができます。
固定オーバーヘッド部分が十分に小さい場合、(4 + M) および (9 + M) が所要サイクル数の大部分を占めますが、その差「5」サイクルは、M が 100 とか 200 のような場合には、ほとんど無視できるような差となり、タップ係数をプログラム・メモリに置く場合の性能劣化のデメリットよりデータ・メモリを消費しないメリットの方が大きくなります。
タップ係数や遅延要素の配列のアドレスなどは、個別に関数の引数として渡すのではなく、「FIRStruct」型の構造体にまとめて扱うようになっています。
FIRStruct の定義を下に示します。
typedef struct { int numCoeffs; fractional* coeffsBase; fractional* coeffsEnd; int coeffsPage; fractional* delayBase; fractional* delayEnd; fractional* delay; } FIRStruct;
「numCoeffs」はタップ係数および遅延要素の数 (上記の「M」) を指定します。
タップ係数のアクセスおよび遅延要素のアクセスにはハードウェアの「モジュロ・アドレッシング」機能を利用しており、
coeffsBase / coeffsEnd / coeffsPage
はタップ係数 (X-データ) 側の、
delayBase / delayEnd / delay
は遅延要素 (Y-データ) 側の設定値を表しています。
ハードウェアの制約により、タップ係数/遅延要素のメモリ上の「アライメント」には制限があります。
それは、開始アドレスの LSB 側から
floor(2 + log2(M - 1))
ビット分がゼロとなっていることが条件となっています。
「アライメント」の値で表現すると、(「**」をべき乗の演算子として)
2 ** floor(2 + log2(M - 1))
バイト境界に割り付ける必要があります。
代表的な値を表にすると、
M | アライメント |
---|---|
9 ~ 16 | 32 |
17 ~ 32 | 64 |
33 ~ 64 | 128 |
65 ~ 128 | 256 |
129 ~ 256 | 512 |
となります。
タップ係数を X-データ・メモリに (初期値付き) 配列データとして配置する場合、
#define M_tap 127 . . . . . <中略> . . . . . // FIR tap coefficient array in X-data memory fractional _XDATA(256) h[M_tap] = { -13, 15, 24, 2, -16, 4, 23, -4, . . . . . <中略> . . . . . };
のように宣言します。
ここで、「_XDATA(N)」は xc.h の中から呼び出されるデバイス別のインクルード・ファイル中で定義されている引数付きマクロで、対象を X-データ領域に割り付け、アライメントを引数 N の値に設定します。
FIRStruct 構造体の「coeffsPage」フィールドの値は、係数を X-データ領域に置く場合は、あらかじめ定義ずみの「COEFFS_IN_DATA」を指定します。
係数をプログラム・メモリに配置し、PSV 機能を使う場合には、デフォルトでは、(コンパイラ・オプションで「const-in-data」を指定しない限り) 「const」修飾子を付けるだけで PSV 機能が有効になって、コード領域に書き込まれた値がデータ領域から読み出せるようになります。
このマッピングは 32 Kバイト・ページ単位で行われるので、どのページを使うのかを FIRStruct 中の「coeffsPage」フィールドで指定する必要があります。
ただし、タップ係数として使わない「const」データも含め、全体で 32 Kバイト以下に収まっていれば、マッピングは 1 通りしかなく、ページ切り替えの必要もありません。
その場合には、あらかじめ設定されている「PSVPAG」レジスタの値を「coeffsPage」フィールドに設定すれば済みます。
したがって、コード領域にタップ係数を置く場合には、
// FIR tap coefficient array in program memory const fractional __attribute__((aligned(256))) h[M_tap] = { -13, 15, 24, 2, -16, 4, 23, -4, . . . . . <中略> . . . . . };
のように宣言します。
遅延要素については、Y-データ領域に割り付ける必要がありますが、初期値は必要ないので、.bss として扱えばよく、「_YBSS(N)」マクロを利用することができます。
// delay element array in Y-data memory fractional _YBSS(256) d[M_tap];