跳到主要内容

标准扩展指令集

RISC-V 采用模块化设计,除了基础整数指令集外,还定义了多个标准扩展。本章介绍常用的标准扩展指令集。

扩展命名规范

RISC-V 扩展使用字母命名,常用扩展包括:

扩展名称描述
I基础整数RV32I 或 RV64I,必选
M乘除法整数乘法和除法指令
A原子操作原子内存操作指令
F单精度浮点32 位单精度浮点运算
D双精度浮点64 位双精度浮点运算
C压缩指令16 位压缩指令
V向量扩展向量运算指令

组合扩展的命名示例:

  • RV32IMAC:32 位基础 + 乘除法 + 原子操作 + 压缩指令
  • RV64GC:64 位基础 + 所有通用扩展(IMAFDC)

M 扩展:整数乘除法

M 扩展添加了整数乘法、除法和取余指令。

乘法指令

MUL - 乘法(低位)

mul rd, rs1, rs2    # rd = (rs1 * rs2)[XLEN-1:0]

返回乘积的低 XLEN 位。

mul  a0, a1, a2     # a0 = (a1 * a2) 的低 32 位

MULH - 乘法(高位,有符号 × 有符号)

mulh rd, rs1, rs2   # rd = (rs1 * rs2)[2*XLEN-1:XLEN]

返回有符号数乘积的高 XLEN 位。

MULHSU - 乘法(高位,有符号 × 无符号)

mulhsu rd, rs1, rs2 # rd = (rs1 * rs2)[2*XLEN-1:XLEN]

rs1 为有符号数,rs2 为无符号数。

MULHU - 乘法(高位,无符号 × 无符号)

mulhu rd, rs1, rs2  # rd = (rs1 * rs2)[2*XLEN-1:XLEN]

两个操作数都是无符号数。

64 位乘法示例

计算两个 32 位有符号数的 64 位乘积:

mul   a0, a1, a2    # 低 32 位
mulh a1, a1, a2 # 高 32 位
# 64 位结果在 a1:a0 中

除法指令

DIV - 除法(有符号)

div rd, rs1, rs2    # rd = rs1 / rs2(有符号除法)

DIVU - 除法(无符号)

divu rd, rs1, rs2   # rd = rs1 / rs2(无符号除法)

REM - 取余(有符号)

rem rd, rs1, rs2    # rd = rs1 % rs2(有符号取余)

REMU - 取余(无符号)

remu rd, rs1, rs2   # rd = rs1 % rs2(无符号取余)

除法特殊情况

除法指令对特殊情况的处理:

情况结果
除数为 0结果为全 1(-1 或最大无符号数)
有符号除法溢出(-2^31 / -1)结果为被除数
li   a0, 10
li a1, 0
div a2, a0, a1 # a2 = 0xFFFFFFFF(除零)

W 版本指令(RV64)

在 RV64 中,M 扩展还提供了 32 位版本的指令:

mulw  rd, rs1, rs2   # 32 位乘法,结果符号扩展
divw rd, rs1, rs2 # 32 位有符号除法
divuw rd, rs1, rs2 # 32 位无符号除法
remw rd, rs1, rs2 # 32 位有符号取余
remuw rd, rs1, rs2 # 32 位无符号取余

A 扩展:原子操作

A 扩展提供了原子内存操作指令,用于多处理器同步和并发编程。

加载保留/存储条件

LR.W - 加载保留字

lr.w rd, (rs1)      # rd = Mem[rs1]; 预留 rs1 地址

加载并预留地址,用于后续的条件存储。

SC.W - 存储条件字

sc.w rd, rs2, (rs1) # if (预留有效) { Mem[rs1] = rs2; rd = 0; } else { rd = 1; }

如果预留仍然有效,存储成功,rd = 0;否则失败,rd = 1。

自旋锁实现

# 获取锁
spin_lock:
lr.w t0, (a0) # 加载锁状态
bnez t0, spin_lock # 如果锁被占用,重试
sc.w t0, a1, (a0) # 尝试获取锁
bnez t0, spin_lock # 如果失败,重试
ret

# 释放锁
spin_unlock:
sw x0, 0(a0) # 释放锁
ret

原子内存操作(AMO)

AMO 指令在单条指令中完成内存读取-修改-写入操作。

AMOSWAP.W - 原子交换

amoswap.w rd, rs2, (rs1)   # tmp = Mem[rs1]; Mem[rs1] = rs2; rd = tmp

AMOADD.W - 原子加法

amoadd.w rd, rs2, (rs1)    # tmp = Mem[rs1]; Mem[rs1] += rs2; rd = tmp

AMOAND.W / AMOOR.W / AMOXOR.W - 原子逻辑运算

amoand.w rd, rs2, (rs1)    # tmp = Mem[rs1]; Mem[rs1] &= rs2; rd = tmp
amoor.w rd, rs2, (rs1) # tmp = Mem[rs1]; Mem[rs1] |= rs2; rd = tmp
amoxor.w rd, rs2, (rs1) # tmp = Mem[rs1]; Mem[rs1] ^= rs2; rd = tmp

AMOMIN.W / AMOMAX.W - 原子最小/最大值(有符号)

amomin.w rd, rs2, (rs1)    # tmp = Mem[rs1]; Mem[rs1] = min(Mem[rs1], rs2); rd = tmp
amomax.w rd, rs2, (rs1) # tmp = Mem[rs1]; Mem[rs1] = max(Mem[rs1], rs2); rd = tmp

AMOMINU.W / AMOMAXU.W - 原子最小/最大值(无符号)

amominu.w rd, rs2, (rs1)   # 无符号最小值
amomaxu.w rd, rs2, (rs1) # 无符号最大值

原子计数器示例

# 原子递增计数器
amoadd.w t0, t1, (a0) # 计数器地址在 a0,增加值在 t1

64 位版本(RV64)

lr.d     rd, (rs1)         # 64 位加载保留
sc.d rd, rs2, (rs1) # 64 位存储条件
amoswap.d rd, rs2, (rs1) # 64 位原子交换
amoadd.d rd, rs2, (rs1) # 64 位原子加法

F 扩展:单精度浮点

F 扩展添加了 32 位单精度浮点运算支持,需要额外的 32 个浮点寄存器 f0-f31。

浮点加载和存储

FLW - 加载浮点字

flw fd, offset(rs1)   # fd = Mem[rs1 + offset](单精度浮点)

FSW - 存储浮点字

fsw fs2, offset(rs1)  # Mem[rs1 + offset] = fs2

浮点算术运算

FADD.S - 浮点加法

fadd.s fd, fs1, fs2   # fd = fs1 + fs2

FSUB.S - 浮点减法

fsub.s fd, fs1, fs2   # fd = fs1 - fs2

FMUL.S - 浮点乘法

fmul.s fd, fs1, fs2   # fd = fs1 * fs2

FDIV.S - 浮点除法

fdiv.s fd, fs1, fs2   # fd = fs1 / fs2

FSQRT.S - 浮点平方根

fsqrt.s fd, fs1       # fd = sqrt(fs1)

浮点比较

FEQ.S / FLT.S / FLE.S

feq.s rd, fs1, fs2    # rd = (fs1 == fs2) ? 1 : 0
flt.s rd, fs1, fs2 # rd = (fs1 < fs2) ? 1 : 0
fle.s rd, fs1, fs2 # rd = (fs1 <= fs2) ? 1 : 0

浮点转换

FCVT.W.S - 浮点转整数

fcvt.w.s rd, fs1      # rd = (int32_t)fs1
fcvt.wu.s rd, fs1 # rd = (uint32_t)fs1

FCVT.S.W - 整数转浮点

fcvt.s.w fd, rs1      # fd = (float)rs1
fcvt.s.wu fd, rs1 # fd = (float)(uint32_t)rs1

浮点移动

FMV.X.W / FMV.W.X

fmv.x.w rd, fs1       # rd = fs1(位模式复制,不转换)
fmv.w.x fd, rs1 # fd = rs1(位模式复制,不转换)

浮点符号注入

FSGNJ.S / FSGNJN.S / FSGNJX.S

fsgnj.s  fd, fs1, fs2  # fd = {fs2[31], fs1[30:0]}(复制符号)
fsgnjn.s fd, fs1, fs2 # fd = {~fs2[31], fs1[30:0]}(取反符号)
fsgnjx.s fd, fs1, fs2 # fd = {fs2[31]^fs1[31], fs1[30:0]}(异或符号)

常用伪指令:

fmv.s   fd, fs1        # fd = fs1,展开为 fsgnj.s fd, fs1, fs1
fneg.s fd, fs1 # fd = -fs1,展开为 fsgnjn.s fd, fs1, fs1
fabs.s fd, fs1 # fd = |fs1|,展开为 fsgnjx.s fd, fs1, fs1

D 扩展:双精度浮点

D 扩展在 F 扩展基础上添加 64 位双精度浮点支持。

浮点加载和存储

FLD / FSD

fld fd, offset(rs1)   # 加载 64 位双精度浮点
fsd fs2, offset(rs1) # 存储 64 位双精度浮点

双精度算术运算

fadd.d fd, fs1, fs2   # 双精度加法
fsub.d fd, fs1, fs2 # 双精度减法
fmul.d fd, fs1, fs2 # 双精度乘法
fdiv.d fd, fs1, fs2 # 双精度除法
fsqrt.d fd, fs1 # 双精度平方根

双精度比较

feq.d rd, fs1, fs2    # rd = (fs1 == fs2) ? 1 : 0
flt.d rd, fs1, fs2 # rd = (fs1 < fs2) ? 1 : 0
fle.d rd, fs1, fs2 # rd = (fs1 <= fs2) ? 1 : 0

精度转换

单精度与双精度转换

fcvt.s.d fd, fs1      # 双精度转单精度
fcvt.d.s fd, fs1 # 单精度转双精度

双精度与整数转换

fcvt.l.d  rd, fs1     # 双精度转 64 位整数
fcvt.lu.d rd, fs1 # 双精度转 64 位无符号整数
fcvt.d.l fd, rs1 # 64 位整数转双精度
fcvt.d.lu fd, rs1 # 64 位无符号整数转双精度

C 扩展:压缩指令

C 扩展提供 16 位压缩指令,减少代码体积。

压缩整数指令

C.LI - 压缩立即数加载

c.li rd, imm    # rd = imm(-32 到 31)

C.LUI - 压缩高位立即数加载

c.lui rd, imm   # rd = imm << 12

C.ADDI - 压缩立即数加法

c.addi rd, imm  # rd = rd + imm

C.ADDI16SP - 压缩栈指针调整

c.addi16sp sp, imm   # sp = sp + imm

C.SLLI / C.SRLI / C.SRAI - 压缩移位

c.slli rd, imm  # rd = rd << imm
c.srli rd, imm # rd = rd >> imm(逻辑)
c.srai rd, imm # rd = rd >> imm(算术)

压缩加载存储

C.LW / C.SW - 压缩字加载存储

c.lw  rd, offset(rs1)   # 加载字
c.sw rs2, offset(rs1) # 存储字

压缩分支和跳转

C.BEQZ / C.BNEZ - 压缩条件分支

c.beqz rs, label   # if (rs == 0) goto label
c.bnez rs, label # if (rs != 0) goto label

C.J / C.JAL - 压缩无条件跳转

c.j   label        # 跳转
c.jal label # 跳转并链接(RV32)

C.JR / C.JALR - 压缩寄存器跳转

c.jr   rs          # 跳转到 rs
c.jalr rs # 跳转到 rs 并链接

压缩算术指令

C.ADD / C.MV / C.SUB / C.AND / C.OR / C.XOR

c.add  rd, rs      # rd = rd + rs
c.mv rd, rs # rd = rs
c.sub rd, rs # rd = rd - rs(RV32)
c.and rd, rs # rd = rd & rs
c.or rd, rs # rd = rd | rs
c.xor rd, rs # rd = rd ^ rs

压缩浮点指令(FC 扩展)

c.flw  fd, offset(rs1)   # 压缩单精度加载
c.fsw fs2, offset(rs1) # 压缩单精度存储
c.fld fd, offset(rs1) # 压缩双精度加载
c.fsd fs2, offset(rs1) # 压缩双精度存储

V 扩展:向量运算

V 扩展提供可伸缩向量运算支持,适用于高性能计算和机器学习。

向量寄存器

V 扩展定义了 32 个向量寄存器 v0-v31,每个向量寄存器可以保存多个元素。

向量配置

VSETVLI - 设置向量长度

vsetvli rd, rs1, vtype   # 设置向量长度和类型

VSETVL - 设置向量长度(完整版)

vsetvl rd, rs1, rs2      # 设置向量长度

向量加载存储

VLE32.V / VSE32.V - 向量加载存储(32 位元素)

vle32.v vd, (rs1)        # 加载向量
vse32.v vs3, (rs1) # 存储向量

向量算术运算

VADD.VV - 向量加法(向量-向量)

vadd.vv vd, vs1, vs2     # vd[i] = vs1[i] + vs2[i]

VADD.VX - 向量加法(向量-标量)

vadd.vx vd, vs1, rs1     # vd[i] = vs1[i] + rs1

VADD.VI - 向量加法(向量-立即数)

vadd.vi vd, vs1, imm     # vd[i] = vs1[i] + imm

向量乘法

VMUL.VV - 向量乘法

vmul.vv vd, vs1, vs2     # vd[i] = vs1[i] * vs2[i]

向量点积

vle32.v   v1, (a0)       # 加载向量 1
vle32.v v2, (a1) # 加载向量 2
vmul.vv v3, v1, v2 # 逐元素乘法
vredsum.vs v4, v3, v0 # 归约求和

小结

本章介绍了 RISC-V 的标准扩展指令集:

  • M 扩展:整数乘除法指令
  • A 扩展:原子操作指令,用于多核同步
  • F 扩展:单精度浮点运算
  • D 扩展:双精度浮点运算
  • C 扩展:压缩指令,减少代码体积
  • V 扩展:向量运算,适用于高性能计算

根据应用需求选择合适的扩展组合,可以实现性能、功耗和成本的最佳平衡。下一章将介绍汇编语言编程。