Alveo U50で100GbEを使ってみる

Pocket

XilinxのFPGAボード Alveo U50には,100GbEのポートが用意されています.内蔵されている100GbEコアを使うと,結構簡単にでデータのやりとりができます.

というわけで,とりあえず,Alveo U50の100GbEを使ってみた手順の紹介です.

これは,およそ9000バイトのデータが233サイクル@322MHz(おそらく)で受信されてる様子.

関連情報

環境

今回は,Alveo U50と,100GbEカードを搭載したパソコンを対向で接続して動作を確認してみました.100GbEのカードはいろいろありますが,Mellanox の ConnectX-5 を使用しました.Ubuntu 18.04でデフォルトのデバイスドライバに認識させた状態で使っています(のはず…)

ライセンスの準備

100GbEのIPコアを含むデザインを合成、配置配線してbitファイルを作るためには、コアのライセンスが必要です。ライセンスは無償で得ることができます(後で書く)

プロジェクトの作成

いろんな始め方があると思いますが,今回は100GbEのサンプルデザインをベースにすすめた方法を紹介します.こんな手順でやってみました.

  1. Alveo U50のボード定義ファイルをダウンロードする
  2. Alveo U50のプロジェクトをつくる
  3. Vivado IPIで100GbEのIPコアのインスタンスを作る
  4. 作成したインスタンスから Example Design を作る
  5. ILA,VIOを適当に接続して合成
  6. 対向のサーバでパケットを読み書き

Alveo U50のボード定義ファイルをダウンロードする

Vivado 2019.2.1で,Alveo U50向けのプロジェクトを作成します.このエントリを書いている 2020年3月11日時点では,Alveo U50向けのボード定義ファイルはありませんでした.Alveo U50のボード定義ファイルは,Vivadoを起動したあと,メニューバーからTools -> Download Latest Boards…を選択すると出てくるダイアログの”Download”ボタンをクリックすると,ローカルにダウンロードして使用できるようになります.

Alveo U50のプロジェクトを作る

ボード定義ファイルを使えば,プロジェクトは簡単に作成できますね.Alveo U50DDは,ES品時代のボードなので,間違えないようにしましょう.

Vivado IPIで100GbEのIPコアのインスタンスを作る

Create Block DesignでIPIを開いた後,BoardのQSFP Connectorをダブルクリックします.Connect Board Component ダイアログが開くので, Component mode の選択ボックスで,qsfp_4x_161を選択し,UltraScale+100G Ethernet Subsystemを選択してOKをクリックするとインスタンスを生成できます.

100GbEのExample Designを開く

生成したインスタンスの上で右クリックをしてコンテクストメニューを開き,”Open IP Example Design …”を選択するとExample Design用のプロジェクトを生成できます.プロジェクト生成先のディレクトリの入力を求められますので,適当に.少し待つとExample Designのプロジェクトが別のVivadoで起動します.

作成したExample Designのプロジェクトは,cmac_usplus_0(100GbEのIPコア)とパケットジェネレータ/モニタの簡単な回路です.

DUTをダブルクリックするとIPコアのパラメタを確認・変更することができます.ここでは,特に変更することはありません.Receiveに関するパラメタにMax Pkt Lenというのがあって9600になっていますね.デフォルトでジャンボフレームも扱えそうで楽しみです.

ILA,VIOを適当に接続して合成

さて,作成された,Example DesignですがAlveo U50向けではない上に,制御/ステータス用のI/Oを必要とします.Alveo U50の場合,適切なスイッチやLEDに接続するというわけにもいかないので,ILAやVIOに接続して合成することにします.

また,送信パケットの組み立ての感じを掴むために,サンプルのパケットジェネレータではなくHello World的な簡単なパケット生成ロジックに置き換えてみました.

というわけで,トップモジュールのVerilogコードはこんな感じです.cmac_usplus_0_exdes.v トップモジュールのI/Oをばっさりコメントアウトしていますので,関連して,制約ファイル中のI/Oポートの定義も削除あるいはコメントアウトします.必要なILAやVIOは適宜IPカタログから選択,生成します.

準備ができたら,いつものように Generate Bitstreamで合成です.

対向サーバで読み書きしてみる

実験には,生イーサパケットの読み書きが必要です.たとえば,Pythonで,こんな感じで送信することができます.

import socket

if __name__ == '__main__':
    ETH_P_ALL = 3
    dev = sys.argv[1]
    message = "Hello Alveo U50"
    data_bytes = int(sys.argv[2])//len(message) if len(sys.argv) > 2 else 1

    dest_addr  = b'\x00\x01\x02\x03\x04\x05' # destination address
    src_addr   = b'\x98\x03\x9b\x1d\x63\x89' # source address
    frame_type = b'\x34\x34' # frame type
    data = (message*data_bytes).encode() # payload

    s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
    s.bind((dev, 0))
    s.sendall(dest_addr + src_addr + frame_type + data)
    s.close()</code></pre></div>

ILAで受信した様子は次の通りです.

Pythonプログラム通りの値がFPGAに届いていることが確認できます.ifconfigでインターフェースのMTUを大きめにせっとして,9000Bytes程度送ってみたときの様子は次の通りです.enが上がり下がりしつつ,(おそらく)322MHzのクロックに対して233サイクルかかっているので,(IPコア内部でのバッファリングはあるでしょうが,)およそ12.4GBps程度の速度,100Gps程度でていることがわかります.

FPGAによる送信もためしてみます.雑ですが,とりあえずは,定数で埋め込み.

   reg user_kick_d = 1'b0;
   always @(posedge txusrclk2) begin
      user_kick_d <= user_kick;
      if(user_kick_d == 1'b0 && user_kick == 1'b1) begin

    	  my_tx_datain0[127:80] <= 48'h98039b1d6389; // Destination MAC
	      my_tx_datain0[79:32]  <= 48'h000102030405; // Source MAC
	      my_tx_datain0[31:16]  <= 16'h3434; // Ether header
	      my_tx_datain0[15:0] <= 16'h4865; // He
	      my_tx_datain1 <= 128'h6c6c6f20776f726c6400000000000000; // llo world
	      my_tx_datain2 <= 128'h00000000000000000000000000000000;
	      my_tx_datain3 <= 128'h00000000000000000000000000000000;

    	  my_tx_enain0 <= 1'b1;
	      my_tx_enain1 <= 1'b1;
	      my_tx_enain2 <= 1'b1;
	      my_tx_enain3 <= 1'b1;

	      my_tx_sopin0 <= 1'b1;
	      my_tx_sopin1 <= 1'b0;
	      my_tx_sopin2 <= 1'b0;
	      my_tx_sopin3 <= 1'b0;

	      my_tx_eopin0 <= 1'b0;
	      my_tx_eopin1 <= 1'b0;
	      my_tx_eopin2 <= 1'b0;
	      my_tx_eopin3 <= 1'b1;

	      my_tx_errin0 <= 1'b0;
	      my_tx_errin1 <= 1'b0;
	      my_tx_errin2 <= 1'b0;
	      my_tx_errin3 <= 1'b0;

    	  my_tx_mtyin0 <= 4'h0;
	      my_tx_mtyin1 <= 4'h0;
	      my_tx_mtyin2 <= 4'h0;
	      my_tx_mtyin3 <= 4'h0;
      end else begin
	      my_tx_enain0 <= 1'b0;
	      my_tx_enain1 <= 1'b0;
	      my_tx_enain2 <= 1'b0;
	      my_tx_enain3 <= 1'b0;

	      my_tx_sopin0 <= 1'b0;
	      my_tx_sopin1 <= 1'b0;
	      my_tx_sopin2 <= 1'b0;
	      my_tx_sopin3 <= 1'b0;

	      my_tx_eopin0 <= 1'b0;
	      my_tx_eopin1 <= 1'b0;
	      my_tx_eopin2 <= 1'b0;
	      my_tx_eopin3 <= 1'b0;

	      my_tx_errin0 <= 1'b0;
	      my_tx_errin1 <= 1'b0;
	      my_tx_errin2 <= 1'b0;
	      my_tx_errin3 <= 1'b0;

	      my_tx_mtyin0 <= 4'h0;
	      my_tx_mtyin1 <= 4'h0;
	      my_tx_mtyin2 <= 4'h0;
	      my_tx_mtyin3 <= 4'h0;
      end
   end

受信側のプログラムはこんな感じ

import sys
import socket

if __name__ == '__main__':
    ETH_P_ALL = 3
    dev = sys.argv[1]
    s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
    s.bind((dev, 0))
    data = s.recv(9000)
    print(data)
    s.close()

VIOでロジックをキックして送信してみます.

Pythonスクリプトで無事にパケットを受信することができました.

次は

というわけで,Alveo U50の100GbEが簡単に使えることは確認できました.次はHBMとつないで,Ethernet経由で読み書きしてみる手順を紹介しようと思っています.

また,Vitisのプラットフォームデザインとして整理してVitisで使えるようにしたいなあ,というのが,少し先の目標です.