标准扩展指令集
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 扩展:向量运算,适用于高性能计算
根据应用需求选择合适的扩展组合,可以实现性能、功耗和成本的最佳平衡。下一章将介绍汇编语言编程。