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

更新


公開メモ

概要

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 での作業

プロジェクトの作成

new-project.png [File]-[Project]-[New]

project-name.png 適当に名前を付ける

project-type.png RTL プロジェクトを選ぶ

project-add-source.png Block Design をトップにするのでここでは何もしない

project-add-xdc.png 制約ファイルを1つ作っておく

project-select-kintex.png 対象とする FPGA を選択する

あ、本当は xc7k160tfbg484-1 を選んだのにキャプチャした図は違うのを選んでるときのものだった。。。

project-finish.png 以上でプロジェクト作成できた

ブロック図の作成

こちらが完成形:

block-diagram-final.png

  • 入力端子として
    • クロック(差動入力)
    • リセット(負論理)
    • スイッチ2つ
    • UART-RX (USB-Serial 変換器の TX 端子と繋ぐ)
  • 出力端子として
    • LED 2つ
    • UART-TX (USB-Serial 変換器の RX 端子と繋ぐ)

を持っており、それらが適切に IP に繋がっている。

以下が手順:

create-block-design.png Block Design を新規作成

block-design-name.png プロジェクトに1つしか作らないなら名前は適当でも

add-microblaze.png Add IP ボタンから MicroBlaze を追加する

add-uart.png 同様にシリアル通信用の UART モジュール を追加

add-gpio.png 同様に LED やスイッチの制御のための GPIO モジュール を追加

regenerate-layout.png Regenerate Layout ボタンで配置を調整

上部の Run Block Automation を押し、MicroBlaze についていろいろ設定する

block-automation-microblaze.png

OK すると以下が自動的に追加される。

  • クロックジェネレータ (Clocking Wizard)
  • リセットコントローラ (Processor System Reset)
  • ローカルメモリ (local memory)
  • AXI ハブ (AXI Interconnect)
  • 割り込みコントローラ (AXI Interrupt Controller)

block-automation-result.png

Clocking Wizard をダブルクリックして

adjust-clocking-wizard.png 入力周波数を 200MHz に変更

adjust-clocking-wizard2.png リセットを反転入力とする

GPIO をダブルクリックして

adjust-gpio.png 下位2ピンを出力にし、割り込みを有効にする

add-slice.png Slice を追加しダブルクリック

adjust-slice.png From を 1 にする

add-concat.png Concat を追加しダブルクリック

adjust-concat.png 28bit, 2bit, 2bit のポートを用意する

add-const.png Const を追加しダブルクリック

adjust-const.png 幅を 28bit, 値をゼロに設定

GPIO モジュールの GPIO バスをクリックして開き、slice, concat, const と結ぶ。
また、Slice の出力、Concat の2本の入力について、右クリックから Make External して外部端子を出す。

gpio-connection.png

interrupt-connection.png UART, GPIO からの割り込み線を INTC へ繋がる Concat へ接続

uart-make-external.png UART の UART 端子を右クリックして Make External

clock-make-external.png Clocking Wizard の入力端子を Make External

ext_reset_in にも繋ぐ

左上の Run Connection Automation をクリック

connection-automation.png

これで UART と GPIO が AXI ハブに繋がる。

Regenerate Layout して完成。

block-diagram-final.png

アドレスを確認

Diagram のとなりのタブに Address Editor がある。

ここで、それぞれの IP のベースアドレスを確認できる。
必要に応じて変更することもできる。

address-editor.png

ブロック図から HDL を生成する

上図の左下で Generate Block Design を押すと以下のダイアログが現れる。

generate-block-design.png Global を選ぶ

design_1 上で右クリックして Create HDL Wrapper を選択。

create-hdl-wrapper.png

扱いは Vivado に任せる。

create-hdl-wrapper2.png

design_1_wrapper.v が自動生成され、以降、必要に応じて自動更新される。

generated-wrapper.png

Implement してみる

この時点で Implement が可能になっている。

implementation-success.png

まだ xdc ファイルには何の制約も追加していないのに「タイミング制約が満たされている」というような表示があるのは、Clocking Wizard に設定した入力周波数 200MHz で全体に対してクロック周波数制約がかかっているため。

[Open Implemented Design]-[Report Timing Summary] すると、

report-timing-summary.png

implementation-timing-result.png

Setup, Hold, Pulse Width とも Slack (余裕) の値は正になっていて、タイミング制約を満たしていることを確認できる。そして Clock Summary で各クロックに正しくタイミング制約がかかっていることを確認できる。

clock-summary.png

注意が必要なのは、[Open Synthesized Design] から [Report Timing Summary] を見ると、

synthesis-timing-result.png

のように、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] を使って情報をエクスポートする。

export-hardware.png

include-bitstream.png include bitstream にチェックを入れる

以下に見るように、MicroBlaze 用のソフトウェアは WebPack 版の Xilinx SDK では開発できないので、 実はこの作業は無意味であった。

EDK を持っていて、SDK でソフト開発ができる場合にはこの手順で正しいはず。

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 バスと共に使う場合には

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"

スタートアップコードが足りないみたい???

上記の手順で一見動いているように見えたのだけれど、動作が不安定だ。

実は上で不足していると怒られた libxil のソースコードは Xilinx SDK に付いているので、

C:\Xilinx\SDK\2018.2\data\embeddedsw\lib\bsp\standalone_v6_7

不足するヘッダーファイル等を補うことでこれをそのまま上記環境でコンパイルして使えるっぽい。

ただ、そうしたときに完動するコードが、上記のように完全にベアな状態で走らせたときにうまく動かないケースがあった。

ここに付いてきたコードをそのままコンパイルして使っていいものかどうか、ライセンス的にちょっと心配なので、必要そうなコードを吟味して、独自のスタートアップルーチンを作るのがいいのかもしれない???

質問・コメント





*1 厳密には後段のゲートへクロックよりも次のデータが早く到達してしまっている

Counter: 17470 (from 2010/06/03), today: 4, yesterday: 0