SonicBOOM (BOOMv3)のデザインを読み解いていきたいと思う。まずはいくつか小さなマイクロベンチマークプログラムを流して、データパスの流れを追っていきたいと思う。
一番基本となるのは加算をチェインだろう。依存関係の加算を連続して実行すると加算器のレイテンシとフォワーディングの様子が見えてくる。これをまずは解析する。以下のようなプログラムを作ってみよう。
test.S
.section .text .global simple_add simple_add: addi x10, x0, 1 addi x11, x10, 2 addi x12, x11, 3 addi x13, x12, 4 addi x14, x13, 5 addi x15, x14, 6 addi x16, x15, 7 addi x17, x16, 8 addi x18, x17, 9 mv x10, x18 ret
これを呼び出すような形でC言語のmain()
を作り、これをコンパイルしてテストベンチプログラムを作成する。2回呼び出しているのはキャッシュミスによるストールの影響を無くすため。
main.c
#include <stdio.h> extern int simple_add(); int main() { printf("ans = %d\n", simple_add()); printf("ans = %d\n", simple_add()); return 0; }
Makefile
(CRTにはriscv-tests/benchmarks
のものをそのまま利用した。pk
を使えばもう少し簡単にできそうだが...)
all: riscv64-unknown-elf-gcc -static -mcmodel=medany -fno-common \ -fno-builtin-printf -nostdlib -nostartfiles -lm -lgcc -T ../common/test.ld \ ../common/syscalls.c ../common/crt.S \ -I../common/ \ main.c test.S -o main.elf riscv64-unknown-elf-objdump -D main.elf > main.dmp
実行してみる。以下のコマンドでシミュレーションすることができる。ただしVCDを出力するためにはDebugビルドが必要だ。
./simulator-chipyard-MediumBoomConfig-debug +verbose --vcd simple_add.vcd ${main.elfの含まれているディレクトリ}/main.elf 2>&1 | tee simple_add.log
一応最後までシミュレーションすることができた。波形を確認してみる。しかしこれではさっぱりわからない。対象テストベンチに対してブートストラップのコードが多すぎるため、どこで対象となる加算が実行されているのか分からないのだ。
BOOMのログはテキストファイルの表記も分かりにくい。どのタイミングでどの命令が実行されたかも取得できないので、いくつかログのフォーマットを変えることにした。
3 0x0000000080000004 (0x4181) x 3 0x0000000000000000 3 0x0000000080000006 (0x4201) x 4 0x0000000000000000 3 0x0000000080000008 (0x4281) x 5 0x0000000000000000 3 0x000000008000000a (0x4301) x 6 0x0000000000000000 3 0x000000008000000c (0x4381) x 7 0x0000000000000000 3 0x000000008000000e (0x4401) x 8 0x0000000000000000 3 0x0000000080000010 (0x4481) x 9 0x0000000000000000 3 0x0000000080000012 (0x4501) x10 0x0000000000000000 3 0x0000000080000014 (0x4581) x11 0x0000000000000000 3 0x0000000080000016 (0x4601) x12 0x0000000000000000 ...
- 先頭にタイミング情報を載せる。これはCSRから
time
レジスタの値を取得すれば良かろう。 spike-dasm
を使用するために(0x...)
の部分をDASM(0x...)
に変更する。これによりspike-dasm
の逆アセンブル対象となる。
diff --git a/src/main/scala/common/parameters.scala b/src/main/scala/common/parameters.scala index 1a0c25fd..8d7c910c 100644 --- a/src/main/scala/common/parameters.scala +++ b/src/main/scala/common/parameters.scala @@ -92,7 +92,7 @@ case class BoomCoreParams( clockGate: Boolean = false, /* debug stuff */ - enableCommitLogPrintf: Boolean = false, + enableCommitLogPrintf: Boolean = true, enableBranchPrintf: Boolean = false, enableMemtracePrintf: Boolean = false diff --git a/src/main/scala/exu/core.scala b/src/main/scala/exu/core.scala index bd532526..140afde5 100644 --- a/src/main/scala/exu/core.scala +++ b/src/main/scala/exu/core.scala @@ -1331,14 +1331,15 @@ class BoomCore(implicit p: Parameters) extends BoomModule // To allow for diffs against spike :/ def printf_inst(uop: MicroOp) = { when (uop.is_rvc) { - printf("(0x%x)", uop.debug_inst(15,0)) + printf("DASM(0x%x)", uop.debug_inst(15,0)) } .otherwise { - printf("(0x%x)", uop.debug_inst) + printf("DASM(0x%x)", uop.debug_inst) } } when (rob.io.commit.arch_valids(w)) { - printf("%d 0x%x ", + printf("%d %d 0x%x ", + csr.io.time, priv, Sext(rob.io.commit.uops(w).debug_pc(vaddrBits-1,0), xLen)) printf_inst(rob.io.commit.uops(w))
これで再度コンパイルしてVerilator
バイナリを生成し、シミュレーションを行う。この結果ログは以下のようなフォーマットに変更された。これをspike-dasm
で理フォーマットすると以下のようになる。
199 3 0x0000000000010074 DASM(0x0005a023) 201 3 0x0000000000010078 DASM(0x0010051b) x10 0x0000000000000001 202 3 0x000000000001007c DASM(0x01f51513) x10 0x0000000080000000 214 3 0x0000000000010080 DASM(0x34151073) 226 3 0x0000000000010084 DASM(0xf1402573) x10 0x0000000000000000 240 3 0x0000000000010088 DASM(0x00000597) x11 0x0000000000010088 246 3 0x000000000001008c DASM(0x01458593) x11 0x000000000001009c 247 3 0x0000000000010090 DASM(0x08000613) x12 0x0000000000000080 253 3 0x0000000000010094 DASM(0x30063073) 265 3 0x0000000000010098 DASM(0x30200073) 295 3 0x0000000080000000 DASM(0x4081) x 1 0x0000000000000000 295 3 0x0000000080000002 DASM(0x4101) x 2 0x0000000000000000 296 3 0x0000000080000004 DASM(0x4181) x 3 0x0000000000000000
199 3 0x0000000000010074 sw zero, 0(a1) 201 3 0x0000000000010078 addiw a0, zero, 1 x10 0x0000000000000001 202 3 0x000000000001007c slli a0, a0, 31 x10 0x0000000080000000 214 3 0x0000000000010080 csrw mepc, a0 226 3 0x0000000000010084 csrr a0, mhartid x10 0x0000000000000000 240 3 0x0000000000010088 auipc a1, 0x0 x11 0x0000000000010088 246 3 0x000000000001008c addi a1, a1, 20 x11 0x000000000001009c 247 3 0x0000000000010090 li a2, 128 x12 0x0000000000000080 253 3 0x0000000000010094 csrc mstatus, a2 265 3 0x0000000000010098 mret 295 3 0x0000000080000000 c.li ra, 0 x 1 0x0000000000000000 295 3 0x0000000080000002 c.li sp, 0 x 2 0x0000000000000000 296 3 0x0000000080000004 c.li gp, 0 x 3 0x0000000000000000
これでタイミングを見ることができるようになった。そして対象となるテストベンチの場所はここだ。
3826 3 0x000000008000322e li a0, 1 x10 0x0000000000000001 3827 3 0x0000000080003232 addi a1, a0, 2 x11 0x0000000000000003 3828 3 0x0000000080003236 addi a2, a1, 3 x12 0x0000000000000006 3829 3 0x000000008000323a addi a3, a2, 4 x13 0x000000000000000a 3830 3 0x000000008000323e addi a4, a3, 5 x14 0x000000000000000f 3831 3 0x0000000080003242 addi a5, a4, 6 x15 0x0000000000000015 3832 3 0x0000000080003246 addi a6, a5, 7 x16 0x000000000000001c 3833 3 0x000000008000324a addi a7, a6, 8 x17 0x0000000000000024 3834 3 0x000000008000324e addi s2, a7, 9 x18 0x000000000000002d
CSR.time
が3826である場所を確認する。この時の物理レジスタ書き込み状態は波形は以下のように読み取ることができた。なるほど。この周辺を解析する。そしてレジスタ書き込みは1サイクル毎に行われており、依存関係のある加算でもストールなしで実行できていることが分かる。次はこのフォワーディングパスの作り方を読み解いていこうと思う。