RoCCインタフェースを使って専用命令を作成し、ハードウェアアクセラレーションができるようになった。 とりあえず、まずは小手調べに、メモリからデータを読み込んで加算する専用ハードウェアを作成し、その性能を見てみよう。
作成するハードウェア
まずは小手調べだが、メモリから特定領域のデータを読み出し、その値をすべて加算するハードウェアを作成する。 C言語で記述すると以下のようになるが、これをChiselを使ってハードウェアを記述するわけだ。
uint64_t total = 0; for (int j = 0; j < max_count; j++) { total += num_array[j]; }
たったこれだけだ。非常にシンプルなのでハードウェアは作成しやすい。
専用ハードウェアのデバッグ
Chiselで記述したものをシミュレーションしているので、デバッグ文を挿入してその動作を見ている。例えば、ステートマシンの最初と最後にChiselで言うprintf()
を挿入しておけば、ログから確認できる。
when (io.cmd.fire()) { printf("MemTotalExample: Command Received. %x, %x\n", io.cmd.bits.rs1, io.cmd.bits.rs2) ... when (r_state === s_finish && io.resp.fire()) { r_state := s_idle printf("MemTotalExample: Finished.\n") }
実際にRoCCを接続した状態でRTLシミュレーションを実行すると、以下のようなログが表示される。
make CONFIG=RoccExampleConfig ./emulator-rocketchip-RoccExampleConfig +verbose ~/riscv64/riscv64-unknown-elf/riscv64-unknown-elf/bin/pk ../../rocket-rocc-examples/build/test-accumulator 2> rocc_memtotal.log
... C 0: 4505882 [1] pc=[0000010134] W[r11=000000008fffdaf0][1] R[r19=000000008fffdaf0] R[r 0=0000000000000000] inst=[00098593] DASM(00098593) C 0: 4505883 [1] pc=[0000010138] W[r12=0000000000000000][1] R[r 8=0000000000000001] R[r31=0000000000000403] inst=[fff40613] DASM(fff40613) C 0: 4505884 [1] pc=[000001013c] W[r10=000000008fffdaf0][1] R[r11=000000008fffdaf0] R[r12=0000000000000000] inst=[00c5f55b] DASM(00c5f55b) MemTotalExample: Command Received. 000000008fffdaf0, 0000000000000000 C 0: 4505885 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=0000000000000000] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) C 0: 4505886 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) C 0: 4505887 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) C 0: 4505888 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) MemTotalExample: Finished. C 0: 4505889 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) C 0: 4505890 [0] pc=[000001013c] W[r10=0000000000000000][1] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) C 0: 4505891 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) C 0: 4505892 [0] pc=[000001013c] W[r 0=000000008fffdaf0][0] R[r11=000000008fffdaf0] R[r12=000000008fffdaf0] inst=[00c5f55b] DASM(00c5f55b) ...
では、次のようなプログラムシーケンスを作って、データサイズを変えて専用ハードウェアを呼び出し、処理にかかるサイクル数を測定しよう。
for (int count = 0; count < 10; count++) { uint64_t total; memtotal (total, phy_data_addr, max_count-1); printf ("[countChar] %ld\n", total); max_count *= 2; }
次に、通常のプログラムを記述してサイクル数を測定する。以下がソフトウェア記述だ。
for (int count = 0; count < 10; count++) { volatile uint64_t start_cycle, stop_cycle; uint64_t total = 0; start_cycle = 0x0; // サイクルカウント用 for (int j = 0; j < max_count; j++) { total += num_array[j]; } stop_cycle = 0x0; // サイクルカウント用 printf ("[countChar] %ld\n", total); max_count *= 2; }
サイクル数の取得
加算するメモリサイズを1~512ワードまで変化させ、そのサイクル数を測定した。
専用ハードウェアで動作させた方法と、ソフトウェアで記述した方法のサイクル数をカウントするために、ログ中からプログラムカウンタをインデックスとしてサイクル数を引き出した。
grep -e 00010188 -e 000101b0 rocc_memtotal.log | sed 's/ */ /g' | cut -d' ' -f3 | awk '{if(NR%2==0) {print $1}else{print $1-start}}' grep MemTotal -B1 rocc_memtotal.log | awk '{if(NR%3==1){print $3}}' | awk '{if(NR%2==1){start=$1}else{print $1-start}}'
それぞれのメモリサイズでサイクル数を比較すると以下のようになった。
まあこの程度の機能じゃあ、ほとんどサイクル数は変わらないか。。。でも、きちんとスケールするプログラムが書けたのは良かった。