FPGA開発日記

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

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

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

前回のサンプルプログラムは、一応意図通りに動作したのだが、いったいどのような仕組みになっているのか調査してみる。

test_accumulator.c の解析

test_accumulator.c は、大きく2つのセクションに分かれている。accumulatorのテストと、 translatorのテストだ。

f:id:msyksphinz:20170826134021p:plain

accumulatorの動作

accumulatorの制御は、簡略化すると以下のようになっている。

  uint64_t data [] = {0xdead, 0xbeef, 0x0bad, 0xf00d}, y;

  uint16_t addr = 1;

  doWrite(y, addr, data[0]);    // 
  doRead(y, addr);
  uint64_t data_accum = -data[0] + data[1];
  doAccum(y, addr, data_accum);
  doRead(y, addr);
  1. doWrite により、Accumulator内のレジスタ(addr=1)に0xdeadを書き込む。
  2. doReadにより、Accumulator内のレジスタ(addr=1)を読み込む。(0x0000deadが読み込まれる)。
  3. -data[0]+dead[1]を実行する。答えは、-0x0000dead+0x0000beef=0xffffe042
  4. Accumulator内レジスタに対して0xffffe042を加算する。 0xffffe042+0x0000dead=0x0000beef
  5. Accumulator内レジスタを読み込む。0x0000beefが読み込まれる。

という仕組みになっている。doWrite(), doRead(), doAccum()はどのように実装されているかというと、custom0命令によって実現されている。

  • src/main/c/accumulator.h
#define k_DO_WRITE 0
#define k_DO_READ 1
#define k_DO_LOAD 2
#define k_DO_ACCUM 3

#define XCUSTOM_ACC 0

#define doWrite(y, rocc_rd, data)                                       \
  ROCC_INSTRUCTION(XCUSTOM_ACC, y, data, rocc_rd, k_DO_WRITE);
#define doRead(y, rocc_rd)                                              \
  ROCC_INSTRUCTION(XCUSTOM_ACC, y, 0, rocc_rd, k_DO_READ);
#define doLoad(y, rocc_rd, mem_addr)                                    \
  ROCC_INSTRUCTION(XCUSTOM_ACC, y, mem_addr, rocc_rd, k_DO_LOAD);
#define doAccum(y, rocc_rd, data) \
  ROCC_INSTRUCTION(XCUSTOM_ACC, y, data, rocc_rd, k_DO_ACCUM);
  • src/main/c/rocc.h
#define ROCC_INSTRUCTION(X, rd, rs1, rs2, funct)                \
  ROCC_INSTRUCTION_R_R_R(X, rd, rs1, rs2, funct, 10, 11, 12)

// rd, rs1, and rs2 are data
// rd_n, rs_1, and rs2_n are the register numbers to use
#define ROCC_INSTRUCTION_R_R_R(X, rd, rs1, rs2, funct, rd_n, rs1_n, rs2_n) { \
    register uint64_t rd_  asm ("x" # rd_n);                            \
    register uint64_t rs1_ asm ("x" # rs1_n) = (uint64_t) rs1;          \
    register uint64_t rs2_ asm ("x" # rs2_n) = (uint64_t) rs2;          \
    asm volatile (                                                      \
        ".word " STR(CUSTOMX(X, rd_n, rs1_n, rs2_n, funct)) "\n\t"      \
        : "=r" (rd_)                                                    \
        : [_rs1] "r" (rs1_), [_rs2] "r" (rs2_));                        \
    rd = rd_;                                                           \
  }

translatorの動作

translatorは、仮想アドレスを物理アドレスに変換宇するためのハードウェアを制御している。このために、ptwを制御するためのフローを用意している。

  private val ptw = io.ptw(0)

  when (ptw.req.fire()) { state := s_ptw_resp }

  when (state === s_ptw_resp && ptw.resp.valid) {
    pte := ptw.resp.bits.pte
    state := s_resp
  }

CharacterCountの動作

これだけサンプルが無かったのでいろいろ探すしかないのだが、io.autlという説明のない場所にリクエストが渡っており、正直良く分からん。。。

以下のようなプログラムを作って動作を見てみたが、自分の考える答えと一致しないなあ。。。調査中。

  uint64_t data_addr;
  char str[] = "hello world";
  doTranslate (data_addr, str);
  printf ("[DoTranslate] virt: %p, phys: %p\n", str, (void *)data_addr);
  uint64_t count;
  countChar (count, data_addr);
  printf ("[countChar] 0x%lx\n", count);
  data_addr = (uint64_t)str;
  countChar (count, data_addr);
  printf ("[countChar] 0x%lx\n", count);

シミュレーション結果。

$ ./emulator-rocketchip-RoccExampleConfig ~/riscv64/riscv64-unknown-elf/riscv64-unknown-elf/bin/pk ../../rocket-rocc-examples/build/test-accumulator
[DoTranslate] virt: 0xfeefb00, phys: 0x8ffffb00
[countChar] 0x0
[countChar] 0x1