Kintex-7にMicroblazeを載せる のバックアップ差分(No.2)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

* うまく行かないケースがある? [#kb3d846d]
* 概要 [#kb3d846d]

手順を記録して、どうしてうまく行かないかを探ってみたい
WebPack 版の Vivado を使って Kintex-7 に MicroBlaze を載せ、UART 経由で PC と通信したい。

最近は無償の WebPack 版でも MicroBlaze IP を使えるようになっているのだけれど、
実はソフト開発に使う SDK は WebPack 版では MicroBlaze をサポートしないことを後から知った。

以下の手順で自ら MicroBlaze 用のコンパイラを用意すれば、
ちょっと面倒ながらも MicroBlaze を使ったベアメタルシステムの開発が可能なことが分かったので
まとめておく。

#contents

* 回路の作成 [#gde59039]

MicroBlaze, ClockWizard, UART, GPIO, INTC などを並べる。

* クロスコンパイラの準備 [#fee22d62]

** Windows 上の Vagrant で Ubuntu を立ち上げる [#i2f75e69]

https://qiita.com/watame/items/1c17aaf266f14abef13f を見ながら

 LANG:console
 $ cd \Users\osamu\Vagrant
 $ mkdir microblaze-gcc
 $ cd microblaze-gcc
 $ vagrant init bento/ubuntu-16.04
 $ vagrant up --provider virtualbox
 $ vagrant ssh

これでコンソールが立ち上がる。

まずは日本語キーボードに変更し( https://blog.amedama.jp/entry/2017/03/10/210552 )、~
タイムゾーンを日本にする( https://qiita.com/koara-local/items/32b004c0bf80fd70777c )。

 LANG:console
 $ sudo dpkg-reconfigure keyboard-configuration
  > Generic 105-key (Intel) PC
  > Japanese
  > Japanese
  > The default for the keyboard layout
  > No compose key
 $ sudo timedatectl set-timezone Asia/Tokyo

** 共有フォルダの設定 [#u0a2c50b]

一旦 exit 出抜けて、

 LANG:console
 $ vagrant halt

で仮想マシンも止める。

\Users\osamu\Vagrant\microblaze-gcc\Vagrantfile に

 config.vm.synced_folder "./shared", "/home/vagrant/shared"

という行を追加した。

これはホストマシンの

\Users\osamu\Vagrant\microblaze-gcc\shared

の内容を仮想マシンの

/home/vagrant/shared

に接続するおまじない。

** 必要なパッケージを追加する [#o29ee0bd]

もう一度、

 LANG:console
 $ vagrant up --provider virtualbox
 $ vagrant ssh

としてシェルを立ち上げ、

 LANG:console
 $ sudo apt-get -y install build-essential m4 g++ texinfo

** 開発用ツールチェインのビルド [#a90659f6]

https://qiita.com/watame/items/1c17aaf266f14abef13f を参考にしつつ、
以下の内容を \Users\osamu\Vagrant\microblaze-gcc\shared\build-gcc.sh 
として保存した。

 LANG:sh
 export MB_WORK=~/work
 
 export MB_LINUX_DEP=${MB_WORK}/mb-linux-dep
 export MB_LINUX_PREFIX=${MB_WORK}/mb-elf-toolchain-linux
 
 export MB_BUILD=x86_64-linux-gnu
 export MB_TARGET=microblaze-elf
 export MB_PREFIX=mb-
 
 export PATH=${MB_LINUX_PREFIX}/bin:$PATH
 
 # prepare --------------------------------------------------
 mkdir -p ${MB_WORK}/build-linux
 mkdir -p ${MB_WORK}/source
 
 # download sources...
 cd ${MB_WORK}/source
 wget http://ftp.gnu.org/gnu/gmp/gmp-6.1.1.tar.xz
 wget http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.4.tar.xz
 wget http://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gz
 wget http://isl.gforge.inria.fr/isl-0.17.tar.xz
 wget http://ftp.gnu.org/gnu/binutils/binutils-2.26.1.tar.bz2
 wget http://ftp.gnu.org/gnu/gcc/gcc-5.4.0/gcc-5.4.0.tar.bz2
 wget ftp://sources.redhat.com/pub/newlib/newlib-2.4.0.20160527.tar.gz
 tar xzf gmp-6.1.1.tar.xz
 tar xzf mpfr-3.1.4.tar.xz
 tar xzf mpc-1.0.3.tar.gz
 tar xzf isl-0.17.tar.xz
 tar xzf binutils-2.26.1.tar.bz2
 tar xzf gcc-5.4.0.tar.bz2
 tar xzf newlib-2.4.0.20160527.tar.gz
 
 # linux ----------------------------------------------------
 # gmp
 cd ${MB_WORK}/build-linux
 mkdir build-gmp
 cd build-gmp
 ../../source/gmp-6.1.1/configure --prefix=${MB_LINUX_DEP}
 make && make install && make check
 
 # mpfr
 cd ${MB_WORK}/build-linux
 mkdir build-mpfr
 cd build-mpfr
 ../../source/mpfr-3.1.4/configure \
   --enable-static --disable-shared \
   --with-gmp=${MB_LINUX_DEP} \
   --prefix=${MB_LINUX_DEP}
 make && make install
 
 # mpc
 cd ${MB_WORK}/build-linux
 mkdir build-mpc
 cd build-mpc
 ../../source/mpc-1.0.3/configure \
   --enable-static --disable-shared \
   --with-gmp=${MB_LINUX_DEP} \
   --with-mpfr=${MB_LINUX_DEP} \
   --prefix=${MB_LINUX_DEP}
 make && make install
 
 # isl
 cd ${MB_WORK}/build-linux
 mkdir build-isl
 cd build-isl
 ../../source/isl-0.17/configure \
   --without-piplib \
   --enable-static --disable-shared \
   --with-gmp-prefix=${MB_LINUX_DEP} \
   --prefix=${MB_LINUX_DEP}
 make && make install
 
 # binutils
 cd ${MB_WORK}/build-linux
 mkdir build-binutils
 cd build-binutils
 ../../source/binutils-2.26.1/configure --target=${MB_TARGET} \
   --prefix=${MB_LINUX_PREFIX} \
   --program-prefix=${MB_PREFIX}
 make && make install
 
 # bootstrap gcc
 cd ${MB_WORK}/build-linux
 mkdir build-gcc1
 cd build-gcc1
 ../../source/gcc-5.4.0/configure \
   --target=${MB_TARGET} \
   --program-prefix=${MB_PREFIX} \
   --with-gnu-as --with-gnu-ld \
   --enable-static --disable-shared \
   --with-gmp=${MB_LINUX_DEP} \
   --with-mpfr=${MB_LINUX_DEP} \
   --with-mpc=${MB_LINUX_DEP} \
   --with-isl=${MB_LINUX_DEP} \
   --with-newlib \
   --without-headers \
   --enable-languages="c,c++" --enable-interwork --enable-multilib \
   --disable-decimal-float --disable-libffi \
   --disable-libgomp --disable-libmudflap --disable-libquadmath --disable-libssp --disable-libstdcxx-pch \
   --disable-nls --disable-shared --disable-threads --disable-tls \
   --prefix=${MB_LINUX_PREFIX} \
   --with-system-zlib
 make -j2 all-gcc
 make install-gcc
 
 # because newlib not support program-prefix, we need to create symbol link
 cd ${MB_LINUX_PREFIX}/bin
 ln -s mb-gcc mb-cc
 ln -s mb-ar microblaze-elf-ar
 ln -s mb-as microblaze-elf-as
 ln -s mb-c++ microblaze-elf-c++
 ln -s mb-gcc microblaze-elf-gcc
 ln -s mb-gcc microblaze-elf-cc
 ln -s mb-gcc-ar microblaze-elf-gcc-ar
 ln -s mb-gcc-nm microblaze-elf-gcc-nm
 ln -s mb-gcc-ranlib microblaze-elf-gcc-ranlib
 ln -s mb-gcov microblaze-elf-gcov
 ln -s mb-gcov-tool microblaze-elf-gcov-tool
 ln -s mb-ld microblaze-elf-ld
 ln -s mb-ld microblaze-elf-ld.bfd
 ln -s mb-nm microblaze-elf-nm
 ln -s mb-objcopy microblaze-elf-objcopy
 ln -s mb-objdump microblaze-elf-objdump
 ln -s mb-ranlib microblaze-elf-ranlib
 ln -s mb-readelf microblaze-elf-readelf
 ln -s mb-size microblaze-elf-size
 ln -s mb-strings microblaze-elf-strings
 ln -s mb-strip microblaze-elf-strip
 
 # newlib
 cd ${MB_WORK}/build-linux
 mkdir build-newlib
 cd build-newlib
 ../../source/newlib-2.4.0.20160527/configure \
   --target=${MB_TARGET} \
   --prefix=${MB_LINUX_PREFIX}
 make && make install
 
 # gcc
 cd ${MB_WORK}/build-linux
 mkdir build-gcc2
 cd build-gcc2
 ../../source/gcc-5.4.0/configure \
   --target=${MB_TARGET} \
   --program-prefix=${MB_PREFIX} \
   --with-gnu-as --with-gnu-ld \
   --enable-static --disable-shared \
   --with-gmp=${MB_LINUX_DEP} \
   --with-mpfr=${MB_LINUX_DEP} \
   --with-mpc=${MB_LINUX_DEP} \
   --with-isl=${MB_LINUX_DEP} \
   --with-newlib \
   --enable-languages="c,c++" --enable-interwork --enable-multilib \
   --disable-decimal-float --disable-libffi \
   --disable-libgomp --disable-libmudflap --disable-libquadmath --disable-libssp --disable-libstdcxx-pch \
   --disable-nls --disable-shared --disable-threads --disable-tls \
   --prefix=${MB_LINUX_PREFIX} \
   --with-system-zlib
 make -j 2
 make install
 
 cd ${MB_LINUX_PREFIX}

リンク先のままだと make -j4 all-gcc で、

 checking dynamic linker characteristics... configure: error: Link tests are not allowed after GCC_NO_EXECUTABLES.
 Makefile:8349: recipe for target 'configure-zlib' failed
 make: *** [configure-zlib] Error 1
 make: *** Waiting for unfinished jobs....

というエラーが出たので、https://stackoverflow.com/questions/29481325/arm-gcc-build-error-under-fedora-21 に従って configure に --with-system-zlib を追加してある。

あとは、

 LANG:console
 $ cd
 $ source shared/build-gcc.sh 2>1 | tee shared/build-gcc.log

とすれば、かな~り時間はかかるもののビルドに成功するはず。

 LANG:console
 $ cd
 $ ls work/mb-elf-toolchain-linux/bin
  mb-addr2line  mb-gcc-ar      mb-ranlib           microblaze-elf-gcc-5.4.0   microblaze-elf-objdump
  mb-ar         mb-gcc-nm      mb-readelf          microblaze-elf-gcc-ar      microblaze-elf-ranlib
  mb-as         mb-gcc-ranlib  mb-size             microblaze-elf-gcc-nm      microblaze-elf-readelf
  mb-c++        mb-gcov        mb-strings          microblaze-elf-gcc-ranlib  microblaze-elf-size
  mb-cc         mb-gcov-tool   mb-strip            microblaze-elf-gcov        microblaze-elf-strings
  mb-c++filt    mb-ld          microblaze-elf-ar   microblaze-elf-gcov-tool   microblaze-elf-strip
  mb-cpp        mb-ld.bfd      microblaze-elf-as   microblaze-elf-ld
  mb-elfedit    mb-nm          microblaze-elf-c++  microblaze-elf-ld.bfd
  mb-g++        mb-objcopy     microblaze-elf-cc   microblaze-elf-nm
  mb-gcc        mb-objdump     microblaze-elf-gcc  microblaze-elf-objcopy
 $ microblaze-elf-gcc --version
  microblaze-elf-gcc (GCC) 5.4.0
  Copyright (C) 2015 Free Software Foundation, Inc.
  This is free software; see the source for copying conditions.  There is NO
  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

** パスに追加 [#v74c676c]

 LANG:console
 $ cat >> ~/.bashrc
  PATH=/home/vagrant/work/mb-elf-toolchain-linux/bin/:$PATH
  ^D

** 試しにコンパイルしてみる [#wc9a93b0]

 LANG:console
 $ cd shared
 $ mkdir hello
 $ cd hello
 $ cat > hello.c
  int main()
  {
    return 0;
  }
 $ mb-gcc -c hello.c
 $ ls
  hello.c  hello.o
 $ mb-gcc hello.c -o hello.elf
  /home/vagrant/work/mb-elf-toolchain-linux/lib/gcc/microblaze-elf/5.4.0/../../../../microblaze-elf/bin/ld: cannot find -lxil
  collect2: error: ld returned 1 exit status

コンパイルはできるけれど、リンクで libxil というライブラリが見つからないといって失敗する。

標準ライブラリなしでリンクするには -nostdlib を付けると良いらしいのだけれど、

 LANG:console
 $ mb-gcc hello.c -nostdlib -o hello.elf
 /home/vagrant/work/mb-elf-toolchain-linux/lib/gcc/microblaze-elf/5.4.0/../../../../microblaze-elf/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000050

今度は _start が見つからないと言われてしまう。

[[Wikipedia:フリースタンディング環境]] などで解説されているとおり、-nostdlib を付けた場合には始めから main ではなく _start をエントリポイントとすれば良いらしい。

 LANG:console
 $ cat > hello.c
  void _start()
  {
  }
 $ mb-gcc hello.c -nostdlib -o hello.elf
 $ ls
  hello.c  hello.elf

めでたくコンパイルが通る。

** コンパイルオプション [#fa20e3ea]

MicroBlaze を AXI バスと共に使う場合には

https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_1/ug984-vivado-microblaze-ref.pdf#page=11

にあるように Little Endian になるので、コンパイル時にもそうなるようにオプションを付ける必要がある。

 LANG:console
 $ mb-gcc -mlittle-endian -O2 hello.c -nostdlib -o hello.elf

* UART ライブラリを作成 [#lf3f59f8]

address.h
 LANG:c
 #define UART_BASE   0x40600000
 #define GPIO        0x40000000
 #define INTC_BASE   0x41200000

uart.h
 LANG:c
 #ifndef UART_LIB_INCLUDED
 #define UART_LIB_INCLUDED
 extern void uart_init(int enable_interrupt);
 extern int  uart_status();
 extern int  uart_readable();
 extern int  uart_readc();
 extern int  uart_writable();
 extern void uart_writec(char c);
 extern void uart_puts(const char *str);
 extern void uart_write_hex1(int n);
 extern void uart_write_hex2(int n);
 extern void uart_write_hex4(int n);
 extern void uart_write_hex8(int n);
 #endif

uart.c
 LANG:c
 #include "address.h"
 
 #define UART_RX       (UART_BASE+0)
 #define UART_TX       (UART_BASE+4)
 #define UART_STATUS   (UART_BASE+8)
 #define UART_CTRL     (UART_BASE+12)
 
 //////////////////////////////////////////////////
 
 static void reg_set(int addr, int value)
 {
     *(volatile int *)(addr) = value;
 }
 
 static int reg_get(int addr)
 {
     return *(volatile int *)addr;
 }
 
 //////////////////////////////////////////////////
 
 void uart_init(int enable_interrupt)
 {
     reg_set(UART_CTRL, 3 + ((!!enable_interrupt) << 4));
 }
 
 int uart_status()
 {
     return reg_get(UART_STATUS);
 }
 
 int uart_readable()
 {
     return uart_status() & 1;
 }
 
 int uart_readc()
 {
     while ( !uart_readable() )  // Empty
       ;
     return reg_get(UART_RX) & 0xff;
 }
 
 int uart_writable()
 {
     return 0 == ( uart_status() & 8 );
 }
 
 void uart_writec(char c)
 {
     while ( !uart_writable() )  // Full
       ;
     reg_set(UART_TX, c & 0xff);
 }
 
 void uart_puts(const char *str)
 {
     while (*str) {
         uart_writec(*str);
         str++;
     }
 }
 
 void uart_write_hex1(int n)
 {
     n = n & 0xf;
     uart_writec('0' + n + (n >= 10 ? 'a'-'0'-10 : 0));
 }
 
 void uart_write_hex2(int n)
 {
     uart_write_hex1(n >> 4);
     uart_write_hex1(n);
 }
 
 void uart_write_hex4(int n)
 {
     uart_write_hex2(n >> 8);
     uart_write_hex2(n);
 }
 
 void uart_write_hex8(int n)
 {
     uart_write_hex4(n >> 8);
     uart_write_hex4(n);
 }

* hello.c [#neaedd80]

hello.c
 LANG:c
 #include "uart.h"
 void _start()
 {
   uart_puts("Hello World!!\n");
   while(1) {
     char c = uart_readc();
     uart_puts("detected key '");
     uart_writec(c);
     uart_puts("'\n");
   }
 }

* コンパイル [#a302f96e]

 LANG:console
 $ mb-gcc -mlittle-endian -O2 hello.c uart.c -nostdlib -o hello.elf

* elf ファイルを bit ファイルへ埋め込む [#p094f6a5]

updatemem と言うコマンドを使えばいいらしい。

 LANG:tcl
 exec updatemem -force \
 -meminfo C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/design_1_wrapper.mmi \
 -bit C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/design_1_wrapper.bit \
 -data C:/Users/osamu/Vagrant/microblaze-gcc/data/hello/hello.elf \
 -proc design_1_i/microblaze_0 \
 -out C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/download.bit

これで、design_1_wrapper.bit の中の design_1_i/microblaze_0 のメモリ内容を 
hello.elf で置き換えたものを download.bit として保存できる。

* FPGA をプログラムする [#p818f079]

あとは以下のようにして download.bit を FPGA へダウンロードできる。

 LANG:tcl
 set_property PROBES.FILE {} [get_hw_devices xc7k160t_0]
 set_property FULL_PROBES.FILE {} [get_hw_devices xc7k160t_0]
 set_property PROGRAM.FILE {C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/download.bit} [get_hw_devices xc7k160t_0]
 program_hw_devices [get_hw_devices xc7k160t_0]

* ツールボタンに割り当てる [#p8d08f3a]

コンパイル → bit 置き換え → FPGAプログラム

を何度も繰り返すのはかなり面倒なので、「bit 置き換え → FPGAプログラム」をワンクリックで可能なツールボタンを作成する。

Vivado の [Tools]-[Custom Commands]-[Customize Commands...] に
上記のスクリプトを ; で区切りながら1行に繋げた手順を入れてボタンにしておく。

コンパイルしたらボタンを押すだけでプログラムができるようになる。

* フラッシュメモリに書き込むには [#wca2c2e3]

普通なら Generate Bitstream のオプションで同時に bin ファイルを作成するようにしておけば良いのだけれど、elf を書き込んだ bit をフラッシュメモリに焼くにはどうしたらいいものか???

http://www3.hdl.co.jp/spc/xcm/466-q20160229.html

の手順で mcs ファイルを作れば良いらしい。

 LANG:tcl
 write_cfgmem -format MCS -interface SPIx1 -size 64 \
  -loadbit "up 0x0 C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/download.bit" \
  "C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/download.mcs"


Counter: 17467 (from 2010/06/03), today: 2, yesterday: 3