Section 16.7 Floating-Point Hardware
Floating-point operations can be implemented in software, but the ARM processor in the Raspberry Pi includes floating-point hardware. Early models (Table 8.0.1) include a Coprocessor that provides additional registers and the capability to perform floating-point operations on values in those registers. The ARM Cortex A-53 used in the Raspberry Pi 3 B includes floating-point hardware in the main CPU. As with integer operation differences between AARCH32 and AARCH64, there are some differences between the coprocessor and built-in floating-point instructions. Both use the IEEE-754 32-bit and 64-bit storage formats.
The AARCH32 architecture defines a Vector Floating-point subarchitecture. The versions used in the Raspberry Pi are shown in Table 16.7.1. It has thirty-two 32-bit registers, s0
—s31
. Each register can hold one float
. These registers can be used in pairs for double
(64-bit) operations, as shown in Table 16.7.2. Note that the pairing is not arbitrary. For example, registers s0
and s1
can be paired and called d0
, but s1
cannot be paired with s2
.
Raspberry Pi | ARM CPU | VFP version |
Pi Zero | ||
Pi 1 A+ | ARM1176JZFS | VFPv2 |
Pi 1 B+ | ||
Pi 2 B | Cortex-A7 | VFPv4 |
Pi 3 B | Cortex-A53 | VFPv4 |
Float | Double | ||
Bank | Name | Name | Usage |
0 |
s0 —s7
|
d0 —d3
|
Scalar |
1 |
s8 —s15
|
d4 —d7
|
Vectorial |
2 |
s16 —s23
|
d8 —d11
|
Vectorial |
3 |
s24 —s31
|
d12 —d15
|
Vectorial |
The VFP registers are arranged in four banks. Bank 0 is scalar, and banks 1–3 are vectorial. The differences between the banks come into play when doing vector computations, which are not covered in this book.
Listing 16.7.3 shows the addition of two float
s.
/* * addFloat1.c * Add two floats. * 2017-09-29: Bob Plantz */ #include <stdio.h> int main() { float x = 1.23; float y = 4.56; float z; z = x + y; printf("%f + %f = %f\n", x, y, z); return 0; }
float
s. (C)Listing 16.7.4 shows how the gcc
compiled the C code in Listing 16.7.3.
.arch armv6 .file "addFloat1.c" .section .rodata .align 2 .LC0: .ascii "%f + %f = %f\012\000" .text .align 2 .global main .syntax unified .arm .fpu vfp .type main, %function main: @ args = 0, pretend = 0, frame = 16 @ frame_needed = 1, uses_anonymous_args = 0 push {fp, lr} add fp, sp, #4 sub sp, sp, #32 ldr r3, .L3 str r3, [fp, #-8] @ float ldr r3, .L3+4 str r3, [fp, #-12] @ float vldr.32 s14, [fp, #-8] @@ load x into fp reg vldr.32 s15, [fp, #-12] @@ load y into fp reg vadd.f32 s15, s14, s15 @@ fp add vstr.32 s15, [fp, #-16] vldr.32 s15, [fp, #-8] vcvt.f64.f32 d5, s15 @@ convert x to double vldr.32 s15, [fp, #-12] vcvt.f64.f32 d7, s15 @@ convert y to double vldr.32 s13, [fp, #-16] vcvt.f64.f32 d6, s13 @@ convert z to double vstr.64 d6, [sp, #8] @@ pass z on stack vstr.64 d7, [sp] @@ pass y on stack vmov r2, r3, d5 @@ pass x in r2/r3 ldr r0, .L3+8 @@ pointer to format string bl printf mov r3, #0 mov r0, r3 sub sp, fp, #4 @ sp needed pop {fp, pc} .L4: .align 2 .L3: .word 1067282596 .word 1083304837 .word .LC0 .ident "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
float
s. (gcc asm)As pointed out earlier in this book, the gcc
compiler generates pre-UAL assembly language. The differences between this and the UAL syntax are greatest with the floating-point instructions, so we will go directly to my solution of this problem in Listing 16.7.5, which uses the UAL syntax.
@ addFloat2.s @ Adds two floats and prints results @ 2017-09-29: Bob Plantz @ Define my Raspberry Pi .cpu cortex-a53 .fpu neon-fp-armv8 .syntax unified @ modern syntax @ Constants for assembler .equ arg3,0 @ args to printf .equ arg4,8 .equ argSpace,16 @ Constants for assembler .section .rodata .align 2 format: .asciz "%f + %f = %f\n" @ The program .text .align 2 .global main .type main, %function main: sub sp, sp, 24 @ space for saving regs @ (keeping 8-byte sp align) str r4, [sp, 4] @ save r4 str r5, [sp, 8] @ r5 str r6, [sp,12] @ r6 str fp, [sp, 16] @ fp str lr, [sp, 20] @ lr add fp, sp, 20 @ set our frame pointer sub sp, sp, argSpace @ room to pass args vldr s0, x @ load x into fp reg vldr s1, y @ load y into fp reg vadd.f32 s2, s1, s0 @ fp add ldr r0, formatAddr @ pointer to format string vcvt.f64.f32 d5, s0 @ convert x to double vmov r2, r3, d5 @ pass x in r2/r3 vcvt.f64.f32 d6, s1 @ convert y to double vstr d6, [sp, arg3] @ pass y on stack vcvt.f64.f32 d7, s2 @ convert z to double vstr d7, [sp, arg4] @ pass z on stack bl printf mov r0, 0 add sp, sp, argSpace @ deallocate arguments ldr r4, [sp, 4] @ restore r4 ldr r5, [sp, 8] @ r5 ldr r6, [sp,12] @ r6 ldr fp, [sp, 16] @ fp ldr lr, [sp, 20] @ lr add sp, sp, 24 @ sp bx lr @ return .align 2 x: .float 1.23 y: .float 4.56 formatAddr: .word format
float
s. (prog asm)The program in Listing 16.7.5 introduces five floating-point instructions.
VADD
-
Adds two floats or two doubles.
VADD{<c>}.F32 {<Sd>,} <Sn>, <Sm> % float VADD{<c>}.F64 {<Dd>,} <Dn>, <Dm> % double
<c>
is the condition code, Table 9.2.1.<Sd>
and<Dd>
are the destination registers, and<Sm>
and<Sn>
,<Dm>
and<Dn>
are the source registers.
All numbers are stored in IEEE 754 format. In the “float” form, the value in
<Sm>
is added to the 32-bit value in<Sn>
and the result is stored in<Sd>
. In the “double” form, the 64-bit value in<Dm>
is added to the value in<Dn>
and the result is stored in<Dd>
. VCVT
-
Converts between a float and a double.
VCVT{<c>}.F32.F64 <Sd>, <Dm> % double to float VCVT{<c>}.F64.F32 <Dd>, <Sm> % float to double
<c>
is the condition code, Table 9.2.1.<Sd>
and<Dd>
are the destination registers, and<Sm>
and<Dm>
and<Dn>
are the source registers.
All numbers are stored in IEEE 754 format. In the “double to float” form, the 64-bit value in
<Dm>
is converted to a 32-bit value and stored in<Sd>
. In the “float to double” form, the 32-bit value in<Sm>
is converted to a 64-bit value and stored in<Dd>
. VLDR
-
Loads a value from memory into a floating-point register.
VLDR{<c>} <Sd>, <label> % float VLDR{<c>} <Dd>, <label> % double
<c>
is the condition code, Table 9.2.1.<Sd>
and<Dd>
are the destination registers.<label>
is a programmer-labeled memory address. The labeled address must be within \(\pm 1,020\) bytes of the location of this instruction.
In the “float” form, the 32-bit value stored at the memory location is loaded into
<Sd>
. In the “double” form, the 64-bit value stored at the memory location is loaded into<Dd>
. No format conversions are made. VSTR
-
Stores a value in a floating-point register in memory.
VSTR{<c>} <Sd>, <label> % float VSTR{<c>} <Dd>, <label> % double
<c>
is the condition code, Table 9.2.1.<Sd>
and<Dd>
are the source registers.<label>
is a programmer-labeled memory address. The labeled address must be within \(\pm 1,020\) bytes of the location of this instruction.
In the “float” form, the 32-bit value in
<Sd>
is stored at the memory location. In the “double” form, the 64-bit value in<Dd>
is stored at the memory location. No format conversions are made. VMOV
-
Transfers a 64-bit value between a floating point register and two integer registers.
VMOV{<c>} <Dm>, <Rt>, <Rt2> % to double VMOV{<c>} <Rt>, <Rt2>, <Dm> % to int
<c>
is the condition code, Table 9.2.1.<Dm>
is the floating point register.<Rt>
is the low-order 32-bit portion and<Rt2>
is the high-order 32-bit portion in the integer registers.
In the “to double” form, the two 32-bit values in
<Rt2>
and<Rt>
are copied into<Dm>
. In the “to int” form, the 64-bit value in<Dm>
is copied into the<Rt2>
and<Rt>
registers. No format conversions are made.