dsPIC33FJ64GP802 (5) --- DSP 命令と DSP ライブラリ (3)

DSP ライブラリの FIR() 関数を使う前に、下のようなデータを用意しておく必要があります。

  • 入力データが格納された配列 (要素数 N)
  • 出力データを受け取るための配列 (要素数 N)
  • タップ係数の配列 (要素数 M,、X-データ・メモリあるいはプログラム・メモリ、アライメント制限あり)
  • 遅延要素の配列 (要素数 M、Y-データ・メモリ、アライメント制限あり)
  • FIRStruct

 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];