FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

SonicBOOMのデザインを読み解く (算術演算のデータパスとレイテンシ)

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

一応最後までシミュレーションすることができた。波形を確認してみる。しかしこれではさっぱりわからない。対象テストベンチに対してブートストラップのコードが多すぎるため、どこで対象となる加算が実行されているのか分からないのだ。

f:id:msyksphinz:20201227231109p:plain

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
...
  1. 先頭にタイミング情報を載せる。これはCSRからtimeレジスタの値を取得すれば良かろう。
  2. 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サイクル毎に行われており、依存関係のある加算でもストールなしで実行できていることが分かる。次はこのフォワーディングパスの作り方を読み解いていこうと思う。

f:id:msyksphinz:20201227231139p:plain