Kintex-7にMicroblazeを載せる の履歴(No.3)
更新概要†
WebPack 版の Vivado を使って Kintex-7 に MicroBlaze を載せ、UART 経由で PC と通信したい。
最近は無償の WebPack 版でも MicroBlaze IP を使えるようになっているのだけれど、 実はソフト開発に使う SDK は WebPack 版では MicroBlaze をサポートしないことを後から知った。
以下の手順で自ら MicroBlaze 用のコンパイラを用意すれば、 ちょっと面倒ながらも MicroBlaze を使ったベアメタルシステムの開発が可能なことが分かったので まとめておく。
回路の作成†
MicroBlaze, ClockWizard, UART, GPIO, INTC などを並べる。
(詳細手順未稿)
SDK でエラーが出る†
プロジェクトを作成しようとすると、bsp 生成部分で "Couldn't figure out compiler's library directory" というエラーが出てしまう。
ERROR : (XSDB Server)ERROR: [Hsi 55-1545] Problem running tcl command ::sw_cpu_v2_7::generate : Couldn't figure out compiler's library directory while executing "error "Couldn't figure out compiler's library directory" "" "hsi_error"" (procedure "::sw_cpu_v2_7::generate" line 139) invoked from within "::sw_cpu_v2_7::generate microblaze_0" ERROR : (XSDB Server)si 55-1442] Error(s) while running TCL procedure generate() ERROR : (XSDB Server)ERROR: [Hsi 55-1450] Error: run ERROR : (XSDB Server)ning generate_bsp. ERROR : Error generating bsp sources: Failed in generating sources
SDK を使って MicroBlaze 開発をするには EDK のライセンスが必要ということらしい。
クロスコンパイラの準備†
そこで SDK を使わずにソフトウェアを開発する。
実は MicroBlaze 用のコードを出力する gcc を無償で利用することができるのだ。
ただし現状では、自分でビルドしなければならないみたい?
Windows 上の Vagrant で Ubuntu を立ち上げる†
gcc のビルドには Linux 環境が必要なので、お手軽な環境として Windows10 に VirtualBox を入れ、Vagrant から利用する。
ここではすでに VirtualBox と Vagrant は導入済みとして、 以下の手順で進める。
ただし、ビルドする開発ツール自体はローカルフォルダ (./work) にインストールすることになるため、必ずしもこれだけのために 仮想マシンが必要ではない。いくつかのパッケージを入れることをいとわなければ、 既存の Ubuntu 環境で作業しても問題ないはず。
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
共有フォルダの設定†
一旦 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
に接続するおまじない。
必要なパッケージを追加する†
もう一度、
LANG:console $ vagrant up --provider virtualbox $ vagrant ssh
としてシェルを立ち上げ、
LANG:console $ sudo apt-get -y install build-essential m4 g++ texinfo
開発用ツールチェインのビルド†
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.
パスに追加†
LANG:console $ cat >> ~/.bashrc PATH=/home/vagrant/work/mb-elf-toolchain-linux/bin/:$PATH ^D
試しにコンパイルしてみる†
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
めでたくコンパイルが通る。
コンパイルオプション†
MicroBlaze を AXI バスと共に使う場合には
にあるように Little Endian になるので、コンパイル時にもそうなるようにオプションを付ける必要がある。
LANG:console $ mb-gcc -mlittle-endian -O2 hello.c -nostdlib -o hello.elf
UART ライブラリを作成†
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†
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"); } }
コンパイル†
LANG:console $ mb-gcc -mlittle-endian -O2 hello.c uart.c -nostdlib -o hello.elf
elf ファイルを bit ファイルへ埋め込む†
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 をプログラムする†
あとは以下のようにして 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]
ツールボタンに割り当てる†
コンパイル → bit 置き換え → FPGAプログラム
を何度も繰り返すのはかなり面倒なので、「bit 置き換え → FPGAプログラム」をワンクリックで可能なツールボタンを作成する。
Vivado の [Tools]-[Custom Commands]-[Customize Commands...] に 上記のスクリプトを ; で区切りながら1行に繋げた手順を入れてボタンにしておく。
コンパイルしたらボタンを押すだけでプログラムができるようになる。
フラッシュメモリに書き込むには†
普通なら 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"
スタートアップコードが足りないみたい???†
上記の手順で一見動いているように見えたのだけれど、動作が不安定だ。
実は上で不足していると怒られた libxil のソースコードは Xilinx SDK に付いているので、
C:\Xilinx\SDK\2018.2\data\embeddedsw\lib\bsp\standalone_v6_7
不足するヘッダーファイル等を補うことでこれをそのまま上記環境でコンパイルして使えるっぽい。
ただ、そうしたときに完動するコードが、上記のように完全にベアな状態で走らせたときにうまく動かないケースがあった。
ここに付いてきたコードをそのままコンパイルして使っていいものかどうか、ライセンス的にちょっと心配なので、必要そうなコードを吟味して、独自のスタートアップルーチンを作るのがいいのかもしれない???