AXI4-LiteスレーブIPの動作テスト

(267d) 更新

公開メモ

概要

z-turn の PL 領域に自作の AXI4-Lite スレーブ IP を置き、 Linux 上のプログラムから UIO モジュール経由でメモリマップレジスタにアクセスできることを確認する。

自作 IP の作成

リファレンスプロジェクト mys-xc7z020-trd.xpr を立ち上げて、design_1_i を開く。

open_design_1_i.png


[Tools]-[Create and Package New IP...]

create_and_package_new_ip.png


[Create a new AXI4 peripheral]

create-axi4-peripheral.png


適当な名前 "accesstest" をつけた。

newip-peripheral-details.png


AXI Lite の Slave を作成。メモリサイズは最小の 64 で、実際の記憶領域は4ワード。

newip-add-interfaces.png


[Add IP to the repository]

newip-create-peripheral.png

IP の編集

[Window]-[IP Catalog] として IP Catalog を開き、

window-ipcatalog.png

上で作った accesstest_v1.0 上で右クリック後 [Edit in IP Packager]

edit-in-ip-packager.png

編集用のプロジェクトをどこに作るか聞かれます。

このとき vivado は編集が終わったら完全に消去するつもりの一時的なプロジェクトを 作成する気まんまんで "C:/z-turn/mys-xc7z020-trd/mys-xc7z020-trd.tmp" などという場所に "accesstest_v1_0_v1_0_project" などという名前のプロジェクトを 作ろうとします。

すでに IP 本体の開発が済んでいて、IP を登録するためだけのプロジェクトであれば そのような一時プロジェクトでかまわないのでしょうけれど、まっさらから IP 開発する場合であればテストコードなどを含めた恒久的なプロジェクトを作りたくなる はずなので、これらを変更して、まともな場所にまともなプロジェクトとして保存します。

このあたり作法はもしかすると本来とは異なるのかもしれないので、 IP 開発に慣れたら再度レビューしたいと思います。

さしあたり以下のようにしました。

ip-packager-project-location.png

すると新しい vivado ウィンドウに C:/z-turn/ip/accesstest/accesstest.xpr というプロジェクトが開きます。

accesstest_v1_0_S00_AXI_inst をダブルクリックして accesstest_v1_0_S00_AXI.v を開きます。

S00_AXI_inst.png

accesstest_v1_0_S00_AXI モジュールは AXI Lite スレーブインターフェースのみ 備えたモジュールで、この AXI Lite スレーブインターフェースは IP のメインモジュール accesstest_v1_0 を通じて IP 外に直接出て行きます。

S00_AXI モジュール内で AXI バスからの指示に応答し、IP の動作を変更したり、 IP の状態を返したりすれば AXI バス経由でアクセス可能な IP ができあがります。

上記の操作で自動的に作成された S00_AXI モジュール内にはダミーのレジスタが 4つ作成されており(slv_reg[0-4])、すでに AXI4-Lite バスから読み書きが可能になっています。

ですのでこのままビルドすれば読み書きのテストができるはずなのですが、 そのままだと書いて、読むだけ、なので本当にうまくいっているか確信が持ちにくい。

そこで、

LANG:verilog
	always @(*)
	begin
	      // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        2'h0   : reg_data_out <= slv_reg0;
	        2'h1   : reg_data_out <= slv_reg1;
	        2'h2   : reg_data_out <= slv_reg2;
	        2'h3   : reg_data_out <= slv_reg3;
	        default : reg_data_out <= 0;
	      endcase
	end

となっている部分を意地悪く、

LANG:verilog
	always @(*)
	begin
	      // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        2'h0   : reg_data_out <= slv_reg1;
	        2'h1   : reg_data_out <= slv_reg2;
	        2'h2   : reg_data_out <= slv_reg3;
	        2'h3   : reg_data_out <= slv_reg0;
	        default : reg_data_out <= 0;
	      endcase
	end

として、読み出し時に1番地だけずれたデータを読み出すようにしておきます。

読み出しアドレス実際に読まれるアドレス
01
12
23
30

IP のリパッケージング

verilog ファイルを編集し、保存してから Package IP のページに戻ると、 [File Groups] の左側のアイコンが緑のチェックマークから編集済みのマークに変化しています。

package-ip-repackage.png

この状態で右側の [Merge Change] を押すと、編集マークが消えます。

そして [Re-Package IP] を押すと、更新された IP がインストールされるのですが、、、

その前に [Tools]-[Project Settings...] から、

tools-project-settings.png


[Delete project after packaging] をクリアしておかないと、 パッケージングが済み次第、プロジェクトが跡形もなく消え去ります。

delete-project-after-packaging.png


上記チェックを外してから、おもむろに [Re-Package IP] を押せば

finished-packaging.png

と出て完了です。

再度プロジェクトを開いたとき

Project Manager の下に [Package IP] ボタンがあるので、これを押すと Package IP パネルが現れます。

package-ip-button.png

作った IP を PS につなぐ

mys-xc7z020-trd.xpr プロジェクトに戻り、 design_1 の Diagram 上で右クリックから [Add IP...]

add-ip.png


Search に "access" まで入力して、accesstest_v1.0 を選択後、ENTER

select-accesstest.png


そのまま [Run Connection Automation] します。

run-connection-automation.png


clock についてのオプションも auto で OK

run-connection-automation-clock.png


つながりました。

diagram-after-auto-connection.png


[Regenerate Layout] して整えます。

regenerate-layout-button.png

diagram-after-regenerate-layout.png

アドレス設定を確認

[Address Editor] を開いて、accesstest_0 の Offset Address を確認します。

address-editor.png

bitstream の生成

[IP Integrator] の [Generate Block Design] をしてから、
[Program and Debug] の [Generate Bitstream]

# Generate Bitstream だけで Generate Block Design もしてくれるべきだと

# 思うのだけれど、どうして自動化されないんだろう???

生成されたスレーブ IP の動作

電気回路/HDL/VivadoでAXIバスを利用#h589575e にて勉強しました。

Linux の Device Tree に IP を登録する

https://formalism.github.io/blog/posts/2014/05/zynqpllinux-dts/
を参考に UIO デバイスを使います

実は割り込みを使用しないデバイスを登録する方法がよくわかりません???
割り込みを使わないモジュールでも、割り込み番号を指定してやらないと

uio_pdrv_genirq 43c00000.accesstest: failed to get IRQ
uio_pdrv_genirq: probe of 43c00000.accesstest failed with error -22

などとして怒られてしまいます。

http://fpga.org/2013/05/28/how-to-design-and-access-a-memory-mapped-device-part-two/
によれば、ダミーの番号を指定しておけばいいそうです。

z-turn ボード付属の devicetree.dtb に、

LANG:dts
 amba {
   compatible = "simple-bus";
   #address-cells = <0x1>;
   #size-cells = <0x1>;
   interrupt-parent = <0x3>;
   ranges;

+  accesstest@43c00000 {
+      compatible = "generic-uio", "uio", "uio_pdrv";
+      reg = <0x43c00000 0x1000>;
+      interrupts = < 0 57 0 >;
+      interrupt-parent = <0x3>;
+  };

を追加したところ、/dev/uio0 というデバイスができました。

自分で xilinx linux をコンパイルした新しい Linux Kernel と dtb だと ちょっとうまくいかなくて悩んでいます。 → 解決しました

以下は VirtualBox にインストールした Debian8 のコンソール

~/linux-zynq-stable 以下にカーネルソースがあるものとします。

LANG:console
$ mkdir ~/devicetree
$ cd ~/devicetree
$ cp ../linux-zynq-stable/arch/arm/boot/dts/zynq-zturn-myir.dts .
$ cp ../linux-zynq-stable/arch/arm/boot/dts/zynq-7000.dtsi .
$ cp ../linux-zynq-stable/arch/arm/boot/dts/skeleton.dtsi .
$ dtc -O dtb -o zynq-zturn-myir.dtb zynq-zturn-myir.dts
$ ls -l zynq-zturn-myir.dtb
 -rw-r--r-- 1 osamu osamu 8878  2月  7 23:47 zynq-zturn-myir.dtb
$ cp zynq-zturn-myir.dts accesstest.dts

ソフトウェアから使う

https://formalism.github.io/blog/posts/2014/05/zynqpllinux/
を参考に、

LANG:c
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>

#define LOG(CMD) CMD; printf("%s\n", #CMD);
#define LOG2(CMD) printf("%s => 0x%08x\n", #CMD, CMD);

int main()
{
    int uiofd;
    uiofd = open("/dev/uio0", O_RDWR);
    if (uiofd < 0) {
        perror("uio open:");
        return errno;
    }

    unsigned int* map_addr = (unsigned int*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, uiofd, 0);
    if (!map_addr){
        fprintf(stderr, "mmap failed\n");
        return errno;
    }

    LOG(map_addr[0] = 0);
    LOG(map_addr[1] = 1);
    LOG(map_addr[2] = 2);
    LOG(map_addr[3] = 3);

    LOG2(map_addr[0]);
    LOG2(map_addr[1]);
    LOG2(map_addr[2]);
    LOG2(map_addr[3]);

    LOG(((unsigned char*)map_addr)[0] = 1);
    LOG2(map_addr[3]);
    LOG(((unsigned char*)map_addr)[1] = 2);
    LOG2(map_addr[3]);
    LOG(((unsigned char*)map_addr)[2] = 3);
    LOG2(map_addr[3]);
    LOG(((unsigned char*)map_addr)[3] = 4);
    LOG2(map_addr[3]);

    LOG(map_addr[0] = 0);
    LOG2(map_addr[3]);

    LOG(((unsigned char*)map_addr)[3] = 4);
    LOG2(map_addr[3]);
    LOG(((unsigned char*)map_addr)[2] = 3);
    LOG2(map_addr[3]);
    LOG(((unsigned char*)map_addr)[1] = 2);
    LOG2(map_addr[3]);
    LOG(((unsigned char*)map_addr)[0] = 1);
    LOG2(map_addr[3]);

    LOG(map_addr[0] = 0);
    LOG2(map_addr[3]);

    LOG(((unsigned short int*)map_addr)[0] = 0x1234);
    LOG2(map_addr[3]);
    LOG(((unsigned short int*)map_addr)[1] = 0x5678);
    LOG2(map_addr[3]);

    LOG(map_addr[0] = 0);
    LOG2(map_addr[3]);

    LOG(((unsigned short int*)map_addr)[1] = 0x1234);
    LOG2(map_addr[3]);
    LOG(((unsigned short int*)map_addr)[0] = 0x5678);
    LOG2(map_addr[3]);

    munmap(map_addr, 4096);
    close(uiofd);

    return errno;
}

として、

LANG:console
$ gcc accesstest.c -o accesstest
$ ./accesstest
 uio open:: Permission denied
$ sudo ./accesstest
 map_addr[0] = 0
 map_addr[1] = 1
 map_addr[2] = 2
 map_addr[3] = 3
 map_addr[0] => 0x00000001
 map_addr[1] => 0x00000002
 map_addr[2] => 0x00000003
 map_addr[3] => 0x00000000
 ((unsigned char*)map_addr)[0] = 1
 map_addr[3] => 0x00000001
 ((unsigned char*)map_addr)[1] = 2
 map_addr[3] => 0x00000201
 ((unsigned char*)map_addr)[2] = 3
 map_addr[3] => 0x00030201
 ((unsigned char*)map_addr)[3] = 4
 map_addr[3] => 0x04030201
 map_addr[0] = 0
 map_addr[3] => 0x00000000
 ((unsigned char*)map_addr)[3] = 4
 map_addr[3] => 0x04000000
 ((unsigned char*)map_addr)[2] = 3
 map_addr[3] => 0x04030000
 ((unsigned char*)map_addr)[1] = 2
 map_addr[3] => 0x04030200
 ((unsigned char*)map_addr)[0] = 1
 map_addr[3] => 0x04030201
 map_addr[0] = 0
 map_addr[3] => 0x00000000
 ((unsigned short int*)map_addr)[0] = 0x1234
 map_addr[3] => 0x00001234
 ((unsigned short int*)map_addr)[1] = 0x5678
 map_addr[3] => 0x56781234
 map_addr[0] = 0
 map_addr[3] => 0x00000000
 ((unsigned short int*)map_addr)[1] = 0x1234
 map_addr[3] => 0x12340000
 ((unsigned short int*)map_addr)[0] = 0x5678
 map_addr[3] => 0x12345678

ちゃんとワード単位、バイト単位、ハーフワード単位で読み書きできることを確認できた。

uio ドライバを使えばベアメタルと同じ感覚でレジスタにアクセスできて 何だか怖いけど便利。

LANG:console
$ sudo chown root:root accesstest
$ sudo chmod 4755 accesstest
$ ls -l accesstest
 -rwsr-xr-x 1 root root 8108 Feb  8 06:41 accesstest
$ ./accesstest
 map_addr[0] = 0
 map_addr[1] = 1
 map_addr[2] = 2
 map_addr[3] = 3
 ...

のように setuid しておけば sudo しなくても実行できるようにできる。 (もっと怖い?)


Counter: 645 (from 2010/06/03), today: 1, yesterday: 3