インテル® PACでホストアプリケーションからネットワークまでのレイテンシを測定してみる

こんにちは。僕です。
ここしばらくは、相変わらずインテル®PACカードとインテル® アクセラレーション・スタック（以下IAS）を触っています。どうしてどのお客様も、自分たちのFPGAカードでもIASを使わせろと言ってこないんだろうと憤る日々です。使い勝手良いのに。
ちなみに、既に次のバージョンであるインテル® オープン FPGA スタック（以下OFS）もリリースされています。IASと似たような感じで使えます。

# Streaming DMA AFUとは

さて、PACカードを使ってFPGA開発をすすめる際は、色々あるデザインサンプルのうち一番近そうなもの・使えそうなものをベースにすることが多いです。その中でも特に使われることが多いものの一つが、下のStreaming DMA AFU(Accelerator Functional Unit)と呼ばれるサンプルです。

https://www.intel.com/content/www/us/en/programmable/documentation/ctb1526061110715.html

なぜ良く使われるかというと、文字通りStreaming的にデータを流して処理できるので、レイテンシー的に有利な結果が出ることが多いためです。詳細は上のドキュメントを読んで頂くと良いのですが、構造としては以下の図のようになっています（上記のドキュメントからの抜粋です）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95821/b4f77026-0a42-f547-b93c-2a7f6f0a75a9.png)

見ていただくと分かる通り、主要なモジュールは水色のハコで書いてある２つのDMAコントローラーです。
M2S DMA BBBとあるのが、Avalon® Memory Mapped Interface(以下AVMM)
でデータを読み込んで、Avalon® Streaming Interface（以下AVST）で読み込んだデータを出力するBBB(Basic Building Block。まあIPだと思っていただければ)です。
S2M DMA BBBとあるのが、逆にAVST I/FからデータをStreamingで読み込んでAVMM I/Fから出力する、というものになります。
ですので、M2S DMA BBBとS2M DMA BBBの間にAVST I/Fの入出力を持ったアクセラレータを挟めば、一旦FPGAボード上のDDRメモリにデータをコピーして云々等やる必要なく、直接ホスト側から流れてきたデータを処理できて、レイテンシー的に嬉しいということになります。

ちなみに、AVMMとAVSTのスペックは以下です。

https://www.intel.com/content/www/us/en/programmable/documentation/nik1412467993397.html

本サンプルはもう少し工夫がしてあって、処理データの入出力はホストI/FだけでなくFPGAボード上のメモリーも選択可能になっていますし（EMIF A/B）、M2S DMA BBBで読み込んだデータをループバック的にS2M側に戻すだけでなくPattern Checkerで期待値比較を出来るようになっていたり、Streaming Decimatorというモジュールでデータの量を減らして（アクセラレーター実装を模擬するイメージ）、S2M側に返すことも出来るようになっています。
また、図上でDFHというオレンジ色のモジュールが２つありますが、これはIAS対応のAccelerator Functional Unit（以下AFU）を開発する際に実装が必須になるものです。詳しくは以下を参照ください。

https://www.intel.com/content/www/us/en/programmable/documentation/bfr1522087299048.html

# 今回の目的

FPGAといえば低レイテンシー処理向けと言われることが多いですが、最近筆者はとある案件で、NICカードではなくFPGAカードを介してUDP通信したときのレイテンシーを知りたいということになりました。
そこで、ちょうど私が作ったUDP Offload Engineがあったこともあり、Streaming DMA AFUサンプルを使って測定しようと思います。
使うFPGAボードは、手軽に使えるところでArria10を搭載したPACカードにしました。

https://www.intel.com/content/www/us/en/products/details/fpga/platforms/pac/arria-10-gx.html


# 修正したところ

既存の機能はデバッグ用にそのまま残したかったので、今回は1:2 De-multiplexerと2:1 Muiltiplexerをそれぞれ1:3、3:1版に修正し、増やしたポートにネットワーク系を接続することにしました。

:::note warn
申し訳ありません…
ソースコード掲載の許可取りが間に合わず、とりあえず今の時点では載せられるものだけ載せます。
実コードがほしい、という方はインテルの担当者にお問い合わせください。
:::

図としてはこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95821/693c453e-092c-2088-cfdb-16da969c6bb2.png)

今回使うPACカードは、10GbE x4として使えるQSFPポートが１つついています。ここに、以下のようなブレークアウトケーブル（今回はこれを使いましたが、特にこれを推奨しているわけではありません）を接続して10GbEを４系統とし、４つのうち今回使う２つに関して10GbE対応のスイッチに接続しました。

https://www.fs.com/jp/products/74541.html

修正するファイルは以下になります。

##two_to_one_streaming_mux.v
`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu/hw/rtl/QSYS_IPs/two_to_one_streaming_multiplexer/two_to_one_streaming_mux.v`

大した変更ではないですが、一応変更点を載せておきます。

```two_to_one_streaming_mux.v
module one_to_two_streaming_demux (
//snip
  src_C_data,
  src_C_sop,
  src_C_eop,
  src_C_empty,
  src_C_valid,
  src_C_ready
);

//snip

 reg [1:0] select;  // when 0 source A is selected, and when 1 source B is selected


  always @ (posedge clk)
  begin
    if (reset)
    begin
      select <= 2'b00;  // coming out of reset sink A will be selected
    end
    else if ((csr_write == 1'b1) & (csr_byteenable[0] == 1'b1))  // lowest byte lane being written
    begin
      select <= csr_writedata[1:0];
    end
  end

//snip

  assign csr_readdata = {62'h00000000, select};
  assign src_C_data = snk_data;
  assign src_C_sop = snk_sop;
  assign src_C_eop = snk_eop;
  assign src_C_empty = snk_empty;
  assign src_C_valid = (select == 2'd2) & snk_valid;  // if source B is selected then forward the sink valid to source B's valid


  // src A & B to sink wiring
  assign snk_ready = (select == 2'd0) ? src_A_ready:
                     (select == 2'd1) ? src_B_ready:
                                        src_C_ready;
// snip
```

##one_to_two_streaming_demux.v

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu/hw/rtl/QSYS_IPs/one_to_two_streaming_demultiplexer/one_to_two_streaming_demux.v`

上とほぼ似たような修正です。

##ccip_std_afu.sv

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu_udp/hw/rtl/ccip_std_afu.sv`

HSSIのI/Fを追加する必要があります。

```ccip_std_afu.sv
//snip
`include "platform_if.vh"
`include "pr_hssi_if.vh" // added

module ccip_std_afu
  #(
    parameter DDR_ADDR_WIDTH = 26
    )
 (
 // CCI-P Clocks and Resets
  pClk,                      // 400MHz - CCI-P clock domain. Primary interface clock
  pClkDiv2,                  // 200MHz - CCI-P clock domain.
  pClkDiv4,                  // 100MHz - CCI-P clock domain.
  uClk_usr,                  // User clock domain. Refer to clock programming guide  ** Currently provides fixed 300MHz clock **
  uClk_usrDiv2,              // User clock domain. Half the programmed frequency  ** Currently provides fixed 150MHz clock **
  pck_cp2af_softReset,       // CCI-P ACTIVE HIGH Soft Reset
  pck_cp2af_pwrState,        // CCI-P AFU Power State
  pck_cp2af_error,           // CCI-P Protocol Error Detected

  hssi, // added

//snip
);
//snip

   pr_hssi_if.to_fiu hssi;

//snip

afu afu_inst(
    .afu_clk(afu_clk),
//snip
        .hssi(hssi),
        .cp2af_sRxPort       ( mpf2af_sRxPort ) ,
        .cp2af_mmio_c0rx     ( pck_cp2af_mmio_sRx.c0 ) ,
        .af2cp_sTxPort       ( af2mpf_sTxPort )
);

endmodule
~
```


##afu.sv

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu_udp/hw/rtl/afu.sv`

筆者はこのモジュールにMAC IPやUDP Offload Engine IPを追加しました。

##streaming_dma_afu.json

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu_udp/hw/rtl/streaming_dma_afu.json`

hssiのところを追加します。

```streaming_dma_afu.json
{
   "version": 1,
   "afu-image": {
      "power": 0,
      "afu-top-interface":
         {
            "class": "ccip_std_afu_avalon_mm_legacy_wires",
               "module-ports" :
                  [
                     {
                        "class": "cci-p",
                        "params":
                           {
                              "clock": "hssi.f2a_prmgmt_ctrl_clk"
                           }
                     },
                     {
                        "class": "hssi",
                        "interface": "raw_pr"
                     }
                  ]
         },
      "accelerator-clusters":
         [
            {
               "name": "streaming_dma_test_afu",
               "total-contexts": 1,
               "accelerator-type-uuid": "eb59bf9d-b211-4a4e-b3e3-753ce68634ba"
            }
         ]
   }
}
s
```

##fileslist.txt

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu_udp/hw/rtl/filelist.txt`
追加したファイルはここに登録します。

##MAC IP類

10Gbps Ethernet Accelerator Functional Unit (AFU) design exampleというのが用意されていますので、ここからMAC関係ファイルをコピーして持ってくるのが良いと思います。

`a10_gx_pac_ias_1_2_1_pv/hw/samples/eth_e2e_e10/hw/rtl/e10/A10/altera_eth_10g_mac_base_r.v`
`a10_gx_pac_ias_1_2_1_pv/hw/samples/eth_e2e_e10/hw/rtl/e10/A10/mac`
`a10_gx_pac_ias_1_2_1_pv/hw/samples/eth_e2e_e10/hw/rtl/e10/A10/pll_mpll`
`a10_gx_pac_ias_1_2_1_pv/hw/samples/eth_e2e_e10/hw/rtl/e10/A10/xcvr_reset_controller`

##UDP Offload Engine IP

これに関しても、追ってgithub.com上で公開したいと思っています。
お急ぎの方はインテルの担当者まで…

##fpga_dma_st_test.cpp

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu_udp/sw/fpga_dma_st_test.cpp` 

```fpga_dma_st_test.cpp
                case 'l':    /* loopback mode */
                        if (NULL == tmp_optarg)
                                break;
                        if (!STR_CONST_CMP(tmp_optarg, "on")) {
                                config->loopback = STDMA_LOOPBACK_ON;
                                debug_print("loopback = on\n");
                        }
                        else if (!STR_CONST_CMP(tmp_optarg, "off")) {
                                config->loopback = STDMA_LOOPBACK_OFF;
                                debug_print("loopback = off\n");
                        }
                        else if (!STR_CONST_CMP(tmp_optarg, "eth")) { // added
                                config->loopback = STDMA_LOOPBACK_ON_ETH;
                                debug_print("loopback = eth\n");
                        }
                        else {
                                config->loopback = STDMA_INVAL_LOOPBACK;
                                fprintf(stderr, "Invalid loopback mode\n");
                                printUsage();
                        }
                        break;

```

##fpga_dma_st_test_utils.cpp

`a10_gx_pac_ias_1_2_1_pv/hw/samples/streaming_dma_afu_udp/sw/fpga_dma_st_testutils.cpp` 

```fpga_dma_st_test_utils.cpp

//snip
static fpga_result loopback_test(fpga_handle afc_h, fpga_dma_handle_t tx_dma_h, fpga_dma_handle_t rx_dma_h, s
truct config *config) {
        fpga_result res = FPGA_OK;

        // configure loopback on
        uint64_t loopback_en;
        if (config->loopback == STDMA_LOOPBACK_ON)
          loopback_en = (uint64_t)0x1;
        else
          loopback_en = (uint64_t)0x2; // loopback via Ethernet

```

# コンパイル＆事前準備

今回のテーマとは直接関係ないですが、コンパイルの仕方等も紹介しておきます。ちょっと強調しておきたいことがあるからです。

## AFUのコンパイル

このあたりはいつもと同じです。

```
$ afu_synth_setup -s hw/rtl/filelist.txt build_synth
$ cd build_synth
$ run.sh
```

## PACSignによるサイン追加

IAS1.2.1またはIAS2.0.1から、run.shで出来上がったGreen bit streamファイル（gbsファイル）に対してPACSignというツールをかけて、セキュリティのためのサインをしなければいけないことになっています。以下は、とりあえず空のサインを入れるためのフローです。

```
$ PACSign PR -t UPDATE -H openssl_manager -i streaming_dma_afu.gbs -o streaming_dma_afu_unsigned.gbs 
Output file streaming_dma_afu_unsigned.gbs exists. Overwrite? Y = yes, N = no: y
No root key specified.  Generate unsigned bitstream? Y = yes, N = no: y
No CSK specified.  Generate unsigned bitstream? Y = yes, N = no: y
```

## ネットワーク設定

私が強調したいのはここです。
もう長いことPACカードでネットワークをいじったりしていますが、ちょっと間があくとすぐここを忘れて、なぜ動かないんだ…と悩むんですよね。ここが抜けてるとネットワーク周りが動きませんのでご注意を！
ちなみに、環境によってパス名がちょっと変わる可能性はあります。

```
$ sudo sh -c "echo 10 > /sys/class/fpga/intel-fpga-dev.0/intel-fpga-fme.0/intel-pac-hssi.0.auto/hssi_mgmt/config"
$ sudo sh -c "echo 1 > /sys/class/fpga/intel-fpga-dev.0/intel-fpga-fme.0/intel-pac-hssi.0.auto/hssi_mgmt/dfe_kickstart"
```

設定が２つありますが、最初のものが10GbEの設定（40GbEも対応出来るため）、２番目がDecision feedback equalizerの起動です。

# 結果

とりあえず8MBのデータで試してみました。最初に通常のループバック、２度めにネットワーク経由のループバックを。今回は諸事情で512byte単位にパケットで区切って送信させています。

```
$ fpgaconf ../build_synth/streaming_dma_afu_unsigned.gbs 
$ cd ../sw
$ ./fpga_dma_st_test -l on -s 8388608 -p 8388608 -t fixed -f 0
Memory to Stream BW = 5401 MBps, Stream to Memory BW = 4044 MBps
Time: 0.001545
$ ./fpga_dma_st_test -l eth -s 8388608 -p 8388608 -t fixed -f 0
Memory to Stream BW = 1078 MBps, Stream to Memory BW = 1010 MBps 
Time: 0.007815
```

通常ループバックが往復約1.5ms、ネットワーク経由で約7.8msという結果になりました。まあそんなに悪くない結果でしょうか。
ちなみに、Ethernetパケット送り側の波形(Low Latency Ethernet 10G MAC Intel® FPGA IP入出力)を見てみると、こんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95821/97db1970-6acf-0869-48ad-b728465bc07a.png)

無駄なくパケットを送れていそうです。
今後、諸々最適化を進めて、どこまで高速化出来るか見ていきたいと思ってます。


# Notices & Disclaimers

Intel technologies may require enabled hardware, software or service activation.

No product or component can be absolutely secure. 

Your costs and results may vary. 

© Intel Corporation.  Intel, the Intel logo, and other Intel marks are trademarks of Intel Corporation or its subsidiaries.  Other names and brands may be claimed as the property of others.