ARM NEON程式設計2.NEON基本資料型別與基本指令集
目錄
ARMv7架構的包含如下暫存器:
- 16個通用暫存器(32bit),R0~R15;
- 16個NEON暫存器(128bit),Q0~Q15,同時也可以被視為32個64bit的暫存器,D0~D31;
- 16個VFP暫存器(32bit),S0~S15.
NEON與VFP都能加速浮點計算,區別在於VFP不具備資料並行能力,在雙精度浮點數的計算效能上更好,而NEON則更優於多資料的平行計算。
一. 基本資料型別
(type)x(lanes)_t
例如,int16x4_t就是一個包含4條向量線的向量資料,每條向量線是一個有符號16位整型數。
1.1 64bit資料型別
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資料型別
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指令按照運算元型別可以分為正常指令、寬指令、窄指令、飽和指令、長指令。
- 正常指令:生成大小相同且型別通常與運算元向量相同到結果向量。
- 長指令:對雙字向量運算元執行運算,生產四字向量到結果。所生成的元素一般是運算元元素寬度到兩倍,並屬於同一型別。L標記,如VMOVL。
- 寬指令:一個雙字向量運算元和一個四字向量運算元執行運算,生成四字向量結果。W標記,如VADDW。
- 窄指令:四字向量運算元執行運算,並生成雙字向量結果,所生成的元素一般是運算元元素寬度的一半。N標記,如VMOVN。
- 飽和指令:當超過資料型別指定到範圍則自動限制在該範圍內。Q標記,如VQSHRUN。
NEON指令按照作用可以分為:載入資料、儲存資料、加減乘除運算、邏輯AND/OR/XOR運算、比較大小運算等。
2.1 初始化暫存器
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
用型別為type的數值,初始化元素型別為type的vector,所有的元素賦值為相同的數值。
Result_t vdup_n_type(type n)
用元素型別為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
功能與vdup相同,用元素為type的數值,初始化元素型別為type的vector,所有的元素賦值相同的數值.
Result_t vmov_n_type(type n)
將vector中的元素bit位擴大到原來的兩倍,元素值不變。
Result_t vmovl_type(Vector_t M)
建立一個新vector,新vector的元素bit位是源vector的一半,新vector只保留原vector低半部分bit資料。
Result vmovn_type(Vector_t M)
2.2 載入資料進暫存器
vld
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)
- 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]}。
- 間隔:交叉存取,是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
void vst[x]_type(Scalar_t* N, Vector M)
void vst[x]q_type(Scalar_t* N)
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));
}
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
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.
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
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
Result_t vadd_type(Vector_t M, Vector_t N) Result_t vaddq_type(Vector_t M, Vector_t N)
計算向量對應位置的資料的和,返回結果型別與輸入型別一致,可能產生溢位。
vaddl
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
Result_t vmul_type(Vector_t M,Vector_t N)
Result_t vmull_type(Vector_t M,Vector_t N)
vmla
Result_t vmla_type(Vector_t M,Vector_t N,Vector_t P)
vmls
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
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
Result_t vmax_type(Vector_t M)
vmin
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
Vector_t vrecpe_type(Vector_t M)
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
Result_t vceq_type(Vector_t M, Vector_t N)
vcge
Result_t vceg_type(Vector_t M, Vector_t N)
vcgt
Result_t vcgt_type(Vector_t M, Vector_t N)
vcle
Result_t vcle_type(Vector_t M, Vector_t N)
vclt
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