Programming with 64-Bit ARM Assembly Language: Single Board Computer Development for Raspberry Pi and Mobile Devices

Chapter 12: Floating-Point Operations


2021.08.12: updated by
floating-point unit (FPU) の働きを説明する。 Armの古い文献だと vector floating-point (VFP) coprocessor について触れているものもあるが、 最近では FPU における vector 処理は NEON coprocessor によって行うのが標準なので、 13章 "Neon Coprocessor" で説明する。

About Floating-Point Numbers

ARM CPU は floating-point number を3種類の精度で表す。IEEE754参照。
NamePrecisionSignFractionalExponent10進換算桁数
Half1611053.31
Single3212387.22
Double641521115.95

[自分へのメモ] IEEE 754 のbinary32形式の説明

 s   e                 f                s:sign, e:exponent(8 bits), f: fraction (23 bits)
 3 32222222 22211111111110000000000
 1 09876543 21098765432109876543210
exponent は8bit幅で [0, 255] の値を取るが 0と255の場合は特別な意味となる。 [1,254]の場合は数値を表すが+127のオフセットがあるため実際は[-126, 127] の値を表すことになる。
種類exponentfraction
ゼロ00
非正規化数00以外
正規化数1-254任意
無限大2550
NaN2550以外の任意

Defining Floating-Point Numbers

.single    1.343, 4.343e20, -0.4343, -0.4444e-10
.double    -4.24322322332e-10, 3.141592653589793

About FPU Registers

32個の128 bitsレジスタ V0, ..., V31 があり、 ARM FPU と NEON coprocessor はこれらのレジスタを共有している。 Vnレジスタの下位64,32, 16bit部分にアクセスするには、Dn, Sn, Hn (D0, ..., D31, S0, ..., S31, H0, ..., H31)というレジスタ名を用いる。

FPUでは64bit以下の浮動小数点数しか扱えない。128bit浮動小数点数を扱うためには NEON Coprocessor が必要となる。

NEON Coprocessor は 128 bit の整数をレジスタに入れることができるが、その場合は Qn (Q0, ..., Q31)というレジスタ名を用いる。 128bitレジスタ全体をスタックに push したり pop したりするためにも Qn というレジスタ名でアクセスする必要がある。


Defining the Function Call Protocol

    STP    Q8, Q9, [SP, #-32]!
    STR    Q10, [SP, #-16]!
    ...
    LDR    Q10, [SP], #16
    LDP    Q8, Q9, [SP], #32

Loading and Saving FPU Registers

    LDR    X1, =fp1
    LDR    S4, [X1]       // fp1
    LDR    D5, [X1, #4]   // fp2
    STR    S4, [X1]       // fp1
    STR    D5, [X1, #4]   // fp2
.data
fp1:    .single    3.14159
fp2:    .double    4.3341
fp3:    .single    0.0
fp3:    .double    0.0

FMOV 命令を使って、CPUの整数レジスタとFPUのレジスタの間、 および、FPUの2つのレジスタの間でデータをコピーできる。 一般には、レジスタは同じサイズでなければならないが、 半精度 H レジスタは大きな整数レジスタとの間でデータをコピーできる。

    FMOV    H1, W2
    FMOV    W2, H1
    FMOV    S1, W2
    FMOV    X1, D2
    FMOV    E2, E3

Performing Basic Arithmetic

乗算と加算のような多少の拡張はあるが、4種類の基本的な命令がある。 また、2乗根のような特別の関数もある。

各命令は H, S, D レジスタのどれでも使うことができる。 ([自分へのメモ]おそらくレジスタ幅は同じである必要がある。) 下に命令の抜粋を示す。

FADD    Dd, Dn, Dm    // Dd = Dn + Dm
FADD    Sd, Sn, Sm
FADD    Hd, Hn, Hm
FSUB    Dd, Dn, Dm    // Dd = Dn - Dm
FMUL    Dd, Dn, Dm    // Dd = Dn * Dm
FDIV    Dd, Dn, Dm    // Dd = Dn / Dm
FMADD   Dd, Dn, Dm, Da    // Dd = Da + Dn * Dm
FMSUB   Dd, Dn, Dm, Da    // Dd = Da - Dn * Dm
FNEGB   Dd, Dn            // Dd = -Dn
FABS    Dd, Dn            // Dd = abs(Dn)
FMAX    Dd, Dn, Dm        // Dd = max(Dn, Dm)
FMIN    Dd, Dn, Dm        // Dd = min(Dn, Dm)
FSQRT   Dd, Dn            // Dd = min(Dn)

2点間の距離を求める

2つの点の座標 ($x_1$, $y_1$), ($x_2$, $y_2$) が単精度で与えられた時、 その間の距離 $d = \sqrt ((y_2 - y_1)^2 + (x_2 - x_1)^2)$ を求めるコード。

// distance between two points in single precision floating-point.
//
// Inputs:
//    X0 - pointer to the 4 FP numbers x1, y1, x2, y2
// Outputs:
//    X0 - the length as single precision
.global distance
distance:
    STR    LR, [SP, #-16]!    // save Link Register
    LDP    S0, S1, [X0], #8
    LDP    S2, S3, [X0]
    FSUB   S4, S2, S0    // s4 = x2 - x1
    FSUB   S5, S3, S1    // s5 = y2 - y1
    FMUL   S4, S4, S4    // s4 = s4^2
    FMUL   S5, S5, S5    // s5 = s5^2
    FADD   S4, S4, S5    // s4 = s4 + s5
    FSQRT  S4, S4        // s4 = sqrt(s4)
    FMOV   W0, S4        // return value
    LDR    LR, [SP], #16 // restore Link Register
    RET
// Main program
.global main
    .eqn    N, 3    // number of points
main:
    STP    X19, X20, [SP, #-16]!
    STR    LR, [SP, #-16]!

    LDR    X20, =points
    MOV    W19, #N
loop:
    MOV    X0, X20
    BL     distance
    FMOV   S2, W0    // S2 <-- return value
    FCVT   D0, S2    // convert double precision

    FMOV   X1, D0
    LDR    X0, =prtstr
    BL     printf

    ADD    X20, X20, #(4*4)    // 4 points each 4 byte
    SUBS   W19, W19, #1
    B.NE   loop

    MOV    X0, #0    // return code
    LDR    LR, [SP], #16
    LDP    X19, X20, [SP], #16
    RET
.data
points: .single    0.0, 0.0, 3.0, 4.0                 // 1st: x1, y1, x2, y2
        .single    1.3, 5.4, 3.1, -1.5                // 2nd: x1, y1, x2, y2
        .single    1.323e10, -1.2e-4, 34.55, 5454.234 // 3rd: x1, y1, x2, y2
prtstr: .asciz     "Distance = %f\n"

Performing FloatingPoint Conversions

半精度 Hn, 単精度 Sn, 倍精度 Dn の間の変換を行う FPU 命令。

    FCVT    Dd, Sm
    FCVT    Sd, Dm
    FCVT    Sd, Hm
    FCVT    Hd, Sm

整数を浮動小数に変換する命令。

    SCVTF    Dd, Xm    // Dd = signed integer from Xm
    UCVTF    Sd, Wm    // Sd = unsigned integer from Wm

浮動小数を整数に変換する命令は、round handled 丸め方によっていくつかある。

    FCVTAS    Wd, Hn    // signed, round to nearest
    FCVTAU    Wd, Sn    // unsigned, round to nearest
    FCVTMS    Xd, Dn    // signed, round towards minus infinity
    FCVTMU    Xd, Dn    // unsigned, round towards minus infinity
    FCVTPS    Xd, Dn    // signed, round towards positive infinity
    FCVTPU    Xd, Dn    // unsigned, round towards positive infinity
    FCVTZS    Xd, Dn    // signed, round towards zero
    FCVTZU    Xd, Dn    // unsigned, round towards zero

Comparing Floating-Point Numbers

floating-point 命令のほとんどは 'S'が付加されないので、条件フラグを変化させない。 条件フラグを変化させるには FCMP 命令を使う。

    FCMP    Hd, Hm
    FCMP    Hd, #0.0
    FCMP    Sd, Sm
    FCMP    Sd, #0.0
    FCMP    Dd, Dm
    FCMP    Dd, #0.0

Hn同士や、Sn同士、Dn同士を比較できる。 また、#0.0という即値と比較できる(zero register がないから必要である)。



http://karel.tsuda.ac.jp/