FPGA開発日記

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

自作RISC-V OoOコアの分岐予測性能解析用Performance Monitorの作成

f:id:msyksphinz:20211004232850p:plain

こういう波形見ると頭がバグってきますよね。

ちまちまと自作RISC-Vコアを実装している。DhrystoneがPASSできるようになったが、まだまだ性能的にはひどいもんだ。

特に分岐予測器を入れていないのがひどい。現在簡単なBTBとBimodal Predictorを実装しているが、適当に実装したところ思ったよりも性能が上がらなかった。 何が起きているのか見てみるために、もう少し詳細なログを取ってみる。

以下はその一端。比較分岐命令を57回実行して30回しかヒットしなかったって。プププ。(それよりもUnconditionalをノーケアなので全く予測できていないのがひどい)。

"               20386" : {
  "commit" : {  "cmt" :   108,   "inst" :   349,   "dead" :   225  },
  "icache" : {  "request" :   439,   "hit" :   409,   "miss" :    30  },
  "dcache" : {
    "port[0]" : {    "req" :     0,     "hit" :     0,     "miss" :     0,     "conflict" :     0    }
    "port[1]" : {    "req" :     0,     "hit" :     0,     "miss" :     0,     "conflict" :     0    }
    "port[2]" : {    "req" :     0,     "hit" :     0,     "miss" :     0,     "conflict" :     0    }
    "port[3]" : {    "req" :    43,     "hit" :    32,     "miss" :     8,     "conflict" :     3    }
    "port[4]" : {    "req" :    82,     "hit" :    68,     "miss" :     5,     "conflict" :     9    }
    "port[5]" : {    "req" :    63,     "hit" :    45,     "miss" :     1,     "conflict" :    17    }
  },
  "branch" : {    "execute" :   138,     "cmp" : { "execute" :    57, "hit" :    30 },     "uncond" : { "execute" :    81, "hit" :     0 },   },
}

やはり分岐命令が実行されたとき、その命令の場所、命令の種類、分岐結果(Taken / NotTaken)、そして予測の結果を一覧で表示すると分かりやすいだろう。早速やってみる。 分岐命令のパイプラインにロガーを突っ込んでひたすらファイルにダンプしていく。以下はそのコード。

integer bim_fp;
initial begin
  bim_fp = $fopen("bru_detail.log", "w");
end

always_ff @ (negedge i_clk, negedge i_reset_n) begin
  if (i_reset_n) begin
    if (ex3_br_upd_if.update) begin
      if (r_ex3_pipe_ctrl.op != OP__) begin
        $fwrite(bim_fp, "%t : pc_vaddr = %08x, target_addr = %08x, %s, bim=%1d, %s, DASM(0x%08x)\n",
                $time,
                r_ex3_issue.pc_addr,
                ex3_br_upd_if.target_vaddr,
                r_ex3_result ? "Taken   " : "NotTaken",
                ex3_br_upd_if.bim_value,
                ex3_br_upd_if.mispredict ? "Miss" : "Succ",
                r_ex3_issue.inst);
      end
    end
  end
end // always_ff @ (negedge i_clk, negedge i_reset_n)

final begin
  $fclose(bim_fp);
end

その結果、下記のようなログが撮れるようになった。命令の表記部分はspike-dasmで加工してある。

               13362 : pc_vaddr = 00800027d8, target_addr = 00800027da, NotTaken, bim=0, Succ, beqz    a5, pc + 20
               13370 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=0, Miss, bne     a2, a5, pc - 6
               13374 : pc_vaddr = 00800027fe, target_addr = 0080002802, NotTaken, bim=0, Succ, bgeu    a0, a2, pc - 20
               13406 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=1, Miss, bne     a2, a5, pc - 6
               13450 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=2, Succ, bne     a2, a5, pc - 6
               13454 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=2, Succ, bne     a2, a5, pc - 6
               13462 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=2, Succ, bne     a2, a5, pc - 6
               13470 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=2, Succ, bne     a2, a5, pc - 6
               13478 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=2, Succ, bne     a2, a5, pc - 6
               13486 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=2, Succ, bne     a2, a5, pc - 6
               13494 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=3, Succ, bne     a2, a5, pc - 6
               13502 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=3, Succ, bne     a2, a5, pc - 6
               13510 : pc_vaddr = 00800027e6, target_addr = 00800027e0, Taken   , bim=3, Succ, bne     a2, a5, pc - 6

最初の単純なループは問題なく予測できているようだ。ではどこで大幅な性能低下となっているのか。問題となるのは以下の部分だ。

               17750 : pc_vaddr = 008000290e, target_addr = 0080002900, Taken   , bim=1, Miss, beq     a5, a4, pc - 14
               17818 : pc_vaddr = 008000290c, target_addr = 008000290e, NotTaken, bim=1, Succ, beqz    a5, pc + 14
               17830 : pc_vaddr = 008000290e, target_addr = 0080002900, Taken   , bim=1, Miss, beq     a5, a4, pc - 14
               17902 : pc_vaddr = 008000290c, target_addr = 008000290e, NotTaken, bim=2, Miss, beqz    a5, pc + 14
               17910 : pc_vaddr = 008000290c, target_addr = 008000290e, NotTaken, bim=2, Miss, beqz    a5, pc + 14
               17962 : pc_vaddr = 008000290e, target_addr = 0080002900, Taken   , bim=2, Succ, beq     a5, a4, pc - 14
               17982 : pc_vaddr = 008000290c, target_addr = 008000290e, NotTaken, bim=1, Succ, beqz    a5, pc + 14
               17994 : pc_vaddr = 008000290e, target_addr = 0080002900, Taken   , bim=1, Miss, beq     a5, a4, pc - 14
               18070 : pc_vaddr = 008000290c, target_addr = 008000290e, NotTaken, bim=0, Succ, beqz    a5, pc + 14
               18082 : pc_vaddr = 008000290e, target_addr = 0080002900, Taken   , bim=0, Miss, beq     a5, a4, pc - 14

うえ、0x008000290eの分岐命令が全く学習されていない。そしてこれは「学習していない」のではなく、「0x008000290cの情報更新に引っ張られてしまい正しく学習結果を取得できていない」というのが正しい気がしてきた。 つまり、同じキャッシュライン上で2種類の分岐命令が存在している場合、現在は最初の分岐命令の学習結果のみを抽出してそのキャッシュラインの代表として使っているが、その結果別の分岐命令の学習結果が活用されていない。 この場合、同一キャッシュライン内であっても命令毎に学習結果を取得する必要があるだろう。分岐予測器が大きくなるがやむを得ない。