BLOG

Record, summarize, and improve.

RVV

简介

RISC-V Vector extension (RVV)是RISC-V标准指令集的一个扩展, 目的是为了为RISC-V架构提供vector处理能力.

Image in a image block

Scalar、Vector、Matrix计算示意图

RVV设计的非常灵活, 下面介绍一下RVV的一些基本特性:

芯片厂商定义的常量:VLEN和ELEN
  • VLEN:

RVV一共32个寄存器, 每个寄存器的长度为VLEN (bit), VLEN是硬件厂商实现的固定长度, 需要是2的幂次方, 最小为64或128. 比方说VLEN=512就相当于Intel的AVX512指令.

Image in a image block

RVV的32个寄存器

  • ELEN

RVV中存储的数据是按照element存储的, ELEN (element length)代表一个element的最大长度(bit), 也是硬件厂商规定的, 8 < ELEN < VLEN, 一般设计成32或64 bit. 比如VLEN=128b, ELEN=64b, 那一个RVV寄存器中就可以存储:

  • 2个64bit的元素
  • 或4个32bit的元素
  • 或8个16bit的元素
  • 或16个8bit的元素
Image in a image block

VLEN and ELEN

上面的VLEN和ELEN是设计芯片时固定下来的,不能改的。

开发者可操作的变量

开发者在编程时指定的参数如下(包括vl、sew、lmul这三个参数都是可以在编程时由开发者指定的,是可以变化的):

  • sew:Standard Element Width,代表指令要操作的元素的宽度,如8/16/32/64等
  • lmul:Length Multiplier,允许跨几个寄存器访问元素
  • vl:Vector Length,代表指令要操作的元素的个数 (这个是元素的个数,不是bit,不要和VLEN混了), 注意vl指定的元素个数不一定要填满寄存器,可以留有尾巴不处理(tail,如下图灰色部分)

这三个参数的示意图如下:

Image in a image block

vl, sew, lmul概念示意图

Mask

RVV允许用户对要操作的任意元素进行mask操作,指令不会操作被mask掉的元素。V0寄存器用来做mask寄存器,其每一位对应的是要操作的一个元素。

比如如下的操作,我们想让V8和V9寄存器中的两个个32bit数相加(元素1和2),那我们可以通过设置sew=32,vl=3来只操作前3个32bit元素,然后通过设置V0寄存器的前3bit为0、1、1,来把元素0 mask掉不操作。

Image in a image block

mask 操作示意图

以上就是RVV的基本概念,主要是VLEN、ELEN两个常量,以及sew、lmul、vl、mask这几个变量。通过这几个变量,可以很灵活的操作Vector中的元素。那如何设置这些变量呢?就涉及到具体的RVV指令了。

常用指令

设置vl、sew、lmul指令

最常用的是vsetvli指令,用来配置vector的参数,格式如下:

vsetvli rd, rs, eN,mX,tP,mP

  • rd:目标寄存器
  • rs:源寄存器,存储着vl的值(如8/16/32/64)
  • eN:代表sew,取值为e8/e16/e32/e64..
  • mX: 代表lmul,取值为m1/m2/m4/m8..
  • tP: tail policy,对于tail的处理策略,tu: tail元素内容保持不变,ta: tail元素内容保持不变或设置成全1
  • mP: mask policy,对于被mask的元素处理策略,mu: 被mask掉的元素内容保持不变,ma:被mask掉的元素内容保持不变或设置成全1

举例, 如下指令设置vl=32,sew=32,lmul=1:

li s5, 32 vsetvli t0, s5, e32, m1, ta, ma

LOAD、STORE指令

load指令用于从内存读取数据到RVV寄存器,store指令用于从RVV寄存器写回内存

load/store在读写内存的时候,有三种访存模式:

  • Unit-Stride:从基地址访问连续的内存
Image in a image block

unit stride

指令格式:

load (vle):

# vd destination, rs1 base address, vm is mask encoding
vle(8/16/32/64).v vd, (rs1), vm # unit-stride load

store (vse):

# vs3 store data, rs1 base address, vm is mask encoding
vse(8/16/32/64).v vs3, (rs1), vm # unit-stride store

  • Strided :从基地址访问连续跨N个字节偏移的内存
Image in a image block

strided

指令格式:

load(vlse):

# vd destination, rs1 base address, rs2 byte stride
vlse(8/16/32/64).v vd, (rs1), rs2, vm # strided load

store(vsse):

# vs3 store data, rs1 base address, rs2 byte stride
vsse(8/16/32/64).v vs3, (rs1), rs2, vm # strided store

  • Indexed :从基地址访问内存,通过vs2寄存器设置要访问的元素坐标,可以实现按任意顺序对内存的任意偏移进行访问。通常用于实现gather、scatter操作。
Image in a image block

indexed

指令格式:

load(vluxe):indexed-unordered load, 不保证内存访问顺序的load

# vd destination, rs1 base address, vs2 byte offsets
vluxei(8/16/32/64).v vd, (rs1), vs2, vm

Load (vloxei):indexed-ordered load, 保证内存访问顺序的load

# vd destination, rs1 base address, vs2 byte offsets
vloxei(8/16/32/64).v vd, (rs1), vs2, v

store(vsuxei):indexed-unordered store,不保证内存访问顺序的store

# vs3 store data, rs1 base address, vs2 byte offsets
vsuxei(8/16/32/64).v vs3, (rs1), vs2, vm

store(vsoxei):indexed-ordered store,保证内存访问顺序的store

# vs3 store data, rs1 base address, vs2 byte offsets
vsoxei(8/16/32/64).v vs3, (rs1), vs2, vm

Vector运算指令

vector运算指令用于vector寄存器之间进行数学运算,分为几种:

  • vop.vv: vector与vector之间进行整型运算
  • vop.vx: vector与scalar之间进行整型运算
  • vop.vi: vector与立即数之间进行整型运算
  • vfop.vv: vector与vector之间进行浮点运算
  • vfop.vf: vector与scalar之间进行浮点运算

指令格式:

vop.vv vd, vs2, vs1, vm # integer vector-vector vd[i] = vs2[i] op vs1[i]
vop.vx vd, vs2, rs1, vm # integer vector-scalar vd[i] = vs2[i] op x[rs1]
vop.vi vd, vs2, imm, vm # integer vector-immediate vd[i] = vs2[i] op imm
vfop.vv vd, vs2, vs1, vm # FP vector-vector operation vd[i] = vs2[i] fop vs1[i]
vfop.vf vd, vs2, rs1, vm # FP vector-scalar operation vd[i] = vs2[i] fop f[rs1]

举例:如下指令执行整型求和运算 v8 = v9 + v8

vadd.vv v8, v9, v8