大数跨境
0
0

RISC-V VPU验证和学习笔记

RISC-V VPU验证和学习笔记 Equilibria
2025-11-04
3

1. 简介

VPU向量计算单元(Vector Processing Unit)是专门设计用于处理向量数据和执行向量操作的计算硬件单元。它是现代计算机体系结构中一种重要的组件,特别是在处理需要高效并行计算的任务时,VPU能够显著提高性能。


VPU是RISC-V的vector标准指令集的实现,允许处理器同时处理多个数据元素,提高计算效率,可用于各种算子的计算。


VPU微架构


2. 向量计算

给定如下场景:输入两个uint32类型,长度为256的向量va和vb,输出结果vc。


通过CPU执行计算,代码及汇编如下:

1. 从地址va和vb,根据偏移i,读取1个uint32数据到寄存器中;

2. 执行加法指令;

3. 将结果从寄存器存储到1个uint32数据到地址vc[i]中;

4. i+1,判断是否到256,小于256则重复步骤1~3。


通过VPU执行计算,代码及汇编如下:

1. 配置VPU参数;

2. 从地址va和vb中,各读取256个数据到寄存器中;

3. 执行加法指令;

4. 将结果从寄存器,存储256个数据到地址vc中。


区别:

  • 使用CPU执行向量计算,需要循环256次,每个循环进行2次load,1次store,1次加法,1次i+1,1次条件跳转;


  • 使用VPU执行向量计算,只需要执行2次load,1次加法,1次store。


在上面的场景中,使用VPU时,可以一次load 256个uint32数据,并行计算256个加法,再一次store回256个uint32数据。可以显著提高计算的效率。


3. 基本原理

VPU有7个CSR和32个vector regs:


CSR regs:

vstart:向量指令要执行的第一个element的索引,任意一条向量指令执行时,vstart之前的向量将被忽略,该条向量执行结束后vstart被置0,所有向量指令包括vset{i}vl{i}执行结束后都会把该寄存器置零;

vxsat:定点饱和标志;

vxrm:定点数四舍五入模式;

vcsr:向量控制和状态寄存器;

vl:实际执行的向量长度,只能在一条vsetvset{i}vl{i}指令执行后自动更新vtype:向量数据类型,只能在一条vset{i}vl{i}指令执行后自动更新,而其标识了向量的实际宽度、组织方式、mask处理策略、tail处理策略等多种信息;

vlenb:VLEN/8,以bytes数来计算VLEN,用于某些需要bytes来表示VLEN的场景。


Vector regs:

VLEN:vector寄存器位宽,硬件参数,可设定为1024,即每个寄存器有1024个bits;

SEW:计算时每个元素的位宽,软件参数,可选值为4/8/16/32/64;

LMUL计算时使用的寄存器组数,软件参数,可选值为:1,2,4,8,1/2,1/4,1/8;

VL:当前指令操作的元素个数。


隐形参数: 

VL_MAX:一条指令最多可以操作的元素个数,计算公式为:LMUL*(VLEN/SEW)


以前文VPU程序举例,说明几个参数的作用。


设置每个元素位宽为32,使用8组寄存器,处理a0个元素,tail元素为undisturbed处理,mask元素undisturbed处理。

li a0,256vsetvli t0,a0,e32,m8,tu,mu
vle32.v v0,%[va]  #将va地址的值load到v0寄存器中 vle32.v v8,%[vb]  #将vb地址的值load到v8寄存器中 vadd.vv v0,v0,v8  #将v0和V8寄存器的值进行加法操作 vse32.v v0,%[vc]  #将v0寄存器store中vc地址中 


VL_MAX = LMUL * (VLEN/SEW) = 8 * (1024/32) = 256


对于尾部元素/mask元素的处理策略

tu/ta:tail undisturbed/tail agnostic 

mu/ma:mask undisturbed/mask agnostic

undisturbed:值不会被改变;agnostic:可以覆写为全1或者保持不变


4. 指令

指令可分为三类:配置指令,Ioad/store指令,算数指令


4.1 配置指令

配置指令用于配置接下来的vector指令的类型(即配置元素类型,元素个数,寄存器组数等)


4.2 Ioad/store指令


基本向量扩展支持单位跨步(unit-stride),跨步(strided)和索引(indexed)寻址模式。


  • unit-stride就是向量元素在内存中的排布就是挨个的,可以直接一个一个取出来。


  • strided是为了按照一个固定间隔取向量元素的方式。比如在进行两个矩阵相乘A*B,A的一行会跟B的一列做向量乘。A和B在内存中都是按行存储的。A做向量乘时,可以挨个拿出来放到向量寄存器,而B需要按列取出,所以需要每隔一行元素个数取一个元素,放到向量寄存器,才能取出B的一列。此时就需要用到stride模式了。 


  • indexed模式是最精细的,可以精确控制向量寄存器中的某个元素从哪里来。在取每个元素时,用vs2向量寄存器的值在mem中索引要取出的元素。数据的读取还可以区分为有序和无序。对于需要强访存顺序依赖的区域,例如IO,需要使用ordered indexed方式才能保证顺序。


4.3 算数指令

可以分为几类:整数指令,定点指令,规约指令,mask指令,置换指令,浮点指令


5. Intrinsic

除了直接手写汇编以外,riscv官方提供了一套封装好的intrinsic接口,直接调用对应的指令。


以vadd为例,该条算数指令有如下一些参数:


寄存器类型:使用两个vector寄存器相加(vv),使用一个vector寄存器和一个通用寄存器相加(vx),使用一个vector和立即数相加(vi)

元素类型:8,16,32,64

寄存器组数:1,2,4,8,1/2,1/4,1/8

操作元素个数:1~VLMAX

mask:是否有元素mask


对于一条vadd指令,官方提供了如下intrinsic接口:


6. 验证笔记

对于VPU验证,采用开源工程riscv-vector-test进行验证。


该工程可以生成随机的数据,对指令不同的参数进行遍历,通过官方模拟器spike跑出一个结果。


将同样的程序在VPU跑,比对实际跑出来的结果和spike跑出的结果是否一致,来确认功能是否正常。


配置指令

遍历配置指令(vsetvli/vsetivli/vsetvl)的不同类型配置,检查vstart,vtype,vl等是否符合预期。


load/store指令

遍历不同load/store的模式(unit-stride,strided,indexed等),不同元素宽度(e8,e16,e32,e64),不同组数(LMUL=1,2,4,8),向量数量(0~VLMAX),是否mask,对比load/store的值与预期是否相符。


算数指令

遍历不同算数指令(整形,定点,浮点,规约,置换,mask),不同元素宽度(e8,e16,e32,e64),不同组数(LMUL=1,2,4,8),向量数量(0~VLMAX),是否mask,对比计算的值与预期是否相符。


7. 应用

即使有intrinsic指令,直接手写指令来做运算,还是有不小难度。


7.1 计算库

对于上位机而言,可以调用已经移植好riscv vector指令集的算术库,直接使用vector指令进行计算。


BLAS是一个数学计算库的标准,定义了一套矩阵数组操作的API,例如:sgemm float矩阵乘法、sgemv float矩阵乘以数组...诸如此类。


BLAS(Basic Linear Algebra Subprograms 基础线性代数程序集)程序集最初发布于1979年,并用于建立更大的数值程序包(如LAPACK)。


在高性能计算领域,BLAS被广泛使用。例如,LINPACK的运算成绩则很大程度上取决于BLAS中子程序DGEMM的表现。为提高性能,各软硬件厂商则针对其产品对BLAS接口实现进行高度优化。 


OpenBLAS是基于GotoBLAS21.13BSD版本进行研发并开源。


7.2 自动向量化

目前LLVM框架下支持的自动向量化工具有SLPVectorize和LoopVectorize。 


SLPVectorize

SLP向量化关注单次迭代间的向量化机会,在基本块中搜集相似的标量指令,通过将相似标量指令合并为向量指令的方式,来实现向量化。如下程序可以通过构造向量(a1,a2)和(b1,b2),完成向量化算术运算。


LoopVectorize 

循环向量化关注循环迭代间的向量化机会,将多次迭代处理的数据利用更宽的位宽寄存器存储,使其一次能够完成多次循环迭代的数据处理,此后每次循环迭代的下标步长将扩宽成SIMD的位宽/向量元素的位宽。


8. 参考资料

riscv-v-spec-1.0.pdf

《v-intrinsic-spec.pdf》

【声明】内容源于网络
0
0
Equilibria
分享在自我迭代过程中掌握的技能树、确立的原则和一些读过的书
内容 31
粉丝 0
Equilibria 分享在自我迭代过程中掌握的技能树、确立的原则和一些读过的书
总阅读47
粉丝0
内容31