Timeline
Timeline
2025-10-30
init
This article introduces the basics of NEON instructions and floating-point operations in the ARM architecture, discusses in detail the history of VFP development, the division of vector registers and lanes, and data types, and summarizes the binary conversion rules for IEEE 754 standard single and double precision floating-point numbers under the ARMv8 architecture, as well as the functions of relevant control bits in the FPCR register.
Reference documents:
Floating-point operations
Floating-point operations FP and NEON instructions
- History of VFP development
- VFPv1: Early version
- VFPv2: VFP coprocessor in ARMv5 and ARMv6 processors
- VFPv3: ARMv7 processors
- VFPv4: ARMv7 processors
- NEON: Supports SIMD instructions and floating-point operation instructions
Vector registers and lanes

- Vectors are divided into multiple lanes, each containing a vector element

- Lane data types:
- Vn: 128-bit data type
- Dn: 64-bit data type
- Sn: 32-bit data type
- Hn: 16-bit data type
- Bn: 8-bit data type
| Name | Bit width | Corresponding V register range | Description |
|---|---|---|---|
| Vn | 128 bit | V0–V31 | NEON 128-bit register (full vector) |
| Dn | 64 bit | D0–D31 | Lower 64 bits of Vn (Double precision) |
| Sn | 32 bit | S0–S31 | Lower 32 bits of Dn (Single precision) |
| Hn | 16 bit | H0–H31 | Next level below S register (Half precision) |
| Bn | 8 bit | B0–B31 | Lowest 8 bits (Byte) |


| Vector group representation method | Meaning |
|---|---|
| Vn.8B | 8 bits × 8 lanes, representing 8 data channels, each data element is 8 bits. |
| Vn.4H | 16 bits × 4 lanes, representing 4 data channels, each data element is 16 bits. |
| Vn.2S | 32 bits × 2 lanes, representing 2 data channels, each data element is 32 bits. |
| Vn.2D | 64 bits × 2 lanes, representing 2 data channels, each data element is 64 bits. |
| Vn.16B | 8 bits × 16 lanes, representing 16 data channels, each data element is 8 bits. |
| Vn.4S | 32 bits × 4 lanes, representing 4 data channels, each data element is 32 bits. |
| Vn.2D | 64 bits × 2 lanes, representing 2 data channels, each data element is 64 bits. |
Index the value of a specific channel
- For example, “V0.S[1]” represents the first 32-bit data in vector group V0, i.e., Bit[63:32]
Vector register list

Vector register list Index the value of a channel in the vector register list

Index the value of a channel in the vector register list
Floating-point number
- ARMv8 supports the IEEE 754 standard
- ARM64 processors support single-precision and double-precision floating-point numbers. In ARM64 processors, single-precision floating-point numbers are represented using 32-bit Sn registers, and double-precision floating-point numbers are represented using 64-bit Dn registers
- In C language, the float type can be used to represent single-precision floating-point numbers, and the double type to represent double-precision floating-point numbers
- A floating-point number consists of three parts: sign bit S, exponent, and mantissa
- A sign bit of 0 indicates a positive number, and 1 indicates a negative number
- The exponent has a fixed bias of 127
- Single-precision floating-point numbers use 32 bits of space, with 8 bits for the exponent and 24 bits for the mantissa
- Double-precision floating-point numbers use 64 bits of space, with 11 bits for the exponent and 53 bits for the mantissa

Binary representation of floating-point numbers
Convert the decimal number (5.25) to a single-precision floating-point number; what is its binary storage format?
Steps
- Convert the decimal number to binary: directly convert the integer part to binary, and multiply the fractional part by 2 and take the integer part.
For the integer part, directly convert 5 to binary, resulting in the binary number: 101
For the fractional part, multiply the decimal fraction by 2, and take the digit to the left of the decimal point (0 or 1) as a digit in the binary representation, continuing until the required precision is met.
0.25 * 2 = 0.5, the digit to the left of the decimal point is 0
0.5 * 2 = 1.0, the digit to the left of the decimal point is 1
The decimal number (5.25) is converted to the binary number 101.01
- Normalize the binary number by adjusting the exponent so that only the first significant digit is before the decimal point.
The binary number (101.01) after normalization becomes: 1.0101 * 2^2, where the mantissa is 0101 and the exponent is 2
- Calculate the exponent. For single-precision floating-point numbers, add a bias of 7F (127); for double-precision, add a bias of 3FF (1023).
In this example, the final exponent is 129
- Combine the sign bit, exponent, and mantissa to obtain the floating-point storage format.
In this example, the sign bit is 0, the exponent is: 1000 0001, the mantissa is: 0101, represented in hexadecimal as: 0x40a80000
Experiment 1: Binary Representation of Floating-Point Numbers

fmov instruction
The floating-point constants supported by the fmov instruction are limited

FPCR register
Floating-point Control Register

AHP (bit 26) — Alternative half-precision control bit
This flag controls whether half-precision floating-point uses the IEEE 754-2008 standard or an alternative implementation designed by ARM. When this flag is 0, the IEEE 754-2008 half-precision representation is used; when it is 1, ARM’s own alternative representation is used.
It should be noted that the new FP16 instruction extension introduced from ARMv8.2 directly uses the IEEE format for half-precision floating-point and ignores this flag entirely.
Below is a brief introduction to ARM’s own half-precision floating-point representation. It is described in detail in Section A1.4.2 (Half-precision floating-point formats) of the ARMv8 Reference Guide. Like the IEEE 754-2008 specification, it has 1 sign bit, 5 exponent bits, and 10 mantissa bits. However, there are differences in the representation of infinity and NaN.

When the exponent is all 1s (i.e., 0x1F):
- For IEEE 754-2008: This half-precision floating-point number may be positive or negative infinity (±∞, ±INF) or a NaN. It depends on the mantissa — if the mantissa is all zeros, then when the sign bit S=0, the number is +INF; when S=1, it is -INF. If the mantissa is non-zero, the most significant bit of the mantissa (bit 9) determines the type: if it is 0, the NaN is an SNaN; otherwise, it is a QNaN.
- For ARM’s alternative half-precision floating-point: This floating-point value is treated as a normalized number, equivalent to (-1)^S * 2^16 * (1.fraction). This means the maximum positive normalized number is (2 - 2^-10) * 2^16 = 131008.
DN (bit 25) — Default NaN mode control bit
This flag controls whether a NaN operand is propagated to the output of a floating-point operation, meaning that even if an operand is NaN, the calculation proceeds normally. If this flag is set to 1, then for a floating-point operation instruction, if any operand is NaN, a default NaN value set by the ARM processor is immediately returned. This flag can also improve floating-point execution performance.
If the DN flag is 1, the default NaN values for the three floating-point types are as follows:
- 16-bit half-precision floating point: 0x7E00
- 32-bit single-precision floating point: 0x7FC0’0000
- 64-bit double-precision floating point: 0x7FF8’0000’0000’0000
FZ (bit 24) — Flush-to-zero mode control bit
The behavior of this mode is consistent with the x86 DAZ flag (rather than FTZ) — if this flag is set to 1, then for a floating-point computation instruction, all denormalized numbers in the operands are rounded to 0. Additionally, this flag only controls 32-bit single-precision and 64-bit double-precision floating point, and does not affect the 16-bit half-precision floating point introduced by ARMv8.2-FP16.
RMode (bits 23:22) — Rounding Mode control field
This field relates to the rounding mode of floating-point numbers.
FZ16 (bit 19) — Flush-to-zero mode control bit on half-precision data-processing instructions
This flag is only valid when the ARM architecture supports the ARMv8.2-FP16 instruction extension. This flag controls the flush-to-zero operation for half-precision floating point, with semantics identical to the FZ above. However, this flag is only used to control 16-bit half-precision floating point and does not affect other floating-point types.
Architectural Feature Access Control Register CPACR_EL1
There is a control field for whether FP/NEON registers trap to EL1:FPEN
- When FPEN is 0b01, accessing SVE, Advanced SIMD, and floating-point unit registers in EL0 will trap to EL1 for handling, with exception type code 0x7
- When FPEN is 0b00 or 0b10, accessing SVE, Advanced SIMD, and floating-point unit registers in EL0 or EL1 will trap to EL1 for handling, with exception type code 0x7
- When FPEN is 0b11, it indicates no trapping to EL1
Condition code for floating-point numbers

Commonly used floating-point instructions
- Instructions start with the letter F, about several dozen instructions
- See Chapter C.2 of the ARMv8.6 manual
- See <<ARM Compiler arm asm User Guide,v6.6>> Chapters 18-20

Neon instruction optimization
SISD and SIMD
- SISD (Single Instruction Single Data) refers tosingle instruction, single data。each instruction performs its specified operation on a single data source
1 | ADD w0, w0, w5 |
- SIMD refers tosingle instruction, multiple data streams, whichPerform the same operation on multiple data elements simultaneously. These data elements are packed into independent lanes within a larger register

- SIMD stands for Single Instruction, Multiple Data, which performs the same operation on multiple data elements simultaneously. These data elements are packed into independent lanes within a larger register

LD1 instruction
The LD1 instruction is used to load multiple elements into one, two, three, or four vector registers.
The LD1 instruction supportsno offsetandpost-indexedtwo modes
- no offset mode
1
2
3
4LD1 {<Vt>.<T> }, [<Xn|SP>]
LD1 {<Vt>.<T>, <Vt2>.<T>}, [<Xn|SP>]
LD1 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>]
LD1 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>, <Vt4>.<T>}, [<Xn|SP>]
LD1 instruction encoding - post-indexed mode
1
2
3
4LD1 {<Vt>.<T> }, [<Xn|SP>], <imm>
LD1 {<Vt>.<T>, <Vt2>.<T>}, [<Xn|SP>], <imm>
LD1 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>], <imm>
LD1 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>, <Vt4>.<T>}, [<Xn|SP>], <imm>
Example 1: LD1 instruction loads RGB24
- Taking the RGB24 image format as an example, one pixel uses 24 bits (3 bytes) to represent the three colors R (red), G (green), and B (blue). Their storage format in memory is R0, G0, B0, R1, G1, B1, and so on.

- Use the LD1 instruction to load data in RGB24 format into vector registers
1 | LD1 {V0.16B, V1.16B, V2.16B}, [x0] |

ST1 instruction
The ST1 instruction stores the contents of multiple data elements from one, two, three, or four vector registers into memory
The ST1 instruction supports no offset and post-indexed modes
- No offset mode:
1
2
3
4ST1 {<Vt>.<T>}, [<Xn|SP>]
ST1 {<Vt>.<T>,<Vt2>.<T>},[<Xn|SP>]
ST1 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>},[<Xn|SP>]
ST1 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>]- Post-indexed mode:
1
2
3
4ST1 {<Vt>.<T>}, [<Xn|SP>], <imm>
ST1 {<Vt>.<T>,<Vt2>.<T>},[<Xn|SP>], <imm>
ST1 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>},[<Xn|SP>], <imm>
ST1 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>], <imm>
Example: ST1 instruction stores RGB24
- Vector registers V0, V1, and V3 store data in RGB24 format, and the ST1 instruction is used to store the data into memory
1 | ST1 {V0.16B, V1.16B, V2.16B}, [x0] |

Experiment 2: Use of LD1 and ST1 instructions


LD2/ST2: Interleave Load and Store
The LD2 and ST2 instructions support loading and storing data in an interleaved manner
Supports both no offset and post-indexed modes
1
2
3
4
5LD2 {<Vt>.<T>,<Vt2>.<T>},[<Xn|SP>]
ST2 {<Vt>.<T>,<Vt2>.<T>},[<Xn|SP>]
LD2 {<Vt>.<T>,<Vt2>.<T>},[<Xn|SP>], <imm>
ST2 {<Vt>.<T>,<Vt2>.<T>},[<Xn|SP>], <imm>Example
1 | LD2 {V0.8H, V1.8H}, [x0] |

Experiment 3: Using LD2 and ST2 Instructions

LD3/ST3: Three-Channel Interleave
- In RGB24 to BGR24 conversion, if we use the LD1 instruction to load RGB24 data into vector registers, we need to obtain different color components in different channels, then shift and recombine these components, which is inefficient
- The LD3 and ST3 instructions support loading and storing data in an interleaved manner
- Supports both no offset and post-indexed modes
1 | LD3 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>] |
Example: RGB24 to BGR24
- Use the LD3 instruction to load RGB24 format data into vector registers
1 | LD3 {V0.16B, V1.16B, V2.16B}, [x0] |


Store RGB24 to memory

Experiment 4: Using LD3/ST3 to implement RGB24 to BGR24 conversion

C language implementation

Assembly implementation

LD4/ST4: Four-channel interleave
- ARGB images add an Alpha (transparency) channel to RGB. To speed up data loading and storing operations for ARGB format, NEON instructions provide LD4 and ST4 instructions. LD4 is similar to LD3, but it can deinterleave data into four vector registers
- LD4 and ST4 instructions support loading and storing data in an interleaved manner
- Supports both no offset and post-indexed modes
1 | LD4 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>] |
LDnR instruction
- The LDn instruction also has a variant, the LDnR instruction,R stands for replacementIt loads a set of data elements from memory and then copies the data into all lanes of a vector register.
- Example:
1 | LD3R {V0.16B, V1.16B, V2.16B}, [x0] |

Read or write the value of a specific lane
- The LDn instruction can load data into a specific lane of a vector register, while the values of other lanes remain unchanged.
- Example:
1 | LD3 {V0.B, V1.B, V2.B}[4], [x0] |

MOV instruction
- Move data from a general-purpose register
1 | mov w1, #0xa |
- Vector register move
1 | MOV V3.16B, V0.16B |
- Move data elements to a vector register
1 | mov h2, v1.8h[2] |
- Move data elements
1 | mov v1.8h[2], v0.8h[2] |
MOVI
movi is in the ARMv8 (AArch64) architecture a vector immediate load instruction of NEON (Advanced SIMD) .
Its full name is“Move Immediate (vector)”。
moviUsed for:
Loading animmediate valueinto aSIMD/floating-point register (Vn or Qn).
It can fill all elements of the register at once, commonly used to initialize vector registers.
Syntax format
There are several common forms (using A64 assembly as an example):
1 | movi Vd.<T>, #imm |
Where:
Vd: destination register (e.g.,v0、v1)<T>: data type (e.g.,8b、16b、4h、2s、1d, etc.)#imm: immediate valueLSL/MSL: logical left shift / sign-extended left shift, used to modify the immediate value filling rules
Reverse instruction
- REV16 instruction
Indicates that 16-bit data elements in a vector register form a container. Within this 16-bit container, the order of 8-bit data elements is reversed, i.e., the order between B[0] and B[1] is reversed.
- REV32 instruction
Indicates that 32-bit data elements in a vector register form a container. Within this container, the order of 8-bit or 16-bit data elements is reversed.
- REV64 instruction
Indicates that 64-bit data elements in a vector register form a container. Within this container, the order of 8-bit, 16-bit, or 32-bit data elements is reversed.
Example 1:
1 | REV16 V0.16B, V1.16B |
V0 is the destination register, V1 is the source register.

Example 2:
1 | REV32 V0.16B, V1.16B |

Example 3:
1 | REV64 V0.16B, V1.16B |

ZIP1 and ZIP2 instructions
- The ZIP1 instruction extracts half of the data elements from each of two source vector registers, then interleaves them to form a new vector, which is written to the destination vector register.
- The ZIP2 instruction extracts half of the data elements from each of two source vector registers, here extracting from thehigh partof the data elements, then interleaves them to form a new vector, which is written to the destination vector register.
Example:
1 | ZIP1 V0.8H, V3.8H, V4.8H |

TRN1 and TRN2 interleave and swap instructions
- The TRN1 instruction interleaves and extracts odd-numbered data elements from two source vector registers to form a new vector, which is written to the destination vector register.
- The TRN2 instruction interleaves and extracts even-numbered data elements from two source vector registers to form a new vector, which is written to the destination vector register.
Example
1 | TRN1 V1.4S, V0.4S, V3.4S |

1 | TRN2 V2.4S, V0.4S, V3.4S |

TBL table lookup instruction
- The TBL instruction format is as follows

Example:
1 | TBL V4.16B, {V1.16B, V2.16B}, V0.16B |

There is also a variant instruction TBX, the only difference is that when the index is out of bounds, the original value is kept unchanged instead of writing 0
Multiply-accumulate instruction MLA
- MLA instruction is a multiply-accumulate instruction, Vd += Vn * Vm
- Example
1 | mla v2.4s, v0.4s, v1.4s |

This method supports b
1 | mla v2.4s, v0.4s, v1.4s[0] |

This method supports h and s but not b
Experiment 5: Familiarize with the MLA instruction

1 | neon_mla_test: |
Vector arithmetic instructions

Experiment 6: Case study 1 - RGB24 to BGR24
C language implementation

Inline assembly

Note two points:
- Using the post-indexed mode, so dst and src should be placed in the output section
- In the clobber section, tell the compiler that v0, v1, v2, v3 are used, otherwise the compiler might allocate these vector registers elsewhere
Using NEON intrinsics
Some NEON instructions wrapped by the compiler

Documentation:
- <<Arm Neon Intrinsics Reference for ACLE Q3 2020>>
- <<NEON Programmer Guide>>
4×4 matrix multiplication

C language implementation

Manually writing NEON assembly

Using NEON intrinsics







Auto-vectorization
- There are three ways to optimize code using the NEON instruction set:
- Manually writing NEON assembly code
- Using NEON intrinsics provided by the compiler
- Using the auto-vectorization option provided by the compiler to let it automatically generate NEON instructions for optimization
- The GCC assembler has built-in automatic vectorization optimization. GCC provides the following compilation options:
- -ftree-vectorize: Perform vectorization optimization. This will enable “-ftree-loop-vectorize” and “-ftree-slp-vectorize” by default.
- -ftree-loop-vectorize: Perform loop vectorization optimization. Unroll loops to reduce the number of iterations while performing more operations in each iteration.
- -ftree-slp-vectorize: Bundle scalar operations together to utilize the bandwidth of vector registers. SLP stands for Superword-Level Parallelism.
- GCC’s “-O3” optimization option automatically enables “-ftree-vectorize”, i.e., enables automatic vectorization optimization.
Constraints of Automatic Vectorization Optimization
- GCC’s automatic vectorization optimization may not work in some cases.
- In iterations of different loops with interdependencies
- Loops with break clauses
- Loops with complex conditions
