連載2回目となりました。前回は FPGA 設計ツールはさわらずに、高速シリアルトランシーバに関する基本的なことがらを紹介しましたが、「習うより慣れろ」とか「百聞は一見にしかず」と申しますので、今回は難しい設計は一切やらずに、Xilinx の Aurora コアをぽちぽちっと生成して、シミュレーションで動かしてみることにしましょう。シミュレーションなので、トランシーバのポートを持った FPGA ボードがなくてもできますし、ボードをお持ちの方はちょっと手を加えれば実機で動かすこともそれほど難しくありません。
まずは AXI4-Stream
前回の連載では「Aurora 64B/66B を使うと FPGA どうしを接続できる」という話をしました。ところで、もうちょっとまじめに「Aurora 64B/66B コアって何?」と聞かれたら、「AXI4-Stream 形式で FPGA の高速シリアルトランシーバを使うための IP コアです」と答えることにしています。では、AXI4-Stream とはなんでしょうか。
以前の ACRi ブログの記事、たとえば「IP の世界からこんにちは」でも、IP コアの間を接続するための AXI とよばれる規格について解説しています。この記事に出てくるのはアドレスのついたタイプの AXI バスで、主にプロセッサと周辺回路を接続するのに用いられます。アドレスがついていると、プロセッサから扱うのには便利ですが、IP コアどうしを直接つなぐにはちょっと大げさです。
そこで、アドレス線を省略して、単に一方向へ流れるデータを扱うことだけを考えたのが AXI4-Stream (あるいは単に AXI-Stream) と呼ばれる方式です。Intel の FPGA 設計ツールでは Avalon-ST という方式が提供されていますが、これもほぼ同様です。AXI4-Stream の最低限の信号は共通のクロックに加えて、送信側から受信側に向かう tdata, tvalid の 2 本だけです。
上のように、データが有効なときにだけ tvalid が 1 になるという、きわめて簡単なやりかたです。AXI4-Stream ではこれに加えてさまざまなオプションの信号線があり、そのうち比較的よく使われるものは、受信側が受け入れ可能であることを示す tready と、送信データの末尾を示す tlast です。
tready を追加すればデータの取りこぼしを防ぐことができます。tlast を追加すればデータをブロックに区切ることができます。これらオプションの信号線は IP コアの仕様やその設定によってあったりなかったりします。
Aurora 64B/66B コアの AXI4-Stream インタフェイス
Aurora 64B/66B コアでは、データ転送用のメインの AXI4-Stream インタフェイスのほかに、設定次第でフロー制御用のインタフェイスを設けることができますが、ここではデータのインタフェイスについてだけご紹介しておきます。以下は、デフォルトの設定で Aurora 64B/66B コアを生成したときの、データ用 AXI4-Stream ポートの様子です。
送信側 (USER_DATA_S_AXIS_TX) には tdata, tvalid, tready が3つとも揃っていますが、受信側 (USER_DATA_M_AXIS_RX) には tready がありません。つまり、Aurora 64B/66Bコアの受信側からは tready 信号を使って送信を抑制することができない、ということです。このような操作をバックプレッシャ、あるいはフロー制御と呼び、Aurora 64B/66Bコアではこれを実現するための専用のフロー制御用ポート (UFC, NFC) を設けています。一見不便なようにみえますが、アプリケーションによってどのようなフロー制御が必要かは異なるので、これは適切な切り分けなのではないかと思っています。また、自動的にフロー制御のメッセージを送信することはそれほど難しくありません。フロー制御の詳しいことについては、また機会があればご紹介したいと思います。
また、tkeep はさきほどの説明にはなかった信号ですが、これは最終ワード (tlast=1) が64ビットに満たない場合に、有効なバイトを示すための信号で、少なくともいまのところは気にしなくて構いません。また tlast が不要な場合にはポートを生成しないように設定することが可能で、この場合にはあわせて tkeep もなくなります。
Aurora 64B/66B コアを作ってみる
さて、それでは実際に Vivado を起動して Aurora 64B/66B コアを作ってみましょう。今回はシミュレーションだけを予定しているので、特定のボードは想定せず、
- ラインレート: 10.3125 Gbps (データレート 10.0 Gbps)
- FPGA: Kintex Ultrascale 040 (fbva676 パッケージ)
- トランシーバのリファレンスクロック: 156.25MHz
- ユーザロジックから 100MHz のクロックを供給
ということにします。Kintex Ultrascale でなくとも、Ultrascale または Ultrascale+ のボードをお持ちでしたら、それにあわせた設定でもだいたい同じようにして試すこともできます。ただし、ユーザロジックからのクロックの周波数の範囲にはある程度の制限があります。必要ならば Clocking wizard などを使って周波数を調整してください。
コアを作る準備
Vivado を起動してプロジェクトを作成します。とりあえず、デバイスは xcku040-fbva676-1-c としておき、ソースファイルなどは特にないまま作成完了します。つづいて、IP Catalog で “Aurora 64B66B” を検索してダブルクリックします。
これで、以下のような設定画面が開きます。コア生成の準備完了です。
コアの設定パラメータ
それでは、さきほどの設定画面をざっと見ていきましょう。Component name は生成する IP コアインスタンスの名前で、HDL のモジュール名に相当します。
その下には Core Options と Shared Logic のふたつのタブが見えます。このうち、Core Options が主要な設定を行う部分です。このタブにある主要な設定項目を順番に見ていきましょう。
- Physical Layer
- Line rate (Gbps): ラインレート (通信速度) です。64B/66B コーディングを行うのでデータレートの 66/64 倍の値になります。デフォルトのままの 10.3125 にしておきましょう。これで、ちょうどデータが 10.0Gbpsになります。
- Lanes: 使用するレーン数、つまりトランシーバの数です。1レーンの場合には1つのトランシーバを使い、64bit 幅のデータをやりとりします。2レーンにすれば 2 倍の 128bit になって、通信速度も 2 倍になります。このように複数のレーンを束にして通信速度を向上する手法をチャネルボンディングと呼びます。
- GT Refclk (MHz): トランシーバの専用ピンに入力される参照クロックです。トランシーバの PLL によって使用され、最終的には PMA を駆動するクロックになりますので、ボード上に低ジッタの専用クロック源が用意されます。デフォルトのまま、156.25MHz にしておきます。
- INIT clk (MHz): こちらはFPGAのユーザロジック側から供給されるクロックで、主にリセット後の初期化に使われます。今回はこれを 100MHz に設定します。
- Column used, Starting GT Quad, Lane, GT Refclk selection: 使用するトランシーバの位置やクロックバッファを選択します。トランシーバの位置やクロックバッファはピン番号と 1:1 の関係なので正しく選ばないといけない… ような気がしますが、実際のところはプロジェクトの制約ファイルでピン位置を書いてやればそちらが適用されるので、デフォルトのままにしておいても特に問題ありません。
- Link Layer
- Dataflow mode: これは通常 Duplex (双方向) を使います。Simplex (片方向) もできますが、ちょっとややこしくなります。
- Interface: AXI4-Stream の tlast 信号を使う場合は Framing で、tlast 信号が不要なら Streamingを選択します。今回はこれを Streaming にしておきます。
- Flow control: 今回の記事の最初に説明したように、Aurora 64B/66B では tready によるバックプレッシャは使えないので、フロー制御には別の方法が必要です。これは次回以降に紹介することにして、今回は None のままにしておきます。
- Debug and Control
- DRP mode: トランシーバの設定を FPGA 全体の再構成なしに変更したい場合に使用します。今回は特に使いませんので、AXI4 Lite のままにしておきます。
以上、デフォルトの値から設定変更の必要なところをまとめると、次の2点だけです。
- INIT clk: 100
- Interface: Streaming
さて、もうひとつのタブにある、Shared Logic のほうはいったい何でしょうか。前回の記事で、送受信のためのクロックを生成する PLL をいくつかのシリアルトランシーバで共有していることを説明しました。Shared Logic と呼ばれるのは、この PLL とそれに付随するリセット制御などの回路のことで、隣接する複数のトランシーバを使う場合はこれを共有することができます。
PLL は数が限られてるので、複数の Aurora コアを使用する場合には適切な使い分けが必要です。Shared Logic 込み (“Include Shared Logic in core” の設定) と Shared Logic なし (“Include Shared Logic in example design” の設定) を簡単に図解したのが上の図です。Shared Logic 込みのコアが上、なしのコアが下で、Shared Logic 込みのコアには各種のクロック信号の出力ポートが、Shared Logic なしのコアにはそれに対応する入力ポートがそれぞれ存在します。基本的には対応するものどうしを接続すれば OK です。なお、Shared Logic を共有する場合、PLL の出力周波数に直接関係する、ラインレートなどの設定が一致しないとうまく動作しないので、そのあたりにも注意が必要です。
さて、Shared Logic 込み (Include Shared Logic in core) の設定にしたら、”OK”を押して Aurora 64B/66B コアを生成します。
コアの入出力ポート
生成された Aurora 64B/66B コア、びっくりするほどたくさんのポートがあります。ただ、今回の前提としては、
- Shared Logic なしのコアと一緒に使う (クロックなどを接続する必要あり) ことはしない
- トランシーバの DRP (動作中に設定を変更する機能) は利用しない
ということにしていますし、ちょっと試すだけなら重大なエラーの発生を通知する信号なども無視することができます。そういうわけで、動作に最低限必要な、主要な信号をまとめてみましょう。まずは制御関係の信号からです。
信号 | 役割 | |
シリアル信号 | rxp, rxn, txp, txn | トランシーバの送受信シリアル信号 |
クロック入力 | init_clk | コアの初期化と DRP 用クロック |
gt_refclk1_[p,n] | PLL の参照クロック | |
リセット | reset_pb, pma_init | ロジック部分とPMAのリセット |
ステータス | lane_up, channel_up | |
その他動作モード制御 | loopback | ループバック設定、0 で通常動作 |
gt_rxcdrovrden_in, power_down | 基本的に 0 |
コア生成のときにも出てきたように、クロック入力の init_clk は Aurora 64B/66B コアの制御回路で使われるクロックで、今回は 100MHz です。gt_refclk1 のほうはトランシーバの PLL が使用して、トランシーバを駆動するための参照クロックで、今回は 156.25MHz です。前者は FPGA 上で生成することもできますが、後者はボード上で生成したものをそのまま専用のピンから入力する点が大きな違いです。
リセット信号は2本ありますが、これは Aurora 64B/66B コアの実装上の都合と思われます。どう動かすかはコアのマニュアルに書かれていますし、次のセクションに出てくるテストベンチに含まれるモジュールを使えば実機でもちゃんと動かせます。
接続先 (リンクパートナー) の Aurora 64B/66B コアとの接続が完了すると lane_up や channel_up といった信号が high になります。lane_up はトランシーバごとの接続完了、channel_up は特に、チャネルボンディングをしている場合、全体の接続完了を示します。ただ、実際に通信を行う上では、AXI4-Stream の tready 信号があるのでそちらを参照すれば充分で、どちらかというとデバッグのための信号といえます。
もうひとつ重要なのは loopback です。0 にしておけば通常動作になりますが、これを 1 にすれば PCS で、2 にすれば PMA で、それぞれトランシーバ内部で通信を折り返すループバック動作をさせることができます。ボードが 1 枚しかなくて、ケーブルを接続せずにテストしたい場合に活用できます。つづいて、ユーザロジック側からの通信に使う信号もみていきましょう。
信号 | 役割 | |
クロック・リセット | user_clk_out, sys_reset_out | データ入出力インタフェイスのクロック出力・リセット出力 |
送受信データ | s_axi_tx_t* | 送信データの入力 |
m_axi_rx_t* | 受信データの出力 | |
DRP | 上記以外の s_axi_* | DRP インタフェイス |
Aurora 64B/66B とユーザロジックのインタフェイスは、この記事の最初に紹介した AXI4-Stream 形式になっています。それらのインタフェイスは Aurora 64B/66B コアからのクロックで駆動されており、これが user_clk_out です。併せて、 sys_reset_out も出力されているので、Aurora 64B/66B コアとインタフェイスする部分についてはこれでリセットするのがよいでしょう。
送受信のインタフェイスは s_axi_tx_ で始まる信号が送信、m_axi_rx_ で始まる信号が受信のインタフェイスです。
もうひとつ、トランシーバの設定を操作するための DRP インタフェイスが AXI4-Lite で提供されており、これは s_axi_tx_ 以外の s_axi_ ではじまる信号です。今回はこれは使用しないので特に説明しませんが、このインタフェイスのクロックは init_clk である点に注意が必要です。
ちょっとだけ寄り道ですが、user_clk_out のクロックは (おそらく) Tx PMA の使用する送信用クロックから生成されています。受信側の Rx PMA は受信データからクロックデータリカバリによって復元したクロックで動きますが、これは Tx PMA のクロックとは同期していないので、Aurora 64B/66B コアでは、受信データを user_clk_out に乗せるために非同期 FIFO を使用していると筆者は推測しています。実際に、さきほど生成したコアの合成結果をみると、BRAM が 1.0 ブロック使用されています。
これでコアの入出力の信号がわかりました。次はテストベンチを書いてシミュレーションしてみましょう。
シミュレーションしてみる
いよいよシミュレーションで Aurora 64B/66B コアを動かしてみたいと思います。シミュレーションして動かすためにはコアのリセット信号を生成するモジュールと、送信用のテストデータを作るモジュールがあるとよさそうです。これらは論理合成+配置配線して実機テストを行う場合にも必要ですし、テストベンチにべた書きするのではなく、べつのモジュールにしておきましょう。
検証のための補助モジュール
まずは、リセット信号の生成モジュールです。
module ku_aurora_boot
( input wire CLK100,
input wire DCM_LOCKED,
output reg PMA_INIT, RESET_PB
);
reg [7:0] PMA_INIT_CNT = 0;
initial PMA_INIT <= 1;
initial RESET_PB <= 1;
always @ (posedge CLK100) begin
if (~DCM_LOCKED) PMA_INIT_CNT <= 0;
else PMA_INIT_CNT <= PMA_INIT_CNT + ((~&PMA_INIT_CNT) ? 1 : 0);
case (PMA_INIT_CNT)
100: PMA_INIT <= 0;
200: RESET_PB <= 0;
endcase
end
endmodule
PMA_INIT と RESET_PB はそれぞれ所定の時間アサートして、所定の間隔をあけて解除しなければならないので、こんなふうにしています。合成する RTL で initial 文を書いたことないよ、という方も多いと思いますが、Xilinx のツールでは initial 文でレジスタを初期化すると、コンフィギュレーション直後の初期値になりますので、このようにして書くこともできます。また、100MHz のクロックは Clocking Wizard などを使って FPGA 内で生成することも多いので、リセット信号のような扱いにしています。続いて、テストデータの生成モジュールです。
module tx_gen
( input wire CLK, RST,
input wire TREADY,
output reg TVALID,
output reg [63:0] TDATA );
parameter PayloadLen=100;
always @ (posedge CLK) begin
if (RST) begin
TDATA <=0; TVALID <= 0;
end else begin
if (TREADY) begin
TVALID <= (TDATA==0) ? 1 : (TDATA==PayloadLen) ? 0 : TVALID;
if (TDATA!=PayloadLen) TDATA <= TDATA+1;
end
end
end
endmodule
こちらはリセット後、リンクが確立されて TREADY が high になると一定長 (ここでは 100 ワード、parameter PayloadLen で設定) 送るようにしています。シミュレーションしてみるとわかりますが、動作中にも TREADY が下がることがあるので、それを考慮しておくことは大事です。
テストベンチを書いてシミュレーション
ではいよいよテストベンチを書きましょう。Aurora 64B/66B コアから出ている、トランシーバのシリアル差動ペアは送信と受信を直結して、送信したデータがそのまま受信されるループバック接続の状態にしています。また、今回接続しないポートはテストベンチでの記述を省略しています。
`timescale 1ns/1ps
module tb();
wire LB_P, LB_N;
reg CLK100, CLK156;
wire RESET_PB, PMA_INIT, CH_UP, LANE_UP;
wire TX_TVALID, TX_TREADY, RX_TVALID;
wire [63:0] TX_TDATA, RX_TDATA;
wire CLK, RST;
aurora_64b66b_0 uut
( .rxp(LB_P), .rxn(LB_N), // I [0:0]
.txp(LB_P), .txn(LB_N), // O [0:0]
.init_clk ( CLK100), // I
.gt_refclk1_p ( CLK156), // I
.gt_refclk1_n (~CLK156), // I
.reset_pb (RESET_PB), // I
.pma_init (PMA_INIT), // I
.channel_up (CH_UP), // O
.lane_up (LANE_UP), // O [0:0]
.s_axi_tx_tdata (TX_TDATA), // I [0:63]
.s_axi_tx_tvalid (TX_TVALID), // I
.s_axi_tx_tready (TX_TREADY), // O
.m_axi_rx_tdata (RX_TDATA), // O [0:63]
.m_axi_rx_tvalid (RX_TVALID), // O
.user_clk_out (CLK), // O
.sys_reset_out (RST), // O
// Normal operation + DRP AXI Lite disabled
.loopback (3'b000), // I [2:0]
.gt_rxcdrovrden_in (0), // I
.power_down (0), // I
.s_axi_bready (0), // I
.s_axi_awvalid (0), // I
.s_axi_wvalid (0), // I
.s_axi_arvalid (0), // I
.s_axi_rready (0) // I
);
initial begin
CLK100 <= 1; CLK156 <= 1; end
always # (5) CLK100 <= ~CLK100;
always # (3.2) CLK156 <= ~CLK156;
ku_aurora_boot boot ( .CLK100(CLK100), .DCM_LOCKED(1),
.PMA_INIT(PMA_INIT), .RESET_PB(RESET_PB) );
tx_gen tg ( .CLK(CLK), .RST(RST),
.TREADY(TX_TREADY), .TVALID(TX_TVALID), .TDATA(TX_TDATA) );
endmodule // tb
Flow navigator で Simulation → Run Simulation → Run Behavioral Simulation します。デフォルトでは 1us までシミュレーションされますが、シミュレーションで Aurora コアのリンクが確立するまでは 20us くらい必要なので、ツールバーで 20us の追加のシミュレーションを実行しましょう。数字をいれて ▶(T) をクリックします。
これでようやっとシミュレーション波形がでました。コアをリセットする PMA_INIT と RESET_PB はそれぞれ 1us, 2us でリセット解除されます。シリアルトランシーバの信号である LB_P と LB_N は 4.5us あたりから出力され始めますが、リンクが確立されて CH_UP が high になるのは 19us あたりと、かなりの時間がかかっていることがわかります。
この間に何が起きているかは次回に譲るとして、リンク確立後のデータ通信についてみてみましょう。上の図からわかるように、CH_UP が high になってしばらくすると、送信側の AXI4-Stream の TX_TREADY が high になって、送信が可能になることがわかります。データ転送は下のように行われます。TX_TDATA と RX_TDATA の値は出ていませんが、Tx で送信した値がそのまま Rx のほうに出てきます。ところで、TX_TREADY がときどき Low になるのは、なぜでしょう。
そこで、トランシーバの基準クロックになっている 156.25MHz (6.4ns 周期) の CLK156 と、Aurora 64B/66B コアから出ているユーザロジック用の user_clk_out (CLK) を比べてみます。シミュレータの波形で確認してみると、CLK の周期は 6.204ns です。
6.204 という数字、実は 6.4ns の 64/66 倍です。Aurora 64B/66B では 64bit のデータを 66bit に符号化して送受信するので、ラインレートはデータレートの 66/64 倍になる、ということは以前に説明しました。このとき、データの送受信インタフェイスをラインレートの 1/66 の周波数にするか 1/64 の周波数にするかは、トランシーバを扱うインタフェイスの設計者に委ねられているわけですが、Aurora 64B/66B ではこれを 1/66 にしている、というわけです。この場合、66クロックのうち2クロックはデータを送信できないサイクルが生じるので、s_axi_tx_tready (TX_TREADY) は概ね32クロックに1クロック程度 low になる、というのも波形からわかります。
おわりに
今回は、中身を気にしたりすることなく、ふつうに Aurora 64B/66B の IP コアのインスタンスを生成してシミュレーションによって動作を確認しました。また、コアから出力されるクロックの周期だとか、送信の s_axi_tx_tready 信号をみるだけでも内部のことがいろいろ想像できる、ということも感じていただけたと思います。
今回、Aurora 64B/66B コアの中身はブラックボックスとしてみてきましたが、次回以降ではトランシーバだけのコアをみて動作を理解したり、Aurora 64B/66B プロトコルについても紹介していきたいと思います。
琉球大学 長名保範