Skip to content

重命名映射表的设计

硬件描述 1 — 端口密度之王

在一个6-wide超标量处理器中,RAT每周期承受18次并行访问——6次读src1、6次读src2、6次写dst。这18个端口压缩在仅32条目×\times7位的微小存储阵列上,使RAT成为整个处理器核心中端口密度最高的结构。作为对比,L1数据Cache通常只有2\sim3个端口,容量却大了三个数量级。RAT的设计不是存储设计问题,而是端口设计问题——每增加一个端口,位单元面积增长约40%,功耗增长约35%。

设计提示

统一视角。处理器设计的本质是在有限的晶体管预算和功耗约束下,通过投机和并行的层层叠加来逼近指令吞吐率的理论上限。RAT和PRF的设计复杂度是并行宽度的直接代价——发射宽度WW的每一次翻倍,RAT端口数增长3W3W,PRF读端口数增长2W2W,面积和功耗以超线性方式膨胀。这正是"宽度墙"(width wall)的微架构根源:并行并非免费,它的物理代价由最密集的存储结构来承担。回顾第 24.0 章中重命名的原理,我们知道消除假依赖可以释放ILP;本章将展示释放ILP的物理代价有多大,以及处理器设计师如何通过多体(banked)PRF、复制RAT和端口合并等技巧来缓解这一代价。在SRAM物理约束方面,第 3.0 章中讨论的多端口SRAM面积增长规律将在本章得到直接体现。

第 24.0 章中,我们建立了寄存器重命名的基本原理:通过将体系结构寄存器(逻辑寄存器)映射到物理寄存器,消除WAR和WAW假相关性,使乱序执行成为可能。本章将深入探讨实现这一映射关系的核心硬件结构——重命名映射表(Register Alias Table, RAT)的具体电路设计。

重命名映射表是超标量处理器重命名阶段的核心数据通路。它维护着每个逻辑寄存器到物理寄存器的当前映射关系,在指令被重命名时完成三个关键操作:(1) 读取源操作数的物理寄存器编号;(2) 为目的操作数分配新的物理寄存器编号;(3) 更新映射关系。在一个NN-wide的超标量处理器中,这三个操作必须在单个时钟周期内为NN条指令并行完成——这对RAT的端口数、访问延迟和旁路逻辑提出了极其苛刻的要求。

RAT的实现有两种主流方案:基于SRAM(静态随机存取存储器)的直接索引方案和基于CAM(内容可寻址存储器)的关联查找方案。两者在面积、时序、功耗上有着截然不同的特性,本章将分别进行详细的电路级分析。此外,与RAT紧密配合的还有空闲列表(Free List)——管理可用物理寄存器的硬件结构,以及物理寄存器文件(Physical Register File, PRF)本身的设计。本章将一并深入讨论这些结构的设计权衡。

从本章的组织结构来看,25.1 节节首先讨论SRAM RAT的物理结构、端口需求推导、多端口位单元的晶体管级分析,以及多条指令同时重命名时的旁路逻辑设计。25.2 节节转向CAM RAT,分析10T NOR型CAM位单元的工作原理、匹配线和搜索线的电气特性、多匹配优先级处理、以及环形缓冲区回绕问题。25.3 节节讨论空闲物理寄存器的管理——包括FIFO和位向量两种实现方案、物理寄存器的完整生命周期追踪、以及引用计数机制。25.4 节节深入物理寄存器文件的设计,覆盖端口分析、多bank结构、寄存器缓存和物理设计约束。最后,25.5 节节将各组件综合起来,分析重命名流水线的全局时序和面积预算。

基于SRAM的重命名映射表

基于SRAM的重命名映射表是最直观、也是目前高性能处理器中最常见的实现方案。其核心思想极其简单:使用一个以逻辑寄存器号为地址、以物理寄存器号为数据的SRAM表格。每个逻辑寄存器占据SRAM中的一个条目(entry),该条目存储当前映射到的物理寄存器编号。

SRAM RAT的结构

考虑一个典型的RISC-V处理器,拥有32个整数逻辑寄存器(x0\simx31),物理寄存器文件包含128个物理寄存器(编号为p0\simp127)。SRAM RAT的基本结构是一个32条目的存储阵列,每条目存储一个7位的物理寄存器编号(log2128=7\lceil\log_2 128\rceil = 7位)。

基于SRAM的重命名映射表结构:以32个逻辑寄存器、128个物理寄存器为例
基于SRAM的重命名映射表结构:以32个逻辑寄存器、128个物理寄存器为例

SRAM RAT的读操作是一个标准的SRAM读取过程:将逻辑寄存器编号作为地址送入SRAM的地址译码器,对应条目中存储的物理寄存器编号通过位线(bit line)被读出。写操作也同样直接:当一条指令的目的寄存器被分配了新的物理寄存器时,将逻辑寄存器编号作为写地址、新的物理寄存器编号作为写数据,更新对应的SRAM条目。

地址译码器设计

RAT的地址译码器将5位逻辑寄存器编号译码为32条字线中的一条。对于多端口SRAM,每个读端口和每个写端口都需要一个独立的译码器——总共需要R+WR + W个5到32译码器(RR个读译码器和WW个写译码器)。

5到32译码器的标准实现是两级:第一级将5位地址分成2位和3位两组,分别译码为4条和8条线;第二级用4×8=324 \times 8 = 32个AND门将两组的输出交叉组合,生成32条字线信号。每个AND门为一个2输入NAND+反相器(约4T),32个AND门共约128T。

对于18R/6W的RAT,需要24个译码器,总晶体管数约24×128=3,07224 \times 128 = 3{,}072T。译码器延迟约2级逻辑门30\approx 30 ps。在总读取延迟(\sim200 ps)中,译码器仅占15%——位线放电和感应放大是主要瓶颈。

感应放大器设计

感应放大器(sense amplifier)是SRAM读取速度的关键组件。它检测位线上的微小电压差(通常仅50\sim100 mV),将其放大为全摆幅的逻辑电平输出。

对于RAT这种小型SRAM(仅32行),位线很短,放电速度快——位线上的电压差在字线激活后\sim30 ps就达到可检测的50 mV。因此RAT的感应放大器可以使用简单的交叉耦合锁存型设计(cross-coupled latch sense amplifier),面积仅约10\sim12T,速度约20\sim30 ps。

对于每个读端口的每一位数据(7位物理寄存器编号),需要一个独立的感应放大器。18个读端口×\times7位 = 126个感应放大器,总晶体管约126×121,500126 \times 12 \approx 1{,}500T。

但需要注意,对于使用单端读位线(8T位单元方案)的多端口设计,感应放大器可以简化为一个阈值检测器(仅需一个反相器),面积更小(2T/个),但速度可能略慢(需要更大的位线电压摆幅才能可靠翻转反相器)。

设计提示

在RISC-V中,逻辑寄存器x0被硬连线为零值。在SRAM RAT的实现中,x0的条目通常被固定映射到一个特殊的物理寄存器(如p0),该寄存器始终保存值0且不可被重命名覆盖。读取x0时,RAT返回p0;任何试图写入x0的指令,其目的寄存器重命名操作将被抑制——不分配新的物理寄存器,不更新RAT条目。这一优化节省了物理寄存器资源并简化了控制逻辑。

从电路实现的角度看,SRAM RAT的核心存储单元是标准的6T SRAM位单元。对于一个32条目×\times7位的SRAM RAT,总共需要32×7=22432 \times 7 = 224个6T SRAM位单元来存储映射数据。然而,这只是存储面积——RAT的实际面积主要由端口数量决定,因为多端口SRAM的面积随端口数的平方增长。

6T SRAM位单元回顾

标准6T SRAM位单元由两个交叉耦合的CMOS反相器(4个晶体管,2个PMOS + 2个NMOS)和一对存取晶体管(2个NMOS)组成。两个反相器形成双稳态锁存器,存储一位数据。存取晶体管由字线(word line, WL)控制,连接锁存器的存储节点QQQ\overline{Q}到位线(bit line)BLBLBL\overline{BL}

  • 读操作:位线对BL/BLBL/\overline{BL}首先被预充电到VDDV_{DD}。字线WL拉高后,存取管导通,存储节点通过存取管向位线放电。假设Q=0Q=0Q=1\overline{Q}=1,则BLBL通过存取管和下拉NMOS形成放电路径,BLBL电压略低于BL\overline{BL}。感应放大器(sense amplifier)检测BLBLBL\overline{BL}的微小电压差(通常50\sim100 mV),将其放大为完整的逻辑电平。

  • 写操作:写驱动器(write driver)将待写入的数据和其补码分别驱动到BLBLBL\overline{BL}上。字线WL拉高后,存取管导通,位线上的强驱动覆盖锁存器内部的弱保持——迫使锁存器翻转到目标状态。写操作要求写驱动器的驱动能力强于锁存器的保持能力,这对晶体管尺寸比(β\beta ratio)有严格约束。

在RAT的应用场景中,6T位单元的关键限制在于:每个6T单元只有一对位线,即只支持一个读/写端口。要实现多端口访问,必须增加额外的存取管和位线,这正是多端口SRAM面积爆炸的根源。

多端口SRAM位单元的晶体管结构

要为6T位单元增加一个额外的读端口,需要增加2个晶体管:一个存取NMOS(由独立字线控制)和一个用于隔离读取的NMOS(其栅极连接到存储节点QQQ\overline{Q},漏极连接到独立的读位线RBLRBL)。更常见的方案是8T位单元——在6T核心之外增加一个由两个NMOS组成的独立读端口:

  • 底部NMOS的栅极连接存储节点Q\overline{Q},漏极连接到读位线RBLRBL的下拉路径

  • 顶部NMOS的栅极连接读字线RWLRWL,控制读端口的使能

  • RWLRWL有效且Q=1\overline{Q}=1时,两个NMOS均导通,RBLRBL被拉低(读出"0")

  • Q=0\overline{Q}=0时,底部NMOS关断,RBLRBL保持预充电高电平(读出"1")

这种8T结构的优势在于读端口与写端口完全解耦——读操作不会干扰存储节点的稳定性(不存在读扰动问题),且读端口是单端的(只需一条读位线RBLRBL而非差分对),进一步节省面积。

对于需要RR个读端口和WW个写端口的多端口SRAM位单元,晶体管计数的一般公式为:

Tcell(R,W)=6+2R+2WT_{\text{cell}}(R, W) = 6 + 2R + 2W

其中:6个晶体管为核心锁存器和一对读写共用端口,每个额外读端口需要2个晶体管(读使能管+存储节点感应管),每个额外写端口需要2个晶体管(一对存取管,连同差分位线对)。注意,第一个写端口已包含在6T核心中,因此(eq:ch25-multiport-transistors)WW应理解为总写端口数(含6T核心自带的那个)。更精确的公式为:

Tcell=4锁存+2×W写端口+2×R读端口T_{\text{cell}} = 4_{\text{锁存}} + 2 \times W_{\text{写端口}} + 2 \times R_{\text{读端口}}

多端口位单元的面积增长不仅来自晶体管数量的增加,更关键的是布线面积的增长。每个读端口需要一条读位线RBLRBL和一条读字线RWLRWL穿越位单元;每个写端口需要一对写位线WBL/WBLWBL/\overline{WBL}和一条写字线WWLWWL。这些金属线在位单元内部纵横交错,导致面积大致按(R+W)2(R+W)^2的关系增长(而非简单的(R+W)(R+W)线性增长),因为水平和垂直方向的布线都需要扩展。

读端口与写端口的需求

SRAM RAT的端口需求直接取决于处理器的重命名宽度。考虑一个6-wide超标量处理器(每周期最多重命名6条指令),我们来详细分析所需的端口数量。

读端口需求

每条指令最多有2个源操作数(对于RISC-V R型指令),每个源操作数都需要查找RAT以获取对应的物理寄存器编号。因此:

读端口数=Nrename×2=6×2=12\text{读端口数} = N_{\text{rename}} \times 2 = 6 \times 2 = 12

但这是最坏情况。实际上并非每条指令都有2个源寄存器操作数——有些指令只有1个源操作数(如LUI、AUIPC),有些指令没有源寄存器操作数(如JAL)。尽管如此,硬件设计必须为最坏情况做好准备,因此需要提供12个读端口。

写端口需求

每条指令最多有1个目的操作数,需要在RAT中更新映射关系。因此:

写端口数=Nrename×1=6×1=6\text{写端口数} = N_{\text{rename}} \times 1 = 6 \times 1 = 6

同样,这假设每条指令都有目的寄存器。存储指令(如SW)和分支指令(如BEQ)没有目的寄存器,不需要写入RAT。但硬件设计仍需提供6个写端口以覆盖最坏情况。

旧映射读端口

除了上述读端口外,还需要额外的读端口来读取旧映射(old mapping)。当一条指令将逻辑寄存器rdr_d重新映射到新的物理寄存器pnewp_{\text{new}}时,需要记录下rdr_d原来映射到的旧物理寄存器poldp_{\text{old}}。这个旧映射信息将被写入ROB,在指令提交时用于释放poldp_{\text{old}}。因此需要额外的读端口:

旧映射读端口数=Nrename×1=6\text{旧映射读端口数} = N_{\text{rename}} \times 1 = 6
::: warning 性能分析 1 — 6-wide处理器的SRAM RAT端口总需求

综合上述分析,一个6-wide超标量处理器的SRAM RAT需要:

  • 源操作数读端口6×2=126 \times 2 = 12

  • 旧映射读端口6×1=66 \times 1 = 6

  • 写端口6×1=66 \times 1 = 6

  • 总计:12 + 6 = 18个读端口 + 6个写端口 = 18R/6W

然而,旧映射的读取可以与写入复用:在写入新映射之前先读出旧映射,再执行写入。如果SRAM支持读后写(read-before-write)操作,则旧映射读端口和写端口可以合并为6个读写端口(RW端口)。这样总端口数变为:

  • 纯读端口:12个

  • 读写端口(先读旧值再写新值):6个

  • 总计12R + 6RW

在物理实现上,一个RW端口的面积约等于一个读端口加一个写端口。因此等效端口面积仍为12+6×2=2412 + 6 \times 2 = 24端口——这对一个仅有32条目的小型SRAM来说是极为庞大的。

:::

多端口SRAM的面积增长是RAT设计的核心挑战。正如25.1.1 节节所述,6T SRAM位单元每增加一个读端口需要增加2个晶体管,每增加一个写端口也需要增加2个晶体管。对于一个RR个读端口、WW个写端口的SRAM位单元,所需的晶体管数约为:

晶体管数/位=6+2R+2W\text{晶体管数/位} = 6 + 2R + 2W

对于18R/6W的极端情况,每个位单元需要6+2×18+2×6=546 + 2 \times 18 + 2 \times 6 = 54个晶体管——是标准6T位单元的9倍。

6T vs 54T(18R/6W)SRAM位单元的面积对比示意
6T vs 54T(18R/6W)SRAM位单元的面积对比示意

更重要的是,大量端口导致位线负载急剧增加。每条读位线RBLRBL上挂着32个行的存取管(对于32条目的RAT),每个存取管贡献约0.3\sim0.5 fF的漏极电容。18条读位线的总负载为:

CBL-total=18×32×0.4fF230fFC_{\text{BL-total}} = 18 \times 32 \times 0.4\,\text{fF} \approx 230\,\text{fF}

如此高的位线负载直接导致读取速度下降。位线的放电时间与RCRC时间常数成正比,而CC随端口数和行数的乘积增长。这就是为什么多端口SRAM的读取延迟比端口数的线性增长更加严重——它受位线电容的R×CR \times C延迟支配。

端口配置位单元晶体管相对面积估算延迟
2R/1W(基准)12T1.0×1.0\times\sim60 ps
4R/2W18T2.0×2.0\times\sim85 ps
8R/4W30T5.2×5.2\times\sim130 ps
12R/6W42T9.5×9.5\times\sim180 ps
18R/6W54T14.0×14.0\times\sim220 ps

不同端口配置的SRAM RAT面积和延迟估算(32条目×\times7位,7 nm工艺)

从表表 25.1可以看出,端口数从2R/1W增加到18R/6W时,面积膨胀了14倍,延迟增加了近4倍。在7 nm工艺下,18R/6W的SRAM RAT的读取延迟约220 ps,这在3 GHz时钟频率下(周期333 ps)已经占用了超过65%的时钟周期——仅留下约113 ps给地址译码、旁路逻辑和时钟偏移(clock skew),这使得时序收敛变得极为困难。

端口数优化策略

实际处理器设计中采用多种技术来降低RAT的有效端口数。以下逐一分析每种技术的具体实现和权衡。

Bank分区

将RAT的32个条目按照逻辑寄存器编号的某些位分成多个bank。最简单的分区方式是按最低位分成奇偶两个bank:编号为偶数的寄存器(x0, x2, x4, ...)放入Bank 0,奇数寄存器放入Bank 1。每个bank包含16个条目,端口数降低为原来的一半左右。

对于6-wide处理器的18R/6W需求,2-bank分区后每个bank的理想端口数为9R/3W。但这要求同一周期内的指令恰好均匀地访问两个bank——实际上经常出现bank冲突:多条指令同时访问同一个bank。假设寄存器访问均匀分布,6条指令的12个源操作数读请求中,某个bank被访问超过其端口数的概率可用二项分布计算。

对于2-bank、每bank 9个读端口的配置,bank冲突概率极低(<0.1%<0.1\%);但如果为了节省面积将每bank端口降低到6R/3W,则冲突概率显著上升。当bank冲突发生时,冲突的指令必须等待一个周期重试,导致重命名吞吐率下降。

更激进的4-bank分区(每bank 8个条目)可以将端口降低到5R/2W级别,但bank冲突率急剧上升,且bank选择逻辑和仲裁器的复杂度也相应增加。实际设计中,RAT的bank分区通常不超过2个bank——因为RAT本身只有32个条目,过度分区后每个bank的条目数太少,bank冲突率过高。

时分复用(TDM)

时分复用利用一个时钟周期内的多个时钟相位来分时服务不同的指令。具体实现方式如下:

  1. 在处理器核心内部生成一个2倍频时钟(2×\times internal clock)。外部时钟周期为TT,内部半周期为T/2T/2

  2. 前半周期(ϕ1\phi_1)处理指令I0I2I_0 \sim I_2的重命名:读取3条指令的6个源操作数映射(6R),读取3个旧映射(3R),写入3个新映射(3W)。总需求:9R/3W。

  3. 后半周期(ϕ2\phi_2)处理指令I3I5I_3 \sim I_5的重命名:同样9R/3W。

  4. SRAM只需要提供9R/3W端口,而非18R/6W。

时分复用的代价:

  • 内部时钟频率加倍T/2T/2的半周期必须容纳SRAM读取+旁路逻辑的全部延迟。如果外部频率为3 GHz(333 ps),则每个半周期仅有167 ps——这对SRAM的访问速度要求极高。

  • 功耗增加:2倍频时钟的分配和驱动增加约30%\sim50%的时钟树功耗。SRAM的预充电和求值也因频率加倍而功耗加倍。

  • 两相之间的依赖处理I3I_3可能依赖I0I2I_0 \sim I_2的重命名结果。由于I0I2I_0 \sim I_2ϕ1\phi_1已完成写入,I3I_3ϕ2\phi_2可以直接从SRAM读到最新值——但前提是ϕ1\phi_1的写入在ϕ2\phi_2的读取之前完成,这需要精确的时序控制。

  • 时钟偏移敏感:两相之间的timing margin非常紧张,对时钟树的偏移(skew)和抖动(jitter)极为敏感。

复制(Replication)

复制方案维护多份RAT副本,每份服务一部分指令的读取需求,但所有副本共享写入更新。以2份复制为例:

  • RAT-A服务I0I2I_0 \sim I_2的源操作数读取:6R端口(3条指令×\times2个源)+ 3R旧映射端口 = 9R。

  • RAT-B服务I3I5I_3 \sim I_5的源操作数读取:同样9R。

  • 两份RAT都需要6个写端口:因为6条指令的映射更新必须同时写入两份RAT以保持一致性。

  • 每份RAT的端口配置:9R/6W。位单元晶体管数:6+2×9+2×6=366 + 2\times 9 + 2\times 6 = 36T。

与全端口方案(18R/6W,54T/位)相比,复制方案的每份RAT为36T/位,两份总共2×36=722 \times 36 = 72T/位——晶体管总数反而增加了(72T vs 54T)。但关键的收益在于面积:多端口SRAM的面积与端口数的平方成正比,因此2×(9+6)2=4502 \times (9+6)^2 = 450远小于(18+6)2=576(18+6)^2 = 576。更重要的是,每份RAT的读端口减少后,位线负载降低,访问速度显著提升

读-修改-写合并

利用SRAM的读后写(read-before-write)特性,旧映射的读取和新映射的写入可以在同一个端口的同一周期内完成:先在时钟上升沿触发读操作,读出旧映射值;然后在时钟下降沿触发写操作,写入新映射值。这样6个旧映射读端口和6个写端口可以合并为6个RW端口,等效端口数从18R/6W降低到12R/6RW。

从位单元的角度看,一个RW端口需要一对位线(读写共用)和一条字线,与一个纯写端口的晶体管数相同(2个存取管)。因此12R/6RW的位单元需要4+2×12+2×6=404 + 2\times 12 + 2\times 6 = 40T——相比54T节省了26%的晶体管。但RW端口对时钟相位的精确控制要求更高:读取窗口和写入窗口必须互不干扰,两者之间的timing margin通常只有30\sim50 ps。

设计权衡 1 — SRAM RAT的端口数与时序的权衡

在高性能处理器设计中,RAT的端口数直接影响重命名阶段的关键路径延迟。设计师面临的核心权衡是:

  • 全端口方案:提供完整的18R/6W端口,单周期完成所有重命名操作。优点是控制逻辑简单,吞吐率最高;缺点是面积大(54T/位)、延迟高(\sim220 ps读取)。

  • 分相方案:将时钟周期分为2相,每相9R/3W(30T/位)。面积降至约40%,但内部时钟频率加倍,功耗增加约30%\sim50%,且相间需要旁路逻辑和精确时序控制。

  • 复制方案:复制2份,每份9R/6W(36T/位)。总面积约为全端口方案的65%\sim70%,读取延迟降低约20%(得益于更少的位线负载),但需要双写逻辑和一致性维护。

  • RW合并方案:12R/6RW(40T/位)。面积降至约70%,但对时钟相位精度要求高,且旧映射的读取延迟被限制在半个时钟周期内。

  • 混合方案:复制 + RW合并:每份RAT为6R/3R+3RW = 6R/3RW(26T/位),总面积约为全端口方案的45%。这是目前性能与面积的最佳平衡点。

现代高性能处理器(如ARM Cortex-A77、Apple M系列)通常采用复制方案复制+分相的混合方案,在面积、时序和功耗之间取得最佳平衡。

方案位单元T数相对面积读延迟控制复杂度
全端口 18R/6W54T1.00×1.00\times\sim220 ps
RW合并 12R/6RW40T0.70×0.70\times\sim190 ps
2相TDM 9R/3W30T0.40×0.40\times\sim150 ps^*
2份复制 9R/6W×\times236T×\times20.65×0.65\times\sim170 ps
复制+RW 6R/3RW×\times226T×\times20.45×0.45\times\sim150 ps
^*半周期约167 ps内需完成SRAM读取+旁路逻辑。

各种端口优化方案的综合比较(6-wide处理器,32条目RAT)

多条指令同时重命名时的处理

SRAM RAT面临的最大逻辑复杂度挑战不在于存储本身,而在于同一时钟周期内多条指令之间的数据相关性。当6条指令在同一周期被重命名时,它们之间可能存在RAW和WAW相关性——如果不加处理,后面的指令将从RAT中读到过时的映射信息。

RAW相关性

考虑以下两条在同一周期被重命名的指令:

riscv
add  x5, x1, x2     # I1: x5 <- x1 + x2, x5 被重命名为 p40
  sub  x7, x5, x3     # I2: x7 <- x5 - x3, x5 应该读到 p40

指令I1将逻辑寄存器x5重新映射到物理寄存器p40。指令I2的源操作数x5需要读取I1建立的新映射p40,而不是RAT中x5的旧映射。然而,在同一时钟周期内,I1的写入和I2的读取同时发生——如果I2直接从SRAM中读取,将得到旧映射。

解决方案是在RAT的输出端增加旁路逻辑(bypass logic)。具体来说:

  1. 在同一周期内,将所有指令的目的寄存器编号与所有后续指令的源寄存器编号进行比较。

  2. 如果匹配,则后续指令的源操作数应使用前面指令的目的物理寄存器编号(旁路值),而不是从SRAM中读取的值。

  3. 如果多条前面的指令都写同一个逻辑寄存器(WAW情况),应使用程序顺序最新的那条指令的映射。

SRAM RAT的旁路逻辑:同一周期内I1写入x5的新映射p40,I2和I3读取x5时通过旁路获取p40
SRAM RAT的旁路逻辑:同一周期内I1写入x5的新映射p40,I2和I3读取x5时通过旁路获取p40

WAW相关性

当同一周期内多条指令写同一个逻辑寄存器时,产生WAW相关性:

riscv
add  x5, x1, x2     # I1: x5 重命名为 p40
  sub  x5, x3, x4     # I3: x5 重命名为 p55
  or   x7, x5, x6     # I4: x5 应该读到 p55(I3的映射,更新)

I1和I3都写x5,I4读x5。I4应该获取程序顺序中最后一条x5的指令(I3)的映射结果p55,而不是I1的p40。实现这一点需要优先级编码器

  1. 将I4的源寄存器编号与同周期内所有先行指令(I1、I2、I3)的目的寄存器编号逐一比较。

  2. 如果有多个匹配,选择程序顺序最靠后的那个(即slot编号最大的匹配)。

  3. 这需要一个优先级从高到低递减的编码器——slot 5的匹配优先于slot 4,slot 4优先于slot 3,以此类推。

对于一个6-wide处理器,Ik_kk=1,2,,6k = 1, 2, \ldots, 6)的每个源操作数需要与I1_1到Ik1_{k-1}的所有目的寄存器进行比较。总比较次数为:

比较器数量=k=262×(k1)=2×(1+2+3+4+5)=30\text{比较器数量} = \sum_{k=2}^{6} 2 \times (k-1) = 2 \times (1 + 2 + 3 + 4 + 5) = 30

每个比较器比较5位(log232\lceil\log_2 32\rceil)的逻辑寄存器编号。5位比较器的硬件实现是5个XNOR门加一个5输入AND门:每位用XNOR检测是否相等(输出1表示该位匹配),然后AND门将5位的匹配结果合并——只有5位全部匹配时,AND输出为1。在7 nm工艺下,一个5位比较器的面积约为40\sim60个晶体管(每个XNOR 6T + 5输入AND约10T),延迟约30\sim40 ps。

30个比较器的总晶体管数约为30×50=1,50030 \times 50 = 1{,}500T。这虽然远少于SRAM位单元阵列的晶体管数(224位×\times54T = 12,096T),但旁路逻辑位于关键路径上,其延迟直接影响时钟频率。

优先级MUX链的详细结构

每条指令的每个源操作数需要一个优先级选择逻辑。以6-wide处理器中第6条指令(I5I_5)的源操作数rs1rs1为例,它需要与I0I4I_0 \sim I_4的5个目的寄存器编号进行比较,产生5个匹配信号m0,m1,,m4m_0, m_1, \ldots, m_4。当多个匹配信号为1时,必须选择程序顺序最新的(即编号最大的)匹配。

优先级选择可以用级联MUX链实现:

  1. 初始值为SRAM读出的映射psramp_{\text{sram}}

  2. 依次检查m0,m1,,m4m_0, m_1, \ldots, m_4(按程序顺序从早到晚)。

  3. 每个MUX:如果mj=1m_j = 1,选择IjI_j的目的物理寄存器pjp_j;否则保留前级结果。

  4. 最后一级MUX的输出即为最终结果——它自然选择了最新的匹配(因为后面的匹配会覆盖前面的选择)。

这种级联MUX链的延迟为k1k-1级MUX延迟。对于I5I_5的源操作数,需要5级MUX,每级约20\sim25 ps,总延迟为100\sim125 ps。加上比较器的30\sim40 ps,I5I_5的旁路路径总延迟为130\sim165 ps——这是所有指令中最长的旁路路径,决定了整个重命名阶段的关键路径。

树形优先级编码器优化

级联MUX链的延迟为O(N)O(N),对于宽重命名处理器(N8N \geq 8)不可接受。一种优化是使用树形优先级编码器将延迟降低到O(logN)O(\log N)

  1. 将5个匹配信号分为两组:{m0,m1,m2}\{m_0, m_1, m_2\}{m3,m4}\{m_3, m_4\}

  2. 每组内部并行执行优先级选择(选出组内最新的匹配)。

  3. 第二级在两组的结果中选择——如果高编号组有匹配,选高编号组的结果;否则选低编号组的结果。

树形结构将5级MUX减少为log25=3\lceil\log_2 5\rceil = 3级,延迟从130 ps降低到约90 ps。代价是需要更多的MUX和比较逻辑——面积增加约40%。在实际设计中,当N6N \leq 6时,级联MUX链的延迟通常可以接受;当N8N \geq 8时,树形结构变得必要。

旁路逻辑对旧映射读取的影响

旁路逻辑不仅影响源操作数的读取,还影响旧映射的读取。当IkI_k读取rdr_d的旧映射时,如果I0Ik1I_0 \sim I_{k-1}中有指令写了同一个rdr_d,那么IkI_k应该读到的"旧映射"是Ik1I_{k-1}(或更早的同名写入者)写入的新映射,而非SRAM中的原始值。

例如:I0I_0将x5映射到p40,I3I_3将x5映射到p55。I3I_3的旧映射应该是p40(I0I_0建立的映射),而非SRAM中x5的原始映射。这意味着旧映射读取同样需要旁路逻辑——复杂度与源操作数的旁路逻辑完全相同(30个比较器 + 对应的优先级MUX)。

因此,旁路逻辑的总硬件开销为:

  • 源操作数旁路:30个比较器 + 30个优先级MUX链(2个源×\times15条旁路路径)

  • 旧映射旁路:15个比较器 + 15个优先级MUX链(1个旧映射×\times15条旁路路径)

  • 总计:45个5位比较器 + 45个优先级MUX链

6-wide重命名的旁路比较矩阵与优先级选择逻辑(以5个slot为示意)
6-wide重命名的旁路比较矩阵与优先级选择逻辑(以5个slot为示意)

旁路逻辑的时序影响

旁路比较和优先级选择位于重命名阶段的关键路径上。完整的关键路径为:

  1. SRAM读取:地址译码 + 位线感应 + 输出驱动 \approx 180\sim220 ps

  2. 比较器:5位XNOR + 5输入AND \approx 30\sim40 ps

  3. 优先级MUX:最多5级级联MUX选择 \approx 100\sim125 ps

  4. 最终MUX:选择SRAM输出或旁路输出 \approx 20\sim30 ps

总延迟约330\sim415 ps,在3 GHz(333 ps周期)下已经超出一个时钟周期。即使采用树形优先级编码器将MUX延迟优化到60\sim80 ps,总延迟仍为290\sim370 ps——刚好处于临界状态。

这就是为什么在实际设计中,重命名阶段通常需要2个流水级

  • Rename-1(流水级1):读取SRAM RAT(获取所有源操作数和旧映射的原始映射值),同时从空闲列表分配新的物理寄存器。这一级的延迟主要由SRAM读取决定,约180\sim220 ps。

  • Rename-2(流水级2):执行旁路比较和优先级选择,将最终的源物理寄存器编号和旧映射值输出到流水线寄存器。同时将新映射写入SRAM RAT。这一级的延迟主要由比较器 + 优先级MUX决定,约130\sim165 ps(级联方案)或90\sim120 ps(树形方案)。

将重命名拆分为2级使得每级的时序约束分别为\sim220 ps和\sim165 ps,均在333 ps的时钟周期预算内留有充足裕量。代价是增加了一级流水线延迟,导致分支误预测恢复的penalty增加1个周期、以及指令从取指到执行的总延迟增加1个周期。

SRAM读取与旁路逻辑的并行化

一种进一步优化时序的技巧是将SRAM读取与比较器计算并行化。比较器的输入是逻辑寄存器编号——这些编号在指令译码完成后就已确定,不需要等待SRAM读取结果。因此:

  1. 在Rename-1开始时,同时启动SRAM读取和比较器计算。

  2. SRAM读取约需200 ps,比较器计算约需35 ps——比较器远早于SRAM完成。

  3. 比较器结果被锁存,等SRAM结果到达后立即驱动MUX选择。

  4. 实际关键路径变为:max(TSRAM,TCMP)+TMUX=200+80=280\max(T_{\text{SRAM}}, T_{\text{CMP}}) + T_{\text{MUX}} = 200 + 80 = 280 ps。

通过这种并行化,重命名可以在单级流水线内完成(280 ps << 333 ps),但时序裕量仅53 ps——非常紧张。在实际5 nm/3 nm设计中,由于工艺变异(process variation)和电压/温度波动,53 ps的裕量通常不足以保证量产良率,因此大多数设计仍采用2级重命名流水线。

SRAM RAT的写入时序与一致性

RAT写入的时序涉及多个微妙的正确性问题。

先读后写语义

在同一周期内6条指令的重命名结果需要写回RAT。如果写入发生在周期末尾(如时钟下降沿),则下一周期的指令可以在时钟上升沿读到最新映射——这是一个标准的"写优先"(write-first)SRAM设计。但在同一周期内,后面指令的RAT写入不应影响前面指令的旧映射读取——这要求写入在逻辑上发生在读取之后,即"先读后写"语义。

实际实现中,SRAM RAT通常在时钟上升沿触发读取,在时钟下降沿(或上升沿的后半段)触发写入。旁路逻辑在读取完成后立即开始工作,同时写入操作也在进行中——两者在时间上重叠,因此不会增加额外延迟。

同一周期内多条指令写同一逻辑寄存器

当同一周期的6条指令中有多条写同一个逻辑寄存器时(WAW),RAT的最终状态应只反映程序顺序最后一条指令的映射。例如:

riscv
add  x5, x1, x2    # I0: x5 -> p40
  sub  x5, x3, x4    # I2: x5 -> p55
  or   x5, x6, x7    # I4: x5 -> p63

三条指令都写x5,RAT中x5的最终映射应为p63(I4的结果)。但SRAM只有一个物理条目用于存储x5的映射——如果三条写操作同时到达,需要一个写仲裁逻辑来确保只有最后一条指令的写入生效。

写仲裁的实现方式:

  1. 写使能屏蔽:对6个写端口的写使能信号进行预处理。如果多条指令写同一逻辑寄存器,只有slot编号最大的那条的写使能保持有效,其余被屏蔽。这需要(62)=15\binom{6}{2} = 15个5位比较器来检测写冲突,加上一个6输入优先级编码器。

  2. 最后写胜出:利用SRAM的物理特性——当多个写端口同时写同一地址时,时序上最后到达的写入会覆盖前面的。如果硬件保证高编号slot的写入在时序上晚于低编号slot,则自然实现"最后写胜出"。但这种依赖物理时序的方式不够稳健,在PVT(工艺、电压、温度)变化下可能失效。

方案1(写使能屏蔽)更为可靠,是标准做法。其额外延迟约30\sim40 ps(比较器+优先级编码器),可以与SRAM读取并行完成,不影响关键路径。

以下SystemVerilog代码展示了写仲裁逻辑的实现:

verilog
module rat_write_arbiter #(
  parameter WIDTH     = 6,
  parameter LREG_BITS = 5
)(
  input  logic [WIDTH-1:0]               wr_valid_in,
  input  logic [WIDTH-1:0][LREG_BITS-1:0] wr_lreg,
  output logic [WIDTH-1:0]               wr_valid_out
);
  // 对每个slot k,检查是否有更高编号的slot
  // 写同一逻辑寄存器。如果有,则屏蔽slot k的写入。
  always_comb begin
    for (int k = 0; k < WIDTH; k++) begin
      wr_valid_out[k] = wr_valid_in[k];
      for (int j = k+1; j < WIDTH; j++) begin
        if (wr_valid_in[j] &&
            wr_lreg[j] == wr_lreg[k]) begin
          wr_valid_out[k] = 1'b0;  // 被更高slot覆盖
          break;
        end
      end
    end
  end
endmodule

上述逻辑在综合时展开为(62)=15\binom{6}{2} = 15个5位比较器和6个优先级屏蔽链——与旁路逻辑的比较器可以共享硬件(因为它们比较的是相同的逻辑寄存器编号),进一步节省面积。

跨周期一致性

RAT的跨周期一致性要求:周期TT的写入结果必须在周期T+1T+1的读取中可见。这由SRAM的写时序保证:写入发生在周期TT的时钟下降沿,数据在周期T+1T+1的时钟上升沿稳定可读。但如果SRAM采用了流水线化的读取(如2级流水线读取),则写后读的可见性可能延迟到周期T+2T+2——这需要额外的旁路来"短路"流水线。

SRAM RAT的旁路逻辑实现

以下SystemVerilog代码展示了一个简化的4-wide SRAM RAT旁路逻辑的核心结构。该模块接收4条指令的源和目的寄存器编号,从SRAM中读取的原始映射,以及从Free List分配的新物理寄存器编号,输出经过旁路修正后的最终源物理寄存器编号。

verilog
module rat_bypass #(
  parameter RENAME_WIDTH = 4,
  parameter LREG_BITS   = 5,   // log2(32)
  parameter PREG_BITS   = 7    // log2(128)
)(
  // 每条指令的目的逻辑寄存器和分配的新物理寄存器
  input  logic [RENAME_WIDTH-1:0]               dst_valid,
  input  logic [RENAME_WIDTH-1:0][LREG_BITS-1:0] dst_lreg,
  input  logic [RENAME_WIDTH-1:0][PREG_BITS-1:0] dst_preg,
  // 每条指令的源操作数:从SRAM读出的原始映射
  input  logic [RENAME_WIDTH-1:0][1:0][PREG_BITS-1:0] src_from_sram,
  input  logic [RENAME_WIDTH-1:0][1:0][LREG_BITS-1:0] src_lreg,
  // 输出:经过旁路修正后的源物理寄存器编号
  output logic [RENAME_WIDTH-1:0][1:0][PREG_BITS-1:0] src_preg_out
);
  // 对每条指令 inst_k 的每个源操作数 src_s
  always_comb begin
    for (int k = 0; k < RENAME_WIDTH; k++) begin
      for (int s = 0; s < 2; s++) begin
        // 默认使用 SRAM 读出的映射
        src_preg_out[k][s] = src_from_sram[k][s];
        // 检查所有先行指令(program order: 0 < 1 < ... < k-1)
        // 从最近的先行指令开始检查(优先级最高)
        for (int j = k-1; j >= 0; j--) begin
          if (dst_valid[j] &&
              dst_lreg[j] == src_lreg[k][s]) begin
            src_preg_out[k][s] = dst_preg[j];
            break;  // 取程序顺序最新的匹配
          end
        end
      end
    end
  end
endmodule

需要注意的是,上述代码中的break语句在综合时会被展开为优先级MUX链。综合器将为每条指令的每个源操作数生成一组级联的比较器和MUX——对于4-wide设计,指令槽3的每个源操作数需要3个比较器和一个3选1优先级MUX;对于6-wide设计,指令槽5的每个源操作数需要5个比较器和一个5选1优先级MUX。这种级联结构是重命名阶段关键路径的主要贡献者。

旁路逻辑的综合后面积估算

对于6-wide处理器的旁路逻辑模块,综合后的典型面积和时序特征如下:

  • 比较器阵列:45个5位比较器,每个约50T,总计约2,250T。在7 nm工艺下占面积约150 μ\mum2^2

  • 优先级MUX:45组级联MUX,每组MUX选择7位数据(物理寄存器编号宽度)。总MUX晶体管数约为45×k×7×64,72545 \times \overline{k} \times 7 \times 6 \approx 4{,}725T(k2.5\overline{k} \approx 2.5为平均MUX级数,每级7位×\times6T/位)。面积约300 μ\mum2^2

  • 总面积:旁路逻辑约占450 μ\mum2^2,约为SRAM位单元阵列面积(32×\times7×\times54T \approx 12,096T \approx 800 μ\mum2^2)的56%。这意味着旁路逻辑的面积开销与SRAM存储本身处于同一数量级——重命名RAT并非一个简单的查表结构,其组合逻辑开销非常显著。

旁路逻辑中的特殊情况处理

实际设计中,旁路逻辑还需要处理多个特殊情况:

  1. x0寄存器:RISC-V中x0固定为零。如果源操作数是x0,不需要执行旁路查找——直接返回p0(固定的零值物理寄存器)。这可以通过在比较器输入端屏蔽x0来实现。

  2. 无目的寄存器的指令SWBEQ等指令没有目的寄存器,其dst_valid位为0。旁路逻辑必须检查此位——无效的目的寄存器不应参与比较。

  3. 同一指令的两个源操作数相同:如ADD x3, x5, x5,两个源操作数都是x5。它们各自需要独立的旁路路径,但结果必须相同——这是旁路逻辑的隐式保证(两条路径做相同的比较和选择)。

  4. 源操作数与自身目的操作数相同:如ADD x5, x5, x3,rs1=x5且rd=x5。rs1应该读到x5的映射(在本指令修改之前),旁路逻辑中本指令的目的寄存器不会与本指令的源操作数产生旁路(因为只检查j<kj < k的先行指令),因此正确性天然保证。

Committed RAT与Speculative RAT

在实际处理器中,通常维护两份SRAM RAT:

  1. 推测RAT(Speculative RAT, sRAT):反映当前推测执行状态的映射关系。每条被重命名的指令都会更新sRAT——无论该指令是否最终提交。

  2. 提交RAT(Committed RAT, cRAT / Architectural RAT, aRAT):仅反映已提交指令的映射关系。只有当指令提交时,cRAT才被更新为该指令的映射结果。

cRAT的作用是在分支误预测或异常发生时提供一个已知正确的恢复点。当需要从误预测中恢复时,处理器可以将cRAT的内容复制到sRAT中,瞬间恢复到最后一个已提交的映射状态。这种恢复方式的延迟取决于RAT的复制速度——对于32条目×\times7位的SRAM,复制可以在1\sim2个周期内完成。

以下SystemVerilog代码展示了sRAT和cRAT的双表维护逻辑:

verilog
module dual_rat #(
  parameter N_ARCH   = 32,
  parameter PREG_BITS = 7,
  parameter COMMIT_W  = 6,
  parameter RENAME_W  = 6
)(
  input  logic                              clk, rst_n,
  // 重命名写入 sRAT
  input  logic [RENAME_W-1:0]              ren_valid,
  input  logic [RENAME_W-1:0][4:0]          ren_lreg,
  input  logic [RENAME_W-1:0][PREG_BITS-1:0] ren_preg_new,
  // 提交写入 cRAT
  input  logic [COMMIT_W-1:0]              cmt_valid,
  input  logic [COMMIT_W-1:0][4:0]          cmt_lreg,
  input  logic [COMMIT_W-1:0][PREG_BITS-1:0] cmt_preg,
  // 恢复信号
  input  logic                              recover,
  // sRAT 读端口(源操作数查询)
  input  logic [RENAME_W-1:0][1:0][4:0]     src_lreg,
  output logic [RENAME_W-1:0][1:0][PREG_BITS-1:0] src_preg
);
  // sRAT 和 cRAT 存储
  logic [PREG_BITS-1:0] srat [0:N_ARCH-1];
  logic [PREG_BITS-1:0] crat [0:N_ARCH-1];

  // sRAT 更新:重命名写入或恢复
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      // 复位:逻辑寄存器 i 映射到物理寄存器 i
      for (int i = 0; i < N_ARCH; i++)
        srat[i] <= i[PREG_BITS-1:0];
    end else if (recover) begin
      // 从 cRAT 恢复
      for (int i = 0; i < N_ARCH; i++)
        srat[i] <= crat[i];
    end else begin
      // 正常重命名写入(注意写仲裁:最后一条有效)
      for (int r = 0; r < RENAME_W; r++) begin
        if (ren_valid[r] && ren_lreg[r] != 5'd0)
          srat[ren_lreg[r]] <= ren_preg_new[r];
      end
    end
  end

  // cRAT 更新:仅在提交时
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int i = 0; i < N_ARCH; i++)
        crat[i] <= i[PREG_BITS-1:0];
    end else begin
      for (int c = 0; c < COMMIT_W; c++) begin
        if (cmt_valid[c] && cmt_lreg[c] != 5'd0)
          crat[cmt_lreg[c]] <= cmt_preg[c];
      end
    end
  end

  // sRAT 读取(组合逻辑,旁路逻辑由外部模块处理)
  always_comb begin
    for (int r = 0; r < RENAME_W; r++) begin
      for (int s = 0; s < 2; s++) begin
        src_preg[r][s] = srat[src_lreg[r][s]];
      end
    end
  end
endmodule

注意上述代码中的sRAT恢复操作(recover分支)在一个周期内将32个条目从cRAT复制到sRAT——这在RTL级是可行的(综合器会推断出32个并行的寄存器赋值),但在物理实现中需要32个并行写端口或使用多周期复制。实际设计通常在sRAT的SRAM位单元中增加一组"恢复写端口"或使用可一次性加载的寄存器文件(如基于锁存器而非SRAM的映射表)来实现单周期恢复。

cRAT的端口需求远低于sRAT。由于只在提交阶段更新,且提交是按程序顺序进行的,cRAT只需要与提交宽度匹配的写端口(如6个写端口)和在恢复时的批量读端口(可以通过多周期复制来分摊)。

cRAT到sRAT的恢复操作

当分支误预测被检测到时,sRAT中包含了误预测分支之后所有指令的错误映射更新。恢复过程需要将sRAT的内容回退到正确状态。使用cRAT恢复的步骤如下:

  1. 暂停重命名:立即停止向sRAT写入新的映射。

  2. 批量复制:将cRAT的32个条目×\times7位 = 224位的数据复制到sRAT中。如果sRAT有32个写端口(或采用行并行写入),可以在1个周期内完成复制。实际设计中,sRAT通常只有6个写端口,需要32/6=6\lceil 32/6 \rceil = 6个周期完成复制——这就是恢复延迟(recovery latency)。

  3. 恢复空闲列表:同时将空闲列表的头指针回退到cRAT对应的检查点位置。

  4. 恢复完成:sRAT恢复到最后一个已提交指令的映射状态,重命名阶段重新开始工作。

6个周期的恢复延迟对性能影响显著——在这段时间内,处理器前端无法重命名新指令,相当于浪费了6个周期的重命名带宽。为了降低恢复延迟,高性能处理器采用两种策略:

  • 宽写入端口:为sRAT提供额外的"恢复写入端口"(recovery write ports),这些端口仅在恢复时使用,正常运行时关闭。例如,增加16个恢复写端口,使得复制可以在32/16=2\lceil 32/16 \rceil = 2个周期内完成。代价是sRAT位单元的面积增加(多了16个写端口的存取管)。

  • 检查点RAT:在每个分支指令处保存sRAT的完整快照(checkpoint)。误预测时直接加载分支点的快照——恢复延迟为1个周期(单周期批量加载)。代价是需要为每个未决分支存储一份完整的RAT快照(32×7=22432 \times 7 = 224位/份),如果处理器支持32个未决分支,则检查点存储总量为32×224=7,16832 \times 224 = 7{,}168位 = 896字节。

检查点RAT的实现

检查点RAT(Checkpoint RAT)将sRAT的快照存储在一个独立的SRAM阵列中。该阵列的组织为:CC个检查点×\times32条目×\times7位,其中CC为支持的最大未决分支数。

  • 保存操作:当一条分支指令通过重命名阶段时,当前sRAT的全部32个条目被并行复制到检查点阵列中的一个空闲槽位。这需要32个并行读端口(读sRAT)和一个宽写端口(写检查点)。实际上,sRAT的32个条目可以一次全部读出(因为每个条目只有7位,总数据量仅224位),通过一条宽数据总线写入检查点阵列。

  • 恢复操作:误预测时,从检查点阵列中读出目标检查点的全部32×\times7 = 224位数据,通过宽写入端口一次写入sRAT。恢复延迟降低到1个周期

  • 释放操作:当分支指令提交(确认预测正确)时,对应的检查点槽位被释放。

检查点的面积开销为C×32×7=C×224C \times 32 \times 7 = C \times 224位。对于C=32C = 32个检查点,存储量为7,168位,使用标准6T SRAM实现约需42,000个晶体管——约为sRAT本身的3\sim4倍。这是一个显著的面积代价,但换来的1周期恢复延迟对高IPC处理器的分支误预测性能至关重要。

基于CAM的重命名映射表

基于CAM的重命名映射表采用了与SRAM方案完全不同的设计哲学。在CAM RAT中,映射关系不是通过地址索引来查找的,而是通过内容匹配来搜索的:以逻辑寄存器编号作为搜索关键字(search key),在所有条目中并行查找,匹配的条目返回对应的物理寄存器编号。

CAM RAT的一个重要应用场景是在使用ROB进行寄存器重命名的处理器中。在这种方案中,指令被分配到ROB表项时,ROB表项的编号就是该指令结果的物理位置。要查找某个逻辑寄存器的最新映射,需要在ROB中搜索最后一条写该逻辑寄存器的指令——这天然适合CAM的关联查找特性。

CAM RAT的结构

CAM RAT的每个条目包含两个字段:(1) 关键字字段(key field)存储逻辑寄存器编号;(2) 数据字段(data field)存储对应的物理寄存器编号。搜索操作将输入的逻辑寄存器编号与所有条目的关键字字段并行比较,匹配的条目输出其数据字段。

与SRAM RAT不同,CAM RAT的条目数不等于逻辑寄存器的数量。在ROB重命名方案中,CAM RAT的条目数等于ROB的大小(如256或384项);在独立的CAM RAT中,条目数等于物理寄存器的数量。每个条目记录一条"逻辑寄存器\to物理寄存器"的映射关系。

基于CAM的重命名映射表:以逻辑寄存器号为搜索关键字并行匹配所有条目
基于CAM的重命名映射表:以逻辑寄存器号为搜索关键字并行匹配所有条目

CAM匹配逻辑

CAM的核心电路是匹配线(match line)和搜索线(search line)。每个CAM条目中的每一位都有一个比较电路,将存储的位值与搜索线上的值进行比较。如果所有位都匹配,则该条目的匹配线被激活。

单个CAM位单元的结构

一个标准的NOR型CAM位单元包含以下组件:

  • 存储单元:一个标准的6T SRAM位单元,存储关键字的一位。

  • 比较晶体管:两对NMOS晶体管,将存储值QQQ\overline{Q}分别与搜索线SLSLSL\overline{SL}进行异或比较。当存储值与搜索值不匹配时,比较晶体管导通,将匹配线拉低。

  • 匹配线:一条横向贯穿整个条目所有位的预充电线。搜索前预充电为高电平;如果任何一位不匹配,匹配线被拉低(NOR逻辑)。

对于5位的逻辑寄存器编号(32个逻辑寄存器,log232=5\lceil\log_2 32\rceil = 5),每个CAM条目需要5个比较位单元串联在同一条匹配线上。匹配线的延迟与条目中比较位的数量成正比——位数越多,匹配线的负载电容越大,预充电和求值的速度越慢。

硬件描述 2 — NOR型CAM位单元的晶体管级结构

一个NOR型CAM位单元包含以下晶体管:

  • 6T SRAM核心:2个PMOS上拉管(MP1M_{P1}, MP2M_{P2})+ 2个NMOS下拉管(MN1M_{N1}, MN2M_{N2})组成交叉耦合反相器对,再加2个NMOS存取管(MA1M_{A1}, MA2M_{A2})由写字线WWLWWL控制。共6个晶体管。

  • 比较逻辑:4个NMOS晶体管(MC1M_{C1}, MC2M_{C2}, MC3M_{C3}, MC4M_{C4})组成2条下拉路径,连接在匹配线MLML和地VSSV_{SS}之间。共4个晶体管。

  • 总计:10个晶体管/位(10T CAM单元)

比较逻辑的详细电路连接如下:

  • 下拉路径1MC1M_{C1}MC2M_{C2}串联。MC1M_{C1}的栅极连接搜索线SLSLMC2M_{C2}的栅极连接存储节点Q\overline{Q}MC1M_{C1}的漏极连接匹配线MLMLMC2M_{C2}的源极接地。当SL=1SL = 1(搜索值为1)且Q=1\overline{Q} = 1(存储值Q=0Q = 0)时,两管都导通,MLML被拉低——表示搜索值1与存储值0不匹配

  • 下拉路径2MC3M_{C3}MC4M_{C4}串联。MC3M_{C3}的栅极连接搜索线补SL\overline{SL}MC4M_{C4}的栅极连接存储节点QQ。当SL=1\overline{SL} = 1(搜索值为0)且Q=1Q = 1(存储值为1)时,两管都导通,MLML被拉低——表示搜索值0与存储值1不匹配

真值表总结如下:

存储值QQ搜索值DDMLML下拉路径匹配?
00路径1:SL=0MC1SL=0 \to M_{C1}关断;路径2:Q=0MC4Q=0 \to M_{C4}关断匹配
01路径1:SL=1,Q=1SL=1, \overline{Q}=1 \to导通,MLML拉低不匹配
10路径2:SL=1,Q=1\overline{SL}=1, Q=1 \to导通,MLML拉低不匹配
11路径1:Q=0MC2\overline{Q}=0 \to M_{C2}关断;路径2:SL=0MC3\overline{SL}=0 \to M_{C3}关断匹配

从真值表可以看出,比较逻辑实现的是异或(XOR)功能:QD=1Q \oplus D = 1时不匹配,MLML被拉低;QD=0Q \oplus D = 0时匹配,MLML保持高电平。

搜索操作的时序

  1. 预充电阶段:匹配线MLML通过一个PMOS预充电管被拉高到VDDV_{DD}。预充电管由时钟信号控制,在每次搜索前的半周期完成预充电。

  2. 求值阶段:搜索数据DD被驱动到搜索线SLSLSL\overline{SL}上。对于一个5位关键字的CAM条目,5个位单元的比较逻辑并行工作。如果任何一位不匹配,对应的下拉路径导通,MLML被拉低。

  3. 检测阶段:匹配线末端的感应逻辑(通常是一个反相器或静态锁存器)检测MLML的电平。如果MLML仍为高,则该条目匹配;如果MLML被拉低,则不匹配。

匹配线MLML的关键电气参数:匹配线上挂着5个位单元的比较晶体管(每个位2条下拉路径的漏极节点连接到MLML),加上预充电管和末端感应逻辑的输入电容。MLML的总电容约为:

CML=B(2Cd,NMOS)+Cprecharge+Csense5×2×0.3fF+0.5fF+0.3fF3.8fFC_{ML} = B \cdot (2 \cdot C_{d,\text{NMOS}}) + C_{\text{precharge}} + C_{\text{sense}} \approx 5 \times 2 \times 0.3\,\text{fF} + 0.5\,\text{fF} + 0.3\,\text{fF} \approx 3.8\,\text{fF}

在7 nm工艺下,这对应的预充电延迟约10\sim15 ps,求值延迟(最坏情况:只有一位不匹配时的单路径放电)约20\sim30 ps。5位关键字的CAM匹配延迟在40\sim50 ps范围——显著快于SRAM的位线感应延迟,因为MLML的电容(3.8 fF)远小于SRAM位线的电容(\sim50 fF)。

然而,CAM的总延迟还包括搜索线驱动延迟和优先级编码器延迟(见下文),最终延迟通常高于SRAM RAT。

搜索线的驱动与功耗

搜索线SLSLSL\overline{SL}是CAM功耗的主要来源。每条搜索线纵向贯穿CAM的所有nn个条目,每个条目上连接一个比较晶体管的栅极。搜索线的总电容为:

CSL=nCg,NMOS+Cwiren0.2fF+nlpitchcwireC_{SL} = n \cdot C_{g,\text{NMOS}} + C_{\text{wire}} \approx n \cdot 0.2\,\text{fF} + n \cdot l_{\text{pitch}} \cdot c_{\text{wire}}

对于n=256n = 256个条目的CAM(ROB-based重命名),搜索线电容约为256×0.2+256×0.3128256 \times 0.2 + 256 \times 0.3 \approx 128 fF——这是匹配线电容的33倍。

每次搜索操作,搜索线从上一次搜索值切换到本次搜索值。在最坏情况下(所有位都需要翻转),搜索线的动态功耗为:

PSL=NsearchBCSLVDD2fP_{\text{SL}} = N_{\text{search}} \cdot B \cdot C_{SL} \cdot V_{DD}^2 \cdot f

以6-wide处理器为例:每周期12次搜索,5位关键字,CSL=128C_{SL} = 128 fF,VDD=0.75V_{DD} = 0.75 V,f=3f = 3 GHz:

PSL=12×5×128×1015×0.752×3×10912.96mWP_{\text{SL}} = 12 \times 5 \times 128 \times 10^{-15} \times 0.75^2 \times 3 \times 10^9 \approx 12.96\,\text{mW}

这仅是搜索线的功耗,还不包括匹配线预充电功耗和优先级编码器功耗。相比之下,SRAM RAT的每次读取只激活1条字线(32条目中的1条),位线放电功耗约为CAM的1/501/50。CAM的高功耗是其在移动处理器中被弃用的主要原因。

分段匹配线技术

为了降低CAM的功耗,分段匹配线(Segmented Match Line)将每个条目的匹配分为两个阶段:

  1. 第一阶段:仅比较关键字的低kk位(如低3位),使用一条短匹配线ML1ML_1ML1ML_1的电容仅为全匹配线的k/Bk/B(如3/5=60%3/5 = 60\%)。

  2. 第二阶段:只有ML1ML_1保持高电平(即低3位匹配)的条目才激活第二段比较——比较剩余的高BkB-k位。第二阶段使用独立的匹配线ML2ML_2

  3. 最终匹配ML1ML_1ML2ML_2都为高的条目才算匹配。

功耗节省的关键在于第二阶段只有少数条目被激活。对于5位关键字,低3位匹配的概率为1/23=1/81/2^3 = 1/8。因此第二阶段只有n/8n/8个条目需要激活,第二段搜索线和匹配线的功耗降低到原来的1/81/8。总功耗约为:

PsegmentedPstage1+12kPstage2=kBPfull+12kBkBPfullP_{\text{segmented}} \approx P_{\text{stage1}} + \frac{1}{2^k} \cdot P_{\text{stage2}} = \frac{k}{B} \cdot P_{\text{full}} + \frac{1}{2^k} \cdot \frac{B-k}{B} \cdot P_{\text{full}}

k=3k = 3B=5B = 5Psegmented(3/5+1/8×2/5)Pfull=0.65PfullP_{\text{segmented}} \approx (3/5 + 1/8 \times 2/5) \cdot P_{\text{full}} = 0.65 \cdot P_{\text{full}}——功耗节省约35%。代价是增加了第二阶段的门控逻辑和一级串行延迟。

匹配线的电气特性

匹配线是CAM性能的关键瓶颈。每个CAM条目的匹配线上挂着BB个比较位单元(BB为关键字宽度),每个位单元贡献一定的寄生电容和漏电流。匹配线的性能由以下因素决定:

  1. 预充电延迟:匹配线在每次搜索前必须预充电到高电平。预充电延迟与匹配线的总电容成正比:tprechargeCMLVDD/Iprecharget_{\text{precharge}} \propto C_{\text{ML}} \cdot V_{DD} / I_{\text{precharge}}。对于5位关键字的CAM,CMLC_{\text{ML}}约为20\sim30 fF。

  2. 求值延迟:搜索线驱动后,不匹配的位单元将匹配线拉低。最坏情况延迟发生在只有一位不匹配时(最慢的放电路径):tevalCMLVDD/Idischarget_{\text{eval}} \propto C_{\text{ML}} \cdot V_{DD} / I_{\text{discharge}}

  3. 搜索线功耗:每次搜索时,所有条目的搜索线都需要被驱动。对于一个nn条目的CAM,搜索线电容为CSLnC_{SL} \propto n——条目数越多,搜索功耗越大。这是CAM最大的功耗来源:

    Psearch=nBCSL-unitVDD2f P_{\text{search}} = n \cdot B \cdot C_{\text{SL-unit}} \cdot V_{DD}^2 \cdot f

    其中nn为条目数,BB为关键字位宽,CSL-unitC_{\text{SL-unit}}为单个位单元的搜索线电容,ff为搜索频率。

特性SRAM RATCAM RAT
寻址方式地址索引(直接寻址)内容匹配(关联寻址)
条目数= 逻辑寄存器数(32)= 物理寄存器数或ROB深度
位单元晶体管6T + 端口开销10T(含比较逻辑)
面积小(32条目)大(128\sim384条目)
读延迟\sim180 ps\sim250 ps(含匹配)
功耗较低较高(搜索线全驱动)
多匹配处理不需要需要优先级编码器
扩展性端口数是瓶颈条目数是瓶颈

CAM RAT vs SRAM RAT的关键特性比较

搜索端口的需求

CAM RAT的搜索端口需求与SRAM RAT的读端口需求类似。在一个6-wide处理器中,每条指令最多有2个源操作数需要搜索,因此需要6×2=126 \times 2 = 12个搜索端口。每个搜索端口对应一组独立的搜索线,这意味着每个CAM位单元需要12对搜索线和12对比较晶体管——位单元的晶体管数从10T(单搜索端口)膨胀到6+12×4=546 + 12 \times 4 = 54T(12搜索端口)。

以下逐步分析多搜索端口CAM位单元的构成:

  • 存储核心:6T SRAM锁存器(不变),用于存储关键字的一位。

  • 搜索端口kkk=0,1,,11k = 0, 1, \ldots, 11:需要4个NMOS晶体管——2个串联在匹配线MLkML_kVSSV_{SS}之间(路径1:QSLkQ \cdot SL_k),另2个也串联在MLkML_kVSSV_{SS}之间(路径2:QSLk\overline{Q} \cdot \overline{SL_k})。

  • 总晶体管数:6+12×4=546 + 12 \times 4 = 54T。

  • 每个搜索端口还需要一对搜索线SLk/SLkSL_k / \overline{SL_k}和一条匹配线MLkML_k穿越位单元。12个搜索端口共需24条搜索线和12条匹配线——总共36条额外金属线穿越每个位单元。

每个CAM条目有5个位单元(5位关键字),因此每个条目的晶体管数为5×54=2705 \times 54 = 270T(不含数据字段的SRAM)。加上7位数据字段(物理寄存器编号)的标准6T SRAM存储(7×6=427 \times 6 = 42T)和1位有效位(6T),每个条目总计约270+42+6=318270 + 42 + 6 = 318T。

对于256条目的CAM RAT,总晶体管数约256×31881,400256 \times 318 \approx 81{,}400T——远大于SRAM RAT的总晶体管数(存储阵列224×5412,100224 \times 54 \approx 12{,}100T + 旁路逻辑\sim7,000T \approx 19,100T)。

这与SRAM RAT的多端口问题本质相同:无论是SRAM的多读端口还是CAM的多搜索端口,位单元复杂度都随端口数线性增长,面积随端口数平方增长。因此,CAM RAT在面积和功耗上通常劣于SRAM RAT——这就是为什么现代高性能处理器大多采用SRAM RAT的原因。

CAM写入端口

除了搜索端口,CAM RAT还需要写入端口来添加新的映射条目。每条有目的寄存器的指令需要在CAM中写入一条新的映射记录(逻辑寄存器号\to物理寄存器号)。写入操作发生在重命名阶段,6-wide处理器需要6个写入端口。

CAM的写入操作与SRAM的写入操作类似——通过写字线和写位线对将数据写入指定条目的关键字字段和数据字段。但CAM的写入还需要设置有效位(valid bit)为1,表示该条目包含有效的映射。

写入的条目位置(即ROB中的slot编号)由指令分配逻辑决定,不需要CAM的搜索功能来确定。因此,写入操作的延迟与SRAM写入相同,不涉及匹配线操作。

多匹配时的优先级

CAM RAT面临的一个独特挑战是多匹配问题。同一个逻辑寄存器可能在CAM中有多个条目(对应不同时刻的映射),搜索时会产生多条匹配线同时被激活的情况。处理器需要选择其中最新的映射——即程序顺序中最后一条写该逻辑寄存器的指令所建立的映射。

基于条目位置的优先级

在ROB-based重命名方案中,ROB条目按照指令的程序顺序分配。因此,编号较大的ROB条目(考虑环形缓冲区的回绕)对应程序顺序中更晚的指令。优先级编码器的任务是从所有匹配的条目中选择编号最大的那个(即最新分配的条目)。

这个优先级编码器的复杂度为O(n)O(n),其中nn为CAM的条目数。对于一个256条目的ROB,需要一个256输入的优先级编码器——这在面积和延迟上都是显著的开销。实际实现中通常采用树形优先级编码器将延迟降低到O(logn)O(\log n)级逻辑门:

tpriority=log2ntgate=log2256tgate=8tgatet_{\text{priority}} = \lceil\log_2 n\rceil \cdot t_{\text{gate}} = \lceil\log_2 256\rceil \cdot t_{\text{gate}} = 8 \cdot t_{\text{gate}}

在7 nm工艺下,tgate15t_{\text{gate}} \approx 15 ps,因此优先级编码器延迟约为120 ps——这是在匹配线求值延迟之上额外增加的延迟。

树形优先级编码器的实现

256输入的树形优先级编码器按以下层次构建:

  1. 第1级:将256个匹配信号分为128对,每对用一个2选1 MUX和一个OR门处理。每对输出一个"组匹配"信号和对应的数据。128个2选1单元。

  2. 第2级:将128个组匹配信号再分为64对,重复上述逻辑。64个单元。

  3. 依此类推:共log2256=8\log_2 256 = 8级,每级的宽度减半。

  4. 总逻辑量128+64+32+16+8+4+2+1=255128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255个2选1单元。

每个2选1单元的功能是:如果高编号的输入有匹配,选择高编号的数据;否则选择低编号的数据(如果有匹配)。这是一个标准的"任意匹配选择"(find-last)电路。

对于每个搜索端口,都需要一个独立的256输入优先级编码器。6-wide处理器有12个搜索端口,因此需要12×255=3,06012 \times 255 = 3{,}060个2选1单元——面积开销非常显著,约为CAM存储阵列本身的30%\sim50%。

环形缓冲区的回绕处理

当ROB被实现为环形缓冲区时,条目编号的"最新"含义变得复杂。考虑ROB头指针(head)为200、尾指针(tail)为50的情况:编号在200\sim255和0\sim50之间的条目是有效的,其中50是最新分配的,200是最旧的。简单的数值比较("编号最大")将错误地选择255而非50。

解决方案有两种:

  1. 序列号比较:为每个条目分配一个单调递增的序列号(epoch counter),用序列号而非条目编号来确定新旧关系。代价是增加了序列号的存储位数。对于一个256条目的ROB,如果使用16位序列号,每个条目增加16位存储,总额外存储量为256×16=4,096256 \times 16 = 4{,}096位。序列号溢出时需要全局重编号(retagging),这在现代设计中通常采用足够宽的序列号(如32位)来避免。

  2. 分段优先级:将匹配结果分为两段——从tail到buffer末尾、从buffer起始到head。先在tail所在的段中查找最新匹配;如果该段没有匹配,再在另一段查找。

分段优先级的具体实现如下:

  1. 使用tail指针将256个条目的匹配信号分成两个半弧(half-arc):

    • 上半弧:从tail+1到buffer末尾(条目255)的匹配信号

    • 下半弧:从条目0到tail的匹配信号

    注意:当ROB未发生回绕时(head << tail),只有一个连续的有效区间[head, tail],不需要分段。

  2. 在每个半弧内部独立运行一个优先级编码器,选择该半弧中编号最大的匹配。

  3. 最终选择逻辑:如果下半弧(包含tail所在的段,即最新分配的条目)有匹配,选择下半弧的结果;否则选择上半弧的结果。

  4. 通过一个回绕标志位(wrap bit)来判断当前是否处于回绕状态。当tail从255递增到0时,wrap bit翻转。

这种分段方案增加了一级MUX延迟(约15\sim20 ps),但避免了序列号的额外存储开销。在实际设计中,分段方案更为常用,因为它不增加CAM的存储面积。

CAM RAT的无效化

当指令被提交(commit)或被清除(flush)时,对应的CAM条目需要被无效化(invalidation)。这通过将条目的有效位(valid bit)清零来实现。无效化后,该条目在后续搜索中不再参与匹配。

在分支误预测恢复时,需要将分支指令之后所有指令的CAM条目全部无效化。对于一个256条目的ROB,这意味着在最坏情况下需要清除多达255个条目的有效位——如果逐个清除,需要多个时钟周期;如果使用批量无效化(通过比较序列号范围),可以在1\sim2个周期内完成,但需要额外的比较逻辑。

批量无效化的硬件实现

批量无效化的核心思想是:为每个CAM条目增加一个年龄比较器(age comparator),将条目的序列号与恢复目标的序列号进行比较。所有序列号大于目标的条目被标记为无效。

具体实现:

  1. 每个CAM条目存储一个WseqW_{\text{seq}}位的序列号SiS_i

  2. 恢复时,广播恢复目标序列号StargetS_{\text{target}}到所有条目。

  3. 每个条目执行比较:如果Si>StargetS_i > S_{\text{target}}(考虑回绕),则validi0\text{valid}_i \leftarrow 0

  4. 比较逻辑为一个WseqW_{\text{seq}}位的大小比较器——对于8位序列号(足够覆盖256条目的ROB),每个比较器约需16\sim20个晶体管。

  5. 256个条目共需256×205,120256 \times 20 \approx 5{,}120个额外晶体管。

  6. 所有比较器并行工作,批量无效化在1个周期内完成(比较器延迟约40 ps \ll 时钟周期)。

这种批量无效化是CAM RAT相比SRAM RAT的一个恢复延迟优势:SRAM RAT需要从检查点复制数据(1\sim6周期),而CAM RAT只需广播一个序列号即可完成恢复。

CAM RAT的SystemVerilog建模

以下代码展示了一个简化的CAM RAT搜索逻辑,用于说明其工作原理:

verilog
module cam_rat_search #(
  parameter N_ENTRIES  = 256,
  parameter LREG_BITS  = 5,
  parameter PREG_BITS  = 7,
  parameter SEQ_BITS   = 8
)(
  input  logic [LREG_BITS-1:0]           search_key,
  input  logic [N_ENTRIES-1:0]           valid,
  input  logic [N_ENTRIES-1:0][LREG_BITS-1:0] cam_key,
  input  logic [N_ENTRIES-1:0][PREG_BITS-1:0] cam_data,
  input  logic [N_ENTRIES-1:0][SEQ_BITS-1:0]  cam_seq,
  output logic                           found,
  output logic [PREG_BITS-1:0]           result
);
  // 第一步:并行匹配
  logic [N_ENTRIES-1:0] match;
  always_comb begin
    for (int i = 0; i < N_ENTRIES; i++) begin
      match[i] = valid[i] && (cam_key[i] == search_key);
    end
  end

  // 第二步:从所有匹配中选序列号最大的
  always_comb begin
    found  = |match;  // 至少一个匹配
    result = '0;
    logic [SEQ_BITS-1:0] max_seq;
    max_seq = '0;
    for (int i = 0; i < N_ENTRIES; i++) begin
      if (match[i] && (cam_seq[i] > max_seq)) begin
        max_seq = cam_seq[i];
        result  = cam_data[i];
      end
    end
  end
endmodule

注意:上述代码中的序列号比较(cam_seq[i] > max_seq)在综合时会展开为一个256输入的树形优先级选择网络。由于不需要严格的find-last语义(我们选择的是序列号最大的条目),树形比较的延迟为O(logN×Wseq)O(\log N \times W_{\text{seq}})级门。

CAM RAT的功耗问题

CAM的搜索操作是其最大的功耗来源。每次搜索时,搜索线需要驱动所有条目中的比较晶体管——对于一个256条目、5位关键字的CAM RAT,一次搜索涉及256×5×2=2,560256 \times 5 \times 2 = 2{,}560个比较晶体管的切换。在6-wide处理器中,每周期执行12次搜索(12个源操作数),总切换次数为12×2,560=30,72012 \times 2{,}560 = 30{,}720次/周期。

相比之下,SRAM RAT每次读取只涉及被选中行的位线放电——32条目中只有1行被激活,功耗正比于数据宽度(7位)而非条目数。因此SRAM RAT的每次读取功耗约为CAM RAT的1/(256/32×5/7)1/571/(256/32 \times 5/7) \approx 1/57——这就是为什么功耗敏感的移动处理器几乎无一例外地选择SRAM RAT的原因。

为了降低CAM的搜索功耗,除了前文讨论的分段匹配线技术外,还可以采用以下技术:

  • 选择性搜索线预充电:根据搜索关键字的部分位对条目进行预筛选,只对可能匹配的条目子集驱动搜索线。例如,如果搜索关键字的最高位为1,则只需对关键字最高位存储为1的条目驱动搜索线——这可以通过在每个条目上增加一个预筛选逻辑来实现。平均可节省50%的搜索线切换功耗。

  • 低电压搜索线(Low-Swing Search Line):降低搜索线的电压摆幅,例如从VDDV_{DD}降到VDD/2V_{DD}/2。动态功耗与V2V^2成正比,摆幅减半可节省75%的搜索线功耗。代价是比较晶体管的栅极驱动降低,需要更灵敏的匹配检测电路,或使用增强型比较电路(如差分放大器型比较器)。

  • 条件搜索(Conditional Search):在实际工作负载中,并非每条指令都需要搜索CAM。存储指令的源操作数已经在译码阶段确定,可以提前判断是否需要搜索。通过时钟门控关闭不必要的搜索端口,可以降低平均功耗(虽然最坏情况功耗不变)。

指标SRAM RAT (32×\times7)CAM RAT (256×\times12)
存储位单元数32×7=22432 \times 7 = 224256×(5+7+1)=3,328256 \times (5+7+1) = 3{,}328
每位单元晶体管54T (18R/6W)10T (单端口) \sim 54T (12搜索)
总存储晶体管\sim12,000T\sim33,000T \sim 180,000T
优先级编码器45个5位比较器+MUX(旁路逻辑)12个256输入树形编码器
读/搜索延迟\sim200 ps (SRAM+旁路)\sim290 ps (搜索+编码)
每次读/搜索功耗\sim0.05 pJ\sim2.5 pJ
每周期总功耗\sim1.5 mW\sim45 mW
恢复延迟1\sim6周期(取决于检查点方案)1周期(批量无效化)

CAM RAT vs SRAM RAT的量化比较(6-wide处理器,7 nm工艺)

从表表 25.4可以看出,CAM RAT在面积和功耗上全面劣于SRAM RAT——功耗相差约30倍,面积相差约3\sim15倍。CAM RAT唯一的优势是在分支恢复时,可以通过批量无效化在1个周期内完成恢复(只需将误预测分支之后的所有条目的valid位清零),而无需像SRAM RAT那样从检查点复制数据。但这一优势通常不足以抵消其巨大的面积和功耗劣势。

功耗的详细分解

为了更深入地理解两种方案的功耗差异,以下对各功耗成分进行分解分析(6-wide处理器,3 GHz,7 nm,VDD=0.75V_{DD} = 0.75 V):

功耗成分SRAM RATCAM RAT (256条目)
位线/搜索线切换\sim0.3 mW\sim13 mW
字线/匹配线驱动\sim0.2 mW\sim8 mW
感应放大/匹配检测\sim0.3 mW\sim5 mW
地址译码/优先级编码\sim0.2 mW\sim12 mW
旁路逻辑\sim0.4 mW\sim0 mW(无旁路)
静态漏电\sim0.1 mW\sim7 mW
总计\sim1.5 mW\sim45 mW

SRAM RAT vs CAM RAT功耗成分分解

CAM RAT的功耗主要来自搜索线切换(每次搜索驱动256个条目的搜索线)和优先级编码器(12个256输入的树形编码器)。相比之下,SRAM RAT的功耗主要来自旁路逻辑(45个比较器和MUX),存储阵列本身的功耗很低(仅32个条目,每次只激活1条字线)。

CAM RAT的应用场景

尽管CAM RAT在面积和功耗上劣势明显,它在以下特定场景中仍有应用价值:

  1. ROB-based隐式重命名:在某些低复杂度处理器中,ROB本身充当物理寄存器文件(指令结果存储在ROB条目中)。此时不需要独立的RAT——重命名通过在ROB中搜索最新的写入者来完成,这天然是一个CAM操作。这种方案的功耗劣势被ROB深度较小(如32\sim64条目)所缓解。

  2. CISC处理器的微码重命名:x86处理器的微码(micro-op)可能产生大量临时寄存器映射,这些映射的生命周期很短。CAM的动态添加/删除条目特性比SRAM的固定索引更适合这种场景。

  3. 学术研究和教学:CAM RAT的概念更直观("搜索最新映射"),在处理器架构教学中常作为入门方案介绍。

案例研究 1 — MIPS R10000的重命名设计

MIPS R10000(1996年)是最早采用显式重命名的商用处理器之一。它使用了一个SRAM映射表+活跃列表方案来实现重命名:

  • 映射表:一个32条目的SRAM表(Map Table),以逻辑寄存器号索引,存储当前映射的物理寄存器号。这是一个标准的SRAM RAT。

  • 活跃列表(Active List):类似于ROB的结构,记录每条指令的旧映射,用于恢复。

  • R10000是4-wide处理器,Map Table需要8R/4W端口(4×24 \times 2读 + 4×14 \times 1写)。

  • 为了降低端口数,R10000将整数和浮点寄存器的Map Table完全分离,各自独立维护。

  • 空闲列表:R10000使用一个FIFO管理64个物理寄存器中的空闲寄存器。分配宽度为4(每周期最多分配4个),释放宽度也为4。

  • 恢复机制:R10000支持4个未决分支的检查点。在每个分支指令处,保存Map Table的完整快照(32×6=19232 \times 6 = 192位/检查点)和空闲列表的头指针。误预测时在1个周期内恢复。

R10000的设计表明,即使在上世纪90年代的0.35 μ\mum工艺下,8R/4W的多端口SRAM对于32条目的表来说也是可接受的。今天的6\sim8-wide处理器面临更大的端口压力(12\sim16R/6\sim8W),必须采用更激进的优化策略。

案例研究 2 — Alpha 21264的重命名设计

DEC Alpha 21264(1998年)采用了一种独特的两级映射方案:

  • 第一级:一个80条目的整数寄存器文件(Register File),其中31个条目对应体系结构寄存器,其余49个为重命名用的额外物理寄存器。

  • 整数映射表:一个31条目的SRAM RAT,将体系结构寄存器映射到80个物理寄存器之一。

  • 重命名宽度:4-wide(每周期最多重命名4条指令),RAT需要8R/4W端口。

  • 关键创新——影子映射表(Shadow Map Table):Alpha 21264为每条inflight分支维护一份完整的映射表副本(shadow copy),而不是使用ROB回退。误预测时直接切换到对应分支的shadow copy,恢复延迟为1个周期。

  • 代价:支持6\sim8个未决分支的shadow copy需要6×31×7=1,3026 \times 31 \times 7 = 1{,}302位的额外存储。在0.35μ\mum工艺下,这是可接受的面积开销。

Alpha 21264的影子映射表方案本质上就是检查点方案的一种实现形式,其设计理念影响了后续所有高性能处理器的分支恢复策略。

案例研究 3 — ARM Cortex-A77的重命名子系统

ARM Cortex-A77(2019年)是一款4-wide超标量处理器,其重命名子系统的公开信息包括:

  • 重命名宽度:4-wide(每周期重命名4条Macro-ops),相比A76的4-wide相同,但微操作(Micro-ops)的处理宽度提升。

  • 整数PRF:约160个物理寄存器×\times64位。采用分离的整数和SIMD/浮点PRF。

  • ROB深度:约160条目,与物理寄存器数量相当——这表明A77没有采用非常激进的物理寄存器过量配置。

  • 端口优化:推测采用RAT复制+RW合并的混合方案,将4-wide的端口需求从8R+4RW降低到每份RAT的4R+4RW。

  • 重命名延迟:2级流水线(Rename + Dispatch),其中Rename级完成映射表查找和旁路,Dispatch级完成发射队列分配。

A77的设计体现了移动处理器在性能和功耗之间的折中:4-wide的重命名宽度比桌面处理器的6\sim8-wide窄,但端口数和旁路逻辑的复杂度也相应降低,更适合功耗受限的移动SoC。

案例研究 4 — AMD Zen 4的重命名子系统

AMD Zen 4(2022年)是一款6-wide超标量x86处理器核心(基于5 nm工艺),其重命名子系统的公开信息和分析包括:

  • 重命名宽度:6-wide(每周期最多重命名6条宏操作/Macro-ops)。

  • 整数PRF:约224个物理寄存器×\times64位。相比Zen 3的180个有显著增加,支持更深的指令窗口。

  • 浮点/向量PRF:约192个物理寄存器×\times256位(支持AVX-512在512位模式下使用两个256位物理寄存器拼接)。

  • ROB深度:320条目——远大于物理寄存器数(224),这意味着ROB中的许多条目不需要物理寄存器(如存储指令、分支指令)。

  • 分支恢复:Zen 4采用了检查点方案,支持约32个未决分支的检查点。误预测恢复延迟约12\sim15个周期(包括检测+清除+恢复+重填充的总延迟)。

  • 重命名流水线:推测为2\sim3级重命名流水线(Rename + Dispatch + 可能的Allocate级)。

  • MOV消除:Zen 4支持MOV消除(零延迟MOV操作),因此实现了引用计数机制来管理被多个逻辑寄存器共享的物理寄存器。

Zen 4在5 nm工艺下实现了超过5.5 GHz的峰值频率——这对重命名子系统的时序提出了极高要求(Tcycle182T_{\text{cycle}} \approx 182 ps)。在如此紧张的时序预算下,RAT读取、旁路逻辑和空闲列表分配都需要在单个或两个极短周期内完成,推测采用了复制+分相+树形旁路的激进优化方案。

空闲物理寄存器的管理

重命名映射表解决了"逻辑寄存器到物理寄存器的映射查找"问题,但还有一个同等重要的问题:从哪里获取可用的物理寄存器?每当一条指令的目的操作数需要被重命名时,必须从物理寄存器池中分配一个空闲的物理寄存器。当旧映射的物理寄存器不再被任何指令使用时,它应该被释放回池中供后续分配。管理这个物理寄存器池的硬件结构就是空闲列表(Free List)。

空闲列表(Free List)

空闲列表有两种主流实现方式:FIFO队列位向量(bit vector)。

FIFO实现

FIFO是最直观的空闲列表实现。空闲的物理寄存器编号被依次存储在一个先进先出队列中。分配操作从队列头部取出一个编号;释放操作将编号插入队列尾部。

基于FIFO的空闲列表:物理寄存器编号按先进先出顺序管理
基于FIFO的空闲列表:物理寄存器编号按先进先出顺序管理

对于一个6-wide处理器,FIFO空闲列表需要支持每周期最多分配6个物理寄存器(对应6条指令的目的操作数)和每周期最多释放6个物理寄存器(对应6条指令的提交)。这意味着FIFO需要6个出队端口和6个入队端口——这对FIFO的微架构设计提出了挑战。

多端口FIFO的实现通常有以下几种方案:

方案1:多bank FIFO

将FIFO分成BB个bank,每个bank有独立的读写端口。分配时从不同bank的头部并行取出;释放时向不同bank的尾部并行插入。例如,6-dequeue FIFO可以分成3个bank,每个bank支持2路并行出队。

具体组织方式:物理寄存器在初始化时按round-robin方式分配到各bank——p0到Bank 0,p1到Bank 1,p2到Bank 2,p3到Bank 0,...。分配时,从Bank 0出队2个、Bank 1出队2个、Bank 2出队2个,共出队6个。

多bank FIFO的问题在于负载不均衡:释放的物理寄存器回到哪个bank取决于它最初被分配到哪个bank(保持与初始化时相同的bank归属),而不同bank的释放速率可能不同,导致某些bank提前耗尽。解决方法是:(1) 在bank之间增加重平衡逻辑(rebalancing),将溢出bank的条目迁移到空缺bank——但这增加了控制复杂度;(2) 放宽bank归属约束,允许物理寄存器在释放时进入任意bank——但这破坏了FIFO的有序性保证。

方案2:宽读/宽写FIFO

使用一个宽端口的SRAM实现FIFO的存储,每次读出/写入一组(6个)物理寄存器编号。SRAM的数据宽度为6×7=426 \times 7 = 42位,一次读出6个连续的物理寄存器编号。头指针和尾指针每次移动的单位是6(而非1),即headnext=(head+6)modD\text{head}_{\text{next}} = (\text{head} + 6) \mod D',其中D=D/6D' = D/6是SRAM的条目数(DD为物理寄存器总数)。

这种方案的优点是SRAM只需要1个读端口和1个写端口——面积和延迟最优。缺点是:

  • 当本周期只需要分配k<6k < 6个物理寄存器时,多读出的6k6-k个编号需要暂存在一个旁路寄存器(bypass register)中,等待下一周期使用。

  • 同样,释放端也需要一个暂存寄存器来积累释放请求,凑满6个后再批量写入SRAM。

  • 控制逻辑需要管理旁路寄存器的剩余量与SRAM的新读取之间的协调。

方案3:多头指针FIFO

维护一个普通的SRAM作为FIFO存储,但使用NN个独立的头指针来支持NN路并行出队。每个头指针指向FIFO中不同的位置:head0=h\text{head}_0 = hhead1=h+1\text{head}_1 = h+1,...,headN1=h+N1\text{head}_{N-1} = h+N-1。第ii条指令从headi\text{head}_i位置取出物理寄存器编号。

出队操作的实现:NN个头指针对应NN个SRAM读地址,需要SRAM提供NN个读端口。对于N=6N = 6,SRAM需要6个读端口——这比多bank方案更直接,且不存在bank不均衡问题。出队完成后,头指针统一前进kk步(kk为本周期实际分配数),更新所有NN个头指针。

入队操作类似:NN个尾指针支持NN路并行入队,需要NN个写端口。总端口需求为6R/6W——对于一个96条目×\times7位的SRAM来说,这是完全可行的(远小于RAT的端口需求)。

位向量实现

另一种方案是使用一个位向量(bit vector)来记录每个物理寄存器的占用状态:1表示空闲,0表示已分配。

基于位向量的空闲列表:每位代表一个物理寄存器的占用状态
基于位向量的空闲列表:每位代表一个物理寄存器的占用状态

分配操作从位向量中找到NN个值为1的位(通过NN路优先级编码器),将它们清零并返回对应的物理寄存器编号。释放操作将对应位设为1。

位向量方案的优缺点:

  • 优点:释放操作非常简单(直接置位),无需FIFO的入队逻辑;状态查询方便(直接读取对应位即可知道某个物理寄存器是否空闲);不存在bank不均衡问题;对分支恢复支持更简洁(见下文)。

  • 缺点:分配操作需要NN路优先级编码器,从128位中找出6个空闲位——这是一个O(128)O(128)的电路,延迟和面积都较高。此外,6路优先级编码器需要串行执行(第2个编码器需要排除第1个的结果,第3个需要排除前2个,以此类推),延迟累积为6×tencoder6 \times t_{\text{encoder}}

N-of-P优先级编码器的实现

从128位位向量中找出6个值为1的位,本质上是一个find-first-N问题。最直接的实现方式是串行级联6个find-first-1编码器:

  1. 第1个编码器:从128位中找到第一个1的位置p0p_0。输出p0p_0作为第1个分配的物理寄存器编号。然后将位p0p_0清零,得到更新后的127位向量。

  2. 第2个编码器:从更新后的位向量中找到第一个1的位置p1p_1。输出p1p_1。清零位p1p_1

  3. 依此类推:共串行执行6次,总延迟为6×tencoder6 \times t_{\text{encoder}}

一个128位的find-first-1编码器可以用树形结构实现:

  • 第1级:将128位分为64对,每对用一个OR门和一个选择逻辑。64个单元。

  • 第2级:64个结果分为32对。32个单元。

  • log2128=7\log_2 128 = 7,每级延迟约15 ps,总延迟约105 ps。

  • 6个编码器串行级联,总延迟6×105=630\approx 6 \times 105 = 630 ps——远超一个时钟周期

为了降低延迟,可以使用并行find-first-N技术:

  • 将128位分成N=6N = 6个段,每段128/622\lceil 128/6 \rceil \approx 22位。

  • 在每个段内并行执行find-first-1,每个编码器的延迟为log222×tgate5×15=75\lceil\log_2 22\rceil \times t_{\text{gate}} \approx 5 \times 15 = 75 ps。

  • 6个编码器并行执行后,通过一个跨段的"级联进位"(cascade carry)逻辑来处理段内没有空闲位的情况——如果第kk段内没有空闲位,则第kk个分配请求"溢出"到第k+1k+1段。

  • 级联进位延迟为O(N)=O(6)O(N) = O(6)级门延迟,约6×15=906 \times 15 = 90 ps。

  • 总延迟75+90=165\approx 75 + 90 = 165 ps——可接受。

位向量方案的分支恢复优势

位向量方案在分支误预测恢复方面有一个独特优势:可以通过位向量的逻辑OR高效恢复。

在每个分支指令处保存位向量的检查点BVckptBV_{\text{ckpt}}。误预测恢复时,将当前位向量与检查点进行OR操作:BVrestored=BVcurrentBVckpt-extraBV_{\text{restored}} = BV_{\text{current}} \mathbin{|} BV_{\text{ckpt-extra}},其中BVckpt-extraBV_{\text{ckpt-extra}}包含了在误预测分支之后被分配出去的物理寄存器——将这些位重新置1即可释放它们。

具体来说,在检查点处记录的是当时的位向量状态。恢复时,需要释放的物理寄存器就是"检查点时空闲但现在不空闲"的那些,即BVckpt&BVcurrentBV_{\text{ckpt}} \mathbin{\&} \overline{BV_{\text{current}}}所标记的位。将这些位置1:BVrestored=BVcurrent(BVckpt&BVcurrent)BV_{\text{restored}} = BV_{\text{current}} \mathbin{|} (BV_{\text{ckpt}} \mathbin{\&} \overline{BV_{\text{current}}})

但实际情况更复杂:在误预测分支之后分配的物理寄存器中,有些可能已经被后续的提交操作正确释放了——这些不应该被再次释放。因此,简单的位向量OR可能导致物理寄存器被重复释放。更安全的方案是直接将位向量恢复为检查点值,然后将检查点之后已提交指令释放的物理寄存器重新标记为空闲——但这需要跟踪提交信息,增加了复杂度。

在实践中,FIFO方案通过简单的头指针回退实现恢复,正确性更容易保证。这也是FIFO方案更为流行的原因之一。

设计权衡 2 — FIFO vs 位向量空闲列表的综合比较

特性FIFO位向量
存储开销96×7=67296 \times 7 = 672128位
分配延迟O(1)O(1)(指针递增)O(logN)O(\log N)(优先级编码)
释放延迟O(1)O(1)(指针递增)O(1)O(1)(位置位)
多路并行分配需要多读端口或宽读需要串行/并行编码器
分支恢复头指针回退,1周期位向量恢复,需检查点
分配顺序确定性(FIFO顺序)确定性(优先级编码顺序)
实现复杂度中等中高(编码器复杂)
工艺可扩展性好(SRAM缩放)好(寄存器缩放)

在128\sim256个物理寄存器的典型配置下,FIFO方案的总存储开销(672\sim1,792位)远大于位向量(128\sim256位),但FIFO的分配延迟O(1)O(1)优势在高频设计中至关重要。当物理寄存器数量非常大(如512+个向量物理寄存器)时,FIFO的存储开销变得显著(512×9=4,608512 \times 9 = 4{,}608位),位向量的面积优势更加明显——但此时优先级编码器的延迟也随之增大。

设计权衡 3 — FIFO vs 位向量空闲列表

  • FIFO方案在现代高性能处理器中更为常见,因为分配操作只需移动头指针——延迟为O(1)O(1),与物理寄存器总数无关。代价是FIFO需要额外的存储空间(128×7128 \times 7=896= 896位)。

  • 位向量方案的存储开销最小(128位),但分配的延迟和复杂度更高。它更适合物理寄存器数量较少(如64\leq 64个)的设计。

  • 在实践中,许多处理器采用FIFO + 计数器的混合方案:FIFO管理分配和释放,一个简单的计数器记录当前空闲物理寄存器的总数。当计数器为零时,重命名阶段暂停(stall),等待物理寄存器被释放。

FIFO空闲列表的硬件实现细节

从硬件实现的角度看,FIFO空闲列表通常采用一个循环缓冲区(circular buffer)实现:一块DD条目×W\times W位的SRAM(其中DD为空闲列表深度,WW为物理寄存器编号的位宽),加上头指针(head pointer)和尾指针(tail pointer)。

对于6-wide处理器的FIFO空闲列表,关键的设计考量包括:

  1. 批量出队:每周期最多出队6个条目。头指针需要一个加法器:headnext=(head+nalloc)modD\text{head}_{\text{next}} = (\text{head} + n_{\text{alloc}}) \mod D,其中nalloc{0,1,,6}n_{\text{alloc}} \in \{0, 1, \ldots, 6\}是本周期实际需要分配的物理寄存器数。出队的6个编号可以从head到head+5的连续位置读出——这需要6个读端口或一次宽读(6×7=426 \times 7 = 42位)。

  2. 批量入队:每周期最多入队6个条目(来自提交阶段释放的旧映射)。尾指针的更新类似于头指针。入队的6个编号需要写入tail到tail+5的连续位置。

  3. 空/满检测:头尾指针相等时为空(或满),需要额外的计数器或标志位来区分。空闲计数器nfreen_{\text{free}}的更新逻辑为:

    nfree=nfreenalloc+nrelease n_{\text{free}}' = n_{\text{free}} - n_{\text{alloc}} + n_{\text{release}}

    nfree<nallocn_{\text{free}} < n_{\text{alloc}}时,重命名阶段必须暂停。

  4. 检查点支持:为了支持分支误预测恢复,FIFO的头指针需要在每个分支指令处保存一个检查点。头指针的检查点存储量很小(仅需log2D\lceil\log_2 D\rceil位),但检查点的数量等于处理器支持的最大未决分支数(如32个)。

循环缓冲区的边界处理

当头指针接近SRAM的末尾时,连续读出6个条目可能跨越缓冲区的边界(wrap-around)。例如,D=96D = 96,head = 93时,需要读出位置93, 94, 95, 0, 1, 2的条目。在硬件中处理这种边界跨越有两种方式:

  1. 取模地址生成:为6个读地址分别计算(head+i)modD(\text{head} + i) \mod Di=0,1,,5i = 0, 1, \ldots, 5)。每个地址需要一个加法器和一个取模电路。取模可以用比较器+MUX实现:如果head+iD\text{head} + i \geq D,则地址为head+iD\text{head} + i - D

  2. 双倍存储:将FIFO的DD个条目镜像存储为2D2D个条目(条目kk和条目k+Dk+D存储相同数据)。这样,任何6个连续地址都不会跨越2D2D的边界(只要6<D6 < D),读取逻辑大幅简化。代价是存储面积加倍。

实际设计中,由于FIFO的存储量很小(96×7=67296 \times 7 = 672位),双倍存储的额外开销(672位)微不足道,因此方案2更常用。

FIFO空闲列表的SystemVerilog实现

以下代码展示了一个简化的6-wide FIFO空闲列表的核心逻辑:

verilog
module free_list #(
  parameter DEPTH      = 96,
  parameter PREG_BITS  = 7,
  parameter ALLOC_W    = 6,  // 每周期最大分配数
  parameter RELEASE_W  = 6   // 每周期最大释放数
)(
  input  logic                          clk, rst_n,
  // 分配接口
  input  logic [$clog2(ALLOC_W+1)-1:0]  n_alloc,  // 本周期分配数
  output logic [ALLOC_W-1:0][PREG_BITS-1:0] alloc_pregs,
  output logic                          stall,  // 空闲不足
  // 释放接口
  input  logic [$clog2(RELEASE_W+1)-1:0] n_release,
  input  logic [RELEASE_W-1:0][PREG_BITS-1:0] release_pregs,
  // 检查点接口
  input  logic                          ckpt_save,
  input  logic [$clog2(32)-1:0]         ckpt_id,
  input  logic                          ckpt_restore,
  input  logic [$clog2(32)-1:0]         restore_id
);
  // FIFO 存储
  logic [PREG_BITS-1:0] fifo_mem [0:DEPTH-1];
  logic [$clog2(DEPTH)-1:0] head, tail;
  logic [$clog2(DEPTH):0]   count;  // 空闲数量

  // 检查点存储
  logic [$clog2(DEPTH)-1:0] ckpt_head [0:31];

  // 分配:读出 head ~ head+n_alloc-1
  always_comb begin
    for (int i = 0; i < ALLOC_W; i++) begin
      alloc_pregs[i] = fifo_mem[(head + i) % DEPTH];
    end
    stall = (count < n_alloc);
  end

  // 指针和计数更新
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      head  <= '0;
      tail  <= DEPTH - 32; // 初始:前32个被架构寄存器占用
      count <= DEPTH - 32;
    end else if (ckpt_restore) begin
      head  <= ckpt_head[restore_id];
      // count 需要重新计算
    end else begin
      if (!stall)
        head <= (head + n_alloc) % DEPTH;
      tail   <= (tail + n_release) % DEPTH;
      count  <= count - (stall ? 0 : n_alloc) + n_release;
    end
  end

  // 释放:写入 tail ~ tail+n_release-1
  always_ff @(posedge clk) begin
    for (int i = 0; i < RELEASE_W; i++) begin
      if (i < n_release)
        fifo_mem[(tail + i) % DEPTH] <= release_pregs[i];
    end
  end

  // 检查点保存
  always_ff @(posedge clk) begin
    if (ckpt_save)
      ckpt_head[ckpt_id] <= head;
  end
endmodule
FIFO空闲列表的面积和延迟估算
  • 存储面积96×7=67296 \times 7 = 672位。使用6R/6W的多端口SRAM,每位约6+12+12=306 + 12 + 12 = 30T,总约20,160T。在7 nm工艺下约1,400 μ\mum2^2

  • 指针逻辑面积:头/尾指针各7位,加法器和取模逻辑约200T。检查点存储:32×7=22432 \times 7 = 224×\times6T = 1,344T。总指针/检查点逻辑约2,000T。

  • 总面积:约22,000T \approx 1,500 μ\mum2^2——约为SRAM RAT面积的15%\sim20%。

  • 分配延迟:SRAM读取(\sim80 ps,因为条目数少、端口数适中)+ 地址计算(加法器+取模,\sim40 ps)= 约120 ps。这远短于RAT的读取延迟,不在重命名阶段的关键路径上。

  • 释放延迟:发生在提交阶段,不影响重命名时序。

空闲列表的容量与重命名暂停

空闲列表的容量等于物理寄存器总数减去逻辑寄存器数。对于128个物理寄存器和32个逻辑寄存器,空闲列表最多容纳12832=96128 - 32 = 96个条目(在处理器复位后、所有物理寄存器的初始映射占用32个)。当空闲列表中的可用数量少于当前周期需要分配的数量时,重命名阶段必须暂停或减少重命名宽度——这被称为物理寄存器耗尽(PRF exhaustion),是影响处理器IPC的一个重要因素。

空闲列表深度=NphysNarch=12832=96\text{空闲列表深度} = N_{\text{phys}} - N_{\text{arch}} = 128 - 32 = 96

为了减少物理寄存器耗尽的概率,处理器设计师通常会配置远多于逻辑寄存器数的物理寄存器。经验法则是物理寄存器数为ROB深度的1.5\sim2倍,或逻辑寄存器数的3\sim5倍——具体取决于工作负载的ILP特性和ROB深度。

物理寄存器数量的定量分析

物理寄存器的数量直接影响处理器可以维持的指令窗口深度(即同时处于flight状态的最大指令数)。在稳态运行时,每条inflight指令占用一个物理寄存器(用于其目的操作数),但并非所有inflight指令都有目的寄存器——存储指令和分支指令通常没有。设RISC-V指令中有目的寄存器的比例为α\alpha(典型值α0.7\alpha \approx 0.7),则要支持WROBW_{\text{ROB}}条inflight指令,所需的物理寄存器数约为:

NphysNarch+αWROBN_{\text{phys}} \geq N_{\text{arch}} + \alpha \cdot W_{\text{ROB}}

对于ROB深度WROB=256W_{\text{ROB}} = 256α=0.7\alpha = 0.7

Nphys32+0.7×256=211N_{\text{phys}} \geq 32 + 0.7 \times 256 = 211

在实际中,物理寄存器数取值为22的幂次方或略多——例如224或256。使用256个物理寄存器时,空闲列表容量为25632=224256 - 32 = 224,可以支持224/0.7=320224/0.7 = 320条inflight指令——提供了约25%的裕量。

物理寄存器数对IPC的影响

物理寄存器不足导致重命名暂停,直接降低IPC。以下简化模型估算暂停概率:

假设每周期重命名N=6N = 6条指令,每条指令以概率α=0.7\alpha = 0.7需要分配物理寄存器。每周期的平均分配需求为μ=Nα=4.2\mu = N \cdot \alpha = 4.2个。每周期的释放数量取决于提交速率——在理想情况下,提交速率等于重命名速率,平均释放也为4.2个/周期。因此稳态下空闲列表不会耗尽。

但在瞬态情况下(如长延迟cache miss阻塞提交,导致释放暂停而分配继续),空闲列表可能快速耗尽。如果一个L2 cache miss的延迟为L=20L = 20个周期,在这20个周期内约分配20×4.2=8420 \times 4.2 = 84个物理寄存器而没有释放。如果空闲列表初始有96个空闲条目,则在第96/4.22396/4.2 \approx 23个周期后耗尽——刚好覆盖一个L2 miss。但如果接连发生两个L2 miss(如指针追踪的链表遍历),则在第一个miss结束前空闲列表已经耗尽,重命名暂停开始。

这个分析说明了为什么高性能处理器需要大量的物理寄存器:它们不仅要覆盖正常执行的指令窗口,还要在长延迟事件期间维持足够的缓冲深度,避免频繁的重命名暂停。

物理寄存器的分配与释放时机

物理寄存器的生命周期管理是乱序执行引擎正确性的基石。一个物理寄存器从分配到释放经历以下阶段:

  1. 分配:在重命名阶段,当一条指令I的目的寄存器rdr_d被分配了新的物理寄存器pnewp_{\text{new}}时,pnewp_{\text{new}}从空闲列表中被取出。此时RAT中rdr_d的映射从poldp_{\text{old}}更新为pnewp_{\text{new}}

  2. 写入:当指令I在执行阶段完成计算后,结果被写入物理寄存器pnewp_{\text{new}}

  3. 被读取:在指令I之后、rdr_d再次被重命名之前,所有以rdr_d为源操作数的指令都会通过RAT查找到pnewp_{\text{new}},并在发射(issue)时从物理寄存器文件中读取pnewp_{\text{new}}的值。

  4. 被覆盖:当一条后续指令J将rdr_d重新映射到pnewerp_{\text{newer}}时,pnewp_{\text{new}}变成了"旧映射"。但此时不能立即释放pnewp_{\text{new}}——因为可能仍有在I和J之间的指令尚未从pnewp_{\text{new}}中读取数据。

  5. 释放pnewp_{\text{new}}可以被安全释放的时机是:指令J提交(commit)之后。当J提交时,意味着J之前的所有指令(包括可能读取pnewp_{\text{new}}的指令)都已经完成执行——因此pnewp_{\text{new}}不再被任何指令需要,可以归还空闲列表。

物理寄存器的完整生命周期
物理寄存器的完整生命周期

更精确地表述释放时机:当指令J提交时,释放的不是J自己的物理寄存器pnewerp_{\text{newer}},而是J在重命名时所替换的旧映射pnewp_{\text{new}}。这就是为什么在重命名阶段必须读取并记录旧映射信息——这个旧映射信息被存储在ROB中,等到指令提交时用于释放。

释放规则:指令J提交    释放J的旧映射=pold(J)\text{释放规则:指令J提交} \implies \text{释放J的旧映射} = p_{\text{old}}(J)
释放时机正确性的严格证明

为什么在"覆盖指令J提交时"释放旧物理寄存器poldp_{\text{old}}是安全的?需要证明此时没有任何指令仍需要从poldp_{\text{old}}中读取数据。

  1. poldp_{\text{old}}是指令I分配的物理寄存器,对应逻辑寄存器rdr_d

  2. 所有在I之后、J之前的指令中,以rdr_d为源操作数的指令,在重命名阶段都会被映射到poldp_{\text{old}}

  3. 指令J将rdr_d重新映射到pnewerp_{\text{newer}}。J之后的指令如果以rdr_d为源操作数,将被映射到pnewerp_{\text{newer}}而非poldp_{\text{old}}

  4. 当J提交时,J之前的所有指令都已经提交(因为提交是按程序顺序进行的)。已提交意味着已完成执行——包括从poldp_{\text{old}}的读取操作。

  5. 因此,J提交时,所有可能读取poldp_{\text{old}}的指令都已经完成读取。poldp_{\text{old}}可以安全释放。

这个证明依赖于两个关键前提:(1) 提交是严格按程序顺序进行的;(2) 指令在提交前必须完成执行(包括读取所有源操作数)。如果处理器允许乱序提交或允许提交后仍有未完成的读取操作,上述释放规则将不再安全。

提前释放优化

标准的"覆盖指令提交时释放"策略是保守的——物理寄存器的实际"最后被读取时刻"通常远早于覆盖指令的提交时刻。这意味着物理寄存器被不必要地持有了很长时间,浪费了物理寄存器资源。

提前释放(Early Release)优化试图在物理寄存器"最后一次被读取"之后立即释放它,而不等到覆盖指令提交。要实现这一点,需要跟踪每个物理寄存器的"最后读取者"(last consumer):

  • 在重命名阶段,记录每个物理寄存器被哪些指令的源操作数引用。

  • 当最后一个引用该物理寄存器的指令完成执行(读取了源操作数),且该物理寄存器已经不在RAT中(即已被覆盖),则可以提前释放。

  • 实现代价:需要为每个物理寄存器维护一个"消费者计数器"(consumer counter),每被引用一次加1,每完成一次读取减1。当计数器为0且已被覆盖时释放。

提前释放可以将物理寄存器的平均持有时间缩短30%\sim50%,等效于增加了30%\sim50%的物理寄存器数量——这对IPC有显著提升。但消费者计数器的硬件开销和时序影响使得这一优化仅在少数高端处理器中被采用。

物理寄存器状态转换的完整模型

物理寄存器在其生命周期中经历以下状态转换:

  1. 空闲(Free):物理寄存器在空闲列表中,等待被分配。此时寄存器中的数据是无效的(或包含上一次使用的残留值)。

  2. 已分配/待写入(Allocated / Pending Write):物理寄存器已被某条指令的目的操作数占用,但指令尚未执行完成,寄存器中没有有效数据。依赖此寄存器的后续指令不能发射。

  3. 已写入/活跃(Written / Active):指令执行完成,结果已写入物理寄存器。依赖此寄存器的后续指令可以发射并读取数据。RAT中仍指向此物理寄存器。

  4. 已覆盖/待释放(Overwritten / Pending Release):另一条指令将同一逻辑寄存器重新映射到新的物理寄存器。旧物理寄存器不再出现在RAT中,但可能仍有已发射但未完成的指令需要读取它。物理寄存器不能被释放,必须等待覆盖指令提交。

  5. 可释放(Releasable):覆盖指令已提交,所有消费者都已完成读取。物理寄存器可以安全归还空闲列表。

物理寄存器的状态转换图
物理寄存器的状态转换图

在分支误预测恢复时,处于"已分配"和"已写入"状态的物理寄存器(如果对应的指令在误预测分支之后)需要直接转换回"空闲"状态。处于"已覆盖"状态的物理寄存器不受误预测恢复的影响——因为覆盖它的指令在误预测分支之前(否则覆盖操作也会被取消),物理寄存器仍然处于等待覆盖指令提交的状态。

分支误预测时的释放处理

在分支误预测恢复时,被错误执行的指令(在误预测分支之后的指令)分配的所有物理寄存器都需要被释放。恢复策略取决于RAT的恢复方式:

  • 检查点恢复(Checkpoint Recovery):处理器在每个分支指令处保存RAT的快照。误预测时,直接恢复到分支指令处的快照——恢复后的RAT不再引用被错误分配的物理寄存器,这些寄存器可以通过比较恢复前后的RAT来批量释放回空闲列表。

  • 顺序回退(Sequential Walk):从ROB的尾部向前逐条回退被错误执行的指令,每回退一条,将该指令分配的物理寄存器释放、该指令替换的旧映射恢复到RAT。这种方式恢复延迟较长(正比于需要回退的指令数),但不需要额外的检查点存储。

  • FIFO空闲列表的恢复:如果空闲列表采用FIFO实现,那么在分支预测时同样需要保存FIFO的头指针作为检查点。误预测恢复时,直接将头指针回退到检查点的位置——被错误分配的物理寄存器编号重新出现在FIFO中。这种"回退头指针"的方式非常高效,是FIFO方案的一大优势。

恢复延迟对性能的影响

分支误预测恢复延迟是处理器分支penalty的重要组成部分。总的分支误预测penalty包括:

Branch Penalty=Tdetect+Tflush+Trecover+Trefill\text{Branch Penalty} = T_{\text{detect}} + T_{\text{flush}} + T_{\text{recover}} + T_{\text{refill}}

其中TdetectT_{\text{detect}}为误预测检测延迟(从分支执行到误预测被确认),TflushT_{\text{flush}}为清除流水线中错误指令的延迟,TrecoverT_{\text{recover}}为重命名状态恢复延迟,TrefillT_{\text{refill}}为从正确路径重新填充流水线的延迟。

TrecoverT_{\text{recover}}取决于恢复方式:

  • 检查点恢复Trecover=1T_{\text{recover}} = 1个周期。直接加载检查点数据到sRAT和空闲列表头指针。

  • cRAT复制恢复Trecover=32/WrestoreT_{\text{recover}} = \lceil 32/W_{\text{restore}} \rceil个周期,其中WrestoreW_{\text{restore}}为恢复写端口宽度。对于Wrestore=6W_{\text{restore}} = 6Trecover=6T_{\text{recover}} = 6个周期。

  • 顺序回退恢复Trecover=NwrongT_{\text{recover}} = N_{\text{wrong}}个周期,其中NwrongN_{\text{wrong}}为需要回退的指令数。最坏情况下可达数百个周期。

在现代高性能处理器中,分支误预测率约为2%\sim5%。假设分支指令占所有指令的20%,则每100条指令中约有100×0.2×0.03=0.6100 \times 0.2 \times 0.03 = 0.6次误预测。如果TrecoverT_{\text{recover}}从1个周期增加到6个周期,每100条指令多花费0.6×5=30.6 \times 5 = 3个周期——IPC下降约3%。这就是为什么检查点方案的1周期恢复对高性能处理器至关重要。

引用计数机制

标准的释放策略——"在覆盖该映射的指令提交时释放旧物理寄存器"——在大多数情况下是正确且高效的。但在某些微架构优化(如MOV消除、零化习语识别)中,一个物理寄存器可能被多个逻辑寄存器同时引用,这使得简单的释放策略不再安全。

问题的起源

考虑MOV消除(Move Elimination)优化:当处理器遇到MOV x5, x3指令时,不实际执行数据搬移,而是让x5直接指向x3当前映射的物理寄存器p42p_{42}。这样,x3x5同时映射到p42p_{42}

riscv
add  x3, x1, x2     # x3 -> p42
  mov  x5, x3          # MOV消除:x5 -> p42(不分配新物理寄存器)
  add  x3, x6, x7      # x3 -> p55,旧映射 p42
  # 此时 p42 仍被 x5 引用,不可释放!

当第三条指令提交时,按照标准释放规则,应该释放x3的旧映射p42p_{42}。但此时x5仍然映射到p42p_{42}——如果释放p42p_{42}x5的值将丢失!

引用计数

解决方案是为每个物理寄存器维护一个引用计数器(reference counter),记录当前有多少个逻辑寄存器映射到该物理寄存器。引用计数的操作规则:

  1. 初始值:物理寄存器在空闲列表中时,引用计数为0。

  2. 分配时:物理寄存器被分配给一个逻辑寄存器时,引用计数加1(初始值从0变为1)。

  3. MOV消除时:目的逻辑寄存器共享源物理寄存器时,该物理寄存器的引用计数再加1(从1变为2)。

  4. 被覆盖时:当某个逻辑寄存器被重新映射到其他物理寄存器时,旧物理寄存器的引用计数减1。

  5. 释放条件:只有当引用计数减到0时,物理寄存器才被释放回空闲列表。

事件x3映射x5映射p42引用计数p42状态
初始p10p200空闲
ADD x3提交p42p201占用
MOV x5,x3提交p42p422占用
ADD x3(第二条)提交p55p421占用
之后x5被覆盖时p55p??0释放

MOV消除场景下的引用计数变化(续代码lst:ch25-mov-elim-refcount的示例)

引用计数的硬件实现需要为每个物理寄存器增加一个小计数器。对于128个物理寄存器,如果最大引用计数为4(即一个物理寄存器最多被4个逻辑寄存器同时引用——这在实际中非常罕见),每个计数器只需2位。总存储开销为128×2=256128 \times 2 = 256位——与SRAM RAT本身的32×7=22432 \times 7 = 224位相当。

引用计数的时序影响在于:每次重命名操作(分配或覆盖)都需要更新对应物理寄存器的计数器,且每次释放决策都需要检查计数器是否为零。这些操作的延迟通常不在重命名阶段的关键路径上——因为引用计数的更新可以与RAT写入并行进行,而释放决策发生在提交阶段(commit stage),不影响重命名的时序。

引用计数的硬件实现细节

引用计数器阵列的组织为Nphys×WcntN_{\text{phys}} \times W_{\text{cnt}},其中NphysN_{\text{phys}}为物理寄存器数,WcntW_{\text{cnt}}为计数器位宽。

计数器位宽选择

计数器位宽WcntW_{\text{cnt}}取决于一个物理寄存器可能被同时引用的最大逻辑寄存器数。考虑以下极端情况:

riscv
add  x1, x2, x3    # x1 -> p40, refcount(p40) = 1
  mv   x5, x1        # x5 -> p40, refcount(p40) = 2
  mv   x10, x1       # x10 -> p40, refcount(p40) = 3
  mv   x15, x1       # x15 -> p40, refcount(p40) = 4

在上述代码中,p40被x1, x5, x10, x15四个逻辑寄存器同时引用,计数值为4。理论上最大引用计数等于逻辑寄存器数(32),但实际中超过4的情况极为罕见——编译器不会生成大量连续的MOV指令。因此Wcnt=2W_{\text{cnt}} = 2位(最大值3)通常足够,但需要一个饱和检测机制:当计数器达到最大值3时,不再执行MOV消除(改为正常分配物理寄存器),避免计数器溢出。使用Wcnt=3W_{\text{cnt}} = 3位(最大值7)可以覆盖所有实际场景,无需饱和检测。

计数器的端口需求

每个时钟周期,引用计数器可能被多条指令同时更新:

  • 增加:每条执行MOV消除的指令对目标物理寄存器的计数器加1。最坏情况:6条指令都执行MOV消除——但实际中MOV消除率通常只有5%\sim10%,平均每周期不到1条。

  • 减少:每条被覆盖映射提交的指令对旧物理寄存器的计数器减1。最坏情况:6条指令提交。

  • 同一周期内可能有增加和减少同时发生在同一个计数器上——需要读-修改-写操作,或使用可同时处理加减的计数器电路。

在最坏情况下,计数器阵列需要6个加端口和6个减端口。但由于MOV消除率低,实际设计中通常只提供2个加端口+6个减端口。

引用计数的替代方案:使用矩阵记录映射关系

一种替代引用计数的方案是使用一个Narch×NphysN_{\text{arch}} \times N_{\text{phys}}映射矩阵:矩阵中第(i,j)(i, j)位为1表示逻辑寄存器ii当前映射到物理寄存器jj。物理寄存器jj的引用计数等于矩阵第jj列的汉明权重(Hamming weight,即1的个数)。

释放条件变为:列jj的所有位都为0,且jj不在任何活跃指令的目的寄存器中。这种方案避免了显式的加减操作,但矩阵的面积为32×128=4,09632 \times 128 = 4{,}096位——约为引用计数器阵列(128×3=384128 \times 3 = 384位)的10倍。因此,矩阵方案仅在验证环境中使用,实际硬件中使用引用计数器。

设计提示

并非所有处理器都实现引用计数。引用计数主要是为了支持MOV消除和零化习语识别等优化。如果处理器不实现这些优化(即每条指令都分配独立的物理寄存器),则简单的"提交时释放旧映射"策略已经足够,不需要引用计数的复杂性。在设计早期,建议先不实现引用计数,将其作为后续优化的增量特性。

物理寄存器文件的设计

物理寄存器文件(Physical Register File, PRF)是存储所有物理寄存器值的大型多端口SRAM。它是处理器数据通路中面积最大、功耗最高的单元之一。PRF的端口数量、容量和组织方式对处理器的性能、面积和功耗有着决定性的影响。

端口数量分析

PRF的端口需求来自两个方面:读端口用于指令发射时读取源操作数,写端口用于指令执行完成时写回结果。

读端口需求

每条发射的指令最多需要从PRF中读取2个源操作数。在一个每周期最多发射KK条指令的处理器中:

PRF读端口数=K×2\text{PRF读端口数} = K \times 2

注意,这里的KK发射宽度(issue width),不一定等于重命名宽度NN。在某些设计中,发射宽度大于重命名宽度(如果有多个功能单元可以从同一发射队列中发射),但通常KNK \leq N

对于一个6-wide处理器,假设发射宽度也为6(整数发射队列每周期发射6条整数指令),则整数PRF需要6×2=126 \times 2 = 12个读端口。

写端口需求

每条完成执行的指令最多产生1个结果需要写回PRF。如果每周期最多有MM条指令完成执行:

PRF写端口数=M\text{PRF写端口数} = M

在理想的全流水线(fully pipelined)设计中,M=KM = K——每周期发射KK条指令,经过固定延迟后每周期也完成KK条指令。因此写端口数也为6个。但实际中,由于不同功能单元的延迟不同(ALU为1周期,乘法为3周期,除法为10+周期),完成端口数可能与发射端口数不同。通常取所有功能单元中可能同时完成的最大数量。

性能分析 2 — 6-wide处理器整数PRF的端口需求

假设一个6-wide超标量处理器的整数执行引擎包含以下功能单元:

  • 2个ALU(加法、逻辑、移位),延迟1周期

  • 1个乘法器,延迟3周期(全流水线)

  • 1个除法器,延迟10\sim40周期(非流水线)

  • 1个分支单元,延迟1周期

  • 1个AGU(地址生成单元),延迟1周期

读端口需求:最坏情况下,6条指令同时从发射队列发射,每条需要读取2个源操作数:6×2=126 \times 2 = 12个读端口。

写端口需求:理论上,上述6个功能单元可以在同一周期完成6条指令的执行。但除法器的延迟高度不确定,且非流水线设计意味着同一时刻只能有一条除法指令在执行。保守估计,每周期最多完成5\sim6条指令,因此需要5\sim6个写端口。

总端口数12R+6W=1812R + 6W = 18个端口。

在7 nm工艺下,一个128条目×\times64位(RV64整数寄存器宽度)、18端口的SRAM的面积约为0.02\sim0.04 mm2^2,读取延迟约250\sim350 ps——这在3 GHz频率下占用了大部分时钟周期,是发射阶段的关键路径。

旁路网络对读端口的影响

在实际设计中,许多指令的源操作数可以通过旁路网络(bypass network / forwarding network)直接从前一条指令的执行结果获取,而不需要从PRF中读取。旁路网络的存在意味着PRF的实际读取次数远低于理论最大值——但硬件设计仍须提供完整的端口数,因为无法预知哪些指令能通过旁路获取数据、哪些必须从PRF读取。

旁路网络本身的复杂度也值得关注。在一个拥有MM个写回端口和K×2K \times 2个源操作数的处理器中,旁路网络需要M×K×2M \times K \times 2个比较器(检测写回的物理寄存器编号是否与源操作数的物理寄存器编号匹配)和K×2K \times 2MM选1 MUX。对于M=6M = 6K=6K = 6

旁路比较器数=M×K×2=6×6×2=72\text{旁路比较器数} = M \times K \times 2 = 6 \times 6 \times 2 = 72
旁路MUX数=K×2=12(每个为7选1:6个旁路源+1个PRF读出)\text{旁路MUX数} = K \times 2 = 12 \quad\text{(每个为7选1:6个旁路源+1个PRF读出)}

旁路网络的面积和功耗与PRF本身处于同一量级。在高性能处理器中,旁路网络的布线密度极高(72个比较器的结果信号需要扇出到12个MUX),常常成为物理设计的瓶颈。

性能分析 3 — 五步推导:W-wide处理器PRF端口总数

给定dispatch宽度WW、执行端口数EE(含各类功能单元)、commit宽度CC,推导PRF所需的读/写端口总数。

第1步:确定读端口数。每条发射指令最多读2个源操作数。若每周期最多发射EE条指令到功能单元,则读端口数R=2ER = 2E

第2步:确定写端口数。每条指令最多产生1个结果写回PRF。每周期最多EE条指令完成写回,则写端口数Wp=EW_p = E

第3步:加入旁路减少的有效读端口。设旁路命中率为β\beta(典型β0.30.5\beta \approx 0.3\sim 0.5),则PRF的平均有效读端口利用率为Reff=2E(1β)R_{\text{eff}} = 2E(1-\beta)。但硬件必须按最坏情况设计,故物理端口数仍为R=2ER = 2E

第4步:加入提交RAT的写需求。提交阶段需要将旧映射信息回收到空闲列表。提交RAT每周期写CC个条目,但这些写操作作用于cRAT而非PRF本身,不增加PRF端口。

第5步:汇总。PRF总端口数 = 2E+E=3E2E + E = 3E。对于典型配置:

处理器风格WWEE读端口写端口总端口
小核(2-wide)23639
中核(4-wide)4510515
大核(6-wide)6816824
大核(8-wide)810201030

从2-wide到8-wide,PRF端口数从9增长到30(3.3倍),而PRF面积(由端口数的平方驱动)增长约11倍。这是"宽度墙"的物理本质——并行宽度的线性增长导致存储面积的二次增长。

PRF的面积模型

多端口SRAM的面积可以用以下经验模型估算:

APRF(R+W)2×Nentries×WdataA_{\text{PRF}} \propto (R + W)^2 \times N_{\text{entries}} \times W_{\text{data}}

其中(R+W)2(R+W)^2反映了多端口位单元面积和布线面积的增长。这个平方关系来自两个方向的布线:

  • 水平方向:每个端口需要一条字线穿越所有数据位列。R+WR+W条字线在位单元高度方向上需要(R+W)(R+W)个间距(pitch)的空间。

  • 垂直方向:每个读端口需要一条位线(或一对差分位线),每个写端口需要一对位线。(R+2W)(R+2W)条位线在位单元宽度方向上需要相应的空间。

  • 位单元面积\propto高度×\times宽度(R+W)×(R+2W)(R+W)2\propto (R+W) \times (R+2W) \approx (R+W)^2

以下给出具体的数值估算。假设7 nm工艺下标准6T SRAM位单元面积为A0=0.027μA_0 = 0.027\,\mum2^2

端口配置位单元T数位单元面积阵列面积含外围相对面积
2R/1W(基准)12T\sim0.054 μ\mum2^2440 μ\mum2^21,760 μ\mum2^21.0×1.0\times
4R/2W18T\sim0.100 μ\mum2^2819 μ\mum2^23,280 μ\mum2^21.9×1.9\times
8R/4W30T\sim0.240 μ\mum2^21,966 μ\mum2^27,860 μ\mum2^24.5×4.5\times
12R/6W42T\sim0.450 μ\mum2^23,686 μ\mum2^214,750 μ\mum2^28.4×8.4\times

不同端口配置PRF的面积估算(128条目×\times64位,7 nm工艺)

从表表 25.7可以看出,12R/6W的PRF面积约为2R/1W基准的8.4倍。对于128×\times64位的整数PRF,12R/6W配置的含外围总面积约0.015 mm2^2——这还不包括旁路网络和发射队列的面积。

多bank设计

多端口SRAM的面积和延迟增长是PRF设计的核心挑战。多bank设计是降低单个bank端口数的关键技术。

基本原理

将物理寄存器文件分成BB个bank,每个bank包含Nphys/BN_{\text{phys}}/B个物理寄存器。每个bank只需要R/BR/B个读端口和W/BW/B个写端口(在理想的均匀访问分布下)。bank选择由物理寄存器编号的低位决定。

4-Bank PRF设计:通过交叉开关将读写请求分发到各Bank
4-Bank PRF设计:通过交叉开关将读写请求分发到各Bank

对于一个12R/6W的PRF,4-bank设计将每个bank的端口数降低到3R/2W(假设均匀分布)。根据表表 25.7的数据,3R/2W的SRAM面积估算约为12R/6W的约1/51/5,延迟也显著降低。4个bank的总面积约为单bank的4×1/5=0.84 \times 1/5 = 0.8倍——再加上交叉开关的面积,总面积与单bank大致持平,但延迟改善显著。

交叉开关的设计

交叉开关(crossbar)是多bank PRF的核心互连结构。读交叉开关将BB个bank的读数据路由到K×2K \times 2个功能单元输入端;写交叉开关将MM个写回结果路由到目标bank的写端口。

读交叉开关的结构是一个B×(K×2)B \times (K \times 2)的MUX矩阵:每个功能单元输入端有一个BB选1 MUX,根据源操作数的物理寄存器编号的低位选择从哪个bank读出数据。对于4-bank、12个读请求的配置,需要12个4选1 MUX,每个MUX选择64位数据——总MUX晶体管数约12×3×64×613,80012 \times 3 \times 64 \times 6 \approx 13{,}800T(每级MUX为2选1,4选1需要log24=2\log_2 4 = 2级,每位需要6T/级)。

写交叉开关更简单:6个写请求需要路由到4个bank之一,每个bank最多接受6/4=2\lceil 6/4 \rceil = 2个写请求。写交叉开关为6×46 \times 4的路由矩阵,加上bank内的写端口仲裁逻辑。

交叉开关的延迟约为30\sim50 ps(2级MUX + 走线延迟),叠加在bank的SRAM读取延迟之上。总PRF读取延迟变为:

Tread-banked=Tbank-SRAM+Tcrossbar130+40=170psT_{\text{read-banked}} = T_{\text{bank-SRAM}} + T_{\text{crossbar}} \approx 130 + 40 = 170\,\text{ps}

相比单bank 12R/6W的Tread-flat250T_{\text{read-flat}} \approx 250 ps,多bank设计的延迟改善了约32%。但必须注意,当发生bank冲突时,冲突的读请求必须等待一个额外周期,有效延迟变为Tread-banked+TcycleT_{\text{read-banked}} + T_{\text{cycle}}

bank映射策略

物理寄存器到bank的映射关系决定了冲突率和访问均匀性。常见的映射策略包括:

  1. 低位交织(Low-bit Interleaving):bank_id=pregmodB\text{bank\_id} = p_{\text{reg}} \mod B。物理寄存器p0, p4, p8, ...在Bank 0;p1, p5, p9, ...在Bank 1;以此类推。这是最常用的方案,均匀性好,但连续分配的物理寄存器(来自FIFO空闲列表的连续位置)总是映射到不同bank——这对分配阶段的并行读取有利。

  2. 高位分割(High-bit Partition):bank_id=preg/(Nphys/B)\text{bank\_id} = p_{\text{reg}} / (N_{\text{phys}}/B)。Bank 0包含p0\simp31,Bank 1包含p32\simp63,...。均匀性取决于工作负载——如果物理寄存器的使用集中在某个范围,会导致bank不均衡。

  3. 哈希映射(Hash Mapping):bank_id=h(preg)\text{bank\_id} = h(p_{\text{reg}}),其中hh是一个简单的哈希函数(如XOR折叠)。可以打破某些访问模式的规律性,降低最坏情况冲突率,但增加了地址计算延迟。

实际设计中,低位交织是主流选择,因为它实现最简单(只需取物理寄存器编号的最低log2B\log_2 B位作为bank选择)且均匀性足够好。

Bank冲突

多bank设计的主要问题是bank冲突(bank conflict)。当同一周期内多条指令需要访问同一个bank、且请求数超过该bank的端口数时,就发生bank冲突。冲突的指令必须等待到下一周期——这降低了有效发射宽度。

Bank冲突的概率取决于:

  1. Bank数量BB:Bank越多,冲突概率越低。

  2. 每bank端口数PP:端口越多,能容纳的并发访问越多。

  3. 访问分布:如果指令倾向于集中访问某些物理寄存器,冲突概率升高。

对于BB个bank、每bank PP个读端口、总共RR个读请求的配置,在均匀随机访问假设下,bank冲突概率可以用以下模型近似估算。假设RR个读请求均匀独立地分布在BB个bank中,某个bank收到超过PP个请求的概率为:

P(bank冲突)=1k=0P(Rk)(1B)k(11B)RkP(\text{bank冲突}) = 1 - \sum_{k=0}^{P} \binom{R}{k} \left(\frac{1}{B}\right)^k \left(1 - \frac{1}{B}\right)^{R-k}
配置每Bank端口单Bank冲突概率任一Bank冲突
2-Bank, 6R/bank60.3%0.6%
4-Bank, 3R/bank34.5%16.8%
4-Bank, 4R/bank40.4%1.6%
8-Bank, 2R/bank24.7%32%
8-Bank, 3R/bank30.3%2.4%

不同Bank配置下的Bank冲突概率(12个读请求,均匀分布)

从表表 25.8可以看出,4-Bank/3R配置的冲突概率约17%——即每6个周期中约有1个周期存在至少一个bank冲突,导致一条或多条指令延迟发射。如果将每bank端口增加到4R,冲突概率骤降到1.6%,几乎可以忽略不计。这表明多bank设计需要在bank数量和每bank端口数之间仔细权衡。

bank冲突的IPC影响

bank冲突导致一条或多条指令延迟发射,其对IPC的影响取决于冲突的严重程度和冲突指令是否位于关键依赖链上。

假设每个冲突周期平均有1条指令被延迟1个周期发射。在无冲突时IPC为4.0的工作负载上,17%的冲突率导致的IPC损失估算为:

IPCloss0.17×14.0×IPC0.17×0.25×4.00.17\text{IPC}_{\text{loss}} \approx 0.17 \times \frac{1}{4.0} \times \text{IPC} \approx 0.17 \times 0.25 \times 4.0 \approx 0.17

即IPC从4.0降低到约3.83——约4.3%的性能损失。这在高性能处理器中通常是不可接受的。因此,过量供给端口(将每bank端口从3R增加到4R,使冲突率降到1.6%)是标准做法,对应的IPC损失仅约0.4%。

bank冲突与指令调度的交互

bank冲突不仅影响当前周期的发射,还可能引发级联效应:被延迟发射的指令可能导致依赖它的后续指令也被延迟,形成"冲突波纹"(conflict ripple)。这种级联效应的影响难以用静态模型精确估算,通常通过周期精确(cycle-accurate)模拟器进行评估。

在某些处理器中,发射队列的调度器(scheduler)会考虑bank冲突信息:如果检测到当前周期的候选指令组会产生bank冲突,调度器可能选择替换其中一条指令(用来自不同bank的指令替代)。这种bank感知调度的代价是:

  • 调度器需要在选择阶段之前或之中计算bank冲突——这增加了调度器关键路径上的延迟。

  • 被替换的指令必须等到下一周期——这相当于将bank冲突从PRF读取级推迟到调度级处理,总体效果类似但实现更灵活。

  • 在调度器本身已经是关键路径的情况下(如Intel/AMD的高频设计),增加bank感知逻辑可能不可行。

解决Bank冲突的策略

  1. 过量供给端口(Over-provisioning):每个bank提供比最低需求稍多的端口。如上例所示,4-Bank配置中每bank提供4个读端口(而非理论最低的3个),可以将冲突概率降低到可忽略的水平。

  2. 发射队列的Bank感知调度(Bank-aware Scheduling):发射队列在选择指令发射时,考虑源操作数的bank分布,避免同时发射过多访问同一bank的指令。代价是调度逻辑的复杂度增加。

  3. 读缓冲(Read Buffer):冲突的读请求被暂存到一个小缓冲区中,在下一周期重试。这避免了整条指令延迟发射,但增加了一级流水线延迟。

  4. 寄存器缓存(Register Cache):在PRF前面放置一个小型的、全端口的寄存器缓存——最近使用的物理寄存器值被缓存在此。命中时直接读取,不访问PRF bank;未命中时再访问PRF。寄存器缓存的全端口设计在小容量下(如16\sim32条目)是可接受的。

读缓冲的详细实现

读缓冲(read buffer)是处理bank冲突的一种硬件机制。当某个bank在一个周期内收到的读请求超过其端口数时,多余的读请求被存入一个小型FIFO缓冲区,在下一周期重新发送到目标bank。

  1. 冲突检测:在PRF读取请求到达bank之前,一个仲裁逻辑检测同一bank的并发请求数。如果超过bank的端口数PP,则多余的请求被标记为"冲突"。

  2. 缓冲写入:冲突请求的物理寄存器编号和目标功能单元编号被写入读缓冲。

  3. 重试:在下一周期,读缓冲中的请求与新的读请求一起参与bank仲裁。如果再次冲突,继续等待。

  4. 数据前送:当读缓冲中的请求成功读取后,数据被送到对应的功能单元。由于功能单元可能已经在上一周期就期望收到数据,需要在功能单元的输入端增加一个MUX,选择来自PRF直接读取或读缓冲延迟读取的数据。

读缓冲的容量通常很小(4\sim8条目),因为连续多周期冲突的概率极低。面积开销约为PRF面积的1%\sim2%。

寄存器缓存详解

寄存器缓存(Register Cache)是一种借鉴数据缓存思想的PRF优化技术。其核心观察是:在任意时刻,被活跃引用的物理寄存器只是总PRF的一小部分——大量物理寄存器要么存储着已不再被需要的旧值(等待释放),要么存储着"冷"数据(如循环外的保存寄存器)。

寄存器缓存的实现方式是在主PRF前放置一个小型的、全端口的SRAM:

  • 容量:通常为16\sim32条目,远小于主PRF的128\sim192条目。

  • 端口:全端口配置(如12R/6W),因为容量小,面积开销可接受。

  • 寻址:以物理寄存器编号的低位做索引(直接映射),或使用小型CAM做全关联映射。

  • 命中率:在典型工作负载下,由于时间局部性,寄存器缓存的命中率可达80%\sim90%。

  • 命中延迟:约为主PRF延迟的50%\sim60%,因此在寄存器缓存命中时,发射到执行的路径更短。

寄存器缓存的管理策略与数据L1 Cache类似:写入PRF时同时更新寄存器缓存中的对应条目(write-through);读取时先查寄存器缓存,命中则直接使用,未命中则访问主PRF并将结果填入寄存器缓存。

寄存器缓存的微架构设计

寄存器缓存的详细实现需要考虑以下设计决策:

  1. 映射方式

    • 直接映射:以物理寄存器编号的低log2C\log_2 C位索引(CC为缓存容量)。优点是查找无需CAM,延迟最低;缺点是冲突率高——当两个频繁访问的物理寄存器映射到同一位置时,频繁替换导致命中率下降。

    • 全关联:使用小型CAM将物理寄存器编号与缓存条目关联。优点是冲突率最低;缺点是CAM的面积和功耗开销。对于16\sim32条目的小容量,全关联的CAM开销可接受。

    • 组关联:折中方案。2路或4路组关联通常在命中率和实现复杂度之间取得较好平衡。

  2. 替换策略:当缓存已满且需要填入新条目时,选择被替换的条目。

    • LRU(最近最少使用):标准的缓存替换策略,命中率最优但LRU状态的维护需要额外逻辑。

    • FIFO:实现最简单,命中率略低于LRU。

    • Random:面积最小,命中率最低但在高关联度下与LRU差距不大。

  3. 写策略

    • Write-through:每次写PRF时同时更新寄存器缓存。保证缓存数据始终与PRF一致,但增加了PRF写端口的负载。

    • Write-back:仅更新寄存器缓存,待缓存条目被替换时才写回PRF。减少PRF写入次数,但一致性维护更复杂(需要dirty bit)。

    • 在实践中,由于寄存器缓存极小(16\sim32条目),write-through的额外写入开销可忽略,因此write-through是主流选择。

  4. 投机性预填充(Speculative Prefill):当一条指令被分配了新的物理寄存器时,可以预测性地在寄存器缓存中预留一个条目——虽然此时数据尚未写入,但当指令执行完成写回时,数据可以直接写入寄存器缓存中已预留的位置。这使得后续读取该物理寄存器的指令更可能命中缓存。

寄存器缓存的命中率分析

寄存器缓存的命中率取决于工作负载的寄存器时间局部性——即短时间内重复访问相同物理寄存器的概率。影响命中率的因素包括:

  • 缓存容量:容量从16增加到32条目,命中率通常从75%\sim80%提升到85%\sim90%(SPEC CPU2017 INT benchmarks下的典型值)。

  • 指令窗口大小:指令窗口越大(ROB越深),活跃物理寄存器数越多,缓存命中率越低。

  • 循环密度:紧密循环中反复使用少量寄存器,命中率高;不规则代码(如图算法、解释器)访问模式分散,命中率低。

在典型的SPEC CPU2017 INT工作负载下,一个32条目、4路组关联的寄存器缓存的命中率约88%。这意味着88%的PRF读请求可以在寄存器缓存命中,只有12%需要访问主PRF的多bank结构。由于寄存器缓存的读取延迟(\sim120 ps)比主PRF(\sim200 ps)低40%,平均读取延迟为:

Tavg-read=0.88×120+0.12×200130psT_{\text{avg-read}} = 0.88 \times 120 + 0.12 \times 200 \approx 130\,\text{ps}

相比没有寄存器缓存的200 ps,平均延迟降低了35%。更重要的是,在寄存器缓存命中时,主PRF不被访问——其时钟门控可以关闭对应端口的预充电和感应放大,节省约50%的PRF动态功耗。

整数/浮点/向量PRF的分离

现代处理器通常需要支持多种数据类型的寄存器:整数寄存器(64位)、浮点寄存器(64位双精度或更宽)和向量寄存器(128\sim512位)。这些寄存器的物理寄存器文件可以采用统一设计(单个大型PRF存储所有类型)或分离设计(每种类型有独立的PRF)。

分离PRF的优势

  1. 端口效率:整数指令只访问整数PRF,浮点指令只访问浮点PRF。分离后,每个PRF的端口数只需要匹配对应类型指令的最大发射宽度,而不是所有类型指令的总发射宽度。

    例如,如果一个6-wide处理器分配4个发射槽给整数指令、2个给浮点/向量指令,则:

    • 整数PRF:4×2=84 \times 2 = 8R + 44W = 8R/4W

    • 浮点PRF:2×2=42 \times 2 = 4R + 22W = 4R/2W(如果浮点有3个源操作数如FMA,则2×3=62\times 3=6R)

    相比统一PRF的6×2=126 \times 2 = 12R + 66W = 12R/6W,分离后每个PRF的端口数显著降低。

  2. 数据宽度优化:整数寄存器为64位,浮点寄存器可能也是64位,但向量寄存器可达256位或512位。统一PRF必须以最宽的数据类型为准(如512位),导致整数寄存器浪费大量位宽。分离PRF允许每种类型使用匹配的数据宽度。

  3. 物理布局:分离的PRF可以放置在各自对应的执行单元附近,缩短数据通路的走线长度,降低延迟和功耗。

统一PRF的优势

  1. 寄存器共享:在某些工作负载中,整数寄存器压力大而浮点寄存器空闲(或反之)。统一PRF允许所有物理寄存器在不同类型之间动态共享,提高利用率。

  2. 类型转换指令:整数到浮点的转换指令(如FCVT.D.L)需要从整数PRF读、向浮点PRF写(或反之)。在分离PRF中,这需要跨PRF的数据通路;在统一PRF中,读写都在同一个PRF内完成。

  3. 设计简化:只需要一套重命名映射表和空闲列表管理逻辑。

设计权衡 4 — 分离 vs 统一 PRF

在现代高性能处理器中,分离PRF是主流选择。原因如下:

  • 向量寄存器的宽度(256\sim512位)使统一PRF的面积代价不可接受——128个512位的寄存器仅存储就需要128×512=65,536128 \times 512 = 65{,}536位,而其中大部分对整数指令来说是浪费的。

  • 分离PRF的端口数优化带来的面积/延迟收益通常远大于寄存器共享的效率损失。

  • 分离PRF允许独立的时钟门控(clock gating)——当没有浮点指令执行时,浮点PRF可以被关断以节省功耗。

典型配置:

  • 整数PRF:128\sim192个物理寄存器 ×\times 64位,8R/4W\sim12R/6W

  • 浮点/向量PRF:128\sim192个物理寄存器 ×\times 256位(或更宽),4R/2W\sim8R/4W

  • 对应的整数RAT浮点/向量RAT也是分离的

Intel的Golden Cove微架构采用了分离设计:整数PRF约280个物理寄存器×\times64位,向量PRF约332个物理寄存器×\times512位(支持AVX-512)。ARM Cortex-X4采用类似的分离策略。

整数与浮点RAT的分离

当PRF分离为整数和浮点/向量两份时,对应的RAT也必须分离:

  • 整数RAT:32条目(RISC-V的x0\simx31),映射到整数PRF中的物理寄存器。端口数由整数重命名宽度决定。

  • 浮点RAT:32条目(RISC-V的f0\simf31),映射到浮点/向量PRF中的物理寄存器。端口数由浮点重命名宽度决定。

分离RAT的好处是每份RAT的端口数更少。在6-wide处理器中,如果4个重命名槽分配给整数、2个分配给浮点:

  • 整数RAT:4×2=84 \times 2 = 8R + 4RW = 8R/4RW,位单元约6+16+8=306 + 16 + 8 = 30T。

  • 浮点RAT:2×2=42 \times 2 = 4R + 2RW = 4R/2RW,位单元约6+8+4=186 + 8 + 4 = 18T。

相比统一RAT的12R/6RW(40T/位),分离后的整数RAT(30T)和浮点RAT(18T)的面积都更小,时序也更好。

但分离RAT带来了一个问题:当指令涉及跨类型操作(如整数到浮点的转换指令FCVT.D.L)时,需要同时读取整数RAT(获取整数源操作数的映射)和写入浮点RAT(更新浮点目的操作数的映射)。这种跨RAT操作需要额外的数据通路——从整数RAT的读端口到浮点RAT的写逻辑之间的连线。在大多数RISC-V程序中,跨类型指令的比例很低(<5%<5\%),因此这条额外数据通路的利用率低,不需要高带宽设计。

向量寄存器的重命名挑战

向量寄存器的重命名面临额外的挑战:

  1. 向量长度可变(Variable-Length Vector, VLV):RISC-V的V扩展支持可配置的向量长度(VLEN),从128位到2048位不等。向量PRF的每个物理寄存器必须按照最大VLEN配置,导致面积浪费。例如,VLEN=1024位的物理寄存器在只使用128位运算时,高896位被浪费。

  2. 向量寄存器分组(Register Grouping, LMUL):V扩展允许将多个向量寄存器组合为一个大寄存器(LMUL=2/4/8)。当LMUL=4时,一条指令操作4个连续的向量寄存器(如v0\simv3)。重命名时,这4个寄存器需要作为一个整体被分配到4个连续的物理寄存器——这要求空闲列表支持连续分配(contiguous allocation),远比单个分配复杂。

  3. 解决方案

    • 以最大组为单位重命名:当LMUL=8时,将所有向量寄存器按8个一组管理。这简化了重命名,但在LMUL=1时浪费了7/8的物理寄存器。

    • 混合粒度重命名:维护多个不同粒度的空闲列表(LMUL=1, 2, 4, 8各一个)。分配时从匹配的空闲列表中取出对应大小的物理寄存器块。复杂但面积效率高。

    • 片段化管理(Fragmentation Management):类似于内存管理中的伙伴系统(buddy system),将大块物理寄存器分割或合并以满足不同LMUL的需求。

向量重命名的复杂性是RISC-V V扩展处理器设计中的重要挑战,其详细讨论超出了本章的范围,将在后续章节中专门分析。

条件寄存器重命名

某些指令集(如ARM的条件执行指令、x86的条件移动CMOV)引入了条件写入问题:指令的目的寄存器是否被写入取决于运行时条件。在重命名阶段,条件尚未确定,但必须先分配物理寄存器。

处理条件写入的方案:

  1. 总是分配:无论条件如何,都为目的寄存器分配新物理寄存器。如果条件不满足(不写入),则在执行阶段将旧值复制到新物理寄存器——这保证了RAT中的映射始终有效,但浪费了一次物理寄存器分配和一次不必要的复制。

  2. 条件分裂(Conditional Split):将条件指令分裂为两条微操作:一条执行计算(结果写入临时物理寄存器),一条根据条件选择最终值(条件MUX)。代价是增加了微操作数,但避免了不必要的寄存器分配。

  3. 延迟重命名:将条件指令的重命名延迟到条件确定后再执行。但这需要暂停后续指令的重命名,严重影响吞吐率。

在RISC-V中,由于不支持条件执行(条件分支用B型指令实现,不涉及条件寄存器写入),这个问题不存在——这是RISC-V简洁ISA设计的一个优势,简化了重命名逻辑。

物理设计约束

PRF的物理设计(面积、时序、功耗)是处理器后端微架构的关键约束。本节详细分析这些约束。

面积

PRF的面积由三部分组成:(1) 位单元阵列,(2) 行译码器和列MUX,(3) 读/写驱动电路和感应放大器。

对于一个128条目×\times64位、12R/6W的整数PRF,位单元面积估算如下:

Abitcell=Nentries×Wdata×Acell(R,W)A_{\text{bitcell}} = N_{\text{entries}} \times W_{\text{data}} \times A_{\text{cell}}(R, W)

其中Acell(12R,6W)A_{\text{cell}}(12R, 6W)是一个12读6写SRAM位单元的面积。在7 nm工艺下,标准6T位单元面积约为0.027 μ\mum2^2,12R/6W位单元面积约为0.027×(6+24+12)/6=0.1890.027 \times (6 + 24 + 12)/6 = 0.189μ\mum2^2(粗略估算)。

Abitcell-total=128×64×0.1891,548μm20.0015mm2A_{\text{bitcell-total}} = 128 \times 64 \times 0.189 \approx 1{,}548\,\mu\text{m}^2 \approx 0.0015\,\text{mm}^2

但这只是位单元阵列的面积。加上行译码器、列电路、感应放大器和布线通道后,总面积通常为位单元阵列的3\sim5倍:

APRF4×Abitcell-total0.006mm2A_{\text{PRF}} \approx 4 \times A_{\text{bitcell-total}} \approx 0.006\,\text{mm}^2

在7 nm工艺下,一个高性能处理器核心的总面积约为3\sim5 mm2^2。整数PRF约占核心面积的0.1%\sim0.2%——看似很小,但考虑到其位于关键路径上且端口密度极高,布线拥塞(routing congestion)往往使PRF周围的实际面积远大于逻辑面积。如果加上向量PRF(256位或512位宽),PRF的总面积占比可达0.5%\sim1.5%。

时序

PRF的读取延迟是发射到执行之间的关键路径。一个典型的PRF读取路径包括:

  1. 地址译码:7位地址(128条目)的译码器延迟 \approx 30\sim50 ps

  2. 字线驱动:驱动字线穿越64个位单元列 \approx 30\sim50 ps

  3. 位线放电:位线电容的放电/充电 \approx 50\sim80 ps

  4. 感应放大:感应放大器检测位线差分信号 \approx 30\sim50 ps

  5. 输出驱动:将读出数据驱动到功能单元输入 \approx 30\sim60 ps

总读取延迟约170\sim290 ps。在3.5 GHz(\sim286 ps周期)频率下,PRF读取几乎占满一个时钟周期。这也是为什么许多高性能处理器将"发射"和"寄存器读取"分为两个独立的流水级。

功耗

PRF的动态功耗主要来自位线和字线的充放电:

Pdynamic=α(R+W)NentriesWdataCwireVDD2fP_{\text{dynamic}} = \alpha \cdot (R + W) \cdot N_{\text{entries}} \cdot W_{\text{data}} \cdot C_{\text{wire}} \cdot V_{DD}^2 \cdot f

其中α\alpha为活动因子(activity factor)。在典型工作负载下,不是所有端口在每个周期都被使用,α\alpha通常为0.3\sim0.5。

参数整数PRF浮点PRF向量PRF总计
条目数160160128
数据宽度64位64位256位
端口配置10R/5W6R/3W6R/3W
位单元面积\sim0.003 mm2^2\sim0.002 mm2^2\sim0.006 mm2^2\sim0.011 mm2^2
含外围总面积\sim0.012 mm2^2\sim0.007 mm2^2\sim0.020 mm2^2\sim0.039 mm2^2
读取延迟\sim220 ps\sim180 ps\sim250 ps
动态功耗\sim30 mW\sim15 mW\sim40 mW\sim85 mW

典型处理器PRF的物理参数估算(7 nm工艺,3 GHz)

PRF功耗优化的常用手段包括:

  1. 时钟门控:未使用的读/写端口在每个周期被动态关闭,避免不必要的位线预充电和字线驱动。

  2. 分段位线(Segmented Bit Lines):将长位线分成多段,每段独立预充电和感应。只有被访问行所在的段需要放电——其余段保持预充电状态不消耗功耗。

  3. 低摆幅位线(Low-Swing Bit Lines):降低位线的电压摆幅(如从VDDV_{DD}降到VDD/2V_{DD}/2),减少充放电功耗。代价是需要更灵敏的感应放大器。

  4. 写掩码(Write Masking):当写入的数据中部分字节未改变时,只驱动改变了的位对应的位线。在整数PRF中,32位操作(如RV64中的ADDW)只需写入低32位,高32位可以被掩码。

布线拥塞与布局策略

PRF的物理布局面临严重的布线拥塞问题。一个12R/6W的PRF有18组数据总线(每组64位宽),加上18组地址总线(每组7位),总布线数为18×64+18×7=1,27818 \times 64 + 18 \times 7 = 1{,}278条信号线——这些信号线必须从PRF引出并连接到各功能单元。在PRF周围的布线密度极高,经常成为物理设计的"热点"(hotspot),导致布线层数增加或面积膨胀。

布线通道的量化分析

假设7 nm工艺下最小金属间距(minimum pitch)为36 nm(M1层),则1,278条信号线需要的最小通道宽度为1,278×36nm46μ1{,}278 \times 36\,\text{nm} \approx 46\,\mum——这仅是单层金属的最小宽度。考虑到实际布线需要留空(约50%利用率)、信号完整性间距要求、以及电源/地线的预留,实际通道宽度约为46×3138μ46 \times 3 \approx 138\,\mum。

如果使用4层金属(M5\simM8)进行信号布线,每层承载约320条信号线,通道宽度降低到\sim35 μ\mum。但4层金属的使用意味着PRF上方的金属资源被大量消耗,限制了其他信号的布线能力——这在高密度设计中是一个严重的约束。

导线延迟的影响

在5 nm及以下工艺中,导线延迟(wire delay)在总信号延迟中的占比急剧增加。PRF数据总线从PRF输出端到功能单元输入端的走线长度通常为100\sim300 μ\mum。在7 nm工艺的M3层金属上,电阻约为10\sim15 Ω/μ\Omega/\mum,电容约为0.2\sim0.3 fF/μ\mum。一条200 μ\mum的走线延迟(Elmore delay)约为:

twire=0.4×Rtotal×Ctotal=0.4×(12×200)×(0.25×200)48pst_{\text{wire}} = 0.4 \times R_{\text{total}} \times C_{\text{total}} = 0.4 \times (12 \times 200) \times (0.25 \times 200) \approx 48\,\text{ps}

这48 ps的导线延迟叠加在SRAM的读取延迟之上,使得PRF到功能单元的总数据传输延迟增加了约20%\sim30%。

解决布线拥塞和导线延迟的策略包括:

  • PRF居中布局:将PRF放置在执行单元阵列的中央,使到各功能单元的布线长度尽量均匀,最大走线长度最小化。

  • 分bank布局:多bank PRF的各bank分散布置在执行单元附近,每个bank只连接少数功能单元,缩短走线。例如,Bank 0和Bank 1紧邻ALU 0和ALU 1,Bank 2和Bank 3紧邻乘法器和分支单元。

  • 高层金属布线:使用顶层金属层(如M8、M9)布设PRF的数据总线,这些层线宽和间距较大,电阻较低,延迟约为M3层的1/31/3。但顶层金属层是全局信号(如时钟、电源)的稀缺资源。

  • 中继器插入(Repeater Insertion):在长走线上插入中继器(缓冲器),将一条长线分成多段短线。最优中继器间距约为RunitCunit/(RbufCbuf)\sqrt{R_{\text{unit}} \cdot C_{\text{unit}} / (R_{\text{buf}} \cdot C_{\text{buf}})},通常为50\sim100 μ\mum。每个中继器增加约15\sim20 ps的缓冲延迟,但将导线延迟从O(L2)O(L^2)降低到O(L)O(L)

PRF的ECC保护

物理寄存器文件中存储的数据一旦发生位翻转(由软错误如Alpha粒子或中子辐射引起),将导致程序计算结果错误——且这种错误极难被软件检测。因此,高可靠性处理器(如服务器级处理器)通常在PRF中实现ECC(Error Correcting Code)保护:

  • SECDED(单纠错双检错):对64位数据增加8位奇偶校验位,总存储宽度变为72位。能纠正单比特错误,检测(但不能纠正)双比特错误。

  • 面积开销:数据宽度从64位增加到72位,面积增加约72/641=12.5%72/64 - 1 = 12.5\%

  • 延迟开销:ECC编码在写入时执行(约30 ps),ECC解码和纠错在读出时执行(约40\sim60 ps)。纠错延迟叠加在PRF读取延迟之上,使总读取延迟增加约15%\sim20%。

  • 功耗开销:约15%的额外动态功耗(8位额外数据位的充放电)。

对于移动处理器和嵌入式处理器,PRF通常不实现ECC保护——单比特翻转概率在低辐射环境下极低(约101810^{-18}翻转/位/秒),在PRF的小容量(\sim8,000位)和短数据保留时间(微秒级)下,软错误率可忽略不计。但在高辐射环境(如航空航天)或大规模数据中心(追求极低的静默数据损坏率)中,ECC保护是必须的。

案例研究 5 — Apple M系列处理器的PRF设计推测

基于公开的性能分析数据和芯片面积逆向工程,Apple M系列处理器(基于自研微架构,如Firestorm/Avalanche)的PRF设计具有以下特点:

  • 整数PRF:约192个物理寄存器×\times64位。8-wide解码/重命名,约6个整数执行端口,估计配置为12R/6W或分bank方案。

  • 浮点/向量PRF:约176个物理寄存器×\times128位(支持NEON/SVE),约4个浮点/向量端口。

  • ROB深度:约630条目——远超物理寄存器数量,这表明Apple使用了激进的物理寄存器回收策略和较大的指令窗口。

  • 重命名阶段:推测为2级流水线(读RAT + 旁路/更新RAT),支持8-wide宽重命名。

  • 由于M系列芯片面积紧凑(核心面积约3\sim4 mm2^2),其PRF设计必须在面积效率上做到极致——推测采用了多bank + 分段位线 + 激进时钟门控的组合优化。

工艺演进对PRF设计的影响

随着半导体工艺从14 nm向7 nm、5 nm和3 nm演进,PRF设计面临的挑战也在变化:

  1. SRAM位单元缩放放缓:从7 nm开始,标准6T SRAM位单元的面积缩放速度显著放缓。7 nm的6T面积约为0.027 μ\mum2^2,5 nm约为0.021 μ\mum2^2,仅缩小了22%——远低于节点面积2×\times的理论缩放。多端口位单元由于需要更多间距来容纳存取管,缩放更加困难。

  2. 漏电流增加:工艺缩小后,SRAM位单元的阈值电压降低,静态漏电流增加。对于一个128×\times64位的PRF(8,192个位单元),在3 nm工艺下的静态漏电功耗可达5\sim10 mW——在总动态功耗仅为30\sim50 mW的情况下,漏电占比可达15%\sim25%。

  3. FinFET/GAA对多端口的影响:FinFET和GAA(Gate-All-Around)晶体管的量化宽度特性(fin pitch或纳米线数量)使得多端口位单元的设计更加困难——每个端口需要整数个fin/nanowire,导致面积不能像平面工艺那样精细调整。

  4. 后端布线(BEOL)瓶颈:在5 nm及以下工艺中,金属互连的电阻急剧增加,导致长距离信号传输延迟恶化。PRF的位线和字线延迟受此影响显著增大,进一步加剧了读取延迟问题。

这些工艺挑战推动了新型存储技术在PRF中的探索,包括8T SRAM位单元(分离读写端口,提高稳定性)、eDRAM(嵌入式DRAM,面积更小但需要刷新)、以及基于寄存器缓存的层次化设计。

8T SRAM位单元在PRF中的应用

8T SRAM位单元将读端口和写端口完全分离:6T核心保留差分写端口(通过写位线对WBL/WBLWBL/\overline{WBL}和写字线WWLWWL),额外增加2个NMOS晶体管构成独立的单端读端口(通过读位线RBLRBL和读字线RWLRWL)。

8T位单元在PRF中的优势:

  • 读稳定性:读操作通过独立的读端口进行,不干扰存储节点的稳定性——消除了6T位单元的读扰动(read disturb)问题。在低电压工作时(如VDD=0.5V_{DD} = 0.5 V的near-threshold设计),8T位单元的读稳定性远优于6T。

  • 单端读位线:每个读端口只需一条读位线(而非差分对),节省了垂直方向的布线空间。对于多读端口的PRF,这意味着位单元宽度可以更窄。

  • 读写解耦:读操作和写操作可以使用不同的时钟相位,降低读写冲突的风险。

8T位单元的劣势:

  • 面积增加:8T位单元比6T大约33%(2个额外晶体管)。但在多端口设计中,基准面积已经很大(如42T的12R/6W),额外2T的增量占比不到5%。

  • 单端读速度:单端读位线的电压摆幅较小,需要更灵敏的感应放大器。读取延迟可能比差分6T方案略长5\sim10%。

在先进工艺节点(5 nm及以下),8T位单元正在成为PRF的主流选择,因为其在低电压工作下的稳定性优势对于功耗优化至关重要。

层次化PRF设计

层次化PRF结合了寄存器缓存和多bank主PRF的优势,形成一个两级甚至三级的寄存器存储层次:

  1. L0寄存器文件(即寄存器缓存):16\sim32条目,全端口,读延迟\sim100 ps。存储最近写入或最近读取的物理寄存器值。

  2. L1寄存器文件(主PRF):128\sim192条目,多bank设计(每bank 3\sim4个端口),读延迟\sim170 ps。存储所有物理寄存器值。

  3. L2寄存器文件(可选的退休寄存器文件):仅32条目(等于逻辑寄存器数),2R/1W端口,读延迟\sim60 ps。存储已提交指令的寄存器值。仅在上下文切换和异常处理时被访问。

这种层次化设计的核心思想与缓存层次完全一致:用小而快的存储吸收大部分访问,只有少量访问需要穿透到大而慢的主存储。

层次化PRF的设计挑战在于一致性维护:当主PRF(L1)中的数据被更新时,L0中的对应条目必须同步更新或失效。由于PRF的更新频率极高(每周期可能有6个写入),一致性维护的逻辑必须在关键路径之外完成——通常采用write-through策略,确保每次写入同时更新L0和L1。

PRF的读后写冲突处理

当一条指令正在读取物理寄存器pkp_k的同时,另一条指令正在写入pkp_k的新值时,存在读后写冲突(Write-After-Read, WAR in PRF)。在物理寄存器文件中,这种冲突不影响正确性(因为读取和写入是同一周期内不同指令的独立操作),但需要硬件保证读取获得的是旧值而非正在写入的新值。

SRAM的物理特性通常天然保证了这一点:读操作在时钟上升沿启动(位线预充电+字线激活+感应放大),写操作在时钟下降沿或稍后启动(写驱动器覆盖位线)。由于读操作完成在先,读取的一定是旧值。但在高频设计中(5 GHz+),读写窗口可能重叠——此时需要显式的读写仲裁逻辑来保证正确性。

另一种情况是写后读(Read-After-Write, RAW in PRF):一条指令在周期TT写入pkp_k,另一条依赖它的指令在周期T+1T+1读取pkp_k。这要求写入在周期TT结束前完成,且数据在周期T+1T+1开始时稳定可读——这就是标准的写入建立时间(write setup time)要求。在多bank PRF中,如果写入和读取在不同bank,则不存在冲突;如果在同一bank,则需要bank内部的写-读转发(write-read forwarding)机制。

PRF与发射队列的物理关联

PRF通常紧邻发射队列(Issue Queue)布局。发射队列选择好要发射的指令后,将源操作数的物理寄存器编号送入PRF的读地址端口;PRF读出数据后直接送入功能单元的输入端。这条数据通路(发射队列\toPRF\to功能单元)是后端的关键路径之一,其延迟直接决定了发射到执行之间所需的流水级数。

在某些高频设计中(如Intel/AMD的5 GHz+频率核心),PRF读取被拆分为两个流水级:第一级完成地址译码和字线驱动,第二级完成位线感应和数据输出。这使得每一级的延迟缩短到150\sim180 ps以内,适应5 GHz频率(200 ps周期)的要求。代价是增加了一级发射延迟(从发射到结果可用多了一个周期),影响了依赖链上的IPC。

发射-读取-执行流水线的时序分析

完整的发射到执行路径包括以下步骤:

  1. 发射队列选择:从发射队列中选择准备好发射的指令。选择逻辑的延迟约100\sim150 ps(取决于发射队列深度和选择算法复杂度)。

  2. PRF地址驱动:将选中指令的源操作数物理寄存器编号驱动到PRF的地址端口。走线延迟约20\sim30 ps。

  3. PRF读取:地址译码 + 字线驱动 + 位线放电 + 感应放大 + 输出驱动,总延迟约200\sim300 ps。

  4. 旁路MUX:检查是否有更新的写回数据可以旁路(来自同周期完成的其他指令)。MUX延迟约20\sim30 ps。

  5. 功能单元输入锁存:数据到达功能单元输入端的流水线寄存器。建立时间约10\sim20 ps。

总延迟约350\sim530 ps。在3 GHz(333 ps周期)下,这需要2个流水级:

  • Issue级:发射队列选择(\sim130 ps)+ PRF地址驱动(\sim25 ps)+ PRF读取的前半段(地址译码+字线驱动,\sim80 ps)= \sim235 ps。

  • RF-Read级:PRF读取的后半段(位线放电+感应放大+输出,\sim120 ps)+ 旁路MUX(\sim25 ps)+ 功能单元输入锁存(\sim15 ps)= \sim160 ps。

两级的时序分别为235 ps和160 ps,均在333 ps的周期预算内。但这意味着从发射到结果可用的延迟为3个周期(Issue + RF-Read + Execute),而非理想的1个周期。每增加一级流水线,依赖链上的IPC损失约5%\sim10%。

重命名子系统的综合设计

前面各节分别讨论了SRAM RAT、CAM RAT、空闲列表和物理寄存器文件的独立设计。本节将这些组件综合起来,分析它们在重命名流水线中的协同工作方式、全局时序约束和面积预算分配。

重命名流水线的全局时序

在一个典型的6-wide处理器中,重命名阶段占据1\sim2个流水级。以2级重命名流水线为例,各操作在流水级中的分布如下:

Rename-1级的操作

  1. SRAM RAT读取:以12个源操作数的逻辑寄存器编号为地址,并行读取12个物理寄存器映射。延迟\sim200 ps。

  2. 旧映射读取:以6个目的操作数的逻辑寄存器编号为地址,读取6个旧映射。可以与源操作数读取共用SRAM端口(RW端口的读阶段),延迟\sim200 ps。

  3. 空闲列表出队:从FIFO空闲列表中取出最多6个物理寄存器编号。延迟\sim120 ps。

  4. 旁路比较器计算:以6条指令的逻辑寄存器编号计算45个比较器的匹配结果。延迟\sim35 ps。可以与SRAM读取并行执行。

Rename-1级的关键路径是SRAM读取(\sim200 ps),时序裕量=33320030setup20skew=83= 333 - 200 - 30_{\text{setup}} - 20_{\text{skew}} = 83 ps——足够。

Rename-2级的操作

  1. 旁路MUX选择:使用Rename-1级计算的比较器结果和SRAM读出的映射值,通过优先级MUX链选出最终的源物理寄存器编号。延迟\sim100\sim125 ps(级联方案)或\sim70\sim90 ps(树形方案)。

  2. SRAM RAT写入:将6个新映射写入SRAM RAT。可以在MUX选择完成之前开始(写地址和写数据在Rename-1级末尾已确定)。延迟\sim100 ps。

  3. 写仲裁:检测并处理同一周期内多条指令写同一逻辑寄存器的情况。延迟\sim35 ps。与SRAM写入并行。

  4. 结果输出:将最终的源物理寄存器编号、旧映射物理寄存器编号、新分配的物理寄存器编号写入流水线寄存器,送往发射队列(Issue Queue)。

Rename-2级的关键路径是旁路MUX选择(\sim125 ps),时序裕量=3331253020=158= 333 - 125 - 30 - 20 = 158 ps——非常宽裕。这表明2级重命名流水线的时序是过度保守的——对于3 GHz频率,理论上可以在1级内完成(如果使用树形优先级MUX和并行化技巧)。但2级方案提供了更好的时序裕量,适应工艺变异和电压/温度波动。

面积预算分配

一个完整的重命名子系统包括以下组件。以6-wide处理器、128个整数物理寄存器、7 nm工艺为例,各组件的面积估算如下:

组件存储量端口配置估算面积占比
Speculative RAT (sRAT)32×\times7位12R+6RW\sim1,200 μ\mum2^210%
旁路逻辑45 CMP+MUX\sim500 μ\mum2^24%
Committed RAT (cRAT)32×\times7位6R+6W\sim600 μ\mum2^25%
RAT检查点32×\times32×\times7位批量R/W\sim1,500 μ\mum2^212%
FIFO空闲列表96×\times7位6R/6W\sim1,500 μ\mum2^212%
FL检查点32×\times7位1R/1W\sim150 μ\mum2^21%
整数PRF128×\times64位12R/6W (4-bank)\sim6,000 μ\mum2^248%
引用计数器128×\times3位2加/6减\sim300 μ\mum2^22%
控制逻辑\sim750 μ\mum2^26%
总计\sim12,500 μ\mum2^2100%

重命名子系统各组件的面积预算(6-wide, 128 PReg, 7 nm工艺)

从表表 25.10可以看出,整数PRF占据了重命名子系统面积的近一半。RAT检查点和FIFO空闲列表各占约12%,sRAT本身仅占10%。总面积约0.0125 mm2^2,在一个3\sim5 mm2^2的处理器核心中占比约0.3%\sim0.4%。

如果加上浮点/向量的重命名子系统(包括浮点RAT、浮点PRF等),总面积翻倍到约0.025 mm2^2,占核心面积的0.5%\sim0.8%。虽然面积占比不高,但这些结构位于时序关键路径上,其延迟特性直接决定了处理器的最高时钟频率。

重命名宽度的扩展性分析

重命名宽度从6-wide扩展到8-wide时,各组件的开销变化如下:

  1. RAT端口数:从12R+6RW增长到16R+8RW。位单元晶体管从40T增长到56T,面积增长约(16+8)2/(12+6)2=576/324=1.78(16+8)^2/(12+6)^2 = 576/324 = 1.78倍。

  2. 旁路逻辑:比较器从45个增长到k=283(k1)=3×28=84\sum_{k=2}^{8}3(k-1) = 3 \times 28 = 84个(假设每条指令3个操作数含旧映射)。优先级MUX的最大级数从5增长到7,关键路径延迟增加约40%。

  3. 空闲列表:出队/入队宽度从6增长到8,FIFO存储无需变化,但端口数从6R/6W增长到8R/8W。

  4. PRF端口数:从12R/6W增长到16R/8W。位单元面积增长约(16+8)2/(12+6)2=1.78(16+8)^2/(12+6)^2 = 1.78倍,读取延迟增长约30%。

总体而言,从6-wide到8-wide的扩展使重命名子系统面积增长约70%\sim80%,关键路径延迟增长约30%\sim40%。这意味着在相同工艺节点下,8-wide设计可能需要降低时钟频率或增加流水级数来满足时序约束。

设计提示

重命名宽度的选择是处理器架构级的核心决策。宽度从4增加到6再到8,IPC的边际收益递减(受限于指令级并行度和分支预测精度),但面积和功耗的边际成本递增(主要受RAT端口数和旁路逻辑复杂度驱动)。大多数高性能处理器选择6-wide或8-wide作为最佳平衡点:6-wide在面积效率上最优,8-wide在极端性能(如单线程吞吐率竞赛)中占优。超过8-wide的设计在学术界有所探索(如16-wide),但端口数和旁路逻辑的平方级增长使其在商业处理器中不可行。

重命名子系统的初始化

处理器上电复位或从深度睡眠唤醒后,重命名子系统需要被初始化到一个已知的正确状态。初始化过程包括:

  1. RAT初始化:每个逻辑寄存器xix_ii=0,1,,31i = 0, 1, \ldots, 31)被映射到物理寄存器pip_i。即RAT[i]=i\text{RAT}[i] = i。这要求物理寄存器p0\simp31被预留给体系结构寄存器,不进入空闲列表。

  2. 空闲列表初始化:物理寄存器p32\simp127(共96个)被依次写入FIFO空闲列表。头指针指向p32,尾指针指向p127后的位置。

  3. PRF初始化:所有物理寄存器的值被设为0(或不确定——如果软件必须在使用前显式初始化寄存器,则PRF不需要硬件初始化)。特殊地,p0的值被固定为0(对应x0的零值寄存器语义)。

  4. 引用计数初始化:p0\simp31的引用计数设为1(各被一个逻辑寄存器引用),p32\simp127的引用计数设为0。

  5. cRAT初始化:与sRAT相同——cRAT[i]=i\text{cRAT}[i] = i

  6. 检查点清空:所有分支检查点标记为无效。

初始化过程可以在复位后的前几个时钟周期内完成(通过硬件复位逻辑逐条目写入),期间处理器前端被暂停,不取指、不译码、不重命名。初始化延迟通常为32\sim64个周期——这对于处理器的总启动时间来说是微不足道的。

重命名子系统的功耗管理

重命名子系统虽然面积不大,但由于每个时钟周期都在高频活动(读RAT、写RAT、访问空闲列表、旁路比较),其功耗密度相当高。以下技术用于降低重命名子系统的功耗:

  1. 端口级时钟门控:当某个重命名槽中没有有效指令时(例如取指宽度不足6条),对应的RAT读/写端口被时钟门控关闭——不预充电位线、不驱动字线、不激活感应放大器。平均活动因子α0.5\alpha \approx 0.5(即约50%的端口在任意时刻是空闲的),功耗节省约50%。

  2. x0优化:当源操作数为x0时,不需要读RAT(直接返回p0)。当目的操作数为x0时,不需要写RAT、不分配物理寄存器、不更新引用计数。在RISC-V程序中,约10%\sim15%的操作数是x0,这一优化可节省约10%的RAT访问功耗。

  3. 空闲列表的按需访问:只有当本周期有需要分配物理寄存器的指令时,才访问空闲列表。存储指令和分支指令不需要分配物理寄存器——当一组6条指令全部是存储/分支时,空闲列表可以被完全门控。

  4. RAT的电压缩放:RAT是一个小型的、低负载的SRAM,可以在低于核心VDDV_{DD}的电压下工作。由于动态功耗V2\propto V^2,将RAT的工作电压从0.75 V降到0.65 V可以节省约25%的功耗。但需要确保在低电压下SRAM的读写裕量足够——这在7 nm及以下工艺中可能不可行(SRAM的读写裕量随电压下降而急剧缩小)。

  5. 重命名暂停时的深度门控:当处理器因某种原因(如cache miss、物理寄存器耗尽、ROB已满)暂停重命名时,整个重命名子系统可以进入深度时钟门控状态——关闭RAT、空闲列表和旁路逻辑的所有时钟。只有核心控制逻辑保持活动,等待暂停条件解除。

本章详细分析了重命名映射表和物理寄存器文件的设计空间。表表 25.11总结了各个核心结构的关键设计参数。

结构容量端口配置关键延迟
SRAM RAT(整数)32×\times7位12R+6RW180\sim250 ps
CAM RAT(如用)128×\times12位12搜索 + 6写250\sim350 ps
空闲列表(FIFO)96×\times7位6出队 + 6入队80\sim120 ps
整数PRF128×\times64位12R/6W200\sim300 ps
旧映射缓存(ROB)ROB深度×\times7位6读 + 6写100\sim150 ps

重命名子系统各结构的设计参数总结(6-wide处理器,128物理寄存器)

值得特别强调的是,表表 25.11中各结构的延迟是隔离测量的结果——在实际流水线中,这些结构之间还有走线延迟、时钟分配偏移和建立/保持时间等附加开销。重命名阶段的实际关键路径通常是:

Trename=TRAT-read+Tbypass+TRAT-write-setup+Tclock-skewT_{\text{rename}} = T_{\text{RAT-read}} + T_{\text{bypass}} + T_{\text{RAT-write-setup}} + T_{\text{clock-skew}}

对于6-wide处理器在7 nm工艺、3 GHz目标频率下,各项典型值为:TRAT-read200T_{\text{RAT-read}} \approx 200 ps,Tbypass80T_{\text{bypass}} \approx 80 ps,TRAT-write-setup30T_{\text{RAT-write-setup}} \approx 30 ps,Tclock-skew20T_{\text{clock-skew}} \approx 20 ps。总计约330 ps——刚好在333 ps的周期预算内。这种紧张的时序裕量解释了为什么提升重命名宽度(从6-wide到8-wide)需要增加流水级或采用更激进的电路技术。

值得特别强调的是,表表 25.11中各结构的延迟是隔离测量的结果——在实际流水线中,这些结构之间还有走线延迟、时钟分配偏移和建立/保持时间等附加开销。

重命名子系统的验证要点

重命名子系统的功能验证是处理器设计中最复杂的验证任务之一,因为需要覆盖多条指令之间各种相关性组合的正确性。以下列出关键的验证场景:

  1. 单条指令的基本重命名:验证每条指令的源操作数和目的操作数被正确映射——源读到旧映射,目的分配新物理寄存器并更新RAT。

  2. 同周期RAW相关IkI_k的源操作数与IjI_jj<kj < k)的目的操作数相同时,旁路逻辑是否正确选择IjI_j的新映射。

  3. 同周期WAW相关:多条指令写同一逻辑寄存器时,RAT最终是否保留最后一条指令的映射,旧映射是否被正确记录。

  4. 同周期RAW+WAW组合:既有RAW又有WAW的复杂场景,如I0I_0写x5,I2I_2写x5,I4I_4读x5——I4I_4应读到I2I_2的映射。

  5. x0寄存器处理:以x0为源操作数的指令应始终读到p0;以x0为目的操作数的指令不应更新RAT,不应分配物理寄存器。

  6. 空闲列表耗尽:当物理寄存器全部被分配后,重命名阶段应正确暂停,不产生错误的映射。

  7. 分支检查点的正确性:在分支指令处保存的RAT和空闲列表检查点应精确捕获当时的映射状态。

  8. 误预测恢复:从检查点恢复后,RAT和空闲列表的状态应与分支指令执行时完全一致。被错误分配的物理寄存器应被正确释放。

  9. 提交时的物理寄存器释放:指令提交时,被替换的旧映射物理寄存器应被正确释放到空闲列表。引用计数(如果实现)应正确更新。

  10. 异常处理:当异常发生时,所有推测状态应被清除,RAT和空闲列表应恢复到最后一个已提交指令的状态。

功能验证通常使用约束随机验证(Constrained Random Verification)方法:随机生成大量指令序列,覆盖各种相关性模式和边角情况。参考模型(reference model)采用高层次的C/C++模型模拟理想的重命名行为,与RTL仿真结果进行逐拍比对。

形式验证(Formal Verification)也被越来越多地应用于重命名逻辑的正确性证明。关键的形式属性包括:

  • 映射唯一性:sRAT中的每个逻辑寄存器映射到唯一的物理寄存器(允许MOV消除时多个逻辑寄存器映射到同一物理寄存器)。

  • 空闲列表不重复:空闲列表中不会出现重复的物理寄存器编号。

  • 空闲列表与RAT互斥:空闲列表中的物理寄存器不会出现在RAT的任何映射中(除非有引用计数的延迟释放)。

  • 物理寄存器守恒:在任何时刻,所有物理寄存器要么在RAT中(被某个逻辑寄存器引用),要么在空闲列表中,要么在某条inflight指令的旧映射记录中——不会凭空消失或产生。

  • 恢复后等价性:从任何检查点恢复后的RAT和空闲列表状态,必须与在该检查点创建时的状态完全一致(模空闲列表中已提交释放的物理寄存器的差异)。

  • 提交时旧映射正确性:每条指令提交时释放的旧映射物理寄存器编号,必须等于该指令重命名时记录在ROB中的旧映射值。

这些形式属性可以使用SAT/SMT求解器(如JasperGold、Cadence JasperGold或Synopsys VC Formal)进行穷举验证。对于32条目×\times7位的RAT和96条目的空闲列表,状态空间虽然很大(22242^{224}种RAT状态×\times空闲列表排列),但形式验证工具通过符号执行和抽象技术可以在合理时间内完成关键属性的证明。

本章从电路级的视角深入分析了重命名映射表、空闲列表和物理寄存器文件的设计空间。我们看到,这些看似简单的查表操作,在6\sim8-wide超标量处理器中演变为复杂的多端口存储设计和精密的旁路逻辑网络。SRAM RAT的端口数从理论的18R/6W到实际的复制+RW合并方案的优化过程,体现了处理器设计中面积、时序和功耗三方面的精妙权衡。

设计权衡 5 — 前向桥接——从单条到多条

本章建立了RAT、空闲列表和PRF的电路级设计基础。但这些讨论隐含了一个关键简化:每周期只重命名一条指令。在真实的6-wide处理器中,6条同周期指令之间可能存在RAW依赖——后面的指令读取前面指令刚刚分配的物理寄存器。重命名不是6条独立的并行操作,而是一条既并行又串行的复合操作。如何在一个周期内完成这30个比较器的依赖检测?WAW冲突时哪条指令的RAT写入生效?分支误预测后如何恢复?这些超标量重命名的核心工程挑战将在第 26.0 章中展开。

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