PSoC5LP Prototyping Kit (26) --- SPDIF_Tx と DMA (14)

Filter コンポーネントで生成される DFB (Digital Filter Block) プログラムでは 2 チャネル分のフィルタがサポートされています。
これをステレオ 2 チャネルのストリームとして利用し、データ転送の全てを DMA で行なう場合には、DFB データ入出力の合計が 4 となるのに対応して (少なくとも) DMA チャネルが 4 個必要になります。
実際には、SPDIF_Tx コンポーネントとの間に DMA バッファを設ける必要があるので、下のブロック図に示すように、合計 6 チャネルの DMA が必要になります。

CPU からは DMA バッファ (DAC FIFO バッファ) にステレオ・サンプル (32 ビット、4 バイト) 単位で書き込みます。
それを、 fs 周期のクロックでトリガされる DMA によって 16 ビット (2 バイト) 単位で DFB のステージング・レジスタに書き込みます。 この fs 周期のクロックのタイミングと、SPDIF_Tx コンポーネントの DMA 要求タイミングとは、直接的には関係ありません。
DFB でフィルタ計算が完了してホールディング・レジスタに結果が用意できた時点で DFB から発生する DMA 要求をトリガとして SPDIF_Tx コンポーネントとの間の DMA バッファに 16 ビット (2 バイト) 単位で書き込みます。
SPDIF_Tx コンポーネントの設定としては、L/R 独立の DMA を使用する「セパレート・モード」にします。
SPDIF_Tx 側では、DFB 側とは関係のない自分のタイミングで入力データ転送の DMA 要求を発生させます。
これをトリガとして DMA バッファから 8 ビット (1 バイト) 単位で SPDIF_Tx コンポーネントのデータパス・セルの入力 FIFO に書き込みます。
DFB 出力と SPDIF_Tx 入力との間の DMA バッファにより、独立なタイミングで動く両者の間でのデータの受け渡しに支障がないようにしています。
この構成では、DFB 入力から SPDIF_Tx 入力まで L/R 独立の 2 本のデータ・ストリームを使用する「セパレート・モード」で動作します。
SPDIF_Tx コンポーネントでは、L/R シリアル・データを交互に 1 本のデータ・ストリームに乗せる「インターリーブ・モード」の設定があるので、DFB も「インターリーブ・モード」で動作可能ならば、下のブロック図のように DMA のチャネル数が半減できます。

DFB 入力のデータ・ストリーム数が 2 から 1 に半減したのに伴い、DMA をトリガするレートが倍の 2fs になっています。
DFB の「インターリーブ・モード」とは、偶数番の入力サンプルが例えば L チャネルに属するとすれば、奇数番の入力サンプルは R チャネルに属するものとして内部のサンプル・メモリに記憶してフィルタ計算を行なうものです。
もちろん、Filter コンポーネントでサポートされているわけではないので、Filter コンポーネントで生成される DFB プログラムに変更を加え、DFB コンポーネントで読み込んで利用します。
Filter コンポーネントで A/B ともに FIR フィルタとして指定して得られるプログラムの処理の流れのフローチャートを下に示します。

全体の初期化が済んだ後、ステージング・レジスタ A/B にデータが到着したかどうかを見て、

  • A 側に入力データがあれば A 側の設定でのフィルタ計算をしてホールド・レジスタ A に出力を行い、
  • B 側に入力データがあれば B 側の設定でのフィルタ計算をしてホールド・レジスタ B に出力を行う

というループになっています。
これを、

  • 入力チェックおよび入力データ読み込みは STAGEA のみ
  • 出力は HOLDA のみ

行なうように変更を加えたフローチャートを下に示します。

入出力は A 側しか使いませんが、フィルタ計算自体は A / B それぞれの設定での計算を交互に行います。
これで「インターリーブ・モード」を実現できます。
必要な変更箇所は、

  • 入力チェックを行なう 2 命令
  • 無条件ジャンプ命令の削除 1 ヵ所
  • ステージング・レジスタから入力データを読み込む命令 1 つ
  • ホールディング・レジスタに出力データを書き込む命令 1 つ

の計 5 ヵ所です。
1 命令削除するので、プログラムは小さくなります。 ただし、この無条件ジャンプ命令を残しておいても「無駄」になるだけでプログラムの実行には影響を及ぼしません。
具体的にプログラムを示すと、入力チェック部は、変更前は


channelA:
  acu(hold,hold) dmux(sa,sa) alu(hold) mac(hold) jmp(in1, CHANNELA_INIT)
 

channelB:
  acu(hold,hold) dmux(sa,sa) alu(hold) mac(hold) jmp(in2, CHANNELB_INIT)
 

restart:
  acu(hold,hold) dmux(sa,sa) alu(hold) mac(hold) jmp(eob, channelA)

変更後は、


channelA:
  acu(hold,hold) dmux(sa,sa) alu(hold) mac(hold) jmpl(in1, CHANNELA_INIT)
 

channelB:
  acu(hold,hold) dmux(sa,sa) alu(hold) mac(hold) jmpl(in1, CHANNELB_INIT)
 

//restart:
// acu(hold,hold) dmux(sa,sa) alu(hold) mac(hold) jmp(eob, channelA)

です。 変更箇所は赤字で示してあります。
「jmp()」を「jmpl()」に変更します。 restart: で始まるブロックをコメント・アウトあるいは削除します。
ステージング/ホールディング・レジスタ部は、変更前は、


ChB_init:
  acu(loadm, loadm) addr(ChB_MAX) dmux(sa,sa) alu(hold) mac(hold)
  acu(loadl, loadl) addr(ChB_MIN) dmux(sa,sa) alu(hold) mac(hold)
  acu(hold, hold) addr(0) dmux(ba,sa) alu(hold) mac(hold)
  acu(read, clear) addr(ChB_START) dmux(sra,sra) alu(setb) mac(clra) write(da) jmp(eob,ChB_fir)
 

ChB_firFinish:
  acu(write, clear) addr(ChB_START) dmux(sa, srm) alu(setb) mac(hold)
  acu(hold, hold) dmux(sa, sa) alu(hold) mac(hold)
  acu(hold, hold) addr(0) dmux(sa, sa) alu(hold) mac(hold) write(bus) jmp(eob, ChB_EXIT)

変更後は、


ChB_init:
  acu(loadm, loadm) addr(ChB_MAX) dmux(sa,sa) alu(hold) mac(hold)
  acu(loadl, loadl) addr(ChB_MIN) dmux(sa,sa) alu(hold) mac(hold)
  acu(hold, hold) addr(1) dmux(ba,sa) alu(hold) mac(hold)
  acu(read, clear) addr(ChB_START) dmux(sra,sra) alu(setb) mac(clra) write(da) jmp(eob,ChB_fir)
 

ChB_firFinish:
  acu(write, clear) addr(ChB_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, ChB_EXIT)

となります。 「addr(0)」を「addr(1)」に変更します。
(2016 年 3 月 21 日追記)
ストリーム・エディタ sed が使える環境なら、シェル上で次のような 1 行を実行させれば必要な変更が行えます。

sed -e "s/(in[12]/l(in1/;s/addr(0)/addr(1)" <dfb.v2 >dfb_LR.v2 

ただし、「dfb.v2」を Filter コンポーネントの設計ツールを使用して作成した (改造前の) DFB プログラムのファイル名とし、「dfb_LR.v2」をインターリーブ改造後のファイル名としています。