FPGA開発日記

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

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

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

一つの要因としては分岐予測を全く実装していないこと。これでは分岐が成立すると殆どの命令を破棄することになってしまう。まずは分岐予測を実装するところからだろう。

その前に、まずは性能について定量的なデータを取れるようにならなければならない。そのために、パフォーマンスモニタを実装することにした。これはシミュレーションにのみ使用する機能だ。

取得したい情報としては、

  • 一定期間内にどれだけの命令がコミットしているのか。有効なコミット数はいくつか。逆にいくつのコミットがパイプラインフラッシュにより無効化されたか。
  • 命令キャッシュのヒット率。どれくらいのアクセスがありどれくらいヒットしたか。
  • データキャッシュのヒット率。どのポートからどれくらいのアクセスがあり、どれくらいヒットしたか。
  • 分岐命令。どのくらいの分岐命令でフラッシュが発生したか。

これくらいを収集できればよかろう。1000サイクル毎に統計を出力する様にしてみる。

どういうフォーマットに仕様かと思ったが、とりあえずJSONで良かろう。各モジュールに1000サイクル毎にこれらの情報を蓄積するロガーを設ける。

logic [63: 0] r_cycle_count;
logic [63: 0] r_commit_count;
logic [63: 0] r_inst_count;
logic [63: 0] r_dead_count;

always_ff @ (negedge i_clk, negedge i_reset_n) begin
  if (!i_reset_n) begin
    r_commit_count <= 'h0;
    r_inst_count   <= 'h0;
    r_dead_count   <= 'h0;
    r_cycle_count  <= 'h0;
  end else begin
    r_cycle_count <= r_cycle_count + 'h1;
    if (r_cycle_count % sim_pkg::COUNT_UNIT == sim_pkg::COUNT_UNIT-1) begin
      r_commit_count <= 'h0;
      r_inst_count   <= 'h0;
      r_dead_count   <= 'h0;
    end else begin
      if (o_commit.commit) begin
        if (!o_commit.all_dead) begin
          r_commit_count <= r_commit_count + 'h1;
          r_inst_count <= r_inst_count + $countones(o_commit.grp_id & ~o_commit.dead_id);
        end else begin
          r_dead_count <= r_dead_count   + 'h1;
        end
      end
    end
  end
end


function void dump_perf (int fp);
  $fwrite(fp, "  \"commit\" : {");
  $fwrite(fp, "  \"cmt\" : %5d, ", r_commit_count);
  $fwrite(fp, "  \"inst\" : %5d, ", r_inst_count);
  $fwrite(fp, "  \"dead\" : %5d", r_dead_count);
  $fwrite(fp, "  },\n");
endfunction

で、これらを同じファイルにダンプするようにする。

always_ff @ (negedge w_clk, negedge w_msrh_reset_n) begin
  if (!w_msrh_reset_n) begin
    r_cycle_count <= 'h0;
  end else begin
    r_cycle_count <= r_cycle_count + 'h1;

    if (r_cycle_count % sim_pkg::COUNT_UNIT == sim_pkg::COUNT_UNIT-1) begin
      $fwrite(perf_fp, "\"%t\" : {\n", $time);

      // Commit Rate
      u_msrh_tile_wrapper.u_msrh_tile.u_rob.dump_perf(perf_fp);
      // ICache
      u_msrh_tile_wrapper.u_msrh_tile.u_frontend.u_msrh_icache.dump_perf(perf_fp);
      // DCache
      u_msrh_tile_wrapper.u_msrh_tile.u_msrh_lsu_top.u_msrh_dcache.dump_perf(perf_fp);
      // Branch
      u_msrh_tile_wrapper.u_msrh_tile.u_msrh_bru.u_bru_pipe.dump_perf(perf_fp);

      $fwrite(perf_fp, "\}\n");
    end
  end // else: !if(!w_msrh_reset_n)
end // always_ff @ (negedge w_clk, negedge w_msrh_reset_n)

これで再びDhrystoneを走らせてみた。以下のようなログが1000サイクル毎に採れる。

"               56386" : {
  "commit" : {  "cmt" :   132,   "inst" :   434,   "dead" :   278  },
  "icache" : {  "request" :   601,   "hit" :   601,   "miss" :     0  },
  "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" :    54,     "hit" :    42,     "miss" :     0,     "conflict" :    12    "}
    "port[4]" : {    "req" :   130,     "hit" :   121,     "miss" :     0,     "conflict" :     9    "}
    "port[5]" : {    "req" :    92,     "hit" :    60,     "miss" :     0,     "conflict" :    32    "}
  },
  "branch" : {    "execute" :   195,     "hit" :    42   },
}

うーん、ほらね。132回のコミットが有効で、死んだコミットが278回。つまり倍以上フラッシュで死んでるじゃん。 その要因はやはり分岐命令。 分岐命令195回のうち42回のみが「分岐不成立」つまりフラッシュする必要なかったタイプで、それ以外は全部パイプラインフラッシュが発生。こりゃこれだけ遅くもなるわ。 まずは分岐予測器を作ってみるところから。

一方で命令キャッシュのヒット率は悪くない。まあDhrystoneくらいの大きさだったら普通に命令キャッシュの中に全部収まっちゃって毎回ヒットするでしょ見たいになっている。 データキャッシュも結構しっかりヒットしている。問題はいろんなポートでコンフリクトが起きていること(2つのLSUパイプラインが同時に別のアドレスに対してリクエストを出すと片方をコンフリクトとしている)。これも必要に応じてさらにバンク分けした方が性能は上がるかもしれない。