2012年5月27日日曜日

FPGA FMトランスミッタ: 入力部分の実機テスト

ISEの関連ドキュメントからISimチュートリアルを見つけ、お勉強してました。やはり自己流では限度がありますね。日本語で書かれてますし大変参考になりました。
主要ハードウェアは製作以来放置状態だったので初めての実機テストです。とりあえず光入力をコーデックでD/Aできることを確認します。


マスタクロック周波数の変更
設計変更に合わせ77.824MHzを出力できるようSi570の設定を変えました。
以前の記事を見ながら HSDIV=11, N1=6, RFREQ=02CF192732 として、制御用AVRに書き込みました。オシロで所定の信号が出ていることを確認します。


早速出たエラー
HDLで指定したポート名と実デバイスのピン番号を指定する関連付けるUCFファイルを作成しようとするも、ISEでConstraints Editorを起動 時に早速エラーが。
ERROR:Xst:2035 - Port <clk_i> has illegal connections. This port is connected to an input buffer and other components.

どうやらクロック入力ポートclk_iに、複数ユニットを並列接続していたためのようです。バッファ・プリミティブを追加したらエラーが出なくなりました。
この結果、Constraints Editorを無事起動できたものの設定内容が高度すぎて良くわかりません。
とりあえず最低限の情報のみをテキストエディタで追記作成しました。
UCFファイルの書式は、制約ガイドに記載があります。

作成したUCFファイルの内容は次の通りです。codec_ifのループバックテストをするためだけの最低限の内容です。上の3行以外は、ポート名・FPGAピン番号・信号規格を書いているだけです。


#Created by Constraints Editor (xc3s250e-vq100-4) - 2012/05/27
NET "clk_i" TNM_NET = clk_i;
TIMESPEC TS_clk_i = PERIOD "clk_i" 12.85 ns HIGH 50%;

NET "asrc_mclk_o"         LOC =  "P95" |IOSTANDARD = LVCMOS33 ;
NET "asrc_bck_o"         LOC =  "P92" |IOSTANDARD = LVCMOS33 ;
NET "asrc_lrck_o"         LOC =  "P91" |IOSTANDARD = LVCMOS33 ;
NET "asrc_data_i"         LOC =  "P90" |IOSTANDARD = LVCMOS33 ;
NET "spdif_nonaudio_i"   LOC =  "P85" |IOSTANDARD = LVCMOS33 ;
NET "spdif_error_i"      LOC =  "P79" |IOSTANDARD = LVCMOS33 ;
NET "codec_datar_i"      LOC =  "P11" |IOSTANDARD = LVCMOS33 ;
NET "codec_sck_o"         LOC =  "P3" |IOSTANDARD = LVCMOS33 ;
NET "codec_mck_o"         LOC =  "P9" |IOSTANDARD = LVCMOS33 ;
NET "codec_lrck_o"      LOC =  "P4" |IOSTANDARD = LVCMOS33 ;
NET "codec_datas_o"      LOC =  "P2" |IOSTANDARD = LVCMOS33 ;
NET "clk_i"               LOC =  "P38" |IOSTANDARD = LVCMOS33 ;
NET "resetsw_i"         LOC =  "P89" |IOSTANDARD = LVCMOS33 ;
NET "n_reset_o"         LOC =  "P86" |IOSTANDARD = LVCMOS33 ;
NET "n_sw_i[3]"         LOC =  "P57" |IOSTANDARD = LVCMOS33 ;
NET "n_sw_i[2]"         LOC =  "P58" |IOSTANDARD = LVCMOS33 ;
NET "n_sw_i[1]"         LOC =  "P53" |IOSTANDARD = LVCMOS33 ;
NET "n_sw_i[0]"         LOC =  "P54" |IOSTANDARD = LVCMOS33 ;



最上位HDLソース
code_if と colock_gen モジュールを結びつけるためのHDLです。

module top(

   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,
   clk_i, resetsw_i, n_reset_o, n_sw_i
   );

   output asrc_mclk_o;
   output asrc_bck_o;
   output asrc_lrck_o;
   input asrc_data_i;
   input spdif_nonaudio_i;
   input spdif_error_i;
   input codec_datar_i;
   output codec_sck_o;
   output codec_mck_o;
   output codec_lrck_o;
   output codec_datas_o;
   input clk_i;
   input resetsw_i;
   output n_reset_o;
   input [3:0] n_sw_i;

   wire [23:0] if_l_o;
   wire [23:0] if_r_o;
   wire en;
   wire clk;
   wire reset;
   wire if_sel;
   
   // ASRC/CODEC Interface
   codec_if codec_if(
      .asrc_mclk_o(asrc_mclk_o),
      .asrc_bck_o(asrc_bck_o),
      .asrc_lrck_o(asrc_lrck_o),
      .asrc_data_i(asrc_data_i),
      .spdif_nonaudio_i(spdif_nonaudio_i),
      .spdif_error_i(spdif_error_i),
      .codec_datar_i(codec_datar_i),
      .codec_sck_o(codec_sck_o),
      .codec_mck_o(codec_mck_o),
      .codec_lrck_o(codec_lrck_o),
      .codec_datas_o(codec_datas_o),
      .if_l_o(if_l_o),
      .if_r_o(if_r_o),
      .if_mon0_i(if_l_o),
      .if_mon1_i(if_r_o),
      .en(en),
      .clk(clk),
      .reset(reset),
      .if_sel(n_sw_i[3])
      );

   // Clock Generator
   clock_gen clock_gen(
      .clk_i(clk_i),
      .resetsw_i(resetsw_i),
      .clk(clk),
      .reset(reset),
      .n_reset_o(n_reset_o)
      );

endmodule



実機ループバックテスト
一応動いたものの、31.6kHz付近にナゾのスペクトルが生えています。アナログ系の問題と思っていますが電源のスイッチングノイズか?コンポジット信号を扱うには使えないです。モニタ用に設けた端子ですけど、ノイズ問題は難しい!
この試験中に、光入力で音が出ないトラブルが発生しました。原因は、DIR9001がサンプリング周波数192kHzに非対応であったためです。ちなみにオーディオ的な音質はかなり残念な感じでしたがエージングすれば少しは変わるんですかね。

光入力(1kHz)→CODECアナログ出力

光入力(無音)→CODECアナログ出力

CODECアナログ入力(1kHz)→CODECアナログ出力

CODECアナログ入力(無音)→CODECアナログ出力

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

2012年5月21日月曜日

FPGA FMトランスミッタ: リセット回路

1年ブランクがあると文法を忘れてしまいますね。

まずはいちばんシンプルなリセット回路を書いてみました。
外部のリセットボタンが解除されてから20ビット(約13.5ms)カウントしたのちに内部リセットを解除するものです。


module clock_gen(clk_i, resetsw_i, clk, reset, n_reset_o); input clk_i; input resetsw_i; output clk; output reset; output n_reset_o; // reset signal reg [19:0] reset_count; reg reset; always @(posedge clk or negedge resetsw_i) begin if (~resetsw_i) begin reset_count <= 20'b0; reset <= 1'b1; end else if (reset_count == 20'b1111_1111_1111_1111_1111) reset <= 1'b0; else reset_count = reset_count + 20'b1; end assign n_reset_o = ~reset;
// input clock buffer // IBUFG: Single-ended global clock input buffer // Spartan-3E // Xilinx HDL Libraries Guide, version 13.4 IBUFG #( .IBUF_DELAY_VALUE("0"), // Specify the amount of added input delay for // the buffer: "0"-"12" (Spartan-3E) .IOSTANDARD("DEFAULT") // Specify the input I/O standard ) IBUFG_inst ( .O(clk), // Clock buffer output .I(clk_i) // Clock buffer input (connect directly to top-level port) ); // End of IBUFG_inst instantiation endmodule


2012.05.27 クロック入力clk_iにバッファIBUFGを追加しました


次いで思い出しながらISimでシミュレーション。ちゃんと動いてくれると素直にウレシイです。

シミュレーション結果



2012年5月20日日曜日

FPGA FMトランスミッタ: クロック周波数

長らく放置状態のFMトランスミッタ。頭のリハビリがてら、白紙状態からコード作成に取り組んでいます。
今回は、トップレベルの定義に回路図を使うのをやめHDLを使うことにしました。後から回路を手直しするときに体裁を整えるのが面倒だったり思うように動いてくれないことがあったからです。

以前のトップレベル回路図


トップレベルの定義でHDLを使う場合、モジュール間の接続関係など全体の見通しが悪いので回路図のような図を作成して、それを見ながらHDLを作成することにしました。
(シンプルな構成なのでこうやって図を作ることができますが、大規模なプロジェクトではいったいどうやっているんでしょうか)


作成した全体構成図


この構成図を作成しながら、2点気付いたことがありました。
一つは、クロック周波数です。FPGA FMステレオチューナの真似をして73.728MHzとしていましたが、この周波数はステレオパイロット信号の19kHzと整数倍の関係にありません。
73.728MHzは、FM放送周波数帯にギリギリ重ならずS/PDIF信号の48kHzの整数倍の関係にあってとっても良いクロック周波数だと思いますが、やはり整数倍の関係であるほうが便利なので19kHz×8倍×512倍の77.824MHzに変更します。



もう一つは、RF生成用D/Aコンバータのクロック信号です。この信号はC/N劣化を避けるためFPGAをあえて通さなかったのでFPGA内部クロックとの位相差ができてしまいます。




2012年5月10日木曜日

ISE14.1、ルンバの外部制御端子ほか

自分メモです。

・Xilinx ISE 14.1がリリース

・Simさんのブログによると、
ルンバにSerial Command Interface(SCI)が付いているそうです。(仕様書

QTC-Japan.com
最終形の太いループのマグネチックアンテナ
ループの太さ100mmの極太ループアンテナ、これは凄い!存在感あります
上記Webページより、「マグネチックループアンテナの試作」へ進んで下さい