FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-V LLVM を用いたベンチマークビルドの方法

f:id:msyksphinz:20170325154038p:plain

前回、RISC-V LLVMをビルドしたが、実際にベンチマークプログラムをビルドするためにはどのようにすればよいのだろう。

まず、チュートリアルにのっとってみるのがよいだろう。Freedomプラットフォームの32bit RISC-Vプロセッサ向けにclangを用意する。

いろいろ調査したのだが、32bit対応のRISC-V clangをビルドするためには、現状のリポジトリに対してパッチを当てる必要がある。

RISC-V 32bit 対応Clangのビルド方法

github.com

RISC-V 32bit対応のClangはビルド方法としてはパッチを当てる必要がある。まずは普通通りriscv-llvmをチェックアウトする。

git clone https://github.com/msyksphinz/riscv-llvm.git riscv-llvm32
cd riscv-llvm32/
git submodule update --init

まずはclang向けのパッチをダウンロードしよう。

cd riscv-clang
wget https://github.com/riscv/riscv-llvm/files/893658/riscv-clang.patch.txt
patch -p1 < riscv-clang.patch.txt

次に、llvm向けのパッチをダウンロードする。

cd ../
wget https://github.com/riscv/riscv-llvm/files/893657/riscv-llvm.patch.txt
patch -p1 < riscv-llvm.patch.txt

ビルドを開始する。

cmake -DCMAKE_INSTALL_PREFIX=/opt/riscv32 -DLLVM_TARGETS_TO_BUILD="RISCV" ../
make && sudo make install

最後に、実際に使用するためにsysrootを設定しよう。以下のようにriscv32-unknown-elf-clangをリンクし、--sysrootとして設定するディレクトリを作成する。 RISCV環境は/home/msyksphinz/riscv32/に設定されているものとする。

export RISCV=/home/msyksphinz/riscv32/
cd ${RISCV}/bin/
ln -s /opt/riscv32/bin/clang riscv32-unknown-elf-clang
cd ${RISCV}
mkdir r32 && cd r32
ln -s ../riscv32-unknown-elf usr

これで、clangを利用する際に--sysroot=${RISCV}/r32と指定することでライブラリ等使用できるようになる。

次に、これを使ってベンチマークをビルドしてみよう。

FreedomプラットフォームのベンチマークプログラムにLLVM-Clangビルドを適用する

Freedomプラットフォームは、ベンチマークプログラムなどのビルドのためにテンプレートを持っている。

github.com

まずは、Dhrystoneのビルドからやってみよう。

freedom-e-sdkリポジトリのdhrystoneのディレクトリで、以下のように入力する。

make CC="riscv32-unknown-elf-clang --sysroot=/home/msyksphinz/riscv32/r32" dhry_1.o dhry_2.o dhry_printf.o dhry_stubs.o OPT=-O3
make

これは、まずはC言語で記述されているベンチマーク部分はclangでコンパイルする。次に、ライブラリ等のラッパーは単なるmakeを使ってgccでビルドする、最後にldを使ってclangで生成したobjectとリンクするという訳だ。

同じように、Coremarkのディレクトリでもビルドすることができる。

make CC="riscv32-unknown-elf-clang --sysroot=/home/msyksphinz/riscv32/r32" core_list_join.o core_main.o core_matrix.o core_portme.o core_state.o core_util.o OPT=-O3
make

RISC-V LLVMのビルドとインストール手順

RISC-VはLLVMにも対応しており、リポジトリgithubに公開されている。

github.com

ブランチがいくつか存在するが、 riscv-trunk を選択すること。これは最新のLLVMを追いかけているブランチだ。

リポジトリのチェックアウトとビルド

ビルドまでは非常に簡単だ。ただしLLVMのビルドは時間がかかるので注意すること。手元のVirtualBox(at CPUx4)でビルドしても1時間程度かかった。

git clone -b RISCV https://github.com/riscv/riscv-llvm.git
git checkout riscv-trunk
git submodule update --init
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/opt/riscv -DLLVM_TARGETS_TO_BUILD="RISCV" ../ $ make $ make install

上記でビルド完了だ。/opt/riscv/binにバイナリが配置されている。

追記。RISC-V LLVMを使ったベンチマークビルド方法

msyksphinz.hatenablog.com

Vivado 2017.1がリリースされていました

Vivado 2017.1がリリースされていた。

japan.xilinx.com

一応リリース情報を見てみよう。結構内容が盛りだくさんだ。

• Vivado IDE のユーザーインターフェイスを刷新。

まじで?

こんな感じだった。

f:id:msyksphinz:20170420231605p:plain

AXI Verification IP を導入
- SystemVerilog ベース、ライセンス不要
- AXI3、 AXI4、 および AXI4-Lite をサポー ト
Zynq-7000 VIP を導入 (上記の AXI VIP に基づ く )
- ライセンス不要、 SystemVerilog ベース

これ面白そうね。試してみたい。

消費電力で最適化された大型RAM用の新しい属性。
- 2のべき乗でないアドレスのRTLRAM用に使用するブロックRAMを最小限にすることが可能。
- ブロックRAM推論の改善点
- SDPモードのブロックRAMセルのカスケード接続をサポート
- 非対称ポート幅ブロックRAMのバイトライトイネーブルをサポート。

これも面白そう。

RISC-Vコア BOOM向けのCoremarkリポジトリの準備

BOOMコアの解析の続き。やはり途中で割り込みに飛んでしまう動作が気に食わないので、riscv-testsのリポジトリをForkしてCoremarkを追加してみようと思った。

まず、Coremarkのriscv-testsのディレクトリを解析してみよう。riscv-testsのbenchmarkディレクトリは以下のようになっている。

.
├── Makefile
├── common
│   ├── crt.S
│   ├── syscalls.c
│   ├── test.ld
│   └── util.h
...
├── mt-vvadd
│   ├── bmark.mk
│   ├── dataset.h
│   ├── mt-vvadd.c
│   ├── vvadd.c
│   └── vvadd_gendata.pl
├── multiply
│   ├── bmark.mk
│   ├── dataset1.h
│   ├── multiply.c
│   ├── multiply.h
│   ├── multiply_gendata.pl
│   └── multiply_main.c
├── qsort
...

つまり、中央のMakefileに共通の設定を記述して、個々のbmark.mkに個別の設定を追加するわけだ。早速Coremarkを追加してみよう。

github.com

coremarkのディレクトリを追加した。また、コンパイルオプションに最適化オプションを追加した。

├── coremark
│   ├── bmark.mk
│   ├── bmark.mk~
│   ├── core_list_join.c
│   ├── core_main.c
│   ├── core_matrix.c
│   ├── core_portme.c
│   ├── core_portme.h
│   ├── core_state.c
│   ├── core_util.c
│   ├── coremark.h
│   ├── ee_printf.c
│   ├── encoding.h
│   ├── syscalls.c
│   └── util.h
  • coremark/bmark.mk
FLAGS_STR = "$(PORT_CFLAGS) $(XCFLAGS) $(XLFLAGS) $(LFLAGS_END)"
RISCV_GCC_OPTS += -O3 -funroll-loops -fno-builtin $(PORT_CFLAGS) -I$(PORT_DIR) -I. -DFLAGS_STR=\"$(FLAGS_STR)\" -DITERATIONS=1
RISCV_LINK_OPTS += -O3 -funroll-loops -fno-builtin

これでCoremarkをコンパイルできるようになったが、他のベンチマークは落ちるようになってしまった。これは後で解析しなければならない。

make

riscv64-unknown-elf-gcc -T ./common/test.ld -I./../env -I./common -I./median -I./qsort -I./rsort -I./towers -I./vvadd -I./multiply -I./mm -I./dhrystone -I./spmv -I./mt-vvadd -I./mt-matmul -I./coremark  mm_main.o  mm.o  syscalls.o  crt.o -o mm.riscv -nostdlib -nostartfiles -ffast-math -lgcc -O3 -funroll-loops -fno-builtin
mm_main.o: 関数 `.L18' 内:
mm_main.c:(.text+0x3b0): `fabs' に対する定義されていない参照です
mm_main.c:(.text+0x3c4): `fabs' に対する定義されていない参照です
mm.o: 関数 `.L8' 内:
mm.c:(.text+0xe4): `fma' に対する定義されていない参照です
mm.c:(.text+0x104): `fma' に対する定義されていない参照です
mm.c:(.text+0x128): `fma' に対する定義されていない参照です
mm.c:(.text+0x144): `fma' に対する定義されていない参照です
mm.c:(.text+0x168): `fma' に対する定義されていない参照です
mm.o:mm.c:(.text+0x180): `fma' に対する定義されていない参照がさらに続いています
collect2: error: ld returned 1 exit status
mm/bmark.mk:28: ターゲット 'mm.riscv' のレシピで失敗しました
make: *** [mm.riscv] エラー 1

仕方がないのでCoremarkだけコンパイルする。

make coremark.riscv
...
riscv64-unknown-elf-gcc -T ./common/test.ld -I./../env -I./common -I./median -I./qsort -I./rsort -I./towers -I./vvadd -I./multiply -I./mm -I./dhrystone -I./spmv -I./mt-vvadd -I./mt-matmul -I./coremark  core_list_join.o  core_main.o  core_matrix.o  core_portme.o  core_state.o  core_util.o  ee_printf.o  syscalls.o  crt.o \
    -o coremark.riscv -nostdlib -nostartfiles -ffast-math -lgcc -O3 -funroll-loops -fno-builtin

無事にコンパイルできたので、BOOMに読み込ませて実行してみよう。まずはboomのディレクトリで、coremark.riscvにリンクを張る。

msyksphinz@msyksphinz-VirtualBox:~/work/rocket-chip/emulator$ pwd
/home/msyksphinz/work/rocket-chip/emulator
msyksphinz@msyksphinz-VirtualBox:~/work/rocket-chip/emulator$ ls -ltr output/coremark.riscv
lrwxrwxrwx 1 msyksphinz msyksphinz 83  4月 18 01:10 output/coremark.riscv -> /home/msyksphinz/work/rocket-chip/riscv-tools/riscv-tests/benchmarks/coremark.riscv

これでシミュレーションを実行すると、私のマシンでは約10分くらいでシミュレーションが完了した。いつも通り編集してパイプライントレースを生成してみる。

make CONFIG=BOOMConfig output/coremark.riscv.out
sed -ei '1d' output/coremark.riscv.out 
../boom/util/pipeview-helper.py -f output/coremark.riscv.out > coremark.trace.out
~/work/gem5/util/o3-pipeview.py -o coremark.pipe.out --color coremark.trace.out

それでもやはりいくつかは割り込みによるジャンプが入っていた。ただし、そこまでの量ではない。これ、いったい何なんだろうな?

f:id:msyksphinz:20170419011131p:plain

そして気になるCoremarkスコアは、start_time()=1162240000, stop_time()=1571200000となり、408960サイクル、CMK/MHzは2.44となった。そこまで高くないなあ?

やはりコンパイラGCCを使っているのがダメなんだろうか?Clangとか使ってみようかなあ。

RocketChip RISC-V実装RTLにてベンチマークを計測する(3. -O3によるCoremarkコンパイル)

ずいぶん大昔の記事を引っ張り出してきたが、RASの解析などを行うにあたり、BOOMの実装を調べたくて、BOOMの実性能がどうなっているのか調べたくなってきたので調査している。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

gem5を使えばBOOMのパイプライントレースの解析を行うことができる。これで自前でコンパイルしたCoremarkを解析して、BOOMの性能を解析したい。

-O3 -funroll-loops を加えたCoremarkのコンパイル

github.com

funroll-loopsと-O3を加えて、評価用のCoremarkプログラムをコンパイルした。RocketもBOOMも64bitで動作するし、自作ISSも64bitモードに対応しているのでこれで比較する。

start_time(), stop_time()の間の命令数

grep -e 80006a90 -e 80006aa0 coremark.sw.log
     15260:M:MBar:[0000000080006a90][P000080006a90] 00000033 : add        r00,r00,r00          r00=>0000000000000000 r00=>0000000000000000
    370114:M:MBar:[0000000080006aa0][P000080006aa0] 00000033 : add        r00,r00,r00          r00=>0000000000000000 r00=>0000000000000000

というわけで命令数的には35万命令程度なのだが、-funroll-loopsを加えると命令数が増えてしまった。不思議。

BOOM(2-way)での実行

BOOMで当該バイナリを実行して、同様にstart_time()stop_time()のサイクル数を解析した。

make CONFIG=BOOMConfig output/coremark.riscv.out
../boom/util/pipeview-helper.py -f output/coremark.riscv.out > cleaned_trace.out
/work/gem5/util/o3-pipeview.py -o pipeview.out --colorcleaned_trace.out

前の記事にも書いたが、output/coremark.riscv.outの先頭にある1行を削除しないと解析されないので注意。

f:id:msyksphinz:20170417000310p:plain

$ grep -e start_time -e stop_time coremark.dmp
0000000080006a90 <start_time>:
    80006a98:   1e02aa23                sw      zero,500(t0) # 8000dc88 <start_time_val>
0000000080006aa0 <stop_time>:
    80006aa8:   1e02a023                sw      zero,480(t0) # 8000dc84 <stop_time_val>

$ grep -e 80006a90 -e 80006aa0 pipeview.out
[..................f..di...cr....................................................]-(     1168480000) 0x0080006a90.0 add zero, zero, zero      [    619748]
[====================f==di=======================================================]-(     1168480000) 0x0080006aa0.0 -----add zero, zero, zero [    619752]
[=========================================================f======================]-(     1662480000) 0x0080006a90.0 -----add zero, zero, zero [   1284475]
[.....................................................................f..di...cr.]-(     1666800000) 0x0080006aa0.0 add zero, zero, zero      [   1291132]
[======================f==di=====================================================]-(     1671200000) 0x0080006a90.0 -----add zero, zero, zero [   1296453]
[========================f=======================================================]-(     1671200000) 0x0080006aa0.0 -----add zero, zero, zero [   1296457]

ちなみにpipeiew.outの見方だが、------アセンブリ命令の前についているのは、パイプラインフラッシュされる対象の命令なので無視。こう見ると、start_time()からstop_time()までのサイクル数は大よそ { \displaystyle
1666800-1168480 = 498320
} あれ、サイクル数的にはだいぶ増えたぞ?

途中のサイクルを見ていると、

[................................................................f..di..c.r......]-(     1279680000) 0x0080003690.0 lh a2, 0(s8)              [    764124]
[................................................................f..di...cr......]-(     1279680000) 0x0080003694.0 addw a4, s10, s6          [    764125]
[.................................................................f..di...cr.....]-(     1279680000) 0x0080003698.0 addiw a5, a5, 4           [    764126]
[.................................................................f..d.i...cr....]-(     1279680000) 0x008000369c.0 mulw s10, ra, s7          [    764127]
[==================================================================f==d==========]-(     1279680000) 0x00800036a0.0 -----addw t6, t6, a3      [    764128]
[==================================================================f==d==========]-(     1279680000) 0x00800036a4.0 -----mulw ra, t5, t0      [    764129]
[===================================================================f==d=========]-(     1279680000) 0x00800036a8.0 -----addw s7, t6, s10     [    764130]
[===================================================================f==d=========]-(     1279680000) 0x00800036ac.0 -----mulw a6, t2, a2      [    764131]
[====================================================================f==d========]-(     1279680000) 0x00800036b0.0 -----addw t4, s7, ra      [    764132]
[====================================================================f==d========]-(     1279680000) 0x00800036b4.0 -----addw t6, t4, a6      [    764133]
[=====================================================================f==d=======]-(     1279680000) 0x00800036b8.0 -----bne t3, a5, pc - 224 [    764134]
[======================================================================f==d======]-(     1279680000) 0x00800035d8.0 -----addw s8, a4, s6      [    764135]
[======================================================================f==d======]-(     1279680000) 0x00800035dc.0 -----slli s7, a5, 32      [    764136]
[=======================================================================f==d=====]-(     1279680000) 0x00800035e0.0 -----slli t2, a4, 32      [    764137]
[=======================================================================f==d=====]-(     1279680000) 0x00800035e4.0 -----addiw t5, a5, 1      [    764138]
[========================================================================f==d====]-(     1279680000) 0x00800035e8.0 -----srli a3, s7, 32      [    764139]
[========================================================================f==d====]-(     1279680000) 0x00800035ec.0 -----srli s10, t2, 32     [    764140]
[=========================================================================f==d===]-(     1279680000) 0x00800035f0.0 -----addw a2, s8, s6      [    764141]
[=========================================================================f==d===]-(     1279680000) 0x00800035f4.0 -----slli ra, t5, 32      [    764142]
[==========================================================================f=====]-(     1279680000) 0x00800035f8.0 -----slli a4, s8, 32      [    764143]
[==========================================================================f=====]-(     1279680000) 0x00800035fc.0 -----addiw t0, a5, 2      [    764144]
[.............f..di...cr.........................................................]-(     1279760000) 0x0000000800.0 j pc + 0x3c               [    764145]
[...............f.......di...cr..................................................]-(     1279760000) 0x000000083c.0 csrw dscratch, s0         [    764146]

RISC-Vは通常命令領域は0x0080000000から始まるのだが、mulw命令を実行後に突然0x0000000800に飛んでいたりしている。これは何だろう? それはともかく、現在のCoremarkの解析において気になっている部分を見てみよう。

load –> arith –> storeのループ

例えば、

loop 
lw     a0, 0(a1)
add    a0, a0, 1
sw     a0, 0(a3)
...
bltz   loop

みたいなループがある場合、swのメモリオーダを厳密に守ると、次のループのロードがswよりも先に出せず、パイプラインを活用することができない。 これはBOOMではどのように実行しているだろう。

f:id:msyksphinz:20170417001825p:plain

例えばこういうループだな。lbuとsbに依存関係があって、次のループのlbuの実行を遅らせていると確実にパイプラインに影響があるのだが、BOOMではほとんど影響なくストールを生じさせていない。 このあたりは、ストアバッファの中を探索して、無関係なアドレスであれば先にロードを通すなどの処理が行われていると予想する。

ランニング用のイヤホンを買い替えた

ランニング時に音楽を聴くため、Bluetoothのイヤホンの非常に安いものを購入していたのだが、何度か使用しているうちに問題が分かってきた。

msyksphinz.hatenablog.com

www2.elecom.co.jp

このイヤホンは非常に軽くて良いのだが、コントローラの部分が左右非対称の場所に配置されており、走っている際にそれが大きく揺れて位置がずれる。 これが気になって集中できなくなってしまい、何度も位置を直さなければならない。

これを防ぐために、今度は明確なコントローがついていないイヤホンを購入してみた。

まだランニングには使用していないが、付け心地は悪くなく、また音質も問題ない。

これを使って今度から走ってみよう。

プロセッサの高速化技法: リターンアドレススタックの実装方法検討

リターンアドレススタック(Return Address Stack: RAS)は、関数呼び出しなどの命令(Call命令)が実行された場合、その関数の戻り先をあらかじめスタックに記憶しておき、Return文が実行された場合にスタックから戻りアドレスを取り出し、 そこからフェッチを行う機構である。

f:id:msyksphinz:20170413234223p:plain

この場合、実際にどのような機構を実装すればよいかという話になるが、RASへの格納はCall命令がコミットされた時点でよかろう。 そうして、フェッチが行われる毎にReturn命令かどうかを判定し、Return命令ならばRASからPopするという方式になる。

f:id:msyksphinz:20170413235004p:plain

ただし、この実装をしていて問題を発見した。 すぐに戻ってくるような関数でテストしていると、どうやらCall命令がコミットする前に(分岐予測のため)Return命令がフェッチされてしまい、RASに積む前にPopが必要となってしまう。 実装は簡単だが、これで背右脳を出すのは難しいのだろうか。

もう一方の実装方法として、Call命令がコミットされる前、つまり命令をフェッチしてきた段階でRASにスタックし、Popと同じステージで制御する。 これも実装としては簡単だが、やはり投機的な命令まで使って判定してしまうため、スタックを汚してしまい、性能に影響が出る。

f:id:msyksphinz:20170414004627p:plain

一般的なRASの実装はどのようになっているのだろう?調査する必要がある。