FPGA開発日記

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

RISC-Vベクトル拡張を使ったmemcpyの実装

RISC-Vベクトル拡張について、仕様書の観点からではなく、ベンチマークテストプログラムの観点からどのようにプログラムを構築すれば良いのかについて調べてみる。

RISC-Vベクトル拡張について、いくつかのサンプルプログラムが公式仕様書に添付されている。 まずはmemcpyから。以下からサンプルコードを参照できる。

github.com

  memcpy:
      mv a3, a0 # Copy destination
  loop:
    vsetvli t0, a2, e8, m8, ta, ma   # Vectors of 8b
    vle8.v v0, (a1)               # Load bytes
      add a1, a1, t0              # Bump pointer
      sub a2, a2, t0              # Decrement count
    vse8.v v0, (a3)               # Store bytes
      add a3, a3, t0              # Bump pointer
      bnez a2, loop               # Any more?
      ret                         # Return

いくつかの観点からこのサンプルコードを読み解いていく。

  1. ループ中には、1つのロード命令と1つのストア命令のみが実行される。ループアンローリングは適用されない。理由は以下。
  2. vsetvli命令により処理するデータサイズを指定する。
  3. 上記のループアンローリングをしない理由がここにある。vsetvli命令によりm8が指定されており、LMUL=8が指定されている。
  4. これにより、各ベクトル命令は8個のレジスタを同時に扱うことになる。
  5. vle8.v ではv0からv7までのレジスタにシーケンシャルなデータがロードされる。
  6. vse8.vではv0からv7までのレジスタの値がシーケンシャルにメモリにストアされる。 つまり自動的に8回分のアンローリングがされていることになる。

これを詳細に理解するためには、vsetvliの動作を理解する必要がある。

vsetvli t0, a2, e8, m8, ta, m8

taは末尾要素の対処方法で、ここでは説明を省略する。

この命令では、レジスタのサイズに応じて計算に必要な要素数を算出してt0に格納する。計算対象となる要素数a2に格納されている。

e8は計算対象となる演算単位を指定する(e8は8ビットが演算単位となることを意味する: vadd.vvではベクトルレジスタに対して8ビット単位での演算が行われることを意味する。

つまり、a2(演算対象要素数)=1000で、ベクトルレジスタ長(VLEN=256)の場合、どのように考えるかというと、

  • e8VLEN=256なので、VLEN/8=256/8=32となり、8ビットの要素が32個分処理されるモードになっている。
  • a2が1000だが、同時に処理できるのは上記の通り32要素なので、
  • もしループが繰り返され、残りの演算要素数a2=10くらいになった場合、32よりも小さいので、VL=10となり、ベクトルレジスタ全体の内10要素だけが処理されることになる。
f:id:msyksphinz:20210726234608p:plain

このループを繰り返していくことにより、memcpyが実現される。