这一部分对应的是 lec 18 ~ 20 (哔大上的参考课程为[Summer 20]),主要涉及的是RISC – V的处理器设计,包括实现框架(lec 18)、数据通路(lec 19)、性能校衡(lec 20).
与前一讲的抽象的功能描述不同,本讲深入到用具体的硬件模块(寄存器、ALU、多路选择器等)和控制信号,并最终搭建一个能执行指令的机器。
具体而言,我们将从ISA规范出发,逐步分析指令执行共性与个性,分别为其设计数据通路组件,再将组件集成并添加控制逻辑,并最终实现具体指令。
数据通路与控制 – CPU
作为指令处理的中心部件,我们先对 CPU 进行剖析。
正如小标题所言, CPU 发挥功能离不开数据通路和控制
- 数据通路包含执行操作所需的所有硬件组件(寄存器、ALU、加法器、多路选择器等)以及连接这些组件的线路。它负责执行具体的算术、逻辑和数据传输操作。
- 控制部件则根据当前正在执行的指令产生一系列控制信号,告诉数据通路中的多路选择器如何选择、ALU执行什么操作、寄存器是否写入等。它由控制逻辑电路(未来会讲,本质上是一个有限状态机)构成
控制单元根据 opcode
, funct3
, funct7
等指令字段生成信号,驱动数据通路完成正确操作。一个很简单的理解,数据通路是“怎么做”,而控制单元是“做什么”和“何时做”。
指令执行的五个阶段
为了降低设计复杂度,指令执行被分解为五个标准阶段,这也是经典RISC流水线的基础:
- 指令获取 (Instruction Fetch, IF):从指令存储器(IMEM)中读取当前PC指向的指令。
- 指令译码与寄存器读取 (Instruction Decode/Register Read, ID):解析指令,从寄存器堆(Regfile)中读取
rs1
和rs2
指定的寄存器值。 - 执行 (Execute, EX):在算术逻辑单元(ALU)中进行计算(如加减法、逻辑运算)。
- 存储器访问 (Memory Access, MEM):如果是load或store指令,则访问数据存储器(DMEM)。
- 寄存器写回 (Write Back, WB):将结果(来自ALU或DMEM)写回
rd
指定的寄存器。
需要注意的是,在单周期处理器中一条指令会按顺序经历这五个阶段,并在一个漫长的时钟周期内完成,但这并不完全等同于流水线。
硬件构建块
构建数据通路所需的模块包括:
- 组合逻辑元件:
- ALU:执行算术和逻辑运算。其操作(加、减、与、或等)由
ALUSel
控制信号选择。 - 加法器 (Adder):专门用于地址计算,如
PC + 4
。 - 多路选择器 (Mux):根据选择信号(如
BSel
,WBSel
)从多个输入中选择一个输出。
- ALU:执行算术和逻辑运算。其操作(加、减、与、或等)由
- 状态元件:
- 程序计数器 (PC):一个存储当前指令地址的寄存器。
- 寄存器堆 (Regfile):包含32个寄存器(x0-x31)的存储器。支持同时读取两个寄存器(
rs1
,rs2
)和写入一个寄存器(rd
)。写操作由RegWEn
(Register Write Enable)信号控制,且仅在时钟上升沿生效。 - 存储器 (IMEM & DMEM):
- IMEM只读,用于取指;
- DMEM可读写,用于load/store。
- 读操作是组合逻辑(地址有效后输出即有效),写操作是时序逻辑(需要时钟上升沿和写使能信号)。
Time to Build!
事实上,由于在 ISA 中设计指令时考虑到了相关位的统一和复用,这为我们的处理器设计简化了大量操作。所以对于各类型指令,在完成最基础、最统一的部分后,只需要特化当前类型下所特有的功能即可。
R – Type
首先考虑最简单的 R- Type 类型指令:
- 操作:
Reg[rd] = Reg[rs1] op Reg[rs2]
- 数据流:PC -> IMEM -> 指令字段译码出
rs1
,rs2
,rd
-> Regfile读出DataA
,DataB
-> ALUSel 信号根据funct7
和funct3
选择操作 -> ALU计算结果 -> 写回 Regfile 的DataD
端。同时,PC + 4 计算下一条指令地址。 - 关键控制信号:
RegWEn=1
(允许写寄存器),ALUSel
(选择 ALU 操作,例如区分 add 和 sub )。
那么当前指令构建完成时,基本设计流程应当如下:

I – Type
于 R – Type 相比,我们有了新的需求:第二个ALU操作数不再是来自寄存器的Reg[rs2]
,而是来自指令中的立即数。
为了解决这个问题,我们可以在ALU的第二个输入前添加一个多路选择器(BSel
Mux)。该Mux的一个输入来自Reg[rs2]
(用于 R – type),另一个输入来自立即数生成器。
- 立即数生成器 (Imm Gen):负责根据指令的格式(I-type, S-type, B-type等)从32位指令中提取正确的位并进行符号扩展,生成一个32位的立即数。由
ImmSel
信号控制。
注意此处我们新增了两个控制信号 :
BSel
:用于选择ALU的B操作数来源:寄存器 or 立即数;ImmSel
:告诉立即数生成器当前指令是哪种格式。

S – Type
关于 S – Type,我们的新需求是计算内存地址(Reg[rs1] + imm
)并访问数据存储器(DMEM),比如 lw
需要将读出的数据写回寄存器,而 sw
需要将寄存器的值写入内存。
关于内存计算,注意到还是立即数的问题,所以我们可以考虑复用ALU和 BSel
Mux,让 ALUSel = Add
;而关于 DMEM ,这没办法,老老实实添加 DMEM 模块。其地址来自ALU结果,写数据(DataW
)来自Reg[rs2]
;至于数据写回操作,我们选择添加写回选择多路选择器(WBSel
Mux)。对于lw
,WBSel
需选择从DMEM读出的数据(DataR
),而对于R-type/I-type,则选择ALU的结果。
所以对于当前轮,我们新增了如下信号:
MemRW
:控制DMEM的读写(Read/Write)。ImmSel
:新增S
选项,指导立即数生成器从指令的正确位置(inst[31:25]
和inst[11:7]
)提取并拼接出S-type的立即数。RegWEn
:对于sw
指令,该信号必须为0,防止破坏寄存器值。
那么截至目前,我们的构建扩展为了:

B – Type
分支指令需要完成两件事:
- 计算目标地址:
PC + imm
(B-type的imm是2字节对齐的) - 依据
RS1
和RS2
更新 PC ,决定是顺序执行还是跳转
关于条件分支部分,这没啥说的,添加一个分支比较器(Branch Comparator)。输入为Reg[rs1]
和 Reg[rs2]
,输出为 BrEq
(相等)和 BrLT
(小于)信号。BrUn
信号控制进行有符号还是无符号比较。
而偏移地址就只能再添加一个加法器了,因为复用当前的 +4 加法器会导致结构冲突
既然 PC 的输入有两个来源了,所以新增一个 PC 来源多路选择器(PCSel
Mux)也就是理所应当的了。该Mux选择下一个 PC 是来自 PC+4
,还是来自分支目标地址计算器(PC + imm
),或是其他来源( 如 JALR )。
此时的构建扩展为:

J – Type
JAL
(Jump and Link)已经在 B – Type 时考虑实现,通过设置PCSel = taken
和WBSel = PC+4
即可
JALR
(Jump and Link Register):PC = Reg[rs1] + imm
;Reg[rd] = PC + 4
。这需要ALU计算地址,并引入一个新的Mux来选择ALU的A操作数(Asel
Mux),使其可以选择PC
或Reg[rs1]
。LUI
/AUIPC
(U-type):处理20位高位立即数。LUI
直接将立即数写回寄存器,AUIPC
则进行PC + imm
的计算。这通过ImmSel
的U
选项和ALUSel
的直通(Bypass)功能实现。
综上所述,实现 J – Type 的架构为:

至此,我们完成了 R, I, S, B, J 五种基本类型的指令设计,在设计完成后,我们同样需要对成品进行性能分析。
控制与性能分析
当前我们的控制单元包含的信号包括:
PCSel
, ImmSel
, BrUn
, ASel
, BSel
, ALUSel
, MemRW
, RegWEn
, WBSel
那么如何实现它们呢?讲座给出了两种实现方式:
- ROM(只读存储器)实现:将“指令->控制信号”的映射关系直接做成一张巨大的查表。将指令和标志位拼接成地址,该地址对应的ROM数据就是所有的控制信号。
- 这种设计比较简单,易于修改和调试( reprogrammable );但速度较慢(需要地址解码),且花费面积可能较大。
- 组合逻辑(硬连线)实现:使用传统的数字设计方法(真值表 -> 卡诺图 -> 逻辑表达式 -> 逻辑门)来为每个控制信号生成一个优化的组合电路。
- 这种设计速度更快,是高性能处理器的标准做法。相对的,设计更复杂,而且一旦制造完成就无法修改。
而性能衡量这方面,正如前文提及,时钟周期必须满足最慢指令的需求。而对于单周期设计,最慢的指令是lw
。
我们来看一下 lw
的路径:
PC -> IMEM -> RegFile读 -> Imm Gen -> ALU -> DMEM -> WBSel Mux -> RegFile写
对应的时钟周期为:
$$ t_{cost} \geq t_{clk-to-q} + t_{IMEM} + t_{RegRead} + t_{Mux} + t_{ALU} + t_{DMEM} + t_{Mux} + t_{RegSetup}$$
对于所有其他更快的指令(如add
, beq
),它们都在这个漫长的周期内完成了工作,但必须等待整个周期结束。所以单周期 CPU 的硬件利用率极低。
我们可以说单周期设计是理解CPU工作原理的完美教学模型,但它因其极低的性能而没有任何实际应用价值。它的主要价值在于引出了下一个伟大主题:流水线(Pipelining)。
先提一嘴,流水线技术的核心思想就是指令级并行:将指令执行划分成多个阶段(IF, ID, EX, MEM, WB),并让不同的指令同时处于不同的阶段,就像工厂的装配线。这样可以极大地提高硬件利用率和系统吞吐量,尽管单条指令的延迟(Latency)可能反而会增加。不过这是后面讲座的内容了。
讲座还稍微提及了一下控制与状态寄存器 (CSRs),这是一类不同于通用寄存器(x0-x31)的特殊寄存器,用于控制和监控处理器状态(如中断使能、周期计数器、异常原因等),系统中断便与此相关。
写过 PA 3 的都知道 CSRs 的含金量…( -= . -)ノ|
总而言之,这一部分完成了计算机架构的“从零到一”的构建。 我们不仅看到了一个完整的CPU数据通路,更重要的是理解了其设计思路、控制方法以及性能权衡。这为接下来学习流水线——这个几乎所有现代处理器都在使用的核心技术——扫清了最后的障碍,并让我们能够深刻理解流水线所要解决的问题及其带来的巨大优势。
以上
发表回复