Device Tree OverlayとFPGA Managerを使ってZynqの構成をLinuxから更新する
ZynqのLinuxからの操作で,Zynqの構成を更新してみる.
必要な準備は
- Device Tree Overlay ZynqでDevice Tree Overlayを使う - メモ置き場
- FPGA Manager ZynqでFPGA Managerを使う - メモ置き場
の2つが使えるようになっていること.過去の記事を参考に使えるようにしておく.
DigilentのZYBO-Z7ボードを使って試してみた.
すでにZynqで動いている回路に対して,AXIバスで接続された新しい回路を追加してみる.
回路の組み換え
Vivadoでハードウェアを構成する
この記事*1で作成した回路から
こういった回路へLinuxから変更してみる.
AXI GPIOというIPが1個増え,ZYBO-Z7のSW3~SW0へ繋がっているような構成である.4LEDに繋がっているAXIバスのアドレスは0x4120_0000,4SWに繋がっているのは0x4121_0000となっている.
最初の回路にAXI GPIOのIPを1個追加し,Auto ConnectionでPSと接続する.その後Boardタブから4 Switchesをダブルクリックし,追加したAXI GPIOとsws_4bitsとを接続すれば,上記の回路ができる(もし出来なかったらコメントで教えてください).
回路を作ったらbitファイルとhdfファイルを出力しておく.bitファイルはZynqへコピーしておく.
SDKでデバイスツリーを作成する.
注釈1の記事と同じようにしてpl.dtsiというデバイスツリーを作成する.新しくAXIを追加したのでノードが1つ増えていると思う.
------pl.dtsi / { amba_pl: amba_pl { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges ; axi_gpio_0: gpio@41200000 { #gpio-cells = <3>; clock-names = "s_axi_aclk"; clocks = <&clkc 15>; compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a"; reg = <0x41200000 0x10000>; gpio-controller ; ........ }; axi_gpio_1: gpio@41210000 { #gpio-cells = <3>; clock-names = "s_axi_aclk"; clocks = <&clkc 15>; compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a"; gpio-controller ; reg = <0x41210000 0x10000>; ........ }; }; };
アドレスとラベルの異なるデバイスが見えているはず.前回作った回路にくらべてaxi_gpio_1が新しく追加される.このデバイスをDevice Tree OverlayしてLinuxから見えるようにする必要があるので,Device Tree Overlay用にファイルを作る.
------mydev.dts /dts-v1/; /plugin/; / { fragment@0 { target-path = "/amba_pl"; __overlay__ { axi_gpio_1: gpio@41210000 { #gpio-cells = <3>; clock-names = "s_axi_aclk"; clocks = <&clkc 15>; compatible = "generic-uio"; gpio-controller ; reg = <0x41210000 0x10000>; xlnx,all-inputs = <0x1>; xlnx,all-inputs-2 = <0x0>; xlnx,all-outputs = <0x0>; xlnx,all-outputs-2 = <0x0>; xlnx,dout-default = <0x00000000>; xlnx,dout-default-2 = <0x00000000>; xlnx,gpio-width = <0x4>; xlnx,gpio2-width = <0x20>; xlnx,interrupt-present = <0x0>; xlnx,is-dual = <0x0>; xlnx,tri-default = <0xFFFFFFFF>; xlnx,tri-default-2 = <0xFFFFFFFF>; }; }; }; };
追加するデバイスはamba_pl以下に配置されるのでtarget-path = "/amba_pl"
としている.またUIOで操作できるようにcompatible = "generic-uio"
と変更した.このファイルをZynqへコピーしておく.
Zynqの構成を更新する
作ったbitファイルとdtsファイルをもとに,Zynqの構成を変更しておく.Zynq上の適当なディレクトリで作業する.作業はrootで行う.
$ ls
new_pl.bit mydev.dts
現状の回路で使えるAXIバスを確認しておく.
$ ls /dev/uio* /dev/uio0 $ cat /sys/class/uio/uio0/maps/map0/name /amba_pl/gpio@41200000 $ ls /proc/device-tree/amba_pl/ #address-cells #size-cells compatible gpio@41200000 name phandle ranges
ということで,/dev/uio0
から0x4120_0000のAXIバスをコントロールできることがわかる.
では実際に構成を変えていく.
まずFPGA Managerを使ってPLを書き換える.
$ echo 1 > /sys/class/fpgacfg/fpgacfg0/data_format $ cp new_pl /dev/fpgacfg0 $ echo 1 > /sys/class/fpgacfg/fpgacfg0/load_start $ cat /sys/class/fpgacfg/fpgacfg0/buffer_state done
これでPLを更新できた.
次にDevice Tree Overlayを使ってデバイスツリーを更新する.
$ dtc -I dts -O dtb -o mydev.dtbo mydev.dts $ ls new_pl.bit mydev.dts mydev.dtbo $ mkdir /config/device-tree/overlays/mydev $ cp cp mydev.dtbo /config/device-tree/overlays/mydev/dtbo $ echo 1 >/config/device-tree/overlays/mydev/status
これで更新が完了した.ちゃんと新しいデバイスが見えているか確認してみると
$ ls /dev/uio* /dev/uio0 /dev/uio1 $ cat /sys/class/uio/uio1/maps/map0/name /amba_pl/gpio@41210000 $ ls /proc/device-tree/amba_pl/ #address-cells #size-cells compatible gpio@41200000 gpio@41210000 name phandle ranges
となり,新しいデバイスファイル/dev/uio1
が追加され,0x4121_0000のAXIバスを制御していることがわかる.
追加した回路をLinuxから動かしてみる.
新しく追加した回路は,ZYBO-Z7上の4つのスイッチの状態を読み取って出力してくれる,というものである.以下のようなコードを実行すると,スイッチの状態に合わせて出力される.
#include <stdlib.h> #include <stdio.h> #include <time.h> #include <sys/time.h> #include <sys/mman.h> #include <fcntl.h> #define BLOCK_SIZE 0x1000 #define REG(address) *(volatile unsigned int*) (address) int main(){ int fd; int address; printf("hello, zynq!\n"); fd = open("/dev/uio1", O_RDWR | O_SYNC); if(fd < 0){ perror("open"); return -1; } address = (int)mmap(NULL, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); if(address == MAP_FAILED){ perror("mmap"); return -1; } int data = (int) REG(address); int sw0 = data & 0x1; int sw1 = (data & 0x2) >> 1; int sw2 = (data & 0x4) >> 2; int sw3 = (data & 0x8) >> 3; printf("sw = %d%d%d%d\n", sw3, sw2, sw1, sw0); munmap((void*)address, BLOCK_SIZE); return 0; }
スイッチをパチパチ入れ替えて実行してみると,スイッチの状態に合わせてプログラムの出力が変化することがわかると思う.
これで新しい回路がちゃんと機能していることがわかった.
また注釈1でやった記事のプログラムも実行することができる.
これで,LinuxからZynqの構成を更新することができるようになった!