Skip to content

发射队列的结构

硬件描述 1 — 乱序执行的"心脏"

发射队列(Issue Queue)是乱序处理器的"心脏"——它是指令等待源操作数就绪的缓冲池,也是处理器发掘ILP的物理窗口。容量大,处理器能看到更远的未来,挖掘更多并行性,但选择逻辑的延迟随容量增长,可能拉长时钟周期;容量小,选择速度快但窗口受限,ILP开发不足。Apple M4的大核拥有超过160项的发射队列,而ARM Cortex-A520小核仅有16项——两者的IPC差异超过3倍,发射队列容量是最重要的原因之一。

设计提示

统一视角。处理器设计的本质是在有限的晶体管预算和功耗约束下,通过投机和并行的层层叠加来逼近指令吞吐率的理论上限。发射队列正是ILP的"缓冲池"——它将重命名阶段消除假依赖后释放出的并行性(第 26.0 章)暂存起来,等待时机将其转化为功能单元的实际并行执行。发射队列的容量直接决定了处理器的指令窗口大小——在这个窗口内,处理器可以自由地重排序指令以最大化吞吐率。窗口越大,能发现的ILP越多,但选择逻辑的复杂度(仲裁电路,将在第 29.0 章中详细讨论)也随之增长。

第 26.0 章中,我们讨论了寄存器重命名和分发的过程——指令经过重命名后,其源操作数和目标操作数从体系结构寄存器被映射到物理寄存器,随后被分发到后端的执行资源。然而,指令被分发之后并不能立即执行:它的源操作数可能尚未就绪——产生这些操作数的先行指令可能仍在执行甚至还在等待自己的源操作数。指令必须在某个地方等待,直到所有源操作数都就绪后才能被发射(issue)到功能单元执行。这个"等待的地方"就是发射队列(Issue Queue,IQ),也称为保留站(Reservation Station,RS)。

发射队列是超标量乱序处理器中最关键的微架构组件之一。它的设计直接影响处理器能够同时跟踪多少条待执行指令、能够以多快的速度选出就绪指令并将其发射到功能单元、以及在面积和功耗预算中占据多大的比例。发射队列的容量决定了处理器的指令窗口大小——处理器能够"看到"多远的未来指令以发掘指令级并行性(ILP)。容量太小会限制ILP的开发,导致功能单元频繁空闲;容量太大则面积和功耗急剧增长,且由于唤醒和选择逻辑的复杂度增加而可能拉长时钟周期。

本章系统地讨论发射队列的三个核心设计维度:(1)集中式与分布式的组织方式——所有指令共享一个大发射队列,还是按功能单元类型分成多个小发射队列?(2)数据捕捉与非数据捕捉——操作数的值是否直接存储在发射队列中,还是仅存储操作数的标签(tag)?(3)压缩与非压缩——指令发射后腾出的空位是否被压缩填充?这三个维度的不同组合产生了截然不同的硬件实现,各有其在面积、功耗、时序和利用率上的权衡。

发射队列概述

发射队列在流水线中的位置

发射队列位于超标量乱序处理器后端流水线的核心位置:它接收来自重命名/分发阶段的指令,向功能单元提供就绪的指令。图图 27.1展示了发射队列在典型超标量流水线中的位置。

发射队列在超标量乱序流水线中的位置。指令从分发阶段进入发射队列等待,就绪后被选择发射到功能单元执行。执行单元通过唤醒信号通知发射队列中等待的指令。
发射队列在超标量乱序流水线中的位置。指令从分发阶段进入发射队列等待,就绪后被选择发射到功能单元执行。执行单元通过唤醒信号通知发射队列中等待的指令。

发射队列与其他后端组件的交互

发射队列并非孤立工作——它与流水线中的多个其他组件紧密交互:

  • 与重命名/分发阶段的交互:分发阶段每周期向IQ写入DD条指令(DD为分发宽度)。IQ必须向分发阶段报告当前的空闲表项数——如果空闲表项不足DD个,分发阶段必须暂停(stall),等待IQ腾出空位。这个"信用"(credit)反馈信号的延迟至关重要:如果从IQ释放表项到分发阶段知道有新的空位之间有LL个周期的延迟,则IQ需要预留L×DL \times D个额外的"缓冲"表项来避免虚假的暂停。在L=2L=2D=6D=6的情况下,需要预留12个表项——这占96项IQ的12.5%。

  • 与ROB的交互:每条指令在分发时同时被写入IQ和ROB。IQ中的ROB编号字段建立了两者之间的对应关系。当ROB检测到异常或分支误预测时,通过ROB编号通知IQ清除对应的指令。

  • 与PRF的交互(非数据捕捉结构):指令被IQ选中发射后,其源标签被送往PRF进行读取。PRF的读端口数必须匹配IQ的最大发射宽度——如果IQ每周期最多发射PP条指令,PRF至少需要2P2P个读端口。

  • 与功能单元的交互:被选中的指令的操作码和操作数被送往功能单元。功能单元执行完成后通过CDB反馈标签(和可能的数据值)到IQ,完成唤醒闭环。

指令从分发阶段进入发射队列时,携带了以下关键信息:

  • 操作类型:指令要执行的操作(如加法、乘法、加载、存储、分支等),决定了该指令应被发射到哪个功能单元。

  • 源操作数标签:经过重命名后的物理寄存器编号(tag),用于标识每个源操作数。

  • 源操作数就绪状态:在分发时,每个源操作数可能已经就绪(对应的物理寄存器已被写入),也可能尚未就绪(先行指令仍在执行中)。

  • 目标操作数标签:结果将写入的物理寄存器编号。

  • 立即数/偏移量:如果指令携带立即数或内存地址偏移量。

  • 其他控制信息:如ROB编号、分支掩码、功能单元类型等。

指令在发射队列中等待,直到其所有源操作数都就绪。当一条指令的所有源操作数就绪后,该指令变为可发射状态(ready to issue)。选择逻辑从所有处于可发射状态的指令中挑选一条(或多条)发射到相应的功能单元执行。

发射队列相关的流水线时序

为了更精确地理解发射队列在流水线中的角色,我们需要逐周期地追踪一条指令从分发到执行完成的全过程。图图 27.2展示了一条典型指令在非数据捕捉结构中经历的流水线阶段。

发射队列相关的流水线时序。指令$J$依赖指令$I$的结果。在推测性唤醒下,$I$开始执行的同一周期就唤醒$J$,使$J$在$I$完成后的下一个周期即可开始执行。
发射队列相关的流水线时序。指令$J$依赖指令$I$的结果。在推测性唤醒下,$I$开始执行的同一周期就唤醒$J$,使$J$在$I$完成后的下一个周期即可开始执行。

这个时序图揭示了发射队列设计的几个核心约束。首先,从分发到执行的最短路径(假设所有操作数在分发时已就绪)经过3\sim4个流水线级:分发\to选择\to读PRF\to执行。这意味着即使没有数据依赖,一条指令从被解码到开始执行也需要至少5\sim7个周期(取指1\sim2 + 解码1 + 重命名1 + 分发1 + IQ/选择/PRF读2\sim3)。在分支预测错误的情况下,这些流水线级都是"白白浪费"的——错误路径上的所有指令必须被清除,正确路径的指令需要从取指重新开始。因此,发射队列的深度直接影响分支误预测的惩罚(branch misprediction penalty)。

其次,图中的指令JJ从分发到被唤醒经历了3个周期的等待。在实际工作负载中,大多数指令的等待时间较短——根据文献中的统计,约60%的指令在分发后的2个周期内就被唤醒,约85%的指令在4个周期内被唤醒。但少数指令(如依赖cache miss的load结果)可能等待数十个甚至上百个周期。正是这些长等待指令决定了发射队列需要多大的容量。

上图中有几个关键的时序节点值得仔细分析:

  1. 分发\to写入IQ(周期TT):分发阶段将指令的操作码、重命名后的源/目标标签、立即数、ROB编号等信息写入发射队列的一个空闲表项。同时,分发逻辑查询源操作数的就绪状态——如果产生该操作数的先行指令已经写回结果(即对应的物理寄存器已被写入),则将就绪位置1。这一步需要访问RAT(Register Alias Table)或就绪位表,延迟约为1\sim2个门级。

  2. 唤醒\to选择(周期T+1T+2T{+}1\sim T{+}2):指令在IQ中等待,每个周期检查CDB上的广播标签是否与自己的源操作数标签匹配。当所有源操作数都就绪后,指令进入可发射状态。理想情况下,唤醒和选择可以在同一个周期内完成:唤醒在时钟上升沿后的前半周期更新就绪位,选择在后半周期根据就绪位做出仲裁。但在高频率设计中,这一路径可能过长,需要拆分为两个流水线级——这意味着指令从被唤醒到被选中之间有一个周期的延迟。

  3. 选择\to读PRF(周期T+2T+3T{+}2\sim T{+}3):选择逻辑选中指令后,将其源操作数标签送往PRF。PRF根据标签读出操作数的值。在数据捕捉结构中,这一步可以省略——操作数值直接从IQ表项中读出。

  4. 推测性唤醒(周期T+4T{+}4):对于确定延迟的指令(如1周期延迟的整数ALU操作),处理器在指令开始执行的同一周期就发出唤醒信号。这样,依赖该指令的后续指令可以在下一个周期被选择,再下一个周期读PRF/通过旁路获得操作数,实现背靠背执行。如果没有推测性唤醒,后续指令要等到前驱指令的结果真正写回PRF后才能被唤醒,白白浪费1\sim2个周期。

发射到执行的延迟分析

从发射队列的角度,最关键的性能指标是从唤醒到执行的延迟——即一条指令的最后一个源操作数变为就绪后,该指令需要多少个周期才能开始执行。表表 27.1列出了不同设计配置下的典型延迟。

设计配置唤醒\to选择选择\to操作数就绪总延迟
数据捕捉 + 单周期唤醒选择0 (同周期)0 (直接读出)1周期
非数据捕捉 + 单周期唤醒选择0 (同周期)1 (读PRF)2周期
非数据捕捉 + 两周期唤醒选择11 (读PRF)3周期
非数据捕捉 + 旁路优化0 (同周期)0 (旁路前递)1周期

从唤醒到执行的延迟(不同设计配置)

这个表格中的延迟数字对处理器性能有着深远的影响。考虑一个长度为LL的依赖链(即LL条指令依次依赖前一条的结果),每条指令本身的执行延迟为1个周期。在理想情况下(1周期总延迟),这条依赖链需要LL个周期完成。如果总延迟为2个周期,则需要2L2L个周期——性能减半。对于典型的整数代码,关键依赖链长度LL约为10\sim30条指令,因此从1周期延迟到2周期延迟的差异可能导致5\sim15%的IPC损失。

最后一行"非数据捕捉+旁路优化"是现代高性能处理器的典型配置:虽然IQ本身不存储数据值,但通过推测性唤醒和旁路网络,被唤醒的指令可以直接从旁路网络获取前驱指令刚刚产生的结果,而不需要等待结果写入PRF再读出。这使得依赖链上的背靠背指令可以在连续的周期中执行,达到与数据捕捉结构相同的有效延迟。

发射队列表项的详细结构

发射队列的每个表项存储了指令等待和发射所需的全部信息。不同的设计选择(数据捕捉/非数据捕捉)导致表项的字段有所不同,但核心字段是相似的。表表 27.2给出了一个典型的非数据捕捉发射队列表项的详细字段定义,包括具体的位宽。

字段名缩写位宽说明
有效位Valid1该表项是否包含有效指令。清零表示空闲。
已发射位Issued1该指令是否已被选择发射。用于防止重复发射。
操作码Opcode6指令的微操作类型(如ADD、MUL、LD等)。6位可编码64种微操作。
功能单元类型FUType3目标功能单元的类型(ALU/MUL/FPU/AGU等),用于选择逻辑的路由。
源1标签SrcTag18第一个源操作数的物理寄存器编号。8位支持256个物理寄存器。
源1就绪位SrcReady11第一个源操作数是否就绪。
源2标签SrcTag28第二个源操作数的物理寄存器编号。
源2就绪位SrcReady21第二个源操作数是否就绪。
目标标签DstTag8结果将写入的物理寄存器编号。用于唤醒后续依赖指令。
立即数Imm64指令携带的立即数或内存偏移量。对于不使用立即数的指令,此字段无效。部分设计将此字段缩短为32位以节省面积。
程序计数器PC64指令的PC值。用于分支目标计算和异常处理。许多设计不在IQ中存储PC,而是通过ROB编号间接获取。
ROB编号RobIdx8该指令在ROB中的位置索引。用于提交和异常恢复。8位支持256项的ROB。
分支掩码BrMask8标记该指令依赖哪些未解析的分支。当分支被误预测时,通过检查此掩码来确定是否需要取消该指令。
基本总计(含PC和Imm)181完整版本
精简总计(不含PC,Imm=32位)85精简版本(PC从ROB获取,Imm截短)

发射队列表项的详细字段定义(非数据捕捉结构,64位处理器,256个物理寄存器)

几个字段需要进一步解释:

分支掩码BrMask)是实现精确异常和推测执行清除的关键字段。在一个支持8条未解析分支的处理器中,分支掩码为8位,每位对应一条未解析的分支指令。当指令II在某条分支bkb_k之后被分发时,BrMaskI\texttt{BrMask}_I的第kk位被置1。如果分支bkb_k后来被确认为预测错误,处理器扫描发射队列中所有BrMask\texttt{BrMask}的第kk位为1的表项,将其无效化(设置Valid=0\texttt{Valid}=0)。这个操作可以在1个周期内通过NN个AND门并行完成——每个表项检查BrMask[k]\texttt{BrMask}[k],如果为1则清除Valid\texttt{Valid}位。

已发射位Issued)用于防止同一条指令被重复发射。在正常流程中,指令被选择发射后,其Issued位被置1,选择逻辑不再将其视为候选。但在推测性唤醒失败导致重放(replay)时,Issued位需要被重新清零,使指令重新参与选择。这一位的管理逻辑看似简单,但在存在推测唤醒的处理器中需要仔细处理竞争条件(race condition)——同一个周期内可能同时发生"选择逻辑设置Issued=1"和"重放逻辑设置Issued=0",硬件必须确保重放的优先级高于选择。

在实际设计中,是否在IQ表项中存储PC和完整的64位立即数是一个重要的工程决策。存储PC可以简化分支指令的目标地址计算,但对于大多数非分支指令而言是一种浪费。一个常见的折中是:IQ表项不存储PC,而是在需要时通过ROB编号查询ROB来获取PC。类似地,立即数字段可以缩短为32位(覆盖大多数立即数的范围),或通过指向一个独立的"立即数缓冲区"的指针来替代。

表项位宽对面积的影响

假设一个发射队列有N=96N=96个表项,表项位宽为BB位。IQ的总存储面积为N×BN \times B位。对于上述两种配置:

  • 完整版本(B=181B=181位):96×181=17,37696 \times 181 = 17{,}3762.1\approx 2.1 KB

  • 精简版本(B=85B=85位):96×85=8,16096 \times 85 = 8{,}1601.0\approx 1.0 KB

但IQ的面积并不仅仅由存储位决定。唤醒逻辑中的CAM比较器才是面积的主要贡献者。每个比较器比较一个标签位(1位XOR + 1位AND),一个完整的标签比较器(8位)需要8个XOR门、一个8输入AND门(或等效的门树)。对于96项IQ、2个源操作数、W=8W=8个CDB端口,需要96×2×8=1,53696 \times 2 \times 8 = 1{,}536个8位比较器,总计1,536×8=12,2881{,}536 \times 8 = 12{,}288个XOR门和1,5361{,}536个8输入AND门。按照典型CMOS工艺中一个XOR门约6个晶体管、一个8输入AND门约16个晶体管估算,仅唤醒逻辑的比较器就需要约12,288×6+1,536×1698,00012{,}288 \times 6 + 1{,}536 \times 16 \approx 98{,}000个晶体管——接近10万个晶体管。

发射队列的基本功能

发射队列执行三个紧密耦合的基本功能:存储唤醒选择

存储(Storage)

发射队列为每条等待执行的指令维护一个表项(entry)。表项中存储了指令执行所需的全部信息——至少包括操作类型、源操作数标签和就绪位。根据数据捕捉与非数据捕捉的设计选择(27.3 节),表项中可能还包含源操作数的实际数据值。发射队列的总项数决定了处理器能够同时跟踪多少条待发射指令。

表 27.3列出了一些商用处理器的发射队列容量,包括其分区方式和具体配置。

处理器IQ总项数组织方式发射宽度分区详情
MIPS R10000 (1996)48分布式(3个)4整数16项 + 浮点16项 + 地址16项,压缩结构
Alpha 21264 (1998)35分布式(2个)6整数20项 + 浮点15项,压缩结构
Intel P6/PPro (1995)20集中式3统一保留站20项
Intel Skylake (2015)97集中式8统一调度器97项,内部按端口分组仲裁
Intel Golden Cove (2021)160集中式12统一调度器,支持12个执行端口
AMD Zen 3 (2020)96分布式(4个)10ALU×\times4端口 + AGU×\times3端口 + FP + BR
AMD Zen 4 (2022)112分布式(4个)10INT调度器增至4×\times28项,FP/SIMD调度器扩大
ARM Cortex-A77 (2019)120分布式(4个)8INT×\times2 + FP/NEON + MEM
ARM Neoverse V2 (2022)160分布式(多个)12按功能分区,支持SVE
Apple M1 Firestorm (2020)\sim164分布式8INT约64项 + FP约48项 + MEM约52项(推测值)

商用处理器发射队列容量与组织方式详表

从表中可以看出一个清晰的趋势:1990年代的处理器IQ总容量在20\sim48项,2010年代增长到80\sim100项,2020年代已达到120\sim160项。IQ容量的增长反映了对更大指令窗口的持续需求,但增长速度受限于唤醒/选择逻辑的复杂度。

值得注意的是表中"组织方式"一列的演变。Intel始终坚持集中式(统一调度器),而AMD和ARM系列则普遍采用分布式。Apple的高性能核心(Firestorm/Avalanche)也是分布式的。这一分歧反映了两种不同的设计哲学:Intel依靠工程优化(如分组仲裁、层次化CAM)来管理大规模集中式结构的复杂度;AMD和ARM则通过分治策略将复杂度分解到多个小结构中。两种方案都成功地实现了高性能,说明在当前的工艺水平和IQ规模下,两种方案的性能差异并不显著——真正的差异在于设计验证和物理设计的工作量。

IQ容量与IPC的关系

发射队列的容量对处理器IPC的影响并非线性的。当IQ容量从一个很小的值(如8项)开始增长时,IPC提升非常明显——每增加一项都可能让一条原本被阻塞的指令找到位置。但随着容量增长,边际收益递减:一个128项的IQ和一个160项的IQ之间的IPC差异可能不到1%。

这种边际递减效应可以从概率角度理解。假设处理器的平均指令窗口利用率为UU(即平均U×NU \times N项被占用),则IQ溢出(导致分发暂停)的概率近似为:

Poverflowe2(1U)2NP_{\text{overflow}} \approx e^{-2(1-U)^2 N}

对于U=0.7U=0.7N=64N=64Poverflowe11.52105P_{\text{overflow}} \approx e^{-11.52} \approx 10^{-5}——IQ几乎从不溢出。增大NN到96只会将这个已经极小的概率进一步降低,IPC改善微乎其微。但当U=0.9U=0.9(高负载工作场景)时,Poverflowe1.280.28P_{\text{overflow}} \approx e^{-1.28} \approx 0.28——约28%的周期发生溢出!此时增大NN到96会将溢出率降低到e1.920.15e^{-1.92} \approx 0.15,IPC改善显著。

因此,IQ容量的设计需要针对目标工作负载的"最坏情况"(或某个高百分位数,如95th percentile)而非"平均情况"来确定。在实际设计中,微架构师通常通过RTL仿真在大量benchmark上测量IPC与IQ容量的关系曲线,找到"膝盖点"(knee point)——即IPC增长开始显著变缓的容量值——作为设计目标。一个经验法则是:IQ容量应约为ROB容量的1/22/31/2\sim2/3——如果ROB有256项,IQ容量设为128\sim160项通常是合理的。这是因为IQ中的指令是ROB中指令的一个子集(已分发但尚未执行的指令),而ROB中还包含已执行但尚未提交的指令。当然,这个经验法则需要根据具体的流水线深度、cache miss率和分支误预测率进行调整——cache miss率高的工作负载中,大量指令在IQ中长时间等待Load结果,IQ的有效利用率降低,可能需要更大的IQ容量来维持性能。

唤醒(Wakeup)

当一条指令在功能单元中完成执行、其结果值被写回物理寄存器文件时,产生该结果的指令会在公共数据总线(Common Data Bus,CDB)上广播其目标物理寄存器的标签。发射队列中的每一个表项都将CDB上广播的标签与自己存储的源操作数标签进行比较(tag match)。如果匹配成功,说明该表项等待的某个源操作数已经就绪,对应的就绪位被置1。这个过程称为唤醒(wakeup)。

唤醒操作在硬件上通过CAM(Content-Addressable Memory,内容寻址存储器)实现。每个CDB端口对应一组比较器,与发射队列中所有表项的源操作数标签并行比较。如果发射队列有NN个表项,每条指令有2个源操作数,CDB有WW个写入端口(即WW条指令可以同时完成执行),则唤醒逻辑需要 N×2×WN \times 2 \times W 个标签比较器。对于一个96项的发射队列和8个CDB端口,这意味着 96×2×8=153696 \times 2 \times 8 = 1536 个比较器——这是一个面积和功耗都相当可观的结构。

设计提示

唤醒逻辑的复杂度与发射队列容量NN和CDB端口数WW乘积成正比。这意味着同时增大NNWW将导致复杂度的超线性增长。这是限制发射队列容量和发射宽度的基本物理约束之一。在实际设计中,当N×WN \times W超过一定阈值后,唤醒逻辑的延迟可能成为处理器时钟频率的关键路径。

唤醒逻辑的电路结构

唤醒逻辑是发射队列中面积和功耗最大的组件,理解其电路实现对于优化IQ设计至关重要。

从电路层面看,唤醒逻辑的核心是一个比较器矩阵。矩阵的每一行对应一个IQ表项的一个源操作数,每一列对应一个CDB端口。矩阵中的每个元素是一个tt位比较器(tt为标签位宽,通常7\sim8位)。

一个tt位比较器的典型实现包含:

  • tt个异或(XOR)门,逐位比较两个标签。XOR门的输出为0表示对应位相同,为1表示不同。

  • 一个tt输入的NOR门(或等效的NAND-AND树),将所有XOR的输出汇总:只有当所有位都相同(所有XOR输出为0)时,NOR的输出为1,表示匹配。

比较器的延迟为tXOR+tNORt_{\text{XOR}} + t_{\text{NOR}}。对于8位标签,XOR延迟约1个FO4(Fan-Out of 4)门延迟,NOR需要分解为两级4输入NOR(或NAND-AND),延迟约2个FO4。因此单个比较器的延迟约3个FO4。在14nm工艺中,一个FO4约为10\sim12 ps,所以比较器延迟约30\sim36 ps——远小于一个时钟周期(在5 GHz下为200 ps),为唤醒后的就绪位更新和选择逻辑留出了充足的时间余量。

在实际的CMOS实现中,CAM比较器通常使用预充电匹配线(precharged match line)架构。工作过程如下:

  1. 预充电阶段(时钟低电平期间):匹配线(match line)被预充电到VddV_{dd}。每个IQ表项有一条独立的匹配线。

  2. 求值阶段(时钟高电平期间):CDB上的搜索标签被施加到所有表项的比较晶体管上。每个标签位有一对NMOS下拉晶体管:如果该位不匹配(XOR=1),下拉晶体管导通,将匹配线放电到VssV_{ss}(低电平)。

  3. 判断:如果所有tt位都匹配(所有XOR=0),没有任何下拉晶体管导通,匹配线保持高电平——表示匹配。如果任何一位不匹配,匹配线被放电到低电平——表示不匹配。

预充电匹配线架构的延迟由最慢的放电路径决定。在匹配情况下(匹配线保持高电平),不需要等待放电,延迟最短。在不匹配情况下,至少有一个下拉晶体管导通,匹配线通过该晶体管放电。放电时间取决于匹配线的总电容CMLC_{\text{ML}}和下拉晶体管的驱动能力IpullI_{\text{pull}}

匹配线电容:CML=t×2×Cdrain+CwireC_{\text{ML}} = t \times 2 \times C_{\text{drain}} + C_{\text{wire}},其中CdrainC_{\text{drain}}为每个下拉晶体管的漏极电容,CwireC_{\text{wire}}为匹配线的金属布线电容。对于t=8t=8位,匹配线上有8×2=168 \times 2 = 16个下拉晶体管(每位2个,分别对应标签位为0和1的情况),加上布线电容,CML3050C_{\text{ML}} \approx 30\sim50 fF。

在14nm工艺中,一个最小尺寸NMOS的驱动电流约为100\sim150 μ\muA(在Vdd=0.7V_{dd}=0.7 V下)。放电时间tdischarge=CML×Vswing/Ipull40fF×0.35V/125μA112t_{\text{discharge}} = C_{\text{ML}} \times V_{\text{swing}} / I_{\text{pull}} \approx 40\,\text{fF} \times 0.35\,\text{V} / 125\,\mu\text{A} \approx 112 ps。这已经超过了半个时钟周期(100 ps在5 GHz下),因此需要使用更大尺寸的下拉晶体管或更低的电压摆幅来加速放电。

一种常用的优化是分段匹配线(segmented match line):将t=8t=8位分为两段(如高4位和低4位),每段有自己的局部匹配线。高段先求值,如果高段不匹配就通过门控关闭低段的求值——节省了不匹配情况下低段的放电功耗。同时,每段匹配线的电容只有全段的一半,放电速度加倍。

另一种优化是差分匹配线(differential match line)架构。传统的单端匹配线通过放电来指示不匹配,而差分架构使用一对互补的匹配线(ML和ML\overline{\text{ML}})。匹配时两条线保持差分状态,不匹配时两条线都被放电。差分感知放大器(sense amplifier)检测两条线之间的电压差,比单端架构的阈值检测更快、更抗噪声。差分架构的面积约为单端的1.5倍(多一条匹配线和感知放大器),但速度提升约20\sim30%。在对时序要求极其严格的高频IQ中,差分架构可能是值得的选择。

从工艺演进的角度看,随着制程从14nm缩小到5nm甚至3nm,CAM单元的面积不断缩小,但互连延迟(wire delay)相对增加。这意味着CAM的速度瓶颈正在从晶体管延迟(transistor delay)转移到布线延迟(wire delay)——匹配线和搜索线的RC延迟成为关键路径。在先进工艺中,分段匹配线和分层CAM(将大规模CAM分为多个小规模子CAM,通过层次化连接)变得更加重要。

当比较器报告匹配后,对应的就绪位需要被置1。这一操作通过一个OR门实现——将比较器的匹配信号与当前就绪位做OR,写入就绪位锁存器。如果有多个CDB端口,需要一个多输入OR门将所有端口的匹配信号汇总:

SrcReadynew=SrcReadyoldORw=1WMatch(w) \texttt{SrcReady}_{\text{new}} = \texttt{SrcReady}_{\text{old}} \mathop{\texttt{OR}} \bigvee_{w=1}^{W} \texttt{Match}(w)

其中Match(w)\texttt{Match}(w)为第ww个CDB端口的匹配信号。

推测性唤醒与重放

实际上,在现代处理器中唤醒信号的发送通常不需要等到指令真正完成执行。对于具有确定延迟的指令(如整数加法,延迟为1个周期),唤醒信号可以在指令开始执行的同一个周期就发出——因为在下一个周期该指令的结果一定会出现在旁路网络上。这种推测性唤醒(speculative wakeup)使得被唤醒的指令可以在下一个周期立即开始执行,通过旁路(bypass/forwarding)网络获取操作数,而不需要等待结果写入物理寄存器文件。推测性唤醒是维持背靠背执行(back-to-back execution)的关键——相邻的依赖指令可以在连续的周期中被发射和执行。

然而,推测性唤醒对延迟不确定的指令(如cache访问的load指令)存在风险:如果load指令在L1 cache未命中,实际延迟远大于预期的1\sim2个周期,则被推测性唤醒的后续指令在尝试读取操作数时将得到错误的数据。处理器必须检测这种情况并重放(replay)被错误唤醒的指令链,这将在后续章节中详细讨论。

唤醒逻辑的功耗分析

唤醒CAM是发射队列中功耗最高的组件。其功耗来源可以分为以下几个部分:

  1. 搜索线(Search Line)驱动功耗:每个周期,WW个CDB端口的标签被驱动到全局搜索线。每条搜索线的长度等于IQ的全部NN行(因为标签需要与每一行的存储标签比较)。搜索线的电容正比于NN——行数越多,搜索线越长,翻转功耗越高。对于W=8W=8个端口、每个标签t=8t=8位,每周期有W×t=64W \times t = 64条搜索线翻转,平均每条翻转概率50%(标签值随机变化),实际翻转线数约32条。

  2. 匹配线放电功耗:如前所述,每次不匹配都会导致匹配线放电。放电功耗为CML×Vdd2/2C_{\text{ML}} \times V_{dd}^2 / 2。由于大多数比较结果为不匹配(概率约96%),几乎每条匹配线每个周期都会被放电和再充电——这是一个显著的功耗来源。

  3. 预充电功耗:每个周期开始时,所有N×2N \times 2条匹配线(NN个表项、每个2个源操作数)被预充电到VddV_{dd}。预充电功耗为(N×2)×CML×Vdd2/2(N \times 2) \times C_{\text{ML}} \times V_{dd}^2 / 2。对于N=96N=96CML=40C_{\text{ML}}=40 fF、Vdd=0.7V_{dd}=0.7 V:192×40fF×0.49/21.9192 \times 40\,\text{fF} \times 0.49/2 \approx 1.9 pJ/周期 =1.9= 1.9 pJ ×5\times 5 GHz 9.4\approx 9.4 mW。

总的唤醒CAM功耗约为搜索线+匹配线+预充电的总和,典型值在15\sim30 mW范围内(5 GHz,14nm工艺)。相比之下,整个处理器核心的功耗约5\sim10 W,IQ唤醒占0.3\sim0.6%。虽然比例不高,但IQ的功耗密度(W/mm2^2)在核心中属于最高区域,可能导致局部热点(hotspot),需要在物理设计中特别注意散热。

多周期延迟指令的唤醒策略

不同类型的指令具有不同的执行延迟,这对唤醒策略产生重要影响:

指令类型典型延迟(周期)唤醒策略
整数ALU(ADD/SUB/AND/OR)1推测性唤醒,发射即唤醒
整数移位(SHL/SHR)1推测性唤醒
整数乘法(MUL)3\sim4延迟唤醒,执行倒数第2周期唤醒
整数除法(DIV)10\sim40完成时唤醒(不推测)
浮点加法(FADD)3\sim5延迟唤醒
浮点乘法(FMUL)4\sim5延迟唤醒
Load(L1命中)4\sim5推测性唤醒(假设L1命中)
Load(L1未命中)10\sim200+重放后重新唤醒

对于确定延迟的多周期指令(如3周期的整数乘法),唤醒信号可以在指令开始执行后的第L2L{-}2个周期发出(LL为延迟),使得被唤醒的后续指令恰好在第LL个周期开始执行时通过旁路网络获取结果。这种延迟唤醒需要一个简单的移位寄存器——将指令的目标标签延迟L1L{-}1个周期后才广播到唤醒总线。

对于不确定延迟的Load指令,推测性唤醒的风险最大。现代处理器通常假设Load在L1 cache命中(延迟4\sim5周期),在Load发射后的第3\sim4周期就发出唤醒信号。如果L1确实命中,后续指令可以无缝执行。如果L1未命中,处理器需要取消唤醒并触发重放——所有被该Load推测性唤醒的指令及其传递依赖的指令都必须重新进入等待状态。重放机制的设计将在后续章节中详细讨论。

推测性唤醒的时序要求非常严格。以1周期延迟的ALU指令为例,唤醒信号的传播路径如下:

  1. 周期TT前半段:指令II开始在ALU中执行,同时其目标标签DstTagI\texttt{DstTag}_I被发送到唤醒总线。

  2. 周期TT后半段:IQ中所有表项的源标签与DstTagI\texttt{DstTag}_I进行比较,匹配的表项将就绪位置1。

  3. 周期T+1T{+}1前半段:被唤醒的指令JJ(其所有源操作数现在都就绪)参与选择逻辑的仲裁。

  4. 周期T+1T{+}1后半段:JJ被选中,其信息被读出并送往PRF/旁路网络。

  5. 周期T+2T{+}2JJ开始在ALU中执行,通过旁路网络获取II的结果(II在周期TT完成执行,结果在周期T+1T{+}1写回PRF,旁路网络在周期T+1T+2T{+}1\sim T{+}2前递数据)。

选择(Select)

当发射队列中有多条指令同时处于就绪状态时,选择逻辑(select logic)负责从中挑选指令发射到功能单元。选择逻辑需要满足以下约束:

  1. 资源约束:每个功能单元在每个周期只能接受一条指令。如果有多条就绪指令需要使用同一个功能单元,选择逻辑只能选择其中一条(或几条,如果有多个同类功能单元)。

  2. 优先级约束:当就绪指令数量超过可用功能单元数量时,需要某种优先级策略来决定谁先被发射。常见的优先级策略包括最老优先(oldest-first)、随机选择、以及基于依赖链长度的启发式策略。

  3. 速度约束:选择逻辑必须在一个时钟周期内完成,因此不能采用过于复杂的仲裁算法。

选择逻辑在硬件上通常实现为一个优先级编码器(priority encoder)或仲裁器(arbiter)。最简单的实现是基于表项位置的固定优先级,但这不能保证年龄最老的指令优先发射。更复杂的实现使用年龄矩阵(age matrix)来跟踪指令间的相对年龄关系,从而实现严格的最老优先策略。

选择逻辑的硬件实现

选择逻辑的基本功能是:从NN个候选指令中选出PP条就绪指令(PP为发射端口数),且尽量选择年龄最老的。这是一个NN-to-PP仲裁问题

最简单的实现——固定优先级编码器——将表项按位置编号赋予固定优先级。对于压缩结构,位置编号恰好反映年龄,这是最优的。对于非压缩结构,位置编号与年龄无关,因此需要更复杂的方案。

一个NN-to-1的优先级编码器可以用树形结构实现,延迟为O(log2N)O(\log_2 N)级门。对于N=96N=96,需要log296=7\lceil\log_2 96\rceil = 7级门,每级约1个FO4延迟,总延迟约7个FO4。

NN-to-PP的仲裁器需要选出PP条就绪指令,且不能重复选择。一种常见实现是级联(cascade)方式:第一个优先级编码器选出最高优先级的就绪指令并将其从候选中屏蔽掉,第二个编码器从剩余候选中选出次高优先级的,依此类推。这种级联方式的延迟为O(P×log2N)O(P \times \log_2 N),对于P=8P=8N=96N=96约56个FO4——在高频设计中可能需要2\sim3个周期才能完成。

为了解决级联仲裁器的延迟问题,现代处理器通常采用并行仲裁器。其核心思想是将NN-to-PP仲裁分解为PP个独立的N/PN/P-to-1仲裁,每个发射端口只负责从IQ的一个子集中选择。这将延迟降低到O(log2(N/P))O(\log_2(N/P)),代价是可能错过最优的全局选择。

唤醒和选择之间存在紧密的时序耦合关系。在理想情况下,唤醒和选择在同一个周期内完成:某条指令在本周期被唤醒(其最后一个源操作数变为就绪),在同一周期内被选择逻辑选中并在下一个周期开始执行。这要求唤醒逻辑和选择逻辑在一个时钟周期的前半段和后半段分别完成——唤醒在前半周期更新就绪位,选择在后半周期根据更新后的就绪位做出选择。这是一个非常紧张的时序约束,在高频率处理器中可能需要将唤醒和选择拆分到两个流水线级中。

发射队列的三个核心功能:存储指令表项、唤醒(通过CDB标签匹配更新就绪位)、选择(从就绪指令中挑选发射)
发射队列的三个核心功能:存储指令表项、唤醒(通过CDB标签匹配更新就绪位)、选择(从就绪指令中挑选发射)

图 27.3总结了发射队列的三个核心功能及其交互关系。指令从分发阶段写入发射队列的空闲表项;CDB上的标签广播通过唤醒逻辑更新表项的就绪位;选择逻辑根据就绪状态和优先级策略选出要发射的指令,将其送往功能单元执行。

唤醒-选择的时序耦合

唤醒和选择之间的时序耦合是发射队列设计中最棘手的问题之一。理想情况下,一条指令在被唤醒的同一个周期内就被选择发射——这要求唤醒和选择在一个时钟周期内串行完成。我们来定量分析这条关键路径。

单周期唤醒-选择路径分析

假设时钟周期为TclkT_{\text{clk}},需要在TclkT_{\text{clk}}内完成以下操作:

  1. CDB驱动:执行单元将结果标签驱动到全局唤醒总线。延迟t123t_1 \approx 2\sim3个FO4(包括驱动器延迟和布线延迟)。

  2. 标签比较:CAM比较器将唤醒标签与所有表项的源标签比较。延迟t23t_2 \approx 3个FO4(XOR + NOR树)。

  3. 就绪位更新:比较结果通过OR门更新就绪位,就绪位写入锁存器。延迟t312t_3 \approx 1\sim2个FO4。

  4. 就绪位汇总:将两个源操作数的就绪位取AND,生成指令的总就绪信号Ready[i]=SrcReady1[i]ANDSrcReady2[i]\texttt{Ready}[i] = \texttt{SrcReady1}[i] \mathop{\texttt{AND}} \texttt{SrcReady2}[i]。延迟t41t_4 \approx 1个FO4。

  5. 选择仲裁:选择逻辑从所有就绪指令中选出PP条。延迟t5log2N7t_5 \approx \log_2 N \approx 7个FO4(对于N=96N=96的树形编码器)。

  6. 选择结果输出:被选中指令的信息被读出并锁存。延迟t62t_6 \approx 2个FO4。

总延迟:ttotal=t1+t2+t3+t4+t5+t63+3+2+1+7+2=18t_{\text{total}} = t_1 + t_2 + t_3 + t_4 + t_5 + t_6 \approx 3 + 3 + 2 + 1 + 7 + 2 = 18个FO4。

在14nm工艺中,一个FO4约为10\sim12 ps,所以18×1119818 \times 11 \approx 198 ps。这刚好等于5 GHz的时钟周期(200 ps),时序极其紧张——没有留给时钟偏移(clock skew)、工艺变异(process variation)和温度裕量的余量。

两周期唤醒-选择方案

当单周期方案的时序无法收敛时,设计者必须将唤醒和选择拆分到两个流水线级:

  • 周期1(唤醒级):CDB标签广播\toCAM比较\to就绪位更新\to就绪位锁存。延迟约3+3+2=83 + 3 + 2 = 8个FO4——在200 ps的周期中有充足余量。

  • 周期2(选择级):从锁存的就绪位中选出PP条就绪指令\to读出指令信息。延迟约1+7+2=101 + 7 + 2 = 10个FO4——同样有充足余量。

两周期方案的代价是:一条指令从被唤醒到被选中需要2个周期而不是1个。对于背靠背的依赖链,这意味着每两条依赖指令之间多出1个周期的气泡。如果关键依赖链长度为LL,两周期方案比单周期方案多花LL个周期——对于L=20L=20的典型依赖链,这可能导致约5\sim10%的IPC损失。

半周期锁存方案

一种折中方案是使用半周期锁存(half-cycle latch):将时钟的一个完整周期分为两个半周期,在第一个半周期完成唤醒,在第二个半周期完成选择。每个半周期的时间预算为Tclk/2=100T_{\text{clk}}/2 = 100 ps(在5 GHz下),约8\sim9个FO4。唤醒延迟约8个FO4,选择延迟约10个FO4——选择部分稍有超时。

实际处理器中经常采用的技巧是:在选择逻辑中使用主从锁存器(master-slave latch)的透明模式——就绪位在锁存器的主级(master)更新后,无需等到从级(slave)锁存即开始选择操作。这种"时间借用"(time borrowing)技术可以有效地将唤醒和选择的时间窗口重叠,实现接近单周期的效果。

Intel的统一调度器(如Skylake、Golden Cove)据信使用了类似的半周期/时间借用技术来实现单周期唤醒-选择,从而维持背靠背依赖链的高效执行。具体的实现细节是Intel的商业机密,但学术界和逆向工程的分析表明,Intel的调度器在关键路径上使用了高度优化的定制电路(而非标准综合),以满足极其紧张的时序约束。

发射队列的设计约束与权衡

发射队列的设计必须在以下几个相互矛盾的目标之间取得平衡:

  • 容量:更大的发射队列能跟踪更多指令,提供更大的指令窗口来发掘ILP。但更大的队列意味着更多的CAM比较器(唤醒)和更长的优先级编码链(选择),增加面积、功耗和延迟。

  • 端口数:更多的CDB写入端口和发射端口允许更高的吞吐率,但端口数直接乘以唤醒逻辑的比较器数量。

  • 时序:唤醒和选择的延迟决定了发射流水线的级数。如果无法在一个周期内完成唤醒+选择,背靠背执行的指令之间将出现气泡。

  • 功耗:唤醒逻辑中的大量CAM比较器在每个周期都需要进行标签比较,即使大多数比较的结果是"不匹配",翻转的比较器仍然消耗动态功耗。

硬件描述 2 — 唤醒逻辑的功耗估算

以一个N=96N=96项、W=8W=8端口、标签宽度t=8t=8位的发射队列为例,估算唤醒逻辑每周期的动态功耗。

每个周期,W=8W=8个标签被广播到所有N×2=192N \times 2 = 192个标签存储位置进行比较。每次比较涉及t=8t=8位的XOR操作,其中平均有t/2=4t/2 = 4位发生翻转(假设标签均匀分布)。每位翻转的功耗为CXOR×Vdd2×fC_{\text{XOR}} \times V_{dd}^2 \times f,其中CXORC_{\text{XOR}}为XOR门的负载电容。

总翻转次数:W×N×2×(t/2)=8×96×2×4=6,144W \times N \times 2 \times (t/2) = 8 \times 96 \times 2 \times 4 = 6{,}144次/周期。

在14nm FinFET工艺中,典型的XOR门翻转功耗约为0.05 fJ/bit。则唤醒逻辑的动态功耗约为6,144×0.050.36{,}144 \times 0.05 \approx 0.3 mW/GHz。在5 GHz频率下约1.5 mW。这看似不大,但加上NOR门、锁存器、以及布线的翻转功耗,实际唤醒逻辑的功耗通常是上述估算的5\sim10倍,即8\sim15 mW——占整个处理器核心功耗的1\sim2%。

集中式VS分布式发射队列

发射队列在组织方式上的第一个关键设计决策是集中式(centralized)还是分布式(distributed)。这一决策直接影响处理器后端的物理布局、指令调度的灵活性、以及硬件复杂度。

集中式结构

在集中式发射队列中,所有待发射的指令——无论其类型(整数运算、浮点运算、访存等)——都被存放在同一个发射队列中。分发阶段将指令写入这个统一的大队列,选择逻辑从中选出就绪指令并将其路由到相应的功能单元。

集中式发射队列:所有类型的指令共享一个统一的发射队列
集中式发射队列:所有类型的指令共享一个统一的发射队列

集中式结构最显著的优势是资源利用率高。由于所有指令共享同一个队列,某一类指令(如浮点)较少时腾出的表项可以自动被另一类指令(如整数)使用。不会出现"一个队列满了但另一个队列还有空位"的浪费情况。这对于指令组成变化较大的通用计算负载特别有利。

Intel自Pentium Pro以来的多数x86处理器采用了集中式(或接近集中式)的发射队列设计。Intel将其称为统一调度器(Unified Scheduler,又称Unified Reservation Station)。在Skylake中,统一调度器有97个表项,可以向8个执行端口中的任一个发射就绪指令。在Golden Cove中,统一调度器扩展到160个表项,支持12个执行端口。

集中式唤醒逻辑的CAM面积分析

集中式发射队列的写入端口设计

在集中式结构中,分发阶段需要将多条指令写入同一个发射队列。对于DD-wide的分发(每周期最多分发DD条指令),IQ需要DD个写入端口。但DD个写端口意味着每个表项需要DD组写数据线和DD个写使能信号——如果IQ使用标准SRAM单元,多写端口会导致面积急剧增加。

一种优化是利用集中式结构的一个特性:在非压缩结构中,每个周期写入的DD条指令必定写入DD不同的表项(因为每条指令分配到不同的空闲位置)。因此,虽然IQ有DD个逻辑写端口,但在任何给定时刻,每个表项最多被一个写端口写入——不存在两个写端口同时写同一个表项的情况。

这一特性允许使用写端口时分复用(time-division multiplexing):在一个时钟周期内,将写操作分为DD个时间片,每个时间片写入一条指令。这将DD-port的写入简化为1-port的时序写入——面积从O(D×N)O(D \times N)降低到O(N)O(N)。代价是每个时间片的宽度为Tclk/DT_{\text{clk}}/D,在5 GHz、D=6D=6的情况下约为33 ps——非常紧张,需要高速写电路。

另一种方案是写端口bank化:将IQ的NN个表项分为DD个bank,每个bank有一个独立的写端口。分发逻辑确保同一周期内的DD条指令被分配到DD个不同bank的空闲位置。这需要空闲列表以bank为单位管理——每个bank维护自己的空闲列表,分发逻辑从每个bank各取一个空闲位置。

Bank化的约束是:如果某个bank没有空闲位置(而其他bank有),则该bank无法接受新指令,可能导致分发暂停。为了降低这种情况的概率,bank的大小需要足够大(N/D16N/D \geq 16),且空闲位置的释放(由发射操作触发)在各bank之间大致均匀。

我们可以估算bank冲突导致分发暂停的概率。假设每周期分发D=6D=6条指令,IQ分为Bw=6B_w=6个write bank(每个bank96/6=1696/6=16项),指令被随机分配到各bank。bank冲突发生在两条或更多指令被分配到同一bank的情况下。根据"生日问题"的分析:

P(无冲突)=Bw!(BwD)!BwD=6!0!66=720466561.5%P(\text{无冲突}) = \frac{B_w!}{(B_w - D)! \cdot B_w^D} = \frac{6!}{0! \cdot 6^6} = \frac{720}{46656} \approx 1.5\%

等等,这里D=Bw=6D = B_w = 6,所有6条指令需要分配到6个不同的bank——这恰好是一个排列,概率为6!/661.5%6!/6^6 \approx 1.5\%。这意味着98.5%的周期会发生至少一次bank冲突——这显然不可接受。

实际上,bank冲突的处理不需要暂停分发。更实际的方案是:每个bank有2个写端口(而不是1个),允许同一周期有2条指令写入同一bank。这样,只有3条或更多指令分配到同一bank时才发生冲突。3条指令分配到同一bank的概率远低于2条(约(63)×(1/6)2×(5/6)34.6%\binom{6}{3} \times (1/6)^2 \times (5/6)^3 \approx 4.6\%),因此bank冲突率降低到可接受的水平。

集中式结构的核心挑战在于硬件复杂度。设一个集中式发射队列有NN个表项、WW个CDB端口(唤醒端口)、PP个发射端口:

  • 唤醒逻辑:需要 N×2×WN \times 2 \times W 个标签比较器。每个比较器是一个tt位的位串比较电路。整个唤醒CAM构成一个N×2N \times 2行、WW列的比较器矩阵。矩阵中每个元素占用的面积约为t×(AXOR+AAND/t)t \times (A_{\text{XOR}} + A_{\text{AND}}/t),其中AXORA_{\text{XOR}}为一个XOR单元面积,AANDA_{\text{AND}}为汇总AND树的面积。

  • 选择逻辑:需要一个NN-to-PP的优先级仲裁器,从NN个候选中选出PP条就绪指令。

  • 读取逻辑:需要PPNN-to-1的多路选择器来读出被选中指令的信息。每个MUX的宽度等于表项位宽BB

以Golden Cove为例(N=160N=160W12W \approx 12P12P \approx 12),唤醒逻辑需要 160×2×12=3840160 \times 2 \times 12 = 3840 个比较器,选择逻辑需要一个160-to-12的仲裁器。这些数字反映了集中式设计在规模上的压力——随着处理器宽度的增长,集中式发射队列的面积和功耗增长迅速。

我们可以更定量地分析CAM的面积。在7nm工艺中,一个8位CAM单元(8-bit比较器+匹配线+标签存储位)的面积约为0.8μm20.8\,\mu\text{m}^2。则Golden Cove的唤醒CAM面积约为:

ACAM=3,840×0.8μm23,072μm20.003mm2A_{\text{CAM}} = 3{,}840 \times 0.8\,\mu\text{m}^2 \approx 3{,}072\,\mu\text{m}^2 \approx 0.003\,\text{mm}^2

这看似很小,但需要注意CAM的面积远不止比较器本身——还包括标签存储SRAM、匹配线驱动器、预充电电路、以及密集的布线通道。实际CAM面积通常是纯比较器面积的5\sim8倍,即约0.0150.0240.015\sim0.024 mm2^2。加上选择逻辑、读出MUX、写入端口等,集中式发射队列的总面积可达0.050.10.05\sim0.1 mm2^2——占一个处理器核心面积(约5\sim10 mm2^2)的1\sim2%。

性能分析 1 — 集中式发射队列的利用率优势

考虑一个96项的集中式发射队列,处理器同时执行整数和浮点代码。假设工作负载中整数指令占75%、浮点指令占25%。在集中式设计中:

  • 所有96项可被任意类型指令使用。

  • 在整数密集阶段,几乎全部96项被整数指令占用。

  • 在浮点密集阶段,浮点指令可以利用大部分表项。

  • 有效利用率接近100%。

相比之下,如果将96项拆分为48项整数队列+48项浮点队列(分布式),则在整数密集阶段,整数队列可能溢出而浮点队列几乎空闲,有效利用率可能只有60%\sim70%。

集中式选择逻辑的路由问题

集中式结构中,选择逻辑不仅要从NN个就绪指令中选出PP条,还要确保每条被选中的指令被路由到正确类型的功能单元。例如,一条浮点乘法指令不能被路由到整数ALU。这引入了额外的约束:选择逻辑实际上需要解决一个带约束的多路仲裁问题。

一种常见的实现方式是将选择逻辑分为两级:第一级按功能单元类型对就绪指令进行分组,第二级在每组内独立进行优先级仲裁。例如,将所有就绪的整数指令送入ALU仲裁器,所有就绪的浮点指令送入FPU仲裁器,所有就绪的访存指令送入AGU仲裁器。每个仲裁器独立地选出要发射到其对应功能单元的指令。

这种分组仲裁的效果类似于分布式发射队列——虽然物理上共享一个存储结构,但选择逻辑在逻辑上是分布式的。Intel的"统一调度器"内部很可能就采用了类似的分组仲裁方案。

分布式结构

在分布式发射队列中,指令根据其类型被分发到不同的发射队列。每个发射队列只服务于一种或几种功能单元。分发阶段需要根据指令的操作类型决定将其送入哪个队列。

分布式发射队列:不同类型的指令被分发到专用的发射队列
分布式发射队列:不同类型的指令被分发到专用的发射队列

分布式结构的核心优势在于硬件简单性。每个小队列的容量nn远小于集中式队列的总容量NNnNn \ll N),因此每个队列的唤醒逻辑和选择逻辑都更简单:

  • 每个队列的比较器数量为 n×2×wn \times 2 \times www为该队列的CDB端口数),显著少于集中式的 N×2×WN \times 2 \times W

  • 选择逻辑从nn个候选中选择,优先级编码器的深度更浅,延迟更短。

  • 物理布局上,小队列可以紧邻其服务的功能单元放置,缩短数据传输路径。

分发路由逻辑的实现

分布式结构要求分发阶段根据指令类型将指令路由到正确的发射队列。路由逻辑的实现需要解决两个问题:(1)确定指令应进入哪个队列;(2)处理目标队列已满的情况。

路由决策通常由解码阶段生成的FUType字段驱动。一个简单的查找表(LUT)将FUType映射到目标队列编号。对于kk个队列和3位的FUType,LUT的大小为23×log2k=8×2=162^3 \times \lceil\log_2 k\rceil = 8 \times 2 = 16位——开销极小。

更复杂的情况是某些指令可以在多个功能单元上执行(例如,整数加法可以在ALU0或ALU1上执行)。如果ALU0和ALU1各有自己的发射队列,则分发逻辑需要决定将加法指令送入哪个队列。常见的策略包括:

  • 轮转分配(round-robin):交替将指令送入ALU0队列和ALU1队列。实现简单(一个1位计数器),但不能适应负载不平衡。

  • 空位优先(least-occupied first):将指令送入空闲表项较多的队列。需要比较各队列的计数器,延迟略高但负载均衡效果更好。

  • 关键路径感知:将处于长依赖链上的关键指令分配到负载较轻的队列,以减少其等待时间。这需要额外的依赖链分析硬件,通常只在研究性设计中出现。

队列满处理是分布式结构的一个重要问题。当目标队列已满时,分发阶段有两种选择:

  1. 暂停分发:停止向所有队列分发指令,等待目标队列腾出空位。这种方式实现简单但效率低——其他队列可能仍有空位,却因为一个队列满了而被迫闲置。

  2. 部分暂停:只暂停需要进入已满队列的那种类型的指令,其他类型的指令继续分发。这需要更复杂的分发逻辑——分发窗口中的指令不再是简单地顺序写入,而需要根据类型选择性地写入。

在实际设计中,大多数处理器采用"暂停分发"的简单方案,因为目标队列满的情况在精心调整容量后并不频繁。

分发路由的时序影响

分发路由逻辑位于分发阶段的关键路径上。路由决策的延迟直接加在分发到IQ写入的总延迟中。在一个4\sim6-wide的分发阶段,每条指令的路由决策需要以下步骤:

  1. 读取指令的FUType字段(来自解码)——延迟约0个FO4(字段已在分发流水线寄存器中)。

  2. LUT查找,将FUType映射到目标队列编号——延迟约1个FO4(组合逻辑)。

  3. 检查目标队列是否有空位(查询空闲位计数器)——延迟约1个FO4。

  4. 如果目标队列满,生成暂停信号——延迟约1个FO4。

  5. 如果不暂停,生成写入地址并驱动写数据到目标队列——延迟约2\sim3个FO4(包括地址译码和数据驱动)。

总延迟约5\sim6个FO4,约55\sim72 ps——在200 ps的时钟周期中占了约30%。这个延迟还需要与分发阶段的其他操作(写ROB、更新RAT等)共享时钟周期预算。

对于空位优先的负载均衡策略,延迟更高:需要比较kk个队列的空闲计数器(kk个比较器,每个log2(N/k)\lceil\log_2(N/k)\rceil位宽),然后用优先级编码器选出空位最多的队列。额外延迟约2\sim3个FO4。这就是为什么大多数商业处理器采用简单的轮转分配或固定映射,而不是更复杂的负载均衡策略——后者的时序代价超过了其带来的性能收益。

商业处理器的分布式IQ配置

AMD Zen系列处理器采用了典型的分布式发射队列设计。以下是各代的具体配置:

AMD Zen 3(2020年)的后端包含4个独立的调度器:

  1. ALU调度器:4个ALU端口(ALU0\simALU3),其中ALU0和ALU3还可执行分支指令,ALU1和ALU2可执行乘法。调度器容量约32项。

  2. AGU调度器:3个AGU端口(AGU0\simAGU2),负责Load和Store的地址计算。容量约28项。

  3. FP调度器:4个FP/SIMD端口,处理浮点运算和128/256位SIMD(AVX2)指令。容量约36项。

  4. 分支/特殊调度器:处理分支解析和特殊指令(如CPUID)。与ALU调度器共享,项数较少。

总计约96项。每个调度器的唤醒端口数约为3\sim4个(只接收与该队列相关的CDB广播)。

AMD Zen 4(2022年)在Zen 3基础上进行了扩展:

  1. INT调度器:扩展到4个子调度器,每个约28项,总计约112项整数调度容量。支持AVX-512指令的整数部分。

  2. FP/SIMD调度器:扩展以支持AVX-512的全512位宽度操作,容量增加到约48项。

  3. 总IQ容量增加到约160项(含所有调度器)。

ARM Cortex系列的高性能核心(如Cortex-A77、Cortex-X2)也普遍采用分布式发射队列设计,将整数、浮点/NEON、Load、Store分开调度。Apple的高性能核心同样使用分布式方案。

ARM Cortex-X2(2021年)的分布式调度器配置:

  1. 整数调度器0:服务ALU0和ALU1端口,容量约24项。ALU0可执行整数加减法、逻辑运算、分支解析;ALU1可执行整数加减法和逻辑运算。

  2. 整数调度器1:服务ALU2和MUL端口,容量约24项。ALU2执行简单整数运算;MUL执行整数乘法。

  3. 访存调度器:服务2个Load端口和2个Store端口,容量约36项。该调度器内部进一步区分Load和Store的选择——Load优先级高于Store。

  4. FP/NEON/SVE调度器:服务2个浮点/SIMD端口,容量约36项。支持128位NEON和可变长度SVE向量运算。

总计约120项。每个子调度器的选择逻辑只需从24\sim36项中选出1\sim2条就绪指令,仲裁器规模远小于120项的全局仲裁。

分布式IQ中的负载均衡

分布式发射队列面临的一个重要问题是负载不平衡。不同工作负载中各类型指令的比例差异很大:

工作负载类型整数指令比例浮点/SIMD比例访存指令比例
通用整数(SPEC INT)50\sim60%0\sim5%35\sim45%
科学计算(SPEC FP)20\sim30%35\sim45%25\sim35%
数据库/服务器40\sim50%5\sim10%40\sim50%
AI推理(GEMM-heavy)10\sim20%50\sim70%15\sim25%

当工作负载以整数指令为主时,整数调度器承受的压力远大于浮点调度器。如果整数调度器只有32项,而浮点调度器有36项(总计68项),那么在整数密集的SPEC INT基准测试中,整数调度器可能成为瓶颈——频繁溢出导致分发暂停,而36项的浮点调度器大部分时间空闲。

处理器设计者通过以下策略缓解负载不平衡:

  1. 非对称分区:根据目标工作负载的指令分布调整各队列的容量。例如,为整数调度器分配更多表项。但这会在浮点密集工作负载中造成相反方向的不平衡。

  2. 指令融合与共享:允许某些"跨界"指令在多个调度器中调度。例如,整数与浮点之间的数据移动指令(如MOVD)可以在整数调度器或浮点调度器中等待。

  3. 弹性分区:部分表项可以动态分配给不同类型的指令。例如,设计一个"共享池"(shared pool),当某个专用调度器满时,溢出的指令可以进入共享池等待。这种方案结合了集中式和分布式的优点,但增加了控制逻辑的复杂度。

跨队列唤醒问题

分布式设计的另一个重要优势是唤醒网络的简化。在集中式结构中,每个CDB端口的唤醒信号必须广播到所有NN个表项。在分布式结构中,某些CDB端口的唤醒信号只需广播到相关的队列——例如,浮点执行单元产生的结果标签只需广播到浮点发射队列和可能依赖浮点结果的其他队列,而不需要广播到整数发射队列(除非存在整数-浮点间的数据依赖)。这减少了全局广播线的数量和负载。

然而,分布式结构也引入了一些独有的复杂性:

  • 跨队列唤醒:当一条整数指令的结果被一条浮点指令依赖时,唤醒信号需要从整数队列的CDB传递到浮点队列。这种跨队列唤醒增加了布线复杂度和延迟。

  • 分发路由:分发阶段需要根据指令类型将指令路由到正确的队列。如果某个队列已满而其他队列有空位,分发阶段可能需要暂停(stall),即使总的空闲表项数量足够。

  • 负载不平衡:不同类型指令的比例因工作负载而异。纯整数代码可能导致整数队列频繁溢出而浮点队列空闲;纯浮点代码则相反。

跨队列唤醒的延迟开销值得具体分析。在一个布局良好的设计中,同一个调度器内部的唤醒延迟约为2\sim3个FO4门延迟(比较器延迟+匹配线传播)。跨队列唤醒则需要额外经过全局唤醒总线的驱动和传播,延迟约增加5\sim8个FO4。在5 GHz的处理器中(时钟周期200 ps,约15\sim18个FO4),这8个FO4的额外延迟占了半个时钟周期——这意味着跨队列唤醒可能比本地唤醒晚一个周期到达,导致跨队列依赖链上出现一个额外的气泡。

这个额外气泡的性能影响取决于跨队列依赖的频率。在混合整数-浮点代码中,跨类型依赖(如整数计算结果送入浮点转换指令)相对较少,跨队列气泡的IPC影响不到1%。但在访存密集的代码中,整数ALU计算的地址结果需要跨队列送到Load/Store调度器,跨队列依赖频率可达30\sim40%,额外气泡可能导致2\sim5%的IPC损失。这也是为什么一些处理器(如AMD Zen系列)将整数ALU和AGU(地址生成)放在同一个调度器中或至少共享本地唤醒总线。

AMD Zen系列的解决方案是让所有调度器共享一条全局唤醒总线。每当任一执行单元产生结果,其目标标签同时被广播到所有调度器。这消除了选择性广播的复杂性,代价是每个调度器都需要接收所有的唤醒信号——增加了每个调度器的比较器数量。但由于每个调度器的容量nn较小(28\sim48项),即使接收全部WW个唤醒端口,比较器总数仍然远小于集中式方案。

各自的优缺点

表 27.4从多个维度比较了集中式和分布式发射队列的优缺点。

维度集中式分布式
利用率高:所有表项被所有指令类型共享,不存在类型间的浪费。\sim低:各队列独立,负载不平衡时某些队列可能空闲而另一些溢出。
唤醒复杂度高:所有CDB端口对所有表项广播,比较器数量为O(N×W)O(N \times W)低:每个队列只需接收相关CDB端口的广播,比较器总量显著减少。
选择复杂度高:需要从NN个候选中选择,优先级编码器规模大。低:每个队列独立选择,规模小。
面积大:大规模CAM和仲裁器占用显著面积。小:多个小队列的总面积通常小于一个等容量的大队列。
功耗高:大量比较器每周期翻转。\sim中:小队列的比较器数量少,且可按需时钟门控。
时序紧张:唤醒和选择路径较长,可能限制时钟频率。宽松:每个队列的关键路径短,有利于高频率设计。
分发复杂度简单:所有指令写入同一个队列。复杂:需要路由逻辑将指令分发到正确的队列,且需处理某队列满的情况。
物理布局困难:大结构难以放置在芯片中心,到各FU的距离不均匀。灵活:每个小队列可紧邻对应FU放置,缩短关键路径。

集中式与分布式发射队列的比较

面积的量化比较

从面积角度进行更定量的分析。假设一个集中式发射队列有NN项,每项有2个源操作数标签(每个tt位),WW个CDB唤醒端口。唤醒CAM的比较器面积与 N×2×W×tN \times 2 \times W \times t 成正比。选择逻辑(树形优先级编码器)的面积与 N×log2NN \times \log_2 N 成正比。

现在考虑将其拆分为kk个分布式队列,每个队列N/kN/k项,每个队列的CDB端口数为W/kW/k(假设均匀分布且无跨队列唤醒)。则:

分布式总面积集中式面积=k×(N/k)×2×(W/k)×tN×2×W×t=1k \frac{\text{分布式总面积}}{\text{集中式面积}} = \frac{k \times (N/k) \times 2 \times (W/k) \times t}{N \times 2 \times W \times t} = \frac{1}{k}

即在理想假设下,拆分为kk个队列可使唤醒CAM面积降低为原来的1/k1/k

选择逻辑的面积降低更为显著。集中式的选择逻辑面积正比于Nlog2NN \log_2 N,分布式总面积正比于k×(N/k)log2(N/k)=N[log2Nlog2k]k \times (N/k) \log_2(N/k) = N[\log_2 N - \log_2 k]。面积比为:

分布式选择面积集中式选择面积=1log2klog2N \frac{\text{分布式选择面积}}{\text{集中式选择面积}} = 1 - \frac{\log_2 k}{\log_2 N}

对于N=96N=96k=4k=4,面积比为12/6.580.701 - 2/6.58 \approx 0.70——选择逻辑面积降低30%。

当然,实际情况比这复杂——跨队列唤醒和分发路由的额外开销会部分抵消这一收益——但数量级上的面积优势是确实存在的。

功耗的量化比较

分布式结构在功耗方面还有一个重要优势:时钟门控(clock gating)。当某个队列中没有需要唤醒的指令时(所有表项的源操作数都已就绪或队列为空),可以关闭该队列的唤醒CAM时钟,消除比较器的翻转功耗。在集中式结构中,由于不同类型的指令混在一起,即使浮点指令很少,唤醒逻辑也必须对所有表项进行比较——无法对特定类型的指令进行选择性时钟门控。

假设在某个时间窗口内,浮点队列(占总容量的25%)完全空闲。分布式结构可以对浮点队列施加时钟门控,节省25%的唤醒功耗。集中式结构则无法获得这一收益。在移动端处理器中,这种节省对电池续航有显著贡献。

物理布局的影响

在芯片物理设计层面,集中式和分布式发射队列的差异尤为显著。

集中式发射队列是一个大面积的单一结构,通常位于处理器后端的中心位置。从这个中心位置到各个功能单元的物理距离不尽相同——最近的功能单元可能紧邻IQ,而最远的可能在芯片的另一端。由于发射到执行的信号传播延迟与物理距离成正比,到不同FU的传播延迟差异可能达到1\sim3个FO4门延迟——在高频设计中,这足以影响时序收敛。

设计者通常需要在IQ的输出端插入缓冲器(buffer/repeater)来驱动到远端FU的信号。这些缓冲器增加了面积和功耗,也增加了发射到执行的延迟。在极端情况下,到最远端FU的信号可能需要额外的流水线级——这意味着不同功能单元的发射到执行延迟不同,增加了旁路网络的设计复杂度。

分布式发射队列则可以将每个小队列紧邻其服务的功能单元放置。ALU调度器放在ALU旁边,FPU调度器放在FPU旁边,AGU调度器放在Load/Store单元旁边。这种布局使得每个队列到其FU的物理距离最短,信号传播延迟最小。但跨队列的唤醒信号则需要长距离传输——从一个功能单元的输出到另一个队列的唤醒输入可能跨越整个后端。

发射队列的物理布局是后端设计中最受关注的问题之一,因为它直接影响信号传播延迟和时钟频率。

在7nm及更先进的工艺中,一个处理器核心的后端宽度约为1\sim2 mm。信号在金属互连中的传播速度约为c/3c/3cc为光速),每毫米的传播延迟约为3.3 ps。但由于互连的RC延迟和缓冲器的插入,实际每毫米延迟约为20\sim50 ps。对于1 mm的传播距离,延迟约为30\sim50 ps——在200 ps的时钟周期(5 GHz)中占了15\sim25%。

设计权衡 1 — 集中式与分布式的工程选择

选择集中式还是分布式发射队列,取决于设计团队在以下因素之间的权衡:

  1. 目标频率:追求极高频率(如5 GHz以上)的设计倾向于分布式,因为小队列更容易满足时序约束。

  2. 发射宽度:宽发射处理器(10-wide及以上)几乎必须采用分布式,因为集中式的唤醒和选择逻辑在这种规模下变得不可承受。

  3. 目标负载:通用计算负载(指令类型多变)更适合集中式以获得高利用率;专用计算负载(如纯浮点的HPC)可以用分布式而不损失太多利用率。

  4. 功耗预算:功耗敏感的设计(如移动端处理器)倾向于分布式,利用时钟门控降低空闲队列的功耗。

在实际工程中,混合方案也很常见。例如Intel的"统一调度器"实际上在内部可能按端口分组进行选择仲裁,不是完全的一体化CAM。AMD Zen虽然是分布式的,但各调度器共享唤醒总线以简化跨队列依赖处理。

数据捕捉VS非数据捕捉

发射队列设计的第二个关键维度是数据捕捉(data capture)与非数据捕捉(non-data capture)的选择。这一维度决定了操作数的(data value)是否被存储在发射队列的表项中。

数据捕捉结构

在数据捕捉结构中,发射队列的每个表项不仅存储操作数的标签和就绪位,还存储操作数的实际数据值。这意味着发射队列本身就是一个"小型寄存器文件"——当指令被选中发射时,其操作数的值可以直接从发射队列表项中读出,无需再访问物理寄存器文件(PRF)。

数据捕捉结构的工作流程如下:

  1. 分发时:指令被写入发射队列表项。对于已就绪的源操作数,其值在分发时从PRF读取并写入表项的数据域。对于尚未就绪的操作数,只记录标签和"未就绪"状态。

  2. 唤醒时:当CDB上广播了某个操作数的标签和值时,发射队列中等待该操作数的表项不仅更新就绪位,还将CDB上的数据值捕捉(capture)到表项的数据域中。这就是"数据捕捉"这一名称的由来。

  3. 发射时:指令被选中后,其操作数的值直接从表项的数据域中读出,送往功能单元。不需要再访问PRF。

数据捕捉结构:发射队列表项包含数据域,CDB广播时将数据值捕捉到表项中
数据捕捉结构:发射队列表项包含数据域,CDB广播时将数据值捕捉到表项中

数据捕捉的时序分析

数据捕捉操作的时序路径是该结构设计的关键约束。在同一个时钟周期内,以下操作必须顺序完成:

  1. CDB上的标签被驱动到唤醒总线(从执行单元输出到IQ输入的布线延迟)。

  2. 标签与每个表项的源操作数标签进行CAM比较(比较器延迟约3个FO4)。

  3. 比较结果(匹配/不匹配)驱动数据写入使能信号。

  4. CDB上的64位数据值通过传输门写入匹配表项的数据域(写入延迟约2个FO4)。

  5. 数据值在锁存器中稳定(建立时间约1个FO4)。

总路径延迟约为twire+3+2+1=twire+6t_{\text{wire}} + 3 + 2 + 1 = t_{\text{wire}} + 6个FO4。如果布线延迟为3个FO4,总延迟约9个FO4——在200 ps的时钟周期中占了约50%。这意味着数据捕捉的关键路径比纯唤醒(只需步骤1\sim3,约6个FO4)更长,可能成为时钟频率的瓶颈。

此外,CDB上同时传输标签和数据增加了总线的电容负载。对于W=8W=8个CDB端口、每条72位宽(8位标签+64位数据),总共576条全局信号线需要驱动到所有N=96N=96个表项。每条信号线的扇出为N=96N=96(每个表项一个接收端),负载电容约为96×Cgate96×0.5fF=48fF96 \times C_{\text{gate}} \approx 96 \times 0.5\,\text{fF} = 48\,\text{fF}。驱动48 fF的负载需要一个较大的缓冲器链,增加了CDB驱动器的面积和功耗。

数据捕捉结构的设计理念源自Tomasulo算法中的原始保留站设计。在Tomasulo的方案中,保留站不仅记录操作数的来源(标签),还存储操作数的值。这种设计在Tomasulo的时代(1967年)是自然的选择,因为当时没有独立的物理寄存器文件的概念——保留站本身就承担了临时存储操作数的功能。

Tomasulo保留站与现代数据捕捉IQ的差异

虽然数据捕捉结构的思想源自Tomasulo的保留站,但现代数据捕捉IQ与原始保留站有几个重要的差异:

  1. 集中式vs分散式存储:Tomasulo的保留站分散在各个功能单元旁边,每个功能单元有自己的保留站。现代数据捕捉IQ可以是集中式的——所有指令共享一个统一的数据捕捉队列。

  2. 标签宽度:Tomasulo原始设计中的标签是保留站编号(只有3\sim4位),因为保留站总数很少(如IBM System/360 Model 91只有约15个保留站)。现代IQ的标签是物理寄存器编号,通常7\sim8位。

  3. 操作数宽度:IBM 360/91是32位浮点处理器(单精度),每个操作数32位。现代处理器是64位整数+64位浮点,部分处理器还支持128位或更宽的SIMD操作数。更宽的操作数使得数据捕捉的面积和功耗代价更高。

  4. 规模:IBM 360/91只有不到20个保留站条目。现代处理器的IQ可达160项。数据捕捉的面积随NN线性增长,在160项的规模下变得极为昂贵。

正是由于上述差异,数据捕捉结构在现代处理器中已经不再是主流选择——操作数宽度和IQ容量的增长使得数据捕捉的面积代价从"可接受"变为"不可承受"。

数据捕捉的CDB写端口设计

在数据捕捉结构中,CDB不仅传输标签(用于唤醒),还传输完整的64位数据值(用于捕捉)。这意味着CDB的物理宽度为t+64t + 64位(标签+数据),对于8位标签,CDB每条总线的宽度为72位。如果有W=8W=8个CDB端口,则需要8×72=5768 \times 72 = 576条全局广播线。

更关键的是数据写入IQ表项的机制。当某个CDB端口的标签与表项ii的源操作数1标签匹配时,该CDB上的64位数据值需要被写入表项iiSrc1Data域。这本质上是一个条件写入操作:写使能信号由CAM比较器的匹配输出驱动。

对于NN个表项和WW个CDB端口,每个数据域需要WW个条件写入端口(因为每个CDB端口都可能同时匹配该表项)。每个写入端口包含64个传输门(transmission gate),由匹配信号控制。总的传输门数量为:

N×2×W×64=96×2×8×64=98,304个传输门N \times 2 \times W \times 64 = 96 \times 2 \times 8 \times 64 = 98{,}304 \text{个传输门}

每个传输门约4个晶体管(2个NMOS + 2个PMOS),仅数据捕捉的写入网络就需要约40万个晶体管。这还不包括数据存储本身的SRAM单元(每位6个晶体管)。

数据捕捉结构的优点

  • 消除PRF读取延迟:指令发射时操作数直接可用,不需要额外的PRF读取流水线级。这可以减少发射到执行之间的延迟,有利于缩短关键路径上的依赖链。

  • 减少PRF端口压力:由于发射时不需要读取PRF,PRF的读端口数可以减少。在宽发射处理器中,PRF读端口数量是物理设计的重要约束。

  • 简化旁路网络:CDB上的数据直接被发射队列捕捉,减少了对独立旁路网络的需求。

数据捕捉结构的缺点

  • 面积大:每个表项需要存储两个完整的操作数值(通常为64位),使得每个表项的位宽从"仅标签"的约40\sim60位增加到约170\sim200位。对于一个96项的发射队列,数据域的存储面积约为 96×128=12,28896 \times 128 = 12{,}288\approx 1.5 KB——这还没算上标签和控制位。

  • 功耗高:CDB数据在每次唤醒时被写入发射队列的数据域,这些宽数据写入(64位×\times匹配的表项数)消耗可观的动态功耗。

  • CDB布线负载大:CDB不仅需要广播标签(通常6\sim8位),还需要广播完整的数据值(64位),导致CDB布线的面积和功耗显著增加。

非数据捕捉结构

在非数据捕捉结构中,发射队列的表项不存储操作数的数据值——只存储操作数的标签(tag)和就绪位(ready bit)。操作数的值被存放在独立的物理寄存器文件(PRF)中。当指令被发射队列选中发射后,需要用标签访问PRF来读取操作数的值。

非数据捕捉结构:发射队列只存储标签和就绪位,操作数的值在发射后从PRF读取
非数据捕捉结构:发射队列只存储标签和就绪位,操作数的值在发射后从PRF读取

非数据捕捉结构的工作流程如下:

  1. 分发时:指令被写入发射队列表项,只记录源操作数的物理寄存器标签和就绪状态。操作数的值不被写入发射队列。

  2. 唤醒时:CDB上只需广播标签(不需要广播数据值),发射队列中的表项只进行标签比较和就绪位更新。

  3. 发射时:指令被选中后,其源操作数标签被送往PRF。PRF根据标签读出操作数的值,然后将值送往功能单元。这意味着在选择和执行之间需要插入一个PRF读取流水线级。

非数据捕捉结构的完整数据路径可以概括为以下步骤(假设推测性唤醒和旁路网络存在):

  1. 分发(周期TT):指令II的源标签SrcTag1\texttt{SrcTag1}SrcTag2\texttt{SrcTag2}被写入IQ表项。同时检查这些标签对应的物理寄存器是否已被写入——如果是,设置对应的就绪位。

  2. 唤醒(周期T+kT{+}k):某个CDB端口广播标签Tagw\texttt{Tag}_w。IQ中所有表项并行检查:如果SrcTag1i=Tagw\texttt{SrcTag1}_i = \texttt{Tag}_w,设SrcReady1i=1\texttt{SrcReady1}_i = 1;如果SrcTag2i=Tagw\texttt{SrcTag2}_i = \texttt{Tag}_w,设SrcReady2i=1\texttt{SrcReady2}_i = 1

  3. 选择(周期T+kT{+}kT+k+1T{+}k{+}1):选择逻辑扫描所有SrcReady1=1\texttt{SrcReady1}=1SrcReady2=1\texttt{SrcReady2}=1的表项,根据年龄/优先级选出最多PP条指令。

  4. 读PRF(周期T+k+1T{+}k{+}1T+k+2T{+}k{+}2):被选中指令的SrcTag1\texttt{SrcTag1}SrcTag2\texttt{SrcTag2}被送往PRF。PRF根据标签输出对应的64位数据值。同时,旁路网络检查是否有正在写回的指令产生了相同标签的结果——如果有,优先使用旁路数据。

  5. 执行(周期T+k+2T{+}k{+}2T+k+3T{+}k{+}3):操作数值(来自PRF或旁路网络)被送入功能单元的输入锁存器,执行开始。

这个流程中最关键的决策是:PRF读取和旁路选择是否可以在同一个周期内完成?在现代高频处理器中,PRF读取(SRAM访问)的延迟约为6\sim8个FO4,旁路MUX的选择延迟约为3\sim4个FO4。由于旁路选择发生在PRF读取之后(需要比较标签来决定使用PRF数据还是旁路数据),总延迟约为max(8,4)+2=10\max(8, 4) + 2 = 10个FO4(取较长路径+最终MUX选择)。这约占时钟周期的55\sim60%,余量较紧但可行。

非数据捕捉结构是现代高性能处理器中更为普遍的选择。其原因在于:

  • 发射队列面积小:不存储数据值,每个表项的位宽大幅减少。对于64位处理器,每个表项节省了128位(两个64位操作数)的存储空间。如果发射队列有96项,仅数据域就节省了 96×128=12,28896 \times 128 = 12{,}288 位的SRAM面积。

  • CDB布线简化:CDB上只需传输标签(6\sim8位),布线负载远小于传输完整数据值(64位)。

  • 功耗降低:唤醒时不需要捕捉64位数据到表项中,减少了写入功耗。

  • 扩展性好:由于表项位宽小,增加发射队列容量的面积代价较低。

PRF读取的延迟与端口压力

非数据捕捉结构的主要代价是:

  • 增加PRF读取延迟:发射后需要一个额外的流水线级来读取PRF,增加了发射到执行之间的延迟。在依赖链较长的代码中,这个额外的延迟每一步都会累积。

  • PRF端口压力:PRF需要足够多的读端口来支持多条指令同时读取操作数。对于一个8-wide的处理器,最多每周期发射8条指令,每条需要读取2个操作数,则PRF需要16个读端口——加上写端口,PRF的端口数可能达到20\sim24个。多端口寄存器文件的面积与端口数的平方成正比,这本身就是一个严峻的设计挑战。

  • 旁路网络复杂化:为了避免PRF读取延迟影响背靠背执行,处理器必须提供旁路网络——刚执行完成的指令的结果直接被前递(forward)到下一条指令的输入端,绕过PRF读取。旁路网络的复杂度与执行端口数的平方成正比。

PRF的组织与分bank策略

非数据捕捉结构将操作数值的存储从IQ转移到PRF。PRF的设计因此成为发射队列性能的关键瓶颈之一。

一个简单的单体PRF(monolithic PRF)将所有物理寄存器存储在一个统一的SRAM阵列中,每个寄存器一行,每个端口对应一组位线。如前所述,面积正比于R×P2R \times P^2RR为寄存器数,PP为端口数)。对于R=256R=256P=24P=24(16读+8写),单体PRF的面积在7nm工艺中约为0.010.020.01\sim0.02 mm2^2——面积本身不大,但端口数限制了SRAM单元的密度和访问速度。

分bank PRFRR个寄存器分为BB个bank,每个bank有R/BR/B个寄存器和P/BP/B个端口(假设均匀分配)。Bank面积正比于(R/B)×(P/B)2=R×P2/B3(R/B) \times (P/B)^2 = R \times P^2 / B^3,总面积正比于B×R×P2/B3=R×P2/B2B \times R \times P^2 / B^3 = R \times P^2 / B^2——面积随bank数的平方减小。

对于B=4B=4个bank,PRF面积降为单体方案的1/161/16。代价是bank冲突:如果同一周期内有多个读或写请求访问同一个bank,则部分请求必须被延迟。Bank冲突的概率取决于操作数标签在各bank之间的分布——如果标签是随机分布的,kk个请求全部落在同一bank的概率为(1/B)k1(1/B)^{k-1}。对于B=4B=4k=2k=2(两个请求冲突),概率为25%。但在实际工作负载中,由于寄存器重命名的随机化效果(重命名将体系结构寄存器映射到物理寄存器,物理寄存器的分配具有伪随机性),bank冲突率通常低于5%。

Bank的划分方式通常基于物理寄存器编号的低位——例如,B=4B=4个bank,编号为ii的物理寄存器位于bank imod4i \bmod 4中。这种简单的取模映射在重命名分配器产生的物理寄存器编号分布下,能够实现接近均匀的bank分布。

旁路网络的详细设计

旁路网络的设计与发射队列的选择逻辑紧密耦合。旁路的时序要求是:功能单元在周期TT产生的结果,必须在周期T+1T{+}1可以被正在读PRF的指令获取——这意味着旁路MUX必须在PRF读出数据的同时完成选择。

旁路网络的具体实现通常分为两级:

  1. 标签比较级:将每个读出端口的源标签与所有执行端口的目标标签进行比较。如果匹配,表示该执行端口正在产生所需的操作数——应使用旁路数据而非PRF数据。这一级需要Pread×2×PwriteP_{\text{read}} \times 2 \times P_{\text{write}}个标签比较器(每个读端口的2个操作数与每个写端口比较)。对于Pread=8P_{\text{read}}=8Pwrite=8P_{\text{write}}=8,需要8×2×8=1288 \times 2 \times 8 = 128个比较器——规模与IQ的唤醒CAM相当。

  2. 数据选择级:根据标签比较的结果,用MUX在PRF数据和各旁路数据之间选择。每个操作数需要一个(Pwrite+1)(P_{\text{write}}+1)-to-1的MUX(PwriteP_{\text{write}}个旁路来源+1个PRF来源),64位宽。

旁路网络的面积和功耗通常与IQ的唤醒逻辑相当——这是非数据捕捉结构需要付出的"隐形代价"。在评估数据捕捉vs非数据捕捉的面积权衡时,必须将旁路网络的面积计入非数据捕捉方案的总成本中。

PRF端口压力的具体数字值得展开分析。考虑一个12-wide的处理器(如Intel Golden Cove),最多每周期发射12条指令到12个端口。如果每条指令最多读取2个源操作数,理论上需要12×2=2412 \times 2 = 24个PRF读端口。加上12个写端口(每条指令最多写回1个结果),PRF总共需要36个端口。

一个RR-entry、PP-port的寄存器文件,其面积大致正比于R×P2R \times P^2(因为每个端口需要独立的读/写位线,位线间的间距导致面积随端口数平方增长)。对于R=256R=256个物理寄存器、P=36P=36个端口,面积将是一个24端口PRF(P=24P=24)的(36/24)2=2.25(36/24)^2 = 2.25倍。

为了缓解这一问题,现代处理器通常采用PRF分bank(banking)技术:将PRF分为多个bank,每个bank具有较少的端口数。只要同一周期内对同一bank的访问不超过该bank的端口数,分bank方案就不会降低性能。在典型工作负载中,bank冲突率低于5%,所以分bank是一种非常有效的PRF端口压力缓解手段。

非数据捕捉结构中的旁路网络

旁路网络(bypass/forwarding network)是非数据捕捉结构中实现背靠背执行的关键。其基本工作原理是:当一条指令在周期TT完成执行时,其结果在写入PRF的同时,通过旁路网络直接前递到在周期T+1T{+}1开始执行的后续指令的输入端。这样,后续指令无需等待结果写入PRF再读出,而是直接从旁路网络获取操作数。

旁路网络的硬件实现是一个Pwrite×PreadP_{\text{write}} \times P_{\text{read}}的多路选择器矩阵,其中PwriteP_{\text{write}}为产生结果的执行端口数,PreadP_{\text{read}}为消耗操作数的执行端口数。每个MUX是(Pwrite+1)(P_{\text{write}} + 1)-to-1的(PwriteP_{\text{write}}个旁路输入 + 1个PRF读取输入),宽度为64位。

对于Pwrite=Pread=8P_{\text{write}} = P_{\text{read}} = 8的处理器:

旁路MUX数量=Pread×2=16\text{旁路MUX数量} = P_{\text{read}} \times 2 = 16 \text{个}

(每个读端口的每个操作数需要一个MUX)

每个MUX为9-to-1、64位宽。总的MUX面积正比于16×9×64=9,21616 \times 9 \times 64 = 9{,}216个选择器位。旁路网络的面积虽然不小,但远小于一个等效的数据捕捉IQ的数据存储面积。这也是为什么"非数据捕捉+旁路网络"在总面积上仍然优于数据捕捉方案。

硬件描述 3 — 发射队列表项的位宽对比

假设一个64位处理器,物理寄存器标签为7位,发射队列的每个表项包含以下字段:

字段数据捕捉(位)非数据捕捉(位)
有效位(Valid)11
已发射位(Issued)11
操作码(OpCode)88
功能单元类型(FUType)33
源1标签 + 就绪位88
源1数据640
源2标签 + 就绪位88
源2数据640
目标标签77
立即数/其他3232
ROB编号/年龄88
分支掩码88
总计\sim212位\sim84位

非数据捕捉结构的表项位宽仅为数据捕捉结构的约40%。对于一个96项的发射队列:

  • 数据捕捉:96×212=20,35296 \times 212 = 20{,}352\approx 2.5 KB

  • 非数据捕捉:96×84=8,06496 \times 84 = 8{,}064\approx 1.0 KB

面积差异约为2.5倍。考虑到发射队列中CAM比较器的面积(与标签位宽成正比,两种结构相同),总面积差异约为2\sim2.5倍。但如果考虑数据捕捉结构中CDB数据写入网络的额外面积(约40万个晶体管,如前所述),总面积差异可达3倍以上。

混合结构

在数据捕捉和非数据捕捉之间存在一种混合结构(hybrid structure):发射队列只为一个源操作数提供数据捕捉能力,另一个源操作数的值仍然从PRF读取。这种设计在面积开销和性能之间取得了一个有趣的折中。

混合结构的设计动机

混合结构的设计动机来自对指令操作数就绪模式的观察:

  • 在典型的工作负载中,指令进入发射队列时,其两个源操作数的就绪状态是不对称的:通常有一个操作数已经就绪(来自较早的指令),另一个操作数尚未就绪(来自刚刚分发的先行指令)。

  • 尚未就绪的操作数(即"晚到的"操作数)通常是导致指令等待的原因。当这个操作数通过CDB被广播时,如果能直接在发射队列中捕捉其值,就可以在下一个周期立即发射该指令而无需访问PRF——这对背靠背执行的依赖链特别有利。

  • 已经就绪的操作数可以从PRF读取,因为它已经存在于PRF中,读取延迟不在关键路径上。

捕捉哪个操作数

一个关键的设计问题是:当两个源操作数都未就绪时,应该捕捉哪一个?混合结构的发射队列表项只有一个数据域(64位),只能捕捉一个操作数的值。几种策略如下:

  1. 固定选择第二个源操作数:统计发现,在许多ISA中,第二个源操作数(如R-type指令的rs2)更频繁地出现在关键依赖链上。这种策略实现最简单——没有额外的控制逻辑。

  2. 选择后到达的操作数:动态地确定哪个操作数最晚到达,捕捉那个。这需要在分发时预测哪个操作数更晚就绪(通过检查其生产者指令在IQ中的位置或功能单元延迟)。

  3. 选择关键路径上的操作数:如果处理器有关键路径预测器(criticality predictor),可以选择处于关键路径上的操作数进行捕捉。这提供最好的性能但硬件开销最大。

研究表明,简单的"固定选择第二个源操作数"策略就能获得混合结构大部分的性能收益——相比纯非数据捕捉结构,IPC提升约2\sim4%。这是因为在大多数代码中,第二个源操作数与第一个源操作数在依赖链上的位置差异不大。

混合结构的发射队列表项包含一个数据域(例如64位,用于捕捉一个操作数的值)和两个标签域。在分发时:

  1. 如果两个操作数都已就绪:不需要数据捕捉,两个值都在发射时从PRF读取。

  2. 如果一个已就绪、一个未就绪:未就绪操作数的标签被标记为"需要捕捉"。当CDB广播该标签时,值被捕捉到表项的数据域中。

  3. 如果两个都未就绪:选择依赖链更短的那个(或简单地选择第二个源操作数)进行数据捕捉,另一个在发射时从PRF读取。

设计提示

混合结构的关键洞察是:真正需要数据捕捉的是关键路径上"最后到达"的操作数。这个操作数通常决定了指令何时能被发射。如果它通过CDB到达后能立即被使用(无需再从PRF读取),就可以在下一个周期直接发射到功能单元,实现无气泡的背靠背执行。对于另一个"先到达"的操作数,多花一个周期从PRF读取是可以接受的,因为它不在关键路径上。

混合结构的位宽与面积

混合结构每个表项的位宽约为 84+64=14884 + 64 = 148 位——介于纯非数据捕捉(84位)和纯数据捕捉(212位)之间。对于96项的发射队列,混合结构的数据存储量约为 96×64=6,14496 \times 64 = 6{,}144\approx 0.75 KB,仅为纯数据捕捉的一半。

CDB写入网络的复杂度也减半:只需要为每个表项的一个数据域提供条件写入端口,传输门数量从约40万个减少到约20万个。CDB的数据总线仍然需要64位宽度(因为捕捉操作需要数据值),但写入扇出(fan-out)减半。

混合结构的发射流水线

混合结构的发射流水线需要区分两种操作数的获取路径:

  1. 被捕捉的操作数:其值已在IQ表项的数据域中,发射时直接读出——无需访问PRF,延迟为0(相对于选择)。

  2. 未被捕捉的操作数:需要通过标签访问PRF,延迟为1个周期。

这意味着混合结构的功能单元输入端需要一个MUX,在两个来源(IQ数据域 vs PRF读出)之间选择。这个MUX的延迟约为1个FO4,可以忽略不计。

更关键的问题是:被捕捉的操作数和PRF读出的操作数到达功能单元输入端的时间可能不同——捕捉数据在选择后立即可用,PRF数据在选择后1周期可用。如果功能单元必须等待两个操作数都就绪才能开始执行,那么混合结构的有效延迟取决于较慢的路径——即PRF读取路径。

一种解决方案是提前一个周期发送PRF读地址。在选择逻辑做出仲裁的同时(或之前),将未被捕捉操作数的标签提前送往PRF开始读取。这样,当选择结果确定后,PRF的读数据也同时到达——两个操作数同时就绪。这需要选择逻辑在做出最终仲裁之前就开始"推测性地"向PRF发送读请求,如果最终选择的指令与推测的不同,则PRF的读操作白白浪费(但不影响正确性)。

在实际统计中,混合结构相比纯非数据捕捉结构的IPC提升约为2\sim4%,面积增加约40%(主要来自每个表项额外的64位数据域)。这个收益-代价比对于面积敏感的移动处理器来说可能不够吸引人,但在功耗和延迟敏感的高性能处理器中可能是值得的——特别是在关键依赖链较长的应用(如数据库事务处理)中。

表 27.5总结了三种结构的对比。

指标数据捕捉非数据捕捉混合
表项位宽(位)\sim212\sim84\sim148
IQ数据存储(KB)\sim2.5\sim1.0\sim1.75
CDB数据宽度标签+数据仅标签标签+数据
PRF读取级不需要需要部分需要
背靠背执行容易实现需要旁路关键路径可实现
PRF读端口需求
数据写入晶体管数\sim400K0\sim200K

数据捕捉、非数据捕捉与混合结构的对比(基于96项发射队列)

压缩VS非压缩

发射队列设计的第三个关键维度是压缩(compacting)与非压缩(non-compacting)的选择。这一维度关注的问题是:当指令被发射后,其在发射队列中占据的表项被释放——这个空表项应该如何处理?

压缩结构

在压缩结构中,当一条指令被发射并释放其表项后,发射队列中位于该表项之上(即比它更年轻的)的所有指令都向下移动一位,填补空出的位置。这个操作类似于数组中删除一个元素后将后续元素前移。

压缩结构:指令C发射后,D、E、F向上移动填补空位,空闲表项集中在尾部
压缩结构:指令C发射后,D、E、F向上移动填补空位,空闲表项集中在尾部

压缩结构有以下核心特性:

  1. 年龄顺序自动维持:由于指令总是按顺序从分发端插入、发射后空位被压缩,表项的位置隐式地表示了指令的年龄——位于队列头部(低编号位置)的指令最老,尾部的最年轻。选择逻辑只需按照固定位置优先级(低编号优先)即可实现最老优先策略,无需额外的年龄跟踪硬件。

  2. 空闲表项集中:所有空闲表项总是聚集在队列尾部,形成连续的空间。新指令总是插入到尾部的第一个空位,分发逻辑只需维护一个尾指针即可。

  3. 写入端口简化:新指令只写入队列尾部的固定位置,不需要复杂的空位查找逻辑。

压缩结构的选择逻辑

压缩结构的选择逻辑非常简单。由于表项位置直接反映年龄,选择"年龄最老的就绪指令"只需要一个固定优先级的扫描:从表项0开始向高编号扫描,第一个Valid=1SrcReady1=1SrcReady2=1Issued=0的表项就是要选择的指令。

这个扫描可以用一个简单的NN位AND-OR链实现:

Grant[i]=Ready[i]AND¬Grant[0]AND¬Grant[1]ANDAND¬Grant[i1]\texttt{Grant}[i] = \texttt{Ready}[i] \mathop{\texttt{AND}} \neg\texttt{Grant}[0] \mathop{\texttt{AND}} \neg\texttt{Grant}[1] \mathop{\texttt{AND}} \cdots \mathop{\texttt{AND}} \neg\texttt{Grant}[i{-}1]

即表项ii被选中当且仅当它是就绪的,且所有比它更老的表项都没有被选中(要么不就绪,要么已被更高优先级的端口选中)。这本质上就是一个固定优先级编码器。

对于PP-wide发射(每周期选择PP条指令),需要PP个级联的优先级编码器,第kk个编码器将第k1k{-}1个编码器选中的位屏蔽后再扫描。延迟为O(P×N)O(P \times N)个门延迟——但由于PP通常较小(2\sim4),且可以用前缀逻辑优化到O(P+log2N)O(P + \log_2 N),这在小规模IQ(N32N \leq 32)中是完全可行的。

压缩结构最早被广泛使用的处理器之一是MIPS R10000(1996年),它采用16项压缩发射队列。Alpha 21264(1998年)也使用了类似的压缩结构。在这些早期的乱序处理器中,发射队列的容量较小(16\sim20项),压缩操作的硬件开销尚可接受。

以下代码展示了压缩发射队列的条目移动逻辑。当一条指令被发射后,所有比它更年轻的条目向低地址方向移动一位,填补空出的位置。

verilog
module iq_compact #(
    parameter DEPTH    = 16,
    parameter ENTRY_W  = 84  // tag+ctrl bits per entry
)(
    input  logic                clk, rst_n,
    input  logic [DEPTH-1:0]   issued,     // one-hot: which entry issued
    input  logic [ENTRY_W-1:0] entries_in [DEPTH],
    input  logic [DEPTH-1:0]   valid_in,
    output logic [ENTRY_W-1:0] entries_out[DEPTH],
    output logic [DEPTH-1:0]   valid_out
);
    // Compute shift mask: entry i shifts down if any
    // entry j < i was issued (i.e., has a gap below)
    logic [DEPTH-1:0] shift;

    always_comb begin
        shift[0] = 1'b0;  // entry 0 never shifts
        for (int i = 1; i < DEPTH; i++)
            shift[i] = shift[i-1] | issued[i-1];
    end

    // Perform the compaction
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            valid_out <= '0;
        end else begin
            for (int i = 0; i < DEPTH; i++) begin
                if (issued[i]) begin
                    // This entry was issued — will be overwritten
                    // by the entry above (or invalidated if tail)
                end
                if (shift[i] && !issued[i]) begin
                    // Shift this entry down by one position
                    entries_out[i-1] <= entries_in[i];
                    valid_out[i-1]   <= valid_in[i];
                end else if (!shift[i] && !issued[i]) begin
                    entries_out[i] <= entries_in[i];
                    valid_out[i]   <= valid_in[i];
                end
            end
            // Invalidate vacated tail entries
            for (int i = 0; i < DEPTH; i++)
                if (shift[i] && (i == DEPTH-1
                    || !valid_in[i+1]))
                    valid_out[i] <= 1'b0;
        end
    end
endmodule

上述实现中,shift信号是一个前缀OR链——一旦某个位置的条目被发射(issued为1),所有更高编号的条目都需要向下移动。这个移位网络的关键路径为O(N)O(N)个MUX延迟:对于N=16N=16,约8\sim10级逻辑门(约4\sim5 FO4),在200 ps时钟周期下是可行的。但当N>32N>32时,移位延迟开始成为瓶颈,这也是现代大容量发射队列普遍采用非压缩结构的根本原因。

非压缩结构

在非压缩结构中,指令被发射后,其占据的表项被标记为空闲不进行移位。空闲表项分散在队列的各个位置,新指令可以被分配到任意空闲位置。

非压缩结构:指令C发射后其位置变为空闲,D、E、F不移动
非压缩结构:指令C发射后其位置变为空闲,D、E、F不移动

非压缩结构的核心特性:

  1. 无移位操作:指令发射后其他表项保持不动,避免了压缩结构中复杂的数据移位。

  2. 空闲表项管理:空闲表项散布在队列中,需要一个空闲列表(free list)或空闲位向量(free bit vector)来跟踪哪些表项可用。分发新指令时,从空闲列表中取出一个空闲位置号,将指令写入该位置。

  3. 年龄跟踪:由于表项位置不再反映指令年龄,如果选择逻辑需要实现最老优先策略,就必须使用额外的年龄矩阵(age matrix)来记录指令间的相对年龄。

空闲表项管理

空闲表项的管理可以通过两种方式实现:

空闲位向量(Free Bit Vector):一个NN位的向量,第ii位为1表示表项ii空闲。分发时,通过优先级编码器从向量中找到第一个(或前DD个,DD为分发宽度)为1的位,分配给新指令。发射后将对应位置1。

优先级编码器的延迟为O(log2N)O(\log_2 N)。对于DD-wide分发,需要DD个级联的编码器,总延迟为O(D+log2N)O(D + \log_2 N)。这与压缩结构的简单尾指针(O(1)O(1))相比是一个额外的开销。

空闲列表(Free List):一个FIFO队列,存储所有空闲表项的编号。分发时从FIFO头部取出空闲编号,发射后将释放的编号写入FIFO尾部。FIFO的读写延迟为O(1)O(1),比空闲位向量更快。但FIFO需要N×log2NN \times \lceil\log_2 N\rceil位的存储(NN个编号,每个log2N\lceil\log_2 N\rceil位),对于N=96N=9696×7=67296 \times 7 = 672位的额外存储。

年龄矩阵

年龄矩阵是非压缩发射队列中实现最老优先选择策略的标准硬件机制。对于一个NN项的发射队列,年龄矩阵是一个N×NN \times N的位矩阵MM,其中 M[i][j]=1M[i][j] = 1 表示表项ii中的指令比表项jj中的指令更老。当需要从NN个就绪指令中选出最老的一条时,选择逻辑找到行ii使得 M[i][j]=1M[i][j] = 1 对所有有效且就绪的jjjij \neq i)成立——即指令ii比所有其他就绪指令都老。

年龄矩阵的更新操作

年龄矩阵的更新发生在指令分发时:当一条新指令被分发到表项kk时,需要执行以下操作:

  1. 将第kk全部清零:M[k][j]=0M[k][j] = 0,对所有jj。这表示新指令(刚到达)不比任何现有指令老。

  2. 将第kk全部置1:M[i][k]=1M[i][k] = 1,对所有有效的ii。这表示所有现有指令都比新指令老。

  3. 对角线元素不关心:M[k][k]M[k][k]可设为0或1,不影响逻辑正确性。

下面通过一个具体的4项年龄矩阵例子来展示这个过程。

案例研究 1 — 4\times4年龄矩阵的工作过程

假设一个4项的发射队列,初始状态为空。我们依次分发指令A、B、C、D到表项0、1、2、3。

第1步:分发指令A到表项0。矩阵状态(仅表项0有效):

M=(000000000000)(只有A有效,其他表项空闲)M = \begin{pmatrix} \text{--} & 0 & 0 & 0 \\ 0 & \text{--} & 0 & 0 \\ 0 & 0 & \text{--} & 0 \\ 0 & 0 & 0 & \text{--} \end{pmatrix} \quad \text{(只有A有效,其他表项空闲)}

第2步:分发指令B到表项1。将列1中所有有效行置1,行1全清零:

M=(100000000000)M[0][1]=1A比B老M = \begin{pmatrix} \text{--} & \mathbf{1} & 0 & 0 \\ \mathbf{0} & \text{--} & 0 & 0 \\ 0 & 0 & \text{--} & 0 \\ 0 & 0 & 0 & \text{--} \end{pmatrix} \quad M[0][1]=1 \Rightarrow \text{A比B老}

第3步:分发指令C到表项2:

M=(110010000000)A比B、C老;B比C老M = \begin{pmatrix} \text{--} & 1 & \mathbf{1} & 0 \\ 0 & \text{--} & \mathbf{1} & 0 \\ \mathbf{0} & \mathbf{0} & \text{--} & 0 \\ 0 & 0 & 0 & \text{--} \end{pmatrix} \quad \text{A比B、C老;B比C老}

第4步:分发指令D到表项3:

M=(111011001000)年龄顺序:A > B > C > DM = \begin{pmatrix} \text{--} & 1 & 1 & \mathbf{1} \\ 0 & \text{--} & 1 & \mathbf{1} \\ 0 & 0 & \text{--} & \mathbf{1} \\ \mathbf{0} & \mathbf{0} & \mathbf{0} & \text{--} \end{pmatrix} \quad \text{年龄顺序:A > B > C > D}

第5步:现在假设指令B(表项1)被发射并释放。表项1变为空闲。然后新指令E被分发到表项1(因为表项1现在空闲了):

  • 行1全清零:M[1][j]=0M[1][j] = 0

  • 列1中所有有效行置1:M[0][1]=1M[0][1]=1, M[2][1]=1M[2][1]=1, M[3][1]=1M[3][1]=1

M=(111000011010)年龄顺序:A > C > D > EM = \begin{pmatrix} \text{--} & \mathbf{1} & 1 & 1 \\ \mathbf{0} & \text{--} & \mathbf{0} & \mathbf{0} \\ 0 & \mathbf{1} & \text{--} & 1 \\ 0 & \mathbf{1} & 0 & \text{--} \end{pmatrix} \quad \text{年龄顺序:A > C > D > E}

注意E在表项1中(与之前的B相同的位置),但年龄矩阵正确地记录了E是最年轻的指令。

选择最老的就绪指令:假设A(表项0)和D(表项3)就绪。检查MM

  • A(行0):M[0][3]=1M[0][3] = 1,A比D老。\checkmark

  • D(行3):M[3][0]=0M[3][0] = 0,D不比A老。×\times

选择A。

年龄矩阵的硬件实现

年龄矩阵的每个位M[i][j]M[i][j]需要以下硬件:

  • 一个D触发器(或SR锁存器):存储M[i][j]M[i][j]的当前值。约6个晶体管。

  • 一个置位输入:当新指令被分发到表项jj时,需要将M[i][j]M[i][j]置1(如果表项ii当前有效)。置位逻辑为Setij=DispatchjANDValidi\texttt{Set}_{ij} = \texttt{Dispatch}_j \mathop{\texttt{AND}} \texttt{Valid}_i,需要1个AND门(约4个晶体管)。

  • 一个清零输入:当新指令被分发到表项ii时,需要将M[i][j]M[i][j]清零。清零逻辑为Resetij=Dispatchi\texttt{Reset}_{ij} = \texttt{Dispatch}_i,直接使用分发信号即可。

因此,年龄矩阵中每个位的面积约为10个晶体管。对于N=96N=96的矩阵,总晶体管数约为962×10=92,1609296^2 \times 10 = 92{,}160 \approx 92K——这是一个不小的数字,约占整个IQ总晶体管数的34%(在267K的总预算中)。

年龄矩阵的选择逻辑在矩阵之上叠加一层AND-OR网络:

Oldest[i]=Ready[i]ANDji(¬Valid[j]OR¬Ready[j]ORM[i][j])\texttt{Oldest}[i] = \texttt{Ready}[i] \mathop{\texttt{AND}} \bigwedge_{j \neq i} \big( \neg\texttt{Valid}[j] \mathop{\texttt{OR}} \neg\texttt{Ready}[j] \mathop{\texttt{OR}} M[i][j] \big)

即表项ii是最老的就绪指令,当且仅当:(a)ii本身就绪;且(b)对于所有其他有效且就绪的表项jjM[i][j]=1M[i][j]=1iijj老)。无效或未就绪的表项被忽略(通过¬Valid[j]OR¬Ready[j]\neg\texttt{Valid}[j] \mathop{\texttt{OR}} \neg\texttt{Ready}[j]项屏蔽)。

每个Oldest[i]\texttt{Oldest}[i]信号需要一个(N1)(N{-}1)输入的AND门。对于N=96N=96,这是一个95输入的AND——可以用多级NAND/NOR树实现,深度为log495=4\lceil\log_4 95\rceil = 4级(每级4输入),延迟约4个FO4。

年龄矩阵的面积与延迟分析

年龄矩阵的面积为 N2N^2 位(实际只需要N(N1)/2N(N{-}1)/2位,因为M[i][j]=¬M[j][i]M[i][j] = \neg M[j][i],矩阵是反对称的;但出于实现简便,通常使用完整的N2N^2位)。

对于不同规模的发射队列,年龄矩阵的面积如下:

IQ规模NN年龄矩阵(位)字节占IQ存储的比例
1625632 B4.7%(非数据捕捉BB=84位)
321,024128 B9.5%
644,096512 B19.0%
969,2161,152 B28.5%
16025,6003,200 B47.8%

可以看到,年龄矩阵的面积随N2N^2增长,在N=96N=96时已经占到IQ总存储(96×84=8,06496 \times 84 = 8{,}064位)的9,216/8,064114%9{,}216 / 8{,}064 \approx 114\%——年龄矩阵本身已经比IQ的数据存储更大了。在N=160N=160时,年龄矩阵的面积更是IQ数据存储的约1.9倍。

然而,年龄矩阵的每个存储位只是一个简单的触发器(flip-flop),不需要CAM比较器。触发器的面积远小于CAM单元(约为CAM单元的1/31/41/3\sim1/4)。因此,虽然年龄矩阵的位数可能超过IQ的数据存储,但其面积仍然远小于CAM——在大规模IQ中,年龄矩阵的面积通常占整个IQ总面积的10\sim20%。

年龄矩阵选择最老就绪指令的延迟由以下路径决定:

  1. 对于每个就绪表项ii,将MMii行中所有对应于其他就绪表项jj的位取AND:Oldest[i]=jReady,jiM[i][j]\texttt{Oldest}[i] = \bigwedge_{j \in \text{Ready}, j \neq i} M[i][j]。这是一个最多NN输入的AND操作,延迟为O(log2N)O(\log_2 N)

  2. 但并非所有NN个表项都参与——只有就绪的表项需要比较。实际的AND输入数等于就绪指令数RR,平均情况下RNR \ll N

  3. 对于N=96N=96,最坏情况的AND链深度为log296=7\lceil\log_2 96\rceil = 7级,每级约1个FO4延迟,总延迟约7个FO4(\sim70\sim84 ps)。

这个延迟远短于压缩结构中移位网络的延迟(将在下一节分析),也短于级联优先级编码器的延迟。因此,年龄矩阵是实现最老优先选择的一种延迟友好的方案。

年龄矩阵的功耗优化

年龄矩阵的功耗主要来自两个方面:(1)分发时的更新——每次分发到表项kk时,需要更新MM的第kk行(N1N{-}1位清零)和第kk列(最多N1N{-}1位置1);(2)选择时的查询——每个周期读取所有就绪表项的行。

更新功耗可以通过门控写入(gated write)来降低:只在表项kk确实被分发时才激活第kk行和第kk列的写信号。由于每周期分发的指令数DD远小于NN,大部分行和列的写入被门控关闭。

查询功耗可以通过两阶段查询来降低:

  1. 第一阶段:快速扫描Ready\texttt{Ready}向量,找到所有就绪表项的集合SreadyS_{\text{ready}}。由于大多数表项通常不就绪(统计上,平均只有10\sim20%的表项处于就绪状态),Sready|S_{\text{ready}}|通常远小于NN

  2. 第二阶段:只读取SreadyS_{\text{ready}}中表项对应的行,而不是全部NN行。这可以通过行选择信号(由Ready\texttt{Ready}向量驱动)来实现,将未就绪行的位线保持在预充电状态——它们不翻转,因此不消耗动态功耗。

这种优化可以将查询功耗降低到全矩阵查询的Sready/N15%|S_{\text{ready}}|/N \approx 15\%——功耗降低约6\sim7倍。

半矩阵优化

由于年龄矩阵是反对称的——M[i][j]=1M[i][j] = 1当且仅当M[j][i]=0M[j][i] = 0(假设iji \neq j且两个表项都有效)——实际上只需要存储上三角或下三角部分。存储量从N2N^2减半为N(N1)/2N(N{-}1)/2

对于N=96N=96:完整矩阵9,216位 \to 半矩阵96×95/2=4,56096 \times 95/2 = 4{,}560位。面积节省约50%。

半矩阵的实现需要在查询时根据(i,j)(i, j)的相对位置决定读取的是原始值还是取反值:

Meffective[i][j]={Mstored[i][j]if i<j¬Mstored[j][i]if i>j0if i=jM_{\text{effective}}[i][j] = \begin{cases} M_{\text{stored}}[i][j] & \text{if } i < j \\ \neg M_{\text{stored}}[j][i] & \text{if } i > j \\ 0 & \text{if } i = j \end{cases}

这需要在选择逻辑中为下三角部分的读出值添加一个反相器。反相器的延迟约0.5个FO4,可以忽略不计。

非压缩结构在现代处理器中更为普遍,主要原因是:

  • 现代发射队列的容量较大(64\sim160项),压缩操作的硬件开销已变得不可接受(见27.4.3 节)。

  • 年龄矩阵的面积和功耗开销在这个规模下是可以承受的。

  • 非压缩结构在物理设计上更为友好——不需要大规模移位网络的布线。

压缩的硬件开销

压缩操作的硬件实现本质上是一个大规模的移位网络(shift network)。当某个表项kk被释放时,表项k+1k+1的内容移到kk、表项k+2k+2的内容移到k+1k+1,依此类推——直到队列尾部。如果同一个周期有多个表项被释放(多条指令同时发射),情况更为复杂:后续表项的移位距离取决于在它们之前有多少个空位被释放。

单发射的压缩

最简单的情况:每个周期最多发射一条指令。释放一个表项后,所有在其之后的表项各移动一位。这需要一个NN级的多路选择器链——每一级的选择器决定该表项是否需要从上方的邻居接收数据。硬件实现为NN个2-to-1多路选择器,每个选择器的宽度等于表项的位宽BB。总的选择器面积与 N×BN \times B 成正比。

从延迟的角度看,移位操作需要确定"从哪里开始移"——即被释放表项的位置。一旦确定位置,移位本身可以并行进行(所有在该位置之后的MUX同时选择"从上方接收")。确定位置的延迟为一次优先级编码(O(log2N)O(\log_2 N)),MUX的选择延迟约为1个FO4。因此单发射压缩的总延迟约为O(log2N+1)O(\log_2 N + 1)个FO4。

多发射的压缩

如果每个周期最多发射PP条指令,则每个表项最多需要移动PP位。每个位置需要一个(P+1)(P+1)-to-1的多路选择器(不移、移1位、移2位、\ldots、移PP位),且需要一个前缀计数器(prefix counter)网络来计算每个位置之前有多少个空位——这决定了该位置应移动多少位。

前缀计数器网络的基本思想如下:对于NN个表项位置,定义Freed[i]=1\texttt{Freed}[i] = 1如果表项ii在本周期被释放,否则为0。则表项ii应移动的距离为:

Shift[i]=j=0i1Freed[j]\texttt{Shift}[i] = \sum_{j=0}^{i-1} \texttt{Freed}[j]

这是一个前缀和(prefix sum)计算。前缀和可以用树形电路在O(log2N)O(\log_2 N)级中完成(类似于进位前瞻加法器)。每级需要加法/计数逻辑,每个计数器log2(P+1)\lceil\log_2(P{+}1)\rceil位宽。

前缀计数器网络的延迟为 O(log2N)O(\log_2 N)级,每级约2个FO4(加法器延迟),总延迟约2log2N2\log_2 N个FO4。对于N=96N=96,约14个FO4——在5 GHz的处理器中占了时钟周期的大部分。

前缀计数器的输出驱动(P+1)(P+1)-to-1的MUX。MUX的延迟约log2(P+1)\lceil\log_2(P{+}1)\rceil个FO4。对于P=4P=4,MUX延迟约3个FO4。

总延迟约14+3=1714 + 3 = 17个FO4——接近5 GHz下一个完整的时钟周期。这意味着多发射压缩操作本身就需要一个完整的流水线级,严重限制了压缩结构在高频处理器中的可行性。

压缩移位网络的硬件实现:每个表项位置需要一个MUX来选择是保持原值还是接收上方的值
压缩移位网络的硬件实现:每个表项位置需要一个MUX来选择是保持原值还是接收上方的值

压缩移位网络的晶体管数估算

对于多发射压缩,每个MUX是(P+1)(P{+}1)-to-1的,可以用PP级2-to-1 MUX树实现(P4P \leq 4时,也可以直接用传输门MUX)。一个BB位宽的(P+1)(P{+}1)-to-1 MUX需要约B×(P+1)×4B \times (P{+}1) \times 4个晶体管(每个传输门4个晶体管,每个输入BB个传输门)。

对于N=96N=96P=4P=4B=84B=84位(非数据捕捉):

MUX晶体管数=N×B×(P+1)×4=96×84×5×4=161,280\text{MUX晶体管数} = N \times B \times (P{+}1) \times 4 = 96 \times 84 \times 5 \times 4 = 161{,}280

加上前缀计数器网络(约96×3×205,76096 \times 3 \times 20 \approx 5{,}760个晶体管),移位网络总共约17万个晶体管。

如果使用数据捕捉结构(B=212B=212位),MUX晶体管数增加到约96×212×5×4=407,04096 \times 212 \times 5 \times 4 = 407{,}040——超过40万个晶体管,仅用于移位网络。

性能分析 2 — 压缩操作的面积与功耗代价

对于一个96项的发射队列(表项位宽BB位),支持每周期最多4条指令发射的压缩结构:

  • 移位多路选择器:96×5×B96 \times 5 \times B 个选择器位,其中5为(4+1)-to-1选择器的输入数。以非数据捕捉结构B=84B = 84位为例,总计 96×5×84=40,32096 \times 5 \times 84 = 40{,}320 个选择器位。

  • 前缀计数器网络:96级前缀和计算,每级3位(最多移4位),面积约 96×3×log2962,00096 \times 3 \times \lceil\log_2 96\rceil \approx 2{,}000 个门。

  • 动态功耗:每个周期,压缩操作中被移动的表项数平均约为N/2=48N/2 = 48项(假设发射的指令均匀分布在队列中)。每次移位翻转约BB位,总翻转约 48×84=4,03248 \times 84 = 4{,}032 位/周期。

如果使用数据捕捉结构(B=212B = 212位),移位网络的面积和功耗将增加约2.5倍。这就是为什么数据捕捉+压缩的组合在大容量发射队列中特别昂贵——不仅表项本身大,连压缩移位网络也要随之变大。

压缩对唤醒逻辑的影响

压缩操作还引入了一个微妙的问题:唤醒地址失效。在非压缩结构中,每个指令在IQ中的位置是固定的,唤醒逻辑可以可靠地通过位置编号来引用表项。但在压缩结构中,指令的位置随着其他指令的发射而变化——今天在位置5的指令,明天可能在位置3。

这意味着CDB上的唤醒标签不能直接使用IQ的位置编号来寻址——必须使用物理寄存器标签进行内容匹配(CAM),与非压缩结构相同。但压缩操作可能在唤醒操作的同一周期发生——如果一条指令在周期TT被唤醒(其就绪位被置1),但同一周期的压缩操作将该指令从位置ii移到位置i1i{-}1,则选择逻辑在周期T+1T{+}1需要从位置i1i{-}1(而不是ii)找到这条就绪指令。

这种唤醒与压缩的并发要求精心设计时序,确保压缩操作在唤醒之后、选择之前完成。在小规模IQ中这是可行的,但在大规模IQ中,压缩的延迟可能与唤醒-选择的关键路径冲突。

压缩与唤醒的时序冲突

压缩操作与唤醒操作之间存在一个容易被忽视但影响深远的时序冲突。考虑以下场景:

在周期TT中,两个事件同时发生:

  1. 指令B(位于表项3)被选中发射,其表项将被释放,触发压缩操作。

  2. CDB上广播了标签X,该标签匹配表项5中指令E的源操作数1。

压缩操作将把表项4、5、6的内容分别移到位置3、4、5。唤醒操作要将表项5的SrcReady1\texttt{SrcReady1}置为1。问题是:唤醒操作应该修改压缩的表项5,还是压缩的表项4(因为指令E在压缩后移到了位置4)?

如果唤醒先于压缩执行(在时钟周期的前半段),那么唤醒修改的是位置5的就绪位。随后压缩将位置5的全部内容(包括刚刚更新的就绪位)移到位置4。结果正确。

如果压缩先于唤醒执行,那么指令E已经从位置5移到了位置4。唤醒逻辑的CAM比较仍然在位置4找到匹配(因为CAM比较的是标签值而非位置号),正确地更新位置4的就绪位。结果也正确。

如果唤醒和压缩同时执行(完全并行),则可能出现竞争条件:唤醒逻辑更新了位置5的就绪位,同时压缩逻辑将位置5的旧内容(不含更新的就绪位)移到位置4——唤醒的效果丢失了。

为了避免这种竞争,设计者通常采用以下策略之一:

  • 唤醒优先:强制唤醒在压缩之前完成。在时钟的前1/31/3完成唤醒,后2/32/3完成压缩。这要求唤醒延迟Tclk/3\leq T_{\text{clk}}/3,对高频设计是一个严格的约束。

  • 双写策略:唤醒逻辑同时更新原位置和压缩后的目标位置。这需要将CAM匹配信号与压缩偏移量结合,计算出更新目标——增加了硬件复杂度。

  • 延迟压缩:将压缩推迟到下一个周期执行。这简化了时序但意味着每次发射后有一个周期的"空洞"存在于队列中——选择逻辑需要在这个周期跳过空洞,增加了选择逻辑的复杂度。

这些时序问题是压缩结构在大规模IQ中被放弃的又一个重要原因。非压缩结构没有移位操作,因此不存在唤醒-压缩的时序冲突——唤醒逻辑只需修改固定位置的就绪位,不受任何并发操作的影响。

表 27.6总结了压缩与非压缩结构的关键对比。

维度压缩非压缩
年龄排序位置隐式表示年龄,选择逻辑简单(固定优先级)。需要额外的年龄矩阵(N2N^2位),或使用非严格的优先级策略。
空位管理空位集中在尾部,分发逻辑简单(仅需尾指针)。空位分散,需要空闲列表或空闲位向量。
移位开销需要大规模移位网络,面积N×P×B\propto N \times P \times B无移位开销。
功耗移位网络每周期消耗可观的动态功耗。无移位功耗,但年龄矩阵的查询有一定功耗。
时序移位网络和前缀计数器可能需要额外流水线级。年龄矩阵查询的延迟通常短于移位网络。
扩展性差:移位网络面积随NN线性增长,随BB线性增长。好:年龄矩阵面积随N2N^2增长,但系数小(每位仅1个触发器)。
适用规模小规模IQ(\leq32项)。中大规模IQ(\geq32项)。

压缩与非压缩发射队列的对比

压缩与非压缩结构的分发逻辑对比

压缩和非压缩结构在分发逻辑的复杂度上有显著差异,这也是影响设计选择的重要因素。

压缩结构的分发逻辑

在压缩结构中,空闲表项总是聚集在队列尾部。分发逻辑只需要维护一个尾指针(tail pointer),指向第一个空闲表项的位置。分发DD条指令时,将它们写入尾指针指向的连续DD个位置,然后将尾指针递增DD

尾指针的更新逻辑需要考虑两个并发事件:(1)本周期分发了DD条新指令(尾指针增加DD);(2)本周期发射了PP条指令并进行了压缩(尾指针减少PP,因为压缩将尾部空位前移了PP个位置)。净变化为DPD - P

TailPtrnew=TailPtrold+DP\texttt{TailPtr}_{\text{new}} = \texttt{TailPtr}_{\text{old}} + D - P

这是一个简单的加法器操作,延迟极低。而且,分发逻辑不需要搜索空闲位置——位置是确定的,减少了关键路径延迟。

但压缩结构的分发有一个微妙的时序问题:本周期的分发和压缩可能发生冲突。新指令被写入队列尾部,而压缩操作正在将已有指令向头部移动。如果分发和压缩在同一周期的不同阶段进行,需要确保新写入的数据不会被压缩操作覆盖。一种常见的解决方案是将分发安排在时钟的前半周期,压缩安排在后半周期——这样新指令先被写入固定位置,然后压缩操作不会触及刚写入的数据(它们在尾部,压缩只影响被释放位置之后的表项)。

非压缩结构的分发逻辑

非压缩结构的分发逻辑更为复杂。空闲表项散布在队列的各个位置,分发逻辑需要:

  1. 找到DD个空闲位置——通过空闲列表(FIFO)弹出DD个空闲编号,或通过空闲位向量的DD-wide优先级编码器找到DD个空闲位。

  2. DD条新指令分别写入这DD个不连续的位置——这需要DD个独立的写端口,每个写端口可以写入NN个表项中的任何一个。

DD-wide的写端口是一个重要的面积开销。每个写端口需要一个NN-to-1的译码器来选择目标表项,以及BB位的写数据通路。DD个写端口的译码器面积正比于D×ND \times N,写数据通路面积正比于D×N×BD \times N \times B(每个表项需要DD个写入传输门组)。

对于D=6D=6(6-wide分发)、N=96N=96B=84B=84位:

写端口晶体管数D×N×B×4=6×96×84×4194,000\text{写端口晶体管数} \approx D \times N \times B \times 4 = 6 \times 96 \times 84 \times 4 \approx 194{,}000

这约19万个晶体管的写入网络是非压缩结构相对于压缩结构的一个额外开销。压缩结构只需要在尾部的DD个固定位置写入,写端口的译码逻辑简单得多。

然而,非压缩结构的写端口开销可以通过写端口bank化来缓解:将IQ分为多个bank,每个bank只接受D/kD/k个写端口(kk为bank数)。分发逻辑根据空闲位置所在的bank将指令路由到对应的bank。Bank冲突(多条指令被分配到同一bank的空闲位置)可以通过选择不同bank的空闲位置来避免。

从压缩到非压缩的历史演进

案例研究 2 — 从压缩到非压缩的演进

处理器发射队列的设计历史清晰地展示了从压缩到非压缩的演进趋势:

  • MIPS R10000(1996):16项整数IQ + 16项浮点IQ + 16项地址IQ,全部使用压缩结构。16项的规模下压缩开销可以接受。R10000的IQ使用数据捕捉+压缩的组合,每个表项存储两个64位操作数值。在16项的规模下,移位网络需要16×2×(64×2+40)5,37616 \times 2 \times (64 \times 2 + 40) \approx 5{,}376位的MUX——这在0.35μ\mum工艺下是可行的。

  • Alpha 21264(1998):20项整数IQ + 15项浮点IQ,使用压缩结构。21264的整数IQ是压缩的,但浮点IQ由于表项更宽(存储64位浮点操作数)而使用了半压缩方案——只压缩标签部分,数据部分通过指针间接引用。

  • Intel Pentium 4(2000):引入了统一调度器,采用非压缩结构。P4的IQ容量增长到约126项(两个调度器总计),在这个规模下Intel选择了非压缩+年龄跟踪的方案。P4的调度器使用了一种简化的年龄跟踪机制——基于ROB编号的比较来近似年龄排序。

  • AMD K8/K10(2003\sim2007):分布式调度器,每个约20\sim36项,使用非压缩结构。AMD从K8开始全面转向非压缩方案。

  • 现代处理器(2020年代):IQ容量普遍在64\sim160项,几乎全部采用非压缩结构。压缩结构在这个规模下的移位网络面积和功耗开销已不可接受。

这一演进趋势反映了一个基本的工程权衡:随着发射队列容量的增长,压缩带来的"简单选择逻辑"优势被日益增大的"移位网络开销"所抵消。我们可以定量地找到这个交叉点。

压缩结构的总开销主要包括移位网络:CcompactN×(P+1)×BC_{\text{compact}} \propto N \times (P{+}1) \times B

非压缩结构的总开销主要包括年龄矩阵+空闲列表:Cnon-compactN2+Nlog2NC_{\text{non-compact}} \propto N^2 + N \log_2 N

令两者相等,N×(P+1)×B=α×N2N \times (P{+}1) \times B = \alpha \times N^2α\alpha为面积系数比,约0.30.50.3\sim0.5,因为触发器面积小于MUX),解得:

Ncrossover=(P+1)×Bα5×840.41,050N_{\text{crossover}} = \frac{(P{+}1) \times B}{\alpha} \approx \frac{5 \times 84}{0.4} \approx 1{,}050

这个计算给出的交叉点远大于实际观察到的N32N \approx 32,说明我们的简单模型高估了移位网络的效率(或低估了其延迟成本)。实际上,移位网络的主要代价不是面积而是延迟和功耗——17个FO4的延迟在高频处理器中是致命的,而每周期移动约N/2N/2个表项的功耗也是不可忽视的。当综合考虑面积、延迟和功耗三个维度时,交叉点确实在N2432N \approx 24\sim32左右。

非严格年龄优先的选择策略

值得指出的是,严格的最老优先并不是选择策略的唯一选择。在非压缩结构中,有些设计采用近似年龄优先分组优先的策略来简化年龄跟踪硬件:

  • 分组年龄优先:将发射队列划分为若干组(如每组8项),组间按年龄排序,组内使用固定位置优先级。这样年龄矩阵的大小从N2N^2缩小为(N/G)2(N/G)^2GG为组大小),代价是组内的选择可能不严格按年龄顺序。

  • FIFO序号:为每条指令分配一个递增的序号,选择逻辑比较序号大小来确定年龄。序号比较器的面积与 N×log2NN \times \lceil\log_2 N\rceil 成正比,通常小于N2N^2的年龄矩阵。但序号比较器的延迟(多位比较)可能比年龄矩阵的单位AND-OR操作更长。

分组年龄优先的详细设计

分组年龄优先将NN项IQ分为GG组,每组N/GN/G项。年龄矩阵的尺度降为G×GG \times G(组间)和GG(N/G)×(N/G)(N/G) \times (N/G)(组内),总位数为G2+G×(N/G)2G^2 + G \times (N/G)^2

对于N=96N=96G=8G=8(每组12项):

  • 完整年龄矩阵:962=9,21696^2 = 9{,}216

  • 分组年龄矩阵:82+8×122=64+1,152=1,2168^2 + 8 \times 12^2 = 64 + 1{,}152 = 1{,}216

  • 面积缩减到完整方案的1,216/9,21613%1{,}216/9{,}216 \approx 13\%

选择过程分两步:第一步在每组内选出最老的就绪指令(使用组内年龄矩阵),第二步在GG个组的代表中选出全局最老的(使用组间年龄矩阵)。总延迟为两次年龄矩阵查询的最大值——由于组内矩阵和组间矩阵可以并行查询(组间矩阵不依赖组内的选择结果),总延迟约为max(log2(N/G),log2G)=max(log212,log28)=max(3.6,3)4\max(\log_2(N/G), \log_2 G) = \max(\log_2 12, \log_2 8) = \max(3.6, 3) \approx 4级门延迟。

FIFO序号方案

FIFO序号方案为每条指令分配一个ss位的递增序号(s=log2WROBs = \lceil\log_2 W_{\text{ROB}}\rceil,其中WROBW_{\text{ROB}}为ROB的容量)。选择逻辑比较就绪指令的序号,选择序号最小的。

序号比较器用于找到NNss位数中的最小值。一种高效实现是锦标赛树(tournament tree):NN个候选两两比较,胜者(序号较小者)进入下一轮,直到选出全局最小。锦标赛树有log2N\lceil\log_2 N\rceil级,每级需要ss位比较器。

对于N=96N=96s=8s=8

  • 比较器数量:N1=95N - 1 = 95ss位比较器

  • 总延迟:log296×tcmp(s)=7×3=21\lceil\log_2 96\rceil \times t_{\text{cmp}}(s) = 7 \times 3 = 21个FO4(每个8位比较器约3个FO4)

这比年龄矩阵方案(约7个FO4)慢3倍,但面积显著更小。在不追求极致选择速度的设计中(如分布式IQ中每个小队列只有24\sim36项),FIFO序号方案是一个有吸引力的选择。

研究表明,对于大多数工作负载,近似年龄优先的选择策略与严格年龄优先相比,IPC损失非常小(通常不到1%)。这是因为在典型代码中,同一周期内有多条同类型指令同时就绪的概率本身就不高;即使出现这种情况,选择了一条稍年轻的指令而不是最老的指令,对整体执行进度的影响也微乎其微。

随机选择策略

一种更激进的简化方案是完全放弃年龄排序,使用随机选择(random select):从所有就绪指令中随机选择一条发射。随机选择的硬件实现极其简单——一个NN位的就绪向量、一个线性反馈移位寄存器(LFSR)产生的伪随机数、以及一个用伪随机数索引就绪位的查找电路。总面积不超过2N2N位,远小于年龄矩阵的N2N^2位。

随机选择的IPC损失比近似年龄优先更大——文献报告约为2\sim5%。损失的来源是:年轻指令被优先发射可能导致老指令长期滞留在IQ中,增加了IQ溢出的概率(因为老指令占用表项不释放)。在极端情况下,如果一条很老的指令一直被随机选择"跳过",它可能阻塞ROB的提交——因为ROB按顺序提交,最老的指令不完成,其后所有指令都无法提交。

因此,纯随机选择在商业处理器中很少采用。但随机+年龄约束的混合方案是可行的:在随机选择的基础上,强制保证每条指令在等待超过TmaxT_{\max}个周期后获得最高优先级。这种方案的面积接近随机选择,性能接近年龄优先。

最老优先的必要性分析

从理论角度看,最老优先策略的重要性取决于工作负载中同时就绪的同类型指令数量。如果在任意给定周期内,只有1\sim2条同类型指令就绪,那么无论采用何种选择策略,结果都相同——因为没有竞争。只有当就绪指令数>>可用功能单元数时,选择策略才产生差异。

统计研究表明,在典型的SPEC INT基准测试中:

  • 约65%的周期,每种功能单元类型只有0\sim1条指令就绪(无竞争)。

  • 约25%的周期,有2\sim3条同类型指令竞争(轻度竞争)。

  • 约10%的周期,有4条以上同类型指令竞争(重度竞争)。

在大多数周期中选择策略不起作用,这解释了为什么近似年龄优先与严格年龄优先的IPC差异如此之小。然而,在那10%的重度竞争周期中,错误的选择可能导致连锁反应——一条关键路径上的老指令被延迟发射,导致其所有后续依赖指令也被延迟,最终影响较大。最老优先策略的价值主要体现在避免这种连锁延迟。

发射队列的异常处理与刷新

发射队列的设计不仅涉及正常的唤醒-选择流程,还必须处理各种异常情况——分支误预测、异常(exception)、以及推测性唤醒失败。这些异常事件需要快速、正确地清除发射队列中的受影响指令。

分支误预测的处理

当分支预测器做出了错误的预测时,在误预测分支之后的所有指令——无论它们处于流水线的哪个阶段——都需要被取消。对于发射队列中的指令,取消操作需要:

  1. 识别受影响的指令:发射队列中哪些指令是在误预测分支之后被分发的?这通过BrMask字段来确定。每个IQ表项的BrMask记录了该指令依赖哪些未解析的分支。当分支bkb_k被确认为误预测时,所有BrMask[k]=1\texttt{BrMask}[k]=1的表项对应的指令需要被取消。

  2. 无效化表项:将受影响表项的Valid\texttt{Valid}位清零。这一操作可以在1个周期内并行完成——每个表项只需执行Validnew=ValidoldAND¬(BrMask[k]ANDMispredSignal)\texttt{Valid}_{\text{new}} = \texttt{Valid}_{\text{old}} \mathop{\texttt{AND}} \neg(\texttt{BrMask}[k] \mathop{\texttt{AND}} \texttt{MispredSignal})。这是一个2级门延迟的操作,远在一个时钟周期的预算内。

  3. 回收空闲表项:被无效化的表项变为空闲,需要将其编号归还到空闲列表中。在非压缩结构中,直接将Valid=0\texttt{Valid}=0的位在空闲位向量中标记即可。在使用FIFO空闲列表的设计中,需要将所有被取消表项的编号批量写回FIFO——这可能需要2\sim3个周期才能完成(取决于被取消的指令数量和FIFO的写端口数)。

分支误预测的清除延迟通常为1\sim2个周期(不计前端的重新取指延迟)。清除操作本身的面积开销很小——每个表项只需一个额外的AND门。

推测性唤醒失败的处理

推测性唤醒失败(如load指令在L1 cache未命中)是一种更复杂的异常情况。被错误唤醒的指令可能已经开始执行,其结果是无效的——但更糟糕的是,这些错误结果可能已经通过CDB广播出去,推测性地唤醒了更多后续指令,形成一个错误唤醒链

处理推测性唤醒失败需要:

  1. 检测失败:当load指令在L1 cache未命中时,处理器检测到推测性唤醒失败。这通常在load指令开始执行后的1\sim2个周期内发生(cache tag比较完成时)。

  2. 取消错误执行的指令:所有被该load推测性唤醒的指令(以及它们的传递依赖指令)需要被标记为"需要重放"(replay)。这些指令的Issued位被清零,SrcReady位被回滚到唤醒前的状态。

  3. 重新等待:被标记为重放的指令重新在IQ中等待,直到load的结果真正可用(从L2/L3 cache或内存返回数据)后再次被唤醒。

SrcReady位的回滚是这个过程中最棘手的部分。有两种实现方式:

方式1:精确回滚。为每个源操作数维护一个"推测唤醒"标志位SpecWake\texttt{SpecWake}。当推测性唤醒发生时,SpecWake\texttt{SpecWake}被置1。当推测失败时,所有SpecWake=1\texttt{SpecWake}=1的就绪位被清零。这需要跟踪每个就绪位是由推测唤醒还是确定唤醒(如分发时已就绪)设置的——每个源操作数多1位存储,面积增加约2N2N位。

方式2:全局重放。当推测失败发生时,不区分哪些指令被推测唤醒,而是将所有IQ中指令的就绪位全部重新计算——通过查询每个源标签对应的物理寄存器是否已被确定地写入。这种"全量刷新"不需要额外的存储位,但需要一个完整的就绪位重计算周期——期间IQ不能进行正常的唤醒和选择,相当于一个气泡。这种方式更简单但性能代价更高。

Intel Pentium 4的调度器使用了一种基于重放队列(replay queue)的方案:被推测唤醒并执行的指令在执行后不立即从调度器中删除,而是被复制到一个独立的重放队列中。如果推测失败,重放队列中的指令被重新注入调度器。这种方案避免了就绪位回滚的复杂性,但增加了重放队列的面积。

IQ清空操作

在某些情况下,处理器需要完全清空发射队列——取消其中所有指令,将所有表项标记为空闲。这通常发生在:

  • 严重的异常(如页面故障、中断):异常指令之后的所有指令都被取消。如果异常指令足够老(接近ROB的提交端),则IQ中几乎所有指令都比它年轻,需要全部清除。

  • 流水线刷新(pipeline flush):某些特殊指令(如x86的CPUIDFENCE等序列化指令)要求前面所有指令完成后才执行,之后的所有指令必须重新取指——这等效于清空整个IQ。

  • 上下文切换:操作系统切换到另一个线程时,当前线程的所有IQ内容必须被清除(在SMT处理器中,可能只清除该线程对应的表项)。

清空操作在硬件上非常简单:将所有NN个表项的Valid\texttt{Valid}位同时清零。这可以通过一个全局复位信号实现——每个Valid\texttt{Valid}触发器的异步复位端(asynchronous reset)连接到清空信号。一个周期内即可完成。

清空后,空闲列表需要被重置为包含所有NN个表项编号。对于FIFO空闲列表,这等价于将FIFO重新初始化为{0,1,2,,N1}\{0, 1, 2, \ldots, N{-}1\}——可以通过头/尾指针复位实现(将头指针设为0、尾指针设为NN,预填充的编号从0到N1N{-}1常驻在FIFO中)。对于空闲位向量,只需将所有位设为1即可——同样一个周期完成。

年龄矩阵在清空后不需要特殊处理——所有表项都变为空闲(Valid=0\texttt{Valid}=0),年龄矩阵中的值不影响任何逻辑。下次分发新指令时,年龄矩阵会被正确地更新。

取消操作的时序要求

无论是分支误预测清除还是推测唤醒失败的重放,取消操作的延迟都是关键性能指标。取消延迟越长,错误执行的指令越多,浪费的周期和功耗越大。

分支清除的延迟

分支清除的延迟可以分解为:

  1. 分支解析延迟:从分支指令被发射到预测结果被确认(或否定)的时间。对于条件分支,这通常在分支指令执行完成的同一周期——即分支的执行延迟(1\sim2个周期)。

  2. 清除信号传播延迟:从分支解析单元到IQ的清除信号传播时间。这取决于物理布局,通常1\sim2个FO4。

  3. IQ内部清除延迟:检查所有表项的BrMask并无效化匹配项。如前所述,这是一个1周期的并行AND操作。

总的分支清除延迟约为3\sim4个周期(从分支发射到IQ中受影响指令被无效化)。在这3\sim4个周期内,错误路径上的指令可能继续被分发、唤醒甚至发射——这些都是无用功,浪费了IQ的表项和功能单元的执行周期。

从数字上看,假设分支预测准确率为97%(每100条分支有3条误预测),分支指令占总指令的15\sim20%,则平均每200\sim300条指令就会发生一次误预测清除。在5 GHz、IPC=4的处理器中,每200条指令约需50个周期,即大约每50个周期就有一次清除事件。每次清除都会导致IQ中被无效化的指令表项需要回收——如果清除延迟较长,IQ的有效可用容量会降低。

现代处理器通过分支检查点(branch checkpoint)技术来加速清除:在每个未解析分支点保存IQ的关键状态(如就绪位向量、空闲列表指针),分支误预测时直接恢复到检查点状态,而不需要逐项检查BrMask。这可以将清除延迟降低1\sim2个周期,但增加了检查点存储的面积(每个检查点约NN位的就绪位+日志信息)。

异常的精确处理

当一条指令触发异常(如非法操作码、除零、页面故障)时,该指令之后的所有指令——包括IQ中等待的指令——都可能需要被取消。但在乱序处理器中,IQ中可能存在比异常指令更老的指令(它们比异常指令先被分发但尚未就绪)。这些更老的指令不应被取消——它们是异常指令之前的正确执行流的一部分。

因此,异常清除需要精确地区分"比异常指令老"和"比异常指令年轻"的指令。这可以通过以下方式实现:

  • 使用ROB编号:每个IQ表项存储了其ROB编号。异常指令的ROB编号也已知。通过比较两个ROB编号可以确定哪个更老。但ROB编号是循环使用的(wrap-around),需要注意环绕比较的正确性。

  • 使用年龄矩阵:如果IQ已经有年龄矩阵,可以直接查询异常指令对应行中的位——M[excp][j]=1M[\text{excp}][j]=1表示异常指令比jj老,jj应被取消。

  • 使用分支掩码的扩展:将异常处理也纳入分支掩码机制——在异常指令之后分配一个"虚拟分支标记",之后的所有指令都标记此标记。异常确认后,按照分支误预测的流程清除。

在实际设计中,异常处理通常通过ROB完成——ROB按顺序扫描并取消异常指令之后的所有条目,然后通知IQ清除对应的表项。这种间接方式的延迟稍长(2\sim3个周期),但避免了在IQ中实现复杂的年龄比较逻辑。

发射队列的流水线化

在高频率处理器中,发射队列的唤醒和选择操作可能无法在单个时钟周期内完成。此时需要对IQ的操作进行流水线化——将一个周期内的操作分解到多个流水线级中。

两级流水线方案

最常见的IQ流水线化方案是将唤醒和选择分为两个流水线级:

  • 级1(Wakeup级):接收CDB上的唤醒标签,执行CAM比较,更新就绪位。在周期末尾,就绪位被锁存到流水线寄存器中。

  • 级2(Select级):根据锁存的就绪位执行选择仲裁,确定要发射的指令。选择结果在周期末尾被输出到PRF读取阶段。

两级方案的代价是背靠背依赖链上出现额外的气泡。考虑指令II(在周期TT的执行级产生结果)和依赖II的指令JJ

周期TTT+1T{+}1T+2T{+}2T+3T{+}3T+4T{+}4T+5T{+}5T+6T{+}6
指令II执行写回
指令JJ(单级IQ)唤醒+选择读PRF执行
指令JJ(两级IQ)唤醒选择读PRF执行

两级IQ使得JJ的执行比单级IQ晚1个周期。对于长度为LL的依赖链,两级IQ累计增加LL个周期的延迟。

投机选择的优化

为了减轻两级流水线的性能损失,一些设计采用投机选择(speculative select)技术:在唤醒完成之前就开始选择。具体而言,选择逻辑不等待本周期唤醒的结果,而是基于上一周期的就绪状态进行选择。同时,对于本周期可能被唤醒的指令(即CDB上正在广播的标签匹配的指令),选择逻辑推测性地将其视为就绪并纳入候选。

投机选择的正确性需要在下一周期验证:如果被投机选择的指令实际上没有被唤醒(例如CDB广播被取消),则需要撤销选择并重试。投机错误的概率通常很低(<1%),因此这种方案在大多数情况下等效于单级IQ,只在极少数情况下出现额外的气泡。

三级流水线方案

在极高频率(>5 GHz)且IQ容量较大(>128项)的设计中,即使两级流水线方案也可能无法满足时序。此时可以考虑三级方案:

  • 级1(Wakeup):CDB标签广播和CAM比较。

  • 级2(Ready Compute):就绪位更新、就绪信号汇总(AND两个源操作数的就绪位)。

  • 级3(Select):优先级仲裁和指令信息读出。

三级方案使得背靠背依赖链上每对依赖指令之间有2个周期的气泡(而不是两级的1个或单级的0个)。对于长度为LL的依赖链,三级方案比单级方案慢2L2L个周期——性能损失约为2L/(L+non-critical)2L/(L + \text{non-critical}),在L=20L=20的典型情况下约15\sim25%。这个性能损失通常是不可接受的。

因此,三级方案在实际商业处理器中极少使用。设计者更倾向于通过以下方式避免三级流水线:

  • 降低IQ容量以缩短CAM和选择逻辑的延迟。

  • 使用分布式IQ将大规模的唤醒和选择分解为多个小规模的并行操作。

  • 使用定制电路(而非标准综合)来优化关键路径的延迟。

  • 采用投机选择技术来隐藏唤醒和选择的串行延迟。

三个维度的交叉影响

前面三节分别讨论了发射队列的三个设计维度,但这三个维度并不是完全独立的——某一个维度上的选择会影响其他维度的设计空间和权衡。本节分析这些交叉影响。

数据捕捉与压缩的交互

数据捕捉结构的表项位宽BB远大于非数据捕捉结构(约212位 vs 84位)。压缩结构的移位网络面积正比于N×BN \times B。因此,数据捕捉+压缩的组合面临最严重的面积问题——移位网络不仅需要移动标签和控制位,还需要移动128位的操作数数据。

N=96N=96P=4P=4为例,移位网络的MUX位数:

  • 数据捕捉+压缩:96×5×212=101,76096 \times 5 \times 212 = 101{,}760

  • 非数据捕捉+压缩:96×5×84=40,32096 \times 5 \times 84 = 40{,}320

  • 面积比:101,760/40,320=2.52×101{,}760 / 40{,}320 = 2.52\times

数据捕捉+压缩的移位网络面积是非数据捕捉+压缩的2.5倍。更重要的是,移动128位的数据值每周期的动态功耗也增加2.5倍(因为翻转位数与位宽成正比)。

这解释了为什么MIPS R10000虽然同时采用了数据捕捉和压缩,但每个IQ只有16项——在这个小规模下,移位网络的面积和功耗尚可接受。如果将R10000的IQ扩展到96项,仅移位网络就需要超过40万个晶体管,在当时的0.35μ\mum工艺下几乎不可行。

相比之下,非数据捕捉+非压缩的组合是面积最优的选择:既不需要存储操作数数据(节省IQ面积),也不需要移位网络(节省移位面积),额外开销只是年龄矩阵(N2N^2位触发器)和空闲列表(Nlog2NN \log_2 N位)。这正是现代处理器的主流选择。

集中式/分布式与压缩的交互

压缩结构在集中式IQ中的问题尤为突出。集中式IQ的容量NN通常较大(96\sim160项),压缩移位网络的面积随NN线性增长。而分布式IQ的每个子队列容量nn较小(24\sim48项),压缩的开销在每个子队列中更可控。

理论上,可以设计一种"分布式+压缩"的混合方案:将IQ拆分为多个小队列,每个小队列内部使用压缩结构。由于每个小队列只有n2432n \approx 24\sim32项,压缩的延迟和面积开销在可接受范围内。这种方案兼具压缩结构的简单选择逻辑和分布式结构的硬件简单性。

然而,实际上没有现代处理器采用这种组合。原因是:在n=2432n=24\sim32的小队列规模下,年龄矩阵的面积(n2=5761,024n^2 = 576\sim1{,}024位)已经非常小——比同规模的压缩移位网络(n×B2,0002,700n \times B \approx 2{,}000\sim2{,}700位MUX)更小。既然年龄矩阵的开销已经低于压缩网络,就没有理由选择压缩方案。

集中式/分布式与数据捕捉的交互

数据捕捉结构需要CDB同时传输标签和数据值。在集中式IQ中,CDB的广播扇出为NN(所有表项都需要接收)。在分布式IQ中,每个子队列只需要接收相关CDB端口的广播,扇出为nn

然而,在数据捕捉模式下,CDB传输的数据宽度为64位。即使扇出只有n=24n=24,驱动24个64位宽的写入端口的电容负载也不可忽视。而且,跨队列的唤醒信号如果也需要携带数据值(用于跨队列的数据捕捉),则全局CDB的数据宽度无法缩减。

这是一个经典的局部性vs全局性的权衡。将全局CDB的数据传输完全消除可以大幅降低功耗和布线拥塞,但可能增加跨队列依赖链的延迟。

以一个具体的4队列分布式系统为例进行定量分析。假设:

  • 4个调度器:INT(32项)、FP(24项)、LD(28项)、ST(12项),总计96项。

  • 每个调度器有2个本地执行端口,产生的结果标签本地广播(本地唤醒延迟=0)。

  • 跨队列唤醒通过全局唤醒总线传输,额外延迟=1个周期。

  • 全局CDB传输标签宽度=8位(仅标签,不传数据)。

在数据捕捉模式下:

  • 本地捕捉:本地执行端口的结果通过CDB广播标签+64位数据到本队列表项。CDB宽度=72位。本地扇出=32\sim28个表项。

  • 远程唤醒:跨队列只广播标签(8位),不传数据。远程队列中被唤醒的指令在发射时需要从PRF读取操作数——相当于对跨队列依赖使用非数据捕捉模式。

这种本地捕捉+远程非捕捉的混合方案的有效性取决于跨队列依赖的比例。统计数据表明:

  • 整数指令的源操作数来自整数执行单元的概率约85\sim90%(本地依赖)。

  • Load指令的地址操作数来自整数ALU的概率约95%(跨队列依赖——地址计算在INT队列,Load在LD队列)。

  • 浮点指令的源操作数来自浮点执行单元的概率约70\sim80%。

因此,INT队列几乎完全可以受益于本地数据捕捉(85\sim90%的依赖是本地的);FP队列约70\sim80%受益;LD队列的地址操作数几乎全部是跨队列的(来自INT),无法从本地捕捉中受益。

分布式+数据捕捉的一种优化是本地捕捉:只在本队列的执行单元产生的结果上执行数据捕捉,跨队列的依赖通过PRF读取获得操作数。这种选择性捕捉减少了全局CDB的数据传输,但增加了控制逻辑的复杂度——需要区分本地唤醒(带数据捕捉)和远程唤醒(不带数据捕捉),且对于跨队列依赖链上的指令可能增加一个周期的延迟。

功耗优化技术

发射队列的功耗在整个处理器核心中占有相当的比重。以一个典型的96项发射队列为例,在5 GHz、0.7V条件下,其动态功耗约为15\sim30 mW,占核心总功耗(约5\sim10 W)的0.3\sim0.6%。虽然比例不大,但由于发射队列是"always-on"的组件(每个周期都在工作),其功耗密度(每单位面积的功耗)在核心中属于最高的区域之一。

CAM比较器的门控

唤醒CAM中的大量比较器是IQ功耗的主要来源。每个周期,WW个CDB端口的标签被广播到所有N×2N \times 2个标签位置进行比较——但绝大多数比较的结果都是"不匹配"(因为平均每个周期只有WW条指令完成,而IQ中有2N2N个等待中的标签,匹配概率约为W/(2N)4%W/(2N) \approx 4\%)。96%的比较器翻转是"无用功"。

比较器门控(comparator gating)是一种有效的功耗优化。其核心思想是:在进行完整的tt位标签比较之前,先比较标签的一个子集(如高2位),如果高位不匹配就跳过剩余位的比较。

具体实现方式为:将t=8t=8位的标签分为高2位和低6位。每个表项先用高2位与CDB标签的高2位比较(2个XOR + 1个2输入NOR),延迟约2个FO4。如果高2位不匹配(概率约75%),则通过一个门控信号关闭低6位比较器的时钟——这6个XOR门不翻转,节省了6/8=75%6/8 = 75\%的比较器功耗。只有当高2位匹配时(概率约25%),低6位才被激活进行比较。

总功耗降低比例:原来的100%比较器翻转变为100%×2/8+25%×6/8=25%+18.75%=43.75%100\% \times 2/8 + 25\% \times 6/8 = 25\% + 18.75\% = 43.75\%——功耗降低约56%。代价是增加了高2位比较器和门控逻辑的面积(约10%的额外面积),以及高位比较延迟(2个FO4)与全比较延迟串行化的问题。但由于高位比较只有2个FO4,而全比较约3个FO4,总延迟增加到约5个FO4——仍然在时序预算内。

空闲表项的时钟门控

对于空闲(Valid=0\texttt{Valid}=0)的表项,其唤醒比较器完全不需要工作——空表项不等待任何操作数。通过将Valid\texttt{Valid}信号作为该表项CAM时钟的门控条件,可以完全消除空闲表项的比较器翻转功耗。

在IQ平均利用率为70%的情况下,约30%的表项是空闲的。门控这30%的比较器可以进一步降低功耗约30%。结合上述的高位预过滤技术,总功耗可降低到原来的0.44×0.70=0.310.44 \times 0.70 = 0.31——即功耗降低约69%。

选择性唤醒端口分配

另一种功耗优化是减少每个表项需要监听的CDB端口数。在集中式IQ中,每个表项需要与所有WW个CDB端口进行比较。但如果处理器知道某个表项的源操作数只可能由特定类型的功能单元产生(例如,一个浮点操作数只能由FPU产生),则该表项只需要监听FPU对应的CDB端口,而不需要监听ALU的CDB端口。

这种选择性监听需要在分发时为每个源操作数确定其可能的产生者类型——这一信息可以从RAT(Register Alias Table)中获得(RAT记录了每个物理寄存器的最后写入者的功能单元类型)。然后,IQ表项中额外存储一个"CDB端口掩码"字段(WW位),指示该操作数需要监听哪些CDB端口。

对于W=8W=8个CDB端口(4个ALU + 2个FPU + 2个AGU),如果一个操作数只监听4个ALU端口,则比较器功耗降低4/8=50%4/8 = 50\%。掩码字段的额外面积为2×W=162 \times W = 16位/表项——占84位表项的约19%,是一个不可忽略的面积代价。因此,这种优化通常只在功耗极其敏感的设计中采用。

SIMD/向量指令对发射队列的影响

现代处理器广泛支持SIMD(Single Instruction Multiple Data)和向量指令集(如Intel AVX-512、ARM SVE),这对发射队列的设计产生了重要影响。

宽操作数的挑战

SIMD指令的操作数宽度远大于标量指令:

  • SSE:128位操作数

  • AVX/AVX2:256位操作数

  • AVX-512:512位操作数

  • ARM SVE:可变长度,最大2048位

在数据捕捉结构中,每个IQ表项需要存储完整的操作数值。对于AVX-512指令,两个512位操作数需要512×2=1,024512 \times 2 = 1{,}024位——是64位标量操作数(64×2=12864 \times 2 = 128位)的8倍。一个96项的数据捕捉IQ的数据域存储量将从1.5 KB增长到12 KB——这在面积和功耗上都是不可接受的。

这也是为什么支持宽SIMD指令的处理器几乎无一例外地采用非数据捕捉结构——SIMD操作数值存储在独立的(通常是分bank的)向量寄存器文件中。IQ表项只存储物理寄存器标签,不受操作数宽度的影响。

标签宽度的影响

SIMD/向量寄存器通常独立于整数寄存器进行重命名。例如,Intel的AVX-512使用一组独立的物理向量寄存器(约168个),需要8位标签。ARM SVE的谓词寄存器(predicate register)又是另一组独立的物理寄存器。

在分布式IQ中,浮点/SIMD调度器的标签域可以只存储向量寄存器标签,而整数调度器只存储整数寄存器标签——两种标签的位宽可以不同,减少了存储浪费。在集中式IQ中,标签域的宽度必须取所有类型中最大的,造成对较短标签类型的浪费。

SIMD指令的调度复杂性

某些SIMD指令需要在多个执行端口上同时执行(如512位操作需要两个256位端口)。这对选择逻辑引入了额外的约束——选择逻辑不仅需要确保功能单元可用,还需要确保多个端口同时可用。这类指令的调度等价于一个多资源分配问题,比单端口调度更复杂。

AMD Zen 4处理AVX-512的方式是将512位操作拆分为两个256位微操作(micro-op),每个微操作独立调度。这避免了多端口同时分配的复杂性,但增加了微操作的数量和IQ的压力。Intel的AVX-512实现则在调度器中直接支持512位宽的操作,使用专门的调度逻辑来协调两个256位端口的同时使用。

微操作融合与拆分对IQ的影响

除了SIMD指令的宽度问题,微操作融合(micro-op fusion)和微操作拆分(micro-op fission/cracking)也对IQ容量产生重要影响。

微操作融合将原本需要两条微操作表示的指令(如x86的"内存-ALU"指令ADD [mem], reg)在前端作为一条融合微操作处理,减少了IQ的容量压力。例如,Intel Skylake在分发时可以保持某些融合微操作不拆分,使一个IQ表项同时包含Load和ALU操作的信息。只有在选择发射时,融合微操作才被拆分为两条独立的微操作分别发射到Load单元和ALU单元。

这种设计有效地提高了IQ的逻辑容量——同样数量的物理表项可以容纳更多的x86指令,减少了分发暂停的频率。

这种"晚期拆分"策略的IQ表项需要额外的字段来存储第二条微操作的信息(如第二个目标标签、额外的操作码位等),增加了每个表项的位宽约15\sim25位。但由于融合减少了IQ中的表项占用数量(两条微操作合为一个表项),总的IQ利用效率提高了约10\sim15%。

微操作拆分是相反的过程:一条复杂指令在解码阶段被拆分为多条微操作,每条微操作占用一个IQ表项。例如,x86的REP MOVS(字符串复制)指令可能被拆分为数十条Load/Store微操作,每条都需要一个IQ表项。这种"微操作爆炸"可能瞬间耗尽IQ容量,导致严重的分发暂停。

ARM的AArch64 ISA通过简洁的定长指令格式大幅减少了微操作拆分的需求——大多数ARM指令可以直接映射为一条微操作。这是ARM处理器在相同IQ容量下能够实现更高利用率的原因之一。在SPEC INT基准测试中,x86处理器的微操作膨胀率(micro-op expansion ratio)约为1.15\sim1.25(即100条x86指令产生115\sim125条微操作),而AArch64的膨胀率约为1.02\sim1.05——差异显著。

三源操作数指令

某些ISA支持三源操作数的指令——例如ARM的MADD Xd, Xn, Xm, Xa(乘加指令,Xd=Xn×Xm+XaXd = Xn \times Xm + Xa)需要三个源操作数。x86的FMA(Fused Multiply-Add)指令也需要三个源操作数。

三源操作数指令对IQ表项的设计产生直接影响:每个表项需要三个源标签域和三个就绪位,而不是通常的两个。这增加了每个表项约t+1=9t + 1 = 9位(一个额外的标签+就绪位),即每个表项从84位增加到93位——增幅约10%。

唤醒逻辑的比较器数量也相应增加:从N×2×WN \times 2 \times W增加到N×3×WN \times 3 \times W——增加50%。选择逻辑中的就绪信号变为三个源操作数就绪位的AND:Ready[i]=R1[i]ANDR2[i]ANDR3[i]\texttt{Ready}[i] = \texttt{R1}[i] \mathop{\texttt{AND}} \texttt{R2}[i] \mathop{\texttt{AND}} \texttt{R3}[i],增加了一个门延迟(约0.5个FO4),对时序影响微小。

在实际设计中,不是所有指令都需要三个源操作数——三操作数指令(如FMA)只占指令总数的很小一部分。因此,一些处理器选择不在IQ中为所有表项提供第三个源标签域,而是将三操作数指令拆分为两条微操作(如MUL+ADD\texttt{MUL} + \texttt{ADD}),每条只需要两个源操作数。这简化了IQ设计但增加了微操作数量。

向量长度不可知的挑战

ARM SVE(Scalable Vector Extension)引入了向量长度不可知(Vector Length Agnostic,VLA)编程模型。SVE的向量寄存器长度在硬件实现时确定(128位到2048位),但软件在编写时不需要知道具体长度。

这对发射队列的设计产生了有趣的影响:IQ表项的标签字段宽度(用于引用SVE向量寄存器)不随向量长度变化,因为物理向量寄存器的数量是固定的(无论每个寄存器多宽)。但执行单元的宽度可能不足以一次处理整个SVE向量——例如,256位的执行单元处理512位的SVE操作需要2个周期。此时,调度器需要将一条SVE指令分为多个"切片"(slice),每个切片独立调度。每个切片不需要额外的IQ表项(因为它是同一条指令的不同执行阶段),但需要功能单元在多个周期内保持占用状态——选择逻辑必须知道哪些功能单元正在执行多周期SVE操作,不能在这些周期向它们发射新指令。

发射队列设计的全局优化视角

综合以上分析,可以将发射队列的设计空间视为一个多目标优化问题:

mindesign{面积(d),功耗(d),延迟(d)}s.t.IPC(d)IPCtarget\min_{\text{design}} \big\{ \text{面积}(d), \text{功耗}(d), \text{延迟}(d) \big\} \quad \text{s.t.} \quad \text{IPC}(d) \geq \text{IPC}_{\text{target}}

其中dd表示设计参数的组合(容量NN、分区数kk、数据捕捉模式、压缩模式等)。

这个优化问题通常没有解析解——设计空间中的Pareto最优前沿需要通过大量的仿真和综合实验来探索。

设计空间中的关键权衡曲线

为了直观地理解设计权衡,我们可以绘制几个典型的权衡曲线(基于文献中的仿真数据)。

容量-IPC曲线:IPC随IQ容量NN的增长呈对数形状——在N<32N < 32时快速增长,在32<N<9632 < N < 96时中速增长,在N>96N > 96时接近饱和。对于SPEC INT 2006基准测试(6-wide处理器),典型的IPC值如下:

IQ容量NN16326496160
归一化IPC0.820.910.960.981.00
相对于N=160N=160的IPC损失18%9%4%2%0%

这些数据来自学术文献中使用周期精确模拟器(如SimpleScalar或gem5)在SPEC基准测试上的仿真结果。不同的工作负载对IQ容量的敏感度差异很大:整数代码(如gccperl)对IQ容量较为敏感,因为其依赖链长且分支密集,需要较大的IQ来容纳等待的指令;浮点代码(如swimmgrid)对IQ容量不太敏感,因为其并行度高、依赖链短,较小的IQ就足以发掘全部ILP。

这些数据表明,从96项到160项的扩容只带来约2%的IPC提升,但面积和功耗增加约67%(面积正比于NN的唤醒逻辑)。这2%的IPC是否值得67%的面积增加,取决于设计的目标和约束。

功耗-面积Pareto前沿:在给定IPC目标(如归一化IPC 0.95\geq 0.95)下,不同设计方案的功耗和面积形成一条Pareto前沿。集中式+非数据捕捉+非压缩方案通常位于"面积较大、功耗较高但IPC最高"的端点;分布式+非数据捕捉+非压缩方案位于"面积较小、功耗较低、IPC略低"的中间区域;分布式+混合捕捉+非压缩方案可能在特定工作负载下提供最佳的功耗-性能比。

在实际工程中,设计团队通常通过以下步骤确定最终方案:

  1. 性能建模:使用周期精确的模拟器(如gem5)在目标benchmark上扫描不同NNkk值,生成IPC-面积曲线。

  2. 时序评估:使用逻辑综合工具(如Synopsys Design Compiler)评估不同方案的关键路径延迟,确认在目标频率下可行。

  3. 功耗估算:使用功耗分析工具(如PrimePower)估算不同方案的动态功耗和漏电功耗。

  4. 物理设计试验:对少数候选方案进行布局布线(P&R)试验,评估实际面积和时序。

  5. 最终选择:在满足IPC和频率目标的前提下,选择面积-功耗最优的方案。

本章小结

本章系统地分析了发射队列的三个核心设计维度:集中式与分布式的组织方式、数据捕捉与非数据捕捉的操作数管理策略、以及压缩与非压缩的空位处理机制。这三个维度的不同组合构成了一个 2×3×2=122 \times 3 \times 2 = 12 种可能的设计空间。表表 27.7总结了几种典型的组合及其特征。

组织数据管理压缩代表/特征
集中式数据捕捉压缩Tomasulo原始保留站。面积和功耗极高,仅适用于小规模。
集中式非数据捕捉非压缩Intel统一调度器。利用率高,硬件复杂度可通过工程优化管控。
分布式数据捕捉压缩早期MIPS R10000(每个小IQ内压缩)。在小规模分布式IQ中可行。
分布式非数据捕捉非压缩AMD Zen系列、ARM Cortex-X系列。主流高性能设计的首选。
分布式混合捕捉非压缩学术研究方案。兼顾背靠背执行和面积效率。

发射队列设计空间中的典型组合

硬件描述 4 — 各设计组合的总面积估算(96项IQ)

将三个维度的不同组合下的总面积进行估算(含IQ存储、CAM、选择逻辑、年龄矩阵/移位网络、旁路网络等),给出一个全面的面积对比:

组合IQ存储CAM选择/年龄旁路/移位总计
(K晶体管)(K晶体管)(K晶体管)(K晶体管)(K晶体管)
集中+数据捕捉+压缩122988407635
集中+数据捕捉+非压缩12298670287
集中+非数据捕捉+压缩48988161315
集中+非数据捕捉+非压缩489867128341
分布式+非数据捕捉+非压缩482517128218

注:分布式方案假设分为4个队列,每个24项。"旁路"列中分布式方案仍需旁路网络(128K),但CAM和选择逻辑的面积显著降低。集中式方案的旁路网络面积与非数据捕捉组合一起列出。

从上表可以看出:

  • 集中式+数据捕捉+压缩(Tomasulo风格)的总面积最大(635K),是分布式+非数据捕捉+非压缩(现代主流)的2.9倍。

  • 移位网络(压缩方案中的"旁路/移位"列)是压缩方案面积的最大贡献者——对于数据捕捉结构更是如此。

  • 分布式方案的CAM面积(25K)仅为集中式(98K)的约1/4。

从工程实践的角度看,现代高性能处理器的发射队列设计呈现出几个明确的趋势:

  1. 分布式为主:随着发射宽度从4\sim6增长到8\sim12甚至更宽,集中式结构的唤醒和选择逻辑复杂度变得难以承受。分布式结构通过将问题分解为多个小规模子问题来保持时序可行性。

  2. 非数据捕捉为主:独立的PRF配合旁路网络已成为标准方案。PRF的多端口问题通过bank划分和分层设计来缓解。

  3. 非压缩为主:发射队列容量的增长使得压缩移位网络的开销不可接受。年龄矩阵或近似年龄跟踪机制足以提供良好的选择策略。

  4. 容量持续增长:从1990年代末的16\sim20项到2020年代的128\sim160项,发射队列容量增长了约8\sim10倍,反映了对更大指令窗口的持续需求。

表 27.8给出了一个典型的现代发射队列(96项、非数据捕捉、非压缩、8唤醒端口)的硬件预算汇总,帮助读者建立对各部分开销的量化认知。

组件位数/单元数晶体管数(估算)说明
IQ表项存储8,064位\sim48K96×8496 \times 84位,6T SRAM
唤醒CAM1,536比较器\sim98K96×2×896 \times 2 \times 8个8位比较器
就绪位更新192个OR门\sim2K96×296 \times 2个多输入OR
年龄矩阵9,216位\sim55K96296^2位触发器
选择逻辑8个优先级编码器\sim12K并行仲裁器
空闲列表672位\sim4K96×796 \times 7位FIFO
读出MUX8个96-to-1 MUX\sim32K每个MUX 84位宽
写入端口8个写端口\sim16K分发宽度为4\sim8
合计\sim267K约27万个晶体管

96项非数据捕捉非压缩发射队列的硬件预算估算

27万个晶体管——这大约相当于一个Intel 80486处理器(1989年,120万晶体管)的22%。在一个包含数十亿个晶体管的现代处理器中,27万个晶体管微不足道,但发射队列的重要性在于它处于处理器性能关键路径上:唤醒和选择的延迟直接决定了相邻依赖指令之间的发射间隔,进而影响整个处理器的IPC。

从上表的数据可以看出,唤醒CAM(约98K晶体管)和年龄矩阵(约55K晶体管)是面积的两大主要贡献者,合计占总面积的57%。这解释了为什么发射队列的设计优化主要集中在这两个组件上。例如:

  • CAM优化:采用分布式结构将CAM拆分为多个小型CAM,或采用分段比较(先比较标签的高位快速淘汰不匹配的表项,再比较低位确认)来降低功耗。

  • 年龄矩阵优化:采用分组年龄矩阵或FIFO序号来减少位数,或利用矩阵的反对称性只存储上三角部分(面积减半)。

  • 读出MUX优化:采用分层MUX(先在8个一组中选择,再在12组中选择)来减少MUX的宽度和延迟。

设计提示

发射队列的设计是处理器微架构设计中约束最紧的部分之一。它必须在一个时钟周期的预算内完成唤醒(CAM比较)和选择(优先级仲裁)——这两个操作串行在关键路径上。任何增加唤醒或选择延迟的设计决策(如更大的队列容量、更多的唤醒端口、更严格的年龄排序)都可能导致关键路径超时,迫使降低时钟频率或插入额外的流水线级。因此,发射队列的设计永远是面积、功耗、延迟和IPC之间的精细平衡。

最后,表表 27.9对本章讨论的三个维度的关键结论进行了简明汇总。

维度现代主流选择主要原因关键数据
组织方式分布式(4\sim6个子队列)每个子队列的CAM和选择逻辑更小,时序更宽松,物理布局更灵活分布式CAM面积为集中式的1/k1/kkk为分区数)
数据管理非数据捕捉(标签+PRF)表项位宽从\sim212位降至\sim84位,CDB只需传输标签面积节省约2.5×\times,通过旁路网络弥补PRF读取延迟
压缩方式非压缩(年龄矩阵)移位网络的延迟和面积在N>32N>32时不可接受年龄矩阵面积N2N^2位,对N=96N=96约9K位

发射队列三个设计维度的关键结论汇总

发射队列的三个设计维度之间的交叉影响决定了可行设计空间的大小。理解这些交叉影响——而不是孤立地分析每个维度——是做出正确设计决策的关键。

作为对本章内容的最后总结,读者应当理解以下核心概念:

  1. 发射队列的根本作用是解耦指令的分发和执行——允许乱序处理器在存在数据依赖的情况下最大化功能单元的利用率。没有发射队列,处理器就无法实现乱序执行。

  2. 发射队列的三个设计维度(集中式/分布式、数据捕捉/非数据捕捉、压缩/非压缩)构成了设计空间的基本坐标。每个维度的选择影响面积、功耗、时序和IPC,且三个维度之间存在交叉影响。

  3. 现代高性能处理器的主流选择——分布式+非数据捕捉+非压缩——是数十年工程经验的结晶。这一组合在面积效率、时序可行性和性能之间取得了最佳的平衡。

  4. 唤醒-选择的时序耦合是发射队列设计的核心挑战。能否在一个时钟周期内完成唤醒和选择,直接决定了背靠背依赖链的执行效率——这是处理器IPC的关键决定因素。

  5. 发射队列的容量需要根据目标工作负载的需求来确定。盲目增大容量不仅增加面积和功耗,还可能因唤醒/选择逻辑变慢而降低时钟频率,得不偿失。

设计权衡 2 — 前向桥接——从结构到时序

本章建立了发射队列的三个设计维度:集中式/分布式、数据捕捉/非数据捕捉、压缩/非压缩。这些维度回答了"发射队列存储什么、如何组织"的结构问题。然而,发射队列最紧张的约束不是面积,而是时序——发射需要在一个周期内完成三件事:广播标签唤醒等待指令、从就绪指令中仲裁选择、读取操作数送往执行单元。在5 GHz处理器中,这三个操作的总延迟预算仅有200 ps,但实际需要250\sim300 ps。打破这一矛盾的方法是流水线化——但流水线化引入了投机唤醒的正确性问题。第 28.0 章将深入分析这一最紧张的关键路径。

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