メモ置き場

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

[tex: ]

ZynqでAXI GPIOを使ってLチカ

Zynq搭載ボードZYBO-Z7-20を使ってLチカをする.Linuxの構築や開発環境の準備は
Vivado 2018.2とDigilentのボードファイルをUbuntu 18.04にインストール - メモ置き場
ZYBO-Z7でUbuntu 16.04 LTSを動かす - メモ置き場
を参考のこと.

バイスツリーの書き換えについては
qiita.com
こちらの記事を参考にした.

Vivadoでハードウェアの作成

まずはVivadoを使ってLEDへ何らかの信号を出力できるような回路を構築する.Digilentのボードファイルをインストールしておくと,そういったサンプルデザインがすでに含まれているようだ.

Linuxの構築ではPSだけのシンプルなデザインを構築した.これをもとにAXIバスを使ってLEDへ信号を遅れるようなデザインに変更する.

f:id:okchan08:20190128213209p:plain
元々のデザイン
といってもやることは簡単で,ブロックデザインを開いてBoardのタブからGPIO→4 LEDsを選択するだけ.デフォルトのままOKを押すとAXI GPIOというIPが追加される.Run Connection Automationを押してデフォルトのままOKをクリックすると,画像のようなデザインが生成されると思う.
f:id:okchan08:20190131205232p:plain
AXI InterconnectといったIPが追加される.AXI GPIOをダブルクリックして設定を変更する.

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

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.dtspl.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が動いたまま回路の構成を動的に変更できるのはかなりありがたい.
この辺をもう少し調べてみようと思う.