5.1概述
在第4章中,我们看到一台计算机的性能由三个关键因素决定:指令数目、时钟周期和每条指令所需时钟周期数(CPI)。我们在第2、3章学习的编译器和指令集系统决定了一个程序所需的指令数目。而处理器的实现方式则决定了时钟周期长度和每条指令所需的时钟周期数。在本章中,我们为MIPS指令系统的两种不同实现方式建立数据通路和控制单元。
本章阐述了实现一个处理器所涉及的原理和技术。本节对整章内容进行了高度抽象和简单概括,后面的各节分别介绍如何建立数据通路,如何设计一个能够执行简化MIPS指令集的处理器,最后引出实现类似IA-32的复杂指令集所必需的概念。
对于渴望了解如何用硬件实现各种指令的读者而言,5.3和5.4节提供了所需的补充材料。这两章应当做深入阅读。
本章各节覆盖了现代硬件实现的一般方法,也包括非常复杂的处理器。这些章节还解释了有限状态控制的基本原理及其不同实现方法,包括微程序实现方式。那些有兴趣深入了解处理器及其性能的读者会发现5.4和5.5节很有用。5.7节介绍微程序设计,这是一种用来实现负载控制机制的方法;在5.8中可以看到如何使用硬件设计语言和CAD工具来进行硬件实现。
5.1.1MIPS基本实现
首先来看我们要实现的核心MIPS指令集系统的一个子集:
--存储访问指令 lw和sw
--算数逻辑指令 add、sub、and、or和slt
--跳转指令 beq和j
在讨论此实现时,我们将会看到指令集如何在多个方面对具体实现方式产生的影响,以及不同实现策略是如何影响时钟速率和机器CPI值的。许多在第四章介绍的关键的设计原理,如加快常用操作的速度和简单来自规整的指导思想,在这个过程中都将得到体现。并且,在本章及下一章中用于实现MIPS子集的大多数概念与设计其他的多数计算机的基本思想是一致的,这包括从高性能服务器到通用微服务器到嵌入式处理器等,而后者将越来越广泛地应用在从VCR到汽车等各种产品中。
5.1.2处理器实现概述
第2,3章介绍了MIPS的核心指令,包括整体算数逻辑指令、存储访问指令及分支指令。这些指令的实现过程大致相同,而从具体的指令类型无关。每条指令执行的前两个步骤是一样的:
(1)根据程序计数器(PC)从内存中取出指令,PC指向存放该指令的地址。
(2)通过指令字段的内容,选择读取一个或两个寄存器。对于取字指令,只需读取一个寄存器,而其他大多数指令则要求读取两个寄存器。
这两步之后,为完成指令而进行的操作将取决于具体的指令类型。幸运的是,对三种指令类型的每一种而言,其动作大致相同而与具体操作码无关。
即使是不同类型的指令,也有一定的共性。例如,所有类型的指令在读取寄存器后,都要使用算数逻辑单元(ALU)。存储指令用ALU计算地址,算术逻辑指令用来执行运算,分支指令用ALU进行比较。可以看出,指令的简洁和规整使许多指令的执行很相似,因而简化了实现过程。
使用ALU之后,不同类型的指令需要进行不同的操作。存储访问指令需要对存储单元进行读出或写入而访问存储器。算数逻辑指令需要将ALU产生的数据写回寄存器中。而分支指令会根据比较的结果,决定是否需要更改下一条指令的地址。
第一,图5-1显示某些单元的数据来雨阿奴于两个不同的源头。例如,写入PC的值可能从两个加法器中任一个得到,还有写入寄存器堆的数据可能来源于ALU或者内存。实际上,这些数据线不可能简单地连在一起;必须增加器件用来从多个数据源中选择一个传输给目的单元。虽然这个选择设备称为数据选择器更合适,它还是同城称为多路复用器。多路复用器根据空盒子信啊的设置从多个输入中进行选择。
第二,图中有些单元必须根据指令的类型进行控制。例如,数据存储器在执行加载指令时进行读操作,在执行存储指令时进行写操作。在执行加载和算术-逻辑指令时,寄存器堆必须进行写操作。当然,像在第3章看到的,ALU也必须执行几种不同操作中的一个。类似多路选择器,这些操作由控制线信号决定,而控制线则根据指令码的不同域设置。
5.2逻辑设计规则
在考虑计算机的设计时,必须决定机器的逻辑实现如何操作以及机器的时钟。本节将讨论一些本章经常使用的有关数字逻辑的关键思想。
MIPS实现中的功能单元由两种不同类型的逻辑单元组成:计算数据值的组合单元和包含状态的时序单元。操作数据值的单元是组合式的,也就是说它们的输出只依赖于当前的输入。给出相同的输入,组合逻辑单元总能产生相同的输出。ALU就是组合逻辑单元。因为ALU没有内部状态,对于给定的一组输入,ALU总是生成相同的输出。
本设计中其他的单元不是组合逻辑式的,它们包含某种状态(state)。如果一个单元拥有内部存储功能,它就会包含状态。因此称这些单元为状态单元,因为关机后,可以通过给这些状态单元设置为其各自原来的值而重启计算机。并且,如果保存并恢复了状态单元的值,机器就好像没有断电一样继续运行。因此,这些状态单元完全刻画了机器的特性。指令和数据存储器还有寄存器都属于状态单元。
一个状态单元至少有两个输入和一个输出。这两个输入分别为待写入该单元的数据值和决定何时写入的时钟信号。状态单元的输出提供了在先前某个时钟周期写入该单元的数据值。比如,逻辑上最简单的状态单元是一个D触发器,它有两个输入(一个数据值和一个时钟)和一个输出。除了触发器,MIPS的实现中还用了另外两种状态单元:存储器和寄存器。时钟决定状态单元何时被写入;状态单元随时可读。
包含状态的逻辑部件又称为时序逻辑电路,因为他们的输出由输入和内部状态共同决定。比如,代表寄存器的功能单元的输出取决于所提供的寄存器数和以前写入寄存器的内容。本书使用已有效的信号表示一个逻辑高的信号,有效信号是指应该设置为逻辑高的信号,无效信号和已无效信号表示逻辑低
时钟同步方法
时钟同步方法规定了信号可以读出和写入的时刻。规定信号读写的时间很重要,因为若一个信号同时被读出和写入,则所读出的信号可能是写入前的值,也可能是新写入的值,甚至是两者的混合值。
为简单起见,假定采用边沿触发的时钟同步方法,它意味着时序逻辑单元中存储的所有值都只允许在时钟跳变的边沿时改变。也就是说,状态单元都只在时钟跳变的边沿改变其存储内容。因为只有状态单元能存储数据值,所有的组合逻辑都必须从状态单元集合接收输入,并将输出写入状态单元集合中。其输入为从前某时钟周期写入的数据,其输出可供应以后的某个时钟周期使用。
为简单起见,当某个状态单元在每个有效的时钟边沿都进行写入操作时,我们不画出它的写控制信号。相反,若某个状态单元不是咩个周期都进行更新,那么它就要有一个显式的写控制信号。卸空孩子喜好有效且时钟边沿到来时,状态单元才能改变状态。
使用边沿触发的方法,可以在一个之中周期内读出一个寄存器的值,并经过一些组合逻辑,再次写入该寄存器。选择在时钟的上升沿还是下降沿进行写操作无关机械能要,因为组合逻辑的输入只有在有所规定的时钟边沿才可能发生变化。使用边沿触发定时方法,不存在同一个时钟周期内的反馈,图5-4所示的逻辑可以正确工作。
几乎所有这些状态和逻辑单元的输入和输入都为32位,因为处理器处理的大多数据的宽度为32位。若某单元的输入或输入不是32位,会被特别指出。图中用粗线表示总线(bus),即宽度为1位以上的信号。有时要把几根总线合契合构成一个更宽的总线;比如,可能将2根16位总线合成一根32位总线。在这种情况下,总线标注将做出相应说明。另外还加上箭头以指明单元间数据传输的方向。最后,用不同粗细区分数据信号和控制信号;这两者的差别将在本章的后面更清楚地体现。
5.3数据通路的建立
开始设计数据通路时,比较合理的方法是先看看每种MIPS指令执行时所需的主要部件。先来确定每条指令需要什么数据通路部件,再用这些部件为每种指令类型建立其数据通路。在指出数据通路部件的同时,确立它们对应的控制信号。
首先需要的部件是一个存储程序指令的地方:一个存储单元用来存储程序的指令,并根据所给地址提供指令,如图5-5所示。当前指令的地址也必须存放在一个状态单元中,称之为程序计数器(PC).最后,需要一个加法器增加PC的值以指向下条指令的地址。这个加法器是一个组合单元,可以用第3章设计的ALU实现。
要执行任何一条指令,首先要从存储器中将指令驱虎。为准备执行下一条指令,也必须把程序计数器加到指向下条指令,即向后移动4个字节。此时所需的取指令以及增加PC以获得下一时序指令的地址相对应的数据通路。
现在讨论R型指令。这种指令读两个寄存器,对它们的内容进行ALU操作,再写回结果。这类指令称为R型指令或算数逻辑指令。这个指令集合包括第二章介绍的add、sub、and、or和slt指令。
处理器的32个通用寄存器位于一个叫做寄存器堆的结构中。一个寄存器堆就是一个寄存器几个,其中的寄存器都可以通过指定相应的寄存器序号来进行读写。寄存器堆包含了计算机的寄存器状态。另外,还需要一个ALU来对从寄存器读出的数值进行运算。
对每条R型指令,都要从寄存器中读出一个数据字,再写入一个数据。为从寄存器中读出一个数据字,寄存器堆需要一个输入信号指定要读的寄存器号和输出信号指示从寄存器堆读出的结果。为写入一个数据字,寄存器堆要有连个读入:一个指定要写的寄存器序号,另一个提供要写的数据。寄存器堆总是根据寄存器序号输出相应的寄存器内容,而写操作由写控制信号控制,在学操作发生的时钟边沿,写控制信号必须是已有效的。
创建单条数据通路:
现在已经检视过单个指令类需要的数据通路部件,我们可以将它们组合到单个数据通路并加上控制来完成实现。最简单的数据通路会尝试在一个时钟周期执行所有指令。这意味着对每条指令,数据通路资源不能重复使用,因此任何要使用不止一次的单元都要复制。因此我们需要一个数据存储器外的指令存储器。尽管一些功能单元需要复制,许多单元仍然可以由不同的指令流共享。
要在两个不同的指令类间共享数据通路单元,需要允许多个到功能单元输入端的连接,使用多路复用器和控制信号在对个输入间选择。
例题:构建数据通路
算数逻辑(R类型)指令的操作和内存指令十分相似。关键的区别如下:
--算术逻辑指令通过来自两个寄存器的输入使用ALU进行计算。内存指令也能使用ALUop进行地址计算,尽管第二个输入视符号扩展的16位偏移字段
--存储到目标寄存器的值来自ALU(大型指令)或者存储器(load指令)
说明如何为存储引用指令和算术逻辑指令的操作部分构建数据通路,其中只使用了一个寄存器堆和一个ALU来处理两类指令,加上任意必需的多路复用器。
解:要创建只有单个寄存器堆和ALU的数据通路,我们必须为第二个ALU输入以及存入寄存器堆的数据支持两个不同的源。因此,一个复用器放在ALU输入处,另一个放在寄存器堆的数据输入处。
现在,通过增加指令取的数据通路,R类型和内存指令的数据通路以及分支的数据通路,我们可以组合各个部分得到MIPS体系结构的简单数据通路。
分支指令使用主ALU进行寄存器操作数比较,因此必须用图5-9的假发器计算分支目标地址。还要一个额外的复用器,用于选择将顺序指令地址(PC+4)或分支目标地址写入PC。现在完成了简单数据通路,可以加上控制单元了。控制淡雅uu呢必须能接受输入并对每个状态量、选择器控制和ALU控制写信号。在设计控制单元时,最好先设计ALU控制,因为它的变化形式比较多。
5.4一个简单的实现方案
本节将讨论被认为是最简单的MIPS子集实现方案。通过把上一节中数据通路的各部分集成起来,并加入必要的控制线路,可得到这个简单的数据通路及其控制。这个简单的实现方案包括取字(lw)、存字(sw),等值分支(beq)和算术逻辑指令add、sub、and、or和小于则置位。稍后还会将它扩充为包括跳转指令(j).
5.4.1ALU的控制
对于取字和存字指令,ALU进行假发运算获取存储地址。对于R型指令,根据指令低六位的功能字段(功能),确定ALU执行5中操作中的一种(与、或、加、减、小于则置1)。对等值分支指令,ALU做减法操作。
可以用一个小的控制单元生成4位的ALU控制输入,这个功能单元的输入为指令的控制字段和一个占2位的称为ALUOp的控制字段。ALUop指名要进行的操作是存/取数需要的加法(00),beq需要的减法(01),还是由指令的功能字段决定(10).该ALU控制单元输出4位信号,即前面介绍的6种4位组合之一,直接对ALU进行控制。
下图说明了怎样根据2位的ALUop和6位的功能码来设置ALU的控制输入。为更加完整,对ALUop各位与指令操作码之间的关系也进行了说明。在本章的后面将会看到怎样由主控单元生成ALUop。
这种多层解码的手法(即,主控单元生成ALUop位作为ALU控制的输入,ALU控制再生成真正控制ALU单元的信号)是一种常用的实现技术。使用多层控制可以减小主控制单元的规模。使用多个小控制单元还可能提高控制单元的速度。这种优化是很重要的,因为控制单元的性能通常很关键。把2位的ALUop字段和6位的功能字段映射为3位的ALU操作控制位,有多种不同方法。因为功能字段的64种可能取值中只有很小一部分有意义,并且仅当ALUop位取值为10时才使用功能字段,可以用一个逻辑单元去识别可能取的值,以生成正确的ALU控制位。
为设计这个逻辑单元,有必要为ALUop位和功能码字段的有意义的组合生成一张真值表。这个真值表说明了3位的ALU控制信号怎样根据这两个输入字段得到。由于完整的真值表很大,并且对其中许多种输入组合的ALU控制的值都不关心,只列出了使ALU控制有确定值的真值表项。在本章中,均只列出了那些必须有效的真值表项,而省略那些恒为0的项或无关项。
由于在许多情况下对某些输入的取值无关,为了使真值表简练,也列出无关项。真值表中的无关项('X')表明,输出与该列对应的输入取值无关
5.4.2主控单元的设计
在设计完以功能码和2位信号作为控制输入的ALU后,现在来看看控制的其他部分。开始之前,首先看看一条指令的各个字段和控制线路。为了理解怎样将指令的各个字段与数据通路相连,需要复习一下三种指令类型的格式:R-型指令、分支指令和存/取指令
遵循以下几条指令格式的主要规则:
--op字段,亦称操作码,总是31:26位。用Op[5:0]来表示
--对于R-型指令、分支指令和存数指令,要读取的两个寄存器为rs和rt字段,分别为25:21位和20:16位
--存取数指令的基址寄存器字段在25:21位(rs字段)
--等值分支指令、存数/取数指令的16位偏移量在15:0位
--有两个地方存放目标寄存器。对存数指令20:16位,对R-型指令为15:11位。所以需要一个多路复用器,以指示要写的寄存器序号在指令的哪个字段中。
根据这些信息,可以给简单的数据通路加上指令标记和一个额外的多路复用器(给寄存器堆的写2寄存器号输入)。
5.4.3为什么不使用单周期实现方式
虽然单周期设计也可以正确地运作,但现代设计种并不采用这种方式,因为它的效率太低。究其原因,是在单周期设计中,时钟周期对所有指令等长,即CPI为1.当然,时钟周期要由计算机中可能的最长路径决定。这条路基几乎肯定是一条取数指令,它顺序使用5个功能单元:指令存储器、寄存器堆、ALU、数据存储器、寄存器堆。虽然,CPI为1,单周期实现方式的总体性能并不见得很好,因为某些指令类型本来可在更短的时钟周期内完成。
5.5多周期实现方案
在前面的一个例子中,曾根据所需进行的功能单元的操作,将一条指令的执行分解为一系列步骤。可以用这些步骤来生成一个多周期实现方案。在这个方案中,指令的每一步将占用一个时钟周期。一个功能单元可以在一条指令的执行过程中使用多次,只要是在不同周期中。这种共享可减少所需硬件数量。允许指令的执行占用不同的周期数和功能单元可在单指令执行过程中共享,是多周期设计的主要优点。
多周期的轮廓:
--指令和数据使用同一个存储器单元。
--只有一个ALU,而不是一个ALU和两个加法器
--每个主要的功能单元都加上了一个或多个寄存器存储输出值,以便在后面的时钟周期中使用。
一个时钟周期结束时,在以后的周期中将用到的所有数据都必须存储在状态单元中。以后时钟周期中随后的指令将要用到的数据被存入一个程序员可见的状态单元中(即寄存器堆、PC或存储器)。相反,在以后的周期中同一指令要用到的数据必须存入这些附加寄存器中。
这样,附加寄存器放在哪里将由两个因素决定:哪些组合单元适合在单个周期中使用,以及哪些数据将在指令执行过程中后面的各个周期中使用。在这个多周期设计中,假设一个时钟周期内最多只能完成下列操作之一:一次访存、一次寄存堆访问(2次读或1次写)或一个ALU操作。于是这三个功能单元的任何一个(存储器、寄存器堆或ALU)产生的数据必须存储在一个临时寄存器中,以供后面的周期使用。如果没有被缓存,则可能出现周期竞争,导致使用不正确的值。
为了满足这些要求,加入下面的临时寄存器:
--指令寄存器(IR)和存储器数据寄存器(MDR)分别用于存储从存储器读取的指令和数据的输出。使用两个独立的寄存器,是因为在同一周期中两者的值都要被用到。
--寄存器A、B,用于存储从寄存器堆中读出的寄存器操作数
--寄存器ALUOut,用于存储ALU的输出