Skip to content

发射过程的流水线

硬件描述 1 — 200皮秒内的三重奏

发射过程需要在一个时钟周期内完成三件事:广播标签通知等待指令、唤醒匹配的就绪指令、仲裁选择并发射。在5 GHz处理器中,一个周期仅有200 ps——但这三个操作的组合延迟约需250\sim300 ps。矛盾不可调和。解决方案:将发射过程流水线化,把唤醒和选择拆分到不同的流水级。但流水线化引入了新的问题——投机唤醒:假设Load在预期延迟(L1命中,4周期)内完成,提前唤醒依赖指令。如果Load实际未命中(L2延迟12+周期),被唤醒的指令读到的是错误的旧值。处理器必须检测这种情况并取消、重发射相关指令。投机唤醒是发射流水线中"投机"的典型体现。

设计提示

统一视角。处理器设计的本质是在有限的晶体管预算和功耗约束下,通过投机和并行的层层叠加来逼近指令吞吐率的理论上限。投机唤醒正是发射流水线中"投机"的直接体现——它乐观地假设Load将在L1命中的延迟内完成,从而提前唤醒依赖链上的后续指令。这种投机在93%\sim98%的情况下是正确的(对应L1命中率),其性能收益(消除4\sim5周期的等待)远超偶发的重发射代价。这与第 27.0 章中发射队列的ILP缓冲作用一脉相承:队列提供了等待的空间,流水线化提供了投机的时机,二者共同决定了乱序执行的实际吞吐率。仲裁电路的具体设计(第 29.0 章)将进一步展开选择逻辑的实现细节。

第 27.0 章中,我们介绍了发射队列的各种组织方式——集中式与分布式、数据捕捉与非数据捕捉、压缩与非压缩。这些讨论集中在发射队列的"结构"层面,回答了"发射队列存储什么、如何组织"的问题。然而,要理解指令发射在真实处理器中的运作,我们还必须回答一个更加关键的问题:发射过程如何被流水线化?

指令发射涉及多个紧密耦合的操作:等待操作数就绪、从就绪指令中选择一条(或多条)发射、读取操作数值并送往执行单元。这些操作之间存在复杂的时序依赖——例如,一条指令的执行结果可能在同一周期唤醒另一条等待中的指令,而被唤醒的指令需要在下一个周期就被选中并发射。这种紧密的循环依赖使得发射流水线成为超标量处理器中时序最紧张的路径之一。

本章将详细分析发射过程的流水线组织。我们首先讨论非数据捕捉结构和数据捕捉结构各自的流水线划分方式,然后深入分析CDB广播网络的设计挑战,接着讨论Intel等工业界广泛使用的CAM+Payload RAM分离结构,最后讨论指令如何被分配到发射队列中——包括空闲表项的管理、反压机制和资源耗尽时的处理策略。

非数据捕捉结构的流水线

在非数据捕捉(non-data-capture)结构中,发射队列不存储操作数的数据值——它只记录每个操作数对应的物理寄存器标签以及该操作数是否已经就绪。操作数的实际值存储在物理寄存器文件(PRF)中,只有当指令被选中发射时才从PRF中读出。这种结构的核心流水线包含四个关键阶段:标签广播、CAM比较与匹配、仲裁选择、PRF读出与执行。本节将按照信号在流水线中传播的时序顺序,逐一推导每个阶段的硬件实现和时序约束。

标签广播

标签广播(Tag Broadcast)是发射流水线的起点。当一条指令在执行单元中完成计算后,它需要通知发射队列中所有正在等待其结果的指令:"你们等待的数据已经准备好了。"这个通知的方式就是将已完成指令的目的寄存器标签(即物理寄存器编号)广播到发射队列中的所有表项。

广播的时机

标签广播不一定在指令真正写回物理寄存器文件的那个周期发生。对于单周期指令(如简单的ALU操作),标签广播可以在执行的最后一个周期发起——此时结果虽然尚未写入PRF,但已经可以通过旁路网络(bypass network)转发给下游指令。对于多周期指令(如乘法、除法),标签广播的时机取决于微架构的设计选择:

  • 保守策略:在结果真正可用时才广播。这种策略简单安全,但引入了不必要的延迟——依赖指令需要多等一个或多个周期。

  • 提前广播:在结果即将可用时提前广播。例如,一条3周期的乘法指令可以在第2个执行周期就广播标签,使得依赖指令在第3个周期被选中、在乘法结果真正可用时开始执行。这种策略需要精确地计算提前量,以确保依赖指令读取操作数时旁路数据恰好就绪。

让我们通过一个具体的时序推导来理解提前广播的精确计算。假设一条3周期乘法指令MUL在周期TT进入执行单元,其结果在周期T+2T+2末尾可用。如果唤醒-选择延迟为2个周期(即从广播到依赖指令开始执行需要2个周期),那么MUL应当在周期T+0T+0就广播标签——这样依赖指令在T+1T+1被选中,在T+2T+2从旁路网络读取MUL的结果并开始执行。如果等到T+2T+2才广播,依赖指令要到T+4T+4才能开始执行,白白浪费了2个周期。

然而,提前广播面临一个根本性的风险:如果MUL在执行过程中发生了意外的延迟(例如,某些微架构中乘法器的延迟依赖于操作数的值,或者MUL因为资源冲突而被暂停),则提前广播就变成了一个虚假唤醒——依赖指令被唤醒并选中发射,但到达执行阶段时发现操作数尚不可用。此时处理器需要取消已发射的依赖指令,并在后续周期重新发射。这种推测唤醒-取消-重发射机制将在第 29.0 章中详细讨论。

广播的发起点

标签广播信号的物理发起点位于执行单元的输出锁存器。在一个典型的流水线设计中,执行单元在其最后一个流水级的输出端包含一个结果锁存器(result latch),该锁存器同时捕获计算结果值和目的寄存器标签。广播信号从这个锁存器出发,穿过芯片的物理布局到达发射队列阵列。在物理设计层面,执行单元和发射队列在芯片上的相对位置决定了广播线的长度和延迟。

在现代处理器的布局中,发射队列(调度器)通常被放置在执行单元的旁边或上方,以最小化广播线的物理长度。然而,在分布式调度器架构中,不同调度器可能位于芯片的不同区域——例如,整数调度器靠近整数ALU,浮点调度器靠近浮点单元。此时,跨调度器的广播(例如,整数指令的结果需要唤醒浮点调度器中的消费者指令)需要更长的物理线路和更大的驱动能力。

广播网络的结构

广播网络由多条广播总线(broadcast bus)组成。在一个WW-wide的超标量处理器中,每个周期最多可能有WW条指令同时完成执行,因此需要WW条广播总线。每条广播总线承载一个物理寄存器标签(通常为6\sim8位,对应64\sim256个物理寄存器),该标签被同时送达发射队列中的所有表项

标签广播过程:执行单元完成后将目的寄存器标签广播到发射队列的所有表项
标签广播过程:执行单元完成后将目的寄存器标签广播到发射队列的所有表项

广播网络的物理实现是一组长的全局信号线,从执行单元的写回阶段延伸到发射队列的每一行。在一个拥有64个表项的发射队列和4条广播总线的处理器中,每条广播线需要驱动64个比较器的输入——这意味着广播线的负载非常大,需要使用中继缓冲器(repeater)来维持信号完整性和满足时序约束。

我们可以从电气角度定量分析广播线的负载。设每个CAM比较器输入端的栅极电容为CgC_g,则一条广播线驱动NentryN_{\text{entry}}个表项、每个表项2个源操作数时,总负载电容为:

Cbroadcast=2NentryCg+Cwire C_{\text{broadcast}} = 2 \cdot N_{\text{entry}} \cdot C_g + C_{\text{wire}}

其中CwireC_{\text{wire}}为广播线本身的互连电容。对于64个表项和7nm工艺,Cg0.3C_g \approx 0.3 fF,Cwire50C_{\text{wire}} \approx 50 fF,则Cbroadcast2×64×0.3+50=88.4C_{\text{broadcast}} \approx 2 \times 64 \times 0.3 + 50 = 88.4 fF。如果要求信号在40 ps内稳定(5 GHz频率下约1/51/5周期),驱动器需要提供的电流为I=CV/t88.4×1015×0.7/40×10121.5I = C \cdot V / t \approx 88.4 \times 10^{-15} \times 0.7 / 40 \times 10^{-12} \approx 1.5 mA——这对于一个普通的CMOS反相器来说负载偏大,因此需要在广播线上插入中继缓冲器,将其分为2\sim3段。

设计提示

广播网络是发射队列功耗的主要来源之一。每个周期,即使大多数表项的操作数标签与广播标签不匹配,所有比较器仍然必须切换——这是因为CAM(内容寻址存储器)结构的固有特性。在高端处理器中,发射队列的广播网络功耗可以占到整个调度器功耗的30%\sim50%。降低广播功耗的方法包括:(1)使用门控时钟关断已匹配操作数的比较器;(2)采用预匹配(pre-matching)技术,先比较标签的高位子集,只有在预匹配成功时才激活完整的比较器。

广播总线的仲裁

当某个周期内完成执行的指令数量超过广播总线数量时,需要对广播总线进行仲裁。例如,在一个4-wide的处理器中如果有6条指令同时完成,但只有4条广播总线,则有2条指令必须延迟到下一个周期才能广播。这种情况在实践中较为罕见,因为处理器的发射宽度通常等于或小于广播总线数量,而且不同类型的执行单元(整数ALU、乘法器、Load单元等)的完成时间通常不会完全重叠。

然而,在配置了多个延迟相同的执行单元的设计中(例如4个单周期ALU),确实可能出现同一周期4条ALU指令和1条Load指令同时完成的情况——这就需要5条广播总线或仲裁机制。实践中,设计者通常会将广播总线数量设置为等于或略大于发射宽度,并依靠以下策略避免总线冲突:

  1. 错开发射时机:选择逻辑在分配广播总线时就考虑到潜在的冲突。例如,如果当前周期已经有3条ALU指令将在下周期广播,选择逻辑可能推迟第4条ALU指令的发射,为可能的Load完成预留广播总线。

  2. 固定绑定:将每条广播总线静态绑定到特定的执行端口。例如,总线0绑定到ALU0,总线1绑定到ALU1,总线2绑定到乘法器,总线3绑定到Load单元。这种方式彻底消除了仲裁逻辑,但可能在某些周期浪费总线资源(例如乘法器未产出结果时,总线2空闲)。

  3. 动态仲裁:使用一个小型仲裁器,在多个执行单元竞争同一总线时进行裁决。被延迟的广播请求放入一个小队列中,在后续周期重新参与仲裁。

广播有效位

并非每条广播总线在每个周期都承载有效的标签。例如,如果某个执行单元在当前周期没有指令完成,则其对应的广播总线应当被标记为无效。这通过广播总线上的一个有效位(valid bit)来实现。当有效位为0时,CAM比较器应当忽略该总线上的标签值,不触发匹配。

在硬件实现中,有效位可以用两种方式控制比较器的行为:(1)将有效位作为CAM比较结果的门控信号——比较照常进行,但最终的匹配输出被有效位门控(AND操作),只有当有效位为1且标签匹配时才产生有效的唤醒信号;(2)将有效位直接用于门控比较器的时钟或使能信号——当有效位为0时,比较器不翻转,从而节省动态功耗。方式(2)对功耗更友好,因为它消除了无效比较的开关活动,但需要更精细的时钟控制逻辑。

CAM比较与匹配

当广播标签到达发射队列后,每个表项都需要将自己等待的操作数标签与广播标签进行比较——这就是经典的内容寻址存储器(Content-Addressable Memory, CAM)操作。在非数据捕捉结构中,每个发射队列表项通常包含以下字段:

字段位宽说明
有效位(Valid)1位该表项是否包含有效指令
操作码8\sim12位指令的内部操作类型
源操作数1标签(Tag1)7\sim8位第一个源操作数的物理寄存器编号
源操作数1就绪(Rdy1)1位第一个源操作数是否已就绪
源操作数2标签(Tag2)7\sim8位第二个源操作数的物理寄存器编号
源操作数2就绪(Rdy2)1位第二个源操作数是否已就绪
目的寄存器标签(Dst)7\sim8位目的物理寄存器编号
立即数16\sim32位立即数字段
年龄位/序列号6\sim8位用于最老优先选择

非数据捕捉结构中发射队列表项的典型字段

CAM比较的逻辑

对于每个表项的每个源操作数,比较逻辑如下:

  1. 如果该操作数的就绪位Rdy已经为1,则无需比较——该操作数已经就绪,跳过。

  2. 如果Rdy为0,则将该操作数的标签Tag与所有WW条广播总线上的标签逐一比较。

  3. 如果任何一条广播总线上的标签与Tag匹配,则将Rdy置为1,表示该操作数已就绪。

每个表项需要2W2W个比较器(2个源操作数×\times WW条广播总线),每个比较器比较log2P\log_2 P位(PP为物理寄存器数量)。在一个拥有128个物理寄存器(7位标签)和4条广播总线的处理器中,每个表项需要2×4=82 \times 4 = 8个7位比较器。对于一个64表项的发射队列,总共需要64×8=51264 \times 8 = 512个7位比较器——这是一个相当大的硬件开销。

单个发射队列表项中的CAM比较逻辑(2个源操作数$\times$2条广播总线的简化示例)
单个发射队列表项中的CAM比较逻辑(2个源操作数$\times$2条广播总线的简化示例)

比较操作的时序

CAM比较操作是发射流水线中的关键路径之一。一个nn位比较器的延迟包括:

  1. XNOR阵列:将标签的每一位与广播标签对应位进行XNOR操作,产生nn个匹配信号。延迟约为1个逻辑门。

  2. AND归约:将nn个匹配信号进行AND归约,判断是否全部匹配。对于7位标签,这可以用一棵3级深度的AND树实现。

  3. OR归约:将多条广播总线的匹配结果进行OR操作。对于4条广播总线,需要1级OR门。

  4. Rdy位更新:将匹配结果写入就绪位锁存器。

总的比较延迟约为4\sim6级逻辑门(FO4),在5 GHz的工作频率下约为80\sim120 ps——大约占据半个时钟周期的时间预算。这意味着CAM比较通常需要占用一个完整的流水线阶段。

我们可以更精确地推导这个延迟。设FO4反相器延迟为dFO4d_{\text{FO4}},在7nm FinFET工艺下dFO412d_{\text{FO4}} \approx 12 ps。则:

  • XNOR层:1×dFO4=121 \times d_{\text{FO4}} = 12 ps

  • 7位AND归约(3级树):3×dFO4=363 \times d_{\text{FO4}} = 36 ps

  • 4路OR归约:1×dFO4=121 \times d_{\text{FO4}} = 12 ps

  • Rdy锁存器设置:1×dFO4=121 \times d_{\text{FO4}} = 12 ps

合计6×dFO4=726 \times d_{\text{FO4}} = 72 ps。在5 GHz下一个时钟周期为200 ps,因此CAM比较占用约36%的周期时间预算——这还未计入广播线上的信号传播延迟和时钟偏差(clock skew)。将广播线传播和时钟余量加入后,CAM比较实际上需要\sim130\sim150 ps,即约65%\sim75%的周期时间,基本上消耗了一个完整的流水线阶段。

匹配后的就绪判断

当一个表项的所有源操作数都变为就绪状态时,该指令就成为可发射(ready-to-issue)指令。就绪判断只需要将两个(或更多)源操作数的就绪位进行AND操作。如果指令只有一个源操作数(如某些一元操作),则另一个就绪位被硬连线为1。

需要注意的是,就绪判断的AND操作可以与CAM比较重叠进行。具体来说,如果src1在本周期之前已经就绪(Rdy1=1),则只需等待src2的CAM比较结果:一旦src2匹配成功,该指令立即就绪,不需要额外的AND延迟。这是因为AND门的一个输入已经为1,输出完全由另一个输入决定。因此,在最好的情况下(只有一个操作数需要被唤醒),就绪信号在CAM比较完成的同一时刻就可用;在最坏的情况下(两个操作数同时被唤醒),需要额外一级AND门延迟。

已就绪操作数的比较器门控

一个重要的功耗优化是:当操作数已经就绪(Rdy=1)时,关闭该操作数的CAM比较器。这可以通过门控时钟(clock gating)或数据门控(data gating)来实现。具体做法是将Rdy位取反后作为比较器的使能信号:

comparator_enable=ValidRdy\text{comparator\_enable} = \text{Valid} \wedge \overline{\text{Rdy}}

当Rdy=1时,comparator_enable=0,比较器输入被锁定,不产生开关活动。由于发射队列中通常有相当比例的操作数已经就绪(在分配时就绪或在之前的周期被唤醒),这种门控可以显著降低CAM阵列的动态功耗——典型情况下可以减少30%\sim50%的比较器功耗。

CAM位元的电路实现

发射队列的CAM位元(CAM cell)是整个调度器设计中最关键的基本单元。其电路实现直接决定了比较速度、面积和功耗。常用的CAM位元实现方式有以下几种。

NOR型CAM位元。最基本的CAM位元由一个SRAM存储单元(6T)加上一对比较晶体管组成,总共约9\sim10个晶体管。比较操作使用NOR匹配方式:匹配线(match line)被预充电为高电平,如果任何一位不匹配,则匹配线被拉低。所有位共享一条匹配线,因此nn位CAM的匹配检测是一个"线与"(wired-AND)操作——只有当所有位都匹配时,匹配线才保持高电平。

NOR型CAM的优点是速度快(匹配线的放电是并行的,最快的不匹配位决定了放电速度)。缺点是功耗高——即使匹配成功(所有位匹配),预充电仍然消耗功耗;如果不匹配,匹配线的放电也消耗功耗。

NAND型CAM位元。NAND型CAM使用串联的比较晶体管——只有当所有位都匹配时,电流才能从匹配线流过所有串联晶体管到地。NAND型的优点是匹配线不需要预充电(降低了功耗),且在不匹配时串联路径被断开(零功耗)。缺点是速度较慢——匹配信号需要通过nn个串联晶体管的RC链路传播,延迟与位宽nn成正比。

混合型。高性能处理器通常使用混合方案:在高位使用NOR型(快速排除不匹配),在低位使用更紧凑的结构。或者使用分段匹配线——将nn位标签分为kk段,每段使用独立的NOR匹配线,最后用一个AND门合并各段结果。这种分段方式将匹配线的负载降低为n/kn/k位,加速了匹配操作。

硬件描述 2 — 发射队列中的CAM结构

一个32表项、4-wide的非数据捕捉发射队列的CAM结构规模:

  • 物理寄存器标签位宽:7位(128个物理寄存器)

  • 每表项比较器数量:2×4=82 \times 4 = 8

  • 总比较器数量:32×8=25632 \times 8 = 256个7位比较器

  • 每个比较器包含7个XNOR门和一棵3级AND树

  • 广播线总长度:4×7=284 \times 7 = 28条信号线,每条驱动32个比较器输入

  • 功耗估计:在14 nm工艺下,CAM比较每周期的动态功耗约为20\sim40 mW

将队列规模扩大到64表项时,比较器数量翻倍为512个,但更关键的变化是广播线的负载翻倍——每条广播线需要驱动64个比较器输入,信号传播延迟增加约40%。这就是大型发射队列面临的根本时序挑战:CAM比较延迟随队列大小亚线性增长(主要受广播线RC延迟支配),而不是简单地保持恒定。

仲裁与选择

当CAM比较完成后,发射队列中可能有多条指令同时变为就绪状态。选择逻辑(Select Logic)从这些就绪指令中挑选出NN条进行发射(NN为每周期可发射的指令数,通常等于执行端口数量)。选择逻辑的设计将在第 29.0 章中详细讨论,这里只关注选择在流水线中的位置以及选择之后的操作数读出过程。

选择阶段的时序位置

选择逻辑是CAM比较和PRF读取之间的"桥梁"。它的输入是所有表项的就绪信号向量(一个NentryN_{\text{entry}}位的位向量,每一位表示一个表项是否就绪),输出是WW个被选中的表项索引。选择逻辑的延迟直接影响整个唤醒-选择循环的延迟。

对于一个简单的优先级编码器(priority encoder)方式的选择逻辑,延迟约为log2Nentry\log_2 N_{\text{entry}}级逻辑门。对于64个表项,这意味着约6级逻辑门的延迟。如果使用更复杂的最老优先(oldest-first)策略并且队列是非压缩的,则需要额外的年龄比较逻辑,延迟可能更大。

关键的设计决策是:选择阶段是否与CAM比较放在同一个时钟周期中?如果CAM比较占用了\sim70%的周期时间,那么留给选择逻辑的时间只有\sim30%周期——对于64表项的优先编码器来说可能不够。在这种情况下,选择逻辑必须被推迟到下一个周期,使得唤醒-选择延迟至少为2个周期。

现代处理器普遍采用的做法是将唤醒(CAM比较)和选择分配在两个半周期中:CAM比较在时钟上升沿触发,占用前半周期;选择逻辑在时钟下降沿(或前半周期末尾)触发,占用后半周期。这样,唤醒+选择在一个时钟周期内完成,但每个阶段只有半个周期的时间预算。这种设计对CAM比较和选择逻辑的延迟都有严格的要求。

从PRF读取操作数

在非数据捕捉结构中,操作数值存储在物理寄存器文件中,而非发射队列本身。因此,当一条指令被选中发射后,必须使用其源操作数的物理寄存器标签去访问PRF,读取实际的操作数值。这个PRF读取操作需要至少一个时钟周期。

PRF读取的具体过程如下:

  1. 选择逻辑输出被选中指令的表项索引。

  2. 使用表项索引从发射队列中读取该指令的源操作数物理寄存器标签(Tag1和Tag2)。

  3. 将Tag1和Tag2作为PRF的读地址,启动PRF SRAM的读操作。

  4. 经过SRAM的译码、位线读出、灵敏放大器放大后,操作数值出现在PRF的读数据端口。

PRF的读取延迟取决于其大小和端口数量。一个128项、8读4写端口的整数PRF的读取延迟约为8\sim12级FO4(96\sim144 ps),通常占用一个完整的流水线阶段。在更大的PRF中(如256项),读取延迟可能增加到14\sim16级FO4,可能需要占用超过一个周期。

非数据捕捉结构的发射流水线:唤醒$\to$选择$\to$读出$\to$执行
非数据捕捉结构的发射流水线:唤醒$\to$选择$\to$读出$\to$执行

图 28.3展示了非数据捕捉结构的完整发射流水线。从唤醒(标签广播+CAM比较)到选择、再到PRF读出、最后到执行,一共需要4个流水线阶段。关键观察是:执行完成后产生的标签广播会启动新一轮的唤醒-选择循环,这形成了一个闭环。这个闭环的延迟(通常称为唤醒-选择延迟或scheduler loop latency)直接决定了两条存在数据依赖的指令之间的最小间隔:

Tb2b=Twakeup+Tselect+Tread+Texecute T_{\text{b2b}} = T_{\text{wakeup}} + T_{\text{select}} + T_{\text{read}} + T_{\text{execute}}

对于单周期ALU操作,如果Tb2b=4T_{\text{b2b}} = 4个周期,则意味着ADD x3, x1, x2完成后,依赖x3的SUB x5, x3, x4需要等4个周期才能开始执行——这比理想的1周期延迟(背靠背发射)差了3个周期。

唤醒-选择循环的数学建模

可以用排队论的视角来分析唤醒-选择循环对指令吞吐率的影响。将发射队列建模为一个服务系统:指令是"顾客",执行单元是"服务器",Tb2bT_{\text{b2b}}是"服务时间的下界"。

对于一条长度为LL的依赖链(每条指令依赖前一条的结果),在WW-wide处理器中,该链的执行时间为L×Tb2bL \times T_{\text{b2b}}个周期。在此期间,其他W1W-1个执行端口理论上可以执行独立的指令。因此,依赖链的存在将处理器的有效利用率降低为:

U=1W+W1W×Uindep U = \frac{1}{W} + \frac{W-1}{W} \times U_{\text{indep}}

其中UindepU_{\text{indep}}是独立指令流的利用率。如果独立指令充足(Uindep1U_{\text{indep}} \to 1),则U1U \to 1;如果没有独立指令(Uindep=0U_{\text{indep}} = 0),则U=1/WU = 1/W——此时处理器退化为单发射,IPC = 1/Tb2bT_{\text{b2b}}

这个简化模型说明了两个关键点:(1)Tb2bT_{\text{b2b}}对依赖链密集的代码有决定性影响;(2)发射队列的作用是提供足够的指令窗口,使得UindepU_{\text{indep}}尽可能高——即使存在依赖链,也能从其他独立指令中找到可以并行执行的指令。

完整的时序推导

让我们以一个具体的例子来推导非数据捕捉结构中两条依赖指令的完整时序。设指令I1I_1ADD x3, x1, x2(已在执行中),指令I2I_2SUB x5, x3, x4(在发射队列中等待x3就绪)。

  1. 周期TTI1I_1在ALU中完成执行,结果值产生。同时,I1I_1的目的标签P3被放上广播总线。

  2. 周期TT(后半周期)到周期T+1T+1(前半周期):广播标签P3到达发射队列,与I2I_2的src1标签(P3)进行CAM比较,匹配成功,I2I_2的Rdy1被设为1。由于I2I_2的src2(x4对应的P4)之前已经就绪,I2I_2现在完全就绪。

  3. 周期T+1T+1(后半周期):选择逻辑看到I2I_2就绪,将其选中发射。

  4. 周期T+2T+2I2I_2的源操作数标签P3和P4被发送到PRF,启动读取。PRF在本周期内返回x3和x4的值。但是,x3的值在PRF中可能尚未被写入(如果I1I_1的写回发生在同一周期),此时需要通过旁路网络获取。

  5. 周期T+3T+3I2I_2带着操作数值进入ALU开始执行。

I1I_1执行完毕到I2I_2开始执行,经过了3个周期的延迟。如果使用推测唤醒(在I1I_1执行完毕之前就广播标签),可以将这个延迟缩短1\sim2个周期。

压缩唤醒-选择延迟

为了将唤醒-选择延迟缩短到尽可能少的周期,现代处理器采用了多种技术:

  1. 推测唤醒:在指令开始执行之前就预先广播其目的寄存器标签,假设执行将按时完成。这使得依赖指令可以在当前指令执行的同一个周期内就被唤醒。推测唤醒将在第 29.0 章中详细讨论。

  2. 唤醒与选择的合并:将CAM比较和选择逻辑在同一个周期内完成。这要求CAM比较的延迟足够短,或者使用特殊的电路技术(如动态逻辑)来加速。

  3. PRF读取与执行的重叠:在PRF读取的同时启动旁路多路选择器(bypass mux)的设置,使得从旁路获取的操作数可以在与PRF读取相同的周期内被使用。

  4. 两阶段唤醒:将唤醒过程分为"预唤醒"(pre-wakeup)和"最终唤醒"(final wakeup)两个阶段。预唤醒在指令进入执行流水线时就发生(此时仅比较标签的高位子集),如果预匹配成功,则在下一周期进行完整的标签比较。这种方式将CAM比较的延迟分摊到两个周期中,每个周期只需比较一半的标签位。

分阶段唤醒-选择的流水线变体

根据唤醒和选择的时间安排,非数据捕捉结构的流水线可以有多种变体。

变体1:2周期唤醒-选择。唤醒(CAM比较)占用完整的周期nn,选择占用完整的周期n+1n+1。这是最简单的实现——每个阶段有完整的周期时间预算,时序压力最小。但Tb2b=4T_{\text{b2b}} = 4周期(唤醒+选择+读出+执行),对依赖链密集的代码非常不利。

变体2:1周期唤醒-选择(半周期切分)。唤醒在周期nn的前半部分完成,选择在周期nn的后半部分完成。Tb2b=3T_{\text{b2b}} = 3周期(唤醒/选择+读出+执行),比变体1改善了1个周期。但CAM比较和选择逻辑各自只有半个周期的时间预算,对电路速度要求高。

变体3:推测唤醒+1周期选择。唤醒在指令被选中发射的同时进行(推测广播),选择在唤醒的下一周期完成。通过推测唤醒,消费者指令在生产者被选中(而非执行完毕)时就被唤醒。Tb2b=1T_{\text{b2b}} = 1周期(对单周期ALU)——这是现代处理器的标准配置。

变体4:推测唤醒+半周期选择。唤醒在半周期内完成(使用超快速CAM电路),选择在同一周期的后半部分完成。这种超激进的设计可以在某些情况下将多周期指令的Tb2bT_{\text{b2b}}也缩短1个周期,但对电路速度的要求极为苛刻。

在理想情况下,通过推测唤醒和唤醒-选择合并,可以将单周期ALU操作的Tb2bT_{\text{b2b}}压缩到1个周期——即背靠背发射。这意味着在周期TT执行的指令I1I_1,其依赖指令I2I_2在周期T+1T+1就可以开始执行。实现这一目标需要以下时序安排:

  • I1I_1在进入执行单元的同时(而非完成时)就广播标签——这比保守策略提前了1个完整周期。

  • CAM比较和选择在同一周期的两个半周期内完成。

  • I2I_2不从PRF读取操作数,而是直接通过旁路网络从I1I_1的执行结果获取。

性能分析 1 — 唤醒-选择延迟对IPC的影响

考虑一段具有长依赖链的代码(如链式ADD:ADD x1,...; ADD x2,x1,...; ADD x3,x2,...; ...)。设依赖链长度为LL条指令,唤醒-选择延迟为Tb2bT_{\text{b2b}}个周期,则执行这条依赖链至少需要L×Tb2bL \times T_{\text{b2b}}个周期。

在一个4-wide处理器中,如果Tb2b=1T_{\text{b2b}} = 1(理想情况),依赖链执行时间为LL个周期,IPC为1.0(受限于依赖链的串行性)。如果Tb2b=2T_{\text{b2b}} = 2,依赖链执行时间翻倍为2L2L个周期,IPC下降为0.5。

对于整数代码(如SPEC CPU2017的gcc),平均依赖链长度约为3\sim5条指令,IPC对Tb2bT_{\text{b2b}}非常敏感。这就是为什么现代处理器不惜一切代价将单周期ALU操作的Tb2bT_{\text{b2b}}优化到1个周期。

从定量角度来看,将Tb2bT_{\text{b2b}}从2周期缩短到1周期对不同工作负载的影响如下:

工作负载Tb2b=2T_{\text{b2b}}=2时IPCTb2b=1T_{\text{b2b}}=1时IPC
依赖链密集(gcc)1.83.0
ILP丰富(bzip2)3.23.6
混合型(mcf)1.11.4

可见,对依赖链密集的工作负载,Tb2bT_{\text{b2b}}的影响是决定性的。

旁路网络与操作数转发

Tb2b=1T_{\text{b2b}} = 1周期的设计中,操作数不是从PRF读取的——因为PRF中的值尚未被写入。取而代之的是,操作数通过旁路网络(bypass network / forwarding network)直接从执行单元的输出端转发到下一条指令的输入端。旁路网络是一组多路选择器(MUX),每个执行单元的输入端有一个MUX,从所有执行单元的输出和PRF读端口中选择正确的操作数值。

在一个NN端口的执行引擎中,旁路网络的规模为N×NN \times N——每个执行单元的每个输入都需要从NN个可能的旁路源中选择。在一个8-wide的处理器中,旁路网络包含8×8=648 \times 8 = 64个大型MUX,每个MUX的输入宽度为64位(整数数据宽度)。旁路网络的延迟和面积随执行宽度的平方增长,这是限制处理器进一步增宽的重要因素之一。

旁路网络的全连接结构:每个执行单元的每个输入都可以从所有执行单元的输出或PRF中选择操作数
旁路网络的全连接结构:每个执行单元的每个输入都可以从所有执行单元的输出或PRF中选择操作数
::: info 硬件描述 3 — 旁路网络的面积与功耗

旁路网络的硬件开销随处理器宽度的平方增长:

执行宽度MUX总数相对面积MUX延迟
4-wide4×2=84 \times 2 = 8个5-to-1 MUX1×\times\sim2级FO4
6-wide6×2=126 \times 2 = 12个7-to-1 MUX2.6×\times\sim3级FO4
8-wide8×2=168 \times 2 = 16个9-to-1 MUX5.1×\times\sim3级FO4
12-wide12×2=2412 \times 2 = 24个13-to-1 MUX12×\times\sim4级FO4

在14 nm FinFET工艺下,一个8-wide整数旁路网络的面积约为0.02 mm2^2,动态功耗约为30\sim50 mW。虽然这个数值看起来不大,但旁路网络位于处理器的最关键时序路径上,其MUX延迟直接影响Execute阶段的时间预算。

:::

部分旁路网络

为了降低旁路网络的复杂度,某些处理器设计采用部分连接的旁路网络——不是每个执行单元都能从所有其他执行单元获取旁路数据。例如:

  • 整数ALU之间完全旁路(ALU0和ALU1互相连接)。

  • 乘法器结果只旁路到整数ALU,不旁路到Load地址生成单元。

  • 浮点执行单元与整数执行单元之间不设旁路(通过PRF间接传递)。

部分旁路网络降低了面积和布线复杂度,但在某些依赖模式下会引入额外的延迟——当两条指令之间的旁路路径不存在时,后者必须多等一个周期从PRF读取操作数。编译器和微架构协同优化可以尽量减少这种情况的发生频率。

在分布式调度器架构中,部分旁路是自然的结果:每个调度器对应的执行单元之间有完整的旁路网络,但不同调度器之间的旁路路径可能不存在或具有更高的延迟。例如,在AMD Zen系列中,整数调度器内部的ALU-to-ALU旁路延迟为1个周期,但整数结果传递到浮点/SIMD调度器的延迟可能需要5\sim7个周期(通过写回PRF、跨域传递)。

旁路选择信号的生成

旁路MUX的选择信号来自发射队列的选择逻辑和指令的操作数标签。当一条指令B被选中发射时,其源操作数标签与所有正在执行中的指令的目的寄存器标签进行比较,确定应该从哪个旁路源获取数据。这些比较通常在Select和Read阶段完成,选择信号在Execute阶段开始时驱动旁路MUX。

旁路选择信号的生成逻辑可以形式化如下。设指令IBI_B的源操作数标签为Tagsrc\text{Tag}_{\text{src}},同一周期中从NN个执行端口输出的目的标签分别为Tagdst,0,Tagdst,1,,Tagdst,N1\text{Tag}_{\text{dst},0}, \text{Tag}_{\text{dst},1}, \ldots, \text{Tag}_{\text{dst},N-1},以及上一周期的目的标签Tagdst,01,\text{Tag}_{\text{dst},0}^{-1}, \ldots(用于2周期延迟的操作)。则旁路选择信号为:

bypass_seli=(Tagsrc=Tagdst,i)validi\text{bypass\_sel}_i = (\text{Tag}_{\text{src}} = \text{Tag}_{\text{dst},i}) \wedge \text{valid}_i

如果所有bypass_seli\text{bypass\_sel}_i都为0,则选择PRF读端口的数据。如果有多个匹配(理论上不应发生,因为同一物理寄存器不会有多个同时的写入者),需要优先级逻辑选择最新的生产者。

旁路网络的时序约束

旁路MUX位于执行阶段的最前端——它的输出直接连接到执行单元的操作数输入。因此,旁路MUX的延迟是从"时钟上升沿(新周期开始)"到"操作数数据稳定在ALU输入端"这段时间的一部分。

在一个单周期ALU操作中,执行阶段的时间预算需要被以下操作共享:

  1. 旁路MUX选择(\sim2级FO4)。

  2. ALU运算(\sim8\sim12级FO4,取决于操作类型)。

  3. 结果锁存器设置(\sim1级FO4)。

总共约11\sim15级FO4。在5 GHz下(200 ps周期,约17级FO4),这留下约2\sim6级FO4的裕量(margin)用于时钟不确定性和工艺变异。可见,执行阶段的时间预算是非常紧张的——旁路MUX每增加一级延迟,就直接侵蚀ALU的可用时间预算。

这也解释了为什么在非常宽的处理器中(12-wide),旁路MUX可能需要4级FO4而非2级,从而挤压ALU的时间预算——设计者可能需要将执行阶段拆分为2个子阶段(bypass + ALU前半 和 ALU后半),导致整数ALU的有效延迟从1周期变为2周期。这是增宽处理器面临的根本物理限制之一。

多级旁路

在多周期执行单元中,旁路数据可能来自不同的流水级。例如,一条2周期乘法指令在第1个执行周期结束时有一个中间结果,在第2个执行周期结束时有最终结果。旁路网络需要区分这两种情况:

  • 如果消费者指令在乘法的第2周期开始执行,它可以从乘法器的第2级输出获取最终结果(1周期旁路延迟)。

  • 如果消费者指令在乘法的第1周期结束时就被选中发射,但乘法尚未完成——这就是推测唤醒失败的场景,需要取消消费者指令。

因此,多级旁路MUX的输入不仅来自各执行端口的当前输出,还可能来自执行流水线中不同级的中间锁存器。这进一步增加了旁路网络的复杂度,但也是实现低延迟背靠背发射所必需的。

非数据捕捉结构的功耗特征

非数据捕捉结构的功耗特征与数据捕捉结构有本质区别。由于操作数值不存储在发射队列中,非数据捕捉结构的每表项位数较少(\sim30\sim40位),广播线更窄(仅标签),因此CAM阵列的动态功耗较低。然而,非数据捕捉结构需要从PRF读取操作数,PRF的多读端口本身是一个功耗大户。

让我们对比两种结构在一个64表项、4-wide处理器中的功耗分项:

组件非数据捕捉数据捕捉
CAM阵列(比较+更新)18 mW12 mW
广播网络8 mW35 mW
选择逻辑6 mW6 mW
PRF读取(8端口)25 mW0 mW
数据读出MUX0 mW8 mW
旁路网络20 mW20 mW
总计77 mW81 mW

非数据捕捉 vs. 数据捕捉结构的功耗对比(64表项、4-wide、7nm、5 GHz)

有趣的是,两种结构的总功耗在这个配置下非常接近——非数据捕捉结构省下的广播功耗被PRF读取的功耗所抵消。然而,这个平衡点随发射队列大小变化:在更大的队列中(>>96表项),数据捕捉结构的广播功耗增长更快(因为更宽的总线需要驱动更多行),使得非数据捕捉结构在功耗上更有优势。

性能分析 2 — 五步推导:CDB广播网络的每周期切换能量

给定执行端口数EE、IQ条目数NN、物理寄存器标签宽度KK位,推导CDB广播网络每周期的动态能量消耗。

第1步:确定广播总线数量。每个执行端口完成时需要广播其目的标签。每周期最多EE条广播总线同时活跃,每条承载KK位标签。

第2步:计算每条总线的比较器负载。每条广播线驱动IQ中所有NN条表项的两个源标签比较器(src1和src2),扇出为2N2NKK位比较器。

第3步:计算单条总线的每周期切换能量。假设每位平均翻转概率α=0.25\alpha = 0.25(标签位均匀分布),每个比较器输入电容Cin=0.5C_{\text{in}} = 0.5 fF(7 nm工艺),供电电压Vdd=0.75V_{dd} = 0.75 V。单条总线的每周期动态能量为:

Ebus=αK2NCinVdd2E_{\text{bus}} = \alpha \cdot K \cdot 2N \cdot C_{\text{in}} \cdot V_{dd}^2

第4步:代入典型值。E=8E = 8N=96N = 96K=8K = 8(256个物理寄存器):

Ebus=0.25×8×192×0.5fF×0.752=0.25×768fF×0.5625=108fJE_{\text{bus}} = 0.25 \times 8 \times 192 \times 0.5\text{fF} \times 0.75^2 = 0.25 \times 768\text{fF} \times 0.5625 = 108\,\text{fJ}

EE条总线的总能量:Etotal=8×108fJ=864fJE_{\text{total}} = 8 \times 108\,\text{fJ} = 864\,\text{fJ}。在5 GHz下,功耗为:

PCDB=864fJ×5GHz=4.3mWP_{\text{CDB}} = 864\,\text{fJ} \times 5\,\text{GHz} = 4.3\,\text{mW}

第5步:加入互连线电容。实际中广播线的互连电容CwC_w远大于比较器输入电容。若Cw=100C_w = 100 fF/条,则每条总线额外消耗αKCwVdd2=0.25×8×100fF×0.5625=112.5\alpha \cdot K \cdot C_w \cdot V_{dd}^2 = 0.25 \times 8 \times 100\text{fF} \times 0.5625 = 112.5 fJ,8条总线增加8×112.5=9008 \times 112.5 = 900 fJ,功耗增加4.5 mW。

总计PCDB4.3+4.5=8.8P_{\text{CDB}} \approx 4.3 + 4.5 = 8.8 mW。这仅是广播网络本身的功耗——CAM比较器的内部切换功耗(约15\sim25 mW)需要另行计算。CDB广播网络的功耗虽然不是处理器功耗的主要部分,但它与IQ规模NN线性增长——这是大容量发射队列功耗不可忽视的贡献者。

CDB广播网络的设计

公共数据总线(Common Data Bus, CDB)是Tomasulo算法中引入的核心概念。在现代超标量处理器中,CDB的角色由广播网络(broadcast network)或结果总线(result bus)承担。广播网络的设计直接决定了发射流水线的性能和功耗,是发射队列微架构中最具挑战性的物理设计问题之一。

扇出问题

广播网络面临的核心挑战是扇出(fan-out)问题。一条广播线需要将标签(和可能的数据值)同时传送到发射队列的所有表项。设发射队列有NN个表项,每个表项有SS个源操作数,则每条广播线的扇出为N×SN \times S个比较器输入。

让我们来推导扇出对信号传播延迟的影响。设单个比较器输入的电容负载为CinC_{\text{in}},广播线的互连电容为CwC_w,驱动缓冲器的固有延迟为d0d_0,输出电阻为R0R_0。则广播线的延迟可以用Elmore延迟模型估算:

dbroadcastd0+R0(NSCin+Cw) d_{\text{broadcast}} \approx d_0 + R_0 \cdot (N \cdot S \cdot C_{\text{in}} + C_w)

对于一个64表项、2源操作数、4条广播总线的设计,NS=128N \cdot S = 128。假设Cin=0.5C_{\text{in}} = 0.5 fF,Cw=80C_w = 80 fF,R0=500R_0 = 500Ω\Omegad0=10d_0 = 10 ps:

dbroadcast10+500×(128×0.5+80)×1015/1012=10+500×144×103=10+72=82psd_{\text{broadcast}} \approx 10 + 500 \times (128 \times 0.5 + 80) \times 10^{-15} / 10^{-12} = 10 + 500 \times 144 \times 10^{-3} = 10 + 72 = 82\,\text{ps}

在5 GHz下(200 ps周期),82 ps的广播延迟已经占用了41%的周期时间——这还没包括CAM比较本身的延迟。可见,对于大型发射队列,广播线的扇出延迟是决定性的瓶颈。

当发射队列规模扩大到128个表项时,扇出翻倍,广播延迟增加到约10+500×(256×0.5+160)×103=10+144=15410 + 500 \times (256 \times 0.5 + 160) \times 10^{-3} = 10 + 144 = 154 ps——已经接近一个完整的时钟周期。这就解释了为什么超大型集中式发射队列在物理上是不可行的。

分段广播总线

为了解决扇出问题,现代处理器采用分段广播总线(segmented broadcast bus)设计。核心思想是将一条长的广播线分成多段,每段通过中继缓冲器(repeater)驱动,将原本的单级大扇出分解为多级小扇出。

中继缓冲器的插入

最简单的分段策略是在广播线上等距插入中继缓冲器。设广播线被分为KK段,每段驱动NS/KN \cdot S / K个比较器输入。则每段的延迟为:

dsegment=drepeater+Rrep(NS/KCin+Cw/K) d_{\text{segment}} = d_{\text{repeater}} + R_{\text{rep}} \cdot (N \cdot S / K \cdot C_{\text{in}} + C_w / K)

总延迟为KK段的串行延迟之和:

dtotal=Kdsegmentd_{\text{total}} = K \cdot d_{\text{segment}}

存在一个最优的分段数KK^*使得dtotald_{\text{total}}最小。对上述的数值例子,最优分段数通常为K=24K^* = 2 \sim 4段。例如,将64表项的广播线分为3段,每段驱动约43个比较器输入,总延迟可以从82 ps降低到约55 ps——节省了33%的延迟。

H树拓扑的广播线

除了简单的线性分段外,更高级的广播网络拓扑是H树(H-tree)结构。H树是一种二叉分配树,从单一的驱动源出发,经过log2N\log_2 N级的分叉,将信号均匀分配到所有接收端。H树的优点是所有接收端看到的延迟几乎相同(因为路径长度均等),这减少了信号偏差(skew),使得所有比较器几乎同时收到广播标签。

H树拓扑的广播网络:信号从根驱动器出发,经过多级中继缓冲器,到达所有CAM比较器
H树拓扑的广播网络:信号从根驱动器出发,经过多级中继缓冲器,到达所有CAM比较器

分段总线的时序对齐

分段广播总线的一个重要设计约束是时序对齐——所有段的比较器必须在大致相同的时刻完成匹配判断,以便选择逻辑能够在统一的时间点看到所有表项的就绪状态。如果某些段的延迟显著大于其他段,则这些段中的表项的唤醒会比其他表项晚——导致选择逻辑可能在它们就绪之前就完成了本周期的选择,错过这些表项。

为了实现时序对齐,中继缓冲器的尺寸需要被精心设计。距离驱动源较近的段(信号较早到达)可以使用较小的缓冲器(增加延迟以对齐),或者在这些段的匹配线输出上插入额外的延迟元件。实际设计中,物理设计工具会自动进行这种时序对齐优化,但设计者需要在RTL中提供正确的时序约束。

广播线的屏蔽层布线

在物理设计层面,广播线通常使用芯片的高层金属(如M8或M9)布线,因为高层金属更厚、电阻更低,且远离衬底使得耦合电容更小。广播线之间需要插入屏蔽线(shielding wire)以减少串扰(crosstalk)——相邻广播线的同时翻转可能通过电容耦合导致信号失真。

在7nm工艺下,金属线的宽度和间距约为28\sim36 nm(低层金属)到\sim100 nm(高层金属)。广播线的典型配置是:广播信号线-屏蔽线(接地或VddV_{dd})-广播信号线-屏蔽线-...,即广播线与屏蔽线交替排列。这种配置使广播线的有效布线密度降低为1/21/2,但确保了信号质量。

分布式调度器中的广播隔离

分布式调度器架构自然地实现了广播线的分段——每个调度器有自己独立的广播网络,只需要驱动该调度器内部的表项。例如,在一个拥有3个调度器(各32项)的设计中,每条广播线只需驱动32个表项的比较器,而非96个。这将单条广播线的扇出降低为集中式方案的1/31/3,显著改善时序。

然而,分布式方案引入了一个新问题:跨调度器的唤醒。如果指令IAI_A在调度器A中执行,而依赖IAI_A的指令IBI_B在调度器B中等待,则IAI_A的广播标签需要跨越调度器边界到达调度器B。这种跨调度器广播的延迟通常比本地广播多1\sim2个周期,因为信号需要穿过更长的物理距离和额外的缓冲器级。

设计提示

跨调度器唤醒延迟是分布式调度器架构的固有代价。设计者需要在以下两个方案之间权衡:(1)接受跨调度器唤醒的额外延迟(通常1\sim2个周期),这意味着跨调度器的依赖链的Tb2bT_{\text{b2b}}增加;(2)将可能相互依赖的指令尽量分配到同一个调度器中,这需要在分配阶段进行更复杂的依赖分析。方案(2)在实践中难以完美实现,因为分配时可能尚不知道生产者指令在哪个调度器中。因此,大多数设计接受方案(1),并通过编译器调度来尽量减少跨调度器依赖。

广播网络的功耗优化

广播网络是发射队列中功耗最大的组件之一,因为它在每个周期都需要切换大量的信号线。以下是几种常用的功耗优化技术。

预匹配门控

预匹配(pre-matching)技术利用标签的高位子集进行初步筛选。具体做法是:先比较广播标签和存储标签的高kk位(例如高3位),只有当这kk位匹配时,才激活完整的nn位比较器。由于高kk位不匹配的概率为12k1 - 2^{-k}(假设均匀分布),因此大部分比较器不需要激活:对于k=3k=3,87.5%的比较器可以跳过。

预匹配的延迟开销是增加了一级预比较门,约1×dFO41 \times d_{\text{FO4}}。但由于大部分完整比较器被门控关闭,总的动态功耗可以降低70%\sim85%。预匹配是否值得取决于设计者对功耗和面积(额外的预比较器)的权衡。

零位编码

另一种功耗优化是零位编码(zero-bit encoding)。观察到广播总线上的标签值在相邻周期之间通常有较大的汉明距离(因为不同指令的目的标签通常不同),导致广播线每个周期都有大量的位翻转。零位编码的思想是:在广播标签无效时(即该总线本周期不承载有效标签),将总线上的所有位都驱动为0,而不是保持前一周期的值。这样,当总线从无效变为有效时,只有需要为1的位才翻转——减少了平均翻转次数。

动态电压缩放

对于不在时序关键路径上的广播线段(例如队列中位于驱动器附近、信号较早到达的段),可以使用较低的电压摆幅。这种低摆幅(low-swing)信号传输技术可以将广播功耗降低40%60%40\%\sim60\%(功耗与电压的平方成正比),但需要在接收端使用灵敏放大器将低摆幅信号恢复到全摆幅,增加了接收端的面积和延迟。

CDB仲裁与结果总线的管理

在一个NEUN_{\text{EU}}个执行单元共享NbusN_{\text{bus}}条结果总线的设计中,当NEU>NbusN_{\text{EU}} > N_{\text{bus}}时,需要仲裁机制来解决总线竞争。

静态绑定 vs. 动态仲裁

最简单的策略是静态绑定——每个执行单元固定使用一条总线。例如ALU0总是使用bus0,ALU1总是使用bus1。静态绑定完全消除了仲裁逻辑,但灵活性差:如果某条总线空闲而另一条总线过载,无法动态调整。

动态仲裁允许多个执行单元竞争同一条总线。仲裁器的设计需要满足以下约束:

  1. 公平性:不能让某个执行单元长期无法获得总线。通常使用轮转优先级(round-robin)来保证公平。

  2. 确定性延迟:对于推测唤醒机制来说,每条指令的广播时机必须是可预测的。如果仲裁导致不确定的延迟,推测唤醒的提前量就无法准确计算。

  3. 低延迟:仲裁器本身的延迟不能太大——它位于执行单元输出和广播线之间的关键路径上。

在实践中,大多数现代处理器使用半静态策略:每个执行端口有一条"首选"总线,只有在首选总线被占用时才使用备选总线。这种方式在大多数情况下表现得像静态绑定(延迟确定),只在冲突时需要仲裁(极少发生)。

延迟广播的缓冲

当一条指令因总线竞争而无法立即广播时,其结果和标签需要被暂存在一个小型结果缓冲区(result buffer)中,等待总线可用时再广播。这个缓冲区通常很小(每个执行单元1\sim2项),因为总线竞争应该是罕见事件。

如果结果缓冲区溢出(连续多个周期无法获得总线),执行单元需要暂停——不能接受新指令执行。这种暂停会通过选择逻辑传播到发射队列(不选中需要使用该执行单元的指令),间接降低吞吐率。

写回端口与广播端口的统一

在许多设计中,写回PRF广播标签使用同一套总线——即结果总线同时承载写回数据(送往PRF)和广播标签(送往发射队列CAM)。这简化了总线架构,但也意味着PRF写端口的数量等于广播总线的数量。

另一种方案是将写回和广播解耦:广播标签在执行完成时立即发送到CAM阵列,而写回数据可以延迟1\sim2个周期写入PRF。这种解耦的好处是:即使PRF写端口数量小于执行单元数量,标签广播也不会被阻塞。代价是需要一个小型的写回队列(write-back queue)来暂存等待写入PRF的数据,以及额外的旁路网络路径(从写回队列到执行单元输入)。

硬件描述 4 — CDB广播网络的规模估算

对于一个8-wide处理器(8个执行端口,6条广播总线),使用7位物理寄存器标签,发射队列分为3个调度器分区(各40项):

  • 本地广播线:每个分区内6条×\times7位×\times40行 = 每分区1680个比较器输入点。

  • 跨分区广播线:每个分区接收来自其他2个分区的6条广播线 = 额外12条×\times7位×\times40行 = 3360个输入点。

  • 总比较器数:每分区40×2×6=48040 \times 2 \times 6 = 480个7位比较器(仅计本地唤醒);如果包含跨分区唤醒,增加到40×2×18=144040 \times 2 \times 18 = 1440个。

  • 总广播线长度:假设每个分区CAM阵列高度50μ\mum(7nm工艺),3个分区分布在约200μ\mum的区域内。本地广播线长度\sim50μ\mum,跨分区广播线长度\sim100\sim150μ\mum。

  • 功耗估计:本地广播\sim15 mW/分区,跨分区广播\sim10 mW/分区对,总计\sim75 mW。

实际设计中,跨分区广播通常不连接所有总线——只连接最可能产生跨分区依赖的总线子集,以降低面积和功耗。

数据捕捉结构的流水线

数据捕捉(data-capture)结构中,操作数的实际值在写入发射队列时就被"捕捉"并存储在表项中,而不是仅存储标签。这意味着当指令被选中发射时,操作数值可以直接从发射队列中读出,无需再访问PRF。数据捕捉结构消除了PRF读取这个流水线阶段,从而有可能缩短唤醒-选择延迟。

数据的写入时序

在数据捕捉结构中,操作数值的写入发生在两个时刻,每个时刻对应不同的硬件路径和时序约束。

分配时写入

当一条新指令被分配到发射队列时,如果其源操作数已经就绪(即对应的物理寄存器已经包含有效数据),则操作数值在分配时就从PRF读出并写入发射队列表项。具体过程如下:

  1. 重命名阶段查询映射表,确定每个源操作数对应的物理寄存器编号和就绪状态。

  2. 对于已就绪的操作数,在分配周期或下一个周期从PRF读取数据值。

  3. 将读取的数据值写入发射队列表项对应的操作数字段。

  4. 将该操作数的就绪位设置为1。

分配时写入的关键时序约束在于步骤2和步骤3的延迟。PRF读取需要一个完整的周期,而将64位(或更宽)的数据写入发射队列表项的SRAM也需要相当的时间。在某些设计中,分配和PRF读取被安排在不同的流水线阶段——指令先在分配阶段获得表项索引,然后在下一个阶段完成PRF读取和数据写入。这意味着新分配的指令在进入发射队列后还需要一个额外的周期才能变为就绪状态,即使其操作数在分配时就已经可用。

广播时写入

对于分配时尚未就绪的操作数(即其生产者指令尚未执行完成),数据的写入发生在生产者指令完成执行并广播结果时。这个过程结合了标签比较和数据写入:

  1. 生产者指令完成执行后,在写回总线上同时广播其目的寄存器标签和数据值

  2. 发射队列中的每个表项将自己等待的操作数标签与广播标签进行CAM比较。

  3. 如果匹配,将广播总线上的数据值写入表项的操作数字段,同时将就绪位设为1。

数据捕捉结构:广播时同时写入标签和数据值
数据捕捉结构:广播时同时写入标签和数据值

广播时写入的关键时序挑战在于:CAM比较和数据写入必须在同一个时钟周期内串行完成。具体的时序链条为:

  1. 广播标签到达CAM比较器(广播线传播延迟)。

  2. CAM比较器完成匹配判断(XNOR + AND归约延迟)。

  3. 匹配结果驱动写使能信号(1级门延迟)。

  4. 数据值通过写入晶体管写入存储单元(写入延迟)。

这条关键路径的总延迟约为8\sim12级FO4。在5 GHz下,这接近甚至超过一个完整的时钟周期。如果无法在单周期内完成,则数据写入需要延迟到下一个周期——这意味着虽然就绪位在广播周期被设置(用于唤醒和选择),但数据值在下一个周期才真正写入。被选中的指令读出操作数时,可能需要等待一个额外的周期来确保数据已经稳定写入。

数据捕捉的写入电路

数据捕捉结构的写入电路比普通SRAM写入更复杂,因为写入由CAM匹配结果控制(而非地址译码器)。具体电路如下:

每个数据存储位元旁边有一个条件写入控制晶体管。该晶体管的栅极连接到匹配线——只有当CAM匹配成功(匹配线为高)时,数据总线上的值才能通过该晶体管写入存储位元。

这种条件写入的时序特殊性在于:写入使能信号(匹配线)是计算产生的(需要经过CAM比较才能确定),而非直接由地址译码产生。因此,写入使能信号到达存储位元的时间比普通SRAM的写入使能晚了一个CAM比较延迟(\sim4\sim6级FO4)。在写入使能到达后,数据值需要足够的时间稳定写入存储位元(\sim2\sim3级FO4的建立时间)。

总的写入延迟链为:

dwrite=dbroadcast+dCAM+dmatch-to-WE+dsetup3+6+1+3=13级FO4d_{\text{write}} = d_{\text{broadcast}} + d_{\text{CAM}} + d_{\text{match-to-WE}} + d_{\text{setup}} \approx 3 + 6 + 1 + 3 = 13\text{级FO4}

在5 GHz下,13级FO4 \approx 156 ps,接近一个完整的200 ps周期。这意味着数据写入恰好能在单周期内完成——但几乎没有裕量。如果广播线传播延迟稍大(例如在更大的队列中),写入就无法在单周期内完成。

写入端口冲突

在数据捕捉结构中,每个发射队列表项的操作数字段可能同时被两个源写入:(1)分配时的PRF读数据,和(2)广播时的CDB数据。如果某个周期既有新指令分配到该表项(写入已就绪操作数的PRF数据),又有广播匹配(写入未就绪操作数的CDB数据),则需要确保这两个写入操作不冲突。

实际上,由于分配和广播操作的目标是不同的操作数字段(src1和src2),且一个表项的分配和广播在时间上不会同时针对同一个操作数,冲突不会发生。但是,如果考虑表项释放后被重新分配的情况,设计者需要确保释放-重新分配的时序不会覆盖尚未被读出的数据。

数据捕捉的带宽需求

与非数据捕捉结构相比,数据捕捉结构的写回总线宽度显著增加。在非数据捕捉结构中,广播总线只需要携带log2P\log_2 P位的标签(例如7位)。而在数据捕捉结构中,写回总线还需要携带完整的操作数数据值(例如64位)。这意味着写回总线的宽度从7位增加到71位——增长了约10倍。

对于4条写回总线的系统,数据捕捉结构的写回总线总宽度为4×(7+64)=2844 \times (7 + 64) = 284位,而非数据捕捉结构仅为4×7=284 \times 7 = 28位。这些宽总线需要被路由到发射队列的每一个表项,导致布线拥塞和功耗的显著增加。

设计权衡 1 — 数据捕捉vs非数据捕捉的写回总线开销

数据捕捉结构的写回总线面积开销约为非数据捕捉结构的10倍:

  • 非数据捕捉:4条×\times7位 == 28条信号线穿过发射队列。

  • 数据捕捉:4条×\times71位 == 284条信号线穿过发射队列。

然而,数据捕捉结构不需要PRF读端口(或显著减少读端口数量),而PRF的多读端口本身也是面积和功耗的巨大来源。对于一个128项、8读4写端口的PRF,其SRAM面积可能超过发射队列本身。因此,两种结构的总面积对比取决于具体的设计参数——在发射队列较小(<<32项)的设计中,数据捕捉通常更有优势;在发射队列较大(>>64项)的设计中,非数据捕捉可能更有优势,因为数据捕捉要求每个表项都存储完整的操作数值(128位或更多),使得表项面积大幅膨胀。

就绪判断

在数据捕捉结构中,就绪判断的逻辑与非数据捕捉结构相同:当一个表项的所有源操作数的就绪位都为1时,该指令就绪。然而,数据捕捉结构的就绪判断在时序上可能更快,原因在于:

  1. 数据写入和就绪位设置在CAM匹配的同一个周期内完成(或在CAM匹配之后的同一半周期内完成)。

  2. 就绪信号可以直接驱动选择逻辑的请求输入,无需等待额外的PRF读取延迟。

然而,在实践中,数据捕捉结构的CAM比较延迟可能反而更长——因为广播总线上同时携带了数据值,总线的电容负载更大,信号传播速度更慢。这部分抵消了消除PRF读取所带来的时序优势。

就绪位的复位

当一条指令被选中发射并离开发射队列时,它的表项被释放。在数据捕捉结构中,释放表项意味着清除有效位和就绪位。但需要注意的是,如果该指令后来被发现是推测执行的错误路径上的指令(例如由于分支预测失败),则其表项可能需要被恢复——在某些设计中,表项的恢复是一个复杂的问题。

数据就绪与标签就绪的时序差异

在数据捕捉结构中存在一个微妙的时序问题:就绪位(Rdy)在CAM匹配成功后立即被设置为1,但此时数据值可能尚未完全写入存储单元。如果选择逻辑在就绪位被设置的同一个半周期内就选中了该指令,并在下一个半周期试图读出数据——此时数据可能尚未稳定。

为了避免这个问题,有两种策略:

  1. 延迟就绪位设置:在数据值确认写入完成后才设置就绪位。这增加了唤醒延迟(约半个周期),但保证数据读出正确。

  2. 延迟选择:就绪位在CAM匹配时立即设置,但选择逻辑在下一个周期才将该指令纳入选择候选——等效于给数据写入预留了一个周期的余量。

大多数实际设计采用策略(2),因为它不增加唤醒延迟的逻辑深度,只是在就绪位和选择之间插入一级流水线锁存器。

就绪位的原子更新

就绪位的更新需要是原子的——一旦被设置为1,就不应该在同一周期被其他操作重置为0(除非该表项被清空)。然而,在某些竞争条件下可能出现问题:如果一条指令的就绪位刚被CAM匹配设为1,但同一周期该表项因为误预测清空而需要被无效化——此时就绪位的设置和清空发生了冲突。

解决方案是在就绪位的更新逻辑中加入Valid位的检查:

Rdyt+1=(RdytCAM_match)Validt+1\text{Rdy}_{t+1} = (\text{Rdy}_t \vee \text{CAM\_match}) \wedge \text{Valid}_{t+1}

如果本周期该表项被清空(Validt+1=0\text{Valid}_{t+1} = 0),则无论CAM匹配与否,就绪位都被清零。这确保了清空操作的优先级高于唤醒操作。

多操作数指令的就绪逻辑

虽然大多数指令只有2个源操作数,但某些指令可能有3个甚至4个源操作数。例如:

  • 融合乘加(FMA):FMADD rd, rs1, rs2, rs3有3个源操作数。

  • 条件移动CMOV rd, rs1, rs2, cc需要2个数据源和1个条件码源。

  • Store指令:需要Store数据(rs2)和Store地址的2个操作数(base rs1 + offset)。

对于3源操作数的指令,每个表项需要3W3W个比较器(而非2W2W个),就绪判断需要3输入AND门(而非2输入)。在实践中,设计者通常为所有表项统一配置3个源操作数槽,对只需要2个操作数的指令将第3个就绪位硬连线为1。这种统一设计避免了不同表项类型的复杂性,但增加了约50%的比较器数量。

某些处理器(如AMD Zen系列)对不同的调度器分区使用不同的源操作数数量——ALU调度器使用2源,FP调度器使用3源(支持FMA),Store调度器使用3源(地址的base、offset和数据)。这种异构设计减少了不必要的硬件开销。

选择与读出

在数据捕捉结构中,选择逻辑的工作方式与非数据捕捉结构基本相同。关键的不同在于读出阶段:被选中的指令直接从发射队列表项的操作数字段中读取数据值,而不需要访问PRF。

数据捕捉结构的发射流水线:唤醒$\to$选择+读出$\to$执行(消除了PRF读取阶段)
数据捕捉结构的发射流水线:唤醒$\to$选择+读出$\to$执行(消除了PRF读取阶段)

图 28.7展示了数据捕捉结构的发射流水线。与图图 28.3中非数据捕捉结构的4级流水线相比,数据捕捉结构将流水线缩短为3级——选择和读出被合并到同一个周期中,因为操作数数据已经存储在发射队列表项中,不需要额外的PRF访问周期。

然而,需要注意的是,将选择和读出合并到一个周期中也有其代价:选择逻辑和数据读出MUX必须在同一个周期的时间预算内串行完成。如果选择逻辑本身的延迟较大(例如在一个64表项的队列中执行最老优先的选择),则选择+读出的总延迟可能超出单周期的时间预算,迫使设计者将其拆分为两个周期——此时数据捕捉结构的流水线级数优势就消失了。

设计提示

数据捕捉结构的核心优势在于消除PRF读取延迟,但这一优势的前提是选择和读出能够在同一个周期内完成。在大规模发射队列中(>>48个表项),选择逻辑的延迟可能过大,使得选择+读出无法在单周期内完成。因此,数据捕捉结构更适合小型发射队列\leq32个表项),而非数据捕捉结构更适合大型发射队列\geq48个表项)。这也解释了为什么某些处理器(如Intel Pentium 4/Prescott)在发射队列较大时反而选择了非数据捕捉结构。

从IQ表项读出数据的物理实现

当选择逻辑确定了WW条被选中的指令后,需要从发射队列中读出这些指令的操作数值。这个读出操作本质上是一个WW端口、NN行的SRAM读操作——使用选中表项的索引作为地址,从操作数存储阵列中读出对应的数据。

对于一个32表项的发射队列、4条选择通道、每条通道需要读出2个64位操作数的配置,读出MUX的总规模为4×2=84 \times 2 = 8个32-to-1、64位宽的多路选择器。这些MUX的延迟约为log232=5\log_2 32 = 5级2-to-1 MUX(每级约1.5×dFO41.5 \times d_{\text{FO4}}),总延迟约7.5×dFO4907.5 \times d_{\text{FO4}} \approx 90 ps。加上选择逻辑本身约5×dFO4605 \times d_{\text{FO4}} \approx 60 ps的延迟,选择+读出的总延迟约为150 ps——刚好在5 GHz下200 ps周期的时间预算之内。

如果队列规模扩大到64表项,读出MUX变为64-to-1(6级),延迟增加到约9×dFO41089 \times d_{\text{FO4}} \approx 108 ps,加上选择逻辑的增加延迟,总共约180\sim200 ps,接近或超出单周期预算。这就是为什么大型数据捕捉队列在时序上不可行。

Payload RAM:CAM与数据存储的分离

前面讨论的非数据捕捉结构和数据捕捉结构各有优劣。现代高性能处理器广泛采用的是一种混合结构——将发射队列拆分为两个物理上分离的部分:一个紧凑的CAM阵列负责标签比较和就绪判断,一个标准的SRAM阵列(称为Payload RAM)负责存储操作数数据和其他指令信息。这种CAM+Payload RAM的分离架构被广泛认为是Intel Core微架构系列和AMD Zen系列处理器实际采用的设计方式。

分离架构的动机

为什么要将CAM和数据存储分离?核心原因是CAM和SRAM的设计目标冲突

  1. CAM需要速度:CAM阵列位于唤醒-选择关键路径上,其延迟直接影响Tb2bT_{\text{b2b}}。CAM位元(cell)必须使用高性能但面积较大的电路风格(如动态逻辑、差分匹配线)。

  2. 数据存储需要密度:每个表项的操作数数据(2×64=1282 \times 64 = 128位或更多)需要大量的存储容量。如果将这些数据直接放在CAM阵列中,CAM表项的面积会膨胀到不可接受的程度,反过来增加广播线的长度和延迟。

  3. 访问模式不同:CAM阵列在每个周期都被广播标签访问(并行搜索),而数据只在指令被选中发射时才需要读出(索引访问)。对所有数据都执行CAM式的并行搜索是浪费的。

通过将CAM和数据存储分离,可以独立优化每个部分:

  • CAM阵列只存储标签(\sim20位/表项)和控制信息(\sim10位/表项),总共约30位/表项。

  • Payload RAM存储操作数数据、立即数、目的标签、操作码等,总共约200\sim300位/表项。

  • CAM阵列使用高速但低密度的电路,Payload RAM使用标准的高密度SRAM位元。

CAM阵列的设计

在分离架构中,CAM阵列的每个表项只包含以下信息:

字段位宽说明
有效位(Valid)1位该表项是否包含有效指令
源1标签(Tag1)7\sim8位第一个源操作数的物理寄存器编号
源1就绪(Rdy1)1位第一个源操作数是否已就绪
源2标签(Tag2)7\sim8位第二个源操作数的物理寄存器编号
源2就绪(Rdy2)1位第二个源操作数是否已就绪
执行端口类型2\sim3位该指令需要哪种类型的执行端口
年龄信息6\sim8位用于最老优先选择
总计\sim27\sim30位远小于完整数据捕捉表项的200+位

CAM阵列表项的字段(分离架构)

由于CAM表项非常紧凑(仅约30位),CAM阵列的物理面积远小于等效的全数据捕捉结构。这带来了三个直接的好处:

  1. 更短的广播线:CAM阵列的物理高度更小,广播线更短,传播延迟更小。

  2. 更小的比较器负载:CAM行更窄,比较器的输入电容更小。

  3. 更快的CAM操作:上述两个因素共同使得CAM比较可以在更短的时间内完成。

CAM阵列的布局

在物理设计中,CAM阵列通常被组织为一个矩形阵列,行方向对应各个表项,列方向对应标签的各个位。广播线沿列方向布置(垂直穿过所有行),标签存储和比较器沿行方向布置。选择逻辑位于CAM阵列的底部或侧面,接收所有行的就绪信号。

由于CAM阵列是时序关键组件,物理设计者通常会对其进行全定制(full-custom)设计——手动绘制版图而非使用自动布局布线工具。这种全定制设计可以比自动综合的结果快15%\sim25%,但需要更多的设计时间和验证工作量。

Payload RAM的设计

Payload RAM是一个标准的多端口SRAM阵列,通过地址索引而非内容搜索来访问。当选择逻辑从CAM阵列确定了被选中指令的表项索引后,该索引被发送到Payload RAM作为读地址,读出该指令的操作数数据。

Payload RAM的内容

每个Payload RAM表项存储与对应CAM表项关联的所有"大数据"字段:

字段位宽说明
操作码(opcode)8\sim12位内部micro-op操作类型
目的寄存器标签7\sim8位用于结果写回
源1物理寄存器标签7\sim8位用于PRF读取或旁路选择
源2物理寄存器标签7\sim8位用于PRF读取或旁路选择
立即数16\sim32位指令中的立即数字段
ROB索引8\sim10位该指令在ROB中的位置
分支标志/epoch2\sim4位用于误预测清空
功能单元类型2\sim3位冗余存储,用于读出后的路由
总计\sim60\sim95位

Payload RAM表项的字段

注意,在纯粹的分离架构中,Payload RAM不存储操作数数据值——操作数值仍然在PRF中,在指令被选中后通过PRF读取获得。Payload RAM只存储指令的"元数据"(标签、操作码、控制信息等)。

然而,在某些混合设计中,Payload RAM存储操作数数据值(类似于数据捕捉)。此时Payload RAM的每个表项扩大到200\sim300位。这种"full payload"设计消除了PRF读取延迟,但Payload RAM的面积和读出延迟相应增加。

Payload RAM的读取时序

Payload RAM的读取与PRF读取并行进行:

  1. 选择逻辑输出WW个被选中表项的索引。

  2. 索引同时被发送到Payload RAM和PRF。

  3. Payload RAM返回操作码、目的标签、立即数等控制信息。

  4. PRF返回操作数数据值。

  5. 控制信息和数据值一起被送往执行单元。

由于Payload RAM和PRF的读取是并行的,Payload RAM的读取不在关键路径上——只要Payload RAM的延迟不超过PRF的延迟即可。在实践中,Payload RAM通常比PRF更小(行数更少,位宽也可能更小),因此读取更快。

CAM + Payload RAM分离架构:CAM负责唤醒和选择,Payload RAM和PRF并行提供数据
CAM + Payload RAM分离架构:CAM负责唤醒和选择,Payload RAM和PRF并行提供数据

分离架构的时序分析

让我们推导分离架构的完整发射流水线时序。设指令I1I_1(生产者)在周期TT完成执行,指令I2I_2(消费者)在发射队列中等待I1I_1的结果。

  1. 周期TTI1I_1完成执行,广播目的标签到CAM阵列。如果使用推测唤醒,则广播可能发生在I1I_1进入执行单元时(提前若干周期)。

  2. 周期TT(后半周期):CAM阵列完成匹配,I2I_2的就绪位被设置。选择逻辑在同周期后半段选中I2I_2

  3. 周期T+1T+1I2I_2的表项索引被发送到Payload RAM和PRF。Payload RAM返回I2I_2的操作码和控制信息。PRF返回I2I_2的操作数值(或通过旁路网络从I1I_1的结果获取)。

  4. 周期T+2T+2I2I_2带着操作数值进入执行单元。

这个时序给出了Tb2b=3T_{\text{b2b}} = 3个周期(从I1I_1开始执行到I2I_2开始执行)。通过推测唤醒,可以将Tb2bT_{\text{b2b}}进一步缩短到1\sim2个周期。

硬件描述 5 — Intel Core微架构的调度器结构

Intel Core(Skylake及后续)微架构的调度器被认为采用了CAM + Payload RAM的分离架构。其特征包括:

  • 统一调度器端口(Unified Reservation Station):约97个表项(Skylake),在后续代中扩展到160个表项(Golden Cove)。

  • 分布式组织:调度器被分为多个调度器分区(scheduler partition),每个分区负责特定类型的执行端口。

  • 推测唤醒:对单周期ALU操作实现Tb2b=1T_{\text{b2b}} = 1周期的背靠背发射。

  • Payload RAM:存储micro-op的完整操作码、源/目的标签、立即数和控制信息。操作数值不在Payload RAM中,而是从PRF读取或通过旁路获得。

  • 功耗管理:CAM阵列使用激进的门控技术,已就绪的操作数的比较器被关闭;Payload RAM在空闲行使用保持模式降低泄漏功耗。

Payload RAM的写入路径

Payload RAM的写入发生在指令分配阶段——当一条新指令被分配到发射队列时,其元数据(操作码、标签、立即数等)被写入Payload RAM对应的表项。

写入时序

写入过程的时序链条如下:

  1. 分配逻辑确定空闲表项索引(从空闲列表获取)。

  2. 指令的各字段被组装成Payload RAM的写入数据。

  3. 表项索引作为写地址,写入数据被送入Payload RAM的写端口。

  4. SRAM完成写入操作。

同一周期内,分配逻辑还需要将标签和就绪信息写入CAM阵列的对应行。CAM阵列的写入和Payload RAM的写入可以并行进行,因为它们使用相同的表项索引但写入不同的物理存储结构。

多端口写入

WW-wide的处理器中,每周期最多分配WW条指令,因此Payload RAM需要WW个写端口。多写端口的SRAM在面积和时序上都比单端口SRAM更昂贵。常用的优化手段包括:

  • 银行化(Banking):将Payload RAM分为WW个bank,每个bank只有1个写端口。不同指令的表项索引被分配到不同的bank中(通过索引的低位选择bank)。这要求空闲列表在分配时能够提供分布在不同bank中的索引——这可以通过bank-aware的空闲列表管理来实现。

  • 时分复用(Time-division multiplexing):在一个时钟周期的不同相位(phase)进行不同指令的写入。例如,在4-wide处理器中使用2端口SRAM,在前半周期写入指令0和1,后半周期写入指令2和3。这要求SRAM的访问延迟小于半个时钟周期。

  • 副本(Replication):维护Payload RAM的WW个副本,每个副本有1个写端口和1个读端口。所有WW个副本被同时写入相同的数据,但每个副本只负责一个读端口的读出。这种方式用面积换取端口数。

写入数据的来源

Payload RAM的写入数据来自解码和重命名阶段的输出。在一个典型的流水线中,数据在以下时间点生成:

  • 操作码:在解码阶段确定。

  • 源/目的物理寄存器标签:在重命名阶段确定。

  • 立即数:在解码阶段从指令中提取。

  • ROB索引:在分配阶段从ROB空闲列表获取。

  • 分支标志/epoch:在分配阶段从分支控制逻辑获取。

这些数据从不同的流水线阶段汇聚到分配阶段,被打包成Payload RAM的写入字。打包逻辑需要在分配阶段的时间预算内完成,通常只需要简单的线连接(wire routing)和少量的多路选择。

Payload RAM的容量与面积权衡

Payload RAM的面积与发射队列的容量和每表项的位宽成正比。设发射队列有NN个表项,每表项的Payload位宽为BpB_p位,写端口数为WwW_w,读端口数为WrW_r,则Payload RAM的面积可以估算为:

Apayload=NBpf(Ww,Wr)Abitcell A_{\text{payload}} = N \cdot B_p \cdot f(W_w, W_r) \cdot A_{\text{bitcell}}

其中f(Ww,Wr)f(W_w, W_r)是端口数对位元面积的乘数因子(单端口f=1f=1,双端口f1.4f \approx 1.4,4端口f2.2f \approx 2.2),AbitcellA_{\text{bitcell}}是单个SRAM位元的面积。

以7nm工艺为例,Abitcell0.021A_{\text{bitcell}} \approx 0.021μ\mum2^2(6T SRAM),对于N=64N=64Bp=80B_p=80位、双端口(f=1.4f=1.4)的配置:

Apayload=64×80×1.4×0.021=150μm20.00015mm2A_{\text{payload}} = 64 \times 80 \times 1.4 \times 0.021 = 150\,\mu\text{m}^2 \approx 0.00015\,\text{mm}^2

这个面积非常小——仅约0.00015 mm2^2。相比之下,同一工艺下PRF(128项、8读4写端口、64位宽)的面积约为:

APRF=128×64×3.5×0.021602μm20.0006mm2A_{\text{PRF}} = 128 \times 64 \times 3.5 \times 0.021 \approx 602\,\mu\text{m}^2 \approx 0.0006\,\text{mm}^2

可见,Payload RAM的面积远小于PRF。即使将Payload扩展为"full payload"(包含操作数数据,Bp250B_p \approx 250位),面积也仅约0.0005 mm2^2,仍然与PRF在同一数量级。这表明从面积角度看,Payload RAM不是发射队列设计的瓶颈——CAM阵列的面积和广播线的面积才是主要关注点。

设计权衡 2 — Payload RAM配置的设计空间

Payload RAM的设计者需要在以下维度之间权衡:

  • 字段宽度:存储更多字段(如包含操作数数据)可以消除PRF读取延迟,但增加面积和读出延迟。

  • 端口数量:更多的读/写端口支持更高的吞吐率,但增加位元面积和访问延迟。

  • 银行化程度:更多的bank降低每个bank的端口需求,但增加bank选择逻辑的复杂性和面积开销。

  • 冗余字段:某些字段(如源标签)在CAM阵列中已经存储,是否也在Payload RAM中冗余存储?冗余存储增加面积,但简化读出路径(不需要同时从CAM和Payload读取然后合并)。

在大多数工业设计中,Payload RAM不存储操作数数据(采用纯分离架构),字段宽度约80\sim100位,使用银行化来支持多端口访问。这是面积和延迟的最佳平衡点。

三种结构的综合比较

特征非数据捕捉数据捕捉CAM+Payload
操作数存储位置PRFIQ表项PRF(Payload存元数据)
广播线宽度窄(仅标签)宽(标签+数据)窄(仅标签)
IQ表项面积CAM小+SRAM中
PRF读端口需求
CAM速度慢(负载大)快(CAM紧凑)
发射流水线级数4级3级3\sim4级
最适队列规模\geq48项\leq32项32\sim128项
典型应用早期设计早期设计Intel Core/AMD Zen

三种发射队列结构的综合比较

从表表 28.5可以看出,CAM+Payload RAM分离架构在现代设计中占据主导地位,因为它成功地解耦了"快速唤醒"和"大容量存储"这两个冲突的需求。

Payload RAM的旁路整合

在分离架构中,Payload RAM的读出数据中包含源操作数的物理寄存器标签。这些标签在PRF读取阶段被用作PRF的读地址,同时也用于旁路选择信号的生成。

从Payload RAM到旁路MUX

当一条指令被选中发射后,其操作数的获取有两种路径:

  1. PRF路径:源标签\toPRF读端口\to64位数据值\to旁路MUX的"PRF"输入。

  2. 旁路路径:源标签与当前正在写回的指令的目的标签比较\to如果匹配,从结果总线直接取值\to旁路MUX的"bypass"输入。

旁路路径的比较逻辑需要知道"当前正在写回的指令的目的标签"——这个信息来自上一周期(或更早)被选中发射的指令。在推测唤醒机制下,这些信息在选择阶段就已知(因为推测唤醒需要广播这些标签)。

旁路选择的关键时序路径为:

dbypass_sel=dPayload_read+dtag_compare+dMUX_sel_setupd_{\text{bypass\_sel}} = d_{\text{Payload\_read}} + d_{\text{tag\_compare}} + d_{\text{MUX\_sel\_setup}}

其中dPayload_readd_{\text{Payload\_read}}是Payload RAM的读出延迟,dtag_compared_{\text{tag\_compare}}是旁路标签比较延迟,dMUX_sel_setupd_{\text{MUX\_sel\_setup}}是MUX选择信号的建立时间。这条路径必须在旁路MUX需要数据之前完成。

Payload RAM读出与PRF读出的时序对齐

由于旁路MUX需要同时看到PRF数据和旁路数据(以及选择信号),这三者必须在时序上对齐:

  • PRF读出数据在周期T+1T+1的末尾到达MUX的PRF输入端。

  • 旁路数据(来自上周期执行单元的输出锁存器)在周期T+1T+1的开头就已可用。

  • MUX选择信号在周期T+1T+1的中段到达(基于Payload RAM中的标签与旁路标签的比较)。

由于旁路数据比PRF数据更早可用(提前约半个周期),MUX可以先试探性地选择旁路数据,如果旁路选择信号最终无效,再切换到PRF数据。这种投机性旁路选择可以减少旁路MUX在关键路径上的延迟。

发射队列与执行引擎的接口

发射队列的输出接口是连接调度域和执行域的"桥梁"。一条指令从被选中到进入执行单元,需要经过以下数据传递:

  1. 操作码传递:Payload RAM读出的操作码被送往执行单元的解码逻辑,确定ALU应执行的具体操作(加法、减法、逻辑运算等)。

  2. 操作数传递:PRF读出的值(或旁路值)被送往执行单元的操作数输入锁存器。

  3. 目的标签传递:Payload RAM读出的目的标签被送往结果总线控制逻辑,用于在执行完成后广播。

  4. 控制信息传递:ROB索引、分支标志等控制信息被送往流水线控制逻辑,用于后续的提交和异常处理。

这些数据和控制信号从Payload RAM和PRF出发,经过布线到达执行单元。在物理设计中,Payload RAM和PRF通常被放置在执行单元附近(相距<<100μ\mum),以最小化传递延迟。

发射队列的分配与释放

在指令能够进入发射队列之前,必须先为其分配(allocate)一个空闲的发射队列表项。分配过程是发射流水线的"入口",它决定了哪个表项被用来存储新到达的指令。分配的效率直接影响处理器的吞吐率——如果分配过程成为瓶颈,即使后端有空闲的执行资源,也无法被充分利用。

空闲表项的管理

发射队列需要一种机制来跟踪哪些表项是空闲的、可以被分配给新指令。主要有两种管理方式:

位向量方式

使用一个NN位的空闲位向量(free-entry bit vector),其中NN为发射队列的表项数。每一位对应一个表项:0表示该表项已被占用,1表示空闲。分配新指令时,从位向量中找到一个(或多个)为1的位,将其设为0,并返回对应的表项索引。释放表项时,将对应位设为1。

使用位向量和优先编码器管理空闲表项
使用位向量和优先编码器管理空闲表项

位向量方式的优点在于逻辑简单,且能够在O(1)O(1)时间内判断是否有空闲表项(只需对位向量进行OR归约)。缺点在于:当需要每周期分配WW个表项时,优先编码器需要找到WW个最低位的1——这是一个WW-of-NN编码器问题,其延迟随WWNN的增大而增长。

对于4-wide分配和64个表项的发射队列,44-of-6464优先编码器的延迟约为4\sim5级逻辑门(使用树形结构)。如果需要在单周期内完成分配,这个延迟需要被纳入分配阶段的时间预算中。

让我们详细推导WW-of-NN优先编码器的实现。最直接的方法是使用级联清除(cascaded clear)结构:

  1. 第1次编码:从NN位向量中找到最低位的1,输出其位置作为第1个空闲表项索引,并将该位清除为0。

  2. 第2次编码:从清除后的(N1)(N-1)位向量中找到最低位的1,输出其位置作为第2个空闲表项索引,并再次清除。

  3. 重复WW次,得到WW个空闲表项索引。

这种级联方法的延迟为W×dencoderW \times d_{\text{encoder}}WW次串行编码),对于W=4W=4N=64N=64,延迟约为4×5=204 \times 5 = 20级逻辑门——过于缓慢。

更高效的方法是并行前缀(parallel prefix)编码器:利用前缀和(prefix sum)技术,在O(logN)O(\log N)级逻辑门内同时产生WW个空闲表项索引。其基本思想是:

  1. 计算位向量的前缀和(每个位置ii的前缀和为位向量中[0,i][0, i]范围内1的个数)。

  2. kk个空闲表项是前缀和首次等于kk的位置。

并行前缀编码器的延迟为O(logN)O(\log N)级逻辑门,不随WW增大而增加(只要WNW \leq N)。对于N=64N=64,延迟约为log264=6\log_2 64 = 6级逻辑门。

FIFO方式

使用一个空闲列表FIFO(free list FIFO)来管理空闲表项。FIFO初始时包含所有表项的索引(0到N1N-1)。分配新指令时,从FIFO头部弹出一个(或多个)表项索引;释放表项时,将释放的表项索引压入FIFO尾部。

使用FIFO管理空闲发射队列表项
使用FIFO管理空闲发射队列表项

FIFO方式的优点在于分配和释放都是O(1)O(1)操作(直接从头部弹出或尾部压入),且支持多端口操作(WW-wide分配只需要WW个读端口)。缺点是FIFO本身需要额外的存储空间(NNlog2N\lceil\log_2 N\rceil位的表项索引),以及头尾指针的管理逻辑。

在实践中,大多数高性能处理器采用FIFO方式管理发射队列的空闲表项,因为FIFO的分配延迟更低、更可预测。但对于压缩式发射队列(compacting issue queue),FIFO方式不太适用——因为压缩操作会改变表项的相对位置,使得FIFO中的索引失效。

FIFO的实现细节

空闲列表FIFO的硬件实现需要考虑几个关键问题:

循环缓冲区:FIFO通常实现为一个循环缓冲区,使用头指针(head)和尾指针(tail)来管理。头指针指向下一个要弹出的条目,尾指针指向下一个要写入的位置。当head=tail\text{head} = \text{tail}时,FIFO为空(无空闲表项);当(tail+1)modN=head(\text{tail} + 1) \mod N = \text{head}时,FIFO为满(所有表项都空闲——即发射队列完全为空)。

多端口弹出:对于WW-wide分配,FIFO需要在单周期内弹出WW个条目。最简单的实现是将FIFO组织为WW个交替(interleaved)bank,每个bank每周期弹出一个条目。弹出后,头指针直接增加WW。这种设计要求初始化时将索引交替分配到各bank中(索引0到bank 0,索引1到bank 1,...,索引W1W-1到bank W1W-1,索引WW到bank 0,以此类推)。

释放与分配的同步:释放的表项索引可能在任何周期到达(当指令完成执行时),而分配发生在前端的分配阶段。释放和分配可能在同一周期发生。设计需要确保在同一周期释放的表项不会立即被同一周期的分配使用——因为释放入队需要一个周期的延迟才能反映在FIFO的有效条目中。这通常不是问题,因为分配使用的是FIFO头部的条目,而释放写入的是FIFO尾部。

批量分配与释放

在高性能处理器中,分配和释放操作通常以批量方式进行,而非逐条处理。

批量分配

在一个WW-wide的处理器中,每个周期的分配阶段需要同时处理最多WW条新指令。这些指令可能需要不同数量的发射队列表项:

  • 大多数指令需要1个表项。

  • 某些复杂指令(如x86的PUSH被分解为store + SP更新两条micro-op)需要2个或更多表项。

  • 在micro-op融合(micro-op fusion)的架构中,两条micro-op可能共享一个表项。

批量分配的实现要求空闲列表管理逻辑能够在单周期内提供WW个(或更多)空闲索引,并原子地更新管理结构。对于FIFO方式,这意味着头指针一次前进WW步;对于位向量方式,这意味着WW-of-NN编码器需要同时输出WW个位置。

批量释放

释放操作发生在指令离开发射队列时。根据释放策略的不同(发射时释放、完成时释放、两阶段释放),每周期释放的表项数量可能与发射宽度或完成宽度相等。

批量释放的一个挑战是释放请求可能不连续:在同一周期中,被释放的表项可能来自发射队列的任意位置(对于非压缩队列)。将这些分散的释放请求高效地归还到空闲列表中需要多入口的写逻辑。对于FIFO方式,需要WW个写端口(或通过bank化来减少端口需求)。

分配-释放的流水线平衡

在稳态运行中,每周期的分配速率应等于释放速率——否则发射队列会逐渐填满或清空。然而,在瞬态(如长延迟缓存未命中、分支误预测恢复后的突发分配)中,分配速率可能短暂超过释放速率。发射队列的容量必须足够大以吸收这些瞬态波动。

从定量角度分析:设发射队列有NN个表项,平均分配速率为rar_a条/周期,平均释放速率为rdr_d条/周期。如果ra>rdr_a > r_d(例如在缓存未命中导致大量指令等待时),队列填满的时间为:

Tfull=NrardT_{\text{full}} = \frac{N}{r_a - r_d}

对于N=64N=64ra=4r_a = 4rd=2r_d = 2(一半指令因缓存未命中而积压),Tfull=32T_{\text{full}} = 32个周期。这意味着在缓存未命中发生后约32个周期,发射队列就会填满并触发反压。

分配策略

分配策略决定了新指令被放置到发射队列的哪个位置。主要有两种策略:

顺序分配

在顺序分配(in-order allocation)中,新指令被依次放入从FIFO头部弹出的表项中。指令在发射队列中的位置由分配的时间顺序决定——先分配的指令占据先弹出的表项。这种策略的优点是简单,且天然地保持了指令的程序顺序信息(越老的指令占据越早分配的表项)。

然而,顺序分配有一个潜在问题:如果发射队列的FIFO返回的空闲表项索引是"分散"的(例如索引为3、17、28、45),则新分配的指令在物理上不是连续存放的。这对压缩式发射队列是一个问题,因为压缩操作需要在相邻表项之间移动数据。

随机分配

在随机分配中,新指令可以被放置到任何空闲表项中,不考虑特定的顺序。这种策略在位向量管理方式中自然出现——优先编码器返回的是"第一个空闲表项"的索引,这个索引取决于当前的占用模式,对新指令来说是"随机"的。

随机分配的优点是不需要FIFO结构,使用位向量即可。缺点是指令在发射队列中的物理位置与其程序顺序没有任何关系,这使得最老优先(oldest-first)的选择策略实现更加复杂——需要额外的年龄标签或年龄矩阵来确定指令之间的年龄关系。

特征顺序分配随机分配
空闲表项管理FIFO位向量
分配延迟O(1)O(1)O(logN)O(\log N)
指令位置规律性有序无序
压缩兼容性
年龄信息隐含在位置中需要额外标签
实现复杂度

两种分配策略的比较

分布式发射队列的分配

在使用多个分布式发射队列的处理器中(例如Intel的分离式整数/浮点调度器),分配过程还涉及到将指令路由到正确的发射队列。路由决策基于指令的类型(整数ALU指令进入整数调度器,浮点指令进入浮点调度器,Load/Store指令进入内存调度器)。当某个发射队列已满而其他队列还有空间时,需要做出权衡:是暂停该类型指令的分配(可能阻塞流水线),还是将指令路由到一个"不太理想"但有空间的队列?

在某些设计中(如AMD Zen系列),每个分布式调度器可以接受多种类型的指令(例如整数ALU和地址生成共享同一个调度器),这种"共享调度器"策略提供了更好的负载均衡,但增加了选择逻辑的复杂度。

分布式分配的另一个挑战是分配端口的竞争。如果一个周期内有4条指令需要分配,但其中3条是整数ALU指令、1条是浮点指令,则整数调度器需要在一个周期内分配3条指令,而浮点调度器只需分配1条。整数调度器的分配端口可能成为瓶颈——如果它只有2个分配端口,则第3条整数指令必须等到下一周期。设计者需要为每种调度器配置足够的分配端口,同时避免过度配置带来的面积浪费。

位向量方式的详细设计

位向量方式是最直观的空闲表项管理方案,但其高效实现需要精心的电路设计。

位向量的更新逻辑

位向量的更新发生在两个时刻:分配时(将对应位从1清除为0)和释放时(将对应位从0设置为1)。这两个更新操作可能在同一周期发生——某些表项被分配(位清零),另一些表项被释放(位置位)。

位向量的更新逻辑可以形式化为:

FreeVect+1[i]=(FreeVect[i]Releaset[i])Alloct[i]\text{FreeVec}_{t+1}[i] = (\text{FreeVec}_t[i] \vee \text{Release}_t[i]) \wedge \overline{\text{Alloc}_t[i]}

其中Releaset[i]\text{Release}_t[i]表示周期tt表项ii被释放,Alloct[i]\text{Alloc}_t[i]表示周期tt表项ii被分配。释放优先于分配——如果同一表项在同一周期既被释放又被分配,则先释放后分配,结果仍为已占用(0)。

批量优先编码器的电路

WW-of-NN优先编码器是位向量方式的性能瓶颈。其高效实现基于前缀扫描(prefix scan)结构:

  1. 计算位向量的前缀OR(prefix OR):PrefOR[i]=FreeVec[0]FreeVec[1]FreeVec[i]\text{PrefOR}[i] = \text{FreeVec}[0] \vee \text{FreeVec}[1] \vee \cdots \vee \text{FreeVec}[i]

  2. 检测上升沿FirstFree[i]=PrefOR[i]PrefOR[i1]\text{FirstFree}[i] = \text{PrefOR}[i] \wedge \overline{\text{PrefOR}[i-1]},即FirstFree[i]=1\text{FirstFree}[i]=1当且仅当位ii是第一个为1的位。

  3. 对于WW-of-NN编码器,用类似的方法找到第2个、第3个、...、第WW个为1的位。

前缀OR的计算使用Kogge-Stone或Ladner-Fischer前缀树,延迟为O(logN)O(\log N)级逻辑门。找到第kk个为1的位需要在前缀和上做kk次阈值检测——使用前缀加法器(prefix adder),总延迟仍然是O(logN)O(\log N)

对于N=64N=64的位向量和W=4W=4的分配宽度,使用Kogge-Stone前缀树的4-of-64编码器延迟约为log264+2=8\log_2 64 + 2 = 8级逻辑门(前缀计算6级 + 阈值检测2级)。在5 GHz下约96 ps——刚好在半周期预算内完成。

分配过程的流水线时序

分配操作涉及多个步骤的协调,理解其时序对于把握发射流水线的整体延迟至关重要。

分配阶段的内部时序

一个典型的分配阶段在单个时钟周期内需要完成以下操作:

  1. 资源检查\sim2级FO4):并行检查IQ、ROB、PRF、LSQ的空闲资源是否足够。

  2. 空闲索引获取\sim3\sim5级FO4):从空闲列表中弹出WW个空闲表项索引。

  3. 写入CAM\sim4级FO4):将源操作数标签和就绪位写入CAM阵列的对应行。

  4. 写入Payload RAM\sim3级FO4,与CAM写入并行):将操作码、目的标签等写入Payload RAM。

  5. 初始唤醒检查\sim2级FO4):检查新写入的表项是否所有操作数都已就绪(即分配时就可以立即参与选择)。

步骤1和2是串行的关键路径(资源不足时不能获取索引),总延迟约5\sim7级FO4。步骤3和4与步骤2有数据依赖(需要知道索引),因此也在关键路径上。整个分配阶段的总延迟约10\sim14级FO4。

在5 GHz下(200 ps周期),14级FO4(每级\sim12 ps)= 168 ps,刚好在单周期预算之内。但如果处理器频率进一步提高,分配阶段可能需要拆分为2个流水线阶段——第一个阶段完成资源检查和索引获取,第二个阶段完成CAM和Payload写入。

分配与唤醒的竞争

分配写入CAM阵列和唤醒广播标签到CAM阵列可能在同一个周期发生。这引入了一个潜在的竞争条件:如果一条新指令IBI_B在本周期被分配到表项kk,同时它的生产者IAI_A在本周期完成执行并广播标签——IBI_B能否在本周期就被唤醒?

答案取决于分配写入和广播到达表项kk的相对时序:

  • 如果分配写入先完成(IBI_B的标签已写入CAM行kk),然后广播标签到达——CAM比较器会正确匹配,IBI_B在本周期被唤醒。

  • 如果广播标签先到达,但此时CAM行kk尚未被写入(仍是旧数据或无效数据)——匹配不会发生,IBI_B错过了本周期的唤醒。

为了避免第二种情况导致的唤醒遗漏,设计者通常采用以下策略之一:

  1. 旁路唤醒:在分配阶段,额外添加一个并行的比较器,直接比较新指令的源标签与本周期广播的标签。如果匹配,新指令在分配时就被标记为就绪(Rdy=1写入CAM行)。这种方式确保新指令不会错过任何唤醒机会,但增加了分配阶段的硬件复杂度。

  2. 接受一个周期的延迟:新分配的指令在分配周期不参与唤醒,要到下一个周期才开始响应广播。如果它的生产者恰好在分配周期广播,则需要多等一个周期——在下一周期,该操作数的就绪信息通过查询记分板(scoreboard)或重命名映射表来更新。这种方式简单,但在某些情况下增加了1个周期的延迟。

大多数现代处理器采用策略(1),因为1个周期的额外延迟对依赖链密集的工作负载影响显著。

反压机制

当发射队列中没有空闲表项时,新的指令无法被分配,导致流水线暂停(stall)。这种由下游资源不足引起的流水线暂停称为反压(back-pressure)。反压机制是维持处理器流水线正常运作的关键控制通路。

反压的传播链路

发射队列满导致的反压会向上游传播:

  1. 分配/分发阶段暂停:无法将新解码的指令送入发射队列。

  2. 重命名阶段暂停:由于分配阶段暂停,重命名阶段积压的指令无法前进。

  3. 解码阶段暂停:重命名阶段暂停后,解码器输出被阻塞。

  4. 取指阶段暂停:如果指令队列(IQ/IDQ)也被填满,取指也会暂停。

发射队列满引起的反压向上游传播
发射队列满引起的反压向上游传播

反压传播的一个关键参数是传播延迟——从发射队列满信号产生到上游最远阶段(取指)实际暂停之间的周期数。在一个典型的设计中,分配到取指之间有3\sim4级流水线,因此反压信号需要3\sim4个周期才能传播到取指阶段。在这3\sim4个周期的传播延迟中,上游各阶段仍然在继续处理指令——这些"管道中的"指令需要有地方暂存。

这就是为什么处理器在各流水线阶段之间设有级间缓冲区(inter-stage buffer)或流水线锁存器(pipeline latch)——它们不仅传递数据,还在反压发生时暂存无法前进的指令。缓冲区的深度必须足够大,以容纳反压传播延迟期间上游产生的指令。

反压的频率与IPC影响

发射队列满引起的反压频率取决于多个因素:

  • 发射队列大小:更大的发射队列能够容纳更多的等待指令,减少反压的频率。但更大的队列也意味着更高的面积、功耗和选择逻辑延迟。

  • 发射/执行延迟:指令在发射队列中停留的时间越长(例如等待长延迟操作的结果),队列越容易被填满。

  • 前端供给速率:更宽的解码和重命名宽度意味着指令更快地进入发射队列,增加了队列满的概率。

  • 后端吞吐率:执行单元的数量和利用率决定了指令离开发射队列的速率。

性能分析 3 — 发射队列大小与IPC的关系

在SPEC CPU2017整数基准测试中,使用模拟器测量不同发射队列大小对IPC的影响(4-wide超标量处理器):

IQ大小IPCIQ满暂停比例
16项2.125%
32项2.88%
48项3.13%
64项3.31%
96项3.4<<0.5%

从16项增加到48项带来了47%的IPC提升,但从48项增加到96项只带来了10%的提升。这表明发射队列大小存在收益递减效应——超过一定容量后,增加更多表项的边际收益迅速下降。现代高性能处理器(如Intel Golden Cove、AMD Zen 4)通常使用约96\sim128项的发射队列(分布式,总计),这是面积/功耗与IPC之间的平衡点。

提前反压信号

为了减少反压引起的流水线气泡,某些处理器使用提前反压(early back-pressure)信号。具体做法是:当发射队列的占用率接近满(例如剩余空闲表项W\leq W,其中WW为分配宽度)时,提前向上游发送暂停信号。这比等到队列完全满时再暂停多提供了1\sim2个周期的预警时间,使得上游流水线能够更优雅地停止。

提前反压的关键参数是水位线(watermark)——空闲表项数量低于多少时触发反压。水位线通常设置为W+dW + d,其中WW为每周期最大分配数,dd为从发出反压信号到上游实际停止之间的流水线延迟(通常为1\sim2个周期)。如果水位线设置得过高,会导致不必要的暂停(发射队列实际上还有空间);如果设置得过低,可能在反压信号传播的过程中队列就已经溢出。

让我们用一个具体的数值例子来推导最优水位线。设处理器为4-wide(W=4W=4),从分配阶段到发射队列之间有1级流水线延迟(d=1d=1),则最优水位线为W+d=4+1=5W + d = 4 + 1 = 5。这意味着:

  • 当空闲表项数量5\leq 5时,发出反压信号。

  • 在下一个周期,上游停止分配。但在这个传播周期内,已经有最多W=4W=4条指令正在进入发射队列。

  • 因此,当上游最终停止时,发射队列中的空闲表项数量可能低至54=15 - 4 = 1

  • 如果水位线设为4(太低),则可能出现44=04 - 4 = 0,即队列恰好满——没有安全余量。

  • 如果水位线设为8(太高),则约有84=48 - 4 = 4个表项在反压期间是空置的——浪费了4个表项的容量。

基于信用的流量控制

一种更精细的反压机制是基于信用(credit-based)的流量控制。上游维护一个信用计数器(credit counter),初始值等于发射队列的总容量NN。每当上游向发射队列发送一条指令时,信用计数器减1;每当下游释放一个表项时,向上游返回一个信用(credit),计数器加1。当信用计数器为0时,上游停止发送。

基于信用的流量控制相比水位线方式更精确——它精确地跟踪了下游可用的空间数量,不需要设置保守的水位线。其代价是需要一条从下游到上游的信用返回线(credit return path),该路径的延迟会影响系统的有效吞吐率。

在高性能处理器的内部流水线中,基于信用的控制被广泛使用。信用返回通常在指令离开发射队列(发射或取消)后的1\sim2个周期内完成。

信用计数器的实现

信用计数器是一个log2(N+1)\lceil \log_2 (N+1) \rceil位的加减计数器。每当上游发送一条指令时,计数器递减(减1或减WW,取决于每周期发送的指令数);每当收到一个信用返回信号时,计数器递增。

计数器的更新必须在单周期内完成(否则会引入额外的延迟)。对于WW-wide的发送和最多WW个同时返回的信用,计数器的更新逻辑为:

credit_cntt+1=credit_cnttWsent,t+Wreturn,t\text{credit\_cnt}_{t+1} = \text{credit\_cnt}_t - W_{\text{sent},t} + W_{\text{return},t}

其中WsentW_{\text{sent}}为本周期实际发送的指令数(00WW),WreturnW_{\text{return}}为本周期收到的信用返回数(00WW)。

净变化量Δ=WreturnWsent\Delta = W_{\text{return}} - W_{\text{sent}}可能为正(信用增加)或负(信用消耗),范围为[W,W][-W, W]。计数器加法器只需要log2(2W+1)\lceil \log_2 (2W+1) \rceil位的输入——对于W=4W=4,这是一个3位加/减操作,延迟约2\sim3级FO4,完全可以在单周期内完成。

反压信号的时序裕量

反压机制的正确性要求:当反压信号有效时,上游在下一周期确实停止发送。这要求反压信号从产生到到达上游的总延迟不超过1个时钟周期。

在物理设计中,发射队列(反压信号源)和分配阶段(反压信号接收方)之间的物理距离可能导致信号传播延迟。如果发射队列和分配阶段在芯片布局中距离较远(例如分别位于核心的后端和前端,物理距离>>500μ\mum),反压信号可能需要额外的中继缓冲器,增加传播延迟。

为了保证反压信号的及时到达,设计者通常在物理布局中将反压信号线标记为高优先级时序路径(high-priority timing path),物理设计工具会为其分配最短的布线路径和最强的驱动器。

多资源的联合反压

在实际处理器中,分配阶段需要同时检查多种资源的可用性——不仅是发射队列,还包括ROB、物理寄存器文件、Load/Store队列等。任何一种资源不足都会触发反压。

性能分析 4 — 各种资源耗尽导致的暂停分布

在一个典型的4-wide超标量处理器中(ROB=256项,IQ=64项,PRF=128个物理寄存器),运行SPEC CPU2017整数基准测试时,各种资源耗尽导致的暂停分布如下:

暂停原因暂停周期占比对IPC的影响
ROB满3.2%-0.15
IQ满(整数)1.8%-0.09
IQ满(内存)2.5%-0.12
PRF满0.6%-0.03
Load队列满1.1%-0.05
Store队列满0.8%-0.04
总计10.0%-0.48

ROB满是最常见的暂停原因,其次是发射队列满。增大这些结构的容量可以减少暂停,但每种资源的边际收益递减。优化的关键在于找到各种资源之间的均衡配置——使得没有任何一种资源成为显著瓶颈。

资源向量的维护

为了支持高效的联合反压检查,处理器维护一组资源计数器(resource counter),每种资源一个。计数器的值表示该资源的当前空闲数量。

资源计数器名称更新方式
ROB空闲项rob_free_cnt分配时减WW,提交时加WcW_c
IQ空闲项iq_free_cnt分配时减WW,释放时加WrW_r
PRF空闲项prf_free_cnt分配时减DD,提交时加DcD_c
LQ空闲项lq_free_cnt分配Load时减1,Load提交时加1
SQ空闲项sq_free_cnt分配Store时减1,Store提交时加1

其中WW为分配宽度,WcW_c为提交宽度,WrW_r为IQ释放宽度,DD为本周期分配指令中需要目的寄存器的数量,DcD_c为本周期提交指令中释放旧物理寄存器的数量。

每个计数器的位宽为log2N+1\lceil \log_2 N \rceil + 1位(额外1位防止下溢)。分配许可信号为:

alloc_permit=rResources(r_free_cntW)\text{alloc\_permit} = \bigwedge_{r \in \text{Resources}} (\text{r\_free\_cnt} \geq W)

这个比较操作(每个资源一个W\geq W比较器,结果AND在一起)的延迟约为log2N+1\lceil \log_2 N \rceil + 1级FO4(比较器延迟+AND门),对于N=256N=256(ROB)约9级FO4。

联合反压检查的实现方式是:在分配阶段的开始,并行检查所有资源的可用性(使用各自的空闲计数器或水位线比较器),然后将结果进行AND操作——只有所有资源都有足够空间时才允许分配。这个并行检查不在关键路径上(可以与分配阶段的其他操作重叠),因此不会增加流水线延迟。

设计提示

发射队列的设计体现了超标量处理器中一个反复出现的主题:规模与速度的矛盾。更大的发射队列提供了更大的指令窗口、减少了反压导致的暂停,有利于提升IPC。但更大的队列意味着更慢的CAM比较、更慢的选择逻辑、更高的功耗和更大的面积。处理器设计者必须在这两个维度之间找到最佳的平衡点。这个平衡点随工艺节点的演进而变化——在更先进的工艺中,同等面积预算下可以实现更大的队列,但逻辑门延迟的缩减逐渐放缓,使得时序约束仍然是主要挑战。

表项的释放时机

发射队列表项何时被释放是一个影响性能和正确性的重要设计选择。释放时机的选择直接影响发射队列的有效利用率和推测恢复的能力。

三种释放策略

发射时释放

指令一旦被选中发射,其表项立即被释放。这种策略最大化了发射队列的利用率,但如果发射后发现指令需要被取消(例如由于推测唤醒失败或分支误预测),已释放的表项信息已经丢失,需要重新分配和填充。

发射时释放的实现非常简单:选择逻辑在选中一条指令的同时,将该表项的有效位清除,并将表项索引归还空闲列表。这个操作可以与选中操作在同一周期内完成。

然而,发射时释放面临推测唤醒失败的问题。考虑以下场景:

  1. 指令IAI_A(3周期乘法)在周期TT开始执行。

  2. 推测唤醒在周期TT广播IAI_A的目的标签,唤醒依赖指令IBI_B

  3. IBI_B在周期T+1T+1被选中发射,表项被释放。

  4. IAI_A在周期T+1T+1因为流水线冲突而被取消。

  5. IBI_B在周期T+2T+2到达执行阶段,发现IAI_A的结果尚不可用——IBI_B需要被取消并重新发射。

  6. IBI_B的表项已经被释放,无法从发射队列中重新发射!

为了解决这个问题,采用发射时释放策略的处理器通常需要一种重放机制(replay mechanism):被取消的指令不是回到发射队列,而是被送回流水线的更早阶段(如重命名或分配阶段)重新经历完整的调度过程。这增加了恢复延迟,但简化了发射队列的管理。

完成时释放

指令在执行完成并确认正确后才释放表项。这种策略更加保守,支持发射后的取消和重发射,但降低了发射队列的有效利用率——一条正在执行中的指令仍然占据着表项。

完成时释放的优势在于支持简单的重发射:如果推测唤醒失败,被错误发射的指令仍然在发射队列中,只需将其就绪位重置为0(撤销唤醒),等待生产者真正完成后重新参与唤醒-选择过程。

完成时释放的代价是发射队列的有效容量减少。设指令从发射到完成的平均延迟为LL个周期,每周期发射WW条指令,则平均有W×LW \times L条"已发射但未完成"的指令占据发射队列表项。对于W=4W=4L=3L=3(平均),这意味着约12个表项被"已发射"的指令占据。在一个64表项的队列中,有效容量降为约52个表项。

两阶段释放

指令被选中发射时标记为"已发射"但不释放表项;执行完成后才真正释放。在需要重发射时,可以恢复到"已发射"之前的状态。这是最灵活的策略,但也增加了控制逻辑的复杂性。

两阶段释放在每个表项中引入一个已发射位(issued bit):

  • 指令被选中发射时,issued=1。该指令不再参与选择(被排除在就绪向量之外),但表项保持有效。

  • 指令执行完成时,表项被真正释放(Valid=0)。

  • 如果需要重发射,将issued=0,指令重新成为选择候选。

释放策略的性能对比

三种释放策略对有效发射队列容量和性能的影响可以通过以下分析来量化。

有效容量分析

设发射队列有NN个表项,每周期发射WW条指令,平均指令执行延迟为LL个周期(包括等待旁路数据的时间)。在稳态下,各策略的有效容量(可用于新指令的表项数)为:

  • 发射时释放:有效容量=N= N。所有未被占用的表项都可以立即使用,因为指令一发射就释放。

  • 完成时释放:有效容量=NWL= N - W \cdot L。正在执行中的指令占据WLW \cdot L个表项。对于W=4W=4L=2.5L=2.5(平均),约10个表项被占用。在N=64N=64的队列中,有效容量约54项。

  • 两阶段释放:有效容量与完成时释放相同=NWL= N - W \cdot L,因为已发射指令的表项同样被占据。区别在于推测失败时的恢复更快。

推测失败的恢复代价

推测唤醒失败时,各策略的恢复代价不同:

  • 发射时释放:表项已释放,指令信息丢失。需要从流水线更早的阶段(重命名或分配)重新注入指令。恢复代价:8\sim15个周期(取决于流水线深度)。

  • 完成时释放:表项仍在,只需将issued位清零,指令重新参与选择。恢复代价:2\sim3个周期。

  • 两阶段释放:同完成时释放,恢复代价2\sim3个周期。

性能分析 5 — 释放策略对IPC的综合影响

使用模拟器比较三种释放策略在SPEC CPU2017基准测试中的表现(64项发射队列、4-wide处理器、L1命中率90%):

策略有效IQ容量推测失败代价IPC
发射时释放64项高(重新分配)3.15
完成时释放54项低(原地重发射)3.25
两阶段释放54项低(原地重发射)3.28

令人或许意外的是,完成时释放策略的IPC反而高于发射时释放。这是因为虽然有效容量较小(54 vs. 64项),但推测失败的低恢复代价带来的收益更大——在L1未命中率10%的情况下,约有5%\sim8%的动态指令经历推测唤醒失败和重发射。发射时释放策略下,这些指令的恢复代价远高于完成时释放策略。

这个结果说明:发射队列的有效利用率不仅取决于容量,还取决于推测恢复的效率。在推测唤醒频繁失败的工作负载中(如指针追踪密集的代码),完成时释放策略的优势更加明显。

释放时机与推测恢复的关系

释放时机的选择与处理器的推测恢复策略紧密相关。在分支误预测的情况下,处理器需要清除错误路径上的所有指令,包括发射队列中的指令。

分支误预测时的清空

当分支预测失败被检测到后,处理器需要将发射队列中位于误预测分支之后(即在错误路径上)的所有指令清除。这个清除操作需要快速完成,否则会延长分支误预测恢复的时间。

清除的实现方式取决于发射队列是否记录了指令的序列号(或与ROB的关联信息):

  • 基于序列号的清除:每个发射队列表项记录了其在ROB中的位置(ROB索引或序列号)。当分支误预测被检测到时,已知误预测分支的ROB索引为SS,所有ROB索引大于SS的表项都需要被清除。这可以通过将每个表项的序列号与SS进行比较来实现,但对于循环的ROB索引空间,比较逻辑需要处理环绕(wrap-around)情况。

  • 基于标志位的清除:在分支指令被分配时,为其之后分配的所有指令打上一个"分支标志位"(epoch bit或branch tag)。当该分支被检测为误预测时,所有带有相应标志位的表项被清除。这种方式更快,但需要额外的标志位存储空间,且标志位的数量限制了同时可以跟踪的未决分支数量。

在现代高性能处理器中,分支误预测的恢复通常需要2\sim5个周期来完成发射队列的清除和前端的重定向。这个恢复延迟是分支误预测惩罚的重要组成部分。

误预测清空的批量释放

分支误预测清空可能一次释放大量表项——在极端情况下,可能释放发射队列中的大部分甚至全部表项。空闲列表管理逻辑需要能够在1\sim2个周期内接收这些批量释放请求。

对于FIFO方式的空闲列表,批量释放可以通过以下策略高效实现:

  1. 不将每个释放的索引逐一压入FIFO,而是直接重置空闲位向量——将被清除表项对应的位全部设为1。

  2. 维护一个"辅助空闲位向量",专门用于处理批量释放。辅助位向量与主FIFO并行工作:新的分配优先从FIFO获取,当FIFO为空时转向辅助位向量。

  3. 在后台(非关键路径上)逐步将辅助位向量中的空闲索引回填到FIFO中。

这种混合策略确保了批量释放可以在1个周期内完成(只需对位向量进行批量置位),而不需要等待FIFO的多周期写入操作。

误预测深度与清空范围

分支误预测的影响范围取决于误预测被检测到的时间——检测越晚,错误路径上积累的指令越多,需要清除的发射队列表项也越多。

设分支指令在分配后ddetectd_{\text{detect}}个周期被检测为误预测。在此期间,前端以WW条/周期的速率持续供给错误路径上的指令。因此,需要清除的最大表项数为:

Nflush=min(W×ddetect,  N)N_{\text{flush}} = \min(W \times d_{\text{detect}},\; N)

对于W=4W=4ddetect=10d_{\text{detect}}=10个周期(典型的"执行发现误预测"场景),Nflush=40N_{\text{flush}} = 40——在一个64项的队列中,需要清除约63%的表项!这说明分支误预测对发射队列的冲击是巨大的。

为了降低ddetectd_{\text{detect}},现代处理器使用分支预测检查点(branch checkpoint)机制——在分支指令进入执行单元后的第一个周期就检查条件(对于简单的比较分支),而非等到分支到达提交阶段。早期检测将ddetectd_{\text{detect}}从15\sim20个周期缩短到5\sim8个周期,显著减少了需要清除的错误路径指令数量。

选择性清空 vs. 全量清空

理想的清空操作只清除误预测分支之后的指令,保留之前的指令。但实现选择性清空需要每个表项都能快速判断自己是否在误预测路径上——这需要序列号比较或epoch位检查。

某些简化设计使用全量清空(flush all)——检测到误预测后,清空发射队列中的所有指令,包括误预测分支之前的正确指令。这种方式实现极其简单(只需一条全局清零信号),但恢复代价更高——正确路径上的指令也需要重新经过分配过程。全量清空主要用于低功耗的简化核心中,高性能核心通常采用选择性清空。

分配与ROB的协调

在大多数超标量处理器中,发射队列的分配与ROB的分配同步发生——一条新指令在同一个周期内被同时分配一个ROB表项和一个发射队列表项。这两个分配操作必须是原子性的:如果ROB有空间但发射队列满(或反之),则两个分配都不能执行。

这种原子分配通过资源可用性检查来实现:在分配阶段开始时,同时检查ROB、发射队列、物理寄存器文件的空闲资源数量。只有当所有资源都有足够的空闲表项来容纳WW条新指令时,分配才会进行。任何一个资源不足都会触发分配暂停。

多宽度分配的时序挑战

在一个WW-wide的超标量处理器中,每个周期可能需要同时分配WW条新指令到发射队列。这要求空闲表项管理逻辑能够在单个周期内提供WW个不同的空闲表项索引。对于FIFO方式,这意味着FIFO需要WW个读端口——多端口FIFO的实现比单端口FIFO复杂得多。

一种常用的实现方式是使用WW个单端口FIFO的银行化(banked)结构:将空闲表项索引分散到WW个FIFO bank中,每个bank每周期提供一个索引。释放时,释放的表项索引被轮流送入不同的bank中。这种方式的前提假设是分配和释放的速率在长期内是均衡的——否则某些bank可能过早耗尽而其他bank仍有富余。

Alpha 21264的发射流水线

DEC Alpha 21264处理器(1998年发布)是超标量处理器设计史上的一个里程碑。它的发射流水线设计在当时极具创新性,对后续的处理器架构产生了深远影响。本节详细分析Alpha 21264的发射队列设计,将其作为理解发射流水线设计取舍的经典案例。

Alpha 21264的整体架构

Alpha 21264是一个4-wide的乱序超标量处理器,采用0.35μ\mum CMOS工艺制造,工作频率为500\sim600 MHz。其后端执行引擎具有以下关键特征:

  • 4条发射端口:2个整数端口(ALU0、ALU1)和2个浮点端口(FP Add、FP Mul)。

  • 分离的发射队列:20项整数发射队列(IQ)和15项浮点发射队列(FQ)。

  • 4条宽度的前端:每周期最多取指4条、解码4条、重命名4条、分配4条。

  • 80项ROB(等效功能,Alpha 21264使用的是一个80项的寄存器映射表与自由列表方案)。

以今天的标准来看,20项的整数发射队列看起来非常小——现代处理器的发射队列通常有80\sim180项。然而,在Alpha 21264的设计年代,这20个表项已经足以支撑其4-wide的发射宽度和500 MHz的工作频率。理解Alpha 21264的设计选择需要回到1990年代中期的工艺条件和设计约束。

20-entry整数IQ的详细设计

Alpha 21264的整数IQ是一个压缩式(compacting)发射队列,采用非数据捕捉结构。其设计具有以下关键特征。

压缩式组织

当一条指令被选中发射后,它离开发射队列,留下一个"空洞"。Alpha 21264在每个周期执行压缩操作——位于空洞上方的所有指令向下移动一个位置,填充空洞。新到达的指令总是从队列的顶部(最高位置)进入。

这种压缩式组织的核心优势在于:

  1. 年龄有序:由于压缩操作,队列中指令的物理位置直接反映了其年龄——位置越低(索引越小)的指令越老。这使得最老优先(oldest-first)的选择逻辑变得极其简单——只需要从底部开始扫描,找到第一个就绪的指令即可。

  2. 无需年龄标签:因为位置隐含了年龄信息,不需要额外的年龄位或年龄矩阵,节省了表项面积和比较逻辑。

  3. 分配简单:新指令总是进入顶部位置,不需要搜索空闲表项。

然而,压缩操作的硬件开销是巨大的。在最坏情况下,每个周期需要移动多达19个表项的全部内容。每个表项包含以下信息(约80\sim100位):2个源操作数标签(各6位)、2个就绪位、1个目的标签(6位)、操作码(\sim8位)、立即数(\sim16位)、控制位(\sim10位)。压缩操作相当于一个19×10019 \times 100位的大规模移位操作。

选择逻辑

由于队列是年龄有序的,Alpha 21264的选择逻辑可以使用简单的最低位就绪(lowest-ready)优先编码器。对于2个整数发射端口,需要找到就绪向量中最低的2个为1的位——这只需一个22-of-2020优先编码器。

在Alpha 21264的500 MHz工作频率下(2 ns周期),优先编码器的延迟约为1 ns(\sim5级FO4在0.35μ\mum工艺下)——刚好占用半个周期。这使得唤醒和选择可以在一个周期的两个半周期中分别完成。

唤醒机制

Alpha 21264使用推测唤醒实现单周期ALU操作的背靠背发射(Tb2b=1T_{\text{b2b}} = 1周期)。具体时序如下:

  1. 周期TT:指令I1I_1被选中发射(在前半周期确定)。选择逻辑同时将I1I_1的目的标签广播到IQ的所有表项(推测唤醒——此时I1I_1尚未开始执行)。

  2. 周期TT(后半周期):IQ中依赖I1I_1的指令I2I_2被唤醒(Rdy变为1)。

  3. 周期T+1T+1(前半周期):选择逻辑选中I2I_2

  4. 周期T+1T+1(后半周期):I2I_2从PRF读取操作数(或通过旁路获取I1I_1的结果)。

  5. 周期T+1T+1I1I_1在ALU中执行完毕,结果通过旁路网络转发给I2I_2

  6. 周期T+2T+2前沿:I2I_2带着操作数值进入ALU开始执行。

注意:在上述时序中,I1I_1的标签广播发生在I1I_1被选中发射的同时,而非I1I_1执行完毕时。这是推测唤醒的关键——它假设I1I_1会在下一个周期顺利执行完毕。如果I1I_1是一条多周期指令(如乘法),则推测唤醒可能是错误的——I2I_2会在到达执行阶段时发现操作数尚不可用,需要被重新发射。

压缩硬件的详细实现

Alpha 21264的压缩操作是其发射队列设计中硬件开销最大的部分。让我们详细分析其实现。

压缩操作的触发条件是:一条指令在位置jj被发射(离开队列),位置jj上方的所有指令(位置j+1j+1N1N-1)需要向下移动一个位置。如果同一周期有2条指令被发射(位置jjkkj<kj < k),则需要更复杂的压缩逻辑——位置j+1j+1k1k-1的指令下移1位,位置k+1k+1N1N-1的指令下移2位。

对于20项队列和2个发射端口,压缩硬件的核心是一组移位多路选择器(shift MUX)。每个表项位置ii有一个(S+1)(S+1)-to-1 MUX(SS为最大同时发射数),选择该位置在下一周期的内容:

  • 选择0(不移动):保持当前内容(该位置上方没有发射的指令)。

  • 选择1(下移1位):接收位置i+1i+1的内容(上方有1条指令被发射)。

  • 选择2(下移2位):接收位置i+2i+2的内容(上方有2条指令被发射)。

每个MUX的数据宽度等于表项的位宽(\sim100位),因此每个位置需要约200个2-to-1 MUX。20个位置共需约4000个MUX——这是一笔可观的硬件开销。

压缩操作的延迟等于MUX延迟(\sim2级FO4)加上MUX选择信号的生成延迟。选择信号需要根据本周期发射的指令位置来计算每个位置的移位量——这需要一个简单的前缀和计算,延迟约3级FO4。总压缩延迟约5级FO4,刚好可以在半个周期内完成。

完成时释放与重发射

Alpha 21264采用完成时释放策略。指令被选中发射后,其表项不立即释放——它被标记为"已发射"(issued),但仍占据表项。如果推测唤醒导致的错误发射被检测到(例如,I1I_1是一条2周期乘法指令,I2I_2的推测唤醒是错误的),I2I_2的"已发射"标记被清除,I2I_2回到等待状态,重新参与唤醒-选择过程。

这种设计的代价是:在20项的发射队列中,已发射但未完成的指令(通常3\sim6条)仍然占据表项,使得有效的可用表项数降低到14\sim17项。对于一个4-wide的处理器来说,14项的有效容量已经相当紧张——这也是Alpha 21264的IQ经常触发反压的原因之一。

案例研究 1 — Alpha 21264的发射队列管理

DEC Alpha 21264处理器使用了一个20项的整数发射队列和一个15项的浮点发射队列。其设计具有以下特点:

  1. 压缩式队列:Alpha 21264使用压缩式发射队列——当一条指令被发射后,位于其上方的所有指令向下移动一个位置,压缩空出的空间。新分配的指令总是从队列顶部进入。

  2. 年龄有序:由于压缩操作,队列中指令的物理位置直接反映了其年龄——位置越低的指令越老。这简化了最老优先的选择逻辑。

  3. 完成时释放:Alpha 21264在指令执行完成后才释放表项,以支持推测唤醒失败时的重发射。

  4. 推测唤醒:对单周期ALU指令实现Tb2b=1T_{\text{b2b}} = 1的背靠背发射,乘法指令使用保守唤醒。

然而,压缩式队列的硬件开销是巨大的:每个周期可能需要移动多达19个表项的全部内容(标签、数据、控制位),每个表项约100\sim200位宽。这种大规模的数据搬移消耗了可观的功耗,并限制了队列大小的扩展。在后续的高性能处理器设计中(如Intel Core系列、AMD Zen系列),非压缩式队列成为了主流选择,压缩操作被年龄矩阵等逻辑替代。

Alpha 21264的浮点发射队列

Alpha 21264的浮点发射队列(FQ)只有15个表项,服务于2个浮点执行端口(FP Add和FP Mul)。FQ的设计与整数IQ有几个重要的差异。

非压缩式设计

与压缩式的整数IQ不同,Alpha 21264的浮点FQ采用了非压缩式设计。这个差异的原因在于:

  1. 浮点指令的平均延迟较长(4\sim6周期),指令在FQ中停留的时间更长。在完成时释放策略下,15项中可能有8\sim10项被"已发射但未完成"的指令占据,有效容量很小。压缩式设计在这种低利用率下的收益不大。

  2. 浮点操作数宽度为64位(双精度),每个表项存储的数据量更大。如果采用数据捕捉结构,压缩操作需要移动的数据量将非常可观。Alpha 21264的FQ选择了非数据捕捉+非压缩的方案。

  3. FQ只有15项和2个发射端口,选择逻辑本身就很简单,不需要压缩带来的"年龄隐含在位置中"的简化。

浮点唤醒的延迟

浮点指令(如FMUL为4周期、FADD为4周期)不像单周期ALU指令那样追求Tb2b=1T_{\text{b2b}} = 1。Alpha 21264对浮点指令使用精确唤醒(非推测性)——在浮点指令的结果真正可用时才广播标签。这意味着浮点依赖链的Tb2bT_{\text{b2b}}等于浮点指令的执行延迟(4周期),没有任何压缩。

这个设计选择是合理的:对于4周期的浮点乘法,即使推测唤醒将Tb2bT_{\text{b2b}}从4个周期缩短到3个周期(提前1周期广播),收益也只有25%——远不如将单周期ALU的Tb2bT_{\text{b2b}}从2缩短到1的100%收益来得显著。推测唤醒的复杂性(取消和重发射机制)在浮点域中的收益不足以证明其硬件代价。

Alpha 21264设计的历史评价

Alpha 21264的发射队列设计体现了1990年代中期处理器设计者面临的典型困境。在0.35μ\mum工艺下,可用的晶体管预算有限(Alpha 21264总共约15M晶体管),设计者必须在发射队列的容量和复杂度之间做出艰难的取舍。

压缩式 vs. 非压缩式的历史转折

Alpha 21264选择了压缩式队列+小容量(20项)的方案。这个选择在当时是合理的,因为:

  1. 20项的队列在0.35μ\mum工艺下已经是相当大的CAM结构,压缩式组织避免了年龄矩阵的额外开销。

  2. 压缩式队列的选择逻辑极其简单(优先编码器),有利于在高频下运行。

  3. 压缩操作虽然功耗高,但对于20项的队列来说还在可接受范围内。

然而,当后续处理器需要更大的发射队列时(>>32项),压缩操作的开销变得不可承受。Intel Pentium 4(2000年)和AMD K7/K8(1999\sim2003年)转向了非压缩式队列+年龄矩阵的设计,发射队列容量从20项迅速扩展到48\sim96项。这一历史转折说明了一个关键的设计原则:好的设计选择是工艺和容量的函数——在小队列中最优的方案在大队列中可能完全不适用。

对现代设计的启示

尽管Alpha 21264的具体设计方案已经过时,但其设计哲学中有几点对现代设计仍然适用:

  • 推测唤醒是必须的:Alpha 21264率先在工业产品中实现了推测唤醒驱动的Tb2b=1T_{\text{b2b}} = 1背靠背发射,这一技术被后续所有高性能处理器采用。

  • 完成时释放提供了恢复灵活性:虽然降低了有效容量,但避免了复杂的重放机制。现代处理器在推测失败处理上采用了更精细的策略,但基本思路类似。

  • 分离整数/浮点队列是有效的:Alpha 21264的分离式调度器架构成为后续分布式调度器设计的先驱。

性能分析 6 — Alpha 21264 vs. 现代处理器的发射队列对比

特征Alpha 21264Intel Golden CoveAMD Zen 4
工艺节点350 nmIntel 7 (\sim10 nm)5 nm
工作频率600 MHz5.0 GHz5.7 GHz
整数IQ容量20项\sim100项\sim72项
浮点IQ容量15项\sim60项\sim40项
队列类型压缩式非压缩(分离CAM+Payload)非压缩
发射宽度41210
Tb2bT_{\text{b2b}}(ALU)1周期1周期1周期
总晶体管数15M\sim5000M\sim6500M

从Alpha 21264到现代处理器,晶体管数量增长了约300\sim400倍,发射队列容量增长了约5\sim7倍,但Tb2bT_{\text{b2b}}始终保持在1个周期——这说明背靠背发射延迟是不可妥协的设计约束,而队列容量的扩展主要受益于工艺进步带来的面积红利。

完整发射流水线的时序分析

前面的各节分别讨论了发射流水线的各个组件和阶段。本节将这些组件整合在一起,通过一个完整的时序分析来展示指令从进入发射队列到开始执行的全过程。

非数据捕捉+Payload RAM架构的完整时序

以下分析基于一个典型的现代处理器配置:4-wide超标量,64项发射队列(分离CAM+Payload RAM),128个物理寄存器(7位标签),推测唤醒,单周期ALU Tb2b=1T_{\text{b2b}} = 1

指令I1I_1的生命周期

设指令I1I_1ADD x3, x1, x2,其源操作数x1和x2均已就绪。

  1. 周期T0T_0(分配阶段)

    • I1I_1从重命名阶段进入分配阶段。

    • 空闲列表返回表项索引k=17k=17

    • I1I_1的源标签(P12, P25)和就绪位(Rdy1=1, Rdy2=1)被写入CAM行17。

    • I1I_1的操作码、目的标签P33等被写入Payload RAM行17。

    • 由于两个操作数都已就绪,I1I_1在分配阶段末尾就标记为就绪。

  2. 周期T0+1T_0+1(选择阶段)

    • 选择逻辑看到表项17就绪,将其选中发射。

    • 选中信号触发Payload RAM行17的读出和PRF地址P12、P25的发送。

    • 同时,推测唤醒:I1I_1的目的标签P33被广播到CAM阵列(假设I1I_1会在下周期执行完毕)。

  3. 周期T0+2T_0+2(读出+执行阶段)

    • PRF返回x1和x2的值。如果x1或x2的生产者在上一周期刚完成,则通过旁路网络获取值。

    • Payload RAM返回I1I_1的操作码(ADD)和目的标签P33。

    • I1I_1带着操作数值进入ALU执行。

    • ALU在本周期内完成加法运算,结果出现在ALU输出端。

  4. 周期T0+2T_0+2末尾(写回)

    • I1I_1的结果值通过结果总线写入PRF的P33位置。

    • 如果推测唤醒在T0+1T_0+1已经广播了P33(提前1个周期),则此时不需要再次广播。

  5. 周期T0+3T_0+3(释放)

    • I1I_1的执行确认成功,CAM行17被释放(Valid=0)。

    • 表项索引17被归还空闲列表。

依赖指令I2I_2的背靠背发射

设指令I2I_2SUB x5, x3, x4,依赖I1I_1的结果x3(物理寄存器P33)。假设x4已就绪。

  1. 周期T0T_0I2I_2I1I_1的下一条位置被分配到表项18。I2I_2的src1标签为P33,Rdy1=0(P33尚未就绪)。src2标签为P40,Rdy2=1(已就绪)。

  2. 周期T0+1T_0+1I1I_1被选中发射,同时推测唤醒广播P33。CAM行18的src1标签P33与广播的P33匹配——I2I_2的Rdy1被设为1。I2I_2现在完全就绪。

  3. 周期T0+2T_0+2:选择逻辑选中I2I_2。Payload RAM读出I2I_2的信息。PRF读取P33和P40。但P33在本周期才被I1I_1写入PRF——I2I_2通过旁路网络获取I1I_1的ALU输出值。I2I_2同时推测唤醒广播P35(I2I_2的目的标签)。

  4. 周期T0+3T_0+3I2I_2在ALU中执行SUB操作。

从时序可以看出:I1I_1T0+2T_0+2执行,I2I_2T0+3T_0+3执行——间隔恰好1个周期,实现了Tb2b=1T_{\text{b2b}} = 1的背靠背发射。这依赖于三个关键机制的协同:推测唤醒(提前1周期广播)、选择逻辑在唤醒的下一周期选中I2I_2、旁路网络在I1I_1输出的同周期将值转发给I2I_2

$I_1$和$I_2$的背靠背发射时序:推测唤醒使得依赖指令在下一周期就能执行
$I_1$和$I_2$的背靠背发射时序:推测唤醒使得依赖指令在下一周期就能执行

多周期指令的时序

对于多周期指令(如3周期乘法),Tb2bT_{\text{b2b}}等于指令的执行延迟。以I1I_1MUL x3, x1, x2(3周期)、I2I_2ADD x5, x3, x4为例:

  1. I1I_1T0+2T_0+2进入乘法器开始执行(3周期流水线:M1, M2, M3)。

  2. I1I_1的标签P33在T0+1T_0+1被推测广播(提前2个周期)。

  3. I2I_2T0+2T_0+2被选中发射,在T0+3T_0+3进入ALU。

  4. I1I_1的结果要到T0+4T_0+4(M3完成)才可用——I2I_2T0+3T_0+3执行时操作数不可用!

这就是推测唤醒失败的典型场景。处理器检测到I2I_2的操作数不可用后,需要取消I2I_2的执行并重新发射。正确的做法是:乘法器的推测唤醒应在T0+2T_0+2(而非T0+1T_0+1)广播,使得I2I_2T0+3T_0+3被选中、T0+4T_0+4执行,恰好在I1I_1的结果可用时获取旁路数据。

这说明推测唤醒的提前量必须与指令的执行延迟匹配:

唤醒提前量=TexecuteTwakeup-to-execute\text{唤醒提前量} = T_{\text{execute}} - T_{\text{wakeup-to-execute}}

其中TexecuteT_{\text{execute}}是指令的执行周期数,Twakeup-to-executeT_{\text{wakeup-to-execute}}是从唤醒到消费者开始执行的周期数。如果Twakeup-to-execute=2T_{\text{wakeup-to-execute}} = 2(唤醒1周期 + 选择/读出1周期),则:

  • 单周期ALU(Texecute=1T_{\text{execute}}=1):提前量=12=1= 1 - 2 = -1,即需要在执行前1周期广播(推测)。

  • 3周期乘法(Texecute=3T_{\text{execute}}=3):提前量=32=1= 3 - 2 = 1,即在执行的第1个周期广播。

  • 4周期浮点加法(Texecute=4T_{\text{execute}}=4):提前量=42=2= 4 - 2 = 2,即在执行的第2个周期广播。

多个依赖链的交错执行

在实际程序中,多条独立的依赖链在发射队列中并存。发射队列的一个关键功能是通过乱序发射,让独立的依赖链交错执行,从而利用指令级并行性。

考虑以下代码片段(两条独立的依赖链):

链AADD x1, x0, #1 \to ADD x2, x1, #2 \to ADD x3, x2, #3
链BMUL x4, x0, x0 \to ADD x5, x4, #4 \to ADD x6, x5, #5

链A中的3条指令全部是单周期ADD,形成长度为3的串行链。链B以一条3周期MUL开头,后面是2条单周期ADD。这两条链是完全独立的(不共享操作数)。

在一个Tb2b=1T_{\text{b2b}} = 1的2-wide处理器中,发射队列的调度如下:

周期端口0端口1
TTA1: ADD x1,...B1: MUL x4,...
T+1T+1A2: ADD x2,...(B1仍在执行)
T+2T+2A3: ADD x3,...(B1仍在执行)
T+3T+3B2: ADD x5,...
T+4T+4B3: ADD x6,...

5个周期执行6条指令,IPC = 6/5 = 1.2。如果没有乱序发射(按程序顺序发射),B1的3周期延迟会阻塞A2和A3,总执行时间为7个周期,IPC = 6/7 = 0.86。发射队列的乱序调度带来了40%的IPC提升。

这个例子展示了发射队列的核心价值:它不仅管理单条依赖链内部的背靠背发射,更重要的是让多条独立依赖链交错执行,隐藏长延迟操作(如乘法、Load)的延迟。发射队列的容量决定了能够同时跟踪多少条独立链——更大的队列可以容纳更多的链头指令,从而发掘更深层的并行性。

Load指令的不确定延迟

Load指令的执行延迟是不确定的——它取决于数据是在L1缓存命中(3\sim5周期)还是未命中(需要访问L2或更远的层次,10\sim300+周期)。这给推测唤醒带来了特殊的挑战。

大多数处理器乐观地假设所有Load都会在L1命中,并在Load进入执行流水线时就按L1命中延迟广播唤醒标签。如果Load确实命中L1,一切正常——依赖指令在正确的时机被唤醒和执行。如果Load未命中L1:

  1. 依赖Load结果的所有指令已经被唤醒并发射(因为推测唤醒假设L1命中)。

  2. 这些指令到达执行阶段时发现操作数尚不可用。

  3. 处理器取消这些指令并标记为需要重发射。

  4. 这些指令的就绪位被重置为0,回到等待状态。

  5. 当Load最终从内存层次获取数据后,重新广播标签唤醒这些指令。

  6. 被重新唤醒的指令重新参与选择、发射、执行。

这种Load推测唤醒-取消-重发射的机制是现代处理器中最常见的推测恢复操作——在L1未命中率为5%\sim10%的工作负载中,可能有10%\sim30%的动态指令经历过至少一次重发射。

性能分析 7 — Load推测唤醒的性能影响

Load推测唤醒对性能的影响取决于L1缓存的命中率。在典型的SPEC CPU2017工作负载中:

策略L1命中时延迟L1未命中代价总体IPC
保守唤醒(等待Load完成)+2周期无取消开销2.6
乐观唤醒(假设L1命中)0重发射(+5\sim10周期)3.2

乐观策略在L1命中(\sim90%的Load)时节省了2个周期的延迟,而在L1未命中(\sim10%)时只增加了5\sim10个周期的重发射代价。由于L1命中率通常很高,乐观策略的净收益是正的——这就是为什么几乎所有现代处理器都采用乐观的Load唤醒策略。

现代处理器的发射队列配置

表 28.7总结了几款代表性处理器的发射队列配置,展示了工业实践中的设计选择。

处理器总IQ容量组织方式发射宽度队列类型
Intel Golden Cove160分布式(4个)12非压缩
AMD Zen 4112分布式(6个)10非压缩
ARM Cortex-X4160分布式(多个)10非压缩
Apple M3 (P-core)\sim180分布式10非压缩
RISC-V XiangShan80分布式(3个)6非压缩

现代处理器的发射队列配置

从表表 28.7可以观察到几个共同趋势:(1)所有高性能处理器都使用分布式发射队列,而非单一的集中式队列——这是因为大型集中式CAM阵列的时序和功耗无法接受;(2)总IQ容量在80\sim180项之间,远大于早期处理器(如Alpha 21264的20项整数+15项浮点);(3)非压缩结构是主流选择,压缩式队列由于其硬件开销已不再被现代高性能设计采用。

分布式调度器的分区策略

现代处理器的分布式调度器通常按照执行端口类型进行分区。以Intel Golden Cove为例,其调度器分区大致如下:

  • 整数调度器:负责整数ALU、分支、整数乘法/除法指令,约40\sim50项。

  • 向量/SIMD调度器:负责SIMD整数和浮点运算指令,约40\sim50项。

  • 地址生成调度器:负责Load/Store地址计算,约40项。

  • Store数据调度器:负责Store数据的准备和写入,约30项。

每个分区有自己独立的CAM阵列、选择逻辑和(可能的)Payload RAM。分区之间通过跨分区广播网络连接——当某个分区中的指令完成执行时,其结果标签不仅广播到本分区的CAM阵列,还通过跨分区总线广播到其他分区。

AMD Zen系列的调度器设计

AMD Zen系列处理器采用了与Intel不同的调度器分区策略。以Zen 4微架构为例:

  • 4个ALU调度器:每个约16\sim20项,共约72项。每个调度器连接到1个ALU端口,但某些调度器还负责分支和乘法指令。

  • 2个AGU调度器:约20项/个,负责Load和Store的地址生成。

  • 1个FP调度器:约40项,负责浮点和SIMD指令。

AMD的设计特点是更多、更小的调度器分区——每个分区只有16\sim20项,远小于Intel单分区\sim40\sim50项的配置。小分区的优势是CAM比较更快(更少的表项意味着更短的广播线和更少的比较器负载),但劣势是单分区的缓冲容量小,更容易触发反压。

AMD通过灵活的指令路由来弥补小分区的容量不足——分配阶段可以将整数ALU指令路由到4个ALU调度器中任何一个负载最轻的分区。这种负载均衡路由避免了某个分区过早填满而其他分区仍有空间的情况。

案例研究 2 — AMD Zen 4的调度器负载均衡

在AMD Zen 4中,整数ALU指令可以被路由到4个ALU调度器中的任何一个。路由决策基于以下因素:

  1. 当前占用率:优先选择空闲表项最多的分区。

  2. 依赖性亲和:如果新指令依赖的生产者已经在某个分区中,优先将新指令路由到同一分区——这使得唤醒可以在本地完成(无需跨分区广播),降低Tb2bT_{\text{b2b}}

  3. 端口兼容性:某些ALU调度器连接到具有特殊功能的执行端口(如整数除法、位操作),需要这些功能的指令只能被路由到对应的分区。

因素(1)和(2)可能冲突——负载最轻的分区不一定是依赖亲和最好的分区。Zen 4使用启发式策略来权衡:如果依赖亲和分区的占用率不超过最轻分区的占用率+2,则选择依赖亲和分区;否则选择最轻分区。这种"大致最优"的策略在硬件上只需要简单的比较器和优先编码器,延迟约3级FO4。

发射队列容量的扩展趋势

从历史趋势来看,发射队列的总容量每代处理器约增长15%\sim30%,远慢于晶体管密度的增长速度(每代约增长2×2\times)。这说明发射队列容量的增长不仅受面积约束,更主要地受时序约束——更大的CAM阵列意味着更慢的唤醒和选择,这是不可接受的。

设计者通过以下手段在增大总容量的同时控制时序:

  1. 增加分区数量:与其增大单个调度器,不如增加调度器的数量。每个调度器保持在\sim32\sim50项的可控范围内。

  2. 改进CAM电路:使用更先进的定制CAM位元设计(如动态XNOR+预充电匹配线),在更先进工艺下维持或改善CAM延迟。

  3. 层次化调度:在分配阶段进行粗粒度的调度(确定指令进入哪个分区),在分区内进行细粒度的唤醒-选择。粗粒度调度不在关键路径上,可以容忍更长的延迟。

发射队列的功耗管理

在现代处理器中,发射队列(调度器)是功耗最密集的组件之一。以Intel Core i7处理器为例,调度器的功耗可占整个核心功耗的8%\sim12%。主要的功耗来源包括:

  1. CAM比较功耗:每个周期,所有有效表项的比较器都需要与广播标签进行比较,即使大多数不匹配。这种"全搜索"模式导致大量的无效翻转功耗。

  2. 广播网络功耗:多条广播总线穿过整个CAM阵列,每个周期切换标签值。

  3. 选择逻辑功耗:优先编码器和年龄比较矩阵在每个周期都需要评估所有就绪指令。

  4. Payload RAM读写功耗:每个周期的分配写入和发射读出。

  5. 泄漏功耗:在先进工艺节点中,CAM和SRAM的静态泄漏功耗不容忽视。

降低功耗的常用技术包括:

  • Rdy门控:如前所述,已就绪操作数的比较器被关闭。

  • Valid门控:无效表项的比较器被关闭。

  • 分区级门控:当一个调度器分区完全为空时,整个分区可以进入低功耗模式。

  • 选择性广播:只向包含等待特定标签的调度器分区广播,不相关的分区不接收广播。

  • 电压域分离:CAM阵列使用较高的电压(保证速度),Payload RAM使用较低的电压(降低功耗),两者之间通过电平转换器连接。

硬件描述 6 — 发射队列功耗的分项估算

对于一个64项、4-wide的分离式(CAM+Payload RAM)发射队列,在7nm FinFET工艺、5 GHz工作频率下,各组件的功耗估算:

组件动态功耗泄漏功耗
CAM比较(含门控优化)15\sim25 mW3 mW
广播网络10\sim18 mW1 mW
选择逻辑5\sim8 mW1 mW
Payload RAM读/写3\sim5 mW2 mW
控制逻辑(分配、释放等)2\sim4 mW1 mW
总计35\sim60 mW8 mW

在一个功耗预算为5W的处理器核心中,发射队列占约1%\sim1.4%。但对于更宽的处理器(8\sim12-wide),调度器功耗会以接近O(N2)O(N^2)的速率增长(更多的广播线×\times更多的表项×\times更多的选择通道),可能达到200\sim400 mW,占核心功耗的4%\sim8%。

随着处理器设计持续追求更高的性能和效率,发射队列的设计面临新的挑战和机遇。

增宽处理器的调度挑战

随着处理器宽度从4-wide向8-wide甚至12-wide扩展,发射队列面临的压力呈超线性增长:

  • 广播总线数量从4\sim6条增加到8\sim12条,每条广播线的比较器数量也增加。

  • 选择逻辑需要从更多的就绪指令中选出更多的被发射指令。

  • 分配端口和释放端口数量增加,空闲列表管理复杂度上升。

应对这些挑战的主要策略是进一步分区:将一个大型调度器拆分为多个更小的分区,每个分区只负责少量执行端口。例如,一个12-wide处理器可能有6个调度器分区,每个分区只负责2个执行端口,分区内部的CAM阵列和选择逻辑的规模与4-wide处理器相当。

异构调度

在混合大小核(big.LITTLE)架构中,大核和小核的发射队列设计截然不同。大核使用大容量、高性能的分布式调度器(\sim100\sim180项),而小核使用小容量、低功耗的简化调度器(\sim16\sim32项)。某些设计甚至在小核中使用有序发射(in-order issue),完全消除了发射队列和选择逻辑。

大核和小核的调度器设计差异反映了不同的优化目标:大核追求最大IPC,愿意付出面积和功耗代价;小核追求最佳的性能/功耗比,在可接受的IPC损失下大幅降低硬件复杂度。

机器学习工作负载的影响

随着机器学习推理成为处理器的重要工作负载,发射队列的设计可能需要适应新的指令模式。机器学习推理的特点包括:(1)大量的矩阵乘法和向量操作,依赖链较短但并行度极高;(2)内存访问模式规则,L1命中率高,推测唤醒失败率低;(3)指令类型比较单一,调度器分区的负载可能严重不均衡。

这些特点可能推动调度器设计向以下方向演变:

  • 更大的向量/矩阵调度器分区,较小的整数标量分区。

  • 为向量指令优化的批量唤醒机制——一次唤醒多条相同模式的向量指令。

  • 简化的推测恢复逻辑——由于L1命中率高,推测唤醒失败的频率很低,可以使用更简单(但恢复更慢)的重发射机制。

近数据处理的极简调度器

随着处理器和存储器之间的带宽差距("memory wall")持续扩大,某些新兴的处理器架构开始探索将计算靠近数据的方案——即近数据处理(Near-Data Processing, NDP)。在这些架构中,简单的计算单元被嵌入到内存控制器或缓存层次中。

这些嵌入式计算单元通常面积和功耗预算极为有限,因此其调度器设计必须极度简化:

  • 发射队列可能只有4\sim8个表项。

  • 选择逻辑使用最简单的FIFO顺序发射或固定优先级。

  • 不使用推测唤醒——在操作数确认可用后才发射。

  • 不使用CAM结构——表项数量太少,直接使用比较器阵列即可。

这种"极简调度器"的设计回归了早期处理器的设计哲学:用最少的硬件实现基本的乱序执行能力,将复杂的调度优化留给编译器和软件。

RISC-V开源处理器的调度器实践

RISC-V生态系统中的开源处理器为我们提供了审视调度器设计的独特视角。以香山处理器(XiangShan)为例:

  • 南湖(NanHu)微架构:使用3个分布式调度器——2个整数调度器(各16项)和1个浮点/向量调度器(16项),总计80项。采用非压缩式、非数据捕捉结构。

  • 开源实现的特点:由于开源项目使用RTL综合(非全定制),CAM阵列由综合工具自动生成,其性能和面积不如全定制设计。但开源实现提供了完整的设计可见性,使研究者可以精确分析每个设计选择对PPA(性能、功耗、面积)的影响。

  • 设计迭代速度:开源RTL的修改和验证周期远短于商业芯片(数周vs. 数月),允许快速探索不同的调度器配置(表项数、分区方式、唤醒策略等)。

香山处理器的调度器设计在SPEC CPU2006上实现了约7\sim8的IPC,与商业处理器(如ARM Cortex-A76的约9分/GHz)处于同一数量级。这表明,即使使用综合实现(而非全定制),精心设计的调度器仍然可以达到接近商业水平的性能。

设计提示

发射队列的设计是超标量处理器中"大处着眼、小处着手"原则的集中体现。从宏观上看,更大的发射窗口(更多表项)带来更高的ILP发掘能力;从微观上看,每一级逻辑门的延迟都直接影响Tb2bT_{\text{b2b}}和工作频率。优秀的发射队列设计不是简单地将表项数量最大化,而是在给定的工艺节点和频率目标下,找到表项数量、分区方式、CAM电路风格和广播网络拓扑的最优组合。这个优化问题没有解析解——它依赖于大量的RTL模拟、物理设计迭代和性能/功耗仿真。

本章小结

本章详细分析了发射过程的流水线组织——从标签广播、CAM匹配、仲裁选择到PRF读取和执行,涵盖了非数据捕捉结构和数据捕捉结构的完整时序。以下是本章的核心要点:

  1. 唤醒-选择循环是核心Tb2bT_{\text{b2b}}(背靠背发射延迟)是发射流水线最重要的性能指标。现代处理器通过推测唤醒和半周期切分将单周期ALU的Tb2bT_{\text{b2b}}压缩到1个周期。

  2. 广播网络是物理设计瓶颈。CDB广播线的扇出问题随队列规模增长而恶化。分段总线、H树拓扑和分布式调度器是解决扇出问题的主要手段。

  3. CAM+Payload RAM分离是主流。将快速但面积大的CAM阵列(负责唤醒)与紧凑的SRAM Payload RAM(负责数据存储)物理分离,是Intel Core和AMD Zen等现代处理器的实际做法。

  4. 空闲表项管理影响分配吞吐率。位向量+优先编码器和FIFO是两种主要的空闲表项管理方式,各有优劣。批量分配和释放需要多端口或银行化的支持。

  5. 反压机制保证流水线正确性。水位线和信用计数器是两种主要的反压策略,提前反压信号减少了流水线气泡。

  6. 释放时机影响推测恢复。发射时释放最大化有效容量但恢复代价高,完成时释放支持原地重发射但降低有效容量。

  7. 分布式调度器是扩展之道。通过增加分区数量而非增大单个分区,现代处理器在维持时序的同时将总IQ容量扩展到100\sim180项。

  8. Load推测唤醒是性能关键。乐观假设L1命中的推测唤醒在绝大多数情况下是正确的,其性能收益远超偶发的重发射代价。

发射流水线的设计是超标量处理器微架构中最具挑战性的领域之一。它需要在时序、面积、功耗和性能之间进行多维度的精细权衡。

设计权衡 3 — 前向桥接——从流水线到仲裁电路

本章建立了发射流水线的时序框架,但有意将选择逻辑(select logic)和唤醒逻辑(wakeup logic)作为抽象的"黑盒"处理。然而,仲裁器恰恰是发射逻辑中"最不起眼但最关键"的部件——它从NN个就绪指令中选择MM个执行,其电路涉及对数深度的优先级编码和亚纳秒级的时序约束。设计不良的仲裁器可以成为整个处理器的频率瓶颈。此外,推测唤醒失败后的取消与重发射机制涉及复杂的控制状态机。第 29.0 章将深入这些电路级的设计细节,并讨论一种替代传统CAM的依赖矩阵调度器架构。

参数典型范围主要影响
IQ总容量64\sim180项IPC、反压频率
分区数量3\sim6个CAM延迟、跨分区开销
每分区容量16\sim50项CAM比较时间
广播总线数量4\sim12条唤醒带宽、面积
物理寄存器标签位宽7\sim8位比较器面积
Tb2bT_{\text{b2b}}(ALU)1周期依赖链延迟
Tb2bT_{\text{b2b}}(MUL)3\sim4周期乘法依赖链延迟
Tb2bT_{\text{b2b}}(Load)4\sim5周期访存依赖链延迟
反压水位线W+1W+1W+3W+3暂停频率、有效容量
Payload RAM位宽60\sim100位面积、读出延迟

本章涉及的关键设计参数及其典型取值

正文与图片:CC BY-NC-SA 4.0 · 本仓库少量站点配置代码:MIT