FPGA開発日記

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

RISC-V用LinuxのビルドとSpikeによるシミュレーション(vmlinuxとroot.binの解析)

f:id:msyksphinz:20170325154038p:plain

前回、SpikeシミュレータでRISC-V版Linuxをビルドしようとしたのだが、なぜかエラーが発生して起動しなかった。 どこに原因があるのかさっぱりわからないため、まずはルートファイルシステムとvmlinuxを切り替えて起動実験をしてみる。

lowRISCには、すでにプレビルドされたLinuxのイメージとルートファイルシステムが存在する。

$ cd ./fpga-zynq/zedboard/fpga-images-zedboard/riscv
$ ls -1
root.bin
vmlinux

これらを使って、Spikeでシミュレーションをしてみる。

その前に、bblのリビルドをしておこう。

cd ~/work/riscv-tools/
CC= CXX= build_project riscv-pk --prefix=$RISCV --host=riscv64-unknown-elf --with-payload=/home/msyksphinz/work/fpga-zynq/zedboard/fpga-images-zedboard/riscv/vmlinux

シミュレーションしてみる。

$ spike +disk=root.bin bbl vmlinux
$ spike +disk=root.bin bbl /home/msyksphinz/work/linux-4.6.2/vmlinux
              vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
                  vvvvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr       vvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr      vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr      vvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr       vvvvvvvvvvvvvvvvvvvvvv
rr                vvvvvvvvvvvvvvvvvvvvvv
rr            vvvvvvvvvvvvvvvvvvvvvvvv      rr
rrrr      vvvvvvvvvvvvvvvvvvvvvvvvvv      rrrr
rrrrrr      vvvvvvvvvvvvvvvvvvvvvv      rrrrrr
rrrrrrrr      vvvvvvvvvvvvvvvvvv      rrrrrrrr
rrrrrrrrrr      vvvvvvvvvvvvvv      rrrrrrrrrr
rrrrrrrrrrrr      vvvvvvvvvv      rrrrrrrrrrrr
rrrrrrrrrrrrrr      vvvvvv      rrrrrrrrrrrrrr
rrrrrrrrrrrrrrrr      vv      rrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrr          rrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrr      rrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrr  rrrrrrrrrrrrrrrrrrrrrr

       INSTRUCTION SETS WANT TO BE FREE

うーん、途中で止まってしまった。何が悪いのかまだよく分からない。

RISC-V用LinuxのビルドとSpikeによるシミュレーション(Spikeによるシミュレーション→失敗)

f:id:msyksphinz:20170325154038p:plain

msyksphinz.hatenablog.com

前回、Linuxビルド用のRISC-V GCCの構築が終わったので、次にLinux本体のビルドとシミュレータによる実行をしてみたい。

github.com

RISC-V用Linuxの構築

まずはRISC-V用Linuxの作業リポジトリと、Linux本体を取得してくる。

curl -L https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.6.2.tar.xz | tar -xJ
cd linux-4.6.2
git init
git remote add -t master origin https://github.com/riscv/riscv-linux.git
git fetch
git checkout -f -t origin/master
git remote set-branches --add origin linux-4.6.y-riscv
git fetch

make ARCH=riscv defconfig
make -j4 ARCH=riscv vmlinux

これでLinuxの本体であるvmlinuxが生成される。生成される場所を今回は /home/msyksphinz/work/linux-4.6.2/vmlinuxとしている。

BusyBoxのビルド

github.com

BusyBox - Wikipedia

BusyBoxというのは、Linuxの一連のコマンド群をまとめたものだ。 私もあまり詳細は知らないのだが、MS-DOSのcmd.exeみたいなものだろうか。

特定用途のLinuxディストリビューションや組み込みシステムに適しており、「組み込みLinuxの十徳ナイフ」とも呼ばれている。

cd ~/work/riscv-tools/
curl -L http://busybox.net/downloads/busybox-1.21.1.tar.bz2 | tar -xj
cd busybox-1.21.1
make allnoconfig

make -j

これにより、busyboxが生成される。

ls -l
...
drwxr-xr-x  5 msyksphinz msyksphinz  4096  325 14:33 shell
drwxr-xr-x  3 msyksphinz msyksphinz  4096  325 14:33 util-linux
drwxr-xr-x  2 msyksphinz msyksphinz  4096  325 14:33 sysklogd
drwxr-xr-x  2 msyksphinz msyksphinz 16384  325 14:33 libbb
-rw-rw-r--  1 msyksphinz msyksphinz 15998  325 14:33 busybox_unstripped.out
-rw-rw-r--  1 msyksphinz msyksphinz 37906  325 14:33 busybox_unstripped.map
-rwxrwxr-x  1 msyksphinz msyksphinz 13840  325 14:33 busybox_unstripped
-rwxrwxr-x  1 msyksphinz msyksphinz 10344  325 14:33 busybox

Linux用ルートファイルシステムの構築

Linuxのルートファイルシステムを構築するために、イメージファイルの作成を行う。

cd ~/work/riscv-tools/root_bin/
dd if=/dev/zero of=root.bin bs=1M count=64
mkfs.ext2 -F root.bin
mkdir mnt
sudo mount -o loop root.bin mnt

cd mnt
sudo mkdir -p bin etc dev lib proc sbin sys tmp usr usr/bin usr/lib usr/sbin

sudo cp ~/work/riscv-tools/busybox-1.21.1/busybox bin
curl -L https://www.ocf.berkeley.edu/~qmn/linux/linux-inittab > etc/inittab

linux-inittabgithubの説明の場所には存在していなかったので、別の場所から取ってきた。

busyboxをリンクする。

cd sbin
ln -s ../bin/busybox init

cd ../../
sudo umount mnt

bblの再構築

Spikeで完成したLinuxをシミュレーションする前に、bblの再構築を行わなければならない。vmlinuxの場所を指定してriscv-pkを再構築する。

cd ~/work/riscv-tools/
export RISCV=/home/msyksphinz/riscv
. build.common
CC= CXX= build_project riscv-pk --prefix=$RISCV --host=riscv64-unknown-elf --with-payload=/home/msyksphinz/work/linux-4.6.2/vmlinux

Spikeでのシミュレーション

これでSpikeを走らせてみたのだが、どうも起動途中に失敗してしまった。

spike +disk=root_bin/root.bin bbl ../linux-4.6.2/vmlinux

f:id:msyksphinz:20170325161631p:plain

...
[    0.040000] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[    0.040000] CPU: 0 PID: 1 Comm: swapper Not tainted 4.6.2-00044-gdf91b31 #1
[    0.040000] Call Trace:
[    0.040000] [<ffffffff80012fc8>] walk_stackframe+0x0/0xc8
[    0.040000] [<ffffffff80056e90>] panic+0xec/0x20c
[    0.040000] [<ffffffff800011d8>] mount_block_root+0x248/0x328
[    0.040000] [<ffffffff8000292c>] ksysfs_init+0x10/0x40
[    0.040000] [<ffffffff80001488>] prepare_namespace+0x148/0x198
[    0.040000] [<ffffffff80000da0>] kernel_init_freeable+0x1c8/0x200
[    0.040000] [<ffffffff8021b160>] rest_init+0x80/0x84
[    0.040000] [<ffffffff8021b174>] kernel_init+0x10/0x11c
[    0.040000] [<ffffffff8021b160>] rest_init+0x80/0x84
[    0.040000] [<ffffffff80011c0c>] ret_from_syscall+0x10/0x14
[    0.040000] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
: msyksphinz@msyksphinz-pc:~/work/riscv-tools$ ^C

解析の必要あり。

RISC-V Linuxビルド用GCCの構築

f:id:msyksphinz:20170325154038p:plain

RISC-V用のLinuxは、以下のリポジトリで公開されている。

github.com

f:id:msyksphinz:20170325153123p:plain

Compile and install RISC-V cross-compiler · lowRISC

RISC-V用のLinuxには、riscv64-unknown-linux-gnu-gcc が必要だ。このビルドには、riscv-gnu-toolchainフォルダでのリビルドが必要になる。

# set up the RISCV environment variables
export RISCV=/home/msyksphinz/riscv

cd ~/work/riscv-tools/riscv-gnu-toolchain
# ignore if build already exist
mkdir build
cd build
../configure --prefix=$RISCV
make -j linux

一度buildディレクトリが生成され、通常用のriscv64-unknown-elf-gccが生成されている場合は一度ファイルをすべて削除しておくこと。

cd build
rm -rf *

これで、$RISCVディレクトリにriscv64-unknown-linux-gnu-gccが生成される。これでRISC-V Linuxの構築が行える。

Zephyr-OSがRISC-Vをサポート(QEMUでの試行)

少し前に、RISC-VのMLにてZephyr-OSがRISC-Vをサポートしたというアナウンスがあった。

Zephyr-OSは、Linux Foundationが発表した、モバイル用途向けの小規模のオペレーティングシステムだ。

Home | Zephyr Project

Zephyr (operating system) - Wikipedia

f:id:msyksphinz:20170324003639p:plain

Wikipediaによると、Zephyr-OSは以下のような特徴があるということだ。

RISC-V QEMUでZephyr-OS を動作させる

ZephyrのRISC-V移植版は以下で入手できる。

github.com

手順としてはReadmeに書いてある通りで動作させることができる。ただし、事前にriscv32のツールチェインと、riscv-qemuをビルドしておくこと。

  • Compiling zephyr-riscv for the qemu_riscv32 board

github.com

以下を実行することで、とりあえずQEMU中でZephyrを動作させることはできた。

cd zephyr-riscv/samples/philosophers
make BOARD=qemu_riscv32 run

f:id:msyksphinz:20170324004627p:plain

なんか動き始めた。哲学者の食事問題を動作させているのかな?

詳細については、今後勉強していこう。

HiFive1のパフォーマンスカウンタについて

前回Coremarkの測定を行ったし、その前は各命令のレイテンシを測定した。 このとき、実際にプログラムのサイクル数を測定しているわけだが、これはどのようにして実現しているのだろう。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

まず各命令のレイテンシ、スループット測定には、以下のようなサイクル取得ルーチンを使用している。

#define rdmcycle(x)  {                                 \
    uint32_t lo, hi, hi2;                              \
    __asm__ __volatile__ ("1:\n\t"                     \
                          "csrr %0, mcycleh\n\t"       \
                          "csrr %1, mcycle\n\t"        \
                          "csrr %2, mcycleh\n\t"       \
                          "bne  %0, %2, 1b\n\t"                 \
                          : "=r" (hi), "=r" (lo), "=r" (hi2)) ; \
    *(x) = lo | ((uint64_t) hi << 32);                          \
  }

これはつまり、

  1. 現在のmcyclehの値を取得し、hiに代入する。
  2. 現在のmcycleの値を取得し、loに代入する。
  3. 現在のmcyclehの値を取得し、hi2に代入する。
  4. hiとhi2を比較し、不一致ならば1.から繰り返す。

これはmcyclehがオーバフローした場合を考慮したルーチンとなっている。

例えば、

1.を実行した時点で{mcycleh,mcycle}=0x0000_0010_ffff_ffff 2.を実行した時点で{mcycleh,mcycle}=0x0000_0011_0000_0000

となった場合、最後の計算で{mcycleh,mcycle}=0x0000_0011_ffff_ffff となってしまい、大幅にサイクル数が狂ってしまう。 これを防ぐために、mcyclehを2回ロードし、変化していないことを確認してからサイクル数を計算するという訳だ。

32bit版RISC-V (HiFive1) プロセッサでのサイクルカウント方法

計算方法としては難しいけれども、32bit版のRISC-Vでは上記ではそのまま計測できない。そこで、サイクルカウンタのHiレジスタとLoレジスタを別々に出力することにした。

#define rdmcycle(hi_cycle, lo_cycle)  {                            \
    uint32_t lo, hi, hi2;                              \
    __asm__ __volatile__ ("1:\n\t"                     \
                          "csrr %0, mcycleh\n\t"       \
                          "csrr %1, mcycle\n\t"        \
                          "csrr %2, mcycleh\n\t"       \
                          "bne  %0, %2, 1b\n\t"                 \
                          : "=r" (hi), "=r" (lo), "=r" (hi2)) ; \
   hi_cycle = hi; lo_cycle = lo;                               \
  }

以下のようにして出力する。差分の計算とかは難しいけど。とりあえずprintf()くらいは表示できるようになった。

  uint32_t start_cycle[2], stop_cycle[2];
  rdmcycle(start_cycle[1], start_cycle[0]);
...

  printf ("Correct = %d\n", correct);
  printf ("Time = %08x%08x - %08x%08x\n", stop_cycle[1], stop_cycle[0], start_cycle[1], start_cycle[0]);

HiFive1でベンチマークプログラム測定(Coremarkを動作させる)

HiFive1でベンチマークプログラムや一般的なプログラムを動作させるのはそこまで難しい話ではなくて、SiFiveもプログラム開発用のツールセットを公開している。

freedom-e-sdkというリポジトリは、RISC-V向けプログラムのコンパイル環境を提供しており、Coremarkをコンパイルして動作させるまでのフローも公開してある。これをなぞってみよう。

github.com

Freedom-e-sdkを使ってCoremarkをコンパイルする

Coremarkのコンパイル方法は、githubリポジトリに書いてある通り、Coremark本体をダウンロードして当該ディレクトリに配置し、コンパイルするだけだ。

github.com

f:id:msyksphinz:20170322011916p:plain

この際、freedom-e-sdkに付属しているRISC-V向けGCCを再度コンパイルするのが面倒だったので、パスを差し替えて既存のGCCを指すように変更した。

diff --git a/bsp/env/common.mk b/bsp/env/common.mk
index 74f5582..ecb194d 100644
--- a/bsp/env/common.mk
+++ b/bsp/env/common.mk
@@ -23,7 +23,8 @@ INCLUDES += -I$(BSP_BASE)/drivers/
 INCLUDES += -I$(ENV_DIR)
 INCLUDES += -I$(PLATFORM_DIR)

-TOOL_DIR = $(BSP_BASE)/../toolchain/bin
+# TOOL_DIR = $(BSP_BASE)/../toolchain/bin
+TOOL_DIR = /home/msyksphinz/riscv32/bin

 CC := $(TOOL_DIR)/riscv32-unknown-elf-gcc
 AR := $(TOOL_DIR)/riscv32-unknown-elf-ar

あと、OpenOCDが当該パスに必要なので、それだけcloneしてビルドしておく。

git submodule init
git submodule update openocd --recursive
make openocd

あとは、指示通りにビルドしてプログラムを書き込むだけだ。UARTの速度は115200に設定しておく。

  • シリアル受信側
screen /dev/ttyUSB1 115200
  • ビルド&アップロード側

以下のコマンドを実行する。

make software PROGRAM=coremark
make upload PROGRAM=coremark

以下のような出力が得られた。

core freq at 274192793 Hz
2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 798820
Total time (secs): 24.375000
Iterations/Sec   : 410.256410
Iterations       : 10000
Compiler version : GCC6.1.0
Compiler flags   : -O2 -fno-common -funroll-loops -finline-functions --param max-inline-insns-auto=20 -falign-functions=4 -falign-jumps=4 -falign-loops=4
Memory location  : STACK
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0x988c
Correct operation validated. See readme.txt for run and reporting rules.
CoreMark 1.0 : 410.256410 / GCC6.1.0 -O2 -fno-common -funroll-loops -finline-functions --param max-inline-insns-auto=20 -falign-functions=4 -falign-jumps=4 -falign-loops=4 / STACK

Progam has exited with code:0x00000000

Coremark/MHzを測定すると、{ 410.256410 / 274.192793 = 1.496 } となる。

f:id:msyksphinz:20170322014023p:plain

HiFive1のCPUコア性能を測定(ベンチマークプログラム作成)

前回、HiFive1のプログラムをC/C++で開発するための環境を構築した。

次に、HiFive1のCPUコアの基本性能を測定してみよう。まずは、通常のプログラムを動作させる前に、同一の命令を何度も実行して、命令のスループットおよびレイテンシを測定してみる。

msyksphinz.hatenablog.com

命令のスループットおよびレイテンシを測定するベンチマークプログラム

github.com

命令のスループットは、同一の命令を何度も実行して、消費サイクル数を測定する。命令間の依存関係は存在しないように記述しておく。 これにより、プログラムの依存関係のストールを無視して、命令発行能力を測定することができる。

  for(int i=0; i < 65536; i++) {
    func (&dst, src0, src1);
    func (&dst, src0, src1);
    func (&dst, src0, src1);
    func (&dst, src0, src1);
    func (&dst, src0, src1);
    func (&dst, src0, src1);
    func (&dst, src0, src1);
    func (&dst, src0, src1);
  }

一方の命令レイテンシは、同一の命令を何度も実行するが、各命令には依存関係が存在する。 これにより、ある命令を実行してから、次の同一命令を実行するのに必要なサイクル数を測定できる。

  for(int i=0; i < 65536; i++) {
    func (&dst, src0, src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
  }

これを、各命令について実装してみる。まずは、add, sub, mul, divについて実装してみた。

以下のような共通関数を用意し、測定対象を関数ポインタとして渡す。以下は、1ループにつき8命令、

uint32_t inst_latency(void (*func)(int*,int,int), int init_src0, int init_src1)
{
  uint32_t start_time, end_time, wrap_time;
  int dst, src0 = init_src0, src1 = init_src1;
  rdmcycle (&start_time);
  for(int i=0; i < 65535; i++) {
    func (&dst, src0, src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
    func (&dst, dst,  src1);
  }
  rdmcycle (&end_time);
  // printint(0, start_time, 10, 1); uart_print("\r\n");
  // printint(0, end_time, 10, 1);   uart_print("\r\n");
  // printint(0, wrap_time, 10, 1);  uart_print("\r\n");
  return end_time - start_time;
}

測定対象の関数は以下のようになる。

inline void add_inst(int *dst, int src0, int src1) {
  asm volatile ("add %0,%1,%2" :"=r"(*dst) :"r"(src0),"r"(src1) : );
}
inline void sub_inst(int *dst, int src0, int src1) {
  asm volatile ("sub %0,%1,%2" :"=r"(*dst) :"r"(src0),"r"(src1) : );
}
inline void mult_inst(int *dst, int src0, int src1) {
  asm volatile ("mul %0,%1,%2"  :"=r"(*dst) :"r"(src0),"r"(src1) : );
}
inline void div_inst(int *dst, int src0, int src1) {
  asm volatile ("div %0,%1,%2"  :"=r"(*dst) :"r"(src0),"r"(src1) : );
}

以下のように、関数をコールしては測定結果を出力した。

void add_bench ()
{
  uint32_t ret;
  ret = inst_latency    (add_inst, 1, 1); printint(0, ret, 10, 1); uart_print("\r\n");
  ret = inst_throughput (add_inst, 1, 1); printint(0, ret, 10, 1); uart_print("\r\n");

  ret = inst_latency    (sub_inst, 1, 1); printint(0, ret, 10, 1); uart_print("\r\n");
  ret = inst_throughput (sub_inst, 1, 1); printint(0, ret, 10, 1); uart_print("\r\n");

  ret = inst_latency    (mult_inst, 1, 1); printint(0, ret, 10, 1); uart_print("\r\n");
  ret = inst_throughput (mult_inst, 1, 1); printint(0, ret, 10, 1); uart_print("\r\n");

  ret = inst_latency    (div_inst, 0x7fffffff, 1); printint(0, ret, 10, 1); uart_print("\r\n");
  ret = inst_throughput (div_inst, 0x7fffffff, 1); printint(0, ret, 10, 1); uart_print("\r\n");
}

測定結果は以下のようになった。単純な整数演算系は、1サイクル1命令実行することができる。 一方で、乗算命令は実行に7サイクル程度必要であり、除算命令は36命令程度必要である。 ただし、除算命令はオペランドの値に従ってレイテンシが変わるようなので、これよりも小さな場合もあるし、大きな場合もあるかもしれない。 とりあえずここでは、被除数=0x7fffffff, 除数=0x01で測定している。

Latency(Cycle) Throughput(Cycle) Latency(CPI) Throughput(IPC)
add 568291 560092 1.08 0.93
sub 560094 560094 1.06 0.93
mul 3632046 3689406 6.93 0.142
div 18836174 18893504 35.92 0.027