FPGA開発日記

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

RISC-V SpikeシミュレータでC/C++のprintfを実現する仕組み (8. RISC-Vのシステムコールを自作ISS上で実装)

UCBが開発しているRISC-VのシミュレータSpikeや、Rocket-ChipのRTLデザインは通常はシステムコールを持っていない。 つまり、当然ながらC言語printf("Hello World\n");などと書いても動作しないのだが、そこはコンパイラとフロントエンドサーバfesvr、pk(Proxy Kernel)によって肩代わりすることでこれらのシステムコールを実現している。

Proxy KernelというのはRISC-Vのシステムコールにつながる処理をフロントエンドサーバに受け渡すインタフェース、そしてフロントエンドサーバはPKからのシステムコールリクエストを受け取ってRISC-Vコアの代わりに実現する機能を持っている。

これらの機能をRISC-V自作シミュレータに実装して、システムコールを実現した。

f:id:msyksphinz:20180727001523p:plain

例えば、syscall::sys_readシステムコールは、fesvrでは以下のように実装されている。

  • riscv-fesvr/fesvr/syscall.cc
reg_t syscall_t::sys_read(reg_t fd, reg_t pbuf, reg_t len, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> buf(len);
  ssize_t ret = read(fds.lookup(fd), &buf[0], len);
  reg_t ret_errno = sysret_errno(ret);
  if (ret > 0)
    memif->write(pbuf, ret, &buf[0]);
  return ret_errno;
}

やっていることは単純だ。fds.lookupというのはファイルディスクリプタのテーブルで、stdin, stdout, stderrの扱い方を格納したテーブルだ。そこからread()(これはx86で実装されたホストのread()関数)を呼び出し、ホストのシステム上でread()を実行する。

その結果を、RISC-Vシミュレータ上のメモリ空間に書き込む。それをmemif->write()で実現している。 実行していることは単純なのだ。

これを、自作RISC-Vシミュレータ上で実装しているAPIに置き換える。

reg_t RiscvSyscall_t::sys_read(reg_t fd, reg_t pbuf, reg_t len, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  Byte_t *buf = new Byte_t[len];
  ssize_t ret = read(fds.lookup(fd), &buf[0], len);
  reg_t ret_errno = sysret_errno(ret);
  if (ret > 0) {
    for (unsigned int idx = 0; idx < len; idx++) {
      m_pe_thread->StoreMemoryDebug (pbuf + idx, Size_Byte, &buf[idx]);
    }
  }
  return ret_errno;
}

ホストの実装は同じで、memifインタフェースへのアクセスをオリジナルのインタフェースに変更しただけだ。 バースト転送のAPIを持っていなかったので、1バイトずつ書き込んでいる。

これをすべてのAPIにおいて実装し(というか単純にコピーして関数を置き換えた)、RISC-V自作シミュレータに組み込んで実行した。

printf()fopen()などで構成された以下のプログラムをRISC-V向けのGCCコンパイルし、Proxy Kernelを組み込んでシミュレーションした。

#include <stdio.h>
#include <stdlib.h>

int main ()
{
  printf("Hello World\n");

  FILE *fp;
  if ((fp = fopen("sample_hello.txt", "w")) == NULL) {
    perror ("sample_hello.txt");
    exit(EXIT_FAILURE);
  }
  fprintf (fp, "Hello World\n");
  fclose (fp);

  if ((fp = fopen("sample_hello.txt", "r")) == NULL) {
    perror ("sample_hello.txt");
    exit(EXIT_FAILURE);
  }
  char cin[100];
  fscanf (fp, "%s", cin);
  printf ("%s\n", cin);

  return 0;
}
$ riscv64-unknown-elf-gcc file_access.c -o file_access_riscv
$ riscv_iss --bit-mode 64 --use-pk /home/msyksphinz/riscv64//riscv64-unknown-elf/bin/pk --binfile file_access_riscv --debug --out file_access_riscv.forest --trace-hier --trace-out file_access_riscv.trace --max 500000
...
<Loading section Code .htif 0x0000000000000123>
<Loading section ... .data 0x0000000000000123>
<Loading section ... .data 0x0000000000000123>
<Loading section Code .data 0x0000000000000123>
<Loading section ... .sdata 0x0000000000000123>
<Loading section ... .sdata 0x0000000000000123>
<Loading section Code .sdata 0x0000000000000123>
<Loading section ... .bss 0x0000000000000001>
<Loading section ... .bss 0x0000000000000001>
<Loading section Code .bss 0x0000000000000001>
<Info: Set Entry Point as     1000>
<Info: Set Debug Module>
<Info: Set Debug Module Done>
Hello World
Hello
$ cat sample_hello.txt
Hello World

やった!成功だ。Hello Worldが正しく実行され、またファイルにも正しく書き込まれている。すばらしい。

f:id:msyksphinz:20180730012618p:plain

図. RISC-V自作シミュレータを実行した様子。最後にHello Worldと、ファイルから読み取ったHelloが表示されている。