ICS04 处理器体系结构
type
status
date
slug
summary
tags
category
icon
password
Processor Architecture
处理器体系结构是计算机系统设计的核心领域之一,它连接了软件和硬件的世界。通过指令集架构(ISA),处理器设计者提供了一个抽象层,允许操作系统和应用程序以硬件可理解的方式运行指令。同时,处理器体系还包含逻辑设计部分,涉及如何用物理电路实现这些指令的执行。
ISA
指令集架构(ISA)是连接计算机软件与硬件的一座桥梁,定义了程序如何与底层硬件进行交互。ISA的设计覆盖从应用程序到物理芯片的整个软硬件协作过程:
Application → Compiler → OS → ISA → CPU → Circuit → Chip
- ISA之上:专注于程序设计和指令序列的执行。
- ISA之下:关注指令的高效执行和硬件设计指导。
Y86-64
CSAPP提出了一种x86-64架构的简化版本,即Y86-64,其主要内容如下:
硬件组成
- 寄存器堆:15个寄存器,每个64位。
- 条件码:包括ZF(零标志)、SF(符号标志)、OF(溢出标志);不包含CF(进位标志)。
- 程序计数器(PC):
%rip
,存储当前指令地址。
- 程序状态(Program Status):用于指示指令执行状态(如错误)。
- 内存:动态随机存储器,采用小端法存储。
指令格式
- 每条指令长度为1-10字节,从内存中逐字节读取。第一个字节决定指令的长度。
- 指令设计比x86-64简化。
- 每条指令都会访问或修改程序状态。
Y86-ISA指令示例
编码规则
- 寄存器通过4位ID编码,与x86一致;
F
(15)表示“无寄存器”。
- 指令由两部分组成:
- 前4位:Instruction Code(指令码)。
- 后4位:Function Code(功能码)。
示例1:加法指令addq
占用2字节,机器码格式为
6 0 ra rb
功能:将寄存器ra和rb的值相加,并将结果存放到rb中。示例2:数据传送指令XXmovq
- 寄存器到寄存器传送:
rrmovq
-2 0 ra rb
。
- 立即数到寄存器传送:
irmovq
-3 0 F rb imm
(10字节)。
- 寄存器到内存传送:
rmmovq
-4 0 ra rb imm
(10字节)。
- 内存到寄存器传送:
mrmovq
-5 0 rb ra imm
(10字节)。
示例3:跳转指令jXX
占用9字节,机器码格式为
7 fn imm
功能码fn
决定跳转条件,立即数imm
为绝对地址。特殊指令
nop
:01
,无操作。
halt
:00
,停止指令执行。
Y86的运行时栈
- 栈顶地址存储在
%rsp
寄存器中。
- 栈从高地址向低地址增长:
push
操作:先减小%rsp
,再将数据写入栈顶。pop
操作:先读取栈顶数据,再增加%rsp
。
push
和pop
的指令码分别为A
和B
。Y86的程序状态(Status Condition)
- AOK:正常执行。
- HLT:遇到
halt
指令,终止运行。
- ADR:地址错误(如非法内存访问)。
- INS:遇到非法指令。
Review:CISC vs RISC
CISC(Complex Instruction Set Computer)
- 基于栈的指令集,使用栈传递参数。
- 算术指令可直接操作内存中的数据。
- 使用条件码(如ZF、SF、OF)记录算术/逻辑操作结果。
- 指令集庞大,包含复杂指令。
- Memory-Oriented:对内存的频繁访问。
- 历史原因:
- 早期内存昂贵,代码长度需尽量缩短。
- 编译器能力有限,复杂指令能降低编译需求。
- 局限性:
- 处理器设计复杂,功耗高,价格昂贵。
RISC(Reduced Instruction Set Computer)
- 精简的指令集,指令长度和执行时间固定。
- Register-Oriented:更多地依赖寄存器操作,减少对内存的访问。
- 固定长度指令:简化取指和解码。
- 精简指令功能:执行、访存、回写更加高效。
- 更少指令类型:多为简单操作。
- 优势:更适合流水线操作:
- 固定执行时间避免流水线停滞。
- 简化指令集便于并行处理。
示例:RISC/ARM
- 基于寄存器的指令集,典型设计包含32个通用寄存器。
- 仅加载和存储指令(Load/Store)访问内存。
- 不使用条件码,测试结果存储于寄存器中。
Logic Design(逻辑设计)
逻辑门与组合逻辑
- 基本逻辑门:与门(AND)、或门(OR)、非门(NOT)。
- 输入高/低电平,输出对输入持续响应(有微小延迟)。
- 组合逻辑:由逻辑门网络组成的电路,例如比较器、加法器等。
- Hardware Control Language (HCL):
- 类似C语言逻辑表达式,用于描述硬件控制逻辑。
- 不同于C语言的短路逻辑,HCL表达式不会省略任何分支。
多选器(MUX)
ALU:Arithmetic Logic Unit,算术逻辑单元
- 堆叠多个运算黑箱(例如累加器、减法、与、异或等)
- 控制信号选择要计算的功能
- 同时改变条件码
时序逻辑与寄存器
寄存器是电路中重要的存储单元。时序逻辑中的“寄存器”不同于上一章提到的“通用目的寄存器”,可称它为“时钟寄存器”,因为它的行为受时钟信号控制。
时钟寄存器由逻辑门组成的D触发器堆叠而成,它在时钟信号上升时加载输入,下降时保持输出稳定。配备时钟寄存器后的时序电路由于不会出现震荡,可以实现反馈与循环。
寄存器堆与RAM
这里的寄存器是“通用目的寄存器”等用于存取数据的寄存器。寄存器堆(Register File)是多个寄存器的集合。寄存器堆通常包括读口和写口,读口通过多选器选择寄存器,输出对应值。写口接收写操作的寄存器地址和数据,并将数据写入对应寄存器中。
寄存器堆的典型设计是2个读口、1个写口。
SEQ/顺序硬件架构
计算机的核心任务是执行程序代码,而程序的执行过程可以被抽象为一系列指令的处理。在硬件层面,这些指令的处理需要经过取指(Fetch)、译码(Decode)、执行(Execute)、访存(Memory Access)和回写(Write Back)五个基本阶段,这些阶段共同构成了一个完整的指令周期(Instruction Cycle)。
SEQ的主要状态单元
- PC(Program Counter)寄存器:存储下一条指令的地址。
- CC(Condition Code)寄存器:存储条件码,用于记录算术逻辑单元(ALU)运算的结果状态。
- 寄存器堆(Register File):用于存储临时变量,由程序显式管理的寄存器集合。
- Memories:包括指令内存(Instruction Memory)和数据内存(Data Memory)。
SEQ的指令处理过程
顺序执行架构处理指令的过程分为以下五个阶段:
阶段 | 描述 |
取指 | 访问指令内存
根据指令长度增加PC,获取下一条指令
输出指令码 |
译码 | 解码指令,提取出源操作数、目标操作数
从寄存器文件中读取操作数值
输出源操作数值 |
执行 | 对源操作数值执行ALU操作
更新条件码寄存器(CC)
输出计算结果 |
访存 | 如果需要,访问数据内存(加载/存储) |
写回 | 将结果保存到寄存器文件
更新PC以获取下一条指令 |
这五个阶段紧密衔接,将顺序电路的稳定性和指令的连续性结合起来。
1. 取指(Fetch)
取指阶段的主要任务是从指令内存中读取指令,具体步骤如下:
- PC的作用:程序计数器(PC)存储当前指令的地址。
- 读取指令:从指令内存中读取PC所指的指令内容,通过Split和Align分为
icode
、ifun
、rA
、rB
、valC
- 更新PC:将PC加上指令长度,生成下一条指令的地址
valP
。

2. 译码(Decode)
译码阶段的任务是解析指令并获取操作数,具体步骤如下:
- 解析操作码:从指令中提取操作码(Opcode)和寄存器序号
srcA
、srcB
。
- 确定操作类型:根据操作码识别指令类型(如加法、减法、跳转等)。
- 读取操作数:从寄存器堆中提取源操作数(Source Operands)
valA
、valB
。

3. 执行(Execute)
执行阶段是指令处理的核心,完成算术或逻辑操作。具体步骤如下:
- ALU操作:将源操作数
valA
、valB
输入算术逻辑单元(ALU),执行指定的运算(如加法、减法、逻辑与等),输出运算结果valC
。
- 条件判断:根据运算结果修改条件码寄存器(
CC
)。
- 若指令需要做条件判断,Cond根据
CC
内容和ifun
计算Cnd
,也即是否跳转。

在SEQ中,为了更好集成电路线,简化硬件设计,我们规定:
对于不需计算、直接传值的指令(如
rrmovq
/irmovq
),将寄存器序号编码在srcA
中,译码阶段提取出valA
,将valB
置为0,ALU输出的valC
即为valA
的值。对于地址传送指令(如
pushq
/popq
/call
/ret
),它们都需要将地址加上或减去8;将寄存器序号编码在srcB
中,译码阶段提取出valB
,将valA
置为+8或-8,通过ALU计算地址。4. 访存(Memory Access)
访存阶段负责与数据内存交互,仅在需要访问内存的指令中执行,也即
load
/store
指令,在Y86-64中是mrmovq
和rmmovq
。具体步骤如下:- 读取内存:从指定地址加载数据
valM
。
- 写入内存:将数据
valA
或valP
存储到指定的内存地址valE
。

5. 回写(Write Back)
回写阶段将计算结果写回寄存器堆,以供后续指令使用。具体步骤如下:
- 寄存器更新:将ALU或内存操作的结果存储到目标寄存器。
- PC更新:根据
valP
为下一条指令准备好新的PC。
寄存器堆的更新仅在时钟上升沿执行,确保写入的稳定性。

实现示例
OPq rA rB
- 取指:
- 以PC为索引,从指令内存读取指令的操作码
OPq
。 - 读取寄存器索引
rA
和rB
,作为srcA
和srcB
。 - 该指令长度为2字节,计算下一条指令的PC值(PC+2),存放到
valP
。
- 译码:
- 从寄存器堆中读取
srcA
和srcB
的值,记为valA
和valB
。
- 执行:
- 使用ALU对
valA
和valB
执行运算,结果存放到valE
。 - 根据结果更新CC寄存器。
- 访存:无访存操作,跳过该阶段。
- 回写:
- 将
valE
写回寄存器堆的rB
寄存器。
- PC更新为
valP
。
cmovXX rA rB
- 取指:
- 以PC为索引,从指令内存读取指令的操作码
cmovXX
。 - 读取寄存器索引
rA
和rB
,作为srcA
和srcB
。 - 该指令长度为2字节,计算下一条指令的PC值(PC+2),存放到
valP
。
- 译码:
- 从寄存器堆中读取
srcA
的值,记为valA
。
- 执行:
- 上面已经提到过,对于不需计算只取原值的操作,将
valB
置为0。 - 通过ALU计算
valA + valB
,结果存放到valE
。 - 根据CC寄存器内容和指令类型计算
cond
,判断条件是否满足,如果不满足,将rB
设置为F
(不存在)。
- 访存:无需访存。
- 回写:
- 将
valE
写回rB
,如果rB
为F
则跳过写回。
- PC更新为
valP
。
call dest
- 取指:
- 读取要跳转到的目标地址
dest
,作为valC
。 - 该指令长度为9字节,计算下一条指令的PC值(PC+9),存放到
valP
。
- 译码:
- 从寄存器堆中读取
rsp
的值,记为valB
。
- 执行:
- 上面已经提到过,
call
需要计算地址的操作(减去8),将valA
置为-8。 - 通过ALU计算
valA + valB
,结果存放到valE
(也即新的rsp
值)。
- 访存:
call
指令需要将下一条指令的地址压入栈中。以valE
为地址,将值valP
写入到内存中。
- 回写:
- 将
valE
写回rsp
(更新rsp
)。
- PC更新为
valC
。
最终,我们得到了SEQ的整体设计:

PIPE/流水线硬件架构
流水线(Pipeline)技术是现代处理器架构中至关重要的一部分,它通过将指令的执行过程划分为多个阶段,以提高指令的吞吐量。流水线化的设计不仅能提升处理器的执行效率,还能有效利用硬件资源。然而,流水线架构也伴随了许多挑战,包括冒险、数据依赖、控制依赖等问题。理解流水线的工作原理及其优化策略,对于设计高效的处理器和编写高性能的代码至关重要。
基本思路
流水线技术的思想来源于日常生活中的许多流水线操作。例如,餐厅做菜时需要经过洗菜、切菜、烹饪、出餐四个步骤,假设每个步骤需要10分钟;在上文SEQ的语境下,餐厅同一时间只处理一道菜,每道菜的出品需要40分钟,餐厅每40分钟才能出品一道菜;然而我们可以引入流水线机制,将四个步骤分配给四个员工处理,洗菜工洗完菜后将菜交给切菜工,立即开始洗下一份菜;这样,虽然每道菜的出品时间仍是40分钟(由于分工带来的时间成本,还可能更慢),但餐厅每10分钟即可出品一道菜。
体现在处理器设计中,流水线将顺序执行的指令流划分为多个阶段,每个阶段由不同的硬件单元执行,允许多条指令并行处理。
首先,我们将SEQ架构抽象为一大块组合逻辑+一组时钟寄存器。时钟信号控制着时钟寄存器的更新周期。假设组合逻辑的延迟为300ps,寄存器读写延迟为20ps,则总延迟为320ps,执行速率(每秒执行的指令数)为3.12GIPS;这意味着时钟周期需要大于320ps。
按照流水线的思路,我们将组合逻辑拆分成三个部分,每个部分的延迟为100ps,并使用三个时钟寄存器进行间隔。这样,每条指令的总延迟变为360ps;虽然指令执行变慢,但每120ps(上一条指令的第一部分执行完毕后)可以接入一条新指令,执行速率提升至8.33GIPS。
这些时钟寄存器称为流水线寄存器,它们在时钟上升沿写入数据,在时钟稳定期间保持稳定输出。时钟周期应大于流水线中最慢部分的延迟。
为进一步提高执行速率,可以将组合逻辑细分为更多的部分,进而引入更多的流水线寄存器。然而随着流水线的加深,寄存器延迟占据总延迟的比例逐渐升高,边际效益递减,收益下降。此外,处理冒险、分支错误和清除流水线的代价也会增加。
PIPE Hardware
区分数据类型
在流水线架构中,常常需要区分不同阶段之间的数据。数据有时稳定地存储在寄存器中,有时暂时地流经组合逻辑。
我们在数据名前面加上大写的S,表示这是存储在阶段S前的流水线寄存器(以S命名)中的数据。
数据名前加上小写的s,表示这是流经阶段S的组合逻辑中的数据。
反馈路径(Feedback Path)
反馈路径是指从后阶段传递到前阶段的电路线,它主要用于传递与指令执行相关的关键信息,例如:
- 预测的程序计数器(PC)值。
- 分支信号(指令是否需要跳转)。
- 从内存中读取的指令地址。
由此,我们在SEQ的五阶段间加入流水线寄存器,调整PC更新位置,即可得到PIPE-架构图:

冒险(Hazards)
在流水线执行过程中,常常会遇到冒险问题,主要包括数据冒险、控制冒险和结构冒险。数据冒险是最常见的一种,指令之间的依赖关系可能导致错误的执行顺序。
数据冒险
数据冒险发生在前一条指令的结果作为后一条指令的操作数时(Read after Write,RAW)。例如我们有如下Y86-64汇编指令序列:
由于我们在PIPE-中部署了五阶段流水线(F-D-E-M-W),在
movq
指令执行到D(译码)阶段时,需要从寄存器堆中提取rbx
的值;但此时addq
指令只执行到E(执行)阶段,rbx
的新值要等到W(写回)阶段结束后才能写入寄存器堆。在实际程序中,这种情况非常常见,通常需要采取相应的策略来解决。
- 策略1:使用
nop
指令填充
在两条指令之间插入空操作(
nop
指令),以确保后一条指令的操作数已被前一条指令写入寄存器。这种方法需要在软件层面修改汇编代码。在上面的例子中,可以在两条指令间插入三条nop
指令:- 策略2:硬件层面插入气泡(Bubble)
与策略1类似,硬件可以在指令进入解码阶段时,检查寄存器堆是否有需要写入的操作。如果有,将该指令暂停执行,直到相关的写入操作完成。此策略通过硬件实现类似
nop
的功能,称为“bubble”。我们需要在PIPE-的基础上加入流水线控制单元,用于实现stall和bubble。stall使寄存器内容被冻结,仍存放上一个周期中的内容;bubble则清除原寄存器内容,改为nop指令的相关内容。
- 策略3:数据转发(Bypass)
- 局限性:如果后续阶段的值仍然在内存中(例如在
mrmovq
指令执行时,该值仍在Memory阶段),此时无法进行数据转发,称为“Load-Use”;可能需要插入“bubble”来兜底。
数据转发是一种常用的优化策略。当指令进入解码阶段时,如果它需要访问的寄存器值已经在后续的执行阶段(例如内存或写回阶段),可以通过转发路径将该值直接传递给当前指令。这可以避免将指令暂停,提升流水线效率。
下图中,通过在Decode阶段加入“Forward”元件,我们可以将所有青色线的起点数据转发到Decode阶段。

控制冒险:预测PC的策略
控制冒险发生在指令的控制流发生变化时,特别是当指令需要更新程序计数器(PC)以确定下一条指令的位置。上一条指令完成Fetch、进入Decode阶段时,处理器就需要计算出下一条指令的地址(更新PC),这会带有一定风险性。具体而言可以分为以下几类:
- 不传递控制:对于没有控制转移的指令,直接将
valP
(PC Increment的输出)设置为下一条PC值,通常不会出错。
- 无条件跳转(Jump)/调用(Call):将
valC
设置为下一条PC值(总是正确的)。
- 条件跳转:对于条件跳转,“总是跳转”策略将
valC
设置为下一条PC值,但只有在跳转条件成立时才是正确的。一般来说,“总是跳转”策略的准确性大约为60%。 - 其他策略:当跳转发生在前面时,总是执行跳转;当跳转发生在后面时,通常不执行跳转。
- 分支预测错误:若预测错误,可以通过将所有不该执行的指令设置为“bubble”来恢复正确的控制流。
- 返回指令(ret):当
ret
指令完成Memory阶段、输出应跳转到的地址前,处理器将不进行任何预测,始终插入Bubble;共需要插入3个“bubble”来确保正确执行。
总结
在加入Forward/转发模块和处理器控制逻辑后,仍有三种可能的冒险情况,分别需要1、2、3个周期的Bubble:
Load-Use冒险:
mrmovq
指令紧跟着一条需要使用其目的寄存器的指令。例如:当
andq
指令进入Decode阶段时,mrmovq
指令刚结束Decode、进入Execute阶段,还无法取出rax
应存放的数据。需要在Decode阶段插入一个Bubble,待momovq
指令进入Memory阶段、从内存中取出数据后,andq
指令才能顺利执行Decode阶段,从m_valM
转发rax
的值。分支预测错误:
当条件跳转指令
jXX
实际不应该被执行时,需要清空流水线。例如:jne
指令进入Decode阶段时,“总是跳转”策略选择让dest
处的subq
指令进入Fetch阶段;当jne
执行Execute阶段时,处理器控制逻辑发现Cnd
为非(也即不应跳转),它需要将Decode和Execute的寄存器插入两个Bubble,清空被错误执行的subq
和addq
指令,并将Fetch阶段PC更新为正确的xorq
所处地址。RET冒险
当
ret
指令进入Decode阶段时,需要插入三个Bubble,等待ret
指令完成Memory、进入Writeback阶段时,再从W_valM
中取出地址更新PC。具体而言,当ret
指令在Decode/Execute/Memory阶段时,都需要将Fetch寄存器置为Stall、将Decode寄存器置为Bubble。总而言之,三种冒险的处理如下:

Prev
ICS03 汇编语言
Next
ICS05 存储器层次结构
Loading...