RISC-V指令集1
1. RISC-V指令集概述
RISC-V指令集采用模块化,增量化设计理念
1.1 如何查看一个系统支持的指令集
cat /proc/cpuinfo
processor: 0
hart: 0
isa: rv64imafdcsu
mmu: sv48
2. 指令集扩展
从isa可知该系统支持的扩展指令集为 rv64imafdcsu:
- 64位整型最小指令集合 (I)
- 整型乘法和除法扩展指令 (M)
- 原子操作 (A)
- 单精度和双精度浮点数扩展指令 (F, D)
- 压缩扩展指令 ©
- 特权模式 (S)
- 用户模式 (U)
3. 指令编码
RISC-V指令集采用模块化设计,支持多种指令编码方式:
3.1 编码方式对比
变长指令编码 (Variable-length Instruction Encoding):
- 指令编码长度不固定
- 空间不受限制
- 芯片和编译器设计复杂
定长指令编码 (Fixed-length Instruction Encoding):
- 指令编码长度固定
- 芯片和编译器设计简单
- 缺点:指令编码空间固定,受限
3.2 RISC-V指令编码特点
- RISC-V CPU大部分采用32/16位固定编码
- 更宽的指令编码的缺点:
- Code size变大
- I cache命中率会下降
- RISC-V规范支持变长指令编码
3.3 指令长度编码格式
RISC-V支持多种指令长度,通过特定的位模式进行识别:
xxxxxxxxxxxxxxaa: 16位 (aa ≠ 11)
xxxxxxxxxxxxxxxx xxxxxxxxxxxbbb11: 32位 (bbb ≠ 111)
...xxxx xxxxxxxxxxxxxxxx xxxxxxxxxx011111: 48位
...xxxx xxxxxxxxxxxxxxxx xxxxxxxxx0111111: 64位
...xxxx xxxxxxxxxxxxxxxx xnnnxxxxx1111111: (80+16*nnn)位 (nnn ≠ 111)
...xxxx xxxxxxxxxxxxxxxx x111xxxxx1111111: 保留用于≥192位
base+4 base+2 base
3.4 RISC-V指令编码格式详解
RISC-V指令编码格式是其模块化设计的重要体现。
3.4.1 指令宽度
- 标准指令宽度: 32位。
- 压缩指令宽度: 16位 (用于减小代码体积)。
- 未来支持: 规范也支持64位指令,但目前尚未广泛应用。
3.4.2 指令编码目的
指令编码的目的是让CPU能够理解并执行特定的操作。
例如,让CPU执行加法运算:
add x3, x1, x2 // 表示 x1 + x2 = x3
3.4.3 指令Layout的关键元素
RISC-V指令的布局(layout)通常包含以下关键元素:
- 指令分类 Opcode: 用于指示指令的基本操作类型。
- 源操作数寄存器1 (rs1): 第一个源操作数的寄存器地址。
- 源操作数寄存器2 (rs2): 第二个源操作数的寄存器地址。
- 目标寄存器 (rd): 存储运算结果的寄存器地址。
- 立即数 (Immediate): 指令中直接包含的常量值。
3.4.4 寄存器与指令分类的位分配
- RISC-V架构支持32个通用寄存器。
- 索引这32个寄存器需要5位(因为 2^5 = 32)。
- 对于一个典型的三操作数指令(如
add x3, x1, x2),需要三个寄存器字段:源1 (rs1)、源2 (rs2) 和目标 (rd)。 - 这三个字段共占用 5 + 5 + 5 = 15 位。
- 在32位指令宽度中,剩余的 32 - 15 = 17 位可以用于指令分类(opcode)和其他功能码。
3.4.5 指令集设计考量
设计一套全新的指令集,其核心在于构思指令的布局(instruction layout),确保CPU和汇编器都能遵循这套统一的布局。
设计难点:
指令集设计是一门艺术,需要平衡芯片设计、编译器优化和操作系统支持等多个方面,以实现高效的整体性能。
3.5 RISC-V指令布局类型
RISC-V指令根据其布局特征,可以分为6种基本类型:
3.5.1 指令类型说明
| 类型 | 名称 | 用途 | 说明 |
|---|---|---|---|
| R类型 | Register-to-Register | 寄存器与寄存器算术指令 | 如:add x1, x2, x3 |
| I类型 | Register-to-Immediate | 寄存器与立即数算术指令或加载指令 | 如:addi x1, x2, 100 |
| S类型 | Store | 存储指令 | 如:sw x1, 8(x2) |
| B类型 | Branch | 条件跳转指令 | 如:beq x1, x2, label |
| U类型 | Upper Immediate | 长立即数操作指令 | 如:lui x1, 0x12345 |
| J类型 | Jump | 无条件跳转指令 | 如:jal x1, label |
3.5.2 指令布局字段说明
| 字段名 | 位范围 | 用途 | 说明 |
|---|---|---|---|
| opcode | [6:0] | 操作码字段 | 用于指令的分类 |
| funct3 | [14:12] | 功能码字段 | 与操作码字段结合定义指令操作功能 |
| funct7 | [31:25] | 功能码字段 | 与操作码字段结合定义指令操作功能 |
| rd | [11:7] | 目标寄存器编号 | 存储运算结果的寄存器 |
| rs1 | [19:15] | 源操作寄存器1编号 | 第一个源操作数寄存器 |
| rs2 | [24:20] | 源操作寄存器2编号 | 第二个源操作数寄存器 |
| imm | 可变 | 立即数 | 带符号扩展的立即数值 |
3.5.3 32位指令布局图

指令编码整齐,rs1,rs2,rd都在固定位置,便于RTL实现,减少选择器。
立即数均是有符号数,加载到寄存器中需要进行符号扩展。
- 例如12位有符号数0x823,对应的二进制是
0b1000 0010 0011,最高位为1代表它是一个负数 - 计算过程:取反加1得到
0b0111 1101 1101= 2013,所以0x823 = -2013
RV64指令集的数据和地址宽度为64位,为什么指令编码只有32位?
- 基于寄存器加载和存储的架构设计,所有的数据处理都是在通用寄存器中完成的
- 使用5bit可以索引32个通用寄存器
- 使用32位指令编码比64位指令编码有利于提高代码密度,更能提高指令cache命中概率。
RISC-V约定下面两种情况为非法指令:
- 低16位全是0,有可能跳转到刚初始化的内存区域
- 32位全为1,可能跳转到没有编程的存储器设备,断开的内存总线,出错的设备等
- 方便程序员提前捕捉错误
3.6 RISC-V汇编指令书写说明
RV64指令集中常用的符号说明如下:
3.6.1 寄存器符号
- rd: 表示目标寄存器,可以从x0~x31通用寄存器中选择。
- rs1: 表示源寄存器1,可以从x0~x31通用寄存器中选择。
- rs2: 表示源寄存器2,可以从x0~x31通用寄存器中选择。
3.6.2 寻址模式符号
- (): 通常用来表示寻址模式。
- (a0) 表示以a0寄存器的值为基地址进行寻址。
- 这个前面还可以加offset,表示偏移,例如8(a0),表示以a0寄存器的值为基地址,然后偏移8个字节进行寻址。
3.6.3 其他符号
- {}: 表示可选项。
- imm: 表示立即数。
4. 加载指令格式说明
RISC-V的加载指令(Load Instruction)格式如下所示,它定义了如何从内存中读取数据并存入寄存器。
4.1 指令汇编语法示例
l{d|w|h|b}{u} rd, offset(rs1)
4.2 指令位字段图
| 位范围 | 31-20 | 19-15 | 14-12 | 11-7 | 6-0 |
|---|---|---|---|---|---|
| 字段 | imm[11:0] |
rs1 |
funct3 |
rd |
opcode |
| 位数 | 12 | 5 | 3 | 5 | 7 |
| 说明 | offset[11:0] |
base |
width |
dest |
LOAD |
4.3 符号和字段解释
{d|w|h|b}: 表示加载的数据宽度。d: 双字 (Double-word)w: 字 (Word)h: 半字 (Half-word)b: 字节 (Byte)
{u}: 是可选项。如果存在,表示加载的数据为无符号数,即采用零扩展方式(Zero-extended)。如果不存在,表示加载的数据为有符号数,即采用符号扩展方式(Sign-extended)。rd: 表示目标寄存器,数据将加载到此寄存器中。rs1: 表示源地址寄存器,其值作为基地址。(rs1): 表示以rs1寄存器的值为基地址进行寻址,通常简称为rs1地址。offset: 表示以源寄存器rs1的值为基地址的偏移量。offset是一个12位有符号数,其取值范围为[-2048 ~ 2047]。
5. 存储指令格式说明
RISC-V的存储指令(Store Instruction)格式如下所示,它定义了如何将寄存器中的数据写入内存。
5.1 指令汇编语法示例
s{d|w|h|b} rs2, offset(rs1)
5.2 指令位字段图
| 位范围 | 31-25 | 24-20 | 19-15 | 14-12 | 11-7 | 6-0 |
|---|---|---|---|---|---|---|
| 字段 | imm[11:5] |
rs2 |
rs1 |
funct3 |
imm[4:0] |
opcode |
| 位数 | 7 | 5 | 5 | 3 | 5 | 7 |
| 说明 | offset[11:5] |
src |
base |
width |
offset[4:0] |
STORE |
5.3 符号和字段解释
{d|w|h|b}: 表示存储的数据宽度。d: 双字 (Double-word)w: 字 (Word)h: 半字 (Half-word)b: 字节 (Byte)
rs1: 表示源地址寄存器,其值作为基地址。(rs1): 表示以rs1寄存器的值为基地址进行寻址,简称rs1地址。rs2: 源寄存器2,其值将被存储到内存中。offset: 表示以源寄存器rs1的值为基地址的偏移量。offset是一个12位有符号数,取值范围为[-2048 ~ 2047]。
示例:
.section .text
.global main
main:
li a0, 0x80200000
sd a0, (a0)
li a1, 16
lw t0, (a0)
ld t1, (a0)
lui t0, 0x8034f
lui t1, 0x400
nop

- 首先查看该地址处的值,通过x指令可看到0x80200000地址处8个字节为0x00000000 80200000
- lw t0, (a0) 加载了4个字节,由于最高位为1,会将最高位扩展至64位,t0中的值为0xffffffff 80200000
- ld t1, (a0) 加载了两个字,结果为 0x80200000
- lui t0, 0x8034f 将0x8034f左移12位,最高位为1,扩展符号后结果为0xffffffff8034f000
- lui t1, 0x400 将0x400左移12位,最高位为0,扩展符号后结果为0x00000000400000
6. PC相对寻址
6.1 PC(程序计数器)
PC: 程序计数器 (Program Counter, PC) 用来指示下一条指令的地址。
6.2 AUIPC指令
RISC-V指令集提供了1条PC相对寻址的指令: AUIPC
指令格式: auipc rd, imm
操作说明:
- 把
imm立即数左移12位并符号扩展到64位后得到一个新的立即数 - 这个新的立即数是一个有符号的立即数
- 然后加上当前PC值,存储到
rd寄存器中
寻址范围: 这条指令能寻址的范围为基于当前PC偏移±2GB
计算公式: rd = PC + Signed (imm << 12)
AUIPC指令寻址范围示意图:
PC-2GB ←—————————————— PC ——————————————→ PC+2GB
- 32位有符号数的范围即相对PC的寻址范围为±2GB,并且跳转的offset为4096的整数倍
6.3 LUI指令
LUI指令将一个立即数左移12位后得到一个32位立即数,并且最高位符号扩展到64位,LUI指令的imm为20位,范围为0~0xFFFFF
指令格式: lui rd, imm
6.4 示例
示例1:AUIPC和LUI指令执行
问题:假设当前PC值为0x80200000,分别执行如下指令,那么a5和a6寄存器的值是多少?
auipc a5, 0x2
lui a6, 0x2
解答:
- a5寄存器的值:
PC + sign_extend(0x2 << 12) = 0x80200000 + 0x2000 = 0x80202000 - a6寄存器的值:
0x2 << 12 = 0x2000
分析:
auipc指令将立即数左移12位并符号扩展后与PC相加lui指令将立即数左移12位后加载到寄存器的高20位
示例2:使用AUIPC进行PC相对寻址加载数据
问题:假设当前PC值为0x80200000,想加载0x80202008地址的数据,那用auipc指令怎么写?
背景知识:
- AUIPC指令通常和ADDI联合使用来实现32位PC相对寻址
- AUIPC指令可以寻址与被访问地址4KB对齐的地方,即被访问地址的高20位部分
- ADDI指令可以在[-2048 ~ 2047]范围内寻址,即被访问地址的低12位部分
解决方案1:使用AUIPC和ADDI
auipc a5, 2
addi a0, a5, 8
ld t0, (a0)
计算过程:
auipc a5, 2:a5 = PC + (2 << 12) = 0x80200000 + 0x2000 = 0x80202000addi a0, a5, 8:a0 = 0x80202000 + 8 = 0x80202008ld t0, (a0):从地址0x80202008加载数据到t0
解决方案2:使用AUIPC和立即偏移
auipc a5, 2
ld t0, 8(a5)
计算过程:
auipc a5, 2:a5 = 0x80202000ld t0, 8(a5):从地址a5 + 8 = 0x80202008加载数据到t0
6.5 计算高20位和低12位
如果知道了当前PC值和目标地址B,那如何计算AUIPC和ADDI指令的参数呢?
下图展示了PC、目标地址B以及中间计算值的关系:
PC <-------------- Offset -------------->
|--------------------------------------------------------------------|
| ... | | | | ... |
|--------------------------------------------------------------------|
<---- 4KB ---> A <-lo12-> B <---- 4KB --->
关键定义:
- B: 目标地址 (Target Address)
- offset: 地址B与当前PC值的偏移量。即
offset = B - PC。 - A: 地址B与4KB对齐的地方。在计算中,
A可以理解为PC + (hi20 << 12)。 - lo12: 地址A与地址B的偏移量,即
lo12 = B - A。这是一个有符号的12位数值,取值范围为[-2048 ~ 2047]。
计算步骤:
为了计算AUIPC指令的 hi20 立即数和ADDI指令的 lo12 立即数,我们可以按照以下步骤进行:
-
计算总偏移量
offset:offset = B - PC -
计算AUIPC的
hi20立即数:hi20 = (offset + 0x800) >> 12- 这里的
0x800(即2048) 是为了进行四舍五入,确保lo12落在[-2048, 2047]的范围内。
- 这里的
-
计算ADDI的
lo12立即数:lo12 = offset - (hi20 << 12)- 这个
lo12值就是B - (PC + (hi20 << 12)),它表示从PC + (hi20 << 12)(即图中的A点) 到目标地址B的精确偏移。
- 这个
这个计算方法对于理解链接重定位非常重要。
6.6 与PC相关的加载和存储伪指令
在编写汇编代码时,当前PC值是未知的,计算hi20和lo12的过程通常留给链接器在重定位时完成。RISC-V定义了几种常用的伪指令,这些伪指令都是基于AUIPC指令的。
表 3.4 与PC相关的加载和存储伪指令
| 伪指令 | 指令组合 | 说明 |
|---|---|---|
la rd, symbol (非PIC) |
auipc rd, delta[31:12] + delta[11]addi rd, rd, delta[11:0] |
加载符号的绝对地址。这里delta = symbol - pc |
la rd, symbol (PIC) |
auipc rd, delta[31:12] + delta[11]l{w/d} rd, rd, delta[11:0] |
加载符号的绝对地址。这里delta = GOT[symbol] - pc,表示从全局偏移表加载 |
lla rd, symbol |
auipc rd, delta[31:12] + delta[11]addi rd, rd, delta[11:0] |
加载符号的本地地址。这里delta = symbol - pc |
l{b|h|w|d} rd, symbol |
auipc rd, delta[31:12] + delta[11]l{b|h|w|d} rd, delta[11:0](rd) |
将符号的内容加载到寄存器rd中 |
s{b|h|w|d} rd, symbol, rt |
auipc rt, delta[31:12] + delta[11]s{b|h|w|d} rd, delta[11:0](rt) |
将寄存器rd的内容存储到符号中。这里rt是用于地址计算的临时寄存器 |
li rd, imm |
根据情况扩展为多条指令 | 将立即数imm加载到寄存器rd中 |
伪指令说明
1. 地址加载伪指令
la(Load Address): 加载符号的地址到寄存器- 非PIC版本: 直接计算符号地址,适用于静态链接
- PIC版本: 通过全局偏移表(GOT)加载,适用于位置无关代码
2. 本地地址加载伪指令
lla(Load Local Address): 加载本地符号的地址,总是使用PC相对寻址
3. 内容加载/存储伪指令
l{b|h|w|d}: 加载字节/半字/字/双字内容s{b|h|w|d}: 存储字节/半字/字/双字内容- 这些伪指令自动处理地址计算,简化了编程
4. 立即数加载伪指令
li(Load Immediate): 根据立即数大小自动选择最优的指令组合
使用示例
# 加载符号地址
la t0, global_var # 加载全局变量地址到t0
# 加载符号内容
lw t1, global_var # 加载全局变量的值到t1
# 存储到符号
sw t2, global_var # 将t2的值存储到全局变量
# 加载立即数
li t3, 0x12345678 # 加载32位立即数到t3
7. 非对齐访问
在RISC-V中没有强制约束不对齐访问,让CPU设计者做出选择:
- 由硬件实现非对齐访问。
- 遇到非对齐访问时触发异常由软件处理。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)