FPGA開発日記

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

Gem5 for RISC-Vのビルド試行 (3. Gem5でCoremarkをコンパイルして実行してみる)

f:id:msyksphinz:20181129165439p:plain

Gem5のリグレッションテストが流れたので、次は自分でプログラムをコンパイルして動かしてみたい。

手っ取り早く、Coremarkのベンチマークプログラムが動かないかと思い、試行してみることにした。

CoremarkをGitHubからダウンロードし、展開する。

github.com

次に、linux64ディレクトリをコピーして、riscv64gcディレクトリを作成した。内容を編集する。 linux64riscv64gcの差分は以下のようにした。

diff -w linux64/core_portme.c riscv64gc/core_portme.c
79a80
>     #define CLOCKS_PER_SEC 1000000
diff -w linux64/core_portme.h riscv64gc/core_portme.h
18a19,21
> #include <stdint.h>
> #include <stddef.h>
>
38c41
< #define HAS_TIME_H 1
---
> #define HAS_TIME_H 0
45c48
< #define USE_CLOCK 0
---
> #define USE_CLOCK 1
70c73,74
< #error "Please define type of CORE_TICKS and implement start_time, end_time get_time and time_in_secs functions!"
---
> typedef uint64_t CORE_TICKS;
> // #error "Please define type of CORE_TICKS and implement start_time, end_time get_time and time_in_secs functions!"
117c121
< #define SEED_METHOD SEED_ARG
---
> #define SEED_METHOD SEED_VOLATILE
277a282
>
diff -w linux64/core_portme.mak riscv64gc/core_portme.mak
24c24
< CC = gcc
---
> CC = riscv64-unknown-elf-gcc
33c33
< LFLAGS_END += -lrt
---
> # LFLAGS_END += -lrt
64c64
< LD            = gcc
---
> LD            = riscv64-unknonw-elf-gcc

これでコンパイルを行う。

make PORT_DIR=riscv64gc ITERATIONS=10

まずはSpikeで動作するか確認してみよう。

$ spike pk ./coremark.exe
Ready
2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 0
Total time (secs): 0.000000
ERROR! Must execute for at least 10 secs for a valid result!
Iterations       : 10
Compiler version : GCC7.2.0
Compiler flags   : -O2 -DPERFORMANCE_RUN=1
Memory location  : Please put data memory location here
                        (e.g. code in flash, data on heap etc)
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0xfcaf
Errors detected

時間に対する概念が無いのでERRORになってしまっているが、動作自体は問題なさそうだ。

次に、Gem5で実行してみる。

$ build/RISCV/gem5.opt configs/example/se.py -c ${HOME}/work/riscv/coremark/coremark.exe
gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Nov 29 2018 15:22:49
gem5 started Dec  1 2018 15:41:28
gem5 executing on msyksphinz-VirtualBox, pid 10648
command line: build/RISCV/gem5.opt configs/example/se.py -c /home/msyksphinz/work/riscv/coremark/coremark.exe

Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
warn: Unknown operating system; assuming Linux.
0: system.remote_gdb: listening for remote gdb on port 7000
**** REAL SIMULATION ****
info: Entering event queue @ 0.  Starting simulation...
Ready
2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 0
Total time (secs): 0.000000
ERROR! Must execute for at least 10 secs for a valid result!
Iterations       : 10
Compiler version : GCC7.2.0
Compiler flags   : -O2 -DPERFORMANCE_RUN=1
Memory location  : Please put data memory location here
                        (e.g. code in flash, data on heap etc)
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0xfcaf
Errors detected
Exiting @ tick 2220101000 because exiting with last active thread context

同様だが、無事に実行できたようだ。CRCの値も一致している。

Gem5 for RISC-Vのビルド試行 (2. リグレッションテストの実行)

f:id:msyksphinz:20181129165439p:plain

Gem5のビルドが完了して簡単なテストが実行できたので、次はリグレッションテストを実行してみたい。

Gem5にはあらかじめRISC-V向けの命令セットのテストパタンが用意されている。これをビルドして、全て流してみればよいわけだ。

cd tests/test-progs/asmtest/src/riscv/
make # テストパタンがコンパイルされる。
./run-tests.py   # 注意。このまま実行するとCPUを使い切る

上記のrun-test.pyをそのまま実行するとCPUとメモリを使い切って非常に危険なので、CPU数を制限する。 オプションで制限できるかと思っていたのだが、出来ないようなので決め打ちでCPUコア数を16に制限した。

diff --git a/tests/test-progs/asmtest/src/riscv/run-tests.py b/tests/test-progs/asmtest/src/riscv/run-tests.py
index 53a8476c0..930896777 100755
--- a/tests/test-progs/asmtest/src/riscv/run-tests.py
+++ b/tests/test-progs/asmtest/src/riscv/run-tests.py
@@ -118,7 +118,7 @@ for test in tests:
                     ])

 # execute all jobs
-job_pool = Pool(processes = n_tests)
+job_pool = Pool(processes = 16)
 job_outputs = job_pool.map(subprocess.call, job_cmds)
 job_pool.close()

このままテストパタンを流すと、しばらくして実行が完了する。 test-summary.outを参照する。テストがいくつか落ちていることが分かる。25/458本がFailしていた。

rv64samt-ps-sysclone_d-AtomicSimpleCPU             failed - signal = 6
rv64samt-ps-sysclone_d-TimingSimpleCPU             failed - signal = 6
rv64samt-ps-sysclone_d-MinorCPU                    failed - signal = 6
rv64samt-ps-sysclone_d-DerivO3CPU                  failed - signal = 6
rv64samt-ps-sysfutex1_d-AtomicSimpleCPU            failed - status = 1
rv64samt-ps-sysfutex1_d-TimingSimpleCPU            failed - status = 1
rv64samt-ps-sysfutex1_d-MinorCPU                   failed - signal = 6
rv64samt-ps-sysfutex1_d-DerivO3CPU                 failed - status = 1
rv64samt-ps-sysfutex2_d-AtomicSimpleCPU            passed
rv64samt-ps-sysfutex2_d-TimingSimpleCPU            passed
rv64samt-ps-sysfutex2_d-MinorCPU                   failed - signal = 6
rv64samt-ps-sysfutex2_d-DerivO3CPU                 passed
rv64samt-ps-sysfutex3_d-AtomicSimpleCPU            failed - status = 1
rv64samt-ps-sysfutex3_d-TimingSimpleCPU            failed - status = 1
rv64samt-ps-sysfutex3_d-MinorCPU                   failed - signal = 6
rv64samt-ps-sysfutex3_d-DerivO3CPU                 failed - status = 1
rv64samt-ps-sysfutex_d-AtomicSimpleCPU             passed
rv64samt-ps-sysfutex_d-TimingSimpleCPU             passed
rv64samt-ps-sysfutex_d-MinorCPU                    failed - signal = 6
rv64samt-ps-sysfutex_d-DerivO3CPU                  passed
rv64ua-ps-amoadd_d-AtomicSimpleCPU                 passed
rv64ua-ps-amoadd_d-TimingSimpleCPU                 passed
rv64ua-ps-amoadd_d-MinorCPU                        passed
rv64ua-ps-amoadd_d-DerivO3CPU                      passed
rv64ua-ps-amoadd_w-AtomicSimpleCPU                 passed
...

Gem5 for RISC-Vのビルド試行 (1. ビルドとhelloを流す)

f:id:msyksphinz:20181129165439p:plain

Gem5というのは命令セットシミュレータの一つで、複数のISAをサポートしている。

現在のサポートしているアーキテクチャの状況はこんな感じだ。

Supported Architectures - gem5

RISC-Vも限定的にサポートされており、ビルドの試行くらいは出来そうだ。

ソースのダウンロードからRISC-V向けのビルドは以下のようになる。途中でsconなるものをインストールする必要があるが、これはビルドツールの一つらしい。

git clone https://github.com/gem5/gem5.git
sudo apt install scons
scons build/RISCV/gem5.opt 

しばらくするとビルドが完了する。build/RISCV/gem5.optが生成されている。これでテストパタンを流すためには以下のようにするらしい。

./build/RISCV/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/riscv/linux/hello

実行結果。正しく実行できていることが確認できた。

$ ./build/RISCV/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/riscv/linux/hello
gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Nov 29 2018 15:22:49
gem5 started Nov 29 2018 15:37:40
gem5 executing on msyksphinz-VirtualBox, pid 10450
command line: ./build/RISCV/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/riscv/linux/hello

/home/msyksphinz/work/riscv/gem5/configs/common/CacheConfig.py:50: SyntaxWarning: import * only allowed at module level
  def config_cache(options, system):
Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb: listening for remote gdb on port 7000
**** REAL SIMULATION ****
info: Entering event queue @ 0.  Starting simulation...
warn: readlink() called on '/proc/self/exe' may yield unexpected results in various settings.
      Returning '/home/msyksphinz/work/riscv/gem5/tests/test-progs/hello/bin/riscv/linux/hello'
info: Increasing stack size by one page.
Hello world!
Exiting @ tick 3544500 because exiting with last active thread context

TensorFlow LiteでのRISC-V向けビルド試行(1. riscv32-gccを使ったビルド試行→失敗)

f:id:msyksphinz:20181128161624p:plain

TensorFlow Liteは、Raspberry Pi向けであったり、iOS向けにコンパイル環境が用意されているようだが、よく見てみると一応RISC-V向けのビルドファイルが用意されている。

ちゃんとしたサポートになっているのかは分からないが、これを使えばとりあえずRISC-V向けにTensorFlow Liteを動かすことができるのだろうか。 中身を見てみると32-bit版RISC-Vになっているので、HiFive1 あたりで動いてくれると嬉しい。

github.com

とりあえず32-bit版RISC-Vのツールチェインをインストールして、ビルドを試行してみる。

32-bit版RISC-Vツールチェインのインストールが完了して、Raspberry Pi向けの構成を真似てビルドしてみても思いっきりエラーとなった。

  • ./tensorflow/lite/tools/make/build_riscv_lib.sh
set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/../../../.."

CC_PREFIX=riscv64-unknown-elf- make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=riscv TARGET_ARCH=riscv
cd tensorflow
./tensorflow/lite/tools/make/download_dependencies.sh
./tensorflow/lite/tools/make/build_riscv_lib.sh
...
/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/gemmlowp/public/../internal/multi_thread_gemm.h:111:59: error: 'pthread_cond_t' has not been declared
 T WaitForVariableChange(volatile T* var, T initial_value, pthread_cond_t* cond,

これはやはりLinuxGCCを使わなければならないのだろうか。

riscv32-unknown-linux-gnuを使ってみた。しかし最後にやはりエラーとなる。-lzを除去してもエラーになるので、もう少し解析が必要だ。

riscv32-unknown-linux-gnu-g++ -O3 -DNDEBUG --std=c++11 -DTFLITE_MCU -I. -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/../../../../../ -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/../../../../../../ -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/ -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/eigen -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/absl -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/gemmlowp -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/neon_2_sse -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/farmhash/src -I/home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include -I -I/usr/local/include \
-o /home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/gen/riscv_riscv/bin/minimal /home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/gen/riscv_riscv/obj/tensorflow/lite/examples/minimal/minimal.o \
 /home/msyksphinz/others/riscv/tensorflow/tensorflow/lite/tools/make/gen/riscv_riscv/lib/libtensorflow-lite.a  -lstdc++ -lpthread -lm -lz -ldl
/home/msyksphinz/riscv32_linux/bin/../lib/gcc/riscv32-unknown-linux-gnu/7.2.0/../../../../riscv32-unknown-linux-gnu/bin/ld: cannot find -lz
collect2: error: ld returned 1 exit status

Chiselを使ってCPUを作ろう(12. テスト終了コードの取り扱いについて)

f:id:msyksphinz:20181123005953p:plain

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト。RISC-Vにはfromhostやtohostといったレジスタ(メモリマップドレジスタ)が定義されており、それを使用してシミュレーションの制御を行う。

このメモリマップドレジスタはだいたい0x1000あたりに定義されており、そこにストアした値が0であればPassでテスト終了、などといった条件でPass / Failを判定する。

テストパタンの終了条件は、Scalaのテストベンチ側に以下のように記述することで実現した。

import util.control.Breaks._
...
  breakable {
    for (cycle <- 0 to 512) {
      val inst_valid = peek(cpu_tb.io.dbg_monitor.inst_valid)
      if (inst_valid == 1) {
        writer.printf("%10d : ".format(cycle))
        val reg_wren   = peek(cpu_tb.io.dbg_monitor.reg_wren)
        val reg_wraddr : Long = peek(cpu_tb.io.dbg_monitor.reg_wraddr).toLong
        val reg_wrdata : Long = peek(cpu_tb.io.dbg_monitor.reg_wrdata).toLong
...
        if (data_bus_req == 1 && data_bus_cmd == peek(CMD_WR) &&
            data_bus_addr == 0x1000) {
           if (data_bus_wrdata == 0x1) {
             writer.printf(" PASS : Simulation Finished\n")
           } else {
             writer.printf(" FAIL : Simulation Finished\n")
           }
           break
        }
      }

ScalaにはどうやらデフォルトでBreakが無いらしく、util.control.Breaksをインポートしてbreakしたいループをbreakableで囲むらしい。

私の環境では、パイプライントレーサがメモリアクセスを監視しているので、CMD_WRが出力され、かつ data_bus_addr == 0x1000 であるときは終了条件であるとし、ストアデータが0x1であればPass、それ以外であればFailとしてテストパタンを終了させることにした。

実行結果は以下となる。

make cpu_run && spike-dasm < pipetrace.log > pipetrace.dasm.log
  • pipetrace.dasm.log
       489 : x00<=0x0000000000000000 (15, 0000000000000000, 0000000000000000)                                : 0x0000060c : INST(0x00000073) : ecall
       490 : x00<=0x0000000000000000 (21, 0000000000000000, 0000000000000000)                                : 0x00000610 : INST(0xc0001073) : csrw    cycle, zero
       491 : x30<=0x0000000000000008 (21, 0000000000000000, 0000000000000008)                                : 0x00000004 : INST(0x34202f73) : csrr    t5, mcause
       492 : x31<=0x0000000000000008 ( 1, 0000000000000000, 0000000000000008)                                : 0x00000008 : INST(0x00800f93) : li      t6, 8
       493 :                                                                                                 : 0x0000000c : INST(0x03ff0a63) : beq     t5, t6, pc + 52
       495 : x30<=0x0000000000001040 ( 1, 0000000000001000, 0000000000000040)                                : 0x00000040 : INST(0x00001f17) : auipc   t5, 0x1
       496 :                                                                  [00001000]<=0x0000000000000001 : 0x00000044 : INST(0xfc3f2023) : sw      gp, -64(t5)
 PASS : Simulation Finished

無事にテストパタンがPassして、終了することが確認できた。

Chiselを使ってCPUを作ろう(11. 例外やシステムコールでのCSRの取り扱いなど)

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト。テストパタンを動かすためには、CSRレジスタを扱えければならないのと、Ecallなどの例外を扱う命令が実行できなければならない。

Ecallは、例外を発生させ例外コードをmcauseに書き込み、mtvecに格納されているPC位置までジャンプする。 mcauseへの書き込みと、mtvecの読み込みとPC更新ができればよいわけだ。

f:id:msyksphinz:20181123005953p:plain

CSRへの書き込みを実行するのは以下のコードとなる。

  when(io.ecall_inst) {
    reg_mcause := Causes.UserEcall.U
  } .elsewhen (decoded_addr(CsrAddr.mcause)) {
    reg_mcause := wdata & ((BigInt(1) << (63)) + 31).U /* only implement 5 LSBs and MSB */
  }

Ecall命令を実行すると、mtvecの値に従ってPCを変更し、mcauseに値を設定する。

  if_inst_addr := MuxCase (0.U, Array (
    (if_inst_en & dec_jalr_en) -> dec_reg_op0.asUInt,
    (if_inst_en & dec_jal_en)  -> (dec_inst_addr + dec_imm_j),
    (if_inst_en & dec_br_en)   -> (dec_inst_addr + dec_imm_b_sext),
    (if_inst_en & dec_mret_en) -> u_csrfile.io.mepc,
    (if_inst_en & dec_ecall_en)-> u_csrfile.io.mtvec,
    (if_inst_en & io.inst_bus.ack) -> (if_inst_addr + 4.U)
  ))

これで、Ecallでジャンプして、CSRを制御できるようになった。

Chiselを使ってCPUを作ろう(10. Configurationを1つのcaseにまとめる)

f:id:msyksphinz:20181123005953p:plain

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト、テストパタンを動かしながらデバッグしている。 いくつかコンフィグレーションを変えながら動かしたいところがあり、またVerilogのパラメータのように扱いたい部分があるのだが、Chiselにはcaseを用いたコンフィグレーションの設定方法があるらしい。

RISC-V Sodorには以下のような記述がみられた。

  • src/common/configuration.scala
case class SodorConfiguration()
{
   val xprlen = 32
   val nxpr = 32
   val nxprbits = log2Ceil(nxpr)
   val rvc = false
   val vm = false
   val usingUser = false
}
  • src/rv32_1stage/cpath.scala
class CpathIo(implicit val conf: SodorConfiguration) extends Bundle()
{
   val dcpath = Flipped(new DebugCPath())
   val imem = new MemPortIo(conf.xprlen)
   val dmem = new MemPortIo(conf.xprlen)
   val dat  = Flipped(new DatToCtlIo())
   val ctl  = new CtlToDatIo()
   override def cloneType = { new CpathIo().asInstanceOf[this.type] }
}

上記のように、implicit valとしてパラメータの一式をデザインに渡すことができる。これを自作RISC-V CPUにも適用しよう。 以下のようなConfigurationを作成する。

case class RV64IConf()
{
  val xlen = 64
  val bus_width = 16
}

各モジュールを、このConfigurationを使うように書き換えた。

class Cpu (implicit val conf: RV64IConf) extends Module {
  val io = IO (new CpuIo())

  val u_cpath   = Module (new CtlPath)
  val u_regs    = Module (new Regs)
  val u_alu     = Module (new Alu)
  val u_csrfile = Module (new CsrFile)

...

一番上位mainになる部分や、モジュールをインスタンスする部分については、以下のようにして明示的にConfigurationを渡してやることが必要らしい。

object CpuTop extends App {
  implicit val conf = RV64IConf()
  chisel3.Driver.execute(args, () => new CpuTop())
}
  • テスト実行記述
  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    implicit val conf = RV64IConf()
    iotesters.Driver.execute(Array(), () => new CpuTop()) {
      c => new CpuTopTests(c)
    } should be (true)
  }

これで、コンフィグレーションを任意に変更できるようになった。