Cover image for ARM Neon Instruction

ARM Neon Instruction

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

Vector registers and lanes
Vector registers and lanes

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

The vector is divided into multiple lanes
The vector is divided into multiple lanes

  • 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
NameBit widthCorresponding V register rangeDescription
Vn128 bitV0–V31NEON 128-bit register (full vector)
Dn64 bitD0–D31Lower 64 bits of Vn (Double precision)
Sn32 bitS0–S31Lower 32 bits of Dn (Single precision)
Hn16 bitH0–H31Next level below S register (Half precision)
Bn8 bitB0–B31Lowest 8 bits (Byte)

Channel data type
Channel data type

Access method
Access method

Vector group representation methodMeaning
Vn.8B8 bits × 8 lanes, representing 8 data channels, each data element is 8 bits.
Vn.4H16 bits × 4 lanes, representing 4 data channels, each data element is 16 bits.
Vn.2S32 bits × 2 lanes, representing 2 data channels, each data element is 32 bits.
Vn.2D64 bits × 2 lanes, representing 2 data channels, each data element is 64 bits.
Vn.16B8 bits × 16 lanes, representing 16 data channels, each data element is 8 bits.
Vn.4S32 bits × 4 lanes, representing 4 data channels, each data element is 32 bits.
Vn.2D64 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
    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
    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

Floating-point IEEE 754
Floating-point IEEE 754

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

    1. 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

  1. 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

  1. 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

  1. 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

Experiment 1
Experiment 1

fmov instruction

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

Floating-point constants supported by the fmov instruction
Floating-point constants supported by the fmov instruction

FPCR register

Floating-point Control Register

FPCR register
FPCR 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.

half-precision
half-precision

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

Conditional operation codes for floating-point numbers
Conditional operation codes 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

Commonly used floating-point instructions
Commonly used floating-point instructions

Neon instruction optimization

SISD and SIMD

  • SISD (Single Instruction Single Data) refers tosingle instruction, single dataeach instruction performs its specified operation on a single data source
1
2
3
4
ADD w0, w0, w5
ADD w1, w1, w6
ADD w2, w2, w7
ADD w3, w3, w8
  • 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

Single Instruction, Multiple Data
Single Instruction, Multiple Data

  • 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

Perform the same operation on multiple data elements simultaneously
Perform the same operation on multiple data elements simultaneously

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
    4
    LD1 {<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
    LD1 instruction encoding

    • post-indexed mode
    1
    2
    3
    4
    LD1 {<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.

Example 1
Example 1

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

Example 1
Example 1

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
    4
    ST1 {<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
    4
    ST1 {<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]

ST1 instruction stores RGB24
ST1 instruction stores RGB24

Experiment 2: Use of LD1 and ST1 instructions

Experiment 2
Experiment 2

Experiment 2 Debugging
Experiment 2 Debugging

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
    5
    LD2 {<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]

LD2/ST2
LD2/ST2

Experiment 3: Using LD2 and ST2 Instructions

Experiment 3
Experiment 3

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
2
3
4
5
LD3 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>]
ST3 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>]

LD3 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>], <imm>
ST3 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>], <imm>

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]

RGB24 to BGR24
RGB24 to BGR24

Instruction
Instruction

Store RGB24 to memory

Store RGB24 to memory
Store RGB24 to memory

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

Experiment 4
Experiment 4

C language implementation

C language implementation
C language implementation

Assembly implementation

Assembly 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
2
3
4
5
LD4 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>]
ST4 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>]

LD4 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>], <imm>
ST4 {<Vt>.<T>,<Vt2>.<T>,<Vt3>.<T>,<Vt4>.<T>},[<Xn|SP>], <imm>

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]

LDnR instruction
LDnR instruction

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]

Load data into a specific lane of a vector register, while the values of other lanes remain unchanged.
Load data into a specific lane of a vector register, while the values of other lanes remain unchanged.

MOV instruction

  • Move data from a general-purpose register
1
2
mov w1, #0xa
mov v1.h[2], w1
  • Vector register move
1
2
MOV V3.16B, V0.16B
MOV V3.8B, V0.8B
  • 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
2
3
movi Vd.<T>, #imm
movi Vd.<T>, #imm, LSL #shift
movi Vd.<T>, #imm, MSL #shift

Where:

  • Vd: destination register (e.g.,v0v1
  • <T>: data type (e.g.,8b16b4h2s1d, etc.)
  • #imm: immediate value
  • LSL/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.

V0 is the destination register, V1 is the source register.
V0 is the destination register, V1 is the source register.

Example 2:

1
REV32 V0.16B, V1.16B

Example 2
Example 2

Example 3:

1
REV64 V0.16B, V1.16B

Example 3
Example 3

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
2
ZIP1 V0.8H, V3.8H, V4.8H
ZIP2 V1.8H, V3.8H, V4.8H

ZIP1 and ZIP2 instructions
ZIP1 and ZIP2 instructions

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

TRN1 and TRN2 interleave and swap instructions
TRN1 and TRN2 interleave and swap instructions

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

TRN1 and TRN2 interleave and swap instructions
TRN1 and TRN2 interleave and swap instructions

TBL table lookup instruction

  • The TBL instruction format is as follows

TBL instruction format
TBL instruction format

Example:

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

TBL instruction
TBL instruction

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

Multiply-accumulate instruction
Multiply-accumulate instruction

This method supports b

1
mla v2.4s, v0.4s, v1.4s[0]

mla
mla

This method supports h and s but not b

Experiment 5: Familiarize with the MLA instruction

Experiment 5
Experiment 5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
neon_mla_test:
stp x29, x30, [sp, -16]!
movi v3.16b, 0
movi v4.16b, 0
// Continuously read 16 bytes from address x0 and put them into v0.16b
ld1 {v0.16b}, [x0], 16
ld1 {v1.16b}, [x0], 16
mla v3.16b, v0.16b, v1.16b
mla v3.16b, v0.16b, v1.16b

mov w1, #0
mov v2.4s[0], w1
mov w1, #1
mov v2.4s[1], w1
mov w1, #2
mov v2.4s[2], w1
mov w1, #3
mov v2.4s[3], w1
mov w1, #4
mov v5.4s[0], w1
mov w1, #5
mov v5.4s[1], w1

mla v4.4s, v2.4s, v5.4s[0]
mla v4.4s, v2.4s, v5.4s[1]
ldp x29, x30, [sp], 16
ret

Vector arithmetic instructions

Vector arithmetic instructions
Vector arithmetic instructions

Experiment 6: Case study 1 - RGB24 to BGR24

C language implementation

RGB24 to BGR24
RGB24 to BGR24

Inline assembly

RGB24 to BGR24
RGB24 to BGR24

Note two points:

  1. Using the post-indexed mode, so dst and src should be placed in the output section
  2. 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

Using NEON intrinsics
Using NEON intrinsics

Documentation:

  • <<Arm Neon Intrinsics Reference for ACLE Q3 2020>>
  • <<NEON Programmer Guide>>

4×4 matrix multiplication

4×4 matrix multiplication
4×4 matrix multiplication

C language implementation

C language implementation
C language implementation

Manually writing NEON assembly

Manually writing NEON assembly
Manually writing NEON assembly

Using NEON intrinsics

Using NEON intrinsics
Using NEON intrinsics

Using NEON intrinsics
Using NEON intrinsics

Process 1
Process 1

Process 2
Process 2

Process 3
Process 3

Process 4
Process 4

Process 5
Process 5

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