メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

Device Tree OverlayとFPGA Managerを使ってZynqの構成をLinuxから更新する

ZynqのLinuxからの操作で,Zynqの構成を更新してみる.
必要な準備は

の2つが使えるようになっていること.過去の記事を参考に使えるようにしておく.

DigilentのZYBO-Z7ボードを使って試してみた.
すでにZynqで動いている回路に対して,AXIバスで接続された新しい回路を追加してみる.

回路の組み換え

Vivadoでハードウェアを構成する

この記事*1で作成した回路から
f:id:okchan08:20190131205232p:plain
こういった回路へLinuxから変更してみる.
f:id:okchan08:20190205102059p:plain
f:id:okchan08:20190205102102p:plain
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の構成を更新することができるようになった!