我们已经了解了动态调度、多发射和推测等单独的机制是如何工作的。(具体请参见本人前几篇博客)
现在我们把这三种机制结合起来,得到一种和现代微处理器非常相似的微架构。为了简单起见,我们只考虑每个时钟周期发射两条指令的情况,但是这些概念也适用于每个时钟周期发射三条或更多指令的现代处理器。我们假设我们想要扩展Tomasulo算法,以支持多发射超标量流水线,它有独立的整数、加载/存储和浮点(包括浮点乘法和浮点加法)功能单元,每个功能单元每个时钟周期都可以开始一个操作。
我们不想让指令以乱序的方式发射到预约站,因为这可能会违反程序的语义。为了充分利用动态调度的优势,我们将允许流水线在一个时钟周期内发射任意组合的两条指令,使用调度硬件来实际分配操作到整数和浮点单元。
如图所示,基本的组织结构类似于一个具有推测执行和每个时钟周期一条发射的处理器,只是发射和完成逻辑必须增强,以允许每个时钟周期处理多条指令。
在一个动态调度的处理器中(无论是否有推测执行),每个时钟周期发射多条指令是非常复杂的,因为这些指令可能相互依赖。因此,必须同时更新表格中的指令信息;否则,表格可能会不正确或者依赖关系可能会丢失。
有两种不同的方法可以在一个动态调度的处理器中每个时钟周期发射多条指令,而且都依赖于这样一个观察:关键在于分配预约站并更新流水线控制表格。一种方法是让这一步在半个时钟周期内运行,这样就可以在一个时钟周期内处理两条指令;但是这种方法不能很容易地扩展到每个时钟周期处理四条指令的情况。另一种方法是构建必要的逻辑来同时处理两条或更多指令,包括指令之间可能存在的任何依赖关系。现代超标量处理器可以发射四条或更多指令每个时钟周期,可能包含两种方法:它们既流水线化又扩展了发射逻辑。
一个关键的观察是我们不能简单地通过流水线化来解决问题。通过让指令发射需要多个时钟周期来完成,因为新的指令每个时钟周期都在发射,我们必须能够分配预约站并更新流水线表格,以便下一个时钟周期发射的依赖指令可以使用更新后的信息。
我们在动态调度的超标量处理器中,发射指令的步骤是一个最基本的瓶颈。在一个现代的超标量处理器中,每个时钟周期允许发射的所有可能的依赖指令组合都必须考虑。因为可能性的数量随着每个时钟周期可以发射的指令数量的平方而增长,所以发射步骤是超过四条指令每个时钟周期的尝试的一个可能的瓶颈。我们可以把细节概括为以下步骤,来描述在一个动态调度的超标量处理器中,每个时钟周期最多发射n条指令时,更新发射逻辑和预约表格的基本策略:
- 为每一条可能在下一个发射包中发射的指令分配一个预约站和一个重排序缓冲区。这个分配可以在指令类型未知之前完成,只需按顺序把重排序缓冲区条目预分配给包中的指令,使用n个可用的重排序缓冲区条目,并确保有足够的预约站来发射整个包,不管它包含什么。通过限制一类指令(比如说,一个浮点、一个整数、一个加载、一个存储)的数量,必要的预约站可以预分配。如果没有足够的预约站(比如说,程序中接下来的几条指令都是同一种类型),那么包就会被打断,只有一部分指令按照原始程序顺序被发射。包中剩余的指令可以放在下一个包中等待发射。
- 分析发射包中指令之间的所有依赖关系。
- 如果包中的一条指令依赖于包中更早的一条指令,就用分配好的重排序缓冲区编号来更新被依赖指令的预约表。否则,就用现有的预约表和重排序缓冲区信息来更新发射指令的预约表条目。当然,让这些步骤变得非常复杂的是它们都要在一个时钟周期内并行地完成
在流水线的后端,我们必须能够每个时钟周期完成和提交多条指令。这些步骤比发射问题稍微容易一些,因为能够在同一个时钟周期提交的多条指令必须已经处理并解决了任何依赖关系。