いろいろ勉強したCPUのデータプリフェッチャについて、理解を深めるために自作CPUにデータプリフェッチャを実装してみることにした。
まずは簡単なConstant Strideのプリフェッチだ。 LSUのパイプラインが1つ以上あるので、トレーニングテーブルの更新はその部分を気を付けなければならない。 つまり、2つのLSUが同時に同じPCを更新するときに、その学習を正しく更新しなければならない。 そのためにはもう少しアービトレーションが必要なのだが、とりあえずそれはおいておいて、2つのLSUが正しくテーブルを更新できるものとしてRTLを構築した。したがってまだこのRTLは合成できない。
localparam PC_TAG_LEN = 9; typedef struct packed { logic valid; logic [PC_TAG_LEN:1] pc_tag; logic [11: 0] last_line_offset; logic [11: 0] stride; logic [ 1: 0] confidence; } ipt_entry_t;
学習のところはとりあえず省略して、プリフェッチ・リクエストが生成された後のフィルタ部分が以下だ。
scariv_pkg::vaddr_t [15: 0] r_rr_filter; logic [ 3: 0] r_rr_push_ptr; logic [15: 0] w_hit_rr_filter; logic w_pref_gen_valid; generate for (genvar rr_idx = 0; rr_idx < 16; rr_idx++) begin : rr_filter_search_loop assign w_hit_rr_filter[rr_idx] = r_rr_filter[rr_idx][riscv_pkg::VADDR_W-1: $clog2(scariv_conf_pkg::DCACHE_DATA_W/8)] == r_pref_vaddr [riscv_pkg::VADDR_W-1: $clog2(scariv_conf_pkg::DCACHE_DATA_W/8)]; end endgenerate assign w_pref_gen_valid = |w_hit_rr_filter & (r_pref_state == PREF_GEN); always_ff @ (posedge i_clk, negedge i_reset_n) begin if (!i_reset_n) begin r_rr_push_ptr <= 'h0; end else begin if (r_pref_state == PREF_GEN) begin if (~|w_hit_rr_filter) begin r_rr_push_ptr <= r_rr_push_ptr + 'h1; r_rr_filter[r_rr_push_ptr] <= r_pref_vaddr; end end end // else: !if(!i_reset_n) end // always_ff @ (posedge i_clk, negedge i_reset_n)
最後に、LSUパイプラインがBusyであった場合の待ち合わせバッファを置いて、これでとりあえず簡易的なプリフェッチャは完成とする。
ring_fifo #(.T(scariv_pkg::vaddr_t)) u_pref_req_fifo ( .i_clk (i_clk ), .i_reset_n (i_reset_n ), .i_push (w_pref_gen_valid ), .i_data (r_pref_vaddr ), .o_empty (w_pref_ring_fifo_empty), .o_full ( ), .i_pop (w_pref_ring_fifo_pop ), .o_data (w_pref_pipe_vaddr ) ); always_comb begin o_pref_issue = 'h0; if (w_pref_gen_valid) begin o_pref_issue.valid = 1'b1; o_pref_issue.paddr = w_pref_pipe_vaddr; o_pref_issue.is_prefetch = 1'b1; end end
簡単なmemcpyで、その動作を確認してみよう。
一応プリフェッチは出るようになった。でもアドレスが何かおかしいな?デバッグしていく。