FPGA開発日記

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

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (15. FetchCachePluginをSystemVerilogに変換する)

NaxRiscvの実装を解析しながら、SystemVerilog化することで理解を深めていこうと思う。

FetchCachePluginには、いくつかのRAMが定義されている。

  reg [63:0] FetchCachePlugin_logic_banks_0_mem [0:511];
  reg [63:0] FetchCachePlugin_logic_banks_1_mem [0:511];
  reg [63:0] FetchCachePlugin_logic_banks_2_mem [0:511];
  reg [63:0] FetchCachePlugin_logic_banks_3_mem [0:511];
  (* ram_style = "distributed" *) reg [21:0] FetchCachePlugin_logic_ways_0_mem [0:63];
  (* ram_style = "distributed" *) reg [21:0] FetchCachePlugin_logic_ways_1_mem [0:63];
  (* ram_style = "distributed" *) reg [21:0] FetchCachePlugin_logic_ways_2_mem [0:63];
  (* ram_style = "distributed" *) reg [21:0] FetchCachePlugin_logic_ways_3_mem [0:63];
  (* ram_style = "distributed" *) reg [2:0] FetchCachePlugin_logic_plru_ram [0:63];

FetchCachePlugin_logic_banks_*_memはおそらく命令キャッシュのデータを格納する本体だと思う。

  • 書き込み時動作:
    • 書き込み条件 : (FetchCachePlugin_mem_rsp_valid && (FetchCachePlugin_logic_refill_wayToAllocate == 自身のway));
    • アドレス:{FetchCachePlugin_logic_refill_address[11 : 6],FetchCachePlugin_logic_refill_wordIndex};
  • 読み込み時動作:
    • 読み込み条件:全バンク共通して (! (FetchPlugin_stages_0_valid && (! FetchPlugin_stages_0_ready)))
    • アドレス:全メモリ共通して FetchPlugin_stages_0_Fetch_FETCH_PC[11 : 3];

例えば、wordIndexが3ビットならば、1回のキャッシュブロックフェッチで、連続した8エントリが書き込まれるっぽいな。

それぞれのメモリは、バンクというよりもWayとして、PLRUで格納するアドレスを決めているっぽいな。

一方で、FetchCachePlugin_logic_ways_0_memはタグ情報を格納するのだと思う。

  • 書き込み時動作:
    • 書き込み条件:FetchCachePlugin_logic_refill_fire & FetchCachePlugin_logic_refill_wayToAllocate で示されるWayのメモリ
    • アドレス:全Wayで共通して FetchCachePlugin_logic_refill_address
  • 読み込み時動作:
    • 読み込み条件:常時
    • 読み込みアドレス:FetchPlugin_stages_0_Fetch_FETCH_PC[11 : 6];

なるほど、構成はなんとなくわかった気がする。

SiFive Intelligence XM seriesのMatrix Engineについて

SiFiveが新しいRISC-Vプロセッサの系列としてIntelligence XMを発表した。

www.sifive.com

XMのXはIntelligenceシリーズのXシリーズから来ていて、MはMatrix拡張のことと考えて間違いない。

ただ、Matrix Engineの命令に何を使っているのかここでははっきりと言及がない。SKLも色々調べたがまだダウンロードできないようだ。

最初に考えたのは、T-Headが推し進めているMatrix Extensionではないかということだ。これはベクトル命令の一部を切り出して行列演算用に再定義したものだ。

github.com

もう一つは、SiFiveが作った全く独自の拡張ではないか、というところ。以下のページに、Xシリーズが搭載している独自の拡張命令がある(sf.***命令とかかなりいやらしい...)

www.sifive.com

  • SiFive Int8 Matrix Multiplication Extensions Specification
  • FP32-to-int8 Ranged Clip Instructions (Xsfvfnrclipxfqf) Extension Specification
  • Matrix Multiply Accumulate Instruction (Xsfvfwmaccqqq) Extension Specification

発表時期的に2023年の9月くらいなので、X390にそのまま搭載されているものと思われる。 無料で見ることのできるX200のカスタムオプションについても、Xsfvfwmaccqqq の文字が見えるので、上記の命令はX200とX300にすでに搭載されている、ということだろう。 そうすると、全くSiFive独自に新しい命令を定義してMatrix Engine用に実装した、というのが自然な流れな気がする(公開しろよ...)

SiFive自身が命令の分断化を推し進めているような気がするのだが、大丈夫だろうか...

「PCI Express設計の基礎と応用」を買った

今までなるべく避けていた、PCI Expressについていよいよ勉強しなければならないので買った。 基本的な用語もわかっていないので、その辺を解説してくれるのがありがたい。ルート・コンプレックス、レーン数、リンク数、なんとかかんとか。。。

とりあえず一通り何が必要なのかを確認していきたい。

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (14. FetchPluginをSystemVerilogに変換する)

NaxRiscvの実装を解析しながら、SystemVerilog化することで理解を深めていこうと思う。

FetchPluginは、PCPluginから渡されたPCをもとに命令フェッチを行うモジュールのように思われる。

module NaxRiscv_FetchPlugin
  (
   input logic  i_clk,
   input logic  i_reset_n,

   fetchplugin_if.subordinate fetch_stage0_if,
   fetchcache_if.manager      fetchcache_if,
   lsu2plugin_if.fetch        lsu2_if,
   privileged_if.fetch        privileged_if,
   commit_if.fetch            commit_if,
   envcall_if.fetch           envcall_if,
   mmu_if.fetch               mmu_if,
   btb_if.fetch               btb_if
   );

フェッチステージは0~2の3ステージに分けられており、命令キャッシュやGShare, BTBなどのアクセスも含んでいる。

ちょっと意味不明なのは、GShareのハッシュの計算がPCのビット位置を全て入れ替えたもので行われていること。 これはなんでだろう?

assign _zz_w_stage_0_gshare_hash = fetch_stage0_if.payload.pc[15 : 3];

assign w_stage_0_gshare_hash = _zz_w_stage_0_gshare_hash[0:12] ^ w_stages_0_branch_history[12: 0];

1ステージ目から徐々にSystemVerilogに変換している。例えばステージ1はこんな感じ。

always_ff @ (posedge i_clk) begin
  if(fetch_stage0_if.ready_output) begin
    r_stage1.pc             <= fetch_stage0_if.payload.pc;
    r_stage1.gshare_hash    <= w_stage_0_gshare_hash;
    r_stage1.branch_history <= w_stages_0_branch_history;
    r_stage1.ways           <= w_stage0.ways;
    r_stage1.gshare_bypass  <= w_stage0.gshare_bypass;
  end // if (fetch_stage0_if.ready_output)                                                                                                                                                                                                                                                                                                                                                
end // always_ff @ (posedge i_clk)                                                                                                                                                                                                                                                                                                                                                        

命令フェッチを止める条件はいくつかあるようだ。外部のモジュールからのhaltreqに応じて命令フェッチを止める。

assign w_haltreq_fetchcache_0 = !fetchcache_if.invalidate_done || fetchcache_if.invalidate_requested;
assign w_haltreq_fetchcache_1 = fetchcache_if.refill_valid;
assign w_haltreq_fetchcache_2 = fetchcache_if.read_ctrl_redoIt;
assign w_haltreq_fetchcache_3 = !fetchcache_if.translationPort_wake;
assign w_haltreq_lsu          = lsu2_if.flush_busy;
assign w_haltreq_privileged   = (priviledged_if.state != NaxRiscv_pkg::PrivilegedIDLE) |
                                (commit_if.reschedule_valid && commit_if.reschedule_trap));
assign w_haltreq_envcall    = envcall_if.flushes_state != NaxRiscv_pkg::EnvCallIDLE;
assign w_haltreq_mmu        = !mmu_if.invalidate_done || mmu_if.invalidate_requested;

assign when_Pipeline_l278_9 = w_haltreq_mmu | w_haltreq_envcall | w_haltreq_privileged | w_haltreq_lsu |
                              w_haltreq_fetchcache_0 | w_haltreq_fetchcache_1 | w_haltreq_fetchcache_2 | w_haltreq_fetchcache_3;

次のステージの実装も、少しずつ進めていこう。

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (13. PCPluginをSystemVerilogに変換する)

NaxRiscvの実装を解析しながら、SystemVerilog化することで理解を深めていこうと思う。

まずはPCPluginから。このモジュールの役目は、PCの保持とアップデート、各種ハザードに応じてPCを更新することだ。

module NaxRiscv_PcPlugin
  (
   input logic  i_clk,
   input logic  i_reset_n,

   PcPlugin_if.manager        pcplugin_if, // output                                                                                                                                                                                                                                                                                                           
   );

基本的には、PCを送出すると更新することになる。

assign output_fire  = pcplugin_if.valid && pcplugin_if.ready;
always_comb begin
  w_pc = r_pc + _zz_pc;
  if(w_jump_pcload_valid) begin
    w_pc = w_jump_pcload_pc;
  end

  if (r_pc_inc) begin
    w_pc[2 : 2] = 1'b0;
  end
  w_pc[1:0] = 2'b00;
end

always_ff ...
    if(output_fire) begin
      r_correction <= 1'b0;
    end else if(w_correction) begin
      r_correction <= 1'b1;
    end

    if(!pcplugin_if.valid && pcplugin_if.ready) begin
      r_pc_inc <= 1'b0;
    end else if(output_fire) begin
      r_pc_inc <= 1'b1;
    end else if(w_correction || w_pcPropagate) begin
      r_pc_inc <= 1'b0;
    end

各種ハザード通知に応じて、更新するPCを変えている。

always_comb begin
  if (PrivilegedPlugin_setup_jump_valid) begin
    w_jump_pcload_valid = 1'b1;
    w_jump_pcload_pc    = PrivilegedPlugin_setup_jump_payload_pc;
  end else if (CommitPlugin_setup_jump_valid) begin
    w_jump_pcload_valid = 1'b1;
    w_jump_pcload_pc    = CommitPlugin_setup_jump_payload_pc;
  end else if (DecoderPredictionPlugin_setup_decodeJump_valid) begin
    w_jump_pcload_valid = 1'b1;
    w_jump_pcload_pc    = DecoderPredictionPlugin_setup_decodeJump_payload_pc;
  end else if (AlignerPlugin_setup_sequenceJump_valid) begin
    w_jump_pcload_valid = 1'b1;
    w_jump_pcload_pc    = AlignerPlugin_setup_sequenceJump_payload_pc;
  end else if (FetchCachePlugin_setup_redoJump_valid) begin
    w_jump_pcload_valid = 1'b1;
    w_jump_pcload_pc    = FetchCachePlugin_setup_redoJump_payload_pc;
  end else if (BtbPlugin_setup_btbJump_valid) begin
    w_jump_pcload_valid = 1'b1;
    w_jump_pcload_pc    = BtbPlugin_setup_btbJump_payload_pc;
  end else begin
    w_jump_pcload_valid = 1'b0;
    w_jump_pcload_pc    = 40'h0;
  end
end // always_comb                                                                                                                                                                                                                                                                                                                                             

InitCounterは単純なカウンタだが、これはここに配置する意味はあるんだろうか?128サイクル待っているようなカウンタだ。

reg        [6:0]    r_init_counter;
wire                w_init_booted;

assign w_init_booted = r_init_counter[6];

always_ff @ (posedge i_clk, negedge i_reset_n) begin
  if (!i_reset_n) begin
    r_init_counter <= 7'h00;
  end else begin
    r_init_counter <= r_init_counter + ~w_init_booted;
  end
end

オープンソース・アウトオブオーダCPU NaxRiscvのドキュメントを読んでいく (5. 分岐予測ユニット)

NaxRiscvのドキュメントを、改めて位置から読んでみることにしたいと思う。

spinalhdl.github.io

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com


分岐予測

現在、分岐予測はパイプライン内の二か所で実装されている:

  • フェッチ予測 :BTBとGShare予測器で構成されている
  • デコード予測 :RAS予測を使用することができ、BTB予測がない場合は静的ジャンプとgshare分岐予測を行うことができる

以下は分岐予測パイプラインの図示である:

フェッチ予測

フェッチステージでは、BTBは与えられたフェッチワードについていくつかの予測を行う。 - ターゲットPC - 命令の性質(分岐かどうか) - 命令の最後の部分がワードのどのスライスであるか(スライスはRVCなしで4バイト、RVCありで2バイト)

この予測を使用し、最終的にはGShare予測を使用して、予測を適用する。

デコード予測

命令がフェッチされると、その段階での予測を改善するために使用できる情報がかなり多く得られる。

フェッチBTB予測がジャンプ/分岐を行わなかった場合、デコード予測は以下のいずれかを行う可能性がある。

  • 何も行わない
  • 静的ジャンプ(call,j)を適用する
  • GShare予測があった場合、その分岐を適用する
  • 命令がリターン(ret)の場合、RAS予測を適用する

フェッチBTBがジャンプ/分岐を行った場合、デコード予測は以下を行う可能性がある。

  • 命令がジャンプ/分岐でなかった場合、ジャンプ/分岐を元に戻す
  • RASが別の値を提供した場合、予測を修正する
  • 予測対象が正しかったと仮定する(分岐ターゲットを計算し、それを予測されたPCと比較するのは、組み合わせパスが多すぎる)

その後、予測結果はジャンプ/分岐の循環バッファに格納され、実行ユニットの分岐ロジックによって最終的な修正のために使用される。

RASは再スケジュール時にポインタを修復するが、投機的に更新された値は修復しない。

ジャンプ/分岐用循環バッファ

  • Push : デコード予測中
  • Read: 実行ユニットが分岐実行中に、最終的に誤った予測分岐を修正する
  • Pop: 命令確定後、分岐予測器の学習もトリガーする

オープンソース・アウトオブオーダCPU NaxRiscvのドキュメントを読んでいく (4. メモリアクセスユニットについて)

NaxRiscvのドキュメントを、改めて位置から読んでみることにしたいと思う。

spinalhdl.github.io

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com


メモリ・システム

ロードストアの統合

LSUの実装の特徴は以下の通りである。

  • LQ / SQ : 通常、それぞれ16
  • AGUからのロード : ロードレイテンシを削減するために、LQにロードパイプライン用のデータがない場合、LQのレジスタを介さずにAGUが直接、最新の計算結果を提供できる
  • ロードヒット予測 : ロードのレイテンシを削減するため(6サイクルから3サイクルに)、キャッシュヒット予測機能があり、特定の命令を推測的に起動する
  • ハザード予測 : ストアの場合、アドレスとデータは両方とも発行キューを通じて提供される。そのため、遅延したデータは遅延したアドレスも作成し、ストアからロードへのハザードが発生する可能性がある。これを削減するために、ハザード予測機能がロードに追加された。
  • ストア to ロード・バイパス : 指定されたロードが同じサイズの単一ストアに依存している場合、ロード・パイプラインはストアのライトバックを待たずにストア値をバイパスすることがある
  • 並列メモリ変換 : ロードの場合、レイテンシを低減するために、メモリ変換はキャッシュ・リードと並列に実行される。
  • 共有アドレス・パイプライン : ロードとストアは、仮想アドレスを変換し、ハザードをチェックするために同じパイプラインを使用する。

共有およびライトバックパイプラインの図をいくつか示す:

MMU

MMUの実装の特徴は以下の通りである。

  • 2D構成 : ページテーブルの各レベルに対して、パラメータ化された数のダイレクト・マップ・トランケーション・キャッシュを指定できる。
  • ハードウェア・リフィル : これは安価であるため
  • キャッシュ・ダイレクト・ヒット : タイミングを改善するために、命令キャッシュがMMU TLBストレージに対してウェイタグを直接確認できるようにする(面積を犠牲にして)。

RV32では、デフォルト構成は次のとおりである。

  • 4ウェイ*レベル0(4KBページ)TLBの32エントリ
  • 2ウェイ*レベル1(4MBページ)TLBの32エントリ

各ウェイを分散RAMに推論することで、TLBキャッシュの領域は低く抑えられる。

MMU設計の例をいくつか紹介しよう。

コヒーレンシ

CPU上では、メモリーのコヒーレンシは以下の方法で実装されている。

  • メモリ・ブロック(データ・キャッシュ内)には、Permissionレベルがある(unloaded < shared < unique[dirty])。
  • shared Permissionは読み取り専用アクセスのみを許可する。
  • unique Permissionは読み取り/書き込みアクセスを許可する。
  • データ・キャッシュが許可をアップグレードしたい場合、取得要求を発行する(リード・バス上)。
  • データ・キャッシュが領域を解放したい場合、リリース要求を発行する(ライト・バス上)
  • インターコネクトは、指定されたアドレスのデータ・キャッシュの権限をダウングレードできる(プローブバス上)
  • プローブ要求は、データキャッシュのストアパイプラインを再利用して処理される
  • CPUによるアトミックなロード/ストアアクセスを許可するために、ロックインターフェースにより指定されたアドレスの権限ダウングレードを防止できる

NaxRiscvネイティブのデータキャッシュ・インターフェースはメモリに対してカスタム仕様だが、Tilelinkにブリッジできるように作られている。...

SoCの観点では、複数のマスタを接続するために、L2コヒーレント・キャッシュまたはキャッシュレスのコヒーレンシ・ハブのいずれかを選択できる。

L2キャッシュ

L2キャッシュには以下の特性がある。

  • Tilelinkインターフェースの提供(CPU/DMA用にアップ、メモリ用にダウン)
  • L1と一体型(NaxRiscv用はD$のみ)であるため、必要なプローブのみを送信するが、常にL1のコピーを持つ。
  • PLRUラインの排除
  • ノンブロッキング
  • マルチウェイ
  • データメモリはシンプルなデュアルポートRAMとして実装(FPGAに最適
  • アドレス/ソースに応じてアクセスを選択的にキャッシュ可能(ビデオDMAアクセスをキャッシュしない場合に有用

デフォルト構成におけるタイミングは以下の通り: - ヒットレイテンシ 5サイクル - ミスレイテンシペナルティ 4サイクル

アーキテクチャは主に以下の通り:

  • リクエストは中央集権化されたタグフェッチ/コントローラに送られる。
  • そのコントローラは、以下に説明するように、いくつかのパイプライン/FSMに供給することができる。
  • プローブコントローラは、L1キャッシュからデータを削除する必要がある場合、L2キャッシュラインを退避させるか、または固有の許可を取得する必要がある場合に使用される
  • キャッシュラインを再読み込みするか、キャッシュされていない読み取りアクセスを行う必要がある場合は、down.a を読み込む
  • 例えば up.c.release の場合は、up.d に直接応答を送信する
  • データコレクタにリクエストを送信し、データコレクタはメインメモリ/キャッシュに書き込み、さらに up.d にデータを送信する
  • キャッシュヒットおよびキャッシュエビクト時に使用されるリードキャッシュにリクエストを送信する...