Kintex-7にMicroblazeを載せる の履歴(No.8)
更新概要†
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 での作業†
プロジェクトの作成†
Block Design をトップにするのでここでは何もしない
あ、本当は xc7k160tfbg484-1 を選んだのにキャプチャした図は違うのを選んでるときのものだった。。。
ブロック図の作成†
こちらが完成形:
- 入力端子として
- クロック(差動入力)
- リセット(負論理)
- スイッチ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 をダブルクリックして
GPIO モジュールの GPIO バスをクリックして開き、slice, concat, const と結ぶ。
また、Slice の出力、Concat の2本の入力について、右クリックから Make External して外部端子を出す。
UART, GPIO からの割り込み線を INTC へ繋がる Concat へ接続
UART の UART 端子を右クリックして Make External
Clocking Wizard の入力端子を Make External
ext_reset_in にも繋ぐ
左上の Run Connection Automation をクリック
これで UART と GPIO が AXI ハブに繋がる。
Regenerate Layout して完成。
アドレスを確認†
Diagram のとなりのタブに Address Editor がある。
ここで、それぞれの IP のベースアドレスを確認できる。
必要に応じて変更することもできる。
ブロック図から HDL を生成する†
上図の左下で Generate Block Design を押すと以下のダイアログが現れる。
design_1 上で右クリックして Create HDL Wrapper を選択。
扱いは Vivado に任せる。
design_1_wrapper.v が自動生成され、以降、必要に応じて自動更新される。
Implement してみる†
この時点で Implement が可能になっている。
まだ xdc ファイルには何の制約も追加していないのに「タイミング制約が満たされている」というような表示があるのは、Clocking Wizard に設定した入力周波数 200MHz で全体に対してクロック周波数制約がかかっているため。
[Open Implemented Design]-[Report Timing Summary] すると、
Setup, Hold, Pulse Width とも Slack (余裕) の値は正になっていて、タイミング制約を満たしていることを確認できる。そして Clock Summary で各クロックに正しくタイミング制約がかかっていることを確認できる。
注意が必要なのは、[Open Synthesized Design] から [Report Timing Summary] を見ると、
のように、Hold の Slack が負になっていて、タイミング制約を満たしていないように見えることだ。
Hold の Slack が負というのはあるゲートから次のゲートへ信号が「早く到達しすぎている」*1厳密には後段のゲートへクロックよりも次のデータが早く到達してしまっているということで、よほどクロックラインのスキューが大きいような場合でない限り起きえない。Implement 前の情報ではゲート間の遅延を正しく見積もれず、そのために負になっているということ、なのかな?
ここで見た通り Synthesis で Hold に Negative Slack が出ても、 同じ回路で Implementation では問題がなくなる場合があるようなので、 Synthesis 時点の少々の Negative Slack で悩まず Implementation を試すべきだ。
制約を与える†
module design_1_wrapper のインタフェースを見ると、
LANG:verilog input CLK_IN1_D_0_clk_n; // オンボードクロックからの差動負入力 input CLK_IN1_D_0_clk_p; // オンボードクロックからの差動正入力 output [1:0]Dout_0; // LED0, LED1 への出力 input [1:0]In1_0; // SW0, SW1 からの入力 input UART_0_rxd; // USB-Serial 変換器の TX からの入力 output UART_0_txd; // USB-Serial 変換器の RX への出力 input resetn_0; // オンボードのリセットスイッチからの負論理入力
となっている。
そこで、以下のように制約ファイルを与えた。
# 入出力ピン set_property PACKAGE_PIN V4 [get_ports CLK_IN1_D_0_clk_p] set_property IOSTANDARD LVDS [get_ports CLK_IN1_D_0_clk_p] set_property PACKAGE_PIN W4 [get_ports CLK_IN1_D_0_clk_n] set_property IOSTANDARD LVDS [get_ports CLK_IN1_D_0_clk_n] set_property PACKAGE_PIN R19 [get_ports Dout_0[0]] set_property IOSTANDARD LVCMOS33 [get_ports Dout_0[0]] set_property PACKAGE_PIN R19 [get_ports Dout_0[1]] set_property IOSTANDARD LVCMOS33 [get_ports Dout_0[1]] set_property PACKAGE_PIN T20 [get_ports In_1[0]] set_property IOSTANDARD LVCMOS33 [get_ports In_1[0]] set_property PACKAGE_PIN A21 [get_ports In_1[1]] set_property IOSTANDARD LVCMOS33 [get_ports In_1[1]] set_property PACKAGE_PIN K17 [get_ports resetn_0] set_property IOSTANDARD LVCMOS33 [get_ports resetn_0] set_property PACKAGE_PIN AA19 [get_ports UART_0_txd] set_property IOSTANDARD LVCMOS33 [get_ports UART_0_txd] set_property SLEW SLOW [get_ports UART_0_txd] set_property PACKAGE_PIN W22 [get_ports UART_0_rxd] set_property IOSTANDARD LVCMOS33 [get_ports UART_0_rxd] set_property SLEW SLOW [get_ports UART_0_rxd] # 電源に固定されているため使ってはいけないピン set_property PROHIBIT true [get_sites Y11] set_property PROHIBIT true [get_sites Y12] # コンフィギュレーションブロックの設定 set_property CONFIG_VOLTAGE 3.3 [current_design] set_property CFGBVS VCCO [current_design] ## 以下は FPGA のプログラムを短時間で行えるようにするおまじない # 周波数を 50MHz に設定 set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] # 圧縮を有効化 set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
このうち「FPGA のプログラムを短時間で行えるようにするおまじない」はかなり重要で、 これを書いておくのとおかないのとでプログラム時間がもの凄く違う。
Bit Stream を生成する†
launch_runs impl_1 -to_step write_bitstream -jobs 2 [Sat Sep 1 20:36:10 2018] Launched synth_1... Run output will be captured here: C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/synth_1/runme.log [Sat Sep 1 20:36:10 2018] Launched impl_1... Run output will be captured here: C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/runme.log close_design; open_run impl_1 INFO: [Netlist 29-17] Analyzing 305 Unisim elements for replacement INFO: [Netlist 29-28] Unisim Transformation completed in 0 CPU seconds INFO: [Project 1-479] Netlist was created with Vivado 2018.2 INFO: [Device 21-403] Loading part xc7k160tfbg484-1 INFO: [Project 1-570] Preparing netlist for logic optimization INFO: [Timing 38-478] Restoring timing data from binary archive. INFO: [Timing 38-479] Binary timing data restore complete. INFO: [Project 1-856] Restoring constraints from binary archive. INFO: [Project 1-853] Binary constraint restore complete. Reading XDEF placement. Reading placer database... Reading XDEF routing. Read XDEF File: Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.417 . Memory (MB): peak = 2962.469 ; gain = 0.000 Restored from archive | CPU: 0.000000 secs | Memory: 0.000000 MB | Finished XDEF File Restore: Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.418 . Memory (MB): peak = 2962.469 ; gain = 0.000 Generating merged BMM file for the design top 'design_1_wrapper'... INFO: [Memdata 28-144] Successfully populated the BRAM INIT strings from the following elf files: c:/Users/osamu/MicroBlazeHello/MicroBlazeHello.srcs/sources_1/bd/design_1/ip/design_1_microblaze_0_1/data/mb_bootloop_le.elf INFO: [Project 1-111] Unisim Transformation Summary: A total of 223 instances were transformed. LUT6_2 => LUT6_2 (LUT5, LUT6): 127 instances RAM16X1D => RAM32X1D (RAMD32, RAMD32): 32 instances RAM32X1D => RAM32X1D (RAMD32, RAMD32): 64 instances open_run: Time (s): cpu = 00:00:29 ; elapsed = 00:00:28 . Memory (MB): peak = 3021.410 ; gain = 673.984 open_hw reset_run impl_1 -prev_step launch_runs impl_1 -to_step write_bitstream -jobs 2 [Sat Sep 1 21:02:00 2018] Launched impl_1... Run output will be captured here: C:/Users/osamu/MicroBlazeHello/MicroBlazeHello.runs/impl_1/runme.log
となってうまく行った。
ここまでで回路は完成だ。
INFO: [Memdata 28-144] Successfully populated the BRAM INIT strings from the following elf files: c:/Users/osamu/MicroBlazeHello/MicroBlazeHello.srcs/sources_1/bd/design_1/ip/design_1_microblaze_0_1/data/mb_bootloop_le.elf
の行から分かるとおり、microblaze_0_1 に結びつけられたローカルメモリには bootloop という「何もしないプログラム」が書き込まれている。
以下はここに入れるべきソフトウェアプログラムを開発する話になる。
ハードウェアを SDK にエクスポート†
Xilinx SDK を使ってソフトを開発する場合、ハードウェアの情報を SDK に伝えるために、 [File]-[Export]-[Export Hardware] を使って情報をエクスポートする。
以下に見るように、MicroBlaze 用のソフトウェアは WebPack 版の Xilinx SDK では開発できないので、 実はこの作業は無意味であった。
EDK を持っていて、SDK でソフト開発ができる場合にはこの手順で正しいはず。
SDK でエラーが出る†
vivado の [File]-[Launch SDK] から SDK を立ち上げると、
のように、利用した IP の情報が正しく SDK にエクスポートされていることを確認できる。
そこで SDK の [File]-[New]-[Application Project] からソフトウェア開発用の プロジェクトを作成しようとするとエラーが出てしまう。
log を見ると、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
不足するヘッダーファイル等を補うことでこれをそのまま上記環境でコンパイルして使えるっぽい。
ただ、そうしたときに完動するコードが、上記のように完全にベアな状態で走らせたときにうまく動かないケースがあった。
ここに付いてきたコードをそのままコンパイルして使っていいものかどうか、ライセンス的にちょっと心配なので、必要そうなコードを吟味して、独自のスタートアップルーチンを作るのがいいのかもしれない???