FPGA開発日記

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

RTLの情報をC++プログラムにバックポートする方法の調査

自作CPUとかをやっていて、RTLとISS(命令セットシミュレータ)でどうしても誤差が生じる部分と言えば、真っ先に思いつくのがサイクル計測部分だ。 RISC-VのCPUとかを設計していると、ベンチマークを流している場合に必ずMCYCLEが一致しない問題にぶちあたる。

MCYCLEの比較を無視すればなんとかなるが、さらにMCYCLEの値を汎用レジスタに格納してメモリにストアしてしまったら大変。そこから先はロード命令や通常の命令ですら一致が取れなくなる。 そこでRTL側のMCYCLEの情報を抜き出してISSにバックポートするという方法を取る。RTLのMCYCLEの値をISSに転送して上書きすることでISSとRTLのつじつまを合わせるという作戦だ。

この方法はベンチマークソフトウェアなどでの検証を継続できるというメリットはあるものの、RTLのMCYCLEの検証はISSとの比較では決してできないという問題がある。常にRTL側を信頼するという前提に立っているので、一致検証以外の方法でMCYCLEの値は検証する必要がある。

それはさておき、SystemVerilogで書いている自作CPUと、SpikeなどのISSを接続する方法について調査した。

SystemVerilog側ではコミットが発生する度にDPI-Cを経由してC++コードを呼び出しており、Spikeを1ステップ実行しては一致比較する。

 import "DPI-C" function void step_spike
   (
    input longint rtl_time,
    input longint rtl_pc,
    input int     rtl_priv,
    input longint rtl_mstatus,
    input int     rtl_exception,
    input int     rtl_exception_cause,
    input int     rtl_cmt_id,
    input int     rtl_grp_id,
    input int     rtl_insn,
    input int     rtl_wr_valid,
    input int     rtl_wr_gpr,
    input int     rtl_wr_rnid,
    input longint rtl_wr_val
    );

/* ... 中略 ... */
             /* verilator lint_off WIDTH */
             step_spike ($time, longint'(committed_rob_entry.inst[grp_idx].pc_addr),
                         int'(u_msrh_tile_wrapper.u_msrh_tile.u_msrh_csu.u_msrh_csr.r_priv),
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_sim_mstatus[u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_out_cmt_entry_id][grp_idx],
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_valid_except_grp_id[grp_idx],
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_except_type_selected,
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_out_cmt_id,
                         1 << grp_idx,
                         committed_rob_entry.inst[grp_idx].rvc_inst_valid ? committed_rob_entry.inst[grp_idx].rvc_inst : committed_rob_entry.inst[grp_idx].inst,
                         committed_rob_entry.inst[grp_idx].rd_valid,
                         committed_rob_entry.inst[grp_idx].rd_regidx,
                         committed_rob_entry.inst[grp_idx].rd_rnid,
                         w_physical_gpr_data[committed_rob_entry.inst[grp_idx].rd_rnid]);

引数は長いがとりあえず無視。コミットIDとPC、命令ビット、書き込み汎用レジスタアドレス、書き込み値などの情報が転送できていればよろしい。

Spike側だが、汎用レジスタなどの情報を取得して更新することのできるAPIが一応そろっているので、それを活用する。

   if (rtl_wr_valid) {
     int64_t iss_wr_val = p->get_state()->XPR[rtl_wr_gpr_addr];
     if ((((iss_insn.bits() & MASK_CSRRW) == MATCH_CSRRW) ||
          ((iss_insn.bits() & MASK_CSRRS) == MATCH_CSRRS) ||
          ((iss_insn.bits() & MASK_CSRRC) == MATCH_CSRRC) ||
          ((iss_insn.bits() & MASK_CSRRWI) == MATCH_CSRRWI) ||
          ((iss_insn.bits() & MASK_CSRRSI) == MATCH_CSRRSI) ||
          ((iss_insn.bits() & MASK_CSRRCI) == MATCH_CSRRCI))) {
       if (((iss_insn.bits() >> 20) & 0x0fff) == CSR_MCYCLE) {
         p->set_csr(static_cast<int>(CSR_MCYCLE), static_cast<reg_t>(rtl_wr_val));
         p->get_state()->XPR.write(rtl_wr_gpr_addr, rtl_wr_val);
         fprintf(compare_log_fp, "==========================================\n");
         fprintf(compare_log_fp, "RTL MCYCLE Backporting to ISS.\n");
         fprintf(compare_log_fp, "ISS MCYCLE is updated to RTL = %0*llx\n", g_rv_xlen / 4, rtl_wr_val);
         fprintf(compare_log_fp, "==========================================\n");

雑な実装ではあるが、iss_insn.bits()というのはISSの実行した命令ビット、そしてMASKMATCHの定数はSpikeがビルド時に勝手に用意してくれているので活用させて頂く。

そして対象となるCSRがMCYCLEの場合n以上県が成立し、p->get_state()->XPR.write()レジスタアドレスを更新するという訳だ。さらに元となるMCYCLEのシステムレジスタp->set_csr()で更新する。 これをriscv-testsベンチマークで実行すると例えば以下のようなログが得られる。

142535 : RTL(23,1) Exception Cause = 27
142535 : 24552 : PC=[00000000800023b4] (23,01) b00027f3 csrr    a5, mcycle
==========================================
RTL MCYCLE Backporting to ISS.
ISS MCYCLE is updated to RTL = 0000000000010e2d
==========================================
142563 : 24553 : PC=[00000000800023b8] (24,01) 00001717 auipc   a4, 0x1
GPR[14](73) <= 00000000800033b8
142563 : 24554 : PC=[00000000800023bc] (24,02) ab870713 addi    a4, a4, -1352
GPR[14](127) <= 0000000080002e70

RTLのMCYCLEの情報0x10e2dISSにバックポートして、MCYCLEの値とGPRの値が更新されたというログが得られた。 これで一応ベンチマークの継続実行ができるようになった。

AMBA5 の CHI (Coherence Hub Interface)についての調査 (3. トランザクション例)

developer.arm.com

developer.arm.com

いくつかの例に分けてCHIのトランザクションの例を見ていく。

まず、トランザクションには2種類ある。

  • DMT : Direct Memory Transfer
    • スレーブノードはリクエストノードに対して直接データを転送することを許す
  • DCT : Direct Cache Transfer
    • 他のRN-Fノードはリクエストノードに対して直接データを転送することを許す
  • DWT : Direct Write-data Transfer
    • RNノードがスレーブノードに直接データを転送することを許可する

DMTの場合の転送例を見てみる。

  1. REQチャネルに対して以下のSnoopable Readリクエストを送信する
    • ReadClean
    • ReadNotSharedDirty
    • ReadShared
    • ReadUnique
    • ReadPreferUnique
    • MakeReadUnique
  2. ICNはREQチャネルを通じてReadNoSnpリクエストをSNに対して送信する
  3. SNは読み込みデータと、該当する転送レスポンスレスポンスを、CompDataオペコードを使用してリクエスタにRDATチャネルを通じて返答する
  4. トランザクションリクエストにExpCompAckビットが設定されているため、RequestはAcknowledegementを返さなrければならならい。CompAckオペコードを使用してトランザクションが完了したことを通知しなければならない。
f:id:msyksphinz:20210812003022p:plain

DMTでのトランザクションには以下の制限が存在する

  • リクエスターはTxnIDを、すべてのTxnIDが返ってきた後にのみ再利用することができる
  • HomeノードはDMTリクエストが、SN-Fに送信する際に以下のことが適用されていることを確認できるまで待たなければならない。
    • スヌープリクエストを送信する必要がない
    • スヌープリクエストが送信されると、キャッシュラインのDirtyコピーが含まれたスヌープレスポンスが返答される
    • スヌープレスポンスが一部Dirtyなキャッシュラインのコピーを返した場合、DMTは一部分のデータがSN-Fに転送され、その書き込みリクエストが完了する。
    • スヌープがForwardingタイプのスヌープリクエストであった場合、キャッシュラインがリクエスターにフォワードされない。

次に、DCTの場合の転送方法を見てみる。

  1. REQチャネルに対して以下のSnoopable Readリクエストを送信する
    • ReadClean
    • ReadNotSharedDirty
    • ReadShared
    • ReadUnique
    • ReadPreferUnique
    • MakeReadUnique
  2. ICNはSnp[*]FwdリクエストをSNPチャネルを通じてRN-Fノードに対して送信する
  3. RN-FはDATチャネルを通じて関連するトランザクションレスポンスをCompDataオペコードとともにRNに対して送信する。
  4. RN-FはSnpRespFwdedレスポンスをICNに対してSRSPチャネルを通じて送信し、読み込みデータがリクエスタに送信されたことを通知する。
  5. トランザクションリクエストExpCompAckビットが設定されているため、リクエスタはCompAckオペコードを用いてSRSPチャネル上でAcknowlegdementを送信する必要がある。
f:id:msyksphinz:20210812003039p:plain

DMTでもDCTでもない場合の転送方法は以下の通りとなる。

  1. REQチャネルに対して以下のSnoopable Readリクエストを送信する
    • ReadClean
    • ReadNotSharedDirty
    • ReadShared
    • ReadUnique
    • ReadPreferUnique
    • MakeReadUnique
  2. Completerは読み込みデータと関連するトランザクションリクエストをCompDataオペコードとともにRDATチャネルを通じて転送する。
  3. Acknowlegdementを転送する。
f:id:msyksphinz:20210812003106p:plain
  • ReadNoSnp : 他のマスタにスヌープが必要ない場合に使用する。データはReadNoSnpを用いてスレーブノードから転送される。
  • ReadOne, ReadOnceCleanInvalid, ReadOnceMakeInvalid : 他のマスタによりスヌーピングが必要だが、リクエスタが自身のキャッシュラインに割り当てられていない場合に使用する。

AMBA5 の CHI (Coherence Hub Interface)についての調査 (2. トランザクションの概要)

developer.arm.com

developer.arm.com

プロトコル層のトランザクションの種類

プロトコル層のトランザクションは以下のような種類が定義されている。

  • 読み込み要求
    • ReadNoSnp, ReadNoSnpSep
    • ReadOnce.
    • ReadOnceCleanInvalid.
    • ReadOnceMakeInvalid.
    • ReadClean.
    • ReadNotSharedDirty.
    • ReadShared.
    • ReadUnique.
    • ReadPreferUnique.
    • MakeReadUnique.
  • データレス
    • CleanUnique.
    • MakeUnique.
    • Evict.
    • StashOnceUnique, StashOnceSepUnique.
    • StashOnceShared, StashOnceSepShared.
    • CleanShared.
    • CleanSharedPersist.
    • CleanSharedPersistSep.
    • CleanInvalid.
    • MakeInvalid.
  • 書き込み
    • WriteNoSnpPtl, WriteNoSnpFull, WriteNoSnpZero.
    • WriteUniquePtl, WriteUniqueFull, WriteUniqueZero.
    • WriteUniquePtlStash, WriteUniqueFullStash.
    • WriteBackPtl, WriteBackFull.
    • WriteCleanFull.
    • WriteEvictFull, WriteEvictOrEvict.
  • Combined Write
    • WriteNoSnpPtlCleanSh, WriteNoSnpPtlCleanInv, WriteNoSnpPtlCleanShPerSep.
    • WriteNoSnpFullCleanSh, WriteNoSnpFullCleanInv, WriteNoSnpFullCleanShPerSep.
    • WriteUniquePtlCleanSh, WriteUniquePtlCleanShPerSep.
    • WriteUniqueFullCleanSh, WriteUniqueFullCleanShPerSep.
    • WriteBackFullCleanSh, WriteBackFullCleanInv, WriteBackFullCleanShPerSep.
    • WriteCleanFullCleanSh, WriteCleanFullCleanShPerSep.
  • アトミック
    • AtomicStore.
    • AtomicLoad.
    • AtomicSwap.
    • AtomicCompare. Other
    • DVMOp.
    • PrefetchTgt.
    • PCrdReturn.
  • スヌープ
    • SnpOnceFwd.
    • SnpOnce.
    • SnpStashUnique.
    • SnpStashShared.
    • SnpCleanFwd.
    • SnpClean.
    • SnpNotSharedDirtyFwd.
    • SnpNotSharedDirty.
    • SnpSharedFwd.
    • SnpShared.
    • SnpUniqueFwd.
    • SnpUnique.
    • SnpPreferUniqueFwd.
    • SnpPreferUnique.
    • SnpUniqueStash.
    • SnpCleanShared.
    • SnpCleanInvalid.
    • SnpMakeInvalid.
    • SnpMakeInvalidStash.
    • SnpQuery.
    • SnpDVMOp.

RNがHNに対してトランザクションを発行し、それをSNに対して転送するという連携になっていることが分かる。では、これらがどのように関連付けられているかを確認していく。

例えば以下の図では、RN-Fが読み込みリクエストを送信し、ICNがそれを受け取る。ICNはしかるべきルーティングを行った後にSlaveに対してリクエストを転送する。Slaveはデータの読み込み処理を行った後レスポンスを返し、ICNはRN-Fにそれを転送するという流れになる。

CHIプロトコルでは、システム内の各ノードIDが必要となる。RNおよびHNはアドレスとターゲットのノードを関連付けるためのSAM(System Address Map)が存在している。RNのSAMは物理アドレスをHNのIDに変換し、HNのSAMは物理アドレスをSNのIDに変換する機能を持っている。

f:id:msyksphinz:20210811003756p:plain

  1. RN0はRN0内部のSAMを使用してHN0のターゲットIDに向けてリクエストを送信する
    • インターコネクトはノードIDを再マップしない
  2. HN0は内部のSAMを探索しターゲットとなるSNを決定する
  3. SN0はリクエストを受け取り、データレスポンスを送信する
    • データレスポンスパケットはReturnNIDリクエストから得られるTgtIDを含んでいる
  4. RN0はSN0からデータレスポンスを受け取る
  5. 必要であればRN0はHN0のTgtIDの付いたCompAckを明史、パケットのトランザクションを終了する

これらの転送を行うためのノード間の転送については、チャネルを用いて行われる。

f:id:msyksphinz:20210811003808p:plain

AMBA5 の CHI (Coherence Hub Interface)についての調査 (1. 概要)

developer.arm.com

developer.arm.com

AMBA5ではデバイス間のコヒーレンスを取るためのプロトコルとしてCHIが導入される。CHIはCoherent Hub Interfaceの略称で、AMBA4ではACEと呼ばれていたものを拡張したものになる。

AMBA history

ACEとCHIの違いはどのようになっているのだろうか。CHIは3階層に分かれたパケットベースの階層型通信プロトコルを持っているという点がACEと異なる。SoC内のより幅広いデバイスを持っており、CPUクラスタ・グラフィクスプロセッサ・メモリコントローラ・I/Oブリッジなどの機能を持っており、これらの機能をCHIを使って結合することを目的としている。

CHIの特徴は以下のようになっている。

  • スケーラブルなアーキテクチャとモジュール化されたデザインにより、小さなシステムから大きなシステムまで適用できる。
  • 独立した階層によるアプローチを取っている。プロトコル層、ネットワーク層、リンク層に分けられている。
  • パケットベースの送受信を行う。
  • スヌープ・キャッシュ・メモリアクセスなどが必要なインターコネクトベースのホームノードによりすべてのトランザクションが処理される。
  • CHIは以下のコヒーレントプロトコルをサポートする。
    • 64バイトのキャッシュライン
    • スヌープフィルタとディレクトリベースのシステム
    • MESIとMOESIキャッシュモデルにより任意のキャッシュ状態からのデータフォワーディング
    • キャッシュラインステータスを追加する
  • CHIのトランザクションは以下のものを含む
    • システムキャッシュの性能・面積・電力効率を向上させるための高機能なトランザクションタイプを含んでいる。
    • インターコネクト間でのアトミック操作・同期操作をサポートする
    • 排他アクセスの効率的な事項をサポートする
    • 効率的なデータ移動とデータの配置の転送をサポートする。
    • 分散仮想メモリ(Distributed Virtual Memory: DVM)によよる仮想メモリ管理をサポートする
  • プロトコルのリソースを管理するためのリトライリクエストをサポートする
  • エンドツーエンドでのQuality of Service(QoS)をサポートする
  • システム内でのコンフィギャラブルなデータ幅をサポートする
  • トランザクション単位でのARM TrustZoneをサポートする
  • 送信トランザクションフローの最適化
  • エラー通知システムによりシステムの信頼性と整合性を確保する
  • 電力対策

CHIのレイヤ

CHIは以下のレイヤから構成される:

CHIのトポロジ

CHIはトポロジを一般的に定義せず、どのようなトポロジでも動作することができるということになっている。

  • クロスバ:シンプルな構成で低レイテンシの通信を提供できるが、ノード数が小さい場合にのみ適する
  • リング:効率性とレイテンシのトレードオフが存在する。リングが大きくなるとレイテンシも増加する
  • メッシュ:より多くの配線を使用することでより大きなバンド幅を確保する。スイッチを配置することでスケーラビリティがありより大きな構成を取ることができる

コヒーレンスモデル

CHIが想定するコヒーレントシステムの例を以下に示す。

f:id:msyksphinz:20210809235956p:plain:w400

f:id:msyksphinz:20210810000014p:plain:w400

  • RN : Request Node
  • HN-F : Home Node. Request Nodeからトランザクションを受けとる。
    • HN-F : Fully Coherent Home Node. DVMOpを除くすべてのリクエストを受け取る
      • スヌーピングによりコヒーレンシを管理するためのPoC(Point of Coherence)を含んでいる
      • PoS(Point of Serialization)を含んでおりメモリリクエストの順序管理を行う
      • ディレクトリやスヌープフィルタを持っており、冗長なスヌーピングを除去する
    • HN-I : Non-Coherent Home Node
      • プロトコル内の一部のリクエストをサポートする
      • PoCによるスヌーピングなどの処理はサポートしない
      • PoSなどをサポートし、IOサブシステムに対するIOリクエストなどの順序管理を行う
  • SN : Slave Node
    • HNからリクエストを受け付ける。要求された動作を行いレスポンスを生成する
    • SN-F : Slave Node used for Normal Memory. 一般的なスヌープ不可能な読み書きとアトミック操作を行い、Cache Maintenance Operation(CMO)も行う
    • SN-I : Slave Node used for Peripherals or Normal Memory. ペリフェラル向け

オープンソースの浮動小数点演算器FPNewの調査

前回、浮動小数点演算器のHardFloatを調査したが、そのままのデザインだとパイプラインレジスタが入っていないし、Rocket-Chip側のパイプライン形式のFPUを使うのも面倒なので、昔論文で読んだことのあったFPNewを使うとどんな感じだろうということでやってみた。

FPNewの論文は以下からアクセスできる。

arxiv.org

なるほど、PULPのプロジェクトの一つなのか。

github.com

これをそのままとりあえず自作RISC-Vプロジェクトに組み込んで、Verilatorを使ってコンパイルしてみることにした。

%Error-BLKANDNBLK: ../src/fpnew/src/fpnew_fma.sv:106:59: Unsupported: Blocked and non-blocking assignments to same variable: 'fpnew_32.inp_pipe_valid_q'
                                                       : ... In instance msrh_tb.u_msrh_tile_wrapper.u_msrh_tile.fpu_loop[0].u_msrh_fpu.u_fpu.u_msrh_fpnew_wrapper
  106 |   logic                  [0:NUM_INP_REGS]                 inp_pipe_valid_q;
      |                                                           ^~~~~~~~~~~~~~~~
                   ../src/fpnew/src/fpnew_fma.sv:118:10: ... Location of blocking assignment
  118 |   assign inp_pipe_valid_q[0]    = in_valid_i;
      |          ^~~~~~~~~~~~~~~~
                   ../src/fpnew/src/fpnew_fma.sv:130:7: ... Location of nonblocking assignment
  130 |       inp_pipe_valid_q[i+1] <= (flush_i) ? (1'b0) : (inp_pipe_ready[i]) ? (inp_pipe_valid_q[i]) : (inp_pipe_valid_q[i+1]);

あら、Verilatorでのコンパイルはサポートしていないのか?Non-BlockingとBlockingのassignmentが混在しているのが問題のようだ。

verilator.org

なるほど、SystemVerilogとしては許可されている構文だが、推奨はされていないという意味でErrorになっているのか。 おそらくシミュレーションできないわけではないので、このエラーは無視するようにVerilatorにオプションを追加する。

-Wno-BLKANDNBLK

さらにこれだけでなく結構な量の警告が出て、警告が出てくると基本的にVerilatorはエラー扱いにしてしまうので、これを無効化する。 警告を全部無視するのはかなりリスクがあるが、FPNewをコンパイルするためにはやむをえまい。

-Wno-fatal

ここまで行くと、一応VerilatorでのコンパイルC++生成が完了したようだ。次はシミュレーションを確認していきたい。

Chiselで記述された浮動小数点演算器HardFloatの調査

久しぶりに浮動小数点演算器を触っているのだが、自作CPUに浮動小数点演算器を組み込む場合にどうすればいいのかを調査している。 一からFPUを作るのは嫌なので、HardFloatを使って生成されたVerilogファイルをそのまま組み込みたい。 その場合の方法について調査した。

berkeley-hardfloatは以下でGitHubで管理されているので、これをそのままダウンロードしてChiselからVerilogファイルを生成してみる。

github.com

これをそのままダウンロードして、makeをしてみる。すると一応Verilogファイルが生成された。 色々見てみると、どうもこれはクロックを使わずに全部組み合わせ回路として表現されているらしい。 これはちょっと実回路としては使えないなあ。

もう一つ考えられるのは、Rocket-ChipのFMAPipeのデザインを持ってくることだ。この方が多少はパイプラインの制御ができるが、Chiselから生成されるのが少し面倒だ。

それか、最初からSystemVerilogデザインであるFPNewを使ってみるという手もある。FPUNewの方がConfigurabilityが高そうなので、こっちを調査してみよう。

github.com

RISC-Vベクトル拡張仕様書の日本語版pdf生成試行

Wavedromやbit-fieldの機能を調べているうちにわかったのだが、RISC-V ベクトル拡張の仕様書はadocファイルからpdfを生成することができ、その際にビットフィールドなどの情報をbit-fieldプラグインを使用して上手く変換してくれるらしい。

f:id:msyksphinz:20210805232824p:plain

すでにRISC-Vベクトル拡張仕様書v1.0は日本語にしているので、これをpdfにすれば似たような体裁の資料が作れるのではないかと考えたので、試行してみる。

Ubuntu-20.04を使って試行しているのだが、Nodejsのバージョンを上げないと上手く生成してくれないらしい。この辺はマジで面倒くさい。

parashuto.com

npmコマンドを使って必要なパッケージをダウンロードして、PDFファイルを生成する。

$ npm i
$ npm run build

これでPDFファイルを生成できた。

f:id:msyksphinz:20210805233025p:plain

という訳で、生成したPDFファイルは以下で共有してみた。

1drv.ms