FPGA開発日記

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

自作RISC-V CPUコア実装(LDQ/STQ間のフラッシュインタフェースの実装)

自作CPUの実装、ロードストア命令の物理アドレスが決まらないうえでのハザードが性能ボトルネックになっているのを見た。

次に検討するのは、LDQ/STQの最適化だ。以前の記事で説明したとおり、LDQとSTQの間でインタフェースを作成し、STQからのパイプラインが流れて段階でLDQのエントリをチェックするように変更する。

f:id:msyksphinz:20211110002323p:plain
// LDQ Speculative Load Hazard Check
assign ldq_haz_check_if.ex2_valid  = r_ex2_issue.valid & (r_ex2_issue.cat == decoder_inst_cat_pkg::INST_CAT_ST);
assign ldq_haz_check_if.ex2_paddr  = r_ex2_paddr;
assign ldq_haz_check_if.ex2_cmt_id = r_ex2_issue.cmt_id;
assign ldq_haz_check_if.ex2_grp_id = r_ex2_issue.grp_id;

ストア命令において、LDQに対して検索を行う。一方でLDQエントリはサーチ要求に対して当該物理アドレスがヒットするかをチェックする。

  // STQ -> LDQ Hazard check
  for (genvar p_idx = 0; p_idx < msrh_conf_pkg::LSU_INST_NUM; p_idx++) begin : st_ld_haz_loop
    logic ld_is_younger_than_st;
    msrh_rough_older_check
    st_pipe_ldq_older_check
      (
       .i_cmt_id0 (w_ldq_entries[l_idx].cmt_id),
       .i_grp_id0 (w_ldq_entries[l_idx].grp_id),

       .i_cmt_id1 (ldq_haz_check_if[p_idx].ex2_cmt_id),
       .i_grp_id1 (ldq_haz_check_if[p_idx].ex2_grp_id),

       .o_0_older_than_1 (ld_is_younger_than_st)
       );

    logic   w_ex2_same_dw;
    assign w_ex2_same_dw = |(msrh_lsu_pkg::gen_dw(ldq_haz_check_if[p_idx].ex2_size, ldq_haz_check_if[p_idx].ex2_paddr[2:0]) &
                             msrh_lsu_pkg::gen_dw(w_ldq_entries[l_idx].size, w_ldq_entries[l_idx].paddr[2:0]));
    assign w_ex2_ldq_stq_haz_vld[p_idx][l_idx] = ldq_haz_check_if[p_idx].ex2_valid &
                                                 ld_is_younger_than_st &
                                                 (ldq_haz_check_if[p_idx].ex2_paddr[riscv_pkg::PADDR_W-1: 3] == w_ldq_entries[l_idx].paddr[riscv_pkg::PADDR_W-1: 3]) & w_ex2_same_dw;
  end // block: st_ld_haz_loop

rough_older_checkとういうのはどちらの命令が若いかをチェックするためのモジュールで、cmt_id, grp_idの大小比較で命令の順序を判定している。STQのエントリの方がLDQのエントリよりも古ければ、LDQは投機的に実行されたことになり、さらに物理アドレスの範囲が一致していればLDQの投機実行結果を取り消すことになる。

そのとき、LDQの投機実行命令の内もっとも古い命令を抽出してその命令IDをSTQに伝える。 STQはその命令IDをROBに伝え、それらの命令から以降をフラッシュしてもらうように伝えるという仕組みで、誤った投機結果を打ち消すようになっている。

// ==================
// LDQ Flush Hazard
// ==================
generate for (genvar p_idx = 0; p_idx < msrh_conf_pkg::LSU_INST_NUM; p_idx++) begin : ldq_stq_haz_loop
  msrh_entry_selector
    #(
      .ENTRY_SIZE (msrh_conf_pkg::LDQ_SIZE)
      )
  u_entry_selector
    (
     .i_oh_ptr       (w_out_ptr_oh),
     .i_entry_valids (w_ex2_ldq_stq_haz_vld[p_idx]),
     .o_entry_valid  (w_ex2_ldq_stq_haz_vld_oh[p_idx])
     );

  ldq_entry_t w_sel_ldq_entry;

  bit_oh_or
    #(
      .T     (ldq_entry_t),
      .WORDS (msrh_conf_pkg::LDQ_SIZE)
      )
  u_flush_sel
    (
     .i_oh   (w_ex2_ldq_stq_haz_vld_oh[p_idx]),
     .i_data (w_ldq_entries),
     .o_selected(w_sel_ldq_entry)
     );

  always_ff @ (posedge i_clk, negedge i_reset_n) begin
    if (!i_reset_n) begin
      ldq_haz_check_if[p_idx].ex3_haz_valid <= 1'b0;
    end else begin
      ldq_haz_check_if[p_idx].ex3_haz_valid  <= |w_ex2_ldq_stq_haz_vld[p_idx];
      ldq_haz_check_if[p_idx].ex3_haz_cmt_id <= w_sel_ldq_entry.cmt_id;
      ldq_haz_check_if[p_idx].ex3_haz_grp_id <= w_sel_ldq_entry.grp_id;
    end
  end

end
endgenerate