RISC-V Reader 笔记(六)RV32V

RV32V

早期并行数据计算:采用 SIMD 单指令多数据,把一个64位宽寄存器拆成若干个32 16 8 位长度部分并行计算。这种方法前期看起来十分简单诱人。但是后来如果要扩展 SIMD 寄存器宽度,也要复杂化指令集,复杂开销越来越大。

向量操作:把数据取出来放入长长的向量寄存器中,流水线并行运算后从向量寄存器中分别取回到内存。

并且,时钟周期内能进行的最大操作数和向量长度是分离的,不像 SIMD 程序员要考虑怎么分分几位,设计并行硬件的时候不用顾及程序员,程序员也不用重写代码。

向量操作在 ISA 中相比 SIMD 少见多了。

向量计算

基本每一个整数或者浮点运算都有向量版本。.vv 表示操作数都是向量,.vs 表示一个向量一个标量,即一个操作数来自x/f寄存器,一个来自v寄存器。比如 Y=aX+b。.sv 是给非对称运算比如减法 除法用的,表示操作数1是标量 如 Y=a-X。融合的乘法加法则有三个寄存器种类的指示变体。

向量寄存器

向量寄存器不固定大小,这样也减少了生成代码难度和程序员负担,也可以禁用未使用的向量寄存器。比如我们现在只有两个64位浮点类型向量寄存器,处理器有1024字节向量寄存器空间,则分给他俩每个512字节,512/8=64个元素,所以最大向量长度 mvl 为64.

源寄存器和目标寄存器都是有类型的(比如16位浮点 F16,32位浮点,64位浮点......)两者之间长度不兼容处理器会自动进行转换也就是隐式转换。可以通过 vsetdcfg 指令设定向量寄存器的类型。

Load Store

通常用来处理数组。

vld:按地址顺序填充向量寄存器。向量寄存器数据类型确定了元素大小,要取的元素数量存放在长度寄存器 vl 中。

vst:逆操作。

比如 vld v0,0(a0) v0是 x32 向量寄存器,那么 vld 就会从 a0 中的起始地址(假设是1024)开始,依次取1024 1028 1032 1036......(每次4字节)地址中的值直到到达 vl 的限制长度。

有一些结构比如以行优先序存储的二维数组,我们想对列进行顺序访问,就需要有一定的步长设定,而不是单纯的按顺序访问所有元素。可以使用 vlds 和 vsts 因为他们会通过一个寄存器来设定步长。

比如 vlds v0,a0,a1 ,a0 起始地址1024,a1行长64,那么取的位置就依次是1024,1024+64,1024+64*2......直到到达 vl 的限制长度。

第三对操作是针对稀疏数组进行操作的。

vldx v0,a0,v1 a0 起始地址还是1024,v1 中有这些字节索引:16 48 80 160,那么取的位置就是 1024+16 1024+48 1024+80 1024+160.

向量操作期间的并行性

他的并行性和早期的 SIMD 很像,首先取决于处理器每个时钟周期能操作几个多少位数。比如每个时钟周期内能操作4个64位数,那么也可以操作8个32位数,16个16位数,32个8位数。

不过 SIMD 的并行性是 ISA 架构师在设计过程中定好了的,因此寄存器数量增加指令也增加,而且编译器还要修改,相比之下 RVV 就不用改变程序,而且不管硬件结构就都能跑。

条件执行

RVV 的条件执行是每个向量里所有元素分别进行的,结果也是分别赋值给向量中对应的元素的。

RVV 有8个向量谓词寄存器 vpi。他们可以存储向量寄存的结果,也可以进行 vpand vpor vpxor 等运算。

如:vplt.vs vp0,v3,x0 就是判断 v3 中哪些元素是小于0的负数?把 vp0 中对应的这些元素都置1.

然后可以进行一些对部分元素单独进行的操作,比如 add.vv,vp0 v0,v1,v2 就是把前面 vp0 为1的部分替换令v0=v1+v2.

这种操作类似网络里的掩码,vp0 vp1 被规定用作控制向量操作的掩码。(比如我们要操作一个向量中的所有元素,那我们就得用一个全为1的掩码)。

RVV 里有 vpswap 指令可以快速把其他 vpi 的值交换到 vp0 或 vp1. 也可以选择谓词寄存器是否启用,可以通过禁用的方式来清零。

其他向量指令

setvl:把 vl 设置为源操作数和 mvl 中的最小值。如果 mvl 更大,则循环处理 mvl 长度;反之说明源操作数尾部的剩余部分长度小于最大向量长度,处理这一部分即可。

vselect:按第二个源操作数的索引位置,从第一个源操作数中取出一个新的向量。

assembly 复制代码
vselect vedst, vsrc, vindices

比如 vindices 值是 8 0 4 2,则依次取出 vsrc 中第8 0 4 2索引处的元素值替换 vedst 的第0 1 2 3索引处的值。

vmerge:类似 vselect,有两个源操作数,vpo 指代替换的元素来自第一个 vsrc 还是第二个。

assembly 复制代码
vmerge,vpo vdest, vsrc1, vsrc2

比如 vsrc1:1 2 3 4

vsrc2:10 20 30 40

vpo:1 0 0 1

vpo 指代取的数据是:v2的0位,v1的1位,v1的2位,v2的3位。

也就是最终赋值给 vdest 的值是 10 2 3 40。

vextract:从一个向量的中间位置开始取数,放到目标向量的起始位置。

如:vextract vdest, vsrc, start 如果向量长度64,start=32,那么就是从 vsrc 的中间取后32位数放到 vdest 开头32位。

我们可以利用 vextract 来做递归二分操作,比如循环求和所有向量寄存器元素,就可以做6次 vextract。

上例是利用 RV3V 写 DAXPY 程序(y=ax+y,一个函数处理)。

  1. 启用向量寄存器:我们只需要启用两个,给 x y 两个数组用。
  2. 每次循环开始,设定向量长度,y=a*x+y。
  3. 继续遍历或跳出。

简简单单10条左右的指令,一轮循环下来访存3*64次,浮点运算2*64次。每条指令平均有 19 次访存和 13 次运算。

与其他 ISA 向量运算的比较

都拿上例 DAXPY 的例子来进行分析。

MIPS:MSA 寄存器128位宽,每次可以处理2个双精度浮点数。但没有向量长度的寄存器,因此需要单独判断n是否为奇数,奇数最后要多做一次运算。平均每个指令大约有 1 个访存和 0.5 个运算操作。

x86:部分支持 256 位宽,n可以=4. 加载数据时先创建副本并在进入主循环之前进行测试。但是和 MIPS 有相似的问题,需要结尾判断 %4!=0 的部分。每条指令平均有约 2 次访存和 1 次运算。

结语

可以看出向量的一些优势。

如果不用向量,我们就要反复把代码写上好几遍,编程难度大一些。不过新指令程序员记起来也是比较复杂。

RISC-V 的向量运算不受一些向量长度等的影响,比如我把长度修改了,他仍然会自动判断 mvl 并执行,封装的比较好,程序员编码时以及编译器不用考虑这一点的影响。

相关推荐
饕餮争锋1 小时前
设计模式笔记_创建型_建造者模式
笔记·设计模式·建造者模式
萝卜青今天也要开心1 小时前
2025年上半年软件设计师考后分享
笔记·学习
吃货界的硬件攻城狮2 小时前
【STM32 学习笔记】SPI通信协议
笔记·stm32·学习
蓝染yy2 小时前
Apache
笔记
lxiaoj1113 小时前
Python文件操作笔记
笔记·python
半导体守望者4 小时前
ADVANTEST R4131 SPECTRUM ANALYZER 光谱分析仪
经验分享·笔记·功能测试·自动化·制造
啊我不会诶5 小时前
倍增法和ST算法 个人学习笔记&代码
笔记·学习·算法
逼子格5 小时前
振荡电路Multisim电路仿真实验汇总——硬件工程师笔记
笔记·嵌入式硬件·硬件工程·硬件工程师·硬件工程师真题·multisim电路仿真·震荡电流
一条破秋裤5 小时前
一份多光谱数据分析
笔记·数据挖掘·数据分析
zstar-_6 小时前
【算法笔记】6.LeetCode-Hot100-链表专项
笔记·算法·leetcode