ARM VFP的一点体会

您所在的位置:网站首页 vfp的str ARM VFP的一点体会

ARM VFP的一点体会

2024-07-11 03:02| 来源: 网络整理| 查看: 265

 关键字: VFP arm1136JF-SMCIMX31 gcc linux

参考文献:、、 前言: MCIMX31是一款基于ARM1136JF-S的多媒体处理器。他适合用来做智能手机,手持式游戏终端,多媒体播放器等智能手持式设备。有关这款CPU的参数特性可以参考Freescale的DataSheet。 最近使用MCIMX31的VFP有些收获,写出来与大家分享。 调试环境: 成都莱得科技有限公司([url=http://www.nidetech.com/]http://www.nidetech.com[/url])的I.MX31开发板。 1. VFP的功能特点 在我看来VFP除了提供浮点数基本运算(加、减、乘、除、开方、比较、取反)提供支持之外,最有特点是它向量(vectors)功能。它同时支持最多8组单精度4组双精度浮点数的运算。有关这部分的叙述请参考 ChapterC5 VFP Addressing Modes。下面看一个程序实例,程序是用arm-none-linux-gnueabi-gcc 4.1.2编译,运行在MCIMX31 Linux2.6.24.5平台下。 #include #include voidvfp_regs_load(float arrays[32]) {    asm volatile("fldmias %0, {s0-s31}\n"            :            :"r"(arrays)); } voidvfp_regs_save(float arrays[32]) {    asm volatile ("fstmias %0, {s0-s31}"            :            :"r"(arrays)); } voidprint_array(float array[32]) {    int i;    for(i=0; iScalarD 5.0000002.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000  9.00000010.000000 11.000000 12.000000 13.000000 14.000000 15.000000 16.000000  17.00000018.000000 19.000000 20.000000 21.000000 22.000000 23.000000 24.000000  25.00000026.000000 27.000000 28.000000 29.000000 30.000000 31.000000 32.000000  2:VectorA[?]op ScalarB->VectorD[?] 1.0000002.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000  26.00000010.000000 28.000000 12.000000 30.000000 14.000000 32.000000 16.000000  17.00000018.000000 19.000000 20.000000 21.000000 22.000000 23.000000 24.000000  25.00000026.000000 27.000000 28.000000 29.000000 30.000000 31.000000 32.000000  3:VectorA[?]op VectorB[?]->VectorD[?] 1.0000002.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000  42.00000010.000000 46.000000 12.000000 50.000000 14.000000 54.000000 16.000000  17.00000018.000000 19.000000 20.000000 21.000000 22.000000 23.000000 24.000000  25.00000026.000000 27.000000 28.000000 29.000000 30.000000 31.000000 32.000000  第一种情况是最简单的两个浮点数相加(fadds s0, s1, s2) 我们看到的结果是s0(5.00)=s1(2.00)+s2(3.00)。 第二种情况是一组Vector和一个Scalar相加(faddss8,  s24, s0),我们看到的结果就是: S8(26.00)=S24(25.00)+S0(1.00) S10(28.00)=S26(27.00)+S0(1.00) S12(30.00)=S28(29.00)+S0(1.00) S14(32.00)=S30(31.00)+S0(1.00) 第三种情况是两组Vectors相加(fadds s8,  s16, s24),我们看到的结果就是: S8(42.00)=S24(25.00)+S16(17.00) S10(46.00)=S26(27.00)+S18(19.00) S12(50.00)=S28(29.00)+S20(21.00) S14(54.00)=S30(31.00)+S22(23.00) 至于为什么是有4组结果,并且相邻结果间隔一个。有兴趣的可以参考有关FPSCR的叙述。 2. 硬件支持 ARM1136JF-S通过两个协处理器CP10和CP11来实现VFP。其中CP10支持单精度浮点操作,CP11支持双精度浮点操作。所以所有的VFP指令其实就是一些协处理器的指令比如FADDS其实就是一个CDP指令,一个FLDS就是一个LDC指令。理论上讲只要采用了ARM1136JF-S的CPU就应该能够支持VFP。 3. 编译器对VFP的支持 一个浮点数操作最后是翻译成VFP指令,还是翻译成fpa,或者是softfloat是编译器决定的。实例: [sjl@sjlvfp]$ cat f.c intmain() {    float f1=1.2,f2=1.3;    f1 = f2*f1; } [sjl@sjlvfp]$ arm-linux-gcc -v .... gccversion 3.4.4 [sjl@sjlvfp]$ arm-linux-gcc -c f.c [sjl@sjlvfp]$ arm-linux-objdump -d f.o f.o:    file format elf32-littlearm Disassemblyof section .text: 00000000:   0:   e1a0c00d        mov    ip, sp   4:   e92dd800        stmdb  sp!, {fp, ip, lr, pc}   8:   e24cb004        sub    fp, ip, #4      ; 0x4   c:   e24dd008        sub    sp, sp, #8      ; 0x8   10:  e59f3024        ldr    r3, [pc, #36]   ; 3c   14:  e50b3010        str    r3, [fp, #-16]   18:  e59f3020        ldr    r3, [pc, #32]   ; 40   1c:  e50b3014        str    r3, [fp, #-20]   20:  ed1b1104        ldfs    f1, [fp,#-16]   24:  ed1b0105        ldfs    f0, [fp,#-20]   28:  ee910100        fmls    f0, f1, f0   2c:  ed0b0104        stfs    f0, [fp,#-16]   30:  e1a00003        mov    r0, r3   34:  e24bd00c        sub    sp, fp, #12     ; 0xc   38:  e89da800        ldmia   sp, {fp,sp, pc}   3c:  3f99999a        swicc   0x0099999a   40:  3fa66666        swicc   0x00a66666 我们用arm-linux-gcc 3.4.4编译明显,生成的不是VFP指令。 [sjl@sjlvfp]$ arm-none-linux-gnueabi-gcc -v .... gccversion 4.1.2 [sjl@sjlvfp]$ arm-none-linux-gnueabi-gcc -c f.c [sjl@sjlvfp]$ arm-none-linux-gnueabi-objdump -d f.o f.o:    file format elf32-littlearm Disassemblyof section .text: 00000000:   0:   e1a0c00d        mov    ip, sp   4:   e92dd800        stmdb  sp!, {fp, ip, lr, pc}   8:   e24cb004        sub    fp, ip, #4      ; 0x4   c:   e24dd008        sub    sp, sp, #8      ; 0x8   10:  e59f3024        ldr    r3, [pc, #36]   ; 3c   14:  e50b3014        str    r3, [fp, #-20]   18:  e59f3020        ldr    r3, [pc, #32]   ; 40   1c:  e50b3010        str    r3, [fp, #-16]   20:  e51b0014        ldr    r0, [fp, #-20]   24:  e51b1010        ldr    r1, [fp, #-16]   28:  ebfffffe        bl     0   2c:  e1a03000        mov    r3, r0   30:  e50b3014        str    r3, [fp, #-20]   34:  e24bd00c        sub    sp, fp, #12     ; 0xc   38:  e89da800        ldmia   sp, {fp,sp, pc}   3c:  3f99999a        svccc   0x0099999a   40:  3fa66666        svccc   0x00a66666 我们用arm-none-linux-gnueabi-gcc4.1.2 默认也不是生成VFP指令。 [sjl@sjlvfp]$ arm-none-linux-gnueabi-gcc -mfpu=vfp -mfloat-abi=softfp -c f.c [sjl@sjlvfp]$ arm-none-linux-gnueabi-objdump -d f.o f.o:    file format elf32-littlearm Disassemblyof section .text: 00000000:   0:   e1a0c00d        mov    ip, sp   4:   e92dd800        stmdb  sp!, {fp, ip, lr, pc}   8:   e24cb004        sub    fp, ip, #4      ; 0x4   c:   e24dd008        sub    sp, sp, #8      ; 0x8   10:  e59f3020        ldr    r3, [pc, #32]   ; 38   14:  e50b3014        str    r3, [fp, #-20]   18:  e59f301c        ldr    r3, [pc, #28]   ; 3c   1c:  e50b3010        str    r3, [fp, #-16]   20:  ed1b7a05        flds    s14, [fp,#-20]   24:  ed5b7a04        flds    s15, [fp,#-16]   28:  ee677a27        fmuls   s15, s14,s15   2c:  ed4b7a05        fsts    s15, [fp,#-20]   30:  e24bd00c        sub    sp, fp, #12     ; 0xc   34:  e89da800        ldmia   sp, {fp,sp, pc}   38:  3f99999a        svccc   0x0099999a   3c:  3fa66666        svccc   0x00a66666 用arm-none-linux-gnueabi-gcc4.1.2指定-mfpu=vfp -mfloat-abi=softfp参数之后生成VFP指令。 好像是从GCC 4之后才支持VFP,如果你要在原有GCC 3里面使用VFP,如何解决,大家可以一起思考这个问题。 4. 操作系统对VFP的支持 应用程序要使用VFP指令,还需要操作系统配合。 在ARM1136JF-S里面有几个重要的协处理器与VFP有关。 CP15c1 协处理器访问控制寄存器,这个寄存器规定了用户模式和特权对协处理器的访问权限。我们要使用VFP当然要运行用户模式访问CP10和CP11。 另外一个寄存器是VFP的FPEXC Bit30这是VFP功能的使用位。 其实操作系统在做了这两件事情之后,用户程序就可以使用VFP了。 例子: 编译内核取消VFP的支持,编写一个内核驱动,加入以下代码: voidenable_vfp(void) {    int ret = 0;    unsigned int value;    asm  volatile ("mrc p15, 0, %0, c1, c0, 2"            :"=r"(value)            :);    value |= 0xf00000;/*enable CP10, CP11 user access*/    asm volatile("mcr p15, 0, %0, c1, c0, 2"            :            :"r"(value));    asm volatile("fmrx %0, fpexc"              :"=r"(value));    value |=(1switch_to()->__switch_to() __switch_to()在Arch/arm/kernel/entry-armv.S中实现,这段代码不长,这里我比较关心的是 ... movr5, r0 addr4, r2, #TI_CPU_SAVE ldrr0, =thread_notify_head movr1, #THREAD_NOTIFY_SWITCH blatomic_notifier_call_chain movr0, r5 ... 翻译成C就是atomic_notifier_call_chain(thread_notify_head,THREAD_NOTIFY_SWITCH,next->cpu_context); 到我们的VFP代码中去看, staticstruct notifier_block vfp_notifier_block = { .notifier_call= vfp_notifier, }; vfp_init() { ... thread_register_notifier(&vfp_notifier_block); ... } 很明显进程在切换的时候会执行到vfp_notifier()中去。仔细研究vfp_notifier()的代码,没有发现保存VFP寄存器的代码。倒是这段代码比较可疑: ... fmxr(FPEXC,fpexc & ~FPEXC_EN); ... FPEXC的FPEXC_EN是VFP功能的使能位,如果这位未被设置,CPU在执行到VFP指令时会产生“未定义指令”中断。好了,每次一个新进程被切换到CPU的时候FPEXC_EN总是未被设置,此时一个VFP指令就产生了一个“未定义指令”中断。 好了。程序这下跑到__und_usr()->call_fpe()->do_vfp(),这个过程其实还是很复杂的,有兴趣可以仔细阅读代码。 do_vfp: ...   ldrr4, .LCvfp ldrr11, [r10, #TI_CPU] @ CPU number addr10, r10, #TI_VFPSTATE @ r10 = workspace ldrpc, [r4]  @ call VFP entry point ... .LCvfp: .wordvfp_vector 好吗跑到vfp_vector,vfp_vector这个函数指针在vfp_init()里面赋值 vfp_vector =vfp_support_entry; 关键就在vfp_support_entry里面。vfp_support_entry不仅负责保存/恢复进程的VFP寄存器值,还负责处理VFP的异常(例如除0等等)。 具体的实现过程有兴趣的可以慢慢的看了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3