JackLi · 2022年03月01日

HVX 指令介绍

原文:知乎
文章发表于知乎专栏《移动端算法优化》
作者:高性能计算学院

一、概述

随着骁龙多核AI引擎的发展,异构计算算力有了巨大提升,为了发挥CDSP HVX在AI和CV的优势,开发者必须熟练掌握HVX的指令。但是HVX指令多而复杂,基础指令就有上百条及一些复杂的特殊指令,写起来更是费劲。为了发挥其性能高的优势,开发者必须使用intrinsic或汇编的方式编排代码,因此完全掌握HVX指令至关重要。

本文接下来首先详细介绍HVX硬件架构,在了解架构的基础上着重介绍HVX指令相关内容,然后针对HVX开发中必须要掌握的HVX instrinsic展开图文并茂的说明。主要内容有HVX架构、寄存器、VLIW、汇编格式及ALU的Intrinsics指令使用,最后针对几个常用的Intrinsics 指令结合实例进行说明。

二、HVX硬件介绍

2.1 处理器架构

在上一篇文章中HVX架构简介详细介绍了HVX软件和硬件架构,为了能够更加深入理解HVX指令执行过程,此处详细说明Hexagon主处理器和hexagon HVX SIMD协处理的关系和指令执行过程。

Hexagon主处理和HVX协处理架构图

2.1.1 Hexagon主处理器的架构

Hexagon主处理器架构图

  • Hexagon 主处理器架构,由四或六个(V68 cDSP)硬件线程组成,硬件线程共享一个指令cache(I)、数据cache(D)。
  • 每个硬件线程都可以访问执行 Hexagon 标量指令,这些指令在单个或成对 32 位寄存器上执行定点和浮点运算。
  • 每个硬件线程包含通用寄存器(R0-R31)、预测判断寄存器(P0-P3)及执行单元(S0-S3)。
  • 静态分组超长指令集架构(VLIW)。

2.1.2 HVX协处理器的架构

HVX协处理架构图

  • Hexagon HVX协处理架构,有四个HVX硬件线程,硬件线程间共享L2-cache和VTCM内存。
  • 每个硬件线程都可以访问执行HVX向量指令。
  • 每个硬件线程包含向量寄存器(V0-V31)、预测判断寄存器(Q0-Q3)、执行单元(ALU)及内存读写单元。
  • HVX指令可以使用Hexagon主处理器的标量操作数。
  • 静态分组超长指令集架构(VLIW)。

2.1.3 指令执行过程

Hexagon架构图

  • 指令执行过程从L2-cache加载指令和数据到I-cache和D-chche,经过Sequencer指令打包然后在执行单元(S0-S3)上执行。

2.1.4 Hexagon主处理器和HVX协处理指令共享

HVX协处理和主处理器间的VFIFO架构

Hexagon core硬件线程通过访问 HVX 寄存器文件(HVX Context)来使用 HVX 协处理器。Hexagon core和HVX之间的指令包基于VFIFO结构进行共享。因此同一个指令包中HVX指令可以与标量指令混合,如下所示,VFIFO和HVX的处理器结构和指令混合汇编代码。

{
    r8 = add(r15,#128) //标量指令
    r14 = add(r13,#-1)
    r13 = add(r3,#128)
    v8 = vmem(r10+#0)  // HVX指令
}

以上讲解了整体架构,接下来着重说明HVX寄存器的内容。

2.2 HVX向量寄存器

2.2.1 HVX向量长度

HVX协处理随着架构不同向量寄存器长度分为64B(512bit)和128B(1024bit)模式,V66架构既包含64B也包含128B向量长度(两个HVX核为64B、两个HVX核为128B),V68架构以后只包含128B向量长度(四个HVX核均为128B),如下图所示。

向量寄存器长度

2.2.2 HVX向量寄存器数量及类别

本文中我们以V68架构展开说明,如上图右侧框内。每个HVX协处理器包含:

  • 32个1024bit的向量寄存器,V0-V31。
  • 4个128bit的预测寄存器,Q0-Q3。

向量寄存器

存放向量数据,每个向量元素的类型必须相同。向量寄存器根据处理元素的大小可以划分为 32/64/128个通道,对应数据类型为32bit/16bit/8bit。

1024-bit SIMD register

向量寄存器数据在SIMD指令操作中需要指定元素类型,例如:

// vo向量寄存器数据指定数据类型为signed byte
v0.b = vadd(v1.b,v2.b) //汇编
HVX_Vector Q6_Vb_vadd_VbVb(HVX_Vector Vu, HVX_Vector Vv) //intrinsic

常用的元素类型如下:

image.png
预测寄存器

预测寄存器每个bit位对应每个向量寄存器中的byte,既可以用来保存字节、半字和全字的比较结果。预测寄存器用Q表示,以下示例中,Q2代表预测寄存器的结果,在向量V5和V6中选择有效值在结果寄存器中。

// vmux选择指令,如果Q2为真,输出结果为V5,反之输出结果为V6
V4 = vmux(Q2, V5, V6)

三、HVX指令

这部分主要介绍VLIW、HVX指令分类和HVX汇编代码的格式和说明。

3.1 VLIW

3.1.1 VLIW解释说明

将指令的并行化(多发射)显式的声明在指令格式中,而处理器只负责执行指令,这种方式定义为超长指令字(Very Long Instruction Word,VLIW)体系结构。指令的并行化可由编译器完成,也可由开发者手写汇编代码完成,如下图所示红色圈内所示,即指令的并行化过程。

HVX的协处理指令集架构也是基于VLIW。因此,HVX协处理器有四个执行单元,理论上每个周期处理器可以执行4条指令。这4条指令看成一个指令包,取指、译码、执行单元每次都针对一个指令包进行操作,如下图所示,这就是HVX的VLIW。

指令包

虽然说指令按照VLIW并行化打包为指令包,但是也会有一定的打包规则,并不是任意组合,所以接下来会介绍HVX指令的打包规则。

3.1.2 HVX VLIW打包规则

HVX计算资源

HVX 提供了 6 种执行资源支持向量指令执行:loadstoreshiftpermute 以及 两个 multiply

  • 每条 HVX 指令均由上述部分资源组合而成,在下图中的S0-S3硬件资源执行
  • 每次并行执行一个指令包,由下图中指令单元负责打包分发
  • 每个指令包最多可以包含 4 条指令及一个循环结束指令,指令包需按照规则组合

VLIW打包规制

每个指令分别使用四个执行单元(shift、permute 以及 两个 multiply),且每个slot只能有一条指令,下表为HVX指令到slot执行单元的映射关系,手册中每条指令都会写可以占用的slot。

image.png

同时需要注意每个HVX指令对于执行单元的占用情况,在每条指令介绍下都会有说明,例如vmpa指令,下图红框内会对指令的HVX资源使用情况进行说明。

  • 指令包中只能包含两条load指令,或者一条load一条store指令
{
    V4 = vmem(iptrUp++#1)
    vmem(iptrUp++#1) = v4.cur
    nop
    nop
}

{
    V4 = vmem(iptrUp++#1)
    V5 = vmem(iptrUp++#1)
    nop
    nop
}
  • 若指令包中为简单的ALU,则均可以使用S0-S3
{ 
    v14 = vnot(V14)                               // v14 = left bounary mask
    v16 = vsplat(R8)                              // 0xFF 
    v12 = #0                                      // reset flag-collector
    v6 = vlalign(v0,v2,#3)                        //[1]pixels #12
}
  • 不要在指令包中存在多个乘法,因为乘法只有在执行单元S2和S3上执行
//存在错误 -- vmpa指令需要使用S2和S3的两个乘法资源,指令包中只能包含一个vmpa指令
{ 
    v1= vmem(iptr3++#1)                      //[1]
    v2.h += vmpa(v0.ub,c1c1c1c1.b)           //[1]
    v3.h += vmpa(v0.ub,c1c1c1c1.b)          
}

VLIW打包规制示例

以下样例为HVX协处理的指令包,指令在HVX处理器执行。

{
    v15 = vmem(R0++M1)
    vmem(R6++M1) = v14
    v18.uw = vrmpy(v16.ub, v17.ub)
}

上述HVX指令包完成一个1024bit数据加载,一次1024bit数据存储及地址更新,一次向量乘法(vrmpy占用两个乘法资源,在指令包中只能存在一条)。

3.2 HVX指令分类

根据指令使用的硬件资源类型不同,HVX指令分为以下几类 :

image.png

3.3 汇编指令格式介绍

汇编格式

v<x><.><type> = <ops>(<input>)[:carry][:sat]
  • x表示32个向量寄存器的标识数字,type 为向量寄存器中数据的类型
  • type表示既可以向量寄存器中数据的类型:b、ub、h、uh、w、uw,也可以表示Aligned memory Load和Store数据的立即更新还是作为临时数据:.tmp、.new、.cur。
  • <ops> 为指令的具体指令操作标识,如 add、valignvaslvdmp 等等。

汇编示例

以有符号 halfword 的加法汇编指令为例,指令将 V4 中各 lane 的数据与 V3 中各 lane 的数据以有符号short进行进行相加,将结果存放在 V2 寄存器中。

V2.h=vadd(V3.h,V4.h)

HVX 汇编开发难点

HVX 的开发不仅仅是 SIMD 指令集的运用,汇编期 VLIW 指令的静态多发调度也尤为重要

相对于 intrinsic 指令而言

  • HVX 汇编开发需要对 HVX 指令集及硬件架构进行详细的学习 - 增加学习成本
  • HVX 汇编开发过程中需要对 HVX 指令打包,程序流程控制指令,.new\.tmp 指令,指令延迟,寄存器复用以及软件堆栈维护等多方面进行考虑 - 增加开发及维护难度,不利于迭代及增量开发
  • HVX 汇编指令严格对应硬件平台架构 - 不利于向后兼容扩展

因此,在非绝对性能瓶颈的实现中,建议使用 intrinsic 指令进行开发。

四、HVX intrinsics指令介绍

相比于汇编指令,HVX intrinsics 是一种更简单的编写 HVX 代码的方法,HVX Intrinsics 类似于 C 函数调用,在编译时由编译器替换为相应的汇编指令,使用时需要包含头文件hvx\_hexagon\_protos.h_。_

4.1 向量类型格式

// 向量对格式
HVX_VectorPair
// 向量格式
HVX_Vector
// 判断向量格式
HVX_VectorPred

HVX的向量类型包含以上三种,其中HEXAGON\_ARCH架构在V65以后HVX四核的向量长度为1024bit,V62到V65架构之前分为512bit和1024bit的HVX向量长度。HVX\_Vector和HVX\_VectorPred共用联合体,实现多种数据类型宽度兼容。

typedef union 
{
    size8u_t ud[MAX_VEC_SIZE_BYTES/8];
    size8s_t    d[MAX_VEC_SIZE_BYTES/8];
    size4u_t uw[MAX_VEC_SIZE_BYTES/4];
    size4s_t    w[MAX_VEC_SIZE_BYTES/4];
    size2u_t uh[MAX_VEC_SIZE_BYTES/2];
    size2s_t    h[MAX_VEC_SIZE_BYTES/2];
    size1u_t ub[MAX_VEC_SIZE_BYTES/1];
    size1s_t    b[MAX_VEC_SIZE_BYTES/1];
    size4s_t    qf32[MAX_VEC_SIZE_BYTES/4];
    size2s_t    qf16[MAX_VEC_SIZE_BYTES/2];
    size4s_t    sf[MAX_VEC_SIZE_BYTES/4];
    size2s_t    hf[MAX_VEC_SIZE_BYTES/2];
} mmvector_t, mmqreg_t;

#ifdef __HVXDBL__
 typedef mmqreg_t        HEXAGON_VecPred128;
 typedef mmvector_t      HEXAGON_Vect1024;
 typedef mmvector_pair_t HEXAGON_Vect2048;
 #define HVX_VectorPred  HEXAGON_VecPred128
 #define HVX_Vector      HEXAGON_Vect1024
 #define HVX_VectorPair  HEXAGON_Vect2048
#else
 typedef mmqreg_t        HEXAGON_VecPred64;
 typedef mmvector_t      HEXAGON_Vect512;
 typedef mmvector_pair_t HEXAGON_Vect1024;
 #define HVX_VectorPred  HEXAGON_VecPred64
 #define HVX_Vector      HEXAGON_Vect512
 #define HVX_VectorPair  HEXAGON_Vect1024
#endif /*HVXDBL*/
  • HVX\_VectorPair:向量对组合,由两个HVX\_Vector向量组成,常用在指令运算后结果是奇偶排列结果中,例如如下数据扩展指令,把一个HVX\_Vector向量扩展为HVX\_VectorPair,Vdd.v[0]和Vdd.v[1]组成一个向量对(HVX\_VectorPair),占用两个向量寄存器。

4.2 HVX intrinsics格式

Q6_<output_type>_<opname>_<input_type><input_type>_<mod>

1)Q6 表示高通第六代数字信号处理器,固定前缀

2) <output\_type> 表示返回值类型,此处分为W和V开头,比如Ww,Vw等

  • Vw :表示输出类型为HVX\_Vector,元素类型为word类型
  • Ww:表示输出类型为HVX\_VectorPair,元素类型为word类型

image.png
3) <opname> 表示具体操作,比如vadd,vmpy等

4) <input\_type> 表示输入值类型,此处分为W、V、R和Q开头及组合,例如

HVX_VectorPair Q6_Wh_vmpy_VubRb(HVX_Vector Vu, Word32 Rt)

Q6_Wh_vmpy_VubRb
  • W:表示输入类型为HVX\_VectorPair向量对
  • V:表示输入类型为HVX\_Vector向量
  • Q:表示输入类型为HVX\_VectorPred预测向量
  • R:表示输入类型为Word标量

image.png

5) <mod>

  • sat:表示饱和操作,例如
// Vu加Vv的结果做饱和操作
HVX_Vector Q6_Vb_vadd_VbVb_sat(HVX_Vector Vu, HVX_Vector Vv)
  • rnd:表示四舍五入操作,例如
// Vu加Vu的结果做进位输出操作
HVX_Vector Q6_Vw_vadd_VwVwQ_carry(HVX_Vector Vu, HVX_Vector Vv, HVX_VectorPred* Qp)
  • s1:表示左移一位操作,例如
// Vu加Vb的结果取平均值的四舍五入结果
HVX_Vector Q6_Vb_vavg_VbVb_rnd(HVX_Vector Vu, HVX_Vector Vv)

下面的章节将对每种类型的HVX intrinsic指令做出详细的描述。

4.3 Load/Store

按照地址对齐或者地址非对齐的方式在向量寄存器和内存之间搬移数据

HVX LOAD指令

  • 示例:
// 对齐地址
HVX_Vector v1 = *(HVX_Vector *)src;

// 非对齐地址
typedef long HEXAGON_Vect_UN __attribute__((__vector_size__(VLEN)))__attribute__((aligned(4)));
HEXAGON_Vect_UN v2 = *(HEXAGON_Vect_UN *)src; 
  • 作用:完成内存到寄存器的数据搬运
  • 伪代码:Vd=vmem(Rt+#0)
  • 结果示意图:紫色框内代表输入数据

  • 相关指令
// 地址对齐加载             // 地址非对齐加载 
Vd=vmem(Rt)                vd = vmemu(Rt) 
// 立即使用加载            
Vd.cur=vmem(Rt+#s4)    
// 临时立即使用加载
Vd.tmp=vmem(Rt+#s4) 

HVX STORE指令

  • 示例:
// 对齐地址
HVX_Vector v1;
*(HVX_Vector *)src = v1;

// 非对齐地址
HEXAGON_Vect_UN v2;
typedef long HEXAGON_Vect_UN __attribute__((__vector_size__(VLEN)))__attribute__((aligned(4)));
*(HEXAGON_Vect_UN *)src = v2; 
  • 作用:完成寄存器到内存的数据搬运
  • 伪代码:vmem(Rt+#0)= Vd
  • 结果示意图

  • 相关指令
// 字节使能对齐存储
If (Qv4) vmem(Rt) = Vs
// 新存储
vmem(Rt)=Os8.new
// 对齐存储
vmem(Rt)=Vs
// 非对齐存储
vmemu(Rt)=Vs

HVX 非对齐内存操作

  • vmemu非对齐操作指令,需要进行两次内存访问,与地址对齐访问相比会增加功耗和带宽,因此开发中我们建议尽量使用对齐LOAD和STORE指令。
  • vmemu非对齐指令占用两个HVX单元,分别是slot0和slot1。

HVX 对齐内存操作

  • vmem对齐操作指令,内存地址必须保证128Byte对齐。
  • 可以使用memalign(128,xxx)申请128Byte对齐内存,也可以使用ION相关函数申请。

4.4 Arithmetic

基本加减算法指令,完成向量数据的算法操作

vector 算术指令

  • 示例:HVX_Vector Q6_Vb_vsub_VbVb_sat(HVX_Vector Vu, HVX_Vector Vv)
  • 作用:两个输入vector加减法运算
  • 伪代码
for (i = 0; i < VELEM(8); i++) 
{
    Vd.b[i] = sat_s8(Vu.b[i] - Vv.b[i]) ;
}
  • 示意图

  • 相关指令
// type 类型加法:Vd.type = Vu.type + Vv.type;
HVX_Vector Q6_Vtype_vadd_VtypeVtype(HVX_Vector Vu, HVX_Vector Vv);

// type 类型加法,结果进行饱和操作:Vd.type = sat_type(Vu.type + Vv.type);
HVX_Vector Q6_Vtype_vadd_VtypeVtype_sat(HVX_Vector Vu, HVX_Vector Vv);

// type 类型减法:Vd.b = Vu.b - Vv.b;
HVX_Vector Q6_Vtype_vsub_VtypeVtype(HVX_Vector Vu, HVX_Vector Vv);

type:b、ub、h、uh、w、uw

vector pair 算术指令

  • 示例:HVX_VectorPair Q6_Ww_vadd_VhVh(HVX_Vector Vu, HVX_Vector Vv)
  • 作用:向量寄存器 Vu 与 Vv 进行相加操作,结果输出长度是输入的两倍,存放于向量对寄存器里,按照奇偶的方式排列。
奇偶排列:输入数据下标索引02468的结果存放在单独寄存器是偶排列,反之,13579...127是奇排列。
  • 伪代码
for (i = 0; i < VELEM(32); i++) 
{
    Vdd.v[0].w[i] = Vu.w[i].h[0] + Vv.w[i].h[0];
    Vdd.v[1].w[i] = Vu.w[i].h[1] + Vv.w[i].h[1];
}
  • 指令示意图

vadd

  • 结果示意图

  • 相关指令
// type 类型加法:vdd.type = Vu.type + Vv.type;
HVX_VectorPair Q6_Wtype0_vadd_Vtype1Vtype1(HVX_Vector Vu, HVX_Vector Vv)

// type 类型减法:vdd.type = Vu.type - Vv.type;
HVX_VectorPair Q6_Wtype0_vsub_Vtype1Vtype1(HVX_Vector Vu, HVX_Vector Vv)

// type 类型加法累加:vdd.type += Vu.type + Vv.type;
HVX_VectorPair Q6_Wtype0_vaddacc_Wtype0Vtype1Vtype1(HVX_VectorPair Vxx, 
                                      HVX_Vector Vu, HVX_Vector Vv)

type0: h、w

type1: ub、h、uh

4.5 Logical and compare

eq表示相等,gt表示大于,eqand表示等于判断及与操作,gtand表示大于判断及与操作,eqxacc表示等于判断及异或操作,gtxacc表示大于判断及异或操作,eqor表示等于判断及或操作,gtor表示大于判断及或操作。

条件比较指令

  • 示例:HVX_VectorPred Q6_Q_vcmp_eq_VbVb(HVX_Vector Vu, HVX_Vector Vv)
  • 作用:向量寄存器 Vu 与 Vv 进行比较运算,结果存储在预测寄存器中
  • 伪代码
for( i = 0; i < VWIDTH; i += 1) 
{
    QxV[i] =((Vu.b[i] == Vv.b[i]) ? 0x1 : 0);
}
  • 示例结果

  • 相关指令
// type 类型相等判断:Qd4 = Vu == Vv;
HVX_VectorPred Q6_Q_vcmp_eq_Vtype0Vtype0(HVX_Vector Vu, HVX_Vector Vv);

// type 类型大于判断:Qd4 = Vu > Vv;
HVX_VectorPred Q6_Q_vcmp_gt_Vtype1Vtype1(HVX_Vector Vu, HVX_Vector Vv);

// type 类型相等判断及与操作:Qx4 &= Vu == Vv;
HVX_VectorPred Q6_Q_vcmp_eqand_QVtype0Vtype0(HVX_VectorPred Qx, HVX_Vector Vu, HVX_Vector Vv);
                                
// type 类型大于判断及与操作:Qx4 &= Vu > Vv;
HVX_VectorPred Q6_Q_vcmp_gtand_QVtype1Vtype1(HVX_VectorPred Qx, HVX_Vector Vu, HVX_Vector Vv); 
 
// type 类型相等判断及异或操作:Qx4 ^= Vu == Vv;
HVX_VectorPred Q6_Q_vcmp_eqxacc_QVtype0Vtype0(HVX_VectorPred Qx, HVX_Vector Vu, HVX_Vector Vv);

// type 类型大于判断及异或操作:Qx4 ^= Vu > Vv;
HVX_VectorPred Q6_Q_vcmp_gtxacc_QVtype1Vtype1(HVX_VectorPred Qx, HVX_Vector Vu, HVX_Vector Vv);

// type 类型相等判断及或操作:Qx4 |= Vu == Vv;
HVX_VectorPred Q6_Q_vcmp_eqor_QVtype0Vtype0(HVX_VectorPred Qx, HVX_Vector Vu, HVX_Vector Vv);
                                
// type 类型大于判断及或操作:Qx4 |= Vu > Vv;
HVX_VectorPred Q6_Q_vcmp_gtor_QVtype1Vtype1(HVX_VectorPred Qx, HVX_Vector Vu, HVX_Vector Vv); 

type0:b、h、w

type1:b、ub、h、uh、w、uw

位操作指令

  • 示例:HVX_VectorPred Q6_Q_not_Q(HVX_VectorPred Qs)
  • 作用:将条件寄存器 Qs 进行取反操作,结果保存至条件寄存器中
  • 伪代码
for (i = 0; i < VELEM(8); i++) 
{
    QdV[i] = !QsV[i];
}
  • 示意图:蓝色框代表输入数据,红色框代表输出数据

  • 相关指令
// 条件寄存器非与操作:Qd4 = Qs & ((!Qt) 或者 (Qt))
HVX_VectorPred Q6_Q_and_QQ<n>(HVX_VectorPred Qs, HVX_VectorPred Qt);

// 条件寄存器非或操作:Qd4 = Qs | ((!Qt) 或者 (Qt))
HVX_VectorPred Q6_Q_or_QQ<n>(HVX_VectorPred Qs, HVX_VectorPred Qt);

// 条件寄存器非操作:Qd4 = !Qs
HVX_VectorPred Q6_Q_not_Q(HVX_VectorPred Qs);

// Vu 与标量 Rt 进行 u8 类型位与操作,填充条件寄存器:
// Qd4 = Vu.ub & Rt.ub => Qd4[i] = Vu.ub[i] & Rt.ub[i % 4] ? 1 : 0;
HVX_VectorPred Q6_Q_vand_VR(HVX_Vector Vu, Word32 Rt);

// Vu 与标量 Rt 进行 u8 类型位与操作,结果与 Qx 进行或操作,Qx4 |= (Vu.ub & Rt.ub);
HVX_VectorPred Q6_Q_vandor_QVR(HVX_VectorPred Qx, HVX_Vector Vu, Word32 Rt);

// 根据 !Qu 使用 Rt 以 u8 类型选择性填充 Vd
HVX_Vector Q6_V_vand_QnR(HVX_VectorPred Qu, Word32 Rt);

// 根据 !Qu 使用 Rt 以 u8 类型选择性填充 Vd, 然后与 Vx 进行或运算
HVX_Vector Q6_V_vandor_VQnR(HVX_Vector Vx, HVX_VectorPred Qu, Word32 Rt);

// 根据 Qu 使用 Rt 以 u8 类型选择性填充 Vd
HVX_Vector Q6_V_vand_QR(HVX_VectorPred Qu, Word32 Rt);

// 根据 Qu 使用 Rt 以 u8 类型选择性填充 Vd, 然后与 Vx 进行或运算
HVX_Vector Q6_V_vandor_VQR(HVX_Vector Vx, HVX_VectorPred Qu, Word32 Rt);

// 根据条件寄存器 Qt,以 u8 类型从 Vu 及 Vv 中选择填充 Vd
HVX_Vector Q6_V_vmux_QVV(HVX_VectorPred Qt, HVX_Vector Vu, HVX_Vector Vv);

// 根据条件寄存器 Qt,以 u8 类型对 Vu 及 Vv 进行交换操作,结果保存至 HVX_VectorPair 中
HVX_VectorPair Q6_W_vswap_QVV(HVX_VectorPred Qt, HVX_Vector Vu, HVX_Vector Vv);

逻辑操作指令

  • 示例:HVX_Vector Q6_V_vnot_V(HVX_Vector Vu)
  • 作用:对向量寄存器Vu按位逻辑取反
  • 伪代码
for (i = 0; i < VELEM(16); i++) 
{
    Vd.uh[i] = ~Vu.uh[i] ;
}
  • 示意图

  • 相关指令
// Vu和Vv向量寄存器按位逻辑与
HVX_Vector Q6_V_vand_VV(HVX_Vector Vu, HVX_Vector Vv)

// 对Vu向量寄存器按位取反
HVX_Vector Q6_V_vnot_V(HVX_Vector Vu)

// Vu和Vv向量寄存器按位逻辑或
HVX_Vector Q6_V_vor_VV(HVX_Vector Vu, HVX_Vector Vv)

// Vu和Vv向量寄存器按位逻辑异或
HVX_Vector Q6_V_vxor_VV(HVX_Vector Vu, HVX_Vector Vv)

取极值操作

  • 示例指令:HVX_Vector Q6_Vb_vmax_VbVb(HVX_Vector Vu, HVX_Vector Vv)
  • 作用:将两个数值向量寄存器进行比较操作,输出最大值到目的寄存器
  • 伪指令
for (i = 0; i < VELEM(8); i++) 
{
    Vd.b[i] = (Vu.b[i] > Vv.b[i]) ? Vu.b[i] : Vv.b[i] ;
}
  • 示意图

  • 相关指令
// type 类型最小值: Vd = min(Vu, Vv);
HVX_Vector Q6_Vtype_vmin_VtypeVtype(HVX_Vector Vu, HVX_Vector Vv);

// type 类型最大值: Vd = max(Vu, Vv);
HVX_Vector Q6_Vtype_vmax_VtypeVtype(HVX_Vector Vu, HVX_Vector Vv);

type:b、ub、h、uh、w、uw

饱和操作

  • 示例指令:HVX_Vector Q6_Vub_vsat_VhVh(HVX_Vector Vu, HVX_Vector Vv)
  • 作用:向量寄存器 Vu Vv 中 s16 类型数据,进行 u8 类型饱和操作,然后寄存器高低交替保存至 Vd 寄存器中
  • 伪代码
for (i = 0; i < VELEM(16); i++) 
{
    Vd.uh[i].b[0]=usat_8(Vv.h[i]);
    Vd.uh[i].b[1]=usat_8(Vu.h[i]);
}
  • 示意图

  • 相关指令
// s16 类型 Vu Vv 饱和至 u8 类型
HVX_Vector Q6_Vub_vsat_VhVh(HVX_Vector Vu, HVX_Vector Vv);

// u32 类型 Vu Vv 饱和至 u16 类型
HVX_Vector Q6_Vuh_vsat_VuwVuw(HVX_Vector Vu, HVX_Vector Vv);

// s32 类型 Vu Vv 饱和至 s16 类型
HVX_Vector Q6_Vh_vsat_VwVw(HVX_Vector Vu, HVX_Vector Vv;

绝对值操作

  • 示例指令:HVX_Vector Q6_Vb_vabs_Vb(HVX_Vector Vu)
  • 作用:向量寄存器以 s8 的数值类型进行取绝对值操作,结果依旧为 s8 类型
  • 伪代码
for (i = 0; i < VELEM(8); i++) 
{
    Vd.b[i] = ABS(Vu.b[i]);
}
  • 示例结果

  • 相关指令
// type 类型绝对值操作,结果进行饱和操作
HVX_Vector Q6_Vtype0_vabs_Vtype0_sat(HVX_Vector Vu);

// type 类型绝对值操作
HVX_Vector Q6_Vtype0_vabs_Vtype0(HVX_Vector Vu);

// type 类型绝对值差操作
HVX_Vector Q6_Vtype1_vabsdiff_Vtype2Vtype2(HVX_Vector Vu, HVX_Vector Vv);

type0:b、h、w

type1:ub、uh、uw

type2:ub、h、uh、w

数值扩展

  • 示例指令:HVX_VectorPair Q6_Wuh_vzxt_Vub(HVX_Vector Vu)
  • vzxt 为无符号扩展指令,将原数值类型进行正向调整,扩展后使用 HVX_VectorPair 进行存储,数值按照偶数位和奇数位分别存储至 Vdd.v[0] 和 Vdd.v[1] 中
  • 伪代码:注意结果寄存器存储时按照奇偶交替的顺序存储
// vzxt:u8 -> u16 类型转换
for (i = 0; i < VELEM(16); i++) 
{
    Vdd.v[0].uh[i] = Vu.uh[i].ub[0];
    Vdd.v[1].uh[i] = Vu.uh[i].ub[1];
}
  • 指令示意图

  • 相关指令
// u16 类型进行零填充扩展至 u32 类型
HVX_VectorPair Q6_Wuw_vzxt_Vuh(HVX_Vector Vu);

// s16 类型进行有符号填充扩展至 s32 类型
HVX_VectorPair Q6_Ww_vsxt_Vh(HVX_Vector Vu);

// s8 类型进行有符号填充扩展至 s16 类型
HVX_VectorPair Q6_Wh_vsxt_Vb(HVX_Vector Vu);

4.6 Multiply

向量乘标量运算

  • 示例:HVX_VectorPair Q6_Wh_vmpy_VubRb(HVX_Vector Vu, Word32 Rt)
  • 作用:向量寄存器 Vu 与标量 Rt 的元素进行乘法操作,数值按照偶数位和奇数位分别存储至 Vdd.v[0] 和 Vdd.v[1] 中。
  • 伪代码
for (i = 0; i < VELEM(16); i++) 
{
    Vdd.v[0].h[i] = (Vu.uh[i].ub[0] * Rt.b[(2*i+0)%4]);
    Vdd.v[1].h[i] = (Vu.uh[i].ub[1] * Rt.b[(2*i+1)%4]) ;
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// s16 类型向量乘 s16 标量运算,结果左移一位且四舍五入,做饱和操作
HVX_Vector Q6_Vh_vmpy_VhRh_s1_rnd_sat(HVX_Vector Vu, Word32 Rt)

// s16 类型向量乘 s16 标量运算,结果左移一位且饱和操作
HVX_Vector Q6_Vh_vmpy_VhRh_s1_sat(HVX_Vector Vu, Word32 Rt)

// s16 类型向量乘 s16 标量运算
HVX_VectorPair Q6_Wh_vmpy_VubRb(HVX_Vector Vu, Word32 Rt)

// type 类型向量乘加 type 标量运算
HVX_VectorPair Q6_Wtype_vmpyacc_WtypeVtypeRtype(HVX_VectorPair Vxx, HVX_Vector Vu, Word32 Rt)

// s16 类型向量乘加 s16 标量运算
HVX_VectorPair Q6_Ww_vmpyacc_WwVhRh_sat(HVX_VectorPair Vxx, HVX_Vector Vu, Word32 Rt)

向量乘加运算(乘标量)

  • 示例:HVX_VectorPair Q6_Wh_vmpa_WubRb(HVX_VectorPair Vuu, Word32 Rt)
  • 作用:向量对寄存器Vuu乘以Rt标量的各元素,数值按照偶数位和奇数位分别存储至 Vdd.v[0] 和 Vdd.v[1] 中。
  • 伪代码
for (i = 0; i < VELEM(16); i++) 
{
    Vdd.v[0].h[i] = (Vuu.v[0].uh[i].ub[0] * Rt.b[0]) 
                    + (Vuu.v[1].uh[i].ub[0] * Rt.b[1]);
                    
    Vdd.v[1].h[i] = (Vuu.v[0].uh[i].ub[1] * Rt.b[2]) 
                    + (Vuu.v[1].uh[i].ub[1] * Rt.b[3]);
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// type 类型的 Vuu 与 type 类型的Rt进行乘加操作,结果为 type 类型
HVX_VectorPair Q6_Wtype0_vmpa_Wtype1Rtype2(HVX_VectorPair Vuu, Word32 Rt);

// type 类型的 Vuu 与 type 类型的Rt进行乘加操作,结果与Vxx进行累加,结果为 type 类型
HVX_VectorPair Q6_Wtype0_vmpaacc_Wtype0Wtype1Rtype2(HVX_VectorPair Vxx, HVX_VectorPair Vuu, Word32 Rt);

type0:h、w

type1:b、ub、h、uh

type2:b、ub

乘法 vdmpy操作(乘标量)

  • 示例:HVX_Vector Q6_Vh_vdmpy_VubRb(HVX_Vector Vu, Word32 Rt)
  • 作用:向量寄存器 Vu 与标量 Rt 进行乘法操作,结果相邻对累加操作。
  • 伪代码
for (i = 0; i < VELEM(16); i++) 
{
    Vd.h[i] =  (Vu.uh[i].ub[0] * Rt.b[(2 * i    ) % 4]);
    Vd.h[i] += (Vu.uh[i].ub[1] * Rt.b[(2 * i + 1) % 4]) ;
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// type 类型向量与 type 类型标量乘法 2 路归约操作
HVX_Vector Q6_Vtype0_vdmpy_Vtype1Rb(HVX_Vector Vu, Word32 Rt);

// type 类型向量与 type 类型标量完成 2 路归约操作
HVX_Vector Q6_Vtype0_vdmpyacc_Vtype0Vtype1Rb(HVX_Vector Vx, HVX_Vector Vu, Word32 Rt);
  1. type0:h、w
  2. type1:ub、h

乘法 vrmpy 操作(乘标量)

  • 示例:HVX_Vector Q6_Vuw_vrmpy_VubRub(HVX_Vector Vu, Word32 Rt)
  • 作用:向量寄存器 Vu 与标量 Rt u进行乘法操作,结果相邻 4 元素累加操作,输出结果为u32类型。
  • 伪代码:
for (i = 0; i < VELEM(32); i++) 
{
    Vd.uw[i]  = (Vu.uw[i].ub[0] * Rt.ub[0]);
    Vd.uw[i] += (Vu.uw[i].ub[1] * Rt.ub[1]);
    Vd.uw[i] += (Vu.uw[i].ub[2] * Rt.ub[2]);
    Vd.uw[i] += (Vu.uw[i].ub[3] * Rt.ub[3]) ;
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// u8 类型向量与 s8 类型标量完成 4 路归约操作,结果为 s32 类型
HVX_Vector Q6_Vtype0_vrmpy_VubRtype1(HVX_Vector Vu, Word32 Rt);

// u8 类型向量与 u8 类型标量完成 4 路归约操作,然后进行 Vx 结果累加,结果为 u32 类型
HVX_Vector Q6_Vtype0_vrmpyacc_Vtype0VubRtype1(HVX_Vector Vx, HVX_Vector Vu, Word32 Rt);
  1. type0:w、uw
  2. type1:b、ub

4.7 Shift

立即数移位

  • 示例:HVX_Vector Q6_Vuw_vlsr_VuwR(HVX_Vector Vu, Word32 Rt)
  • 作用:向量寄存器 Vu 根据立即数 Rt 进行逻辑右移。
  • 伪代码
// vlsr
for (i = 0; i < VELEM(32); i++) 
{
    Vd.uw[i] = (Vu.uw[i] >> (Rt & (32 - 1)));
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// s16 类型根据 Rt 进行算数右移,结果四舍五入后饱和至 s8 类型,Vu Vv 移位结果奇偶交疊存储之 Vd 中
HVX_Vector Q6_Vb_vasr_VhVhR_rnd_sat(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt);

// type 类型根据 Rt 进行逻辑右移,结果为 type 类型
HVX_Vector Q6_Vtype_vlsr_VtypeR(HVX_Vector Vu, Word32 Rt);

// s16 类型根据 Rt 进行算数右移
HVX_Vector Q6_Vh_vasr_VhR(HVX_Vector Vu, Word32 Rt);

// s16 类型算数右移 Rt,结果饱和至 s8 类型,Vu Vv 移位结果奇偶交疊存储之 Vd 中
HVX_Vector Q6_Vb_vasr_VhVhR_sat(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt);
  1. type:ub、uh、uw

Narrowing移位

  • 示例指令:

HVX_Vector Q6_Vh_vasr_VwVwR_sat(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt)

  • 作用:算术右移,结果饱和操作,输入位宽是输出的两倍。
  • 伪代码
for (i = 0; i < VELEM(32); i++) 
{
    shamt = Rt & 0xF; 
    Vd.w[i].h[0]=[sat_16](Vv.w[i] >> shamt); 
    Vd.w[i].h[1]=[sat_16](Vu.w[i] >> shamt); 
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// type类型(round)算术右移,结果饱和操作
HVX_Vector Q6_Vtype0_vasr_Vtype1Vtype1R_rnd_sat(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt)
 // type类型算术右移,结果饱和操作                                            
HVX_Vector Q6_Vtype0_vasr_Vtype1Vtype1R_sat(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt)
  1. type0:b、ub、h、uh
  2. type1:h、uh、w、uw

4.8 Permute

向量拼接

  • 示例:HVX_Vector Q6_V_valign_VVR(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt)
  • 作用:向量寄存器 Vu 和 Vv 根据 Rt 参数进行移位拼接,向量寄存器内数值按照 u8 类型处理。
  • 伪代码
unsigned shift = Rt & (VWIDTH-1); 
for(i = 0; i < VWIDTH; i++) 
{ 
    Vd.ub[i] = (i + shift>=VWIDTH) ? Vu.ub[i + shift - VWIDTH] \
                : Vv.ub[i + shift]; 
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// 向量 Vu 与 Vv 根据 (Rt & (VWIDTH-1)) 的起始进行移位拼接操作
HVX_Vector Q6_V_valign_VVR(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt);

// 向量 Vu 与 Vv 根据 (VWIDTH - (Rt & (VWIDTH-1))) 的起始进行移位拼接操作
HVX_Vector Q6_V_vlalign_VVR(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt);

// 向量 Vu 根据 Rt 进行向右循环重排
HVX_Vector Q6_V_vror_VR(HVX_Vector Vu, Word32 Rt);

向量旋转

  • 示例:HVX_Vector Q6_V_vror_VR(HVX_Vector Vu, Word32 Rt)
  • 作用:用于1个vector内部的循环移位,Rt最大127
  • 伪代码
for (k = 0; k < VWIDTH; k++) 
{ 
    Vd.ub[k] = Vu.ub[(k + Rt)&(VWIDTH - 1)]; 
}
  • 指令示意图

向量交织操作

  • 示例:HVX_Vector Q6_Vb_vshuffe_VbVb(HVX_Vector Vu, HVX_Vector Vv);
  • 作用:取 Vv 及 Vu 中的偶数位元素,然后进行交织存储到 Vd 寄存器
  • 伪代码
for (i = 0; i < VELEM(16); i++) 
{
    Vd.uh[i].b[0]=Vv.uh[i].ub[0];
    Vd.uh[i].b[1]=Vu.uh[i].ub[0] ;
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// 偶数位交织
HVX_Vector Q6_Vb_vshuffe_VbVb(HVX_Vector Vu, HVX_Vector Vv);

// 奇数位交织
HVX_Vector Q6_Vb_vshuffo_VbVb(HVX_Vector Vu, HVX_Vector Vv);

// 奇偶数位交织
HVX_VectorPair Q6_Wb_vshuffoe_VbVb(HVX_Vector Vu, HVX_Vector Vv);

向量Shuffle - Deal操作

  • 示例
// 将向量内偶数位元素存放至目标寄存器低半位,奇数位元素存放至寄存器高半位
HVX_Vector Q6_Vb_vdeal_Vb(HVX_Vector Vu);

// 向量内以低半位和高半位为单位进行奇偶数位解交织操作
HVX_Vector Q6_Vb_vshuff_Vb(HVX_Vector Vu);
  • 作用: vdeal 指令实现将 Vu 向量内的偶数位元素存放到 Vd 寄存器的低半位,奇数位元素存放到 Vd 寄存器的高半位,实现向量中数据奇偶数位的重排(后续简称交织操作)。 vshuff 指令则以向量的低半位和高半位为基本单位,进行奇偶数位的交织重排,与vdeal 互为逆操作(后续简称解交织操作)。
  • 伪代码
//vdeal
for (i = 0; i < VELEM(16); i++) 
{
    Vd.ub[i ] = Vu.uh[i].ub[0];
    Vd.ub[i+VBITS/16] = Vu.uh[i].ub[1];
}

//vshuff
for (i = 0; i < VELEM(16); i++) 
{
    Vd.uh[i].b[0]=Vu.ub[i];
    Vd.uh[i].b[1]=Vu.ub[i+VBITS/16];
}
  • 指令示意图

  • 结果示意图

  • 相关指令
// 两个向量的交织操作,接受一个常量 Rt 参数,Rt 参数影响交织操作的元素大小
HVX_VectorPair Q6_W_vdeal_VVR(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt);

// 两个向量的解交织操作,接受一个常量 Rt 参数,Rt 参数影响交织操作的元素大小
HVX_VectorPair Q6_W_vshuff_VVR(HVX_Vector Vu, HVX_Vector Vv, Word32 Rt);

4.9 其他

上述章节讲解了基本指令,并没有对复杂指令展开说明,例如查表指令、直方图统计、gather/scatter和vrdelta等复杂指令,后面我们会专门针对以上提到的复杂指令做详细的讲解说明。例如如下所示的vrdelta、vdelta指令的交织网络结构,这种复杂的指令网络设计结构,必须结合设计paper和使用规则才能完全理解。

vrdelta/vdelta指令网络

五、总结

本文主要介绍了 HVX 指令相关的知识,包括 HVX 寄存器相关内容,指令的背景依赖,部分常用 intrinsic HVX 指令。具体指令的详细内容及使用还需阅读 HVX 的指令文档,以及细致的实践操作。

推荐阅读

更多嵌入式AI技术相关内容请关注嵌入式AI专栏。
推荐阅读
关注数
16356
内容数
1226
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:aijishu20(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息