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

更新


公開メモ

概要

WebPack 版の Vivado を使って Kintex-7 に MicroBlaze を載せ、UART 経由で PC と通信したい。

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

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

回路の作成

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

クロスコンパイラの準備

Windows 上の Vagrant で 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 バスと共に使う場合には

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 ライブラリを作成

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"

Counter: 17475 (from 2010/06/03), today: 9, yesterday: 0