前回はブロックデバイスを試してみましたが
今回はネットワークデバイス(NIC)を作って動かしてみました.
実行環境は前回と同じくUbuntu 18.04, Vivado 2021.2, Petalinux 2021.2を使用し
デバイスはDigilent社のZYBO(アナログ出力がある古いタイプ)です.
VirtIOのNIC
ブロックデバイスではLinux側が主体として制御することで実現されていました
しかしNICではパケットをLinuxが送信するときは同じですが
逆に受信するときはNIC側が主体となるためその点で構造が異なります.
実現するためにはVirtuqueue(VQ)を送信と受信用に2つ用意することで実現します.
どちらのVQもLinux側がメモリ確保や最低限の準備を行います.
(FPGA側ではほとんどやることがない!)
送信時はブロックデバイスと同様にLinux側が送信したいデータを
VQが指し示すバッファ領域に書き込み割り込みを行うことでFPGA側で処理が走ります.
受信時は既に用意されているVQのうち空いているキューを選択し
受信したパケットをバッファ領域に書き込み割り込みを行うことでLinux側で処理が走ります.
仕様
今回作成したNICの仕様についてです.
- LinuxからNICとして認識されるデバイス
- Linuxから送信したパケットは読み出すだけで破棄する
(ILAから確認する) - FPGAからスライドスイッチの状況を含むパケットを送信する
- 192.168.0.1:12345(FPGA)から192.168.0.2:12345(Linux)へ送信
必要なファイルはe-trees / fpga_virtio_devicesにあります.
モジュール名をmmiodev_topからnicmmio_topに変更したため画像が一部異なっています.
デバイスの準備を行う
Vivadoを起動しツールバーからFile->Project->New…から新しいプロジェクトを作成します.
New ProjectでNextをクリックする.
Project nameは「nicdevmmio」
Project locationは「/home/dev/vivadoprj」に設定しNextをクリックする.
「RTL Project」を選択したままNextをクリックする.
Add SourcesではAdd Filesからe-trees / fpga_virtio_devicesの
nicdevmmioディレクトリ内の5つのVerilogファイルを追加しNextをクリックする.
Add Constraintsは同じディレクトリ内にあるmain.xdcを追加しNextをクリックする.
(スライドスイッチのピンアサインについてのファイル)AXI Uartlite
Default PartではBoardsから「Zybo」を選択する.
SuammaryではそのままFinishをクリックする.
左のナビゲータからIP Catalogをクリックする.
表示されたカタログの中からILAを探しダブルクリックする.
Component Nameをpacket_send_ilaに,
General OptionsではNumber of Probesを3に,
Probe_portsではProbe1のWidthを32に変更しOKをクリックする.
General Output ProductsではSynthesis OptionsをGlobalに変更しGenerateをクリックする.
再度カタログの中からFIFO Generatorを探しダブルクリックする.
Native Portsの
Read ModeはFirst Word Fall Throughに,
Write Widthは32に,
Write Depthは128に設定しOKをクリックする.
General Output ProductsではSynthesis OptionsをGlobalに変更しGenerateをクリックする.
左のナビゲーターからCreate Block Designをクリックする.
Design nameはそのままでOKをクリックする.
DiagramからAdd IPでZYNQ7 Processing Systemを選択し追加する.
Run Block AutomationをクリックしそのままOKをクリックする.
ZYNQのブロックをダブルクリックし設定画面を表示する.
PS-PL Configurationを開き
ACP Slave AXI Interface->S AXI ACP interface, Tie off AxUSERの両方にチェックを入れる.
次にPeripheral I/O PinsからEthernet 0->MDIOをEMIOからMDIOに変更する.
次にInterruptsからFabric Interrupts->PL-PS Interrupt Ports->IRQ_F2P[15:0]にチェックを入れる.
右下のOKで設定を終える.
Critical Messagesが出るがOKをクリックする.
Diagramの空白で右クリックしAdd Module…をクリックする.
mmiodev_topを選択しOKをクリックする.
モジュール名を変更したため現在は「nicmmio_top」となります.
Run Connection AutomationをクリックしAll Automationにチェックを入れてOKをクリックする.
再度Run Connection AutomationをクリックしAll Automationにチェックを入れてOKをクリックする.
Diagramの空白で右クリックしAdd Module…をクリックし
packetgen_convとpacketgensimpleの2つを追加する.
Run Connection AutomationをクリックしAll Automationにチェックを入れてOKをクリックする.
packetgensimpleとpacketgen_convのsrc_*, dst_*を同じ名前同士接続する.
また, packet_len, data, trig, rdenも図を参考に接続する.
packetgen_convとblkmmio_top mmiodev_topも同様に接続する.
blkmmio_top mmiodev_topとZYNQの割り込みを接続する.
packetgensimpleのswitchピンを右クリックしMake Externalをクリックする.
作成された外部ピンをクリックし名前をswitchに変更する.
以上でDiagramの設計は完了です.
Ctrl+Sなどで保存します.
Diagramの隣のAddress Editorから/nicmmio_top_0/s_axiのノード名と割り当てられているベースアドレスをメモします.
今回は「mmiodev_top_0」で「0x43C0_0000」です.
モジュール名を変更したため現在は「nicmmio_top_0」となります.
PROJECT MANAGERから先程作成したdesign_1をtopモジュールにします.
design_1を右クリック, Create HDL Wrapper…を選択し Let ~のままOKをクリックする.
Critical Messagesが出ますがOKで閉じます.
作成されたdesign_1_wrapperを右クリックしSet as TOPを選択します.
左下のGenerate Bitstreamを選択し論理合成を行います.
合成が終わったらハードウェアファイルを出力します.
File->Export->Export Hardware…をクリックする.
Outputではinclude bitstreamを選択し最後までNextをクリックし, Finishで終了する.
Linuxのビルド
次はPetalinuxのビルドに取り掛かります.
便宜上Vivadoプロジェクトのディレクトリ内にpetalinuxのディレクトリを作成します.
コンフィグが出ますがそのままExit->Yesで終了します.
$ cd ~/vivadoprj/nicdevmmio
$ petalinux-create --type project --template zynq --name petaprj
$ cd petaprj/
$ petalinux-config --get-hw-description=../
カーネルコンフィグにVritIOを組み込むために設定を行います.
$ petalinux-config -c kernel
Device Drivers-> Network device support > Virtio network driverを<*>に設定します.
Device Drivers->Virtio drivers->Platform bus driver for memory mapped virtio devicesを<*>にするともう一つ項目が出てくるのでMemory mapped virtio devices parameter parsingも[*]に設定します.
Exitを繰り返し最後にYesで保存をして終了します.
次にLinuxでパケットの受信確認をするためのソフトウェアを導入する設定をします.
$ petalinux-config -c rootfs
Petalinux Package Groups -> ~networking-debug, ~networking-stackの両方を<*>に設定して
Exitを繰り返し最後にYesで保存して終了します.
VirtIOデバイスであることを認識してもらうためにデバイスツリーに追記します.
ファイルパスは「~/vivadoprj/nicdevmmio/petaprj/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi」です.
regの項目は先程メモったアドレスを書き込みます.
参考 : ZYNQ Linuxが動作するPS部への割り込み
元々の記述はビルドすると「~/vivadoprj/nicdevmmio/petaprj/components/plnx_workspace/device-tree/device-tree/pl.dtsi」に出てきます.
/include/ "system-conf.dtsi"
/ {
};
&nicmmio_top_0 {
compatible = "virtio,mmio";
reg = <0x43c00000 0x10000>;
interrupt-parent = <&intc>;
interrupts = <0 29 1>;
};
すべての準備が完了したのでビルドをしてパッケージ化します.
$ petalinux-build
$ petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot
実行する
ZYNQ上で実際に動かすために必要なファイルをコピーします.
適当なmicroSDカードをFAT32でフォーマットします.
「~/vivadoprj/nicdevmmio/petaprj/images/linux」ディレクトリ下にある3つのファイルをSDカード直下にコピーします.
- BOOT.BIN
- boot.scr
- image.ub
ZYBOに書き込んだmicroSDカードを挿入しJP5のブート選択をSDに切り替えます.
電源を入れると起動します.
今回はシリアル通信用のターミナルとしてscreenを使用しました.
$ sudo screen /dev/ttyUSB1 115200
電源を入れブートします.
しばらく待つとプロンプトが出て起動が完了します.
root@petaprj:~#
認識されているNICデバイスを確認します.
MACアドレスがaa:bb:cc:dd:ee:ffのデバイスが認識されていることがわかります.
root@petaprj:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
inet6 fe80::a8bb:ccff:fedd:eeff/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 00:0a:35:xx:xx:xx brd ff:ff:ff:ff:ff:ff
4: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
root@petaprj:~#
tcpdumpでパケットを確認してみます.
パケットがいくつか流れておりFPGA側からUDPパケットが, Linux側からDHCPリクエストパケットが流れていることがわかります.
root@petaprj:~# tcpdump -i eth0 -ne -vvv -X
device eth0 entered promiscuous mode
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
12:40:44.019978 ff:ff:ff:ff:ee:ff > aa:bb:cc:dd:ee:ff, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 64, id 348, offset 0, flags [DF], proto UDP (17), length 36)
192.168.0.1.12345 > 192.168.0.2.12345: [no cksum] UDP, length 8
0x0000: 4500 0024 015c 4000 4011 b819 c0a8 0001 E..$.\@.@.......
0x0010: c0a8 0002 3039 3039 0010 0000 4865 6c6c ....0909....Hell
0x0020: 6f21 300a o!0.
12:40:45.019973 ff:ff:ff:ff:ee:ff > aa:bb:cc:dd:ee:ff, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 64, id 349, offset 0, flags [DF], proto UDP (17), length 36)
192.168.0.1.12345 > 192.168.0.2.12345: [no cksum] UDP, length 8
0x0000: 4500 0024 015d 4000 4011 b818 c0a8 0001 E..$.]@.@.......
0x0010: c0a8 0002 3039 3039 0010 0000 4865 6c6c ....0909....Hell
0x0020: 6f21 300a o!0.
12:40:45.056010 aa:bb:cc:dd:ee:ff > ff:ff:ff:ff:ff:ff, ethertype IPv4 (0x0800), length 342: (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 328)
0.0.0.0.68 > 255.255.255.255.67: [udp sum ok] BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length 300, xid 0x6950346d, secs 348, Flags [none] (0x0000)
Client-Ethernet-Address aa:bb:cc:dd:ee:ff
Vendor-rfc1048 Extensions
Magic Cookie 0x63825363
DHCP-Message Option 53, length 1: Discover
Client-ID Option 61, length 7: ether aa:bb:cc:dd:ee:ff
MSZ Option 57, length 2: 576
Parameter-Request Option 55, length 7:
Subnet-Mask, Default-Gateway, Domain-Name-Server, Hostname
Domain-Name, BR, NTP
Vendor-Class Option 60, length 12: "udhcp 1.32.0"
END Option 255, length 0
PAD Option 0, length 0, occurs 20
0x0000: 4500 0148 0000 0000 4011 79a6 0000 0000 E..H....@.y.....
0x0010: ffff ffff 0044 0043 0134 5cfd 0101 0600 .....D.C.4\.....
0x0020: 6950 346d 015c 0000 0000 0000 0000 0000 iP4m.\..........
0x0030: 0000 0000 0000 0000 aabb ccdd eeff 0000 ................
0x0040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0100: 0000 0000 0000 0000 6382 5363 3501 013d ........c.Sc5..=
0x0110: 0701 aabb ccdd eeff 3902 0240 3707 0103 ........9..@7...
0x0120: 060c 0f1c 2a3c 0c75 6468 6370 2031 2e33 ....*<.udhcp.1.3
0x0130: 322e 30ff 0000 0000 0000 0000 0000 0000 2.0.............
0x0140: 0000 0000 0000 0000 ........
12:40:46.019965 ff:ff:ff:ff:ee:ff > aa:bb:cc:dd:ee:ff, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 64, id 350, offset 0, flags [DF], proto UDP (17), length 36)
192.168.0.1.12345 > 192.168.0.2.12345: [no cksum] UDP, length 8
0x0000: 4500 0024 015e 4000 4011 b817 c0a8 0001 E..$.^@.@.......
0x0010: c0a8 0002 3039 3039 0010 0000 4865 6c6c ....0909....Hell
0x0020: 6f21 300a o!0.
12:40:47.019962 ff:ff:ff:ff:ee:ff > aa:bb:cc:dd:ee:ff, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 64, id 351, offset 0, flags [DF], proto UDP (17), length 36)
192.168.0.1.12345 > 192.168.0.2.12345: [no cksum] UDP, length 8
0x0000: 4500 0024 015f 4000 4011 b816 c0a8 0001 E..$._@.@.......
0x0010: c0a8 0002 3039 3039 0010 0000 4865 6c6c ....0909....Hell
0x0020: 6f21 300a o!0.
^C
5 packets captured
5 packets received by filter
0 packets droppdevice eth0 left promiscuous mode
ed by kernel
root@petaprj:~#
FPGAが送信するパケットの受信
FPGA側からはスイッチの状況を報告するパケットが流れているので受信してみます.
まずNICにIPアドレスを設定します.
(パケットはIPアドレス192.168.0.2, ポート12345に対して送信しています.)
root@petaprj:~# ip a add 192.168.0.2/24 dev eth0
root@petaprj:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
inet 192.168.0.2/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::a8bb:ccff:fedd:eeff/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 00:0a:35:xx:xx:xx brd ff:ff:ff:ff:ff:ff
4: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
root@petaprj:~#
簡単にnetcatコマンドを用いて受信します.
コマンドを実行後ZYBO上のスライドスイッチを変更するとそれに応じて受信するパケットも変化します. ASCIIコードにそのまま対応させているので10~15については:~?に対応しています.
root@petaprj:~# nc -l -u -p 12345 < /dev/null
Hello!0
Hello!0
Hello!8
Hello!<
Hello!<
Hello!>
Hello!?
Hello!9
Hello!1
Hello!1
Hello!2
Hello!3
Hello!0
Hello!6
Hello!>
Hello!?
Hello!?
^Croot@petaprj:~#
Linuxが送信するパケットの受信
FPGA側でパケットを受信できているかを確認します.
Linuxが稼働している状態でVivadoのFlow NavigatorからPROGRAM AND DEBUG -> Open Hardware Managerをクリックします.
Open target -> Auto Connectをクリックしデバッグ画面を開きます.
右下のTrigger Setupからpacket_enを選択しValueをRにします.
tcpdumpでパケット取得を開始するとともにRun triggerボタンをクリックし開始します.
ILAで取得できたパケットとtcpdumpで取得できたパケットを確認します.
ILAの先頭3クロック分(12byte)はVirtIOで使用しているヘッダーであるため4クロック目以降が目的のパケットです.
ちゃんとパケットが受信できていることがわかりました.
root@petaprj:~# tcpdump -i eth0 -ne -vvv -X
~~~
13:01:56.065972 aa:bb:cc:dd:ee:ff > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Request who-has 192.168.0.1 tell 192.168.0.2, length 28
0x0000: 0001 0800 0604 0001 aabb ccdd eeff c0a8 ................
0x0010: 0002 0000 0000 0000 c0a8 0001 ............
まとめ
今回はVirtIOのネットワークデバイスを実装し実際に動かしてみました.
FPGAでパケット送信や受信が問題なく行われていることが確認できました.
VirtIOドライバは多くの場合, 簡単に用意できることから
FPGAからデータを送信したい状況で手軽に使用できるのではないかと考えます.
またブロックデバイスではLinuxが読み書きを行わないとできなかったのに対して
ネットワークデバイスではFPGAが送信したいタイミングで送る事ができるため
更に有用性は高いと思われます.
コメント