ARM 浮点运算
作者:程老师,华清远见嵌入式学院讲师。
很多时候我们要处理的数据,不仅仅是整数和字符串,还有浮点数即小数。在多媒体数据处理方面表现的更多。是不是所有的CPU都支持,浮点运算呢?答案:不是。
我们常常听到赢浮点和软浮点,这些到底说的是什么呢?下面我们就来一探究竟吧。在这里我们说的是ARM核浮点运算。
(1)硬浮点(hard-float)
编译器将代码直接编译成硬件浮点协处理器(浮点运算单元FPU)能识别的指令,这些指令在执行的时候ARM核直接把它转给协处理器执行。FPU 通常有一套额外的寄存器来完成浮点参数传递和运算。使用实际的硬件浮点运算单元(FPU)会带来性能的提升。
(2)软浮点(soft-float)
编译器把浮点运算转成浮点运算的函数调用和库函数调用,没有FPU的指令调用,也没有浮点寄存器的参数传递。浮点参数的传递也是通过ARM寄存器或者堆栈完成。现在的Linux系统默认编译选择使用hard-float,如果系统没有任何浮点处理器单元,这就会产生非法指令和异常。因而一般的系统镜像都采用软浮点以兼容没有VFP的处理器。
用一句话总结,软浮点是通过浮点库去实现浮点运算的,效率低;硬浮点是通过浮点运算单元(FPU)来完成的,效率高。
一、使用浮点库实现浮点运算(soft-float)
例如:我想实现两个浮点数相加,代码如下:
使用GNU ARM编译器翻译成的部分汇编代码如下:
从图中我们可以知道,默认情况下,编译器使用的是软浮点,图中__aeabi_fadd这个函数是在浮点库中实现。如果想让代码能正常的运行,还需要在连接的时候静态连接一下浮点库。
在这里我们以一个完成的案例来说明一下,软浮点库的使用方法。
start.S:
.global _start
#define USER_MODE 0x10
_start:
@设置CPU为user模式
mov r0,#USER_MODE
msr cpsr_c,r0
@跳到main函数
ldr sp,=0x34000
bl main
stop:
b stop
main.c:
int main()
{
float f1,f2,f3;
f1 = 1.24;
f2 = 1.22;
f3 = f1 + f2;
return 0;
}
Makefile:
LD=arm-none-eabi-ld
OBJDUMP=arm-none-eabi-objdump
RM=rm -rf
CFLAG= -g -c
ASFLAG=-g -c
OBJ=start.o main.o
LDFLAGS= -static -L\
#指定浮点库所在的路径
"C:\Program Files\yagarto\lib\gcc\arm-none-eabi\4.6.2" -lgcc
#设置编译模式
%.o:%.S
$(CC) $(ASFLAG) $< -o $@
%.o:%.c
$(CC) $(CFLAG) $< -o $@
all:$(OBJ)
$(LD) -Ttext=0x20000 $^ -o arm.elf $(LDFLAGS)
$(OBJDUMP) -D arm.elf > arm.dis
clean:
$(RM) *.o arm.dis arm.elf
使用硬件浮点实现浮点运算(hard-float)
使用硬件浮点的时候,我们需要给编译器传递一些参数,让编译器编译出硬件浮点单元处理器能识别的指令。
(1)-mfpu=name
参数-mfpu就是用来指定要产生那种硬件浮点运算指令,常用的右vfp和neon等。
浮点协处理器指令:
ARM10 and ARM9:
-mfpu=vfp(or vfpv1 or vfpv2)
Cortex-A8:
-mfpu=neon
(2) -mfloat-abi=value
-mfloat-abi=soft 使用这个参数时,其将调用软浮点库(softfloat lib)来支持对浮点的运算,GCC编译器已经有这个库了,一般在libgcc里面。这时根本不会使用任何浮点指令,而是采用常用的指令来模拟浮点运算。但使用的ARM芯片不支持硬浮点时,可以考虑使用这个参数。在使用这个参数时,连接时一般会出现下面的提示:
undefined reference to `__aeabi_fdiv'
或者类似的提示,主要因为一般情况下连接器没有去主动寻找软浮点库,这时使用将libgcc库加入即可。
-mfloat-abi=softfp
-mfloat-abi=hard
这两个参数都用来产生硬浮点指令,至于产生哪里类型的硬浮点指令,需要由
-mfpu=xxx参数来指令。这两个参数不同的地方是:
-mfloat-abi=softfp生成的代码采用兼容软浮点调用接口(即使用-mfloat-abi=soft时的调用接口),这样带来的好处是:兼容性和灵活性。库可以采用-mfloat-abi=soft编译,而关键的应用程序可以采用-mfloat-abi=softfp来编译。特别是在库由第三方发布的情况下。
-mfloat-abi=hard生成的代码采用硬浮点(FPU)调用接口。这样要求所有库和应用程序必须采用这同一个参数来编译,否则连接时会出现接口不兼容错误。
我们对main.c文件使用硬件浮点重新编译:
翻译成的汇编代码如下:
start.s:
.global _start
#define USER_MODE 0x10
_start:
@ 设置为所有模式都可以访问协处理器,cortex-A8手册 3.2.27
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
@ 使能NEON and VFP协处理器,NEON and VFP enable bit.
@ 设置fpexc的30位为1去使能NEON and VFP,cortex-A8 手册 13.4.3
ldr r0, =1<<30
fmxr fpexc, r0
@设置CPU为user模式
mov r0,#USER_MODE
msr cpsr_c,r0
@跳到main函数
ldr sp,=0x34000
bl main
stop:
b stop
main.c:
int main()
{
float f1,f2,f3;
f1 = 1.24;
f2 = 1.22;
f3 = f1 + f2;
return 0;
}
Makefile:
CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-ld
OBJDUMP=arm-none-eabi-objdump
RM=rm -rf
CFLAG=-g -c -mfpu=neon -mfloat-abi=softfp
ASFLAG=-g -c -mfpu=neon -mfloat-abi=softfp
OBJ=start.o main.o
#设置编译模式
%.o:%.S
$(CC) $(ASFLAG) $< -o $@
%.o:%.c
$(CC) $(CFLAG) $< -o $@
all:$(OBJ)
$(LD) -Ttext=0x20000 $^ -o arm.elf
$(OBJDUMP) -D arm.elf > arm.dis
clean:
$(RM) *.o arm.dis arm.elf