RISC-V:浮点规格、kernel中关于浮点配置和浮点相关流程
关注RISC-V中浮点配置寄存器、浮点指令,以及Linux内核中浮点相关编译、配置流程、测试工具等。
1 RISC-V规格书关于浮点说明
RISCV提供了多种浮点扩展,包括单精度浮点(F)、双精度浮点(D)、四倍精度浮点(Q)以及十进制浮点(L)扩展。这些扩展是可选的,可以根据应用场景的需求进行配置和使能。
1.2 机器特征寄存器 CSR_MISA
通过CSR_MISA寄存器可以查询当前处理器是否支持单精度或双精度浮点计算。如果支持其中之一,就表示当前处理器支持浮点计算。可以通过以下指令检查:
单精度浮点和双精度浮点配置:
浮点配置判断:
csrr t0, CSR_MISA andi t0, t0, (COMPAT_HWCAP_ISA_F | COMPAT_HWCAP_ISA_D) beqz t0, out
COMPAT_HWCAP_ISA_F和COMPAT_HWCAP_ISA_D分别代表单精度和双精度浮点支持的标志位。如果t0结果为0,表示不支持浮点计算,跳转到out标签处。
1.2 机器状态寄存器CSR_STATUS
如果机器硬件上支持浮点计算单元,通过CSR_STATUS寄存器可以启用或关闭浮点计算功能。其中,设置相应位就是启动浮点计算功能;清零相应位表示关闭浮点计算功能:
浮点在mstatus寄存器中配置:
FS的不同状态包括:
不同FS状态含义如下:
- Off (00):浮点单元处于关闭状态,此时访问浮点相关寄存器会触发非法指令异常。
- Initial (01):浮点单元处于初始状态。这个状态表明浮点寄存器包含初始值。
- Clean (10):浮点单元处于清洁状态。这意味着自上次修改后浮点寄存器没有变化,但与初始值不同。
- Dirty (11):浮点单元处于脏状态。这表明自上次修改后浮点寄存器有变化,需要将这些变化刷新回存储空间中。
FS字段的状态变化通常在操作系统进行上下文切换时非常重要。操作系统可以通过检查FS字段的值来快速判断是否有浮点寄存器的状态需要保存或恢复。
- Context Save:
- Dirty状态:需要保存FPU寄存器状态,并在保存后将状态切换为Clean。
- Off、Init、Clean状态:不需要保存FPU寄存器,状态保持不变。
- Context Restore:
- Off状态:无动作。
- Init状态:可以直接将0加载到FPU寄存器,无需内存加载访问。
- Clean状态:从保存的内存栈中恢复。
- Dirty状态:在Context Save时已经切换到Clean状态,因此不存在Dirty状态需要恢复。
配置浮点开关:
li t0, SR_FS
csrs CSR_STATUS, t0 # 启用浮点计算功能
li t0, SR_FS
csrc CSR_STATUS, t0 # 关闭浮点计算功能
SR_FS是CSR_STATUS寄存器中控制浮点状态的位字段。设置这个位可以启用浮点单元,清除这个位可以禁用浮点单元。
1.3 浮点控制状态寄存器 fcsr
浮点控制状态寄存器fcsr用于控制浮点操作的模式和记录异常状态。它可以通过frcsr和fscsr指令访问,这些指令是伪指令,分别用于读取和写入fcsr的值。
fcsr寄存器中的Rounding Mode用于指定浮点运算的舍入模式。
1.3.1 浮点异常处理
RISC-V架构的浮点指令在产生结果异常时不会跳转进入异常模式,而是设置fcsr寄存器中的异常标志位。这是RISC-V架构的一个显著特点,可以大幅简化处理器流水线的硬件实现。
Accrued Exception标志位详细说明如下:
- NV (Invalid Operation):无效操作异常标志位。如果浮点运算单元在运算中出现了无效操作异常,则会将`fcsr`寄存器中的`NV`标志位设置为1。无效操作包括但不限于:
- 产生非规格化的结果。
- 0乘以无穷大。
- Signaling NaN与任何值相乘。
- 负数的对数。
- 负数的平方根。
- 除以零(不包括无穷大除以无穷大)。
- DZ (Divide by Zero):被0除异常标志位。如果浮点运算单元在运算中遇到了除以零的情况,则会将`fcsr`寄存器中的`DZ`标志位设置为1。
- OF (Overflow):上溢异常标志位。如果浮点运算结果太大,超出了浮点数能表示的范围,则会将`fcsr`寄存器中的`OF`标志位设置为1。
- UF (Underflow):下溢异常标志位。如果浮点运算结果太小,低于浮点数能表示的最小非零值,则会将`fcsr`寄存器中的`UF`标志位设置为1。
- NX (Inexact):不精确异常标志位。如果浮点运算结果需要被舍入,即实际结果与表示结果存在差异,则会将`fcsr`寄存器中的`NX`标志位设置为1。
这些异常标志位是累积的,意味着一旦设置,除非软件显式地将其清零,否则它们会一直保持设置状态。软件可以通过写0的方式单独清除`fcsr`寄存器中的某个异常标志位。这种设计允许软件灵活地处理浮点异常,而不是在每次异常发生时都触发异常处理流程。
1.4 单精度浮点和双精度浮点寄存器和指令
浮点扩展规格说明在《The RISC-V Instruction Set Manual Volume I: Unprivileged ISA》的Chapter 11和Chapter 12。
1.4.1 浮点寄存器
如果包含F(单精度浮点)或D(双精度浮点)扩展,则还有32个独立的浮点寄存器(f0-f31)。
F扩展每个寄存器32位,D扩展每个寄存器64位。
如果处理器同时支持RV32F和RV32D扩展,则单精度数据仅使用f寄存器中的低32位。
控制寄存器:RISCV还有一个浮点控制和状态寄存器fcsr,用于记录或配置浮点运算的状态。
1.4.2 浮点指令
RISCV中单精度和双精度浮点指令的分类及使用方法:
1. 加载/存储指令
FLW (Floatingpoint Load Word):从内存中加载单精度浮点数到浮点寄存器。
flw rd, offset(rs1)
将x[rs1] + sext(offset)处的单精度浮点数加载到f[rd]中。
FLD (Floatingpoint Load Doubleword):从内存中加载双精度浮点数到浮点寄存器。
fld rd, offset(rs1)
将x[rs1] + sext(offset)处的双精度浮点数加载到f[rd]中。
FSW (Floatingpoint Store Word):将单精度浮点数从浮点寄存器存储到内存。
fsw rs2, offset(rs1)
将f[rs2]中的单精度浮点数存储到x[rs1] + sext(offset)处。
FSD (Floatingpoint Store Doubleword):将双精度浮点数从浮点寄存器存储到内存。
fsd rs2, offset(rs1)
将f[rs2]中的双精度浮点数存储到x[rs1] + sext(offset)处。
2. 标准算数指令
FADD.S/D:执行单精度或双精度浮点加法。
fadd.s rd, rs1, rs2
fadd.d rd, rs1, rs2
将f[rs1]与f[rs2]相加,结果存入f[rd]。
FSUB.S/D:执行单精度或双精度浮点减法。
fsub.s rd, rs1, rs2
fsub.d rd, rs1, rs2
将f[rs1]与f[rs2]相减,结果存入f[rd]。
FMUL.S/D:执行单精度或双精度浮点乘法。
fmul.s rd, rs1, rs2
fmul.d rd, rs1, rs2
将f[rs1]与f[rs2]相乘,结果存入f[rd]。
FDIV.S/D:执行单精度或双精度浮点除法。
fdiv.s rd, rs1, rs2
fdiv.d rd, rs1, rs2
将f[rs1]除以f[rs2],结果存入f[rd]。
FSQRT.S/D:执行单精度或双精度浮点平方根。
fsqrt.s rd, rs1
fsqrt.d rd, rs1
计算f[rs1]的平方根,结果存入f[rd]。
3. R4指令(多算数操作指令)
FMADD.S/D:执行单精度或双精度浮点乘加。
fmadd.s rd, rs1, rs2, rs3
fmadd.d rd, rs1, rs2, rs3
将f[rs1]与f[rs2]相乘,并将结果与f[rs3]相加,结果存入f[rd]。
FMSUB.S/D:执行单精度或双精度浮点乘减。
fmsub.s rd, rs1, rs2, rs3
fmsub.d rd, rs1, rs2, rs3
将f[rs1]与f[rs2]相乘,并将结果与f[rs3]相减,结果存入f[rd]。
4. 最大值最小值指令
FMIN.S/D:执行单精度或双精度浮点最小值。
fmin.s rd, rs1, rs2
fmin.d rd, rs1, rs2
取f[rs1]与f[rs2]中的较小值,结果存入f[rd]。
FMAX.S/D:执行单精度或双精度浮点最大值。
fmax.s rd, rs1, rs2
fmax.d rd, rs1, rs2
取f[rs1]与f[rs2]中的最大值,结果存入f[rd]。
5. 比较指令
FEQ.S/D:执行单精度或双精度浮点相等比较。
feq.s rd, rs1, rs2
feq.d rd, rs1, rs2
如果f[rs1]等于f[rs2],则在x[rd]中写入1,否则写入0。
FLT.S/D:执行单精度或双精度浮点小于比较。
flt.s rd, rs1, rs2
flt.d rd, rs1, rs2
如果f[rs1]小于f[rs2],则在x[rd]中写入1,否则写入0。
FLE.S/D:执行单精度或双精度浮点小于等于比较。
fle.s rd, rs1, rs2
fle.d rd, rs1, rs2
如果f[rs1]小于等于f[rs2],则在x[rd]中写入1,否则写入0。
6. 分类指令
FCLASS.S/D:执行单精度或双精度浮点分类。
fclass.s rd, rs1
fclass.d rd, rs1
将表示f[rs1]中浮点数类别的掩码写入x[rd]中。
7. 浮点注入指令
FSGNJ.S/D:执行单精度或双精度浮点符号注入。
fsgnj.s rd, rs1, rs2
fsgnj.d rd, rs1, rs2
使用f[rs1]的值和f[rs2]的符号位构造新的浮点数,结果存入f[rd]。
8. 浮点数与整数之间的转换指令
FCVT.S.W (Floatingpoint Convert to Single from Word):将32位整数转换为单精度浮点数。
fcvt.s.w rd, rs1
将寄存器x[rs1]中的32位二进制补码整数转换为单精度浮点数,结果写入f[rd]中。
FCVT.S.WU (Floatingpoint Convert to Single from Unsigned Word):将32位无符号整数转换为单精度浮点数。
fcvt.s.wu rd, rs1
将寄存器x[rs1]中的32位无符号整数转换为单精度浮点数,结果写入f[rd]中。
FCVT.S.L (Floatingpoint Convert to Single from Long):将64位整数转换为单精度浮点数。
fcvt.s.l rd, rs1
将寄存器x[rs1]中的64位二进制补码整数转换为单精度浮点数,结果写入f[rd]中。
FCVT.S.LU (Floatingpoint Convert to Single from Unsigned Long):将64位无符号整数转换为单精度浮点数。
fcvt.s.lu rd, rs1
将寄存器x[rs1]中的64位无符号整数转换为单精度浮点数,结果写入f[rd]中。
FCVT.W.S (Floatingpoint Convert to Word from Single):将单精度浮点数转换为32位整数。
fcvt.w.s rd, rs1
将寄存器f[rs1]中的单精度浮点数转换为32位二进制补码整数,结果写入x[rd]中。
FCVT.WU.S (Floatingpoint Convert to Unsigned Word from Single):将单精度浮点数转换为32位无符号整数。
fcvt.wu.s rd, rs1
将寄存器f[rs1]中的单精度浮点数转换为32位无符号整数,结果写入x[rd]中。
FCVT.W.D (Floatingpoint Convert to Word from Double):将双精度浮点数转换为32位整数。
fcvt.w.d rd, rs1
将寄存器f[rs1]中的双精度浮点数转换为32位二进制补码整数,结果写入x[rd]中。
FCVT.WU.D (Floatingpoint Convert to Unsigned Word from Double):将双精度浮点数转换为32位无符号整数。
fcvt.wu.d rd, rs1
将寄存器f[rs1]中的双精度浮点数转换为32位无符号整数,结果写入x[rd]中。
9. 单精度与双精度浮点数之间的转换指令
FCVT.S.D (Floatingpoint Convert to Single from Double):将双精度浮点数转换为单精度浮点数。
fcvt.s.d rd, rs1
将寄存器f[rs1]中的双精度浮点数转换为单精度浮点数,结果写入f[rd]中。
FCVT.D.S (Floatingpoint Convert to Double from Single):将单精度浮点数转换为双精度浮点数。
fcvt.d.s rd, rs1
将寄存器f[rs1]中的单精度浮点数转换为双精度浮点数,结果写入f[rd]中。
2 内核关于浮点处理
2.1 内核关于浮点的mabi和march
在arch/riscv/Makefile中定义了RISC-V的-mabi和-march选项:
ifeq ($(CONFIG_ARCH_RV64I),y) BITS := 64 UTS_MACHINE := riscv64 KBUILD_CFLAGS += -mabi=lp64 KBUILD_AFLAGS += -mabi=lp64 KBUILD_LDFLAGS += -melf64lriscv KBUILD_RUSTFLAGS += -Ctarget-cpu=generic-rv64 --target=riscv64imac-unknown-none-elf \ -Cno-redzone else BITS := 32 UTS_MACHINE := riscv32 KBUILD_CFLAGS += -mabi=ilp32 KBUILD_AFLAGS += -mabi=ilp32--内核编译和链接使用的是ilp32不是ilp32d。 KBUILD_LDFLAGS += -melf32lriscv endif ... # ISA string setting riscv-march-$(CONFIG_ARCH_RV32I) := rv32ima riscv-march-$(CONFIG_ARCH_RV64I) := rv64ima riscv-march-$(CONFIG_FPU) := $(riscv-march-y)fd riscv-march-$(CONFIG_RISCV_ISA_C) := $(riscv-march-y)c riscv-march-$(CONFIG_RISCV_ISA_V) := $(riscv-march-y)v
2.2 内核启动过程中浮点配置
内核在初始化时,是关闭浮点功能的:
_start_kernel li t0, SR_FS csrc CSR_STATUS, t0--清除STATUS中FS位,表示关闭浮点功能。 secondary_start_sbi li t0, SR_FS csrc CSR_STATUS, t0--关闭浮点功能。
2.3 上下文中保存浮点寄存器
compat_setup_sigcontext -->compat_save_fp_state copy_process --> dup_task_struct setup_sigcontext --> save_fp_state switch_to --> __switch_to_aux fstate_save--如果SR_FS为SR_FS_DIRTY,则保存上下文。 __fstate_save li t1, SR_FS csrs CSR_STATUS, t1--打开FP功能。 ...--保存fcsr、f0-f31寄存器。 csrc CSR_STATUS, t1--关闭FP功能。 compat_restore_sigcontext --> compat_restore_fp_state start_thread restore_sigcontext --> restore_fp_state swtich_to --> __switch_to_aux fstate_restore--如果SR_FS不为SR_FS_OFF,则恢复上下文。 __fsatate_restore li t1, SR_FS csrs CSR_STATUS, t1--打开FP功能。 ...--恢复f0-f31、fcsr寄存器。 csrc CSR_STATUS, t1 __fstate_clean--设置STATUS寄存器为SR_FS_CLEAN。 flush_thread fstate_off--设置STATUS状态为SR_FS_OFF。
2.4 异常处理中浮点配置
内核中禁止使用浮点功能:
handle_exception _save_context li t0, SR_SUM | SR_FS csrrc s1, CSR_STATUS, t0--读取STATUS值到s1中,然后关闭STATUS中FS和SUM位。关闭FPU可以检测到内核中非法使用浮点功能。
3 浮点测试工具