FPGA開発日記

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

RoCCを使ったRocket Coreの拡張方法の調査 (2. サンプルデザインの実行)

f:id:msyksphinz:20170825021220p:plain

RoCC (Rocket Custom Coprocessor) のチュートリアルって意外と少ないので、調査するのに苦労する。

githubチュートリアルっぽいものがあったので、試行してみようと思った。

github.com

これは、RocketCoreのカスタムデザインの中でアキュムレータを内蔵しているデザインがあるので、これをソフトウェアで叩くアプリケーションを作ってみる、という話になっているようだ。

RocketCoreを生成する際に、CONFIGオプションとしてRoccExampleConfigを指定することで、RoCCにアクセラレータが接続されたデザインを生成することができる。

ちなみに、RoccExampleConfigを指定することで、以下のConfig.scalaの記述の構成が有効になる。

class RoccExampleConfig extends Config(new WithRoccExample ++ new BaseConfig)

うーん、難しいねえ。WithRoccExampleというのは何だ?

class WithRoccExample extends Config(
  (pname, site, here) => pname match {
    case BuildRoCC => Seq(
      RoccParameters(
        opcodes = OpcodeSet.custom0,
        generator = (p: Parameters) => Module(new AccumulatorExample()(p))),
      RoccParameters(
        opcodes = OpcodeSet.custom1,
        generator = (p: Parameters) => Module(new TranslatorExample()(p)),
        nPTWPorts = 1),
      RoccParameters(
        opcodes = OpcodeSet.custom2,
        generator = (p: Parameters) => Module(new CharacterCountExample()(p))))

    case RoccMaxTaggedMemXacts => 1
    case _ => throw new CDEMatchError
  })

なるほど、custom0, custom1, custom2 のカスタム命令に対して、それぞれ AccumulatorExample, TranslatorExample, CharacterCountExampleのデザインを接続しているように見える。それぞれ、

  • AccumulatorExample (src/main/scala/rocket/Rocc.scala)
  • TranslatorExample (src/main/scala/rocket/Rocc.scala)
  • CharacterCountExample (src/main/scala/rocket/Rocc.scala)
class AccumulatorExample(n: Int = 4)(implicit p: Parameters) extends RoCC()(p) {
  val regfile = Mem(n, UInt(width = xLen))
  val busy = Reg(init = Vec.fill(n){Bool(false)})

...
  // datapath
  val addend = cmd.bits.rs1
  val accum = regfile(addr)
  val wdata = Mux(doWrite, addend, accum + addend)

  when (cmd.fire() && (doWrite || doAccum)) {
    regfile(addr) := wdata
  }

本質は↑のような気がしていて、レジスタファイルがアキュムレータとして動作しており、cmd.bits.rs1で指定した数を加算しているように見える。 メモリアクセスを実行すると同じようにレジスタファイルにデータをロードしているようにも見えるね。詳細仕様がないので正直良く分からない。

このRoccExampleConfigを有効にしてRocketCoreをビルドするためには、RocketChipのリポジトリ内でemulatorディレクトリに移動し、以下のようにタイプする。

make CONFIG=RoccExampleConfig

これでビルドが完了する。次に、Accumulatorのテストプログラムを動作させるためにrocket-rocc-exampleリポジトリに格納されているパッチを当てた。

git clone https://github.com/seldridge/rocket-rocc-examples.git
cd ../rocket-chip/riscv-tools/riscv-pk
git apply ../../../rocket-rocc-examples/patches/riscv-pk.patch
mkdir build
cd build
../configure --prefix=$RISCV/riscv64-unknown-elf --host=riscv64-unknown-elf
make
make install

で、環境変数RISCVで指定されているツールセットのディレクトリで、pk(proxy kernel)が更新される。 ちなみに、パッチの内容だが、query-mem()の設定をすべて取り払っており、メモリサイズを固定している。なんだこりゃ?

diff --git a/machine/configstring.c b/machine/configstring.c
index fb2fed7..f3f21d2 100644
--- a/machine/configstring.c
+++ b/machine/configstring.c
@@ -6,12 +6,7 @@

 static void query_mem(const char* config_string)
 {
-  query_result res = query_config_string(config_string, "ram{0{addr");
-  assert(res.start);
-  uintptr_t base = get_uint(res);
-  assert(base == DRAM_BASE);
-  res = query_config_string(config_string, "ram{0{size");
-  mem_size = get_uint(res);
+  mem_size = 0x10000000;
 }

 static void query_rtc(const char* config_string)
diff --git a/machine/minit.c b/machine/minit.c
index b3f2c86..54ff88e 100644
--- a/machine/minit.c
+++ b/machine/minit.c
@@ -19,6 +19,7 @@ static void mstatus_init()
   uintptr_t ms = 0;
   ms = INSERT_FIELD(ms, MSTATUS_VM, VM_CHOICE);
   ms = INSERT_FIELD(ms, MSTATUS_FS, 1);
+  ms = INSERT_FIELD(ms, MSTATUS_XS, 1);
   write_csr(mstatus, ms);

   // Make sure the hart actually supports the VM mode we want

次にテストプログラムのビルドを行った。テストプログラムはrocket-rocc-exampleリポジトリに入っているので、そのリポジトリに戻ってmakeを実行するだけでよい。

cd ../../../rocket-rocc-exsamples
make

これで、rocket-rocc-examples/build/test-accumulatorにテストプログラムが作られた。

このテストプログラム、上手く動作するようならば、以下のようにAccumulatorを動かしてログが出力されるはずである。

[INFO] Write R[1] = 0xdead
[INFO] Read R[1]
[INFO]   Received 0xdead
[INFO] Accum R[1] with 0xffffffffffffe042
[INFO] Read R[1]
[INFO]   Received 0xbeef
[INFO] Load 0xbad (virt: 0x0xfeefac0, phys: 0x0x8ffffac0) via L1 data cache
[INFO] Read R[1]
[INFO]   Received 0xbad
Completed after 5614110 cycles

ところが、今回同様にテストプログラムを走らせると、アサーションエラーとなった。

$ ./emulator-rocketchip-RoccExampleConfig ~/riscv64/riscv64-unknown-elf/riscv64-unknown-elf/bin/pk ../../rocket-rocc-examples/build/test-accumulator
../pk/elf.c:46: assertion failed: !(eh.e_flags & EF_RISCV_RVC)

これは、pk/elf.cの以下の記述が問題らしい。RVCというのはCompressed Instructionのことで、RISC-Vの16bit命令セットのことだ。これで落ちたということは、__riscv_compressedが指定されていなかったにもかかわらず、EF_RISCV_RVCフラグが有効になっていたということかしら。 とりあえず、ここの記述をコメントアウトしてみるとどうだろう。

#ifndef __riscv_compressed
  assert(!(eh.e_flags & EF_RISCV_RVC));
#endif
mkdir build
../configure --prefix=$RISCV/riscv64-unknown-elf --host=riscv64-unknown-elf && make && make install

これで再度RocketChipを実行してみる。

$ ./emulator-rocketchip-RoccExampleConfig ~/riscv64/riscv64-unknown-elf/riscv64-unknown-elf/bin/pk ../../rocket-rocc-examples/build/test-accumulator
[INFO] Write R[1] = 0xdead
[INFO] Read R[1]
[INFO]   Received 0xdead
[INFO] Accum R[1] with 0xffffffffffffe042
[INFO] Read R[1]
[INFO]   Received 0xbeef
[INFO] Load 0xbad (virt: 0x0xfeefae0, phys: 0x0x8ffffae0) via L1 data cache
[INFO] Read R[1]
[INFO]   Received 0xbad

おお、所望通りに動作した。次に、このカスタムデザインの中身を読み解いていこう。