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

更新


[[公開メモ]]

#contents

* 概要 [#ec27aa28]

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

* 自作 IP の作成 [#r8f4b495]

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

&ref(open_design_1_i.png,,66%);

~

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

&ref(create_and_package_new_ip.png,,66%);

~

[Create a new AXI4 peripheral]

&ref(create-axi4-peripheral.png,,66%);

~

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

&ref(newip-peripheral-details.png,,66%);

~

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

&ref(newip-add-interfaces.png,,66%);

~

[Add IP to the repository]

&ref(newip-create-peripheral.png,,66%);

* IP の編集 [#u4937f06]

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

&ref(window-ipcatalog.png,,66%);

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

&ref(edit-in-ip-packager.png,,66%);

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

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

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

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

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

&ref(ip-packager-project-location.png,,66%);

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

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

&ref(S00_AXI_inst.png,,66%);

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番地だけずれたデータを読み出すようにしておきます。

|読み出しアドレス|実際に読まれるアドレス|
|0|1|
|1|2|
|2|3|
|3|0|

* IP のリパッケージング [#j3fa67b6]

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

&ref(package-ip-repackage.png,,66%);

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

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

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

&ref(tools-project-settings.png,,66%);

~

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

&ref(delete-project-after-packaging.png,,66%);

~

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

&ref(finished-packaging.png,,66%);

と出て完了です。

** 再度プロジェクトを開いたとき [#o82b2981]

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

&ref(package-ip-button.png,,66%);

* 作った IP を PS につなぐ [#q1932080]

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

&ref(add-ip.png,,66%);

~

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

&ref(select-accesstest.png,,66%);

~

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

&ref(run-connection-automation.png,,66%);

~

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

&ref(run-connection-automation-clock.png,,66%);

~

つながりました。

&ref(diagram-after-auto-connection.png);

~

[Regenerate Layout] して整えます。

&ref(regenerate-layout-button.png,,66%);

&ref(diagram-after-regenerate-layout.png);

* アドレス設定を確認 [#re5e8792]

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

&ref(address-editor.png,,66%);

* bitstream の生成 [#z81253be]

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

# Generate Bitstream だけで Generate Block Design もしてくれるべきだと
# 思うのだけれど、どうして自動化されないんだろう???

* 生成されたスレーブ IP の動作 [#ve2b5624]

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

* Linux の Device Tree に IP を登録する [#ee317ba8]

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 だと
ちょっとうまくいかなくて悩んでいます。
→ [[解決しました>電気回路/zynq/Device Tree Overlay#ia616ad7]]

以下は 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


* ソフトウェアから使う [#c8d69717]

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: 6654 (from 2010/06/03), today: 1, yesterday: 0