FPGA開発日記

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

System Verilogで記述されたRISC-VコアArianeを試す (2. Arianeのキャッシュサブシステム)

Arianeは、System Verilogで記述されたインオーダの6ステージRISC-Vパイプラインプロセッサだ。

Arianeのデザインは良くできていて、System Verilogできれいに記述されている。キャッシュサブシステムなどCPUの基本構成も綺麗に設計されているので、これを機に勉強してみることにした。

Arianeのデータキャッシュは以下のような構成になっている。

github.com

https://github.com/msyksphinz/ariane/tree/master/src/cache_subsystemを見てみる。

wt_dcacheの構成は以下のようになっていた。

f:id:msyksphinz:20190814221452p:plain
ArianeのDCache Subsystemの構成

構成としてはポートの数だけwt_dcache_ctrlが用意されていて、書き込み用のwt_dcache_wbufferが用意されている。そしてミスが発生した場合の処理を行うwt_dcache_missunitが接続されており、キャッシュの本体であるwt_dcache_memに接続されている。

wt_dcache_ctrlはRead Port向けの制御コントローラだ。基本的にステートマシンで動いている。このステートマシンも若干あいまいだけれども、REPLAYというのが良く分からない。MISSが起きたときに、何度もリクエストをリトライしている、ということだろうか?

f:id:msyksphinz:20190814224420p:plain
wt_dcache_ctrlのステートマシン。曖昧なところあり。

wt_dcache_missunitのステートマシン。こちらはMSHRのステートを制御しているものと思われる。

f:id:msyksphinz:20190814223912p:plain
wt_dcache_missunitのステートマシン。曖昧なところあり。

missunitには、すべてのポートから一気にMissの通知が来る。これを裁くのがArbiterだ。Arbiterはlzcとして実装されており、最も若いポートが優先的に使用される。

  assign miss_req_masked_d = (lock_reqs)  ? miss_req_masked_q      :
                             (mask_reads) ? miss_we_i & miss_req_i : miss_req_i;
  assign miss_is_write     = miss_we_i[miss_port_idx];

  // read port arbiter
  lzc #(
    .WIDTH ( NumPorts )
  ) i_lzc_reqs (
    .in_i    ( miss_req_masked_d ),
    .cnt_o   ( miss_port_idx     ),
    .empty_o (                   )
  );

メモリロードリクエストにおいてミスが発生した場合には、ステートマシンがLOAD_WAITに移行し、MSHRに値が割り当てられる。 同じmshrレジスタにすでに値が入っている場合はCollisionとして再度Replayの指示が出る。

    unique case (state_q)
      //////////////////////////////////
      // wait for misses / amo ops
      IDLE: begin
...
        // we've got a miss to handle
        end else if (|miss_req_masked_d) begin
          // this is a write miss, just pass through (but check whether write collides with MSHR)
...
          // this is a read miss, can only allocate 1 MSHR
          // in case of a load_ack we can accept a new miss, since the MSHR is being cleared
          end else if (!mshr_vld_q || load_ack) begin
            // replay the read request in case the address has collided with MSHR during the time the request was pending
            // i.e., the cache state may have been updated in the mean time due to a refill at the same CL address
            if (mshr_rdrd_collision_d[miss_port_idx]) begin
              miss_replay_o[miss_port_idx] = 1'b1;
            end else if (!tx_rdwr_collision) begin
              mem_data_req_o            = 1'b1;
              mem_data_o.rtype          = DCACHE_LOAD_REQ;
              update_lfsr               = all_ways_valid & mem_data_ack_i;// need to evict a random way
              mshr_allocate             = mem_data_ack_i;
              if (!mem_data_ack_i) begin
                state_d = LOAD_WAIT;
              end

そしてMSHRに必要な情報のバックアップが取られる、という仕組みだ。

  assign mshr_d.size            = (mshr_allocate)  ? miss_size_i    [miss_port_idx] : mshr_q.size;
  assign mshr_d.paddr           = (mshr_allocate)  ? miss_paddr_i   [miss_port_idx] : mshr_q.paddr;
  assign mshr_d.vld_bits        = (mshr_allocate)  ? miss_vld_bits_i[miss_port_idx] : mshr_q.vld_bits;
  assign mshr_d.id              = (mshr_allocate)  ? miss_id_i      [miss_port_idx] : mshr_q.id;
  assign mshr_d.nc              = (mshr_allocate)  ? miss_nc_i      [miss_port_idx] : mshr_q.nc;
  assign mshr_d.repl_way        = (mshr_allocate)  ? repl_way                       : mshr_q.repl_way;
  assign mshr_d.miss_port_idx   = (mshr_allocate)  ? miss_port_idx                  : mshr_q.miss_port_idx;
...
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
  if (!rst_ni) begin
...
    mshr_q                <= '0;
  end else begin
...
    mshr_q                <= mshr_d;
...