PSoC5LP Prototyping Kit (18) --- DFB と Filter (10)

今回は Filter コンポーネントで生成される実際の FIR フィルタ・プログラムについて説明します。
1 行が長くて折り返して表示されるので見にくいですが、フィルタ・チャネル A の計算前準備 / たたみ込み演算 / 後処理部のリストを下に示します。
各ステート間はジャンプ命令で遷移するので、実行順序はソース・プログラム上の記述順ではなく、

  • ChA_init
  • ChA_fir
  • ChA_firFinish

の順に進みます。

ChA_init:
    acu(loadm, loadm) addr(ChA_MAX) dmux(sa,sa) alu(hold) mac(hold)
    acu(loadl, loadl) addr(ChA_MIN) dmux(sa,sa) alu(hold) mac(hold) 
    acu(hold, hold) addr(1) dmux(ba,sa) alu(hold) mac(hold) 
    acu(read, clear) addr(ChA_START) dmux(sra,sra) alu(setb) mac(clra) write(da) jmp(eob,ChA_fir)

ChA_firFinish:
    acu(write, clear) addr(ChA_START) dmux(sa, srm) alu(setb) mac(hold)
    acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold)
    acu(hold, hold) addr(1) dmux(sa, sa) alu(hold) mac(hold) write(bus) jmp(eob, ChA_EXIT)

ChA_fir:
    acu(incr,incr) dmux(sra,srm) alu(setb) mac(macc) jmpl(eob,acubeq, ChA_firFinish)

area acu
    ChA_MAX: dw  ChA_LAST,ChA_LAST
    ChA_MIN: dw  ChA_FIRST, ChA_FIRST
    ChA_START: dw ChA_FIRST, ChA_FIRST

area data_b
ChA_FIRST:dw 18603
. . . . . <中略> . . . . .
ChA_LAST: dw 18603

ChA_EXIT = channelB

まず、ChA_init: ブロックの最初の 2 行の、命令語の中の重要なフィールドだけを抜き出したものを下に示します。

    acu(loadm, loadm) addr(ChA_MAX)
    acu(loadl, loadl) addr(ChA_MIN) 

最初の行は ACU A / B それぞれの MREG に「loadm」命令でモジュロ演算の最大値を間接アドレシングによりロードしており、2 行目は LREG に「loadl」命令でモジュロ演算の最小値を間接アドレシングによりロードしています。
addr(・) のフィールドで ACURAM のインデクスを指定しています。
ChA_MAX と ChA_MIN は、

area acu
    ChA_MAX: dw  ChA_LAST,ChA_LAST
    ChA_MIN: dw  ChA_FIRST, ChA_FIRST
    ChA_START: dw ChA_FIRST, ChA_FIRST

area data_b
ChA_FIRST:dw 18603
. . . . . <中略> . . . . .
ChA_LAST: dw 18603

で定義されています。
ここで「area」は、コード/データ生成の対象となる RAM の種別を指定するアセンブラ・ディレクティブで、「area acu」は ACURAM を対象にすることを示しています。
一番最初の「area acu」指定なので、「カレント・ロケーション・カウンタ」(CLC: Current Location Counter) はデフォルト値の 0 になっています。
したがって、シンボル ChA_MAX / ChA_MIN / ChA_START の値としては、それぞれ 0 / 1 / 2 が割り当てられます。
ChA_MAX, ChA_MIN の内容は初期設定から変化せず、使用するデータ RAM 領域の両端を示す「定数」として使われています。 ChA_START は、新着サンプル・データの格納場所および FIR たたみ込み計算を開始する位置を示す「変数」として使われており、その内容はプログラムによって書き換えられます。
「dw」ディレクティブで ACURAM に設定する初期値を指定します。 カンマで区切って、ACURAM A, B の順に記述します。
ここでは ChA_LAST, ChA_FIRST というシンボルが使われていますが、これらはデータ RAM B を対象にする「area data_b」のセクションでラベルとして定義されています。
データ RAM B は FIR フィルタのタップ係数メモリとして使われており、タップ係数が (N) 個並べられています。 「area data_b」としては最初の指定なので、CLC はゼロになっており、ChA_FIRST の実際の値は「0」になります。
タップ係数が並ぶ部分は省略してありますが、設計時にタップ数 N = 63 のフィルタを指定したので、係数の数は 63 であり、実際の ChA_LAST の値は 62 になります。
ChA_init ブロックの残りの 2 行の、命令語の中の重要なフィールドだけを抜き出したものを下に示します。

    acu(hold, hold) addr(1) dmux(ba,sa) 
    acu(read, clear) addr(ChA_START) dmux(sra,sra) mac(clra) write(da) jmp(eob,ChA_fir)

この 2 行で、

  • ステージングレジスタ A からの新着入力サンプル読み込み
  • データ RAM アドレス・ポインタ REG A へ間接アドレシングで計算開始アドレスのロード
  • MAC のアキュムレータ・クリアおよび最初のタップ係数に対する計算開始

の操作を行なっています。
上の 1 行目 (元の ChA_init ブロックの 3 行目) の acu(hold, hold) は ACU 内の REG の状態を変化さない、つまり、前サイクルと同じ状態に保ち、データ RAM アドレスとして REG の内容を出力することを表します。
hold 状態では addr() の指定は意味がないのですが、ステージング・レジスタ A / B 出力をデータパス内部につなぐマルチプレクサのセレクト入力として addr() で指定する値の LSB が (ACU 内での用途に関わらず) 使われています。 LSB = 1 がステージング・レジスタ A の指定、LSB = 0 がステージング・レジスタ B の指定となります。
また、dmux(ba, sa) でステージング・レジスタからデータ RAM A 側にデータを流すことを指定しています。
dmux() の第 1 引数には A 側、第 2 引数には B 側のマルチプレクサの状態を示す 3 文字あるいは 2 文字のコードを指定します。
コードの 1 文字目は、データ RAM 入力側にある MUX1 の選択状態を示しています。 「b」が bus、つまりステージング・レジスタ側につながることを示し、「s」がシフタ (shifter) 側につながることを示しています。
コードの 2 文字目は、データ RAM 出力側にある MUX2 の選択状態を示しています。 「r」が RAM 出力とつながることを示し、文字が省略されて全体で 2 文字になっている場合には MUX1 出力をそのまま通すことを示しています。
コードの最終文字は、ALU 入力側にある MUX3 の選択状態を示しています。 「m」が MAC 出力とつながることを示し、「a」の場合には、MAC をバイパスして MUX2 出力をそのまま通すことを示しています。
上のプログラムの 1 行目の状態を図示すると次のようになります。

ステージング・レジスタ A からデータ RAM A の入力、MAC の A 入力、ALU の A 入力ににデータを流すことができます。
上のプログラムの 2 行目 (元の ChA_init ブロックの 4 行目) では多くの操作を同時に行なっています。
acu(read, clear) addr(ChA_START) は、ACU A に対するコマンド「read」により、ACURAM のエントリ ChA_START から RAM アドレスを読み出してアドレス・ポインタ REG A にセットしています。 
この ChA_START のエントリに格納されている RAM アドレスは、各サンプルごとに変化します。
ACU B に対するコマンドは「clear」であり、 REG B をゼロ・クリアするものですが、もっと正確に言えば、モジュロ演算モードが選択されている場合には LREG B の値を REG B にロードするものです。
LREG B の値は元の ChA_init ブロックの 2 行目の「loadl」でゼロにセットされるので、実質ゼロクリアと変わりありません。
C 風の式で書くと、REG A / B はそれぞれ、

REG_A = acuram[ChA_START];
REG_B = acuram[ChA_MIN];

ということになります。 タップ係数ポインタである REG B は毎回同じ値 (具体的には「0」) がロードされますが、サンプル・メモリ・ポインタである REG A には毎回異なる値がロードされます。
write(da) はデータ RAM A にデータを書き込む命令です。
mac(clra) は MAC のアキュムレータをクリアし、現在の A / B 入力の積を加算します。 アキュムレータ・クリアだけではないことに注意する必要があります。
この命令で FIR フィルタのたたみ込み演算の最初のタップ係数の計算が行なわれます。
jmp(eob,ChA_fir)で FIR たたも込み演算の本体の ChA_fir へ無条件分岐します。

ChA_fir:
    acu(incr,incr) dmux(sra,srm) alu(setb) mac(macc) jmpl(eob,acubeq, ChA_firFinish)

acu(incr, incr) はアドレス・ポインタの REG A / REG B をそれぞれインクリメントすることを示します。
dmux(sra, srm) なので MAC にはデータ RAM A / B からデータが供給され、その結果は ALU の B 入力に現れます。
mac(macc) で「普通」の multply and accumlate オペレーションが実行されます。
alu(setb) により、ALU の B 入力がそのまま出力されます。 MUX3b により B 入力には MAC 出力が接続されているので、ALU は MAC 出力を素通しすることになります。
jmpl(eob,acubeq, ChA_firFinish) では、acubeq 条件が成立しない限りループし、成立したらChA_firFinish ブロックへ分岐します。
タップ係数メモリ・アドレス・ポインタである REG B が。 MREG B に保持されている最終タップ係数のアドレス ChA_LAST に等しくなると acubeq 条件は成立します。 したがって、最終タップの計算を終えると ChA_fir_finish に分岐します。
最初のタップ係数に関する計算は ChA_init ブロックの中で行なわれているので、ChA_fir のループを回る回数はタップ数を N として (N-1) 回となります。

ChA_firFinish:
    acu(write, clear) addr(ChA_START) dmux(sa, srm) alu(setb) mac(hold)
    acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold)
    acu(hold, hold) addr(1) dmux(sa, sa) alu(hold) mac(hold) write(bus) jmp(eob, ChA_EXIT)

ChA_firFinish の 1 行目では、更新されたサンプル・メモリ・ポインタ REG A の値を ACURAM A に格納しておき、次回のサンプルに対する計算で利用します。
acu(write, clear) addr(ChA_START) は、ACURAM の ChA_START エントリに現在の REG A の値を書き込むことを示しています。
API の Start() 関数を呼び出して初期化された後の最初のサンプルに対しては、ChA_START エントリの初期値 ChA_FIRST からサンプル・データの読み出しが始まり、ChA_LAST で終了します。
したがって、ACURAM の ChA_START エントリには ChA_LAST の値が格納されることになります、 次のサンプルに対する計算は、この ChA_LAST から始まることになります。
mac(hold) なので、FIR たたみ込み計算結果はそのまま保持し、dmux(sa, srm) により MAC 出力を ALU B 入力に接続し、alu(setb) により ALU の B 入力を出力に素通ししています。
ChA_firFinish のブロックの 2 行目は ACU / MAC / ALU ともに「hold」で、特に意味のある動作はしていないようですが、パイプライン・ディレイの調整かも知れません。
3 行目は FIR フィルタ計算結果のホールディング・レジスタ A への書き込みです。
addr(1) はステージング・レジスタの選択と同様に、LSB = 1 によりホールディング・レジスタ A を選択することを示します。 write(bus) は ALU/シフタ/ラウンダ 出力のホールディング・レジスタへの書き込みの命令です。
jmp(eob, ChA_EXIT) により ChA_EXIT のブロックへ無条件分岐します。
ChA_EXIT シンボルについては、

ChA_EXIT = channelB

という equate により、フィルタ・チャネル B の入力があるかどうかをチェックする channelB シンボルに置き換えられます。
フィルタ・チャネル A の処理の後、チャネル A の入力チェックへは飛ばず、チャネル B の入力チェックへ飛ぶことで、チャネル A / B のデータがほぼ同時に入力された場合に少しでも早くチャネル B の処理に移れるようにしています。
次回は IIR バイクアッド・フィルタのプログラムについて説明します。