Zynq搭載ボードZYBO-Z7-20を使ってLチカをする.Linuxの構築や開発環境の準備は
Vivado 2018.2とDigilentのボードファイルをUbuntu 18.04にインストール - メモ置き場
ZYBO-Z7でUbuntu 16.04 LTSを動かす - メモ置き場
を参考のこと.
デバイスツリーの書き換えについては
qiita.com
こちらの記事を参考にした.
- Vivadoでハードウェアの作成
- SDKでPL部分のデバイスツリーの作成
- ZYBO-Z7用にデバイスツリーをコンパイル
- ブートファイルを書き換えてLinuxのリブート
- プログラムからUIOを経由してLチカ
- 次にやりたいこと
Vivadoでハードウェアの作成
まずはVivadoを使ってLEDへ何らかの信号を出力できるような回路を構築する.Digilentのボードファイルをインストールしておくと,そういったサンプルデザインがすでに含まれているようだ.
Linuxの構築ではPSだけのシンプルなデザインを構築した.これをもとにAXIバスを使ってLEDへ信号を遅れるようなデザインに変更する.

AXI InterconnectといったIPが追加される.AXI GPIOをダブルクリックして設定を変更する.

IP ConfigurationのタブからDefault Tri State Valueを0x0にする.Three state logicのことだとすると,これ0xFFFとかじゃないと動かない気がするが… 0x0にしないと動作しなかった.
完了したらbitstreamを生成し,File→Export→Export Hardwareでhdfを出力しておく.
Address Editorを開くとAXI GPIOがつながるアドレスが確認できる.今回は0x4210_0000に繋がっている.

SDKでPL部分のデバイスツリーの作成
SDKを開いて,fsbl.elfとデバイスツリーを生成する.それぞれのやり方は過去の記事を参考に*1.デバイスツリーを作成すると,PLの方に新しく回路が繋がっているので,PL.dtsiというデバイスツリーが生成される.中身を見てみると
/*
* CAUTION: This file is automatically generated by Xilinx.
* Version:
* Today is: Thu Jan 31 19:12:06 2019
*/
/ {
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";
gpio-controller ;
reg = <0x41200000 0x10000>;
xlnx,all-inputs = <0x0>;
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>;
};
};
};なぜかinputが2つあったり,tri-defalutが0x0でなかったりしている.reg = <0x41200000 0x10000>;の値はVivadoで確認したアドレスと一致している.gpioのところをaxi-gpioに変えておく.
----pl.dtsiの中身
* CAUTION: This file is automatically generated by Xilinx.
* Version:
* Today is: Thu Jan 31 19:12:06 2019
*/
/ {
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
axi_gpio_0: axi_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";
gpio-controller ;
reg = <0x41200000 0x10000>;
xlnx,all-inputs = <0x0>;
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>;
};
};
};新しく作成したfsbl.elfとbitstreamファイルを使ってBOOT.binを作っておく.
ZYBO-Z7用にデバイスツリーをコンパイル
この記事で使ったzynq-zyboz7.dtsを編集する.具体的には初めの部分に/include/ "pl.dtsi"を追加して,最後にaxi_gpioのデバイスドライバの指示を追加する.
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2011 - 2015 Xilinx
* Copyright (C) 2012 National Instruments Corp.
*/
/dts-v1/;
/include/ "zynq-7000.dtsi"
/include/ "pl.dtsi"
〜〜〜中略〜〜〜
&axi_gpio_0 {
compatible = "generic-uio";
};
generic-uioとすると,デバイスドライバを制作しなくても,UIOをつかって制御することができるようになる.
zynq-zyboz7.dtsとpl.dtsiを同じディレクトリに置いてコンパイルする.
dtc -I dts -O dtb -o devicetree.dtb zynq-zyboz7.dts
ブートファイルを書き換えてLinuxのリブート
SDカードにあるBOOT.binとdevicetree.dtbを新しく作成したもので置き換える.SDカードを開発マシンにマウントしてコピーしてもいいが,ここではZynqにscpでコピーして書き換えることにする.
まず作成したファイルをscpでzynqに転送する.
scp BOOT.bin aho@192.168.2.10:~ (IPアドレスは各自のものをつかう)
ZynqのUbuntuでSDカードの第一パーティションをマウントする.lsblkで第一パーティションがどう見えているか確認する.
aho@zynq:~$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT mmcblk0 179:0 0 14.5G 0 disk |-mmcblk0p2 179:2 0 14.5G 0 part / `-mmcblk0p1 179:1 0 64M 0 part
なので/dev/mmcblk0p1に見えているので/mntにマウントする
aho@zynq:~$ sudo mount -o loop /dev/mmcblk0p1 /mnt
これでSDカードの第一パーティションにファイルを書き込めるようになった.scpで転送したBOOT.binとdevicetree.dtbをコピーしてrebootする.
reboot後,デバイスツリーが読み込まれたことを確認する.
aho@zynq:~$ ls /proc/device-tree/amba_pl/ #address-cells #size-cells axi_gpio@41200000 compatible name ranges
新しく追加したaxi_gpioが41200000に見えていることがわかる.また/dev/uio0も見えている./dev/uio0がaxi_gpioを掴んでいることを確認する.
aho@zynq:~$ more /sys/class/uio/uio0/maps/map0/name
/amba_pl/axi_gpio@41200000
で正しくaxi_gpioを掴んでいる.
プログラムからUIOを経由してLチカ
/dev/uio0経由でLチカをする.以下のようなプログラムを作成する.
#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/uio0", 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; } for(int i=0;i<16;i++){ REG(address) = i; printf("%d\n", i); sleep(1); } REG(address) = 0x0; munmap((void*)address, BLOCK_SIZE); return 0; }
Zynq上でコンパイルして実行するとLD0〜LD3が光ってくれる.プログラムを実行するときはroot権限で実行しないとダメかもしれない.
次にやりたいこと
ひとまずPLの構成を変更した場合の作業内容がわかったので,もう少し複雑な回路を組んでいこうと思う.
PLの構成とデバイツリーを更新するたびに毎回Linuxをリブートするのは面倒である.リブートせずにこれらの更新ができないか調べてみたら,xdevcfgとdevice tree overlayというのを使うとLinuxが走ったまま回路の更新ができるかもしれない.
FPGAをLinuxからコンフィグするxdevcfgの使い方: なひたふJTAG日記
FPGA+SoC+LinuxでDevice Tree Overlayを試してみた - Qiita
Linuxが動いたまま回路の構成を動的に変更できるのはかなりありがたい.
この辺をもう少し調べてみようと思う.