Arianeは、System Verilogで記述されたインオーダの6ステージRISC-Vパイプラインプロセッサだ。
Arianeのデザインは良くできていて、System Verilogできれいに記述されている。キャッシュサブシステムなどCPUの基本構成も綺麗に設計されているので、これを機に勉強してみることにした。
Arianeのデータキャッシュは以下のような構成になっている。
https://github.com/msyksphinz/ariane/tree/master/src/cache_subsystem
を見てみる。
wt_dcacheの構成は以下のようになっていた。
構成としてはポートの数だけwt_dcache_ctrl
が用意されていて、書き込み用のwt_dcache_wbuffer
が用意されている。そしてミスが発生した場合の処理を行うwt_dcache_missunit
が接続されており、キャッシュの本体であるwt_dcache_mem
に接続されている。
wt_dcache_ctrl
はRead Port向けの制御コントローラだ。基本的にステートマシンで動いている。このステートマシンも若干あいまいだけれども、REPLAYというのが良く分からない。MISSが起きたときに、何度もリクエストをリトライしている、ということだろうか?
wt_dcache_missunit
のステートマシン。こちらはMSHRのステートを制御しているものと思われる。
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; ...