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
不足するヘッダーファイル等を補うことでこれをそのまま上記環境でコンパイルして使えるっぽい。
ただ、そうしたときに完動するコードが、上記のように完全にベアな状態で走らせたときにうまく動かないケースがあった。
ここに付いてきたコードをそのままコンパイルして使っていいものかどうか、ライセンス的にちょっと心配なので、必要そうなコードを吟味して、独自のスタートアップルーチンを作るのがいいのかもしれない???