RISC-V F 扩展:单精度浮点寄存器、CSR 与指令
RISC-V F 扩展定义符合 IEEE 754-2008 的 32 位单精度浮点运算。它增加 32 个独立的浮点寄存器、浮点控制状态 CSR、单精度访存、算术、融合乘加、格式转换、比较和分类指令。F 依赖 Zicsr,因为软件需要通过 CSR 读取舍入模式和累积异常标志。
只实现 F 时,浮点寄存器宽度 FLEN=32。若处理器同时实现 D 或 Q,同一组 f0–f31 会扩宽到 64 或 128 位,单精度值按 NaN-boxing 规则保存于较宽寄存器中。本文先说明 FLEN=32 的 F 基础行为,再指出与较宽扩展及 ABI 相关的差异。
32 个浮点寄存器
浮点寄存器文件与整数寄存器文件相互独立。浮点算术读写 f 寄存器;地址计算、比较结果和浮点/整数转换读写 x 寄存器。FMV.X.W 与 FMV.W.X 用于原样传递 32 位位模式,FCVT 则执行数值转换。
| 物理寄存器 | ABI 名称 | 标准用途 | 硬浮点 ABI 下跨调用保留 | 详细说明 |
|---|---|---|---|---|
f0 | ft0 | 浮点临时寄存器 0 | 否 | 调用者保存。 |
f1 | ft1 | 浮点临时寄存器 1 | 否 | 调用者保存。 |
f2 | ft2 | 浮点临时寄存器 2 | 否 | 调用者保存。 |
f3 | ft3 | 浮点临时寄存器 3 | 否 | 调用者保存。 |
f4 | ft4 | 浮点临时寄存器 4 | 否 | 调用者保存。 |
f5 | ft5 | 浮点临时寄存器 5 | 否 | 调用者保存。 |
f6 | ft6 | 浮点临时寄存器 6 | 否 | 调用者保存。 |
f7 | ft7 | 浮点临时寄存器 7 | 否 | 调用者保存。 |
f8 | fs0 | 浮点保存寄存器 0 | 是* | 被调用者保存。 |
f9 | fs1 | 浮点保存寄存器 1 | 是* | 被调用者保存。 |
f10 | fa0 | 浮点参数 0 / 返回值 0 | 否 | 第一个浮点参数和第一个浮点返回值。 |
f11 | fa1 | 浮点参数 1 / 返回值 1 | 否 | 第二个浮点参数和第二个浮点返回值。 |
f12 | fa2 | 浮点参数 2 | 否 | 第三个浮点参数。 |
f13 | fa3 | 浮点参数 3 | 否 | 第四个浮点参数。 |
f14 | fa4 | 浮点参数 4 | 否 | 第五个浮点参数。 |
f15 | fa5 | 浮点参数 5 | 否 | 第六个浮点参数。 |
f16 | fa6 | 浮点参数 6 | 否 | 第七个浮点参数。 |
f17 | fa7 | 浮点参数 7 | 否 | 第八个浮点参数。 |
f18 | fs2 | 浮点保存寄存器 2 | 是* | 被调用者保存。 |
f19 | fs3 | 浮点保存寄存器 3 | 是* | 被调用者保存。 |
f20 | fs4 | 浮点保存寄存器 4 | 是* | 被调用者保存。 |
f21 | fs5 | 浮点保存寄存器 5 | 是* | 被调用者保存。 |
f22 | fs6 | 浮点保存寄存器 6 | 是* | 被调用者保存。 |
f23 | fs7 | 浮点保存寄存器 7 | 是* | 被调用者保存。 |
f24 | fs8 | 浮点保存寄存器 8 | 是* | 被调用者保存。 |
f25 | fs9 | 浮点保存寄存器 9 | 是* | 被调用者保存。 |
f26 | fs10 | 浮点保存寄存器 10 | 是* | 被调用者保存。 |
f27 | fs11 | 浮点保存寄存器 11 | 是* | 被调用者保存。 |
f28 | ft8 | 浮点临时寄存器 8 | 否 | 调用者保存。 |
f29 | ft9 | 浮点临时寄存器 9 | 否 | 调用者保存。 |
f30 | ft10 | 浮点临时寄存器 10 | 否 | 调用者保存。 |
f31 | ft11 | 浮点临时寄存器 11 | 否 | 调用者保存。 |
星号表示只要求保存宽度不超过目标 ABI ABI_FLEN 的值。以 ILP32F 或 LP64F 为例,ABI_FLEN=32,fs0–fs11 中的 32 位单精度值必须保留。若程序采用纯整数调用约定,即使硬件实现 F,浮点寄存器也可全部视为临时寄存器。
硬浮点调用约定提供八个浮点参数寄存器 fa0–fa7,其中 fa0、fa1 兼作返回值。可变参数仍按整数调用约定传递。一个聚合对象是否拆分到浮点和整数参数寄存器,取决于其字段组成、字段宽度和可用参数寄存器,具体规则由 psABI 定义。
浮点 CSR 的完整表格
F 扩展使用三个 U 级可读写 CSR 地址。它们不是三份独立状态:fflags 与 frm 是 fcsr 低八位的独立访问视图。
| 地址 | CSR | 权限 | 对应 fcsr 位 | 作用 |
|---|---|---|---|---|
0x001 | fflags | URW | [4:0] | 累积浮点异常标志。软件显式清零前持续保留已经发生的异常。 |
0x002 | frm | URW | [7:5] | 动态舍入模式。指令 rm=111 时读取该字段。 |
0x003 | fcsr | URW | [7:0] | frm 与 fflags 的组合视图;[31:8] 保留给其他标准扩展。 |
fcsr 固定定义为 32 位,即使 XLEN=64 也不扩宽。未由其他扩展定义的 [31:8] 写入应被忽略并读为 0;通用软件执行读改写时仍应保留这些位,以兼容后续扩展。
fcsr 位字段
| 位 | 字段 | 复位与保存规则 | 说明 |
|---|---|---|---|
[0] | NX | 累积 | Inexact,舍入结果与无限精度结果不同。 |
[1] | UF | 累积 | Underflow,按舍入后 tininess 规则检测下溢。 |
[2] | OF | 累积 | Overflow,有限结果超出目标格式范围。 |
[3] | DZ | 累积 | Divide by Zero,有限非零数除以零。 |
[4] | NV | 累积 | Invalid Operation,例如 0/0、负数开平方或信号 NaN 参与特定操作。 |
[7:5] | frm | 软件状态 | 动态舍入模式。 |
[31:8] | 保留 | 读改写保留 | 当前 F 扩展未使用。 |
异常标志为粘滞状态。一次运算可同时置多个标志;后续无异常运算不会清零。基础 F 扩展不会因 fflags 置位自动产生 trap,软件需要在适当位置读取和清除。
舍入模式编码
rm/frm 编码 | 助记 | 舍入方式 | 作为指令 rm | 作为 frm |
|---|---|---|---|---|
000 | RNE | 最近值,正好居中时取偶数 | 有效 | 有效 |
001 | RTZ | 向零 | 有效 | 有效 |
010 | RDN | 向负无穷 | 有效 | 有效 |
011 | RUP | 向正无穷 | 有效 | 有效 |
100 | RMM | 最近值,正好居中时取绝对值较大者 | 有效 | 有效 |
101 | — | 保留 | 保留 | 保留 |
110 | — | 保留 | 保留 | 保留 |
111 | DYN | 使用 frm | 有效 | 保留 |
执行依赖舍入模式的指令时,如果静态 rm 或动态 frm 使用保留编码,行为属于规范保留范围。写 frm 往往要求处理器串行化部分浮点流水线,因此频繁切换模式时,静态 rm 可减少控制状态相关停顿。
CSR 访问伪指令
| 伪指令 | 基本效果 | 对应 CSR |
|---|---|---|
frcsr rd | 读取完整 fcsr 到整数寄存器 | fcsr |
fscsr rd, rs1 | 交换整数寄存器与完整 fcsr | fcsr |
frrm rd | 读取 frm | frm |
fsrm rd, rs1 | 交换 frm | frm |
frflags rd | 读取 fflags | fflags |
fsflags rd, rs1 | 交换 fflags | fflags |
这些伪指令最终展开为 CSRRW、CSRRS 等 Zicsr 指令。例如,可在一段需要独立统计异常的运算前清除 fflags,运算后再读取:
csrwi fflags, 0 # fflags = 0
fdiv.s fa0, fa1, fa2
csrr a0, fflags # a0[4:0] = 本次及其后续浮点指令累积的标志
F 扩展指令全集
访存、算术和融合乘加
| 指令 | 操作 | 舍入模式 | 结果位置 |
|---|---|---|---|
flw fd, offset(rs1) | 从 x[rs1]+signext(offset) 读取 32 位位模式 | 不适用 | f[fd] |
fsw fs2, offset(rs1) | 将 f[fs2] 的低 32 位写入内存 | 不适用 | 内存 |
fadd.s fd, fs1, fs2 | fs1 + fs2 | rm | f[fd] |
fsub.s fd, fs1, fs2 | fs1 - fs2 | rm | f[fd] |
fmul.s fd, fs1, fs2 | fs1 × fs2 | rm | f[fd] |
fdiv.s fd, fs1, fs2 | fs1 ÷ fs2 | rm | f[fd] |
fsqrt.s fd, fs1 | sqrt(fs1) | rm | f[fd] |
fmadd.s fd, fs1, fs2, fs3 | (fs1 × fs2) + fs3,乘加整体只舍入一次 | rm | f[fd] |
fmsub.s fd, fs1, fs2, fs3 | (fs1 × fs2) - fs3,整体只舍入一次 | rm | f[fd] |
fnmsub.s fd, fs1, fs2, fs3 | -(fs1 × fs2) + fs3,整体只舍入一次 | rm | f[fd] |
fnmadd.s fd, fs1, fs2, fs3 | -(fs1 × fs2) - fs3,整体只舍入一次 | rm | f[fd] |
fmin.s fd, fs1, fs2 | 选择较小数;-0.0 < +0.0 | 不使用 | f[fd] |
fmax.s fd, fs1, fs2 | 选择较大数;-0.0 < +0.0 | 不使用 | f[fd] |
flw 与 fsw 原样传输 32 位,不规范化 NaN,因此 NaN payload 会保留。只有自然对齐地址保证原子访问;非对齐访问由执行环境决定是透明处理还是产生 trap。
四条融合乘加使用三个源寄存器和一个目的寄存器。中间乘积不单独舍入,因此结果可能与先执行 fmul.s 再执行 fadd.s 不同。∞ × 0 即使与 quiet NaN 相加,也必须置 NV。
符号注入与位模式移动
| 指令 | 位级行为 | 是否设置 fflags | 常见伪指令 |
|---|---|---|---|
fsgnj.s fd, fs1, fs2 | 数值位来自 fs1,符号位来自 fs2 | 否 | fmv.s fd, fs1 在两个源相同时成立 |
fsgnjn.s fd, fs1, fs2 | 数值位来自 fs1,符号位为 fs2 符号取反 | 否 | fneg.s fd, fs1 |
fsgnjx.s fd, fs1, fs2 | 数值位来自 fs1,符号位为两个源符号异或 | 否 | fabs.s fd, fs1 |
fmv.x.w rd, fs1 | 将 32 位浮点位模式复制到整数寄存器低 32 位 | 否 | 无数值转换 |
fmv.w.x fd, rs1 | 将整数寄存器低 32 位复制到浮点寄存器 | 否 | 无数值转换 |
RV64 执行 fmv.x.w 时,整数目的寄存器高 32 位用浮点源的符号位填充。两条 FMV 均不解释数值,也不改变非规范 NaN 的 payload。
浮点与整数转换
| 指令 | 源类型 | 目的类型 | RV32/RV64 | 说明 |
|---|---|---|---|---|
fcvt.w.s rd, fs1, rm | FP32 | 有符号 32 位整数 | RV32/RV64 | RV64 将 32 位结果符号扩展到 XLEN。 |
fcvt.wu.s rd, fs1, rm | FP32 | 无符号 32 位整数 | RV32/RV64 | RV64 仍按规范将 32 位结果符号扩展到 XLEN。 |
fcvt.l.s rd, fs1, rm | FP32 | 有符号 64 位整数 | 仅 RV64 | 超出范围时按规定值截取并置 NV。 |
fcvt.lu.s rd, fs1, rm | FP32 | 无符号 64 位整数 | 仅 RV64 | 超出范围时按规定值截取并置 NV。 |
fcvt.s.w fd, rs1, rm | 有符号 32 位整数 | FP32 | RV32/RV64 | 按 rm 舍入。 |
fcvt.s.wu fd, rs1, rm | 无符号 32 位整数 | FP32 | RV32/RV64 | 按 rm 舍入。 |
fcvt.s.l fd, rs1, rm | 有符号 64 位整数 | FP32 | 仅 RV64 | 按 rm 舍入。 |
fcvt.s.lu fd, rs1, rm | 无符号 64 位整数 | FP32 | 仅 RV64 | 按 rm 舍入。 |
浮点转整数时,先按 rm 舍入,再检查目的类型范围。结果不可表示时置 NV,并输出规范规定的端点值;此时不再同时置 NX。结果可表示但发生舍入时置 NX。
| 输入情况 | fcvt.w.s | fcvt.wu.s | fcvt.l.s | fcvt.lu.s |
|---|---|---|---|---|
| 最小有效值 | -2^31 | 0 | -2^63 | 0 |
| 最大有效值 | 2^31-1 | 2^32-1 | 2^63-1 | 2^64-1 |
负向越界或 -∞ | -2^31 | 0 | -2^63 | 0 |
| 正向越界 | 2^31-1 | 2^32-1 | 2^63-1 | 2^64-1 |
+∞ 或 NaN | 2^31-1 | 2^32-1 | 2^63-1 | 2^64-1 |
比较指令
| 指令 | 条件 | 整数结果 | NaN 与 NV |
|---|---|---|---|
feq.s rd, fs1, fs2 | fs1 == fs2 | 条件成立写 1,否则写 0 | 任一源为 NaN 时结果为 0;仅 signaling NaN 置 NV。 |
flt.s rd, fs1, fs2 | fs1 < fs2 | 条件成立写 1,否则写 0 | 任一源为 NaN 时结果为 0,并置 NV。 |
fle.s rd, fs1, fs2 | fs1 ≤ fs2 | 条件成立写 1,否则写 0 | 任一源为 NaN 时结果为 0,并置 NV。 |
比较结果直接写入整数寄存器,因此整数分支可以紧随其后执行。FLT.S 与 FLE.S 是 signaling comparison,FEQ.S 是 quiet comparison。
fclass.s 分类结果
fclass.s rd, fs1 不执行数值比较,而是在整数寄存器中返回 10 位 one-hot 分类结果。它不设置浮点异常标志。
rd 位 | 浮点类别 |
|---|---|
| 0 | 负无穷 -∞ |
| 1 | 负正规数 |
| 2 | 负次正规数 |
| 3 | 负零 -0 |
| 4 | 正零 +0 |
| 5 | 正次正规数 |
| 6 | 正正规数 |
| 7 | 正无穷 +∞ |
| 8 | signaling NaN |
| 9 | quiet NaN |
NaN、次正规数和异常结果
除各指令另有规定外,浮点运算产生 NaN 时返回规范 NaN。FP32 规范 NaN 的位模式为 0x7fc00000:符号为正,指数全 1,quiet bit 为 1,其余尾数位为 0。F 扩展允许实现提供非标准 NaN payload 传播模式,但规范 NaN 行为必须可用并作为标准软件可依赖的行为。
FLW、FSW、FMV 和 FSGNJ* 属于位模式传输或符号操作,不把输入 NaN 改成规范 NaN。普通算术、比较、最小/最大值及格式转换按各自规则处理 quiet NaN 与 signaling NaN。
F 扩展要求按 IEEE 754 处理次正规数,不允许默认 flush-to-zero。tininess 在舍入后检测。下溢通常与非精确同时发生,但是否置 UF 仍由 IEEE 754 的下溢条件决定。
mstatus.FS 与上下文切换
特权架构在 mstatus[14:13] 定义 FS,sstatus 提供相同状态的受限视图。它记录浮点上下文是否启用以及是否可能被修改。
FS | 状态 | 执行含义 |
|---|---|---|
00 | Off | 执行浮点指令或访问浮点 CSR 产生非法指令异常。 |
01 | Initial | 浮点状态已启用,可按初始状态处理。 |
10 | Clean | 自上次保存后未发现浮点状态修改。 |
11 | Dirty | f0–f31 或 fcsr 可能已修改,上下文切换需要保存。 |
当 FS=Dirty 时,mstatus.SD 也为 1。操作系统可先读取 SD/FS,仅在需要时保存 32 个浮点寄存器和 fcsr。实现允许较保守地把 Initial 或 Clean 提前改为 Dirty,因此软件可以用 Dirty 判断“必须保存”,不能用 Clean 证明任务从未执行过浮点指令。
fcsr 属于线程状态。任务切换、信号处理、调试器和虚拟机监控程序需要与浮点寄存器一起保存和恢复,否则动态舍入模式或异常标志会在不同线程之间相互影响。
RTL 数据通路与控制要求
- 浮点寄存器文件与整数寄存器文件分离后,
FCVT、FMV和比较指令需要跨寄存器文件传输数据或写回结果。 FMADD.S等 R4 型指令有三个浮点源操作数,寄存器文件端口、操作数旁路和相关检测需要覆盖第三源。fcsr写入与浮点流水线中尚未退休的指令存在顺序要求;动态舍入模式修改不能越过依赖旧模式的运算。fflags是多个异常位的按位累积结果。CSR 软件写、浮点指令置位、流水线清除和 trap 重放同时出现时,必须保证已退休指令的标志不丢失,未退休指令不产生可见更新。FMV、FLW/FSW与FSGNJ*必须保留位模式;算术单元内部即使使用重编码格式,也要在架构写回时满足标准编码。- FP32 加减、乘法、除法、平方根和 FMA 的特殊值处理不同,不能由统一的“NaN/无穷”旁路覆盖全部情况。
- 只有自然对齐的
FLW/FSW保证原子性;非对齐策略由执行环境规定,异常必须保持精确。 FS状态更新属于架构状态提交的一部分。被清除的指令、推测执行或重放失败不能错误地使软件观察到浮点结果或异常标志。
参考:RISC-V F 2.2 规范、RISC-V psABI 浮点寄存器约定、Machine-Level ISA 的 FS 状态。