読者です 読者をやめる 読者になる 読者になる

FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://sites.google.com/site/fpgadevelopindex/

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

RISC-V

少し前に、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のパフォーマンスカウンタについて

RISC-V

前回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回ロードし、変化していないことを確認してからサイクル数を計算するという訳だ。

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

RISC-V

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コア性能を測定(ベンチマークプログラム作成)

RISC-V

前回、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

HiFive1向けのC/C++言語プログラムを開発するフロー構築

RISC-V

HiFive1は基本的にArduino経由でプログラムを開発するようになっているが、実際に動作しているのはriscv32-gccであり、隣家スクリプトコンパイルオプションさえ揃えれば普通にC/C++でプログラムを開発できる。

今回、そのプログラム開発フローを構築したので、githubにアップロードした。

ちなみに、Arduinoに付属しているRISC-V GCCを利用しているので、Arduino IDEのインストールと、RISC-V向けツールチェインの導入は必要だ。

github.com

main()内に好きなプログラムを書けば、それを実行できる。 以下はuart_print()関数を定義し、シリアルに対して出力を行った結果。

  • main.cpp
/*
 * \brief Main entry point of Arduino application
 */
int main( void )
{
  //   init();
  // Compute F_CPU inverse, used for millis and micros functions.
  calc_inv(F_CPU/1000, &f_cpu_1000_inv);
  calc_inv(F_CPU/1000000, &f_cpu_1000000_inv);
  freedom_e300_specific_initialization();
  setup();

  uart_print ("Hello World\r\n");

  do {
    loop();
...
  • serial.c
void uart_print (uint8_t *str)
{
  while (*str != '\0') {
    uart_putchar(*str);
    str++;
  }

  return;
}


void uart_putchar (uint8_t a)
{
  const int blocking = 1;

  volatile uint32_t *val = UART_REGP(UART_REG_TXFIFO);
  uint32_t busy = (*val) & 0x80000000;
  if (blocking) {
    while (*val & 0x80000000);
  } else if (busy) {
    return;
  }
  UART_REG(UART_REG_TXFIFO) = a;

  return;
}

9600baudでシリアル受信状態にしておけば、受信できる。

f:id:msyksphinz:20170320235540p:plain

コーヒーメーカー「バリスタアイ」購入

コーヒーメーカーとして、初代バリスタを使用していたのだがしばらく前に壊れてしまった。

買ってから結構時間がたっているし、保証も切れているので、せっかくなので新しいものを購入することにした。 同じメーカーの新機種のバリスタアイだ。本日ヨドバシのエクストリーム配送で届いた。

f:id:msyksphinz:20170319133016p:plain

色は木目調のウッディブラウンとした。初代のバリスタは白色にしていたのだが、ウッディブラウンは結構雰囲気出てよい感じだと思う。

f:id:msyksphinz:20170319133330p:plain

初代バリスタと比較すると、少しだけ背が低い。 これ、普通のコーヒーカップを使っている場合は非常に良いと思っており、背が高いとコーヒーカップと天井が空いており、カプチーノを作る場合にお湯が跳ねてしまい汚くなってしまっていた。 背が低いとお湯がはねる領域が狭くなり汚くなりにくいと考えている。

f:id:msyksphinz:20170319133304p:plain

iPhoneともBluetoothで接続できるので便利。ただし、コップを置いていないのに間違えて抽出しないようにしなきゃ。

Windows版RISC-V GCCのビルド方法(1. msys2環境でRISC-V 32bit向けGCCをビルドする)

RISC-V

RISC-VのMLで話題に挙がっていた、WindowsRISC-V GCCコンパイル方法をやってみた。 まだイマイチ洗練されていないようだが、バイナリ自体は作れるようだ。 環境にはmsys2を利用する。

RISC-V GCC for Windowsのビルド方法

msys2には、あらかじめ以下のライブラリとツールをインストールしておく。

pacman -Su msys2-runtime diffutils

参考にしたのは、以下のgithubリポジトリだ。

github.com

./build.shを利用する。

github.com

ただしこのままではコンパイルできない。自分向けにいくつか改造している。

diff --git a/src/compiler/build.sh b/src/compiler/build.sh
index 0edffaa..aa74063 100755
--- a/src/compiler/build.sh
+++ b/src/compiler/build.sh
@@ -5,37 +5,38 @@
 # XXX sudo for install?

 # XXX impossible to build statically linked binutils?
-# F32C_MAKEOPTIONS="LDFLAGS=-static"
+F32C_MAKEOPTIONS="LDFLAGS=-static"

-SUDO=sudo
+SUDO=
 MAKE=make

 MAKE_JOBS=2

-BINUTILS_SRC_DIR=~/github/riscv-binutils-gdb
-GCC_SRC_DIR=~/github/riscv-gcc
-F32C_SRC_DIR=~/github/f32c
+BINUTILS_SRC_DIR=~/work/riscv-binutils-gdb
+GCC_SRC_DIR=~/work/riscv-gcc
+F32C_SRC_DIR=~/work/f32c
 F32C_TOOLCHAIN_DST_DIR=~/f32c_toolchain
  • SUDOはmsys2には必要ない。
  • ソースファイルの場所とビルド後のツールの格納場所を指定する。
@@ -44,14 +45,17 @@ fi

 ${SUDO} mkdir -p ${F32C_TOOLCHAIN_DST_DIR}

-for TARGET_ARCH in riscv32 mips
+for TARGET_ARCH in riscv32
 do
-    for SRC_DIR in ${BINUTILS_SRC_DIR} ${GCC_SRC_DIR}
+    # for SRC_DIR in ${BINUTILS_SRC_DIR} ${GCC_SRC_DIR}
+    for SRC_DIR in ${GCC_SRC_DIR}
     do
        cd ${SRC_DIR}
        ${MAKE} distclean
        find . -name config.cache -exec rm {} \;
        ./configure --target=${TARGET_ARCH}-elf --enable-languages=c,c++ \
+        --build=x86_64-pc-msys2 \
                --prefix=${F32C_TOOLCHAIN_DST_DIR} \
                --mandir=${F32C_TOOLCHAIN_DST_DIR}/man \
                --infodir=${F32C_TOOLCHAIN_DST_DIR}/info \
  • mipsのビルドターゲットは不要
  • ビルドオプションに--build=x86_64-pc-msys2 \を追加する。

これで./build.shを実行してしばらく待っていると、しばらくして ~/f32c_toolchainにビルドツールが生成されていることが分かる。

msyksphinz@fixedesk MINGW64 ~
$ cd ~/f32c_toolchain/bin/

msyksphinz@fixedesk MINGW64 ~/f32c_toolchain/bin
$ ls
a.out                      riscv32-elf-gcc-7.0.1.exe   riscv32-elf-nm.exe       riscv64-elf-cpp.exe          riscv64-unknown-elf-cpp.exe
riscv32-elf-addr2line.exe  riscv32-elf-gcc-ar.exe      riscv32-elf-objcopy.exe  riscv64-elf-g++.exe          riscv64-unknown-elf-g++.exe
riscv32-elf-ar.exe         riscv32-elf-gcc-nm.exe      riscv32-elf-objdump.exe  riscv64-elf-gcc.exe          riscv64-unknown-elf-gcc.exe
riscv32-elf-as.exe         riscv32-elf-gcc-ranlib.exe  riscv32-elf-ranlib.exe   riscv64-elf-gcc-7.0.1.exe    riscv64-unknown-elf-gcc-7.0.1.exe
riscv32-elf-c++.exe        riscv32-elf-gcov.exe        riscv32-elf-readelf.exe  riscv64-elf-gcc-ar.exe       riscv64-unknown-elf-gcc-ar.exe
riscv32-elf-c++filt.exe    riscv32-elf-gcov-tool.exe   riscv32-elf-run.exe      riscv64-elf-gcc-nm.exe       riscv64-unknown-elf-gcc-nm.exe
riscv32-elf-cpp.exe        riscv32-elf-gdb.exe         riscv32-elf-size.exe     riscv64-elf-gcc-ranlib.exe   riscv64-unknown-elf-gcc-ranlib.exe
riscv32-elf-elfedit.exe    riscv32-elf-gprof.exe       riscv32-elf-strings.exe  riscv64-elf-gcov.exe         riscv64-unknown-elf-gcov.exe
riscv32-elf-g++.exe        riscv32-elf-ld.bfd.exe      riscv32-elf-strip.exe    riscv64-elf-gcov-tool.exe    riscv64-unknown-elf-gcov-tool.exe
riscv32-elf-gcc.exe        riscv32-elf-ld.exe          riscv64-elf-c++.exe      riscv64-unknown-elf-c++.exe

GCCのテストを行う

ためしに以下のようなプログラムを作って、きちんと命令が生成されるか確認する。 ライブラリなどは、とりあえず不要とした。

float add (float a, float b)
{
  return a + b;
}

コンパイルしてみる。

$ ~/f32c_toolchain/bin/riscv32-elf-gcc -c main.cpp
$ ~/f32c_toolchain/bin/riscv32-elf-objdump.exe -d main.o

main.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <_Z3addff>:
   0:   1101                    addi    sp,sp,-32
   2:   ce22                    sw      s0,28(sp)
   4:   1000                    addi    s0,sp,32
   6:   fea42627                fsw     fa0,-20(s0)
   a:   feb42427                fsw     fa1,-24(s0)
   e:   fec42707                flw     fa4,-20(s0)
  12:   fe842787                flw     fa5,-24(s0)
  16:   00f777d3                fadd.s  fa5,fa4,fa5
  1a:   20f78553                fmv.s   fa0,fa5
  1e:   4472                    lw      s0,28(sp)
  20:   6105                    addi    sp,sp,32
  22:   8082                    ret

一応、コンパイルは出来ているようだ。ダンプ結果が何だか変だな。