ARM 汇编入门(十):标量浮点与 VFP 指令全解析

摘要:前九篇从架构、ARM32、AArch64 寄存器/寻址、算术位操作、分支、调用约定、内存模型、NEON、SVE/SVE2 一路走到这里。这一篇回到标量浮点子系统:AArch64 用统一的 V0--V31 寄存器文件同时服务标量 FP、NEON 和 SVE;FPCR 控舍入/非规格化/异常陷阱,FPSR 记粘性异常标志;FMADD 单次舍入、FMAXNM 跳过 NaN、FRECPE/FRSQRTE 做快速倒数/平方根估计;最后落到 FP16/BF16 这些机器学习降精度扩展。


一、统一寄存器文件:H / S / D / V 是同一块物理存储

AArch64 的浮点和 NEON 共用 32 个 128 位寄存器 V0--V31,标量视图只是低宽度别名:

视图 宽度 用途
Hn 16 位 FP16 / bfloat16 半精度
Sn 32 位 binary32 单精度
Dn 64 位 binary64 双精度
Vn / Qn 128 位 NEON 向量全宽

零扩展铁律 :写 S0 会把 V0 的高 96 位清零;写 D0 清高 64 位。这样后续把 V0/Z0 当全宽读时,不会漏进上一轮 NEON/SVE 的脏数据。

标量 FP 完全符合 IEEE-754-2008:渐进下溢、正确舍入、五类异常(Invalid / DivideByZero / Overflow / Underflow / Inexact)齐全。


二、FPCR 与 FPSR:一个管"怎么算",一个管"算炸没炸"

两个系统寄存器用 MRS/MSR 访问,任何 EL 都能碰(受陷阱位影响):

1. FPCR(Floating-point Control Register)

  • RMode23:22 :默认舍入模式
    00=RN 最近偶(银行家)、01=RP 向 +∞、10=RM 向 −∞、11=RZ 向零
  • FZ24:Flush-To-Zero,非规格化输入/输出清零。游戏/ML 常开(非规格化慢 10--100×),科学计算关。
  • DN25:Default NaN,开则任何 NaN 运算回默认 NaN,不传播原 payload
  • AHP26:替代半精度格式控制
  • 异常使能位 IDE/IXE/UFE/OFE/DZE/IOE:默认全 0 = 屏蔽,不陷阱只记标志

改 FPCR 在多数核上是串行化操作(50--100 周期) ,只想改一次舍入就用 FRINT* 指令,别 MSR FPCR 来回存恢复。

2. FPSR(Floating-point Status Register)

  • 粘性标志:IOC / DZC / OFC / UFC / IXC / IDC
  • 一旦置 1 不会自动清,软件写 0 才复位 → 长计算跑完统一 MRS X0, FPSR 查一次即可
  • AArch64 的 FCMP 改的是 PSTATE.NZCV,不是 FPSR 的 N/Z/C/V(那几位是 AArch32 遗留)

生产代码几乎不开 FP 陷阱;glibc 的 fetestexcept() 就是读 FPSR。调试期开 IOC 陷阱能立刻抓 NaN 产生点,比让 quiet NaN 飘过整条流水线强。


三、搬移与转换:FMOV / FCVT

复制代码
FMOV  D0, D1          ; 浮点↔浮点,同宽
FMOV  X0, D0          ; 位级搬运,不转换!原始比特
FMOV  W1, S0
FCVT  D0, S0          ; 单精度→双精度
FCVT  S0, D0          ; 双→单(可能 inexact/underflow)
FCVT  H0, S0          ; 单→半(FEAT_FP16)

整数↔浮点走 SCVTF/UCVTF(int→fp)和 FCVTZS/FCVTZU(fp→int,向零取整)。

⚠️ FMOV X0, D0 是比特搬运 ,不是 (int64)d0;要转整数必须 FCVTZS X0, D0。


四、算术:FADD/FSUB/FMUL/FDIV 与融合乘加

复制代码
FADD  S0, S1, S2
FSUB  D0, D1, D2
FMUL  S0, S1, S2
FDIV  D0, D1, D2      ; 10--20+ 周期,贵
FSQRT D0, D1          ; 15--30+ 周期,更贵
FABS  S0, S1
FNEG  D0, D1

融合乘加(FMA 家族)------标量 FP 最重要的一组

复制代码
FMADD  D0, D1, D2, D3   ; D0 = D3 + D1*D2,只舍入一次
FMSUB  S0, S1, S2, S3   ; S0 = S3 - S1*S2
FNMADD / FNMSUB         ; 取负版

中间乘积无限精度不舍入 ,只有最后加完才舍一次 → 比 FMUL+FADD 快且准。Kahan 求和、补偿点积、double-double 全靠它。GCC/Clang 加 -ffp-contract=fast 会自动把 FMUL+FADD 融合成 FMADD。


五、快速倒数/平方根估计(DSP 热循环利器)

复制代码
FRECPE   S0, S1        ; 1 周期给 ~8 位精度 1/S1
FRSQRTE  D0, D1        ; 1 周期给 1/sqrt(D1) 粗值
FRECPS   S0, S1, S2    ; 一次牛顿迭代精化倒数
FRSQRTS  D0, D1, D2    ; 一次牛顿迭代精化 rsqrt

单精度做 2 次 FRECPS 到 24 位尾数全精度,双精度 3 次;总延迟 ~5 周期 vs FDIV 15+,3× 加速,代价 1--2 ULP 误差。图形、物理、音频 DSP 大量用。


六、Min/Max 与 NaN:NM 变体才是日常该用的

指令 NaN 行为
FMAX / FMIN 任一操作数是 NaN → 回 NaN(毒化结果)
FMAXNM / FMINNM 跳过 quiet NaN,回另一个数(IEEE maxNum/minNum)
FMAXNMV / FMINNMV NEON/SVE 归约版,同样跳 NaN

扫数组求最大值时默认用 NM,否则一个哨兵 NaN 就把整轮结果毁了。


七、比较与无分支选择

复制代码
FCMP   D0, D1          ; 设 PSTATE.NZCV
FCMP   S0, #0.0
FCCMP  S0, S1, #nzcv, cond   ; cond 真则比,假则把立即数灌进标志
FCSEL  D0, D1, D2, eq        ; eq 真选 D1 否则 D2

NaN 检测陷阱 :任一操作数 NaN → V=1,此时 B.EQ/B.GT/B.LT 全不跳。判 NaN 用自比较:

复制代码
FCMP  S0, S0
B.VS  is_nan           ; 非 NaN 永远等于自身,V=1 ⇔ NaN

FCCMP 能把 (a>0 && b<1) 这类复合条件压成两条无分支指令。


八、舍入指令 FRINT*(不碰 FPCR 的局部舍入)

复制代码
FRINTRND / FRINTRI    ; 按当前 RMode / 按整数舍入
FRINTP  S0, S1        ; 向 +∞
FRINTM  S0, S1        ; 向 −∞
FRINTZ  S0, S1        ; 向零
FRINTA  S0, S1        ; 向最近,ties away

比 MSR FPCR 存改恢复便宜得多,适合单点取整。


九、FP16 与 BF16:ML 降精度两条路

FEAT_FP16(IEEE binary16)

  • 所有标准 FP 指令接受 Hn 操作数:FADD H0,H1,H2 / FMADD H0,H1,H2,H3
  • 同 128 位数据通路吞吐 2× FP32(8 个 FP16 vs 4 个 FP32)
  • Apple M 系列、Cortex-A76+ 均支持
  • 缺点:指数只有 5 位,>65504 就溢出,训练不稳

FEAT_BF16(bfloat16)

  • 指数 8 位(同 FP32)→ 动态范围不缩水;尾数 7 位
  • 关键指令:BFMLALB/T、BFDOT、BFMMLA
  • AWS Graviton3(Neoverse V1)首发;PyTorch torch.bfloat16 经 ACL 后端直接映射到 BFDOT/BFMMLA
  • BERT-base 推理 2.1× FP32 吞吐,GLUE 精度掉 <0.1%

选型直觉:推理激活值大、怕溢出 → BF16;纯移动端小模型、带宽极度敏感 → FP16。


十、ABI 坑:hard-float / soft-float 必须全工程一致

  • hard-float:FP 参数走 S/D 寄存器,用真实 VFP 指令(AArch64 默认)
  • soft-float:软件库模拟,参数走 X 寄存器
  • softfp:用 FPU 但参数走整数寄存器(兼容旧 ABI)

混链 hard/soft 目标文件 → 链接错或诡异运行时 bug。AArch64 没有 soft-float 执行态,但工具链仍可 -mfloat-abi=soft 走软仿真。


十一、本文核心复盘

  • V0--V31 是标量 FP / NEON / SVE 共用的 128 位文件;H/S/D 是低宽视图,写窄视图零扩展高位
  • FPCR 管舍入(RMode)、FTZ(FZ)、默认 NaN(DN)、异常陷阱使能;FPSR 记粘性异常标志
  • 改 FPCR 串行化,单点舍入用 FRINT*
  • FMADD/FMSUB 单次舍入,又快又准,编译器 -ffp-contract=fast 自动融合
  • FDIV/FSQRT 贵,热路径用 FRECPE/FRSQRTE + FRECPS/FRSQRTS 牛顿精化
  • 求 max/min 默认用 FMAXNM/FMINNM,避免 NaN 毒化
  • 判 NaN 用 FCMP S0,S0 + B.VS;FCCMP/FCSEL 做无分支复合条件
  • FP16 吞吐翻倍但范围小;BF16 保范围降尾数,是服务器 ML 推理主力
  • hard/soft-float ABI 全工程必须一致
相关推荐
不脱发的程序猿22 分钟前
STM32和VS1053制作电子琴
stm32·单片机·嵌入式硬件·midi·vs1053
Zw-awa18 小时前
C语言函数指针与宏实战:从回调、表驱动菜单到X-Macro,讲透嵌入式代码解耦
stm32
无敌贵点大王1 天前
从寄存器层面重学STM32(9)——DMA理论
stm32·单片机·嵌入式硬件
Zw-awa2 天前
用两个按键做一个OLED菜单:先把选择和执行分开
stm32
Good白10192 天前
嵌入式:1.嵌入式入门|初识嵌入式、处理器、ARM与STM32
arm开发·stm32·嵌入式硬件
小李不想当小白2 天前
HAL库之UART(STM32 HAL库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
Dev_XH2 天前
【成品设计】基于物联网的旅游生态农业互动系统
stm32·单片机·嵌入式硬件·物联网·旅游
lzj_pxxw3 天前
STM32移植FreeRTOS(Cmake+CubeMX)
vscode·stm32·嵌入式硬件
물の韜3 天前
ZYNQ/FMQL系列Linux之I2C开发实战
linux·arm开发·嵌入式硬件·设备树·iic·zynq