ちまちまと自作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パイプラインが同時に別のアドレスに対してリクエストを出すと片方をコンフリクトとしている)。これも必要に応じてさらにバンク分けした方が性能は上がるかもしれない。