Article

2020年5月12日

※2020/07/13更新 近日公開としていた10G Ethernet MACのソースコードを公開&追記しました。

概要

Xilinx Alveo U50 (ES3)

Xilinxのデータセンター向けFPGAボードである Alveo U50には、SFP-DDスロット(ES版)もしくはQSFPスロット(Production版)が搭載されており、Ethernetモジュールを接続することにより、他のデバイスとEthernetで通信することができます。

これらのSFP系スロットを使って10G Ethernetの通信を行うには、10G Ethernetの処理を行う論理回路が必要となります。

10G Ethernetの通信処理を行うIPとして、10G/25G Ethernet Subsystem がVivadoに同梱されていますが、合成して使用するには別途ライセンス購入が必要となります。

実際には、10G/25G Ethernet Subsystemは、PCS/PMAと呼ばれる部分と、MACと呼ばれる部分に分かれており、PCS/PMA部分は無償、MAC部分が有償となっています。

調べてみたところ、MACの処理自体は基本的な処理のみであれば、そんなに複雑ではないことがわかりましたので、Alveo U50で動くものを実装して、PCに接続した10G Ethernet NICとの通信ができるかどうか試してみました。

FPGA内部のブロック

通信を確認する際に用いた、FPGA内部とその周辺のブロック図を図1に示します。

図1.FPGA内部とその周辺のブロック

前述の通り、Xilinxの10G Ethernet Subsystemは、PCS/PMAと呼ばれる64b66b変換を行う部分、および66:1のSERDES部分は無償で使用できますので、PCS/PMAの部分のみ使用しています。

SFP-DDスロットに挿入された10G Ethernet CableとPMAは、GTHトランシーバのレーン1つと接続されています。

今回作成した10G MAC部分がxgmac IP (xgmac)です。10G Ethernet SubsystemのPCSとは、XGMIIというインターフェース規格に基づいてEthernetフレームの内容をやりとりします。また、10G Ethernetを使うユーザー・ロジックとは、イーサネット経由で送受信するヘッダ及びペイロードをAXI4 Stream経由でやりとりします。

PCSとMACのインターフェース XGMII

10G EthernetにおけるPCSとMACの間のインターフェースはXGMII (10G Media Independent Interface)として規格化されており、Xilinxの10G Ethernet Subsystem内のPCSとMACのインターフェースもXGMIIとなっています。

XGMII自体は、データ32bit+制御信号4bit@312.5[MHz]で通信する規格ですが、XilinxのPCSでは、バス幅を倍(データ64bit+制御信号8bit)にするかわりに周波数を半分(156.25[MHz])にした64bit PCSとなっており、動作周波数的に扱いやすくしています。

XGMIIでは、データをオクテット(8bit)単位の塊とそれに対応する制御ビットのペアとして扱います。例えば、データの下位8bit([7:0])と制御信号の0bit目がペアとなります。対応する制御信号のビットが0のデータは、そのままデータとして扱います。対応する制御信号のビットが1のデータは、XGMIIの制御信号として扱います。

XGMIIの制御信号はいくつかありますが、最低限扱う必要があるものは次の通りです。

データの値名称意味
8’h07IDLE何もない
8’hfbSTARTフレーム開始
8’hfdTERMINATEフレーム正常終了
8’hfeERRORフレーム異常
XGMIIの制御信号

XGMIIではEthernetのフレームをSTARTからTERMINATEで囲まれた区間で表します。 また、START は必ずD[7:0]、つまり0番目のオクテットに現れることが規定されています。ただし、これは32bit PCSの場合で、64bit PCSの場合は0番目及び4番目のオクテットにSTARTが現れることに注意が必要です。

STARTの出現位置

Ethernetフレーム送受信中に何かしら異常が発生した場合は、TERMINATEではなく ERROR でフレームを終了します。

正常フレームと異常フレーム

以上より、XGMII経由でEthernetフレームを受信するには、Startを検出して、TerminateかErrorが来るまで間のデータを読み出しつづければよいことがわかります。ただし、前述の通りStartの位置が0オクテット目か4オクテット目か変化するので、どちらなのか検出してその後のデータのずらし方を変えます。

逆にXGMII経由でデータを送信するには、Ethernetフレームの前にStart、Ethernetフレームの後にTerminateもしくはErrorと、パディングとしてIdleを付加すればよいことがわかります。

以上より、XGMIIでEthernetフレームをやり取りする処理は意外と単純であることがわかります。

Ethernetフレームのヘッダとペイロード以外の処理

Xilinxの10G/25G Ethernet Subsystemが提供するMAC IPは、入出力としてEthernetフレームのうちヘッダとペイロードの部分のみをユーザーロジックとの間でやりとりします。よって、Ethernetフレームのヘッダとペイロードを除く、プリアンブル(Preamble)、SOF、FCSはMAC IP側で処理する必要があります。

EthernetフレームのうちMAC IPが処理する箇所

プリアンブルは値が8'h55 のデータが5~7オクテット続いたものです。通信経路上である程度欠損することが許されているため、5~7オクテットの長さになる可能性があります。

SFD(Start Frame Delimiter)は、フレームの開始を表すマーカーで、その値は8'hd5 となります。

SFD以降はEthernetヘッダ (送信元MACアドレス+送信先MACアドレス+Ethernetフレームタイプ) が14オクテット、ペイロードがMTU(Max Transfer Unit, 大抵1500)オクテットが続きます。

最後にFCS(Frame Check Sequence)と呼ばれるCRC32によるヘッダとペイロードのチェックサム が付加されます。

よって、Ethernetフレームの受信を行うには以下の手順が必要です。

  1. SFD(8’hd5)を検出する
  2. SFDの次のオクテットから、フレーム終端-4オクテットまでのCRC32を計算する
  3. フレーム終端4オクテットにあるFCSと、(2)で計算したCRC32の値を比較する。比較して一致すれば正常フレーム、一致しなければFCSエラーとする。

逆に、Ethernetフレームの送信を行うには、以下の手順が必要です。

  1. プリアンブル(8’h55)を7オクテット分送信する。
  2. SFD(8’hd5)を送信する。
  3. ヘッダとペイロードを送信する。
  4. ヘッダとペイロードのCRC32を送信する。

以上より、Ethernetフレームに関する処理もそんなに複雑なことは行わないでよいことがわかります。

10G Ethernet MACに必要な処理

ここまでで、10G Ethernet MACに必要な処理が出そろいました。そこで、以下の通り10G Ethernet MACの受信処理と送信処理の処理ブロックをそれぞれ設計しました。

10G Ethernet MACの受信ブロック

10G Ethernet MACの受信処理は以下のブロックから成ります。

  • XGMIIのSTART信号とTERMINATE/ERRORで囲まれた区間を検出して後続ブロックに流す、XGMII to AXI Streamブロック。
  • プリアンブルとSFDを検出して除去し、残りの部分を後続ブロックに流す Align Frame ブロック。
  • 末尾のFCSを検出して抽出し、残りのヘッダ+ペイロード部分とFCS分離して後続ブロックに流す Remove CRC ブロック。
  • ヘッダ+ペイロード部分のCRC32を計算する CRC32 ブロック。
  • Remove CRCブロックで抽出したFCSと、CRC32ブロックで再計算したCRC32が一致しているかどうかを比較し、後続(外部ユーザーロジック)にエラー通知するかどうかを判定する Compare FCS ブロック
10G Ethernet MACの送信ブロック

10G Ethernet MACの送信処理は以下のブロックから成ります。

  • ユーザーロジックから入力されたヘッダ+ペイロードに対するFCSを計算して末尾に追加する Append CRC ブロック。
  • XGMIIのSTART, EthernetフレームのプリアンブルとSFDを先頭に付加し、前段ブロックのエラー情報をもとにXGMIIのTERMINATEもしくはERRORを追加する AXI Stream to XGMII ブロック。

10G Ethernet MAC IP の作成

前述の送受信処理を行う各ブロックをRTLで記述し、送受信処理を1つの 10G Ethernet MAC という名称のIPとしてまとめました。
XGMIIおよびAXI Streamによる入出力インターフェースは、Xilinxの10G/25G Ethernet Subsystemと互換性があるようにしてあります。

10G Ethernet MAC (xg_mac) IP のインターフェース

Xilinxの10G/25G Ethernet Subsystem (PCS/PMAのみ)との接続は次の通りです。

10G/25G Ethernet Subsystemと10G Ethernet MAC を含むHierarchyの接続

hier_mac_0, hier_mac_1内部に10G Ethernet MACのインスタンスがそれぞれ1つづつ含まれており、前述の10G Ethernet MACのインターフェースと同名のポートがHierarchyのポートとして外に出ています。ポートの接続関係は次の通りです。

10G/25G Ethernet Subsystemのポート10G Ethernet MACのポート信号の内容
mii_rx_{0,1}rx_xgmii受信ブロックへのXGMII信号
mii_tx_{0,1}tx_xgmii送信ブロックからのXGMII信号
tx_mii_clk_{0,1}rx_clock, tx_clock10G/25G Ethernet Subsystemの送受信クロック
user_rx_reset_{0,1}rx_reset受信ブロックのリセット信号
user_tx_reset_{0,1}tx_reset送信ブロックのリセット信号
10G/25G Ethernet Subsystemのポートと10G Ethernet MACのポートの接続関係

10G/25G Ethernet SubsystemはPCS only (64bit)の設定としています。

10G/25G Ethernet Subsystemの設定

10G Ethernet MACの動作確認

10G Ethernet MAC IPをAlveo U50上で操作確認するため、まずは単純に受信したEthernetフレームを送信元にそのまま返すデザインを作成しました。

ループバック用デザインでの10G Ethernet MACのrx_maxisとtx_saxisの接続

ループバックのデザインでは、単純に10G Ethernet MACのrx_maxistx_saxis をAXI Stream Data FIFO経由で接続しています。これで受信したEthernetフレームの内容がそのまま送信されます。

上記デザインを合成します。

また、Alveo U50 (ES3)のSFP-DDスロットに、10G EthernetのSFP+ケーブルを挿入し、ケーブルの反対側を同じPCに接続した 10G Ethernet NICのSFP+スロットに接続します。

ホスト側の10G Ethernet NICとケーブルの型式は以下の通りです。

  • 10G Ethernet NIC: X520-10G-2S-X8 (10Gtek)
  • 10G SFP+ケーブル: SFP-H10GB-CU1M (10Gtek)

まずはデザインを書き込む前の応答を確認します。

ホストPC側の10G Ethernet NICに対応するインターフェースに固定でIP(今回は192.168.1.1/255.255.255.0)を割り当てます。その後、別コンソールでtcpdumpを実行してから、元のコンソールでpingを192.168.1.100当てに送ります。

$ sudo ifconfig enp3s0f1 192.168.1.1 netmask 255.255.255.0
$ ping -I enp3s0f1 192.168.1.100
PING 192.168.1.100 (192.168.1.100) from 192.168.1.1 enp3s0f1: 56(84) bytes of data.
$ sudo tcpdump -i enp3s0f1 -nxe
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp3s0f1, link-type EN10MB (Ethernet), capture size 262144 bytes
06:18:32.745340 00:1b:21:bf:31:32 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.100 tell 192.168.1.1, length 28
        0x0000:  0001 0800 0604 0001 001b 21bf 3132 c0a8
        0x0010:  0101 0000 0000 0000 c0a8 0164
06:18:33.769484 00:1b:21:bf:31:32 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.100 tell 192.168.1.1, length 28
        0x0000:  0001 0800 0604 0001 001b 21bf 3132 c0a8
        0x0010:  0101 0000 0000 0000 c0a8 0164

送信先のMACアドレスがわからないのでARPリクエストが送信されています。

次に、Alveo U50に合成したビットストリームを書き込んで再度同じ操作を行います。

08:50:43.881654 00:1b:21:bf:31:32 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.100 tell 192.168.1.1, length 28
        0x0000:  0001 0800 0604 0001 001b 21bf 3132 c0a8
        0x0010:  0101 0000 0000 0000 c0a8 0164
08:50:43.881735 00:1b:21:bf:31:32 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 60: Request who-has 192.168.1.100 tell 192.168.1.1, length 46
        0x0000:  0001 0800 0604 0001 001b 21bf 3132 c0a8
        0x0010:  0101 0000 0000 0000 c0a8 0164 0000 0000
        0x0020:  0000 0000 0000 0000 0000 0000 0000

ビットストリームを書き込む前と同様、ARPリクエストを送信している様子が見えます。ただし、一度送信した直後に再度ほぼ同じ内容のリクエストが記録されています。

このときの2つ目のARPリクエストが、FPGAから送信されたEthernetフレームです。内容をそっくりそのまま送り返しているため、Ethernetヘッダーの送信元アドレスと送信先アドレスもそのままとなっており、あたかもホストから送信されたかのよな内容が記録されています。

なお、なぜか末尾に0x00のデータが増えていますが、10G Ethernet Subsystemからの受信側XGMIIの信号を観測してみると、その時点で0x00のデータが増えています。よって、ホストPC側のNICが実際に0x00を末尾に付加した信号を送ってきているようです。

受信側XGMIIの信号 (なぜか0x00が末尾に増えている)

pingに応答してみる

ここまでで、単にホストPCのNICから送信したEthernetフレームをそのまま送り返すだけのループバック動作ができるようになりました。Ethernet MACの動作確認としてはこれでよいですが、このままではあまり面白くありません。せめてpingを打ったら返ってくる、つまりICMP Echo Requestに対してICMP Echo Replyを返すくらいはしたいところです。

幸い、EthernetフレームのデータはすべてAXI4 Stream経由でやり取りするため、Vivado HLSでこの手のロジックを作ることはそんなに難しくありません。

そこで、HLSでこれらのロジックを作ろうかと考えていたところ、なんとオープンソースのTCP/IPオフロード・エンジンがGitHub上で公開されているということを社内の別のメンバーから教えてもらいました。

TCP/IPの処理を行えるということは、当然、ICMPの処理や、IPとMACアドレスを変換するためのARPの処理が含まれています。

せっかくのTCP/IPオフロードエンジンですので、TCP/IPの通信処理もできたら面白かったのですが、今回は時間の都合上、TCP/IPオフロードエンジンからARPとICMPの処理モジュールだけを借りてきてpingに応答するデザインを作ってみました。

ARPとICMP処理モジュールの接続

図中の各IPのインスタンスの役割を以下に示します。IPベンダーがTOEとなっているIPは、TCP/IPオフロードエンジンから借りてきたモジュールです。

インスタンス名IP名IPベンダー役割
axis_dwidth_converter_inAXI 4 -Stream Data Width Converter Xilinx受信したEthernetフレームの内容を含む64bit幅のAXI Streamを、TOEから借りてきたモジュールが受けられる512bit幅に変換する
packet_handler_0Packet_handler TOEEthernetフレームのTypeフィールドから種別を判定し、対応する番号をAXI StreamのTDESTに付加する。
axis_switch_inAXI4-Stream SwitchXilinxpacket_handler_0で判定した種別にしたがって、ストリームの出力先を分岐する。
arp_server_instArp_server TOEARPのEthernetフレームを処理する。自身のMACアドレスやIPはポートから入力した信号で設定する。
icmp_server_instIcmp_serverTOEICMPのEthernetフレームを処理する。
ethernet_header_inserter_instEthernet_header_inserterTOE入力されたEthernetのペイロードに対して、現在のARPテーブルの内容に基づいて、適切なEthernetヘッダーを付加して出力する
axis_switch_outAXI4-Stream SwitchXilinxethernet_header_inserter_instおよびarp_server_instから出力された送信データをフレーム単位で切り替えて出力する
axis_dwidth_converter_outAXI4-Stream Data Width ConverterXilinxTOE関連IPの512bit幅AXI4-Streamを64bit幅に変換する
xlconstant_ipConstantXilinx自身のIPアドレスを表す定数信号。TOE IPが扱う信号はビッグエンディアンにする必要があるため、バイト順が逆転することに注意 (他の定数も同様)
例えば、192.168.1.100は0x6401a8c0とする。
xlconstant_macConstantXilinx自身のMACアドレスを表す定数信号
xlconstant_netmaskConstantXilinx自身のIPアドレスのネットマスクを表す定数信号
xlconstant_gwConstantXilinxデフォルトゲートウェイのIPアドレスを表す定数信号
axis_dispose_udpaxis_disposeFixstars今回は使用しないUDPのパケットデータを捨てるために、ダミーのTREADYを出力する。
(こうしないと、UDPのフレームを受信したらaxis_switch_inがハングアップする)
axis_dispose_tcpaxis_disposeFixstars今回は使用しないTCPのパケットデータを捨てるために、ダミーのTREADYを出力する。
ICMP応答デザインの各インスタンスの役割

TCP/IPオフロードエンジンのIPは一部を除いてVivado HLSで記述されています。公開されているソースコードに付属しているVivado HLS合成スクリプトでは、Virtex Ultrascale+ XCVU9P用のIPを生成します。

このままではAlveo U50用のデザインに組み込めないので、合成スクリプトのターゲットFPGAをAlveo U50に対応する型式 (xcvu35p-fsvh2104-2-e) に変更して高位合成を行っています。

また、もともとはVivado HLS 2018.3用のようですが、Vitis 2019.2に付属しているVivado HLSで問題なく合成できています。

pingの動作確認

ループバックの時と同様に、合成したビットストリームをAlveo U50に書き込み、ホストからpingを打ってみます。FPGA側のIPは192.168.1.100、MACアドレスは00:11:22:33:44:55 に設定しています。

$ ifconfig enp3s0f1 # FPGAにつながっているNICはenp3s0f1
enp3s0f1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.1  netmask 255.255.255.0  broadcast 192.168.1.255
        ether xx:xx:xx:xx:xx:xx  txqueuelen 1000  (イーサネット)
        RX packets 38348  bytes 5858313 (5.8 MB)
        RX errors 0  dropped 97  overruns 0  frame 0
        TX packets 483783  bytes 69814135 (69.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

$ sudo arp -i enp3s0f1 -d 192.168.1.100 #ARPエントリを削除
$ arp -i enp3s0f1                       #ARPエントリを確認
arp: 29のエントリ中, 一致するものが見つかりません.

$ ping 192.168.1.100 # FPGAにpingを打つ
PING 192.168.1.100 (192.168.1.100) 56(84) bytes of data.
64 bytes from 192.168.1.100: icmp_seq=1 ttl=128 time=0.036 ms
64 bytes from 192.168.1.100: icmp_seq=2 ttl=128 time=0.081 ms
64 bytes from 192.168.1.100: icmp_seq=3 ttl=128 time=0.077 ms
64 bytes from 192.168.1.100: icmp_seq=4 ttl=128 time=0.078 ms
64 bytes from 192.168.1.100: icmp_seq=5 ttl=128 time=0.040 ms
64 bytes from 192.168.1.100: icmp_seq=6 ttl=128 time=0.044 ms
64 bytes from 192.168.1.100: icmp_seq=7 ttl=128 time=0.078 ms
64 bytes from 192.168.1.100: icmp_seq=8 ttl=128 time=0.060 ms
64 bytes from 192.168.1.100: icmp_seq=9 ttl=128 time=0.076 ms
64 bytes from 192.168.1.100: icmp_seq=10 ttl=128 time=0.080 ms
^C
--- 192.168.1.100 ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 9217ms
rtt min/avg/max/mdev = 0.036/0.065/0.081/0.017 ms

$ arp -i enp3s0f1 # ARPエントリ確認
アドレス               HWタイプ  HWアドレス         フラグ マスク インタフェース
192.168.1.100            ether   00:11:22:33:44:55   C                     enp3s0f1

以上で、ホストPCからのpingに応答することが確認できました。また、pingの後にホストPCのARPエントリに、FPGAのIPアドレスとMACアドレスが追加されており、ARPの処理が動作していることが確認できました。

おわりに

Alveo U50向けに10G Ethernet MAC IPを作って動作させるのは、とりあえず動くものであれば、そんなに難しくないということがわかりました。

製品などに使用するには、検証内容など足りないところがありますが、とりあえず10G Ethernetのフレームに応答して、何かしら処理をして、結果を返すシステムのプロトタイプを作ってみるのには十分かと思います。

今回作成した10G Ethernet MACのソースコードを https://github.com/fixstars/xg_mac に公開しました。使用方法や注意事項などについては README やライセンスファイルを御覧ください。なおファイルを clone する際は以下のように recursive オプションを付けて取得するようにして下さい。

$ git clone --recursive git@github.com:fixstars/xg_mac.git

Tags

About Author

idakenta

Leave a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Recent Comments

Social Media