FPGA開発日記

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

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

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

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

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

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

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

HiFive1のプログラムコンパイルをArduinoIDEを使わずに制御したい(2. OpenOCDを使ったHiFiveへのプログラム書き込み)

前回の続き、今回は、OpenOCDを使って、コンパイルしたバイナリファイルをHiFive1に書き込み、実行する。

msyksphinz.hatenablog.com

OpenOCDを使った書き込みシーケンスを探す

Arduinoディレクトリを探して、OpenOCDを使ったバイナリファイルへのアップロードをしているスクリプトを見つけた。

  • packages/sifive/hardware/riscv/1.0.2/freedom-e-sdk/bsp/tools/openocd_upload.sh

あーこれだな。これにバイナリファイルと、構成ファイルを指定すればHiFive1への書き込みができるに違いない。

export PATH=/home/masayuki/.arduino15/packages/sifive/tools/openocd/9bab0782d313679bb0bfb634e6e87c757b8d5503/bin/:${PATH}
./packages/sifive/hardware/riscv/1.0.2/freedom-e-sdk/bsp/tools/openocd_upload.sh /home/masayuki/work/hifive1/uart_test/uart_test ./packages/sifive/hardware/riscv/1.0.2/freedom-e-sdk/bsp/env/freedom-e300-hifive1/openocd.cfg

openocd.cfgファイルは、ボードの情報、JTAGの書き込み速度などの情報を持っているスクリプトだ。

adapter_khz     10000

interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010
...
flash bank onboard_spi_flash fespi 0x20000000 0 0 0 $_TARGETNAME
init
#reset -- This type of reset is not implemented yet
if {[ info exists pulse_srst]} {
  ftdi_set_signal nSRST 0
  ftdi_set_signal nSRST z
  #Wait for the reset stretcher
  #It will work without this, but
  #will incur lots of delays for later commands.
  sleep 1500
}
halt
#flash protect 0 64 last off

という訳で実行してみた。無事に書き込みが行われ、プログラムが実行されたようだ。

$ ./packages/sifive/hardware/riscv/1.0.2/freedom-e-sdk/bsp/tools/openocd_upload.sh /home/masayuki/work/hifive1/uart_test/uart_test ./packages/sifive/hardware/riscv/1.0.2/freedom-e-sdk/bsp/env/freedom-
e300-hifive1/openocd.cfg
+ openocd -f ./packages/sifive/hardware/riscv/1.0.2/freedom-e-sdk/bsp/env/freedom-e300-hifive1/openocd.cfg -c 'flash protect 0 64 last off; program /home/masayuki/work/hifive1/uart_test/uart_test verify; resume 0x20400000; exit'
+ tee openocd_upload.log
Open On-Chip Debugger 0.10.0-dev-g9bab078 (2017-02-02-01:39)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
adapter speed: 10000 kHz
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
Info : clock speed 10000 kHz
Info : JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (<unknown>), part: 0x0e31, ver: 0x1)
Info : dtmcontrol_idle=5, dbus_busy_delay=1, interrupt_high_delay=0
Info : dtmcontrol_idle=5, dbus_busy_delay=1, interrupt_high_delay=1
Info : Examined RISCV core; XLEN=32, misa=0x40001105
Info : dtmcontrol_idle=5, dbus_busy_delay=1, interrupt_high_delay=2
Info : dtmcontrol_idle=5, dbus_busy_delay=1, interrupt_high_delay=3
Info : dtmcontrol_idle=5, dbus_busy_delay=1, interrupt_high_delay=4
Info : dtmcontrol_idle=5, dbus_busy_delay=1, interrupt_high_delay=5
...
Info : dtmcontrol_idle=5, dbus_busy_delay=2, interrupt_high_delay=249
Info : Retrying memory read starting from 0x20400000 with more delays
Info : dtmcontrol_idle=5, dbus_busy_delay=2, interrupt_high_delay=274
Info : Retrying memory read starting from 0x20400000 with more delays
Info : dtmcontrol_idle=5, dbus_busy_delay=2, interrupt_high_delay=302
Info : Retrying memory read starting from 0x20400000 with more delays
Info : dtmcontrol_idle=5, dbus_busy_delay=2, interrupt_high_delay=333
Info : Retrying memory read starting from 0x20400000 with more delays
verified 4492 bytes in 0.361002s (12.152 KiB/s)
** Verified OK **
halted at 0x20400004 due to step

f:id:msyksphinz:20170315011506p:plain

あ、しまった'A'ばかり表示するプログラムだった!

ま、これで一応C/C++で記述したコードが曲りなりに動作した。今度は、core.aに頼らないネイティブなコードを動かせるようになろう。

HiFive1のプログラムコンパイルをArduinoIDEを使わずに制御したい(1. C/C++コードのコンパイル)

HiFive1を使ったプログラムは、まだ時間が無くてLチカとUARTのテストくらいしか出来ていないが、早くもArduinoIDEに不満が出てきた。

可能ならばEmacsでプログラムを書きたいし、Makeを使ってコンパイルやダウンロードができるようになるとうれしい。

そこで、Arduinoコンパイルフローをトレースすることで、これをMakefileに直接インポートし、ArduinoIDEを経由せずにC/C++でHiFive1用プログラムが書けるなろう。 まずは、C/C++のプログラムでUARTに書き込みを行えるようになるところからかな。

ArduinoIDEのコンパイルシーケンス

ArduinoIDEを使った場合の、HiFive1用プログラムのコンパイルシーケンスは過去の記事で確認した。

msyksphinz.hatenablog.com

UARTへのprintは結局何が起きているのか

これはHiFive1以外で同じことが言えるだろうが、UARTの書き込みは主に2つのシーケンスで成り立っている。

  • Serial.begin(baud) UARTの初期化とボーレートの設定
  • Serial.write(char) UARTへの書き込み

これらのプログラムは、結局以下のC++のコードで記述されている。

UARTClass::begin(unsigned long bauds)
{
  GPIO_REG(GPIO_OUTPUT_XOR)&= ~(IOF0_UART0_MASK);
  GPIO_REG(GPIO_IOF_SEL)   &= ~(IOF0_UART0_MASK);
  GPIO_REG(GPIO_IOF_EN)    |= IOF0_UART0_MASK;

  //F_Baud = f_in/(div+1)

  UART_REG(UART_REG_DIV) = F_CPU / bauds - 1;
  UART_REG(UART_REG_TXCTRL) |= UART_TXEN;
  UART_REG(UART_REG_RXCTRL) |= UART_RXEN;


//  sio_setbaud(bauds);
}

UARTClass::write(const uint8_t uc_data)
{

  sio_putchar(uc_data, 1);
  return (1);
}

UARTClass::sio_putchar(char c, int blocking)
{
  volatile uint32_t *val = UART_REGP(UART_REG_TXFIFO);
  uint32_t busy = (*val) & 0x80000000;
  if (blocking) {
    while (*val & 0x80000000);
  } else if (busy) {
      return 1;
  }
  UART_REG(UART_REG_TXFIFO) = c;
  return 0;
}

じゃあ、これをそのままCのコードに持ってくればよかろう。

#include "./platform.h"

#include "./variant.h"
#include "./uart.h"
#include "./gpio.h"

void setup()
{
  int bauds = 9600;
  GPIO_REG(GPIO_OUTPUT_XOR)&= ~(IOF0_UART0_MASK);
  GPIO_REG(GPIO_IOF_SEL)   &= ~(IOF0_UART0_MASK);
  GPIO_REG(GPIO_IOF_EN)    |= IOF0_UART0_MASK;

  UART_REG(UART_REG_DIV) = F_CPU / bauds - 1;
  UART_REG(UART_REG_TXCTRL) |= UART_TXEN;
  UART_REG(UART_REG_RXCTRL) |= UART_RXEN;
}

void loop()
{
  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;
}

setup()loop()を使っているのは、まだArduinoの生成するcore.aを使うからだ。これは、最終的には取り外せて直接main()から記述出来るようになりたい。

テストプログラムをコンパイルするためのMakefile

以下のようなMakefileを作成した。

  • riscv32-unknown-elf-gccgithubで公開されているものを使おうとしたが、何故か-march=rv32imacが認識されずに、Arduino用に(自動)ダウンロードされたgccを利用した。手動インストールしたものは古いからだろうか?もう一度最新版にアップデートしてやり直してみたい。
  • リンカスクリプトとして$(PACKAGE_BASE)/freedom-e-sdk/bsp/env/freedom-e300-hifive1/link.ldsを指定している。これはArduinoが指定しているリンカスクリプトをそのまま利用している。
  • F_CPU=16000000はこちらもArduinoIDEでのコンパイルオプションで定義してあったので追加した。だけどHiFiveって320MHz動作じゃなかったっけ?
TARGET = uart_test

# ARCH = riscv32-unknown-elf-
ARCH = ~/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-
CC = $(ARCH)g++

OBJ_FILES = entry.o init.o start.o

PACKAGE_BASE = ~/.arduino15/packages/sifive/hardware/riscv/1.0.2
LINKER = $(PACKAGE_BASE)/freedom-e-sdk/bsp/env/freedom-e300-hifive1/link.lds
INC_PATH += $(PACKAGE_BASE)/freedom-e-sdk/bsp/env/freedom-e300-hifive1/
INC_PATH += $(PACKAGE_BASE)/freedom-e-sdk/bsp/env/
INC_PATH += $(PACKAGE_BASE)/freedom-e-sdk/bsp/include/
INC_PATH += $(PACKAGE_BASE)/freedom-e-sdk/bsp/include/sifive/devices/
INC_PATH += $(PACKAGE_BASE)/variants/standard/
INC_PATH += $(PACKAGE_BASE)/cores/arduino/

DEF_PARAM += F_CPU=16000000
DEF_PARAM += FREEDOM_E300_HIFIVE1
DEF_PARAM += __riscv_float_abi_soft

INCLUDE = $(addprefix -I, $(INC_PATH))
DEFINE  = $(addprefix -D, $(DEF_PARAM))

CFLAGS += -march=rv32imac $(INCLUDE) $(DEFINE)

all: $(TARGET)

%.o: %.c
  $(CC) $(CFLAGS) -c $<
%.o: %.S
  $(CC) $(CFLAGS) -c $<

# $(TARGET): $(TARGET).o $(OBJ_FILES)
$(TARGET): $(TARGET).o
  $(CC) $(CFLAGS) -nostartfiles -nostdlib -Wl,-N -Wl,--gc-sections -Wl,--wrap=malloc -Wl,--wrap=free -Wl,--wrap=sbrk -Wl,--start-group "~/Arduino/core/core.a" -lm -lc -lgloss -Wl,--end-group -lgcc -T$(LINKER) $^ -o $@

clean:
  rm -rf *.o $(TARGET)

最後にcore.aを追加しているが、これは以前ArduinoIDEでプログラムをコンパイルした際に自動的に生成されたもので、こちらにいくつか定数が入っていたのでとりあえずやむを得ず追加した。 最終的にはこれも除去し、必要最低限のファイル群でプログラムを生成できるようになりたい。

core.aは以下のように作られている。でも、entry.S, init.S, start.S`があればとりあえず十分じゃないかなあ?glibcなどを使い始めると面倒だけど。

"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/entry.S.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/init.S.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/start.S.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/WInterrupts.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/hooks.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/itoa.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/malloc.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/sbrk.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/wiring.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/wiring_analog.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/wiring_digital.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/wiring_shift.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/drivers/fe300prci/fe300prci_driver.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/drivers/plic/plic_driver.c.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/Print.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/UARTClass.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/WMath.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/WString.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/abi.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/main.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/new.cpp.o"
"/home/msyksphinz/.arduino15/packages/sifive/tools/riscv32-unknown-elf-gcc/3f7b3696217548bc31aeccf9a0c89bdfa4e16a8f/bin/riscv32-unknown-elf-ar" rcs  "/home/msyksphinz/Arduino/core/core.a" "/home/msyksphinz/Arduino/core/wiring_pulse.cpp.o"

これでプログラムをコンパイルできた。uart_testはちゃんとRISC-V互換バイナリとなっている。

$ file uart_test
uart_test: ELF 32-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, not stripped

次は、OpenOCDを使ってHiFive1ボードへ書き込みを行う。

HiFive1におけるシリアル通信

HiFive1を使って、PCとのシリアル通信を実行する。

やり方は非常に簡単だ。プログラムはArduinoのサンプルからコピーしてきた。

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  Serial.print("RISC-V HiFive1 Start\r\n");
}

void loop() {
  // put your main code here, to run repeatedly:
  int c;

  if (Serial.available() > 0) {
    c = Serial.read();
 
    Serial.write(c);
  }
}

HiFive1のバグか、そもそも仕様なのか、setup()内で大量にprintをしていると途中で固まる気がする。

f:id:msyksphinz:20170314221807p:plain

asciinema.org

これ、最初はあまり気が付かなかったのだけれども、そのままC++のプログラムなんだな。

loop()main()内で何度も動作しているし、setup()main()の最初で動作している。 Serialはクラスとして定義されており、Serial.print()はクラスのメンバ関数として定義されている。

~/.arduino15/packages/sifive/hardware/riscv/1.0.2には、HiFive1用の各種関数群などが用意されている。

23:#include "HardwareSerial.h"
25:class UARTClass : public HardwareSerial
class HardwareSerial : public Stream
class Stream : public Print
class Print
{
...

    size_t print(const __FlashStringHelper *);
    size_t print(const String &);
    size_t print(const char[]);
    size_t print(char);
    size_t print(unsigned char, int = DEC);
    size_t print(int, int = DEC);
    size_t print(unsigned int, int = DEC);
    size_t print(long, int = DEC);
    size_t print(unsigned long, int = DEC);
    size_t print(double, int = 2);
    size_t print(const Printable&);
...
};