NEON指令集是ARM64架构的单指令多数据流(SIMD)的标准实现。SVE(可扩展矢量指令Scalable Vector Extension)是针对高性能计算(HPC)和机器学习等领域开发的一套全新的矢量指令集,它是下一代SIMD指令集实现,而不是NEON指令集的简单扩展。SVE指令集中有很多概念与NEON指令集类似,例如矢量、通道、数据元素等。SVE指令集也提出了一个全新的概念:可变矢量长度编程模型(VectorLength Agnostic,VLA)。
传统的SIMD指令集采用固定大小的向量寄存器,例如NEON指令集采用固定的128位长度的矢量寄存器。而支持VLA编程模型的SVE指令集则支持可变长度的矢量寄存器。这样允许芯片设计者根据负载和成本来选择一个合适的矢量长度。SVE指令集的矢量寄存器的长度最小支持128位,最大可以支持2048位,以128位为增量。SVE设计确保同一个应用程序可以在支持不同矢量长度的SVE指令机器上运行,而不需要重新编译代码,这是VLA编程模型的精髓。
SVE指令集是在A64指令集的基础上新增的一组指令集,而SVE2是在ARMv9架构上发布的,它是SVE指令集的一个超集和扩充。
SVE指令集包含了几百条指令,它们可以分成如下几大类。
Ø 加载存储指令以及预取指令
Ø 向量移动指令
Ø 整数运算指令
Ø 位操作指令
Ø 浮点数运算指令
Ø 预测操作指令
Ø 数据元素操作指令
SVE2指令集在SVE指令集的基础进一步扩充和完善,新增了部分指令和扩展。本节不对每条指令做详细的介绍,有兴趣的读者可以阅读ARMv9指令集文档:《Arm A64 Instruction SetArchitecture,Armv9, for Armv9-A architectureprofile》。
SIMD,全称Single Instruction Multiple Data,一条指令操作多个数据,提供小数据并行处理能力。ARM从ARMv7架构开始加入NEON指令集扩展,矢量化并行计算,用于图像处理、音视频处理、视频编解码等场景。
SISD(Single Instruction Single Data)指的是单指令单数据。大多数ARM64指令是单指令单数据(SISD)。每条指令在单个数据源上执行其指定的操作,所以,处理多个数据项需要多个指令。例如,要执行4个加法操作,需要4条指令从4对寄存器进行加法运算。
ADD w0, w0, w5
ADD w1, w1, w6
ADD w2, w2, w7
ADD w3, w3, w8
如果数据元素比较小,例如当将8位值相加时,需要将每个8位值加载到一个单独的64位寄存器中。由于处理器、寄存器和数据路径都是为64位计算而设计的,所以在小数据大小上执行大量单独的操作不能有效地使用机器资源。
SIMD指的是单指令多数据流,它对多个数据元素同时执行相同的操作。这些数据元素被打包成一个更大的寄存器中的独立通道(Lanes)。例如,ADD指令将32位数据元素加在一起。这些值被打包到两对128位寄存器(分别是V8和V9)中的单独通道中。然后将第一源寄存器中的每个通道添加到第二源寄存器中的相应通道,然后将其存储在目标寄存器(V10)中的同一通道中。
ADD V0.4S, V1.4S, V2.4S
如图1所示,ADD指令会并行做4个加法运算,它们分别位于处理器内部的4个计算通道并且是相互独立的,任何一个通道发生了溢出或者进位都不会影响其他通道。
V0.4S[0] = V1.4S[0]+ V2.4S[0]
V0.4S[1] = V1.4S[1]+ V2.4S[1]
V0.4S[2] = V1.4S[2]+ V2.4S[2]
V0.4S[3]= V1.4S[3] + V2.4S[3]
在图1中,一个128位的矢量寄存器Vn可以同时存储4个32位的数据Sn,另外它还可以存储2个64位数据Dn、8个16位数据Hn或者16个8位数据Bn。
SIMD非常适合图像处理场景。图像的数据常用的数据类型是RGB565, RGBA8888,YUV422等格式,这些格式的数据特点是一个像素点的一个分量(A、R、G以及B分量)使用8位数据表示。如果使用传统的处理器做计算,虽然处理器的寄存器是32位或是64位的,处理这些数据确只能使用寄存器的低8位,有点浪费。如果把64位寄存器拆成8个8位数据通道就能同时完成8个操作,计算效率提升了8倍。
总之,SISD和SIMD的区别如图2所示。
在SIMD指令中常常使用矢量数据格式(Vectorformats)。矢量被划分为多个通道(lanes),每个通道包含一个矢量元素(vector elements)。如图3所示,一个Vn矢量寄存器可以分成8个16位数据,如通道0、通道1等。
通道可以由多种不同的数据类型组成,比如128位的数据类型用Vn来表示,64位的数据类型用Dn来表示,32位的数据类型用Sn来表示,16位的数据类型用Hn来表示,8位的数据类型用Bn来表示,如图4所示。
在矢量指令集(NEON/SVE)中,指令通常可以分成两大类,一类是矢量(vector)运算指令,另一类是标量(scalar)运算指令。矢量运算指的是对矢量寄存器中所有通道的数据都同时进行运算,而标量运算指的是只对矢量寄存器中某个通道的数据进行运算。
SVE指令集提供了一组全新的寄存器。
Ø 32个全新的可变长矢量寄存器Z0~ Z31。
Ø 16个预测寄存器(predicateregister)P0 ~ P15。
Ø 首次错误预测寄存器(First Fault predicateRegister,FFR)
Ø SVE控制寄存器ZCR_Elx
(1) 可变长矢量寄存器Z
Z寄存器是数据寄存器,长度是可变长的。它的长度是128的倍数,最高可达2048位。Z寄存器中的数据可以用来存储为8位、16位、32位、64位或128位数据元素,如图5所示。每个Z寄存器的低128位与对应的NEON寄存器复用。
(2) 预测寄存器P
P寄存器为Z寄存器中的每个字节保留一个位,也就是说,P寄存器总是Z寄存器的1/8大小。预测指令(Predicatedinstruction)使用P寄存器来确定要处理哪些向量元素(通道数据)。P寄存器中的每个比特位指定Z寄存器中相应的字节是处于活跃状态还是非活跃状态。
当数据元素为8位宽(Bn)时,P寄存器可以使用1个比特位来表示其活跃状态,这个比特位为1表示活跃,为0表示不活跃。依次类推,当数据元素为128位宽(Vn)时,P寄存器预留了8个比特位来表示Z寄存器中对应的数据元素的活跃状态,不过只使用了最低1位即可表示其活跃状态,其他比特位保留。
假设矢量寄存器的长度为256位,矢量寄存器分成了8个通道,每个通道存储32位宽的数据。如果在SVE指令中想同时操作这8个通道的数据,那么需要使用一个P寄存器来表示这8个数据通道的状态。如图6所示,Pn寄存器也分成8个组,每个组由4个比特位组成,每个组只使用最低的比特位来表示Zn寄存器中对应的数据通道(32位宽)的活跃状态。例如,Pn寄存器的Bit[3:0]表示Zn寄存器的通道0,Pn寄存器的Bit[7:4]表示Zn寄存器的通道1,依次类推。
(3) FFR寄存器
FFR寄存器的大小与格式和预测寄存器P相同。FFR寄存器用于第一异常预测加载指令(First Fault PredicteLoad Instruction),例如LDRFF指令。在使用第一异常预测加载指来加载矢量元素时,FFR寄存器会及时更新每个数据元素的加载状态,是成功还是失败
(4) ECR_ELx寄存器
系统软件可以通过ECR_ELx寄存器中的LEN字段来设置矢量寄存器的长度。不过,设置的长度不能超过硬件实现的长度。
SVE指令的语法与NEON指令有很大不同。SVE指令格式由操作代码、目标寄存器、P寄存器和输入操作符等组成。下面举几个例子来介绍。
【例】
下面是一条LD1D指令的格式。
LD1D {
其中:
Ø Zt表示矢量寄存器,可以使用Z0~Z31。
Ø D表示矢量寄存器中通道的数据类型。
Ø Pg表示预测操作数(predicateoperand),可以使用P0~P15。
Ø
Ø Xn/SP:表示源操作数的基地址,Xn为通用寄存器,SP为栈指针寄存器。
Ø Xm:表示第二个源操作数寄存器。
【例2】
下面是一条ADD指令的格式。
ADD
其中:
Ø Zdn表示第一个源矢量寄存器或者目标矢量寄存器。
Ø Pg表示预测寄存器,可以使用P0~P15。
Ø
Ø Zm表示第二个源矢量寄存器。
Ø T表示矢量寄存器中通道的数据类型。