MyFPGA Forum

 找回密码
 注册
搜索
查看: 3249|回复: 1
打印 上一主题 下一主题

[ARM开发] 【Altera SOC体验之旅 】Soc FPGA之DSP进阶---NEON高性能...

[复制链接]
跳转到指定楼层
1#
发表于 2015-7-28 09:13:57 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 BOB_Sun 于 2015-7-28 15:33 编辑

[ARM开发] 【Altera SOC体验之旅 】Soc FPGA之DSP进阶---NEON高性能媒体引擎(二)

本文转自 http://bbs.eeworld.com.cn/thread-462935-1-1.html

Neon函数在头文件arm_neon.h中已经被定义,头文件既定义内嵌函数,也定义一组向量类型,编译器将NEON内嵌函数视为普通函数调用。
  





基本的DSP处理模块




例程1 实现从0-99的加法运算作为使用NEON函数的基本DSP处理模块。

----------------------------------------------------------------------------------------------

#include<stdio.h>

#include<arm_neon.h>

void fill_array(int16_t *array,int size)

{

     int i;

     for(i=0;i<size;i++)

         {

           array=i;

         }

}



int sum_array(int16_t *array,int size)

{

     int16_t *i;

     int16x4_t acc=vdup_n_s16(0);

     int32x2_t acc1;

     int64x1_t acc2;

               for(i=array;i<array+size;i+=4)

                      {

                         int16x4_t vec;

                          vec=vld1_s16(i);

                          acc=vadd_s16(acc,vec);

                       }

                        acc1=vpaddl_s16(acc);

                        acc2=vpaddl_s32(acc1);

                       return (int)vget_lane_s64(acc2,0);

}

int main()

{

      int16_t my_array[100];

      fill_array(my_array,100);

      printf("sum was %d/n",sum_array(my_array,100));

      return 0;

}



例程2 浮点向量运算,进行简单的浮点向量计算。VFP除了对浮点数基本加减乘除,开方,比较,取反提供支持之外,其向量功能能够使它同时支持最多8组单精度、4组双精度浮点运算。

------------------------------------------------------------------------------------------------------


#include<stdio.h>

#include<arm_neon.h>




int main()

{

      float a[4]={2,4,8,16};

      float b[4]={2,4,8,16};

      float c[4]={0,0,0,0};   



      float32x4_t sample1,sample2,result;



      sample1=vldlq_f32(a);

      sample2=vldlq_f32(b);




      result=vmulq_f32(sample1,sample2);

      

      vst1q_f32(c,result);

      printf("Result:%f,%f,%f,%f",c[0],c[1],c[2],c[3]);

      return 0;

}




例程3 矩阵运算,用NEON优化矩阵乘法的实例,介绍如何用NEON有效地处理一个4X4的矩阵乘法运算,经常用于3D图形,我们认为这些矩阵在内存中是以列为主排列的。假设每列元素在NEON寄存器中表示为一个向量,那么矩阵乘法就是向量乘以标量的运算,而后续的累加也同样可以用向量乘以标量的累加指令实现。

--------------------------------------------------------------------------------------------------------------


#include<stdio.h>

#include<arm_neon.h>




int main()

{

          int i;

          uint16x4_t vectorT1,vectorT2,vectorT3,vectorT4;

          uint16x4_t A[4][4]={ 1,2,3,4,

                                            5,6,7,8,

                                            9,10,11,12,

                                            13,14,15,16};




          uint16x4_t B[4][4]={16,15,14,13,

                                           12,11,10,9,

                                           8,7,6,5,

                                           4,3,2,1};


          uint16_t C[4][4];



          uint16x4_t vectorB1,vectorB2,vectorB3,vectorB4;


          vectorB1=vld1_u16(b[0]);
          vectorB2=vld1_u16(b[1]);搜索
复制




          vectorB3=vld1_u16(b[2]);

          vectorB4=vld1_u16(b[3]);




                for(i=0;i<4;i++)

               {

                         vectorT1=vmul_n_u16( vectorB1,A[0]);

                         vectorT2=vmul_n_u16( vectorB2,A[1]);

                 vectorT3=vmul_n_u16( vectorB3,A[2]);

                         vectorT4=vmul_n_u16( vectorB4,A[3]);




                         vectorT1=vadd_u16( vectorT1, vectorT2);

                         vectorT1=vadd_u16( vectorT1, vectorT3);

                         vectorT1=vadd_u16( vectorT1, vectorT4);



                         vst1_u16(C,vectorT1);

                         printf("C[%d][0]=%ld\n",i,C[0]);

                         printf("C[%d][0]=%ld\n",i,C[1]);

                         printf("C[%d][0]=%ld\n",i,C[2]);

                         printf("C[%d][0]=%ld\n",i,C[3]);



               }

               return 0;

}


附录: ARM和NEON指令

在移动平台上进行一些复杂算法的开发,一般需要用到指令集来进行加速。目前在移动上使用最多的是ARM芯片。
ARM是微处理器行业的一家知名企业,其芯片结构有:armv5、armv6、armv7和armv8系列。芯片类型有:arm7、arm9、arm11、cortex系列。指令集有:armv5、armv6和neon指令。关于ARM到知识参考:http://baike.baidu.com/view/11200.htm
最初的ARM指令集为通用计算型指令集,指令集都是针对单个数据进行计算,没有并行计算到功能。随着版本的更新,后面逐渐加入了一些复杂到指令以及并行计算到指令。而NEON指令是专门针对大规模到并行运算而设计的。
NEON 技术可加速多媒体和信号处理算法(如视频编码/解码、2D/3D 图形、游戏、音频和语音处理、图像处理技术、电话和声音合成),其性能至少为ARMv5 性能的3倍,为 ARMv6 SIMD性能的2倍。
关于SIMD和SISD:Single Instruction Multiple Data,单指令多数据流。反之SISD是单指令单数据。以加法指令为例,单指令单数据(SISD)的CPU对加法指令译码后,执行部件先访问内存,取得第一个操作数;之后再一次访问内存,取得第二个操作数;随后才能进行求和运算。而在SIMD型的CPU中,指令译码后几个执行部件同时访问内存,一次性获得所有操作数进行运算。这个特点使SIMD特别适合于多媒体应用等数据密集型运算。如下图所示:
   

如何才能快速到写出高效的指令代码?这就需要对各个指令比较熟悉,知道各个指令的使用规范和使用场合。
ARM指令有16个32位通用寄存器,为r0-r15,其中r13为堆栈指针寄存器,r15为指令计算寄存器。实际可以使用的寄存器只有14个。r0-r3一般作为函数参数使用,函数返回值放在r0中。若函数参数超过4个,超过到参数压入堆栈。
有效立即数的概念:每个立即数采用一个8位的常数(bit[7:0])循环右移偶数位而间接得到,其中循环右移的位数由一个4位二进制(bit[11:8] )的两倍表示。如果立即数记作<immediate> , 8位常数记作immed_8 , 4位的循环右移值记作rotate_imm ,有效的立即数是由一个8位的立即数循环右移偶数位得到,可以表示成:
<immediate>=immed_8循环右移( 2×rotate_imm)
如:mov r4 , #0x8000 000A    #0x8000 000A 由0xA8循环右移0x2位得到。
下面介绍一些比较常用到一些指令。

内存访问指令:
LDR和STR,有三种方式,比较容易搞混
LDR r0, [r1, #4]   r0 := mem[r1+4]   ,#4是直接偏移量,这时候只能在正负4Kb到范围内。也可以是寄存器偏移,用+/-表示。记住r1不进行偏移。
LDR r0, [r1, #4]!  r0 :=mem[r1+4],r1 := r1 + 4,取值是取偏移量到值,并且r1进行偏移。
LDR r0, [r1], #4   r0 :=mem[r1] ,r1 := r1 +4,取值是取r1地方到值,取值后进行偏移。运算后自动加4,后变址。
另外:LDRB是无符号字节,SB是有符号字节,H无符号半字,SH有符号半字。
存储器和寄存器数据交换:SWP,SWPB
如SWP r0, r1, [r2]   r0 := mem[r2],mem[r2] := r1
多寄存器数据传输:
LDMIA r1, {r0,r2,r5}  r0 = mem[r1], r2 = mem[r1+4], r5=mem[r1+8]
通用数据处理指令
第二操作数,常用到有LSR,LSL等,如mov r1, r2, lsl #2 将r2左移2位然后赋值到r1中。
常用到操作有ADD、SUB、AND、ORR、EOR、BIC、ORN,如果加上了S则会更新条件标记。
MOV移动,MVN取反移动。MOV可以是R寄存器,立即数以及接第二操作数。
REV:在字或半字内反转字节或位到顺序
MUL、MLA和MLS,乘法、乘加和乘减。MLA R1,R2,R3,R4表示R1=R2*R3+R4,还有有符号和无符号乘法等。
跳转指令
B:无条件跳转,BL:带链接到跳转,BX跳转并交换指令集等。

重点介绍一下NEON指令,目前使用较多。而且使用难度也较大,很多文档上都没有比较详细到介绍,也没有给出相应到例子或者图示。

(未完,跟帖中)
2#
 楼主| 发表于 2015-7-28 09:27:07 | 只看该作者
本帖最后由 BOB_Sun 于 2015-7-28 09:32 编辑

一、NEON基本知识
NEON的寄存器:
有16个128位四字到寄存器Q0-Q15,32个64位双子寄存器D0-D31,两个寄存器是重叠的,在使用到时候需要特别注意,不小心就会覆盖掉。如下图所示:
两个寄存器的关系:Qn =D2n和D2n+1,如Q8是d16和d17的组合。

NEON的数据类型:
注意数据类型针对到时操作数,而不是目标数,这点在写的时候要特别注意,很容易搞错,尤其是对那些长指令宽指令的时候,因为经常Q和D一起操作。

NEON中的正常指令、宽指令、窄指令、饱和指令、长指令
正常指令:生成大小相同且类型通常与操作数向量相同到结果向量
长指令:对双字向量操作数执行运算,生产四字向量到结果。所生成的元素一般是操作数元素宽度到两倍,并属于同一类型。L标记,如VMOVL。
宽指令:一个双字向量操作数和一个四字向量操作数执行运算,生成四字向量结果。W标记,如VADDW。
窄指令:四字向量操作数执行运算,并生成双字向量结果,所生成的元素一般是操作数元素宽度的一半。N标记,如VMOVN。
饱和指令:当超过数据类型指定到范围则自动限制在该范围内。Q标记,如VQSHRUN
二、NEON指令
NEON指令较多,下面主要介绍一些常见的指令用法。
复制指令:
VMOV:
两个arm寄存器和d之间
vmov d0, r0, r1:将r1的内容送到d0到低半部分,r0的内容送到d0到高半部分
vmov r0, r1, d0:将d0的低半部分送到r0,d0的高半部分内容送到r1
一个arm寄存器和d之间
vmov.U32 d0[0], r0:将r0的内容送到d0[0]中,d0[0]指d0到低32位
vmov.U32 r0, d0[0]:将d0[0]的内容送到r0中
立即数:
vmov.U16 d0, #1:将立即数1赋值给d0的每个16位
vmov.U32 q0, #1:将立即数1赋值给q0的每个32位
长指令:VMOVL:d赋值给q
vmovl.U16 q0, d0:将d0的每个16位数据赋值到q0的每个32位数据中
窄指令:VMOVN:q赋值给d
vmovn.I32 d0, q0:将q0的每32位数据赋值到q0的每16位数据中
饱和指令:VQMOVN等,饱和到指定的数据类型
vqmovun.S32 d0, q0:将q0到每个32位移动到d0中到每个16位中,范围是0-65535
        
VDUP:
VDUP.8 d0, r0:将r0复制到d0中,8位
VDUP.16 q0, r0:将r0复制到q0中,16位
VDUP.32 q0, d2[0]:将d2的一半复制到q0中
VDUP.32 d0, d2[1]:将d2的一半复制到d0中
注意是vdup可以将r寄存器中的内容复制到整个neon寄存器中,不能将立即数进行vdup,立即数只能用vmov
逻辑运算:
VADD:按位与;VBIC:位清除;VEOR:按位异或;VORN:按位或非;VORR:按位或
移位指令:
VSHL:左移、VSHLL:左移扩展、VQSHL:左移饱和、VQSHLU:无符号左移饱和扩展
VSHR:右移、VSHRN:右移窄、VRSHR:右移舍入、VQSHRUN:无符号右移饱和舍入
通用算术指令:
VABA:绝对值累加、VABD:绝对值相加、VABS:绝对值、VNEG:求反、VADD、VADDW、VADDL、VSUB、VSUBL、VSUBW:加减
VPADD:将两个向量的相邻元素相加
如VPADD.I16 {d2}, d0, d1

VPADDL:VPADDL.S16 d0, d1

VMAX:最大值,VMIN:最小值
VMUL、VMULL、VMLA(乘加)、VMLS(乘减)、
加载存储指令:
VLD和VST


交叉存取的示意图:

VREV反转元素指令:

VEXT移位指令:

VTRN转置指令:可以用于矩阵的转置


VZIP指令:压缩,类似交叉存取

VUZP指令:解压操作,类似交叉存取

VTBL查表指令:从d0,d1中查找d3中的索引值,如果找到则取出,没有找到则为0,存入d2中

三、需要注意的地方
    load数据的时候,第一次load会把数据放在cache里面,只要不超过cache的大小,下一次load同样数据的时候,则会比第一次load要快很多,会直接从cache中load数据,这样在汇编程序设计的时候是非常需要考虑的问题。
     如:求取一个图像的均值,8*8的窗口,先行求和,然后列求和出来均值,这时候会有两个函数,数据会加载两遍,如果按照这样去优化的话则优化不了多少。如果换成上面这种思路,先做行16行,然后再做列,这样数据都在cache里面,做列的时候load数据会很快。
   在做neon乘法指令的时候会有大约2个clock的阻塞时间,如果你要立即使用乘法的结果,则就会阻塞在这里,在写neon指令的时候需要特别注意。乘法的结果不能立即使用,可以将一些其他的操作插入到乘法后面而不会有时间的消耗。
如:vmul.u16 q1, d3, d4
         vadd.u32 q1, q2, q3
此时直接使用乘法的结果q1则会阻塞,执行vadd需要再等待2个clock的时间
使用饱和指令的时候,如乘法饱和的时候,在做乘法后会再去做一次饱和,所以时间要比直接做乘法要慢。
如:  vmul.u16 q1, d3, d4
          vqmul.u32 q1, q2, q3
后一个的时间要比第一个的时间要久。
在对16位数据进行load或者store操作的时候,需要注意的是字节移位。比如是16位数据,则load 8个16位数据,如果指定寄存器进行偏移,此时需要特别注意。
例如:vld1.64 {d0}, [r0], r1
希望能大家带来帮助


(完)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|MyFPGA

GMT+8, 2024-4-28 13:21 , Processed in 0.046656 second(s), 17 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表