この記事は ハードウェア開発、CPUアーキテクチャ Advent Calendar 2016 - Qiita の14日目の記事です。
Advent-Calendarを埋めてくれるかた、今からでも募集中です!是非参加してください! 僕一人では、クオリティのある記事を続けられそうにありません。。。(弱音)
- 1. Rocket ChipをRTLで動かすには
- 2. RocketChip用Coremarkのコンパイル
- 3. コンパイルおよびバイナリファイルの確認
- 4. RocketChipのRTLシミュレーションを実行する
- 5. 計測結果からCoremark値を計算する
- 6. おまけ
1. Rocket ChipをRTLで動かすには
Rocket Chipは様々な環境で動作させることができるが、有料のEDAツールを使わずにシミュレーションするとしたら、Verilatorでシミュレーションするのが良い。
まず、Rocket Chipをリポジトリをクローンし、DefaultのRocketChipを構成してRTLシミュレーションの準備をする。詳細は、Rocket ChipのリポジトリのReadmeを参照して欲しい。
ツールセットが整っているならば、emulatorディレクトリに移動してmakeを実行するだけで良い。
cd emulator
make
ちなみに上記のリポジトリには、RISC-V用のテストセットが入っており、このなかに命令テスト用のリグレッションテストと、いくつかのベンチマークプログラムを動作させるための環境が構成されている。これらを参考に、Rocket用のCoremarkをビルドするためにはどのようにすれば良いのか考えてみる。
riscv-testsに含まれるベンチマークプログラムのコンパイル方法
まずは参考のために、riscv-testsに含まれているベンチマークプログラムはどのようにコンパイルされているのか調べてみよう。まずは当該ディレクトリに移動してコンパイルしてみる。
cd rocket-chip/riscv-tools/riscv-tests/benchmarks/ make $ make riscv64-unknown-elf-gcc -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -DPREALLOCATE=1 -DHOST_DEBUG=0 \ -c -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 ./median/median_main.c -o median_main.o riscv64-unknown-elf-gcc -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -DPREALLOCATE=1 -DHOST_DEBUG=0 \ -c -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 ./median/median.c -o median.o riscv64-unknown-elf-gcc -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -DPREALLOCATE=1 -DHOST_DEBUG=0 \ -c -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 ./common/syscalls.c -o syscalls.o riscv64-unknown-elf-gcc -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -DPREALLOCATE=1 -DHOST_DEBUG=0 -D__ASSEMBLY__=1 \ -c -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 ./common/crt.S -o crt.o 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 median_main.o median.o syscalls.o crt.o -o median.riscv -nostdlib -nostartfiles -ffast-math -lgcc riscv64-unknown-elf-objdump --disassemble-all --disassemble-zeroes --section=.text --section=.text.startup --section=.data median.riscv > median.riscv.dump ...
これは、makeのログの中からmedian.riscvをコンパイルするために必要なログを抜き出したものだ。これらを見ていくと、以下のような特徴が分かる。
- 各ソースのコンパイルにはgccを利用する。また、いくつかのプションを指定している。
- 共通ライブラリとして、syscalls.cとcrt.Sを利用している。また、crt.Sのコンパイルにもgccを利用している(これはおそらく内部でマクロを使用しているため、cppを通過させたい意図があると思われる)。
- リンクにもgccを利用している。またリンカスクリプトとして
./common/test.ld
を使用しており、crtにはcrt.Sを使用し、デフォルトのcrtは利用しない(これは-nostartfiles
を追加していることから分かる)。
このオブジェクトファイルをダンプしたのが、median.riscv.dumpだ。これを見てみよう。
less median.riscv.dump median.riscv: file format elf64-littleriscv Disassembly of section .text: 0000000080001048 <median>: 80001048: 00251793 slli a5,a0,0x2 8000104c: 00f607b3 add a5,a2,a5 80001050: 00062023 sw zero,0(a2) 80001054: fe07ae23 sw zero,-4(a5) 80001058: 00200793 li a5,2 8000105c: 06a7d263 ble a0,a5,800010c0 <median+0x78> 80001060: ffd5071b addiw a4,a0,-3 80001064: 02071713 slli a4,a4,0x20 80001068: 02075713 srli a4,a4,0x20 8000106c: 00270713 addi a4,a4,2 80001070: 00271713 slli a4,a4,0x2 80001074: 00460793 addi a5,a2,4 80001078: 00e60633 add a2,a2,a4 8000107c: 01c0006f j 80001098 <median+0x50> 80001080: 02a74863 blt a4,a0,800010b0 <median+0x68> 80001084: 04d54063 blt a0,a3,800010c4 <median+0x7c> 80001088: 00a7a023 sw a0,0(a5) 8000108c: 00478793 addi a5,a5,4 80001090: 00458593 addi a1,a1,4 80001094: 02f60663 beq a2,a5,800010c0 <median+0x78> 80001098: 0005a683 lw a3,0(a1) ...
テキスト領域は8000_0000から始まっているようだ。これは./common/test.ld
によって制御されている。
さて、ここまで分かれば実装の方針は分かってきた。Coremarkのコンパイルを実行してみよう。
2. RocketChip用Coremarkのコンパイル
まず、Coremarkのアーカイブを展開する。本当はいつも私が利用しているMIPS移植用のコンパイルセットを移植しても良いのだが、ここは一からRISC-V用Coremarkの環境を構築してみよう。
RISC-V Rocket用のコンパイルディレクトリを用意する
まず、ベースとなるコンフィグレーションをコピーして、RocketChip用に改造しよう。今回はbarebonesのディレクトリから移植する。
cp barebones barebones_rocket_m64
ちなみに、Coremarkをコンパイルするときは、どのコンフィグレーションを利用するか指定しなければならない。cleanの時も必要なので注意。
make PORT_DIR=barebones_rocket_m64 clean make PORT_DIR=barebones_rocket_m64
barebones_rocket_m64/core_portme.makの改造
core_portme.makには、コンパイル時のオプションや定義などを指定する領域だ。上記のテストパタンのコンパイル手順を見るに、gas,ldなどを使わずに、全てgccを使った方が良さそうだ。 以下のようにcore_portme.makを変更する。
2a3,6 > GCC_TARGET=riscv64-unknown-elf > MACHINE_TARGET= > 8c12 < CC = gcc --- > CC = $(GCC_TARGET)-gcc 11c15 < LD = gld --- > LD = $(GCC_TARGET)-gcc 14c18 < AS = gas --- > AS = $(GCC_TARGET)-gcc 17c21,22 < PORT_CFLAGS = -O0 -g --- > PORT_CFLAGS = -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -DPREALLOCATE=1 -DHOST_DEBUG=0 -D__riscv_xlen=64 > 27,28c32,33 < LFLAGS = < ASFLAGS = --- > LFLAGS = -nostartfiles -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -T$(PORT_DIR)/test.ld > ASFLAGS = -c -D__riscv_xlen=64 $(MACHINE_TARGET) -I$(PORT_DIR) -I.
ソースコードの修正
#error
で始まる場所は不要だし、面倒なのでとりあえずコメントアウトした。
barebones_rocket_m64/core_portme.c 34: // #error "You must implement a method to measure time in barebones_clock()! This function should return current time.\n" 101: // #error "Call board initialization routines in portable init (if needed), in particular initialize UART!\n" barebones_rocket_m64/ee_printf.c 581: // #error "You must implement the method uart_send_char to use this file!\n";
ユーテリティファイルの移植
RocketChipでシミュレーションを行うためには、crt.S(スタートアップファイル)とsyscalls.c、util.hをビルドディレクトリにコピーする。以下のファイルをbarebones_rocket_m64ディレクトリにコピーした。
- ./riscv-tools/riscv-tests/benchmarks/common/crt.S
- ./riscv-tools/riscv-tests/benchmarks/common/syscalls.c
- ./riscv-tools/riscv-tests/benchmarks/common/util.h
- ./riscv-tools/riscv-tests/benchmarks/common/test.ld
さらに、コンパイル対象に上記のファイルを追加するために、core_portme.makを変更する。crt.Sをコンパイル対象にするために、%.s
を%.S
に変更しているが、これはcrt.S
をcrt.s
に変更しても良い気がする。
> # Flag: PORT_OBJS > # Port specific object files can be added here > PORT_OBJS = $(PORT_DIR)/core_portme$(OEXT) $(PORT_DIR)/ee_printf$(OEXT) $(PORT_DIR)/syscalls$(OEXT) $(PORT_DIR)/crt$(OEXT) > PORT_CLEAN = $(PORT_DIR)/*$(OEXT) *$(OEXT) > 36c46,47 < PORT_SRCS = $(PORT_DIR)/core_portme.c $(PORT_DIR)/ee_printf.c --- > # PORT_SRCS = $(PORT_DIR)/core_portme.c $(PORT_DIR)/ee_printf.c > PORT_SRCS = $(PORT_DIR)/core_portme.c $(PORT_DIR)/ee_printf.c $(PORT_DIR)/syscalls.c $(PORT_DIR)/crt.S 38c49 < vpath %.s $(PORT_DIR) --- > vpath %.S $(PORT_DIR) 58c69 < $(OPATH)$(PORT_DIR)/%$(OEXT) : %.s --- > $(OPATH)$(PORT_DIR)/%$(OEXT) : %.S 65c76,81 < port_pre% port_post% : --- > port_pre% : > > port_postbuild : > $(GCC_TARGET)-objdump -D coremark$(EXE) > coremark.dmp > $(GCC_TARGET)-objcopy -F srec coremark$(EXE) coremark.srec > $(GCC_TARGET)-nm coremark$(EXE) > coremark.nm
最後にtest.ldの内容を変更する。crt.oの場所が違うのでこれを書き換えるだけだ。
25c25 < .text.init : { crt.o(.text) } --- > .text.init : { ./barebones_rocket_m64/crt.o(.text) } 51c51 < crt.o(.tdata.begin) --- > ./barebones_rocket_m64/crt.o(.tdata.begin) 53c53 < crt.o(.tdata.end) --- > ./barebones_rocket_m64/crt.o(.tdata.end) 58c58 < crt.o(.tbss.end) --- > ./barebones_rocket_m64/crt.o(.tbss.end)
3. コンパイルおよびバイナリファイルの確認
下記でコンパイル可能だ。また、同時にcoremark.dmp
, coremark.nm
, coremark.srec
も出力されるように細工をしている。また、通常はイタレーション数を十分取って計測するのだが、今回はRTLシミュレーションであるので、短く終了させるためにITERATIONS=1
を設定する。
make ITERATIONS=1 PORT_DIR=barebones_rocket_m64
計測ポイントはどこか?
Coremarkのスコアは、関数start_time()
からstop_time()
までの時間を計測すれば良い。このアドレスはどこだろう?あらかじめ調べておこう。
grep -e start_time$ -e stop_time$ coremark.nm 0000000080002a1c T start_time 0000000080002a28 T stop_time
4. RocketChipのRTLシミュレーションを実行する
続いて、RocketChipのディレクトリに戻ってRTLシミュレーションを実行してみよう。rocket-chip/emulator
に移り、outputディレクトリにCoremarkを登録しよう。
以下のように、Coremarkのバイナリのリンクを張っておく。
~/rocket-chip/emulator/output$ ls -lt coremark.rocket lrwxrwxrwx 1 vagrant vagrant 64 Dec 13 12:29 coremark.rocket -> /home/vagrant/work/benchmarks/archive/coremark_v1.0/coremark.bin
また、デフォルトではシミュレーション時間が長すぎるので、細工をする。
diff --git a/Makefrag b/Makefrag index 4a64558..1729917 100644 --- a/Makefrag +++ b/Makefrag @@ -35,7 +35,8 @@ ifneq ($(which_disasm),) disasm := 3>&1 1>&2 2>&3 | $(which_disasm) $(DISASM_EXTENSION) > endif -timeout_cycles = 100000000 +# timeout_cycles = 100000000 +timeout_cycles = 1000000
emulatorディレクトリに戻り、シミュレーションを実行しよう。
make output/coremark.rocket.out ./emulator-rocketchip-DefaultConfig +max-cycles=1000000 +verbose output/coremark.rocket 3>&1 1>&2 2>&3 | /home/vagrant/riscv/bin//spike-dasm > output/coremark.rocket.out && [ $PIPESTATUS -eq 0 ]
無事に終了したようだ。計測ポイントを見る前に、有効な命令のみ抽出したい。このためには、ログ内にある[1]
が付加されている行だけ抽出しよう。そして、ログ中からstart_time()
、stop_time()
の場所を抽出した。
grep "\[1\] pc" output/coremark.rocket.out > output/coremark.rocket.inst grep -e "\[0080002a1c\]" -e "\[0080002a28\]" output/coremark.rocket.inst C 0: 236739 [1] pc=[0080002a1c] W[r15=0000000080005a1c][1] R[r 0=0000000000000000] R[r 0=0000000000000000] inst=[00003797] auipc a5, 0x3 C 0: 718511 [1] pc=[0080002a28] W[r15=0000000080005a28][1] R[r 0=0000000000000000] R[r 0=0000000000000000] inst=[00003797] auipc a5, 0x3
5. 計測結果からCoremark値を計算する
Coremarkのスコアの計算方法は、基本的にある特定時間内に何回Iterationを繰替えすことが出来るかということを測定している。つまり、Higher is Betterの関係だ。
今回は1回だけIterationを繰替えした時のサイクル数を計測したので、その逆数で分かるはずだ。/MHzということで、1000000を掛ける。
カタログスペックは?
BOOMの資料によると、RocketChipのCoremark/MHzは2.32だそうだ。少し大きな誤差があるなあ。
- The Berkeley Out-of-Order Machine (BOOM): An IndustryCompetitive, Synthesizable, Parameterized RISC-V Processor
http://digitalassets.lib.berkeley.edu/techreports/ucb/text/EECS-2015-167.pdf
6. おまけ
ちなみに、ITERATIONS=5
で計測した結果がこちら。
make ITERATIONS=5 PORT_DIR=barebones_rocket_m64 ... grep -e "\[0080002a1c\]" -e "\[0080002a28\]" output/coremark.rocket.inst C 0: 237504 [1] pc=[0080002a1c] W[r15=0000000080005a1c][1] R[r 0=0000000000000000] R[r 0=0000000000000000] inst=[00003797] auipc a5, 0x3 C 0: 2643785 [1] pc=[0080002a28] W[r15=0000000080005a28][1] R[r 0=0000000000000000] R[r 0=0000000000000000] inst=[00003797] auipc a5, 0x3
Coremark/MHzとしてはITERATIONS=1
とほぼ一緒だなあ。