Cover image for ARM Neon Instruction

ARM Neon Instruction

Words 4.2k
Views
Visitors
Timeline

Timeline

2025-10-30

init

This article introduces the ARM NEON instruction set and its related technical details, focusing on the concepts of vector registers and lanes, including registers of different bit widths such as Vn, Dn, Sn, Hn, Bn and their corresponding vector group representation methods, and explains how to index specific data values in lanes. The article also elaborates on the representation and operations of floating-point numbers under the ARMv8 architecture, including the storage formats of single-precision and double-precision floating-point numbers, IEEE 754 standard support, and demonstrates the steps of converting decimal numbers to binary floating-point numbers through specific examples. In addition, the article summarizes the roles of key control bits in the FPCR register, such as the AHP bit controlling whether half-precision floating-point uses the IEEE standard or an ARM alternative implementation, the DN bit controlling the default NaN mode, and compares the differences between two half-precision floating-point formats in the representation of infinity and NaN. The overall content covers the basic data structures of NEON instructions, floating-point operation principles, and control register configuration, providing a reference for understanding SIMD and floating-point operations in ARM processors.

Reference documents:

Floating-point operations

Floating-point operations (FP) and NEON instructions

  • VFP development history
  • VFPv1: Early version
  • VFPv2: VFP coprocessor in ARMv5 and ARMv6 processors
  • VFPv3: ARMv7 processors
  • VFPv4: ARMv7 processors
  • NEON: Supports SIMD instructions and floating-point instructions

Vector registers and lanes

Vector registers and lanes
Vector registers and lanes

  • A vector is divided into multiple lanes, each lane contains a vector element.

A vector is divided into multiple lanes.
A 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–B31Least significant 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 being 8 bits of data.
Vn.4H16 bits × 4 lanes, representing 4 data channels, each data element being 16 bits of data.
Vn.2S32 bits × 2 lanes, representing 2 data channels, each data element being 32 bits of data.
Vn.1D64 bits × 1 lane, representing 1 data channel, each data element being 64 bits of data.
Vn.16B8 bits × 16 lanes, representing 16 data channels, each data element being 8 bits of data.
Vn.4S32 bits × 4 lanes, representing 4 data channels, each data element being 32 bits of data.
Vn.2D64 bits × 2 lanes, representing 2 data channels, each data element being 64 bits of data.
  • Index the value of a channel

    • For example, “V0.S[1]” represents the 32-bit data at index 1 in the V0 vector group, 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 numbers

  • 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 by 32-bit Sn registers, and double-precision floating-point numbers are represented by 64-bit Dn registers.
  • In C, 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.
    • A single-precision floating-point number uses 32 bits of space, with an 8-bit exponent and a 24-bit mantissa.
    • A double-precision floating-point number uses 64 bits of space, with an 11-bit exponent and a 53-bit mantissa.

IEEE 754 floating-point
IEEE 754 floating-point

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?

  • Step

    1. To convert a decimal number to a binary number: convert the integer part directly to a binary number, and for the fractional part, multiply by 2 and take the integer digit.

    For the integer part, directly convert 5 to binary, resulting in the binary number: 101

    For the fractional part, multiply the decimal fractional part by 2, and use the digit to the left of the decimal point (0 or 1) of the product as a digit in the binary representation, until the required precision is reached.

    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) converted to binary is 101.01

    1. Normalize the binary number by changing the exponent so that there is only one significant digit before the decimal point.

    After normalization, the binary number (101.01) 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 the bias of 7F (127); for double-precision floating-point numbers, add the 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, expressed 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 bit is used to control whether half-precision floating-point uses the IEEE 754-2008 standard or an alternative implementation designed by ARM. When this flag is 0, the half-precision representation of the IEEE 754-2008 standard is used; when it is 1, the alternative representation designed by ARM is used.

It should be noted here that the FP16 instruction extension introduced from ARMv8.2 directly uses the IEEE format for half-precision floating-point and ignores this flag.

The following briefly introduces 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, the two representations differ in representing infinity and NaN.

half-precision
half-precision

When the exponent field is all 1s (i.e., 0x1F):

  • For IEEE754-2008: this half-precision floating-point number may be positive or negative infinity (±∞, ±INF), or it may be a NaN. It depends on the mantissa field — if the mantissa field is all zeros, then when the sign bit S=0, the floating-point number is +INF; when S=1, it is -INF. If the mantissa field is not zero, then look at the most significant bit of the mantissa (bit 9); if it is 0, the NaN is an SNaN; otherwise it is a QNaN.
  • For the ARM alternative implementation of half-precision floating-point: the 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, i.e., even if an operand is NaN, the computation is still fully executed. If this flag is set to 1, then for a floating-point operation instruction, as long as any operand is NaN, a default NaN value set by the ARM processor is returned immediately. 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 DAZ flag on x86 (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. In addition, 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 pertains 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 the same semantics as 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

Fields that control whether FP/NEON register accesses trap to EL1:FPEN

  • When FPEN is 0b01, it means that accesses to 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, it means that accesses to 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 means no trap to EL1 occurs.

Floating-point condition codes

Floating-point condition codes
Floating-point condition codes

Common floating-point instructions

  • Instructions beginning with the letter F, about several dozen instructions.
  • See Chapter C.2 of the ARMv8.6 manual.
  • See the ARM Compiler arm asm User Guide, v6.6, Chapters 18-20

Common floating-point instructions
Common 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
1234
ADD w0, w0, w5ADD w1, w1, w6ADD w2, w2, w7ADD w3, w3, w8
  • SIMD refers toSingle instruction, multiple data, whichPerforms the same operation on multiple data elements simultaneouslyThese data elements are packed into independent lanes in a larger register.

Single instruction, multiple data
Single instruction, multiple data

Performs the same operation on multiple data elements simultaneously
Performs 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-indextwo modes

    • no-offset mode
    1234
    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-index mode
    1234
    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, a pixel uses 24 bits (3 bytes) to represent 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 RGB24 format data 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 to memory.

  • The ST1 instruction supports no-offset and post-index modes.

    • No offset mode:
    1234
    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-index mode:
    1234
    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

  • V0, V1, and V2 vector registers store data in RGB24 format, and the ST1 instruction is used to store the data to memory.
1
ST1 {V0.16B, V1.16B, V2.16B}, [x0]

ST1 instruction stores RGB24
ST1 instruction stores RGB24

Experiment 2: Using 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 two modes: no offset and post-indexed.

    12345
    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 from different channels, then move and recombine these components, which is very inefficient.
  • The LD3 and ST3 instructions support loading and storing data in an interleaved manner.
  • Supports two modes: no offset and post-indexed.
12345
LD3 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>]ST3 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>]LD3 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>], <imm>ST3 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<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 are based on RGB with an added Alpha (transparency) channel. To speed up data loading and storage operations for the ARGB format, the NEON instructions provide LD4 and ST4 instructions. LD4 is similar to LD3, but it can de-interleave data and load it into four vector registers.
  • The LD4 and ST4 instructions support loading and storing data in an interleaved manner.
  • Supports two modes: no offset and post-indexed.
12345
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 repeat.**It loads a group of data elements from memory, then copies each element to all lanes of the corresponding vector register.
  • Example:
1
LD3R {V0.16B, V1.16B, V2.16B}, [x0]

LDnR instruction
LDnR instruction

Read and 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 general-purpose registers.
12
mov w1, #0xamov v1.h[2], w1
  • Vector register move
12
MOV V3.16B, V0.16BMOV 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:

Move animmediate value Load into a SIMD/floating-point register (Vn or Qn) Middle.

It can fill all elements in a register at once, often used to initialize vector registers.

Syntax format

There are several common forms (using A64 assembly as an example):

123
movi Vd.<T>, #immmovi Vd.<T>, #imm, LSL #shiftmovi Vd.<T>, #imm, MSL #shift

Where:

  • Vd: destination register (e.g.,v0v1
  • <T>: data type (such as8b16b4h2s1detc.)
  • #imm: immediate value
  • LSL/MSL: logical left shift / sign-extended left shift, used to change the immediate value filling rules

Reverse instructions

  • REV16 instruction

Indicates that 16-bit data elements in a vector register form a container. Within this 16-bit container, reverse the order of 8-bit data elements, i.e., reverse the order between B[0] and B[1].

  • REV32 instruction

Indicates that 32-bit data elements in a vector register form a container. Within this container, reverse the order of 8-bit or 16-bit data elements.

  • REV64 instruction

Indicates that 64-bit data elements in a vector register form a container. Within this container, reverse the order of 8-bit, 16-bit, or 32-bit data elements.

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 the 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 the two source vector registers, where it extracts from the source vector registershigh partdata elements, then interleaved to form a new vector, written to the destination vector register

Example:

12
ZIP1 V0.8H, V3.8H, V4.8HZIP2 V1.8H, V3.8H, V4.8H

ZIP1 and ZIP2 instructions
ZIP1 and ZIP2 instructions

TRN1 and TRN2 interleaved exchange instructions

  • The TRN1 instruction extracts odd-indexed data elements from two source vector registers in an interleaved manner to form a new vector, and writes it to the destination vector register
  • The TRN2 instruction extracts even-indexed data elements from two source vector registers in an interleaved manner to form a new vector, and writes it to the destination vector register

Example

1
TRN1 V1.4S, V0.4S, V3.4S

TRN1 and TRN2 interleaved exchange instructions
TRN1 and TRN2 interleaved exchange instructions

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

TRN1 and TRN2 interleaved exchange instructions
TRN1 and TRN2 interleaved exchange 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, it retains the original value instead of writing 0

Multiply-accumulate instruction MLA

  • The 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 full-vector form supports element sizes such as b, h, and s

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

mla
mla

This scalar-index form supports h and s, but not b

Experiment 5: Familiarize with the MLA instruction

Experiment 5
Experiment 5

12345678910111213141516171819202122232425262728
neon_mla_test:    stp x29, x30, [sp, -16]!    movi v3.16b, 0    movi v4.16b, 0    // Read 16 consecutive bytes from the address in x0 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 post-indexed addressing mode, so dst and src should be placed in the output section
  2. In the clobber section, tell the compiler that the four vector registers v0, v1, v2, v3 are used; otherwise, the compiler may allocate these vector registers elsewhere.

Using NEON intrinsics

Some NEON instructions encapsulated by the compiler

Using NEON intrinsics
Using NEON intrinsics

Document:

  • 《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

Handwritten NEON assembly

Handwritten NEON assembly
Handwritten 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

Automatic vectorization

  • There are three ways to optimize code using the NEON instruction set, as follows:
    • Handwritten NEON assembly code
    • Use the NEON intrinsics provided by the compiler
    • Use the compiler’s auto-vectorization option to let the compiler automatically generate NEON instructions for optimization
  • The GCC compiler has built-in auto-vectorization functionality. GCC provides the following compilation options:
    • -ftree-vectorize: Perform vectorization. This enables “-ftree-loop-vectorize” and “-ftree-slp-vectorize” by default.
    • -ftree-loop-vectorize: Perform loop vectorization. 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 is an abbreviation for Superword-Level Parallelism.
    • GCC’s “O3” optimization option automatically enables “-ftree-vectorize”, i.e., enables the auto-vectorization functionality.

Auto-vectorization constraints

  • GCC’s auto-vectorization functionality may not work in some cases.
    • In iterations of different loops that have dependencies on each other
    • Loops with break statements
    • Loops with complex conditions
Loading comments…