Kintex-7にMicroblazeを載せる の履歴(No.4)
更新概要†
WebPack 版の Vivado を使って Kintex-7 に MicroBlaze を載せ、UART 経由で PC と通信したい。
最近は無償の WebPack 版でも MicroBlaze IP を使えるようになっているのだけれど、 実はソフト開発に使う SDK は WebPack 版では MicroBlaze をサポートしないことを後から知った。
以下の手順で自ら MicroBlaze 用のコンパイラを用意すれば、 ちょっと面倒ながらも MicroBlaze を使ったベアメタルシステムの開発が可能なことが分かったので まとめておく。
設計方針†
FPGA ボードは以下を装備しているものとする。
- Kintex-7 FPGA
- LED が2つ
- スイッチが2つ
- USB-Serial 変換モジュール
MicroBlaze のプログラムから 自由に LED を光らせたり、スイッチの状態を読んだり、 USB 経由でホスト PC と通信をしたり、 を可能にしたい。
OS を走らせるようなたいそうなことを考えているわけではなく、 あくまでベアメタルのマイクロコントローラ的な使い方を想定している。
以下の方法でOSを走らせるのはたぶんかなり大変だと思われる。
Vivado での作業†
プロジェクトの作成†
ブロック図の作成†
こちらが完成形:
- 入力端子として
- クロック(差動入力)
- リセット(負論理)
- スイッチ2つ
- UART-RX (USB-Serial 変換器の TX 端子と繋ぐ)
- 出力端子として
- LED 2つ
- UART-TX (USB-Serial 変換器の RX 端子と繋ぐ)
を持っており、それらが適切に IP に繋がっている。
以下が手順:
同様に LED やスイッチの制御のための GPIO モジュール を追加
上部の Run Block Automation を押し、MicroBlaze についていろいろ設定する
OK すると以下が自動的に追加される。
- クロックジェネレータ (Clocking Wizard)
- リセットコントローラ (Processor System Reset)
- ローカルメモリ (local memory)
- AXI ハブ (AXI Interconnect)
- 割り込みコントローラ (AXI Interrupt Controller)
Clocking Wizard をダブルクリックして
GPIO をダブルクリックして
&ref(): File not found: "adjust-conscat.png" at page "電気回路/HDL/Kintex-7にMicroblazeを載せる"; 28bit, 1bit, 1bit, 2bit のポートを用意する
GPIO モジュールの GPIO バスをクリックして開き、slice, concat, const と結ぶ。
また、Slice の出力、Concat の2本の入力について、右クリックから Make External して外部端子を出す。
UART, GPIO からの割り込み線を INTC へ繋がる Concat へ接続
UART の UART 端子を右クリックして Make External
Clocking Wizard の入力端子を Make External
左上の Run Connection Automation をクリック
これで UART と GPIO が AXI ハブに繋がる。
Regenerate Layout して完成。
一カ所訂正†
reset_rtl_0 と resetn_0 は被ってしまうので、reset_rtl_0 を削除して resetn_0 から ext_reset_in へ線を引いた。
アドレスを確認†
Diagram のとなりのタブに Address Editor がある。
ここで、それぞれの IP のベースアドレスを確認できる。
必要に応じて変更することもできる。
ブロック図から HDL を生成する†
上図の左下で Generate Block Design を押すと以下のダイアログが現れる。
design_1 上で右クリックして Create HDL Wrapper を選択。
扱いは Vivado に任せる。
design_1_wrapper.v が自動生成され、以降、必要に応じて自動更新される。
Synthesis してみる†
まだ制約をまったく与えていないものの、この時点で Synthesis が可能になっている。
&ref(): File not found: "synthesis-success.png" at page "電気回路/HDL/Kintex-7にMicroblazeを載せる"; &ref(): File not found: "post-synthesis-utilization.png" at page "電気回路/HDL/Kintex-7にMicroblazeを載せる";
Clocking Wizard に
&ref(): File not found: "post-synthesis-report-timing-summary.png" at page "電気回路/HDL/Kintex-7にMicroblazeを載せる";
制約を与える†
module design_1_wrapper のインタフェースを見ると、
module design_1_wrapper( input wire CLK_IN1_D_0_clk_n, input wire CLK_IN1_D_0_clk_p, output wire [1:0] Dout_0, input wire [0:0] In1_0, input wire [0:0] In2_0, input wire UART_0_rxd, output wire UART_0_txd, input wire resetn_0 );
となっている。
Bit Stream を生成する†
(未稿)
制約が満たされていることを確認†
(未稿)
ハードウェアを SDK にエクスポート†
以下に見るように、MicroBlaze 用のソフトウェアは WebPack 版の Xilinx SDK では開発できないので、 実はこの作業は無意味であった。
EDK を持っていて、SDK でソフト開発ができる場合にはこの手順で正しいはず。
(以下未稿)
SDK でエラーが出る†
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
不足するヘッダーファイル等を補うことでこれをそのまま上記環境でコンパイルして使えるっぽい。
ただ、そうしたときに完動するコードが、上記のように完全にベアな状態で走らせたときにうまく動かないケースがあった。
ここに付いてきたコードをそのままコンパイルして使っていいものかどうか、ライセンス的にちょっと心配なので、必要そうなコードを吟味して、独自のスタートアップルーチンを作るのがいいのかもしれない???





























