前言
现代的通用处理器从指令集方面来看,可以分为精简指令集(RISC)和复杂指令集(CISC)这两种,CISC伴随着处理器的诞生,最开始的处理器都是使用这种指令集,力求在一条指令内完成很多的事情,并且使用尽可能多的指令,覆盖到各种各样的操作,这样可以降低对存储器的需求,并且简化编译器的设计; 随着时间的推移,当存储器和编译器都不再是问题的时候,RISC就产生了,它基于一个观察: 80%的CISC指令只在20%的时间被使用,这样可以只将经常使用的20%的CISC指令使用硬件来实现,剩余80%的指令可以使用软件来模拟,因此可以简化硬件的设计,同时,为了便于使用流水线,不像CISC指令那样,指令的长度可以变化,RISC指令采用了等长的方法,每条RISC指令的长度都是32位,这样可以降低解码的难度,易于流水线的设计,这些因素都使RISC指令集的处理器有着更高的频率,同时功耗和成本相对也更低,虽然有时候为了完成一个任务,它需要使用更多的指令来实现,但是考虑到它有着更高的频率,所以综合来看,执行时间也未必就会变长。
同时,现代的通用处理器从实现方式来看,可以分为标量(scalar)和超标量(superscalar)这两种,标量处理器每周期最多只能执行一条指令,它一般都是按照程序中指定的顺序来执行指令,这称之为顺序执行(inorder); 这种方式制约了处理器性能的提高,于是就有了超标量处理器,它能够在一个周期内执行多条指令,这样可以缩短一个程序的执行时间,指令在处理器中可以按照程序中指定的顺序来执行,也可以不遵循这个顺序,只要指令的源操作数都准备好了,它就可以被执行,这种方式就称为乱序执行(outoforder),当然,乱序执行也不能够改变程序本来的功能,在超标量处理器中,还需要一些方法来使这些乱序执行的指令看起来仍然按照程序中指定的顺序更改处理器的状态,在超标量处理器中的这些功能注定了它的复杂性,相比于标量处理器,它需要更多的硬件资源和更高的功耗,这就是性能提升所带来的代价。
上述的这两种划分(CISC和RISC,以及scalar和superscalar)是相互正交的,也就是说,它们可以有四种组合,这四种组合在处理器的世界中均有使用,分别介绍如下。
Scalar CISC: 这是处理器最开始的时候采用的结构,这种结构一般会直接对CISC指令进行解码,甚至可能不使用流水线,典型的例子就是Intel的8086、80286等处理器,它们均采用了这种结构,随着时间的推移,这种结构已经逐渐被淘汰了。
Scalar RISC: 这是RISC处理器刚刚出现的时候采用的结构,由于RISC指令集降低了对硬件的需求,并且便于流水线的实现,所以这种结构的处理器多使用流水线来提高性能,它的主频一般比较高,并且成本也很低,在RISC处理器出现的早期,典型的例子就是MIPS R2000和R3000、Alpha 21064和ARM7等众多的RISC处理器,这种结构到了现在依然有着旺盛的生命力,在嵌入式低功耗领域的处理器均采用了这种结构,例如MIPS的4K系列处理器、ARM的Cortex M和Cortex R系列处理器等。
Superscalar RISC: 随着对处理器的性能需求越来越高,每周期执行一条指令的处理器已经不能满足要求了,于是在RISC处理器中率先采用了每周期执行多条指令的结构,为什么它会首先出现在RISC处理器中呢?这是因为RISC指令集比较规整,便于使用硬件来实现,而且早期的RISC处理器多面向服务器等性能要求比较高的领域,这就驱动着RSIC处理器采用更多的方法来提高性能,于是就有了超标量的结构,早期的很多RISC处理器都使用了这种结构,例如MIPS R10000、Alpha 21264和PowerPC 620等处理器,即使到了现在,在嵌入式的高性能应用领域,仍然继续采用了超标量结构,例如MIPS 74K系列处理器、ARM Cortex A9和A15处理器等。
Superscalar CISC: 尽管CISC指令集并不容易使用流水线来实现,更很难直接使用超标量结构来实现,但是Intel和AMD采用了一些方法来解决这种问题,使CISC处理器仍然可以每周期执行多条指令,并且采用流水线结构来提高频率,它们所使用的方法就是在处理器内部使用硬件,将一条CISC指令转化为多条RISC指令,这样就可以充分利用RISC指令集的优势了,当然,这比普通的RISC处理器要付出更多的硬件资源,功耗也会偏大,典型的例子就是当代Intel的全系列处理器,例如Pentium 4、Pentium M、Core和Core2等处理器。
还需要注意的是: 上述的划分只是针对通用的处理器来说的,很多专用领域的处理器,还有其他的架构和指令集,例如VLIW,它们很难保证程序的兼容性,但是在特定的应用场合,的确能获得比通用处理器更好的性能,当然,这不在本书讨论的范围之内。
在上述的4种结构中,Superscalar RISC处理器的设计是本书重点关注的内容,而这种处理器的流水线则是贯穿本书的主线,一条指令从程序存储器中取出来之后,需要经过流水线的各个阶段,最后才能够得到结果,并更新处理器的状态,本书正是遵循这条指令的轨迹来进行组织的:
第1章主要介绍普通处理器和超标量处理器的一些背景知识。
第2章开始讲述Cache,这是由于一般的指令都是从ICache中取出来并送到流水线中的,因此流水线始于ICache,当然,在处理器中也存在DCache,它也会在这一章进行讲述。不同结构的Cache对处理器的性能有着重要的影响,尤其是在超标量处理器中,每周期需要同时执行多条指令,这给Cache的设计带来了一些挑战。
第3章主要介绍虚拟存储器(Virtual Memory),因为处理器在取指令的时候,如果送出的是虚拟地址,那么首先需要被转化为物理地址,然后才能够取得指令,对数据的访问也是类似的,虚拟存储器是现代操作系统运行的基础,在处理器中需要软硬件配合工作,才可以对虚拟存储器提供完整的支持。
第4章主要介绍分支预测(Branch Prediction),它也是取指令阶段发生的事情,因为超标量处理器的流水线比较深,导致分支指令的结果在很晚的时间才可以得到,一旦发现这个结果和预想的不一样,那么流水线中很多的指令都是没有用的,需要抹掉并从正确的地址取指令,这样就降低了处理器的执行效率,因此需要对分支指令使用比较准确的预测算法,从而在取指令阶段就可以提前知道分支指令的结果。
第5章主要讲述指令集体系(ISA),一旦指令从存储器中取出来之后,下一步就需要进行解码了,不同的指令集需要不同的解码方式,因此本书在介绍指令的解码之前,首先对基本的RISC指令集进行介绍,这样便于对后续流水线的理解。
第6章就对指令解码(Decode)进行了介绍,在超标量处理器中,由于每周期需要对多条指令进行解码,这会引入一些新的问题,比如指令之间存在的相关性,以及一些复杂指令的处理等,相比于普通的处理器,它的解码过程要复杂一些,但是相比于超标量的CISC处理器,这种解码过程仍然是比较简单的。
第7章主要介绍硬件的寄存器重命名(Register Renaming),指令经过解码之后,就可以得到它的源寄存器和目的寄存器了,但是为了尽量地并行执行指令,需要消除指令之间存在的假的相关性,这些相关性都是和寄存器的名字相关的,通过使用不同的寄存器名字,可以消除这些相关性,于是在处理器内部使用了数量多于指令集中定义的寄存器,称之为物理寄存器,而指令集中定义的寄存器则称为逻辑寄存器,寄存器重命名的过程就是将逻辑寄存器动态地映射到不同的物理寄存器,以消除指令之间存在的假的相关性,从而使这些指令可以并行执行。
第8章主要介绍指令的发射(Issue),当指令经过寄存器重命名之后,就可以在处理器内部的功能单元(FU)中执行了,但是,为了获得最高的性能,超标量处理器多采用乱序执行的方式,只要一条指令的操作数准备好了,即使它之前的指令还没有准备好,它也可以送到FU中执行,这种方式可以最大限度地利用处理器内部的硬件资源,从而提高处理器的执行效率,而发射阶段正是用来实现这个功能的,所有经过寄存器重命名的指令都会放到一个缓存中,这个缓存称为发射队列(Issue Queue),在其中监测每条指令是否已经准备好了,并按照一定的算法,从那些已经准备好的指令中选择合适的指令送到FU中执行,这个过程就称为发射,指令到了这个阶段,就变为乱序执行了,而在这个阶段之前,都遵循着程序中指定的顺序。
第9章主要介绍指令在功能单元的执行,指令被发射之后,就会到对应的FU中开始执行,不同种类的指令需要不同的FU,在超标量处理器中,都会使用多个FU,它们可以并行地执行不同的指令,本章除了介绍处理器中常见的FU之外,还会介绍旁路网络(Bypassing Network),它可以缩短相关指令之间执行的时间,但是却使处理器内部的布线资源变得更复杂,因此现代的一些处理器采用了Cluster结构来缓解这种矛盾,同时,访问存储器的load/store指令也需要一些特殊的方法来加速它们的执行速度。
第10章主要介绍流水线的最后一个阶段: 提交(Commit),指令经过FU的执行而得到结果后,并不会马上使用这个结果对处理器的状态进行更新,这是由于指令的执行是按照乱序来进行的,由于分支预测失败(misprediction)和异常(exception)等原因,一条指令的结果未必是正确的,而且,为了使程序在处理器内部的执行看起来和程序中指定的顺序是一样的(这是串行程序必需的),也需要这些乱序执行的指令按照程序中指定的顺序对处理器的状态进行更新,为了实现这个功能,一条指令在FU中执行完毕后,并不会马上对处理器的状态进行更新,而是先将它的结果写到一个缓存中,这个缓存称为重排序缓存(Reorder Buffer,ROB),在流水线的寄存器重命名阶段,每条指令都已经按照程序中指定的顺序写到了ROB中,当一条指令在FU中执行完毕,就可以将这个结果写到ROB对应的地方,当ROB中最旧的那条指令(或者几条指令)已经得到结果,并且不存在分支预测失败或者异常等特殊情况的话,它就可以离开ROB,使用它的结果对处理器的状态进行更新,这个过程称为指令的退休(retire),一旦指令经过了这个状态,它就再也不能够被撤销了。
第11章介绍现实世界中的一个Superscalar RISC处理器: Alpha 21264处理器,它是处理器发展史上一个非常经典的例子,虽然Alpha系列处理器随着它的东家DEC公司的消失而退出了历史的舞台,但是它影响了之后出现的很多处理器,本章对Alpha 21264处理器进行了详细的介绍。
由于本书涉及的内容比较多,很多知识点无法详细地展开,读者可以自行对感兴趣的内容进行更深入的学习。
在本书的编写过程中,编者参阅了各章节所列出的参考文献,在此对原作者表示敬意和感谢。在本书的出版过程中,得到了清华大学出版社的刘向威博士的大力支持,在此特别致以衷心的感谢!
由于时间仓促和作者水平有限,书中的错误和不妥在所难免,希望广大读者批评指正。
姚永斌
2014年1月于北京