指令集架构

以下笔记 来自阅读:Hennessy J L, Patterson D A. Computer Architecture: A Quantitative Approach 6th Edition. 2019.

指令集架构指代程序可见的指令集,也是软件和硬件的分界线。下面以实际的例子说明指令集架构的具体7个方面:
- 指令集架构的分类:几乎所有今天的ISA都被归类为通用寄存器架构,其操作数不是寄存器就是内存位置。80x86有16个通用寄存器、16个保存浮点数据的寄存器;RISC-V有32个通用和浮点寄存器; 从通用寄存器架构向下划分,可以再分为两类。一类是 register-memory ISA ,比如80x86,可以在很多指令中直接访问内存;一类是 load-store ISA,比如 ARMv8RISC-V ,只能通过 loadstore 指令访问内存。所有从1985年之后发布的ISA都是 load-store ISA。下表是 RISC-V ISA的寄存器示例:

寄存器 名称 用途
x0 zero 常量 '0'
x1 ra 返回地址
x2 sp 栈指针(指向栈下一个空闲区域)
x3 gp 全局指针(指向全局变量区域)
x4 tp 线程指针(指向线程局部变量区域)
x5-x7 t0-t2 临时存储
x8 s0/fp 保存的寄存器/栈帧指针(指向栈帧基址)
x9 s1 保存的寄存器
x10-x11 a0-a1 函数参数/返回值
x12-x17 a2-a7 函数参数
x18-x27 s2-s11 保存的寄存器
x28-x31 t3-t6 临时存储
f0-f7 ft0-ft7 浮点 临时存储
f8-f9 fs0-fs1 浮点 保存的寄存器
f10-f11 fa0-fa1 浮点 函数参数/返回值
f12-f17 fa2-fa7 浮点 函数参数
f18-f27 fs2-fs11 浮点 保存的寄存器
f28-f31 ft8-ft11 浮点 临时存储
  • 内存寻址:基本上所有的台式机和服务器,包括 80x86ARMv8RSIC-V ,使用字节编址来访问内存操作数。某些架构,比如 ARMv8, 要求访存对象必须对齐。而 80x86RISC-V 不需要对齐,但是如果对齐,访问速度会更快。
  • 寻址模式:寻址模式描述了寄存器操作数、立即数(常量)操作数和内存对象的地址。RISC-V 寻址包括:寄存器、立即数(常量)和位移(displacement, 寄存器的值加上一个常量的偏移构成了内存地址)。80x86支持上述三种模式,另外加上三种位移的变种:1)无寄存器(绝对地址) 2)两寄存器(基地址+索引+位移)3)两寄存器(基地址+索引*操作数大小+位移)。
  • 操作数类型和大小:和大多数ISA一样,80x86、ARMv8和RISC-V支持8比特、16比特、32比特、64比特的操作数大小,也支持IEEE 754 32位(单精度)和64位(双精度)浮点数。80x86 也支持80比特的浮点数(扩展双精度)。
  • 操作: 操作一般可分为:数据传输、算术逻辑、控制和浮点几类。下表是RISC-V的数据传输和算术指令示例:
指令类型/操作码 指令含义
数据传输 在寄存器和内存,或者整数寄存器和浮点寄存器、特殊寄存器间搬移数据。只有内存地址是由12比特的偏移加上通用寄存器的值构成
lb, lbu, sb 加载字节(byte 8比特)、加载无符号字节、存储字节
lh, lhu, sh 加载半字(half word 16比特)、加载无符号半字、存储半字
lw, lhw, sw 加载字(word 32比特)、加载无符号字、存储字
ld, sd 加载双字(double word 64比特)、存储双字
flw, fld, fsw, fsd 记载单精度浮点,加载双精度浮点、存储单精度浮点、存储双精度浮点
fmv.S.X, fmv.D.X, fmv.X.S, fmv.X.D 从整数寄存器拷贝到浮点寄存器,或从浮点寄存器拷贝到整数寄存器。S代表单精度,D代表双精度
csrrw, csrrwi, csrrs, csrrsi, csrrc, csrrci 读取计数器并写入状态寄存器,计数器包括:时钟周期数、时间、退役指令数量等。
算术/逻辑 通用寄存器数据上的整型或逻辑数据运算
add, addi, addw, addiw 加, 加立即数(12比特), 有符号加32比特并扩展至64比特, 加立即数(32比特)
sub, subw 减, 减32比特
mul, mulw, mulh, mulhsu, mulhu 乘, 乘32比特, 乘上半部(16比特), 无符号乘有符号上半部, 无符号乘上半部
div, divu, rem, remu 除, 无符号除, 余数, 无符号余数
divw, divuw, remw, remuw 除余低32位, 有符号扩展
and, andi 与, 与立即数
or, ori, xor, xori 或, 或立即数, 异或, 异或立即数
lui 加载立即数到寄存器上半部 (31-12比特位)并进行符号扩展
auipc 加立即数的上半部 (31-12位,余下低12位为0) 到PC; 和JALR 配合使用转移控制到任何32位地址
sll, slli, srl, srli, sra, srai 移位; 逻辑左移, 逻辑右移; 使用立即数或者变量
sllw, slliw, srlw, srliw, sraw, sraiw 移位低32位, 有符号扩展
slt, slti, sltu, sltiu 小于情况下设置; 小于情况下设置; 有符号和无符号
  • 控制流指令:实际上所有的ISA,都支持有条件分支、无条件的跳转、函数调用和返回。80x86、ARM、RISC-V 均使用PC相对寻址,分支地址是通过PC加上地址字段构成的。但也有也许的不同,RISC-V 有条件分支(BE,BNE,etc)是检查寄存器的内容,而80x86和ARM检查的是算术或者逻辑运算副作用设置的比特位。ARM和RISC-V将返回地址放到寄存器中,而80x86调用(CALLF)将返回地址放到内存栈里。下表是RISC-V的控制指令示例:
指令类型/操作码 指令含义
控制 有条件分支和跳转;PC相对寻址或者通过寄存器
beq, bne, blt, bge, bltu, bgeu 通用寄存器相等/不相等/小于/大于或等于时分支;有符号或无符号
jal, jalr 跳转并链接;保存返回地址,目标相对PC寻址或者使用寄存器;如果x0是目标寄存器,则按照跳转来运行
ecall 向执行环境(一般为OS)下发请求
ebreak 调试软件用来将控制交回调试环境
fence, fence.i 线程间同步用以保证内存访问的顺序;同步指令及store使用的数据到指令内存
  • 编码ISA: 在编码时有两种选择:定长和变长。所有的ARMv8和RISC-V指令的长度都是32比特,这样的定义方便了指令的解码过程。80x86是变长编码,指令长度1到18个字节。变长指令相对于定长指令可以占用更少的内存空间,因此为80x86编译的程序一般比RISC-V编译后的程序要小。指的注意的是,我们前述提到的ISA在设计上的选择都将影响指令在二进制形式下的编码方式。例如,由于在一个指令中,寄存器字段和寻址模式字段都可能反复出现,因此,寄存器数量和寻址模式都对指令大小有着重要的影响。(ARMv8和RISC-V后面都增加了支持变长的扩展:Thumb-2RV64IC,支持16比特和32比特指令混合使用。通过这种方式降低程序大小。通过这种压缩方式编译的程序要比80x86要小)下图是RISC-V指令编码的示例: