RoCC (Rocket Custom Coprocessor) のチュートリアルって意外と少ないので、調査するのに苦労する。
前回のサンプルプログラムは、一応意図通りに動作したのだが、いったいどのような仕組みになっているのか調査してみる。
test_accumulator.c の解析
test_accumulator.c
は、大きく2つのセクションに分かれている。accumulatorのテストと、 translatorのテストだ。
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);
- doWrite により、Accumulator内のレジスタ(addr=1)に0xdeadを書き込む。
- doReadにより、Accumulator内のレジスタ(addr=1)を読み込む。(0x0000deadが読み込まれる)。
- -data[0]+dead[1]を実行する。答えは、-0x0000dead+0x0000beef=0xffffe042
- Accumulator内レジスタに対して0xffffe042を加算する。 0xffffe042+0x0000dead=0x0000beef
- 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