2012年5月26日土曜日

FPGA FMトランスミッタ: ASRC、コーデックとのインターフェース

ASRCやコーデックの信号をFPGAに取り込むモジュール codec_if  のコードを作成しました 。ここではASRCかコーデックのいずれかのシリアルデータを受け取って、パラレル信号に変換したり、ASRCとコーデックを動作させるために必要なクロック信号を生成します。



まずは、ASRC、コーデックのデータフォーマットを確認しておきます。
データフォーマットはともに同じで、オーディオデータは24ビット左詰め(Left Justified)です。

ASRC(AD1895)のデータフォーマット


CODEC(CS4270)のデータフォーマット


もう少し細かく見ておきます。
サンプルレート152kHzですからLRCKは同じ152kHz(周期約6.6us)、SCLKはLRCKの64倍の9.728MHz(周期約103ns)です。
ここでAD1895のタイミングダイアグラムを見ると、SCLKの立ち下がりよりtDOPD(20ns)未満でSDATAが確定することがわかります。SDATAは、次のSCLKの立ち下がりからtDOH(3ns)以上ホールドされます。このため、FPGAではSCLKの立ち上がりエッジ付近でSDATAを読めばデータを取り込むことができそうです。

ASRC(AD1895)のタイミングダイアグラム


次にCS4270のタイミングダイアグラムを見ると、AD1895よりもSDOUTが確定している時間が短いです。SCLKの立ち上がりを中心にして、前tstp(10ns以上)~後(5ns以上)の時間のみデータが有効でそれ以外では不定となります。

CODEC(CS4270)のタイミングダイアグラム


ところで、ASRCに与えるマスタクロック(MCLK)にはいくつか条件がついています。

・ASRC出力側サンプルレートの138倍以上
・30MHz以下
・Pulsewidth High 8ns以上

・Pulsewidth High 12ns以上


FPGAのクロック(512fs, 77.824MHz)を3分周しても良いですが、FPGA埋め込みのハードマクロD DCM_SP (Digital Clock Manager) を使ってクロックを3/8倍して192fsを作ります。コード中では、このハードマクロをpll78to29という名称でインスタンシエートしています。

DCM_SPのマニュアル類は次の通りです。

Spartan-3Eライブラリガイド(HDL用)
Spartan-3E FPGAファミリ: データシート(全モジュール)

DCM_SP 設定画面1

DCM_SP 設定画面2


シミュレーション

FPGAをさわり始めた頃は、『良くわからない』からシミュレーションを避けていました。それこそトライアンドエラー。今から考えるとよくやれていたものだと思います。
シミュレーションを使えるようになると何となく自信も出てきます。作成したコードを、実デバイスにインプリメントする前から一発動作してくれるじゃないかという期待です。(検証が十分ではないのでそんなに甘くはありません)
シミュレーションにはISE付属のISimを使っています。パラシリ変換器のパラレル入力(if_mon0_i/if_mon1_i)に定数をセットし、生成したシリアル信号(codec_datas_o)をシリパラ変換器の入力(asrc_data_i/codec_datar_i)にループバックしてパラレル信号(if_l_o/if_r_o)に戻して入出力を比較しました。


ISimでのシミュレーション結果


上の波形を拡大


Verilogコード
(注)このコードは実機テストしていません!


module codec_if(
   asrc_mclk_o, asrc_bck_o, asrc_lrck_o, asrc_data_i,
   spdif_nonaudio_i, spdif_error_i, codec_datar_i, codec_sck_o, codec_mck_o,
   codec_lrck_o, codec_datas_o, if_l_o, if_r_o, if_mon0_i, if_mon1_i,
   en, clk, reset, if_sel
   );

   parameter AUDIO_BIT_DEPTH = 24;

   output asrc_mclk_o;   // 192fs (DCM_SP)
   output asrc_bck_o;   // 64fs
   output asrc_lrck_o;   // fs
   input asrc_data_i;
   input spdif_nonaudio_i;
   input spdif_error_i;
   input codec_datar_i;
   output codec_sck_o;   // 64fs
   output codec_mck_o;   // 64fs
   output codec_lrck_o;   // fs
   output codec_datas_o;
   output [AUDIO_BIT_DEPTH-1:0] if_l_o;
   output [AUDIO_BIT_DEPTH-1:0] if_r_o;
   input [AUDIO_BIT_DEPTH-1:0] if_mon0_i;
   input [AUDIO_BIT_DEPTH-1:0] if_mon1_i;
   output en;
   input clk;   // 512fs
   input reset;
   input if_sel;   // 0: ASRC, 1: CODEC

   reg [8:0] div_count;
   wire [5:0] bit_count;
   wire read_latch;
   wire write_latch;
   wire en;

   // Receive Buffer
   reg [AUDIO_BIT_DEPTH-1:0] sreg0;   // ASRC L
   reg [AUDIO_BIT_DEPTH-1:0] sreg1;   // ASRC R
   reg [AUDIO_BIT_DEPTH-1:0] sreg2; // CODEC L
   reg [AUDIO_BIT_DEPTH-1:0] sreg3; // CODEC R

   // Send Buffer
   reg [AUDIO_BIT_DEPTH-1:0] sreg4;   // Ch0 /L
   reg [AUDIO_BIT_DEPTH-1:0] sreg5;   // Ch1 /R

   reg [AUDIO_BIT_DEPTH-1:0] if_l_o;
   reg [AUDIO_BIT_DEPTH-1:0] if_r_o;
   reg codec_datas_o;
   wire spdif_valid;
   wire pll_locked;
   wire bck;


///////////////////////////////////////////////
//   timing generator
///////////////////////////////////////////////

   always @(posedge clk or posedge reset)
      begin
         if (reset)
            div_count <= 9'b0;

         else if (div_count == 9'b1_1111_1111)
            div_count <= 9'b0;
            
         else
            div_count <= div_count + 9'b1;
      end

   assign en = (div_count == 9'b1_1111_1111);
   assign read_latch = (div_count[2:0] == 3'b011);
   assign write_latch = (div_count[2:0] == 3'b000);
   assign bit_count = div_count[8:3];
   assign bck = div_count[2];   // 64fs
   assign asrc_lrck_o = ~div_count[8];   // fs
   assign codec_lrck_o = ~div_count[8];   // fs
   assign asrc_bck_o = bck;
   assign codec_sck_o = bck;
   assign codec_mck_o = bck;

   // DCM_SP: 77.824MHz *3/8 => 29.184MHz
   pll78to29 pll78to29 (
      .CLKIN_IN(clk),            // Clock input (from IBUFG, BUFG or DCM)
      .RST_IN(reset),            // DCM asynchronous reset input
      .CLKFX_OUT(asrc_mclk_o),   // DCM CLK synthesis out (M/D)
      .LOCKED_OUT(pll_locked)   // DCM LOCK status output
      );


///////////////////////////////////////////////
//   ASRC, CODEC I/F (BITCLOCK_FREQUENCY = 64fs)
// Left-Justified, MSB-First
///////////////////////////////////////////////

   // Serial to Parallel
   always @(posedge clk or posedge reset)
      begin
         if (reset)
            begin
               sreg0 <= 0;
               sreg1 <= 0;
               sreg2 <= 0;
               sreg3 <= 0;
            end

         else if (read_latch)
            begin
               // Left Channel
               if (AUDIO_BIT_DEPTH > bit_count)
                  begin
                     sreg0[(AUDIO_BIT_DEPTH -1) - bit_count] <= asrc_data_i;
                     sreg2[(AUDIO_BIT_DEPTH -1) - bit_count] <= codec_datar_i;
                  end

               // Right Channel
               else if ((bit_count > 31) & (bit_count < (AUDIO_BIT_DEPTH +32)))
                  begin
                     sreg1[(AUDIO_BIT_DEPTH -1) - bit_count +32] <= asrc_data_i;
                     sreg3[(AUDIO_BIT_DEPTH -1) - bit_count +32] <= codec_datar_i;
                  end
            end
      end

   // S/PDIF_valid
   assign spdif_valid = pll_locked & ~(spdif_error_i | spdif_nonaudio_i);

   // Mux
   always @(posedge clk or posedge reset)
      begin
         if (reset)
            begin
               if_l_o <= 0;
               if_r_o <= 0;
            end

         else if (en)
            begin
               // Input Select: CODEC
               if (if_sel)
                  begin
                     if_l_o <= sreg2;
                     if_r_o <= sreg3;
                  end

               // Input Select: ASRC
               else
                  begin
                     if (spdif_valid)
                        begin
                           if_l_o <= sreg0;
                           if_r_o <= sreg1;
                        end
                     else
                        begin
                           if_l_o <= 0;
                           if_r_o <= 0;
                        end
                  end
            end
      end


   // Parallel to Serial
   always @(posedge clk or posedge reset)
      begin
         if (reset)
            begin
               sreg4 <= 0;
               sreg5 <= 0;
            end

         // Send Buffer
         else if (en)
            begin
               sreg4 <= if_mon0_i;
               sreg5 <= if_mon1_i;
            end
      end

   
   always @(posedge clk or posedge reset)
      begin
         if (reset)
               codec_datas_o <= 0;

         else if (write_latch)
            begin
               // Left Channel
               if (AUDIO_BIT_DEPTH > bit_count)
                  codec_datas_o <= sreg4[(AUDIO_BIT_DEPTH -1) - bit_count];

               // Right Channel
               else if ((bit_count > 31) & (bit_count < (AUDIO_BIT_DEPTH +32)))
                  codec_datas_o <= sreg5[(AUDIO_BIT_DEPTH -1) - bit_count +32];

               else
                  codec_datas_o <= 0;
            end
      end

endmodule

0 件のコメント:

コメントを投稿