指令集入门–RISC-V
引子
计算机组成:
- 硬件:运算器、存储器、控制器、输入设备、输出设备
- 软件:系统软件、应用软件
设计处理器核的流程:
- 确定体系结构:概要设计
- 确定微体系结构:详细设计
- 逻辑设计
- 物理设计
计算机体系结构的发展
体系结构的经典定义:
- 软硬件交互的界面,或者说是软件开发人员和硬件设计人员共同遵循的一个规范、一个标准
系列机的概念:
- 系列机指同一厂家生产的体系结构相同、而组成和物理实现不同的一系列不同型号的机器
- 系列机必须保证用户看到的机器属性一致,使得按这个属性编制的程序都能通用于各档机器,令各档机器是软件兼容的,可获得相同的结果,差别只在于不同的运行时间
向前兼容和向后兼容:
- 向前(后)兼容指的是按某个时期投入市场的该型号机器上编制的软件,不加修改就能运行于比它先(后)投入市场的机器上
- 向后兼容才是软件兼容的根本特性,也是系列机的根本特性
CISC:
- 指令类型多,每条指令的功能复杂,通常一条指令可以完成一串操作
- 指令长度不固定,通常为16~64位不等
- 除了load、store指令外,其他指令也可以直接对存储器型操作数进行操作
- 指令格式复杂
- 寻址方式多
RISC:
- 指令集精简,指令类型相对少,每条指令的功能简单,大部分指令一个周期即可完成操作
- 指令长度固定,通常为32位,也有16位的RISC指令集
- 除了load、store指令外,其他指令都不能访问存储器、只能对寄存器型操作数进行操作
- 指令格式简单规整
- 寻址方式相对少
为啥早期体系结构都是CISC类型:
- 不定长的复杂的指令能够提供更高的代码密度,令同一块存储器能够装载更多指令
- 当时的编译器还没办法进行寄存器分配,也没有能力配合微体系结构进行深层次的优化
存在的问题:
- 指令数目越来越多,体系结构越来越复杂
- CISC指令集中的“二八定律”
RISC-V用户态指令集
图灵完备:
指在可计算性理论里,如果一系列操作数据的规则(比如指令集、编程语言等)可以用来模拟单带图灵机,那么这个规则就是图灵完备的
如SUBLEQ这一条指令就是图灵完备的:
- JMP C -> SUBLEQ Z,Z,C
- ADD A,B
1 | SUBLEQ Z,Z # 先构造0 |
设计指令集需要考虑的方面:
- CPU中操作数的存储方法:堆栈、累加器和寄存器,大端还是小端
- 指令集所提供的操作类型
- 指令格式
- 操作数的类型和大小
- 操作数的寻址方式
- 显式表示的操作数个数
RISC-V指令集的概况:
- CPU中操作数的存储方法:寄存器
- 指令在存储器中的字节序:小端
- Load/Store访问的数据在存储器中的字节序:小端或大端
- 显式表示的操作数个数
- 基本整数指令集:没有隐式的操作数;显式表示的操作数不超过1个目的操作数,不超过2个源操作数
- 操作数的寻址方式
- 立即数寻址,如LUI
- 寄存器寻址
- 基址+偏移访问存储器
- PC相对寻址
- RISC-V指令集能表示的操作数类型和大小
- 整数:二进制补码,大小取决于基本整数指令集模块
- 浮点:IEEE754标准,大小取决于浮点指令集模块
- 指令格式:指令长度为op码的低2位
- 指令长度:
- Opcode[1:0] != 2’b11为16位,指令集模块C
- Opcode[1:0] == 2’b11,指令长度为32位,除C以外的其他指令集模块
- 六种基本指令格式:
- R:寄存器-寄存器
- I:短立即数操作数和load操作
- S:store操作
- B:条件分支操作
- U:长立即数操作
- J:跳转操作
- 指令长度:
RISC-V定义了4个基本整数指令集模块:RV32I/RV64I/TV128I/RV32E,4个基本整数指令集模块共用了指令编码,所以执行时只能有一个指令集生效,由系统寄存器misa决定基本整数指令操作的整数寄存器宽度和能使用的整数寄存器个数
- RV32I:32个32位整数寄存器,32位地址空间
- RV64I:32个64位整数寄存器,最大64位地址空间
- RV128I:32个128位整数寄存器,最大128位地址空间
- RV32E:16个32位整数寄存器,32位地址空间
下图是RV32I的指令编码
下图是RISC-V整数和浮点寄存器
JALR的作用是链接并跳转,根据寄存器的不同可以有不同的语义,如下表所示:
rd为x1/x5 | rs1为x1/x5 | rd = rs1 | call/return语义 |
---|---|---|---|
NO | NO | - | None,无条件跳转 |
NO | YES | - | return |
YES | NO | - | call |
YES | YES | NO | return,then call |
YES | YES | YES | call |
target address ← (rs1 + sign_ext(imm[11:0]) & 32’hFFFF_FFFE,最低一位为0,满足对齐要求
可以使用AUIPC + JALR来实现32位地址的跳转
- AUIPC(add upper immediate to pc):rd[31:0] ← pc + {imm[31:12], 12’h0}
- LUI(load upper immediate):rd[31:0] ← {imm[31:12], 12’h0}
LUI + ADDI实现向一个寄存器写入32位常数
AUIPC + load/store实现更大范围的pc相对寻址
立即数为0的AUIPC实现获取当前PC
补充:破坏性指令
破坏性的目的操作数,如
1 | add a,b # a = a+b, a寄存器中的值被破坏了 |
破坏性指令存在的原因:指令字长的限制,减少寄存器域所占位数
如何让操作数不被破坏?–用前缀指令
1 | add a, b # 破坏性指令 |
许多破坏性指令都可能会被编译器优化,增加了很多前缀指令,一种可能的优化思路是用前缀指令来提示硬件合并指令
假设有一条破坏性指令fa可以实现功能$c = a \times b + c$,但是编译器可能会给其优化成两条指令,如下面所示:
1 | movfa c,d # 先保护原来c中的值 |
但是此时的mov指令加上了后缀fa,提示硬件这条指令可以和后面的fa指令合并执行
RISC-V之系统指令和系统寄存器
异常
RISC-V体系结构定义了三个特权级:User、Supervisor、Machine
- Machine-mode:最高特权级,也是一个RISC-V硬件平台必须实现的特权级
- Supervisor-mode:运行操作系统
- User-mode:运行应用程序,最低特权级
特权级之间的切换:只有在响应异常或异常返回时才会发生特权级的切换
- 响应异常时,要进入的特权级不会降低;
- 异常返回时,返回到的特权级不会升高;
事件的类型决定了异常的类型,异常的类型:
Class | Cause | Async/Syn | Return Behavior |
---|---|---|---|
Interrupt(中断) | Signal from I/O device | Async | Always returns to next instruction |
Trap(自陷) | Intentional exception | Sync | Always returns to next instruction |
Fault(故障) | Potentially recoverable error | Sync | Might returns to current instruction |
Abort(中止) | Nonrecoverable error | Sync | Never returns |
trap的例子是系统调用,fault的例子是缺页故障,abort的例子是内存数据发生了奇偶校验错
注:奇偶校验错在有的系统中也被当做异步异常被处理,因为处理时间长,cpu可以先取出数据,奇偶校验存储器自己进行
异常响应流程:
系统寄存器CSR
CSR(Control and Status Registers)
- 提供控制、保存状态,跟异常处理相关的寄存器只是系统寄存器的其中一部分
系统寄存器分三类:
- M态寄存器
- S态寄存器
- U态寄存器
系统寄存器名称的首字母表示可以访问该寄存器的最低特权级,比如sepc,该寄存器只有在M态和S态才能被访问,在U态访问该寄存器会产生异常
系统寄存器地址映射规范:用12位编码标识一个CSR,最多可以表示4096个CSR,各编码的含义如下:
[7:4]:标准/定制
M态系统寄存器
- 注:虚拟化扩展还会引入mtinst和mtval2两个寄存器
misa
作用:标识处理器支持的指令集模块,如果读该寄存器返回0,表示处理器没有实现这个寄存器
字段含义:
- MXL[1:0]:基本整数指令集模块的数据位宽
- 1:32位;2:64位;3:128位
- Extensions[25:0]:实现了哪些标准扩展,26位对应26个字母(0-A, 1-B,…, 25-Z)
- misa[4]是只读字段,misa[4] =~misa[8]
判断例子:
下面介绍与异常有关的寄存器
mtvec
mtvec (Machine Trap-Vector Base-Address Register):当发生异常进入M态时,被用于生成异常表访问地址的异常表基址寄存器
字段含义:
- BASE:4字节对齐
- MODE:决定如何生成异常表访问地址,也就是响应异常后处理器跳转到的目标地址,如下所示
mcause
mcause (Machine Cause Register):当发生异常进入M态时,记录异常产生原因,即给每种异常分配了
一个异常编号
字段含义:
- Interrupt:0表示同步异常,1表示中断
- Exception code:同步异常或中断的编号
mepc
mepc (Machine Exception Program Counter):当发生异常进入M态时,保存发生异常的指令的地址或被中断打断没有提交的指令的地址
异常处理完成后,如果不是中止程序,而是返回发生异常的程序继续执行,那么返回地址和mepc是什么关系呢?
- 中断:中断返回地址就是mepc中保存的地址。因为响应中断时,当前指令提交了,下一条被打断不提交
- 自陷:自陷的返回地址是mepc+4。RISC-V中,执行系统调用指令(ECALL)时,mepc保存的是ECALL指令的地址
- 故障:mepc保存的发生故障的指令的地址,也是故障返回的地址
mtval
mtval (Machine Trap Value Register):当发生异常进入M态时,保存因异常类型而异的信息,供异常处理程序处理异常时使用
写入的值可能是:
- 发生故障的虚地址:breakpoint, address-misaligned, access-fault, or page-fault exception occurs on an instruction fetch, load, or store
- 发生故障的指令的编码:illegal instruction
- 全0:其他异常
mip/mie
mip/mie (Machine Interrupt Pending/Enable):mip 指示了正在提交的中断,mie 包含了处理器能处理的和忽略的中断
字段含义,三类六种中断:
- MEI:M态外部中断
- MTI:M态定时器中断
- MSI:M态软件中断
- SEI:S态外部中断
- STI:S态定时器中断
- SSI:S态软件中断
mie保存的是每一种中断的局部使能,中断能否被响应还要受mstatus中的中断全局使能位的控制
medeleg/mideleg
medeleg/mideleg (Machine Trap Delegation Registers):异常代理寄存器,控制相应的异常被代理到S态进行处理,每一位对应到相应编号的同步异常/中断,没有对应同步异常/中断的位接常值0
- medeleg:同步异常代理寄存器
- mideleg:中断代理寄存器
缺省情况下,所有的异常被响应后,都会进入M态,如果启用了代理,则代理到S态处理异常,1表示代理到S态,0表示不代理
- 问题:假设处理器运行在M态时发生了非法指令异常,且medeleg[2]为1,那么该非法指令异常被响应后,目标特权级是?
- 答案是M而不是S
In systems with three privilege modes (M/S/U), setting a bit in medeleg or mideleg will delegate
the corresponding trap in S-mode or U-mode to the S-mode trap handler.
mscratch
mscratch (Machine Scratch Register):保存的是一个指针,指向M态程序本地上下文(context)空间
使用方式:
响应异常进入M态后,使用CSR交换指令将mscratch的值和一个整数寄存器的值进行交换
整数寄存器就可以作为load指令的基址寄存器,加载M态程序的执行现场
要退出M态时,使用CSR交换指令将mscratch的值和整数寄存器的值进行交换,恢复整数寄存器并把指针重新保存在mscratch中
mstatus
mstatus (Machine Status Register):跟踪处理器的当前操作状态、控制处理器的执行
部分字段含义:
- MIE、SIE:M态中断全局使能位、S态中断全局使能位
- xPP:保存处理器进入x态之前的特权级
- xPIE:保存处理器进入x态之前的中断全局使能位
- 当处理器在特权级y发生异常进入x态时,xpp的值被设置为y,xPIE的值被设置为xIE的值,xIE被置为0(异常响应过程中硬件自动完成的状态保存)
- SXL/UXL:S态/U态下基本整数指令集控制位,00:RV32I/RV32E,10:RV64I
- MBE/SBE/UBE:M态/S态/U态下数据访问的字节序控制位,0:Little-endian,1:Big-endian
- XS:定制扩展指令集的上下文状态位
- FS:浮点功能单元的上下文状态位
- 降低上下文保存和恢复的开销
- 上下文切换时,首先读取上下文状态位,如果是Dirty的,那么需要进行状态保存,否则不需要
Status | FS Meaning | XS Meaning |
---|---|---|
0 | Off | All off |
1 | Initial | None dirty or clean,some on |
2 | Clean | None dirty,some clean |
3 | Dirty | Some dirty |
mtime/mtimecmp(Machine Timer Registers)
- mtime:定时器计数器
- mtimecmp:定时器比较寄存器
使用:
- 当mtime的值大于或等于mtimecmp的值时,产生一个定时器中断,直到mtimecmp被写入一个大于mtime的值时,该中断信号才会被清零
性能检测寄存器
- 通过minstret和mcycle可以计算出程序执行的IPC(Instructions PerCycle)
- 事件计数器可以统计各种体系结构和微体系结构事件,基于统计的数据可以进行性能分析和性能优化
- 一个程序执行的各种类型的指令数目,使用频率高的指令尽可能执行快
- Cache失效率,分支误预测率等
S态系统寄存器
响应异常后处理器可能进入M态,也可能进入S态,所以S态同样有一套与异常相关的系统寄存器,功能与对应的M态系统寄存器相同
- 其中sip、sie、sstatus是M态对应系统寄存器mip、mie、mstatus的子集,仅包含对S态可见的字段
- scounteren:计数器访问使能寄存器,控制对应计数器在U态下是否可被访问
- satp (Supervisor Address Translation and Protection Register):控制S态下的虚实地址转换
U态系统寄存器
系统指令
异常返回指令MRET、SRET
异常处理完成后,使用异常返回指令返回到异常发生时的现场,从x态返回
xRET只能在x态或高于x态的特权级执行,低于x态的特权级执行将产生非法指令异常
执行指令后的处理步骤:
- 处理器将返回到mstatus.xPP指定的特权级,并将mstatus.xPP清零
- 使用mstatus.xPIE更新mstatus.xIE,并将mstatus.xPIE置为1(中断使能)
- 跳转到xepc指定的地址
WFI
WFI(Wait For Interrupt):一条提示指令,暗示处理器可以进入一种低功耗状态,直到被中断唤醒
确切地说,WFI指令并不属于系统指令,该指令在所有特权级都可以执行,只是行为有所不同
- U态下WFI的行为:如果在实现定义的时间内没有执行完毕(在这个时间内有中断请求,WFI指令没有令处理器进入低功耗状态,而是继续执行后续指令),那么发生非法指令异常进入高特权级
- 可以调度其他应用程序占用处理器
- S态下WFI的行为
- mstatus.TW=0:如果在实现定义的时间内没有执行完毕,那么处理器进入低功耗状态
- mstatus.TW=1:如果在实现定义的时间内没有执行完毕,那么发生非法指令异常
- M态下WFI的行为:如果在实现定义的时间内没有执行完毕,那么处理器进入低功耗状态
RISC-V虚存系统
虚拟内存的概念
什么是虚拟内存?
- 虚拟内存是一种内存抽象技术,旨在为进程提供大(large)的、一致(uniform)的私有(private)地址空间
虚拟有两层含义:
- 虚拟出更大容量的内存,物理内存仅作为cache
- 虚拟出用户进程“独占”内存的假象
虚拟内存的管理
虚拟地址的转换过程:
虚拟地址和物理地址的字段划分,主要是页表的页面号和页偏移组成。其中VPO==PPO
页表中的保存着页表项(PTE),页表项的结构如下
PTE各字段的含义:
- 访问位:表示该页是否被访问过(读或写),用于页面置换,判断哪些是常用的
- 驻留位:表示该页是否在内存,否则缺页
- 修改位:表示在内存中的该页是否被修改过,将该页从内存换出时,根据此位判断是否要把它的内容写回外存
- 保护位:表示该页允许的访问方式,只读,可读可写,可执行等
32位和64位也是目前通用处理器系统中常见的两种页表项大小
多级页表的地址转换,多级页表会导致页表增多,但是实际所需的页表项减少,因为有些二级页表可以不用分配
RISC-V的虚存系统
M态总是运行在物理地址模式下
S态和U态可以通过配置寄存器控制其运行在虚拟地址模式下
- 基本页大小为4KB
- 体系结构手册目前定义了3种规模的虚拟内存系统
- Sv32:32位虚拟内存系统
- Sv39/Sv48:39/48位虚拟内存系统
satp
satp (Supervisor Address Translation and Protection register)
下面两图分别是RV32和RV64
字段含义:
- PPN:根页表的物理页号,通常要求实现的PPN位数足够表示整个物理内存空间
- ASID(address space identifier):便于地址转换栅栏指令以地址空间为单位进行同步
- MODE:选择处理器当前采用的地址转换机制
SFENCE.VMA
Supervisor Memory-Management Fence Instruction,S态存储管理栅栏指令
- store还未更新页表,SFENCE.VMA后面的取指就开始了,取指地址进行转换拿到的是旧值
- SFENCE.VMA指令执行完毕后,处理器发起flush操作,清空SFENCE.VMA后续的的所有指令,并重新取指
- store还未更新页表,SFENCE.VMA后面数据访问指令load/store已经开始进行地址转换,拿到了旧值
- SFENCE.VMA完成之前,更新页表的store指令要完成;SFENCE.VMA完成之前,后续的load/store指令不能进行地址转换
- store指令虽然更新了内存中的页表,但是页表缓冲结构,比如TLB中缓存了旧的页表,SFENCE.VMA后面的地址转换读取的旧值
- 执行SFENCE.VMA时,清除页表缓冲结构的旧页表
各字段含义:
- asid:地址空间标识符,如果非0,SFENCE.VMA仅清除指定asid的tlb项
- vaddr:虚拟地址,如果非0,SFENCE.VMA仅清除指定虚拟地址vaddr对应的tlb项
Sv32虚拟内存系统
虚拟地址格式
页表项结构
各字段含义:
- V:有效位
- R/W/X:访问权限位,表示该页表项对应的页面是否可读/可写/可执行
- 三位全0表示指向下一级页表,否则为叶页表项
- 可写页面一定是可读的
- mstatus.MXR字段为1,所有可执行页面也是可读的
- U:用户态可访问位
- 只有U位为1,对应的页面才能被U态程序访问
- 当mstatus.SUM字段为1时,在S态也可以通过load/store指令访问U位为1的页面,通常情况下,S态程序不能访问U位为的页面
- G:全局页表项
- 全局页表项是指存在于所有地址空间的页表项,通常被用于为全局共享数据建立映射关系
- 使用SFENCE.VMA同步asid指定的地址空间的页表数据时,全局页表项不受影响
- 全局页表项没有设置G位只是影响性能,但是非全局页表项设置了G位则是软件bug
- A:访问位
- D:修改位
- 对于非叶页表项,A位和D位要求为0
基于两级页表的地址转换步骤:任何一级页表项都可以是叶页表项,所以Sv32虚拟内存系统下支持的页面大小有两种:4MB(superpage,超页)和4KB
- 令a = stap.ppn x 212,i = 1
- 从地址a + va.vpn[i] x 4处读取页表项,将得到的值用pte表示
- 如果pte.v = 0或(pte.r = 0 && pte.w = 1),将产生缺页故障,地址转换终止,否则跳转到Step4
- 如果pte.r = 1或pte.x = 1,跳转到Step5,否则该PTE不是叶页表项,而是指向下一级页表。令i = i-1,如果i < 0,产生缺页故障,地址转换终止,否则令a = pte.ppn x 212 ,跳转到Step2
- 找到了叶页表项,根据mstatus的SUM和MRX字段的值以及当前的特权级判断页面是否具备需要的访问权限,如果不具备,产生缺页故障,地址转换终止,否则跳转到Step6
- 如果i > 0并且pte.ppn[i-1:0] ≠ 0,地址转换得到的超页地址是非对齐的(misaligned),产生缺页故障,地址转换终止
- 如果pte.a = 0或访存请求是store指令发出时pte.d = 0 ,产生缺页故障(软件修改A位和D位)或由硬件设置A位为1,如果访存请求是store指令发出的,那么D位也要被置为1,然后跳转到Step8
- 地址转换成功,物理地址的生成如下
- pa.pgoff = va.pgoff
- 如果i > 0, pa.ppn[i-1:0] = va.vpn[i-1:0]
- pa.ppn[1:i] = pte.ppn[1:i]
先进的分支预测技术
动态预测有哪些信息可以依赖呢?
- 分支的历史信息
- 分支临近的分支信息
- 分支的地址
Smith预测器
一种局部动态分支预测器,分支的历史信息– 已使用
两级分支预测器
Branch History Register添加了全局分支历史,分支临近的分支信息– 已使用
Bi-Mode预测器
解决了别名问题
还有gskewed预测器、Agree预测器、YAGS预测器、感知器分支预测器
指令动态调度
数据旁路在长延迟指令面前束手无策