1. 程式人生 > 其它 >ARM NEON程式設計2.NEON基本資料型別與基本指令集

ARM NEON程式設計2.NEON基本資料型別與基本指令集

技術標籤:ARM NEON機器學習高效能運算

目錄

一. 基本資料型別

1.1 64bit資料型別

1.2 128bit資料型別

1.3 結構化資料型別

二. 基本指令集

2.1 初始化暫存器

vcreate

vdup

vmov

2.2 載入資料進暫存器

vld

vld_lane

2.3 儲存暫存器到記憶體

vst

vst_lane

2.4 讀取/修改暫存器資料

vget_lane

vget_low

vget_high

vset_lane

2.5 資料重排

vext

vtbl

vrev

vtrn

vzip

vuzp

vcombine

vbsl

2.6 型別轉換

vreinterpret

vcvt

2.7 加法減法指令

vadd

vaddl

vaddw

vhadd

vrhadd

vqadd

vaddhn

2.8 乘法指令

vmul

vmla

vmls

2.9 基本數學計算指令

vabs

vneg

vmax

vmin

vrnd

vrecp

vrsqrt

2.10 比較指令

vceq

vcge

vcgt

vcle

vclt

2.11 歸約指令

vpadd

vpmax

vpmin

2.12 位操作指令

vmvn

vand

vorr

veor

vcls

vshl

vrshl

參考資料


ARMv7架構的包含如下暫存器:

  1. 16個通用暫存器(32bit),R0~R15;
  2. 16個NEON暫存器(128bit),Q0~Q15,同時也可以被視為32個64bit的暫存器,D0~D31;
  3. 16個VFP暫存器(32bit),S0~S15.

NEON與VFP都能加速浮點計算,區別在於VFP不具備資料並行能力,在雙精度浮點數的計算效能上更好,而NEON則更優於多資料的平行計算。

一. 基本資料型別

NEON的向量資料型別都是按照以下規則命名的:

(type)x(lanes)_t

例如,int16x4_t就是一個包含4條向量線的向量資料,每條向量線是一個有符號16位整型數。

1.1 64bit資料型別

64bit的資料型別對映到D0~D31暫存器

typedef __Int8x8_t int8x8_t;
typedef __Int16x4_t int16x4_t;
typedef __Int32x2_t int32x2_t;
typedef __Int64x1_t int64x1_t;
typedef __Float16x4_t float16x4_t;
typedef __Float32x2_t float32x2_t;
typedef __Poly8x8_t poly8x8_t;
typedef __Poly16x4_t poly16x4_t;
typedef __Uint8x8_t uint8x8_t;
typedef __Uint16x4_t uint16x4_t;
typedef __Uint32x2_t uint32x2_t;
typedef __Float64x1_t float64x1_t;
typedef __Uint64x1_t uint64x1_t;

1.2 128bit資料型別

128bit資料型別的暫存器對映為Q0~Q15

typedef __Int8x16_t int8x16_t;
typedef __Int16x8_t int16x8_t;
typedef __Int32x4_t int32x4_t;
typedef __Int64x2_t int64x2_t;
typedef __Float16x8_t float16x8_t;
typedef __Float32x4_t float32x4_t;
typedef __Float64x2_t float64x2_t;
typedef __Poly8x16_t poly8x16_t;
typedef __Poly16x8_t poly16x8_t;
typedef __Poly64x2_t poly64x2_t;
typedef __Uint8x16_t uint8x16_t;
typedef __Uint16x8_t uint16x8_t;
typedef __Uint32x4_t uint32x4_t;
typedef __Uint64x2_t uint64x2_t;

1.3 結構化資料型別

將上述基本的資料組合成一個結構體構成結構化資料,通常被對映到一組向量暫存器中,例如:

typedef struct int8x8x2_t
{
  int8x8_t val[2];
} int8x8x2_t;

typedef struct int8x16x2_t
{
  int8x16_t val[2];
} int8x16x2_t;
...

二. 基本指令集

NEON指令按照運算元型別可以分為正常指令、寬指令、窄指令、飽和指令、長指令。

  1. 正常指令:生成大小相同且型別通常與運算元向量相同到結果向量。
  2. 長指令:對雙字向量運算元執行運算,生產四字向量到結果。所生成的元素一般是運算元元素寬度到兩倍,並屬於同一型別。L標記,如VMOVL。
  3. 寬指令:一個雙字向量運算元和一個四字向量運算元執行運算,生成四字向量結果。W標記,如VADDW。
  4. 窄指令:四字向量運算元執行運算,並生成雙字向量結果,所生成的元素一般是運算元元素寬度的一半。N標記,如VMOVN。
  5. 飽和指令:當超過資料型別指定到範圍則自動限制在該範圍內。Q標記,如VQSHRUN。

NEON指令按照作用可以分為:載入資料、儲存資料、加減乘除運算、邏輯AND/OR/XOR運算、比較大小運算等。

2.1 初始化暫存器

暫存器的每個lane(通道)都賦值為一個值N。

vcreate

將一個64bit的資料裝入vector,並返回type型別的vector。

Result_t vcreate_type(uint64_t N)

例如:

int8x8_t s8_8 = vcreate_s8(5);
int16x4_t s16_4 = vcreate_s16(0x0102030405060708);
// s8_8: 5   0   0   0   0   0   0   0
// s16_4:8   7   6   5   4   3   2   1

vdup

vdup_n_type

用型別為type的數值,初始化元素型別為type的vector,所有的元素賦值為相同的數值。

Result_t vdup_n_type(type n)

vdup_lane_type

用元素型別為type的vector的某個元素,初始化一個元素型別為type的新vector的所有元素。

Result_t vdup_lane_type(Vector_t M, int8_t n)

例如:

int8x8_t s8_8_d = vdup_n_s8(8);
// s8_8_d : 8   8   8   8   8   8   8
int8x8_t vdata={0,1,2,3,4,5,6,7};
int8x8_t s8_8_lane = vdup_lane_s8(vdata, 3);
// s8_8_lane: 3   3   3   3   3   3   3   3

vmov

vmov_n_type

功能與vdup相同,用元素為type的數值,初始化元素型別為type的vector,所有的元素賦值相同的數值.

Result_t vmov_n_type(type n)

vmovl_type

將vector中的元素bit位擴大到原來的兩倍,元素值不變。

Result_t vmovl_type(Vector_t M)

vmovn_type

建立一個新vector,新vector的元素bit位是源vector的一半,新vector只保留原vector低半部分bit資料。

Result vmovn_type(Vector_t M)

2.2 載入資料進暫存器

vld

間隔為x,載入資料進NEON暫存器

Result_t vld[x]_type(Scalar_t *N) Result_t vld[x]q_type(Scalar_t *N)

例如:

int8_t data[32] = {
    0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
    16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
{
    printf("vld[x]_type(Scalar_t *N) x=1\n");
    int8x8_t s8_1 = vld1_s8(data);
    print_neon_valude<int8x8_t>(s8_1);
    
    int8x16_t s8_2 = vld1q_s8(data);
    print_neon_valude<int8x16_t>(s8_2);
}

{
    printf("vld[x]_type(Scalar_t *N) x=2\n");
    int8x8x2_t s8_1 = vld2_s8(data);
    print_neon_valude<int8x8x2_t>(s8_1);
    
    int8x16x2_t s8_2 = vld2q_s8(data);
    print_neon_valude<int8x16x2_t>(s8_2);
}

vld_lane

間隔為x,載入資料進NEON暫存器的相關lane(通道),其他lane(通道)的資料不改變

Result_t vld[x]_lane_type(Scalar_t* N,Vector_t M,int n)
Result_t vld[x]q_lane_type(Scalar_t* N,Vector_t M,int n)

從N中載入x條資料,分別duplicate(複製)資料到暫存器0-(x-1)的所有通道

Result_t vld[x]_dup_type(Scalar_t* N)
Result_t vld[x]q_dup_type(Scalar_t* N)
  1. lane(通道):比如一個float32x4_t的NEON暫存器,它具有4個lane(通道),每個lane(通道)有一個float32的值,因此c++ float32x4_t dst = vld1q_lane_f32( float32_t* ptr, float32x4_t src, int n=2)的意思就是先將src暫存器的值複製到dst暫存器中,然後從ptr這個記憶體地址中載入第3個(lane的index從0開始)float到dst暫存器的第3個lane(通道中)。最後dst的值為:{src[0], src[1], ptr[2], src[3]}。
  2. 間隔:交叉存取,是ARM NEON特有的指令,比如c++ float32x4x3_t = vld3q_f32( float32_t* ptr),此處間隔為3,即交叉讀取12個float32進3個NEON暫存器中。3個暫存器的值分別為:{ptr[0],ptr[3],ptr[6],ptr[9]},{ptr[1],ptr[4],ptr[7],ptr[10]},{ptr[2],ptr[5],ptr[8],ptr[11]}。

2.3 儲存暫存器到記憶體

vst

間隔為x,儲存NEON暫存器的資料到記憶體中。

void vst[x]_type(Scalar_t* N, Vector M)
void vst[x]q_type(Scalar_t* N)

例如:儲存int8x8_t到記憶體中,連續儲存:

int8_t data[16] = {0};
{
    printf("vst[x]_type(Scalar_t *N) x=1\n");
    int8x8_t s8_1 = vdup_n_s8(5);
    vst1_s8(data, s8_1);
    print_neon_valude<int8x8_t>(s8_1);
    print_array<int8_t>(data, sizeof(data));
}

s8_1與data陣列資料如下:

vst[x]_type(Scalar_t *N) x=1
5   5   5   5   5   5   5   5
5   5   5   5   5   5   5   5   0   0   0   0   0   0   0   0

vst_lane

間隔為x,儲存NEON暫存器的相關lane(通道)到記憶體中

void vst[x]_lane_s8(Scalar_t *N, Vector_t M, int lane)
void vst[x]q_lane_s8(Scalar_t *N, Vector_t M, int lane)

例如:儲存第一個通道的資料到記憶體中:

printf("vst[x]_lane_type(Scalar_t *N, Scalar_t *M, int lane) x=1\n");
int8x8_t s8_2 = vdup_n_s8(6);
vst1_lane_s8(data, s8_2, 1);
print_neon_valude<int8x8_t>(s8_2);
print_array<int8_t>(data, sizeof(data));

記憶體資料如下:只儲存了一個通道的資料到記憶體中。

6   6   6   6   6   6   6   6
6   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0

2.4 讀取/修改暫存器資料

vget_lane

讀取暫存器指定n通道的資料

Result vget_lane_type(Vector_t M, int n)

讀取暫存器的高/低位到新的新的暫存器中,類似於後面的mov指令,資料寬度變化。

vget_low

vget_high

Result vget_low_type(Vector_t M)
Result vget_high_type(Vector_t M)

例項程式如下:

int8_t  data8[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int8x16_t s8_1 = vld1q_s8(data8);
int8_t int8_lane3 = vgetq_lane_s8(s8_1, 3);
int8x8_t s8_low = vget_low_s8(s8_1);
int8x8_t s8_high = vget_high_s8(s8_1);

執行結果:

===========Neon Get & Set Instructions===========
vget_lane_s8(Vector M):
int8x16_t: 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
int8x16_t 3 lanel:3
s8_low: 0   1   2   3   4   5   6   7
s8_high: 8   9  10  11  12  13  14  15

vset_lane

與vget_lane_type相反的vset_lane_type.

返回在複製M的基礎上設定通道n為N的暫存器

Result_t vset_lane_type(Scalar N,Vector_t M,int n)

例項程式如下:

int8x8_t s8_set = vset_lane_s8((int8_t)21, s8_high, 3);
s8_high: 8   9  10  11  12  13  14  15
s8_set: 8   9  10  21  12  13  14  15

2.5 資料重排

vext

從暫存器M中取出低位的n個通道的資料置於低位,再從暫存器N中取出x-n個通道的資料置於高位,組成新的一個暫存器資料:

Result_t vext[q]_type(Vector_t N,Vector_t M,int n)

例項程式碼如下:

int8_t  data8[16] = TEST_DATA;
int8x8_t s8_1 = vld1_s8(data8);
int8x8_t s8_2 = vld1_s8(&data8[8]);
int8x8_t s8_reorder = vext_s8(s8_1, s8_2, 3);

結果如下:

vdata1: 0   1   2   3   4   5   6   7
vdata2: 8   9  10  11  12  13  14  15
vdata3: 3   4   5   6   7   8   9  10

vtbl

根據索引值取一個或一組暫存器中的資料組成一個新的暫存器。如果索引值超過輸入的暫存器(組)則賦值為0.其中,M可一時暫存器組,如int8x8x2_t,對應的x也必須是2.

Result_t vtblx_type(Vectorx_t M, int8x8_t N)

例項程式碼如下:

int8x8_t vdata = {0,1,2,3,4,5,6,7};
int8x8_t index = {1,1,2,3,3,7,-1,9};
int8x8_t tbl_1 = vtbl1_s8(vdata, index);
// 則tbl_1: 1   1   2   3   3   7   0   0

x=2從兩個暫存器索引的示例如下:

int8x8x2_t vdata = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int8x8_t index = {1,1,2,3,3,7,-1,9};
int8x8_t tbl_2= vtbl2_s8(vdata , index);
// 則tbl_2: 2   2   4   6   6  14   0   3

vrev

將向量中的元素位置反轉

int8x8_t vrev16_s8(int8x8_t M)
int8x8_t vrev32_s8(int8x8_t M)
int8x8_t vrev64_s8(int8x8_t M)

例如:

int8x8_t vdata = {0,1,2,3,4,5,6,7};
int8x8_t rev_1 = vrev16_s8(vdata);
int8x8_t rev_2 = vrev32_s8(vdata);
int8x8_t rev_3 = vrev64_s8(vdata);
// rev_1: 1   0   3   2   5   4   7   6
// rev_2: 3   2   1   0   7   6   5   4
// rev_3: 7   6   5   4   3   2   1   0

vtrn

將輸入的兩個向量的元素通過轉置生成一個新由兩個向量組成的矩陣

int8x8x2_t vtrn_s8(int8x8_t M, int8x8_t N)
int16x4x2_t vtrn_s16(int16x4_t M, int16x4_t N)
int32x2x2_t vtrn_s32(int32x2_t M, int32x2_t N)

例如:

int8x8_t vdata1 = {0,1,2,3,4,5,6,7};
int8x8_t vdata2 = {8,9,10,11,12,13,14,15};
int8x8x2_t trn_1 = vtrn_s8(vdata1, vdata2);
// vdata1: 0   1   2   3   4   5   6   7
// vdata2: 8   9  10  11  12  13  14  15
// trn_1: 0  8  2  10  4  12  6  14  1  9  3  11  5  13  7  15

vzip

將兩個輸入vector的元素通過交叉生成一個有兩個vector的矩陣。

Result_t vzip_type(Vector_t M, Vector_t N)

例如:

int8x8_t vdata1 = {0,1,2,3,4,5,6,7};
int8x8_t vdata2 = {8,9,10,11,12,13,14,15};
int8x8x2_t zip_1 = vzip_s8(vdata1, vdata2);
// zip_1:0  8  1  9  2 10  3 11  4 12  5 13  6  14  7  15

vuzp

將兩個輸入vector的元素通過反交叉生成有兩個vectr的矩陣

Result_t vuzp_type(Vector_t M, Vector_t N)

例如:

int8x8_t vdata1 = {0,1,2,3,4,5,6,7};
int8x8_t vdata2 = {8,9,10,11,12,13,14,15};
int8x8x2_t zip_1 = vuzp_s8(vdata1, vdata2);
// zip_1: 0  2  4  6  8 10  12  14  1  3  5  7  9  11  13  15

vcombine

將兩個元素型別相同的vector拼接成一個同類型但大小是輸入vector兩倍的新vector,第一個輸入引數存放在新vector低部分元素。

Result_t vcombine_type(Vector_t M, Vector_t N)

vbsl

按為選擇,mask的元素為1則選擇src1中對應的位置的元素,為0則選擇src2中的元素。

Result_t vbsl_type(Vector_t Mask, Vector_M, Vector_t N)

例如

int8x8_t vdata1 = {0,1,2,3,4,5,6,7};
int8x8_t vdata2 = {8,9,10,11,12,13,14,15};
uint8x8_t vmask = {0, 3, 255, 8, 7, 127, 255, 10};
int8x8_t zbsl_1 = vbsl_s8(vmask, vdata1, vdata2);
// zbsl_1: 8   9   2   3  12   5   6   7

2.6 型別轉換

vreinterpret

類似於C/C++的指標型別強制轉換,暫存器資料和長度不發生變化,值型別改變,例如uint8x8_t p1 = vreinterpret_u8_s8(int8x8_t p0);將p0暫存器重新解釋為uint8x8_t型別。

DstType vreinterpret_DstType_SrcType(Vector_t N)

vcvt

f32, u32, s32之間的轉換,f32轉到u32時,向下取整,如果是負數則轉換成0。

DstType vcvt_DstType_SrcType(SrcType M)

float32x2_t vcvt_f32_u32(uint32x2_t __p0)
float32x2_t vcvt_f32_s32(int32x2_t __p0)
int32x2_t vcvt_s32_f32(float32x2_t __p0)
uint32x2_t vcvt_u32_f32(float32x2_t __p0)

2.7 加法減法指令

減法與減法都有類似的指令,下列加法指令中add替換為sub即為減法指令,例如vadd_s8與vsub_s8為同類型的加法與減法指令。

vadd

普通指令,普通加法運算:res=M+N

Result_t vadd_type(Vector_t M, Vector_t N) Result_t vaddq_type(Vector_t M, Vector_t N)

計算向量對應位置的資料的和,返回結果型別與輸入型別一致,可能產生溢位。

vaddl

長指令,計算結果儲存在暫存器長度x2的暫存器中

Result_t vaddl_type(Vector_t M, Vector_t N)

vaddw

寬指令,兩個暫存器寬度不相等的加法,第一個引數寬度大於第二個,例如,int16x8_t vaddw_s8(int16x8_t p0, int8x8_t p1)

Result_t vaddw_type(Vector_t M, Vector_t N)

vhadd

計算和的均值,先計算和,如果溢位則截斷,再右移一位。

Result vhadd_type(Vector_t M, Vector_t N)

vrhadd

計算和的均值,先計算和,再右移一位,如果溢位,則加1,及去四捨五入的均值。

Result vrhadd_type(Vector_t M, Vector_t N)

vqadd

飽和指令,飽和加法運算,如:int8x8_t vqadd(int8x8_t p0, int8x8_t p1),如果某個通道的資料加法的和超過127則設定為127.

Result vqadd_type(Vector_t M, Vector_t N)

vaddhn

窄指令,計算結果比引數M/N的長度小一半,相當於計算結果右移8 bit,例如int8x8_t vaddhn_s16(int16x8_t p0, int16x8_t p1)

Result vaddhn_type(Vector_t M, Vector_t N)

示例程式如下:

int8x8_t vdata1 = {0, 10, 100, 127, -10, -100, -128, -128};
int8x8_t vdata2 = {4, 9,  27,  3,   -9,  -100, -128, -1};
int16x8_t vdata3 = {0, 10, 100, 127, -10, -100, -128, -128};
int16x8_t vdata4 = {4, 9,  27,  3,   -9,  -100, -128, -1};

int8x8_t vdata_0 = vadd_s8(vdata1, vdata2);
int16x8_t vadd_1 = vaddl_s8(vdata1, vdata2);
int16x8_t vadd_3 = vaddw_s8(vdata4, vdata1);
int8x8_t vadd_4 = vhadd_s8(vdata1, vdata2);
int8x8_t vadd_5 = vrhadd_s8(vdata1, vdata2);
int8x8_t vadd_6 = vqadd_s8(vdata1, vdata2);
int8x8_t vadd_7 = vaddhn_s16(vdata3, vdata4);

輸出如下:

vdata1: 0  10  100  127  -10  -100  -128  -128
vdata2: 4   9  27   3  -9  -100  -128  -1
vadd  : 4  19  127  -126  -19  56   0  127
vaddl : 4  19  127  130  -19  -200  -256  -129
vaddw : 4  19  127  130  -19  -200  -256  -129
vhadd : 2   9  63  65  -10  -100  -128  -65
vrhadd: 2  10  64  65  -9  -100  -128  -64
vqadd : 4  19  127  127  -19  -128  -128  -128
vaddhn: 0   0   0   0  -1  -1  -1  -1

2.8 乘法指令

vmul

普通指令,res=M*N

Result_t vmul_type(Vector_t M,Vector_t N)

長指令

Result_t vmull_type(Vector_t M,Vector_t N)

vmla

乘累加運算,res=M+N*P

Result_t vmla_type(Vector_t M,Vector_t N,Vector_t P)

vmls

乘&減法運算,res=M-N*P

Result_t vmls_type(Vector_t M,Vector_t N,Vector_t P)

示例程式如下:

int8x8_t vdata1 = {0, 10, 100, 127, -10, -100, -128, -128};
int8x8_t vdata2 = {4, 9,  27,  3,   -9,  -100, -128, -1};
int8x8_t vdata3 = {0, 1, 2, 3, 4, 5, 6, 7};

int8x8_t vdata_0 = vmul_s8(vdata1, vdata2);
int16x8_t vmul_1 = vmull_s8(vdata1, vdata2);
int8x8_t vmla_0 = vmla_s8(vdata3, vdata1, vdata2);
int16x8_t vmla_1 = vmlal_s8(vmovl_s8(vdata3), vdata1, vdata2);
int8x8_t vmls_0 = vmls_s8(vdata3, vdata1, vdata2);
int16x8_t vmls_1 = vmlsl_s8(vmovl_s8(vdata3), vdata1, vdata2);

輸出如下:

vdata1: 0  10  100  127  -10  -100  -128  -128
vdata2: 4   9  27   3  -9  -100  -128  -1
vdata3: 0   1   2   3   4   5   6   7
vmul  : 0  90  -116  125  90  16   0  -128
vmull : 0  90  2700  381  90  10000  16384  128
vmla : 0  91  -114  -128  94  21   6  -121
vmlal: 0  91  2702  384  94  10005  16390  135
vmls : 0  -89  118  -122  -86  -11   6  -121
vmlsl: 0  -89  -2698  -378  -86  -9995  -16378  -121

2.9 基本數學計算指令

vabs

計算絕對值:res=abs(M)

Result_t vabs_type(Vector_t M)

vneg

計算相反數:res=-M.先計算數值的相反數,再按值取型別,例如vneg_s8(),當出現-128時,取反得128,對應的8bit資料為10000000b,解析為int8_t為-128,所以-128取反還是-128.

Result_t vneg_type(Vector_t M)

vmax

計算最大值:res=max(M, N)

Result_t vmax_type(Vector_t M)

vmin

計算最小值:res=min(M, N)

Result_t vmin_type(Vector_t M)

vrnd

取整,取整有不同的演算法型別, ftype可以是f16, f32, f64

vrndn_ftype: to nearest, tiles to even
vrnda_ftype: to nearest, ties away from zero
vrndp_ftype: towards +Inf
vrndm_ftype: towards -Inf
vrnd_ftype: towards 0

vrecp

求近似倒數,type只能是f32,f64或u32

vrecpe

Vector_t vrecpe_type(Vector_t M)

vrecps

牛頓-拉夫遜迭代演算法求倒數

Vector_t vrecps_type(Vector_t M)

vrsqrt

平方根倒數:

與vrecp同樣,type只能是f32,f64和u32,輸入不能是負數,否則計算出來是nan。

Vector_t vrsqrte_type(Vector_t M) Vector_t vrsqrts_type(Vector_t M)

2.10 比較指令

第一個向量與第二個向量進行比較,如果滿足條件則返回bit位全位1的值,否則返回0。例如uint32x2_t vres = vceq_s32({1,2}, {1,3}), vres={0xffffffff,0x00000000}.

vceq

比較是否相等:res = mask(M==N)

Result_t vceq_type(Vector_t M, Vector_t N)

vcge

比較是否大於等於:res = mask(M>=N)

Result_t vceg_type(Vector_t M, Vector_t N)

vcgt

比較是否大於:res = mask(M>N)

Result_t vcgt_type(Vector_t M, Vector_t N)

vcle

比較是否小於等於:res = mask(M<=N)

Result_t vcle_type(Vector_t M, Vector_t N)

vclt

比較是否小於:res = mask(M<N)

Result_t vclt_type(Vector_t M, Vector_t N)

示例程式如下:

int8x8_t vdata1 = {0, 10, 20, 127, -10, -100, -128, -128};
int8x8_t vdata2 = {4, 9,  20,  3,   -9,  -100, -128, -1};

uint8x8_t vdata_0 = vceq_s8(vdata1, vdata2);
uint8x8_t vdata_1 = vcge_s8(vdata1, vdata2);
uint8x8_t vdata_2 = vcgt_s8(vdata1, vdata2);
uint8x8_t vdata_3 = vcle_s8(vdata1, vdata2);
uint8x8_t vdata_4 = vclt_s8(vdata1, vdata2);

輸出如下:

vdata1: 0  10  20  127  -10  -100  -128  -128
vdata2: 4   9  20   3  -9  -100  -128  -1
vceq  : 0   0  255   0   0  255  255   0
vcge  : 0  255  255  255   0  255  255   0
vcgt  : 0  255   0  255   0   0   0   0
vcle  :255   0  255   0  255  255  255  255
vclt  :255   0   0   0  255   0   0  255

因為向量的基本資料型別為int8_t,比較輸出mask型別為uint8_t,當滿足比較條件時輸出0xff,也就是255。

2.11 歸約指令

向量內部相鄰的元素執行同一個運算,最後組成一個向量。

vpadd

[普通指令]歸約加法,M,N內部相鄰元素對應相加,最後計算和,溢位則截斷

Result_t vpadd_type(Vector_t M,Vector_t N)

[長指令]歸約加法,最後結果為輸入向量寬度的兩倍

Result_t vpaddl_type(Vector_t M,Vector_t N)

vpmax

[普通指令]歸約求最大值,M,N各自內部相鄰元素求最大值,最後組成一個新的向量

Result_t vpmax_type(Vector_t M,Vector_t N)

vpmin

[普通指令]歸約求最小值,M,N各自內部相鄰元素求最小值,最後組成一個新的向量

Result_t vpmin_type(Vector_t M,Vector_t N)

2.12 位操作指令

向量中的資料按位元位進行計算。該類指令會將符號位也做位操作。

vmvn

按位取反

Result_t vmvn_type(Vector_t M)

vand

按位與,符號位除外

Result_t vand_type(Vector_t M,Vector_t N)

vorr

按位或

Result_t vorr_type(Vector_t M, Vector_t N)

veor

按位異或

Result_t veor_type(Vector_t M, Vector_t N)

vcls

計算連續相同的位數,0或1連續個數最多的,例如11000011b,4個連續的0,2個連續的1,返回4.

Result_t vcls_type(Vector_t M)

vshl

左移位, 無論M是有符號還是無符號數,R為有符號陣列成的向量型別,為正數時像左移位,為負數時像右移位。左移符號位固定不變,右邊補零;右移符號位固定不變,左邊移出的bit位補符號位的bit。例如,-3右移1位,11111101b->11111110b,變為-2.

Result_t vshl_type(Vector_t M, Vector_t R)

vrshl

移位並取四捨五入

Result_t vshl_type(Vector_t M, Vector_t R)

示例程式如下:

int8x8_t vdata1 = {0, 1, 3, 7, 127, -1, -3, -128};
int8x8_t vdata2 = {0, 2, 3, 8, -128, -2, -3, 127};

int8x8_t vdata_0 = vmvn_s8(vdata1);
int8x8_t vdata_1 = vand_s8(vdata1, vdata2);
int8x8_t vdata_2 = vorr_s8(vdata1, vdata2);
int8x8_t vdata_3 = veor_s8(vdata1, vdata2);
int8x8_t vdata_4 = vcls_s8(vdata1);

int8x8_t shift_value_l = {1,1,1,1,1,1,1,1};
int8x8_t shift_value_r = {-1,-1,-1,-1,-1,-1,-1,-1};
int8x8_t vdata_5 = vshl_s8(vdata1, shift_value_l);
int8x8_t vdata_6 = vshl_s8(vdata1, shift_value_r);
int8x8_t vdata_7 = vrshl_s8(vdata1, shift_value_r);

執行的各暫存器數值結果如下:

vdata1: 0   1   3   7  127  -1  -3  -128
vdata2: 0   2   3   8  -128  -2  -3  127
vmvn  :-1  -2  -4  -8  -128   0   2  127
vand  : 0   0   3   0   0  -2  -3   0
vorr  : 0   3   3  15  -1  -1  -3  -1
veor  : 0   3   0  15  -1   1   0  -1
vcls  : 7   6   5   4   0   7   5   0
vshl  : 0   2   6  14  -2  -2  -6   0
vshr  : 0   0   1   3  63  -1  -2  -64
vrshl: 0   1   2   4  64   0  -1  -64

參考資料

[1] http://Coding for Neon - Part 1: Load and Stores