超大规模集成电路设计笔记:第三篇
RISC-V简介
也可以看一下 数字系统设计复习笔记:第十二篇
寄存器
RV32/64I指令集定义了32个32/64位的通用整数寄存器x0~x31,其中x0固定用于存放数据0;寄存器位数决定了其可以访问的地址空间大小。
| 寄存器 | 别名 | 常见用途 |
|---|---|---|
x1 |
ra |
返回地址 (Return Address):jal 指令自动将返回地址存入此处。 |
x2 |
sp |
栈指针 (Stack Pointer):指向当前栈顶。 |
x3 |
gp |
全局指针 (Global Pointer):指向 2048 字节的小数据区(.sdata),用于高效访问小全局变量。 |
x4 |
tp |
线程指针 (Thread Pointer):用于线程本地存储(TLS)。 |
x5 |
t0 |
临时寄存器 (Temporary):调用者保存(caller-saved)。 |
x6–x7 |
t1, t2 |
额外的临时寄存器。 |
x8 |
s0 / fp |
保存寄存器 / 帧指针:被调用者保存(callee-saved)。也可用作帧指针。 |
x9 |
s1 |
保存寄存器。 |
x10–x17 |
a0–a7 |
函数参数和返回值:传递前 8 个参数;a0/a1 也用于返回值。 |
x18–x27 |
s2–s11 |
保存寄存器:被调用者保存,适合存放局部变量。 |
x28–x31 |
t3–t6 |
临时寄存器。 |
指令组成
也可以看一下 从零开始学RISCV:序篇
单周期RISC-V处理器的数据通路
I-Type (LOAD)
lw的指令格式为I-Type:
1 | [31:20] [19:15] [14:12] [11:7] [6:0] |
首先从指令内存中取指,根据op决定是lw指令,随后从指令中拆分出寄存器地址rs1并从寄存器堆中读取对应地址的值,再将立即数imm从12位补全到32位,接着将两个值送进ALU进行运算。运算完成后,将结果输入数据内存,读取对应地址数据值,再将数据值传回寄存器堆,写入目标寄存器rd,最后决定下一个指令的地址为PC+4,送回PC寄存器。
S-Type
sw的指令格式为S-Type:
1 | [31:25] [24:20] [19:15] [14:12] [11:7] [6:0] |
首先从指令内存中取指,根据op决定是sw指令,随后从指令中拆分出寄存器地址rs1并从寄存器堆中读取对应地址的值,再将立即数imm从12位补全到32位,接着将两个值送进ALU进行运算。运算完成后,将结果输入数据内存,以选择目标地址,再将数据值从寄存器堆传入数据内存,最后决定下一个指令的地址为PC+4,送回PC寄存器。
R-Type
or的指令格式为R-Type:
1 | [31:25] [24:20] [19:15] [14:12] [11:7] [6:0] |
首先从指令内存中取指,根据op只能决定是R-Type指令,具体为什么指令还需要读取funct7和funct3进行判断。判断为or后,从指令中拆分出寄存器地址rs1与rs2,并从寄存器堆中读取对应地址的值,不经过立即数拓展,直接送入ALU进行比较,随后将数据值传回寄存器堆,写入目标寄存器rd,最后决定下一个指令的地址为PC+4,送回PC寄存器。
B-Type
beq的指令格式为B-Type:
1 | [31:25] [24:20] [19:15] [14:12] [11:7] [6:0] |
首先从指令内存中取指,根据op决定为B-Type,随后从指令中拆分出寄存器地址rs1与rs2,并从寄存器堆中读取对应地址的值,接着将两个值送进ALU进行运算,结果为14-14=0。运算完成后,将CPSR的Z设置为1。同时,立即数扩展产生-12,与PC相加,得到新的PC地址,并送入PC选择器以便随后使用。
这里为什么是-12?
0xFFA为1111_1111_1010,负数补码为取反加一,因此是0000_0000_0101(2)+1(2)=0110(2)=6(10)。所以0xFFA为-6。又因为要低位拓展为0,即算术左移,相当于乘以2,所以最终的立即数为-12。
注意这里的立即数是imm[12:1],低位还需要补0。最终为13位立即数,表达式为:
{20{Instr[31]}, Instr[7], Instr[30:25], Instr[11:8], 1'b0}
控制信号生成
单周期处理器的控制单元基于op、funct3和 funct7设计控制信号。RV32I 指令集只使用funct7的第 5 位。
增强的I-Type指令支持
对于addi指令,其同样为I-Type,但是仅靠先前的通路无法执行,因其控制信号与lw不同。ImmSrc依旧为00,ALUSrc为1,代表使用立即数扩展后的结果。但ResultSrc为0,代表直接使用ALU的计算结果而不从数据寄存器取值。
J-Type
jal的指令格式为J-Type:
1 | [31:11] [11:7] [6:0] |
首先从指令内存中取指,根据op决定为J-Type,随后从指令中拆分出寄存器地址rd,并将pc+4写入寄存器堆,再将立即数imm从21位补全到32位,和pc一起送入ALU进行运算,求得新的地址值,最后传回PC寄存器。
I/S-Type的立即数均为12位,B-Type的立即数为13位,J-Type最长,为21位!
和B-Type类似,J-Type同样需要低位补0(乘以2)。
执行时间
对于单周期处理器来说,周期时间受限于关键路径,即执行时间最长的路径。通常,路径中的瓶颈在内存存取、ALU和寄存器堆读写中。
对于所有指令操作,lw是执行最长的。可将时间简化为:
多周期RISC-V处理器
单周期处理器有 3 个明显的缺点。首先,使用两个独立的内存分别存储指令和数据,而大多数处理器只有一个内存用于存储指令和数据;其次,需要足够长的时钟周期支持最慢的指令lw, 尽管大多数指令可能更快;最后,用了 3 个加法器(1 个在 ALU 中,2 个用于实现 PC 逻辑),而加法器是相对昂贵的电路,尤其是需要快速时价格更高。
对于多周期处理器,使用组合内存,既存储指令也存储数据。这样,可以在一个周期读取指令而在另一个周期读取或写入数据。
流水线RISC-V处理器
这应该是处理器的最终形态了:用流水线来增加吞吐量。
性能计算
必考。了解如何计算,以及哪些指令需要阻塞。
我们假定:
- 40% 的load指令被下一条指令使用;
- 50% 的分支指令预测错误;
- 不停滞时Load CPI = 1, 停滞时为2;
- 不停滞时Branch CPI = 1,停滞时为3;
对于平均Load CPI,计算为 1x0.6 + 2x0.4=1.4;分支CPI同理,可求得为2。
对于 SPECINT2000 benchmark 测评程序:
- 25% loads
- 10% stores
- 11% branches
- 2% jumps
- 52% R-type/I-type ALU
我们认为Jump指令的CPI为3,是因为前两个周期(IF/ID)正常执行,但还需要一周期的气泡。
因此,可求得平均CPI为 0.25x1.4 + 0.1x1 + 0.11x2 + 0.02x3 + 0.52x1 = 1.25。
执行时间
参考上面的表格,计算关键路径:
可列出流水线周期表达式为:
求得流水线的周期为350ps。因此,执行1000亿条指令的程序,在求得CPI为1.25的状况下,总消耗时间为:
| 处理器 | 执行时间 | 加速比 |
|---|---|---|
| 单周期 | 75s | 1 |
| 多周期 | 155s | 0.5 |
| 流水线 | 44s | 1.7 |
先进微架构
- 超标量(Superscalar)架构:在一个时钟周期内发射多条指令(例如 Intel Core、AMD Zen 系列)。
- 深度流水线(Deep Pipeline):将指令执行分解为更多阶段(例如 Intel NetBurst 曾达到 31 级流水线)。
- 动态流水线调整:现代架构(如 Apple M1、AMD Zen 4)可根据功耗和负载动态调整流水线深度。
- 乱序执行(Out-of-Order Execution):打破程序的指令顺序约束,动态调度以最大化执行单元利用率。
- 寄存器重命名(Register Renaming):避免伪依赖(false dependency),提升并行度。
- 混合架构(Hybrid Architecture):如 Intel Alder Lake 的 Performance Core 与 Efficiency Core。
- 多线程(Multithreading):如 Intel 的 Hyper-Threading(超线程)技术
超标量
超标量内部含有多条数据通路,可以同时执行多条指令。
对于两路超标量,理想的IPC(Instruction Per Cycle,每周期执行指令数,为CPI的倒数)为2;对于上面带数据依赖的程序,实际的IPC为6 / 5 = 1.2,因实际周期时间从5到9,为5个周期。注意lw指令后必定阻塞一拍。
乱序发射
乱序提前检测多条指令并尽快发送无依赖的指令。只要保证依赖关系满足,可以产生预期效果,就可以不按照程序编写的顺序发送指令。
对于上面带依赖关系的乱序执行,其IPC为6 / 4 = 1.5。
SIMD
SIMD(Single Instruction Multiple Data,单指令多数据)可以显著增加数据的并行度,如将8个8位元素相加。



