指令系统体系结构概览(C05)。
指令系统设计
每条计算机指令均有一个操作码和0或多个操作数。前章的MARIE指令长度为16位,至多只有一个操作数。根据ISA的不同,指令使用的二进制位数也可能不同(16位,32位,64位),每条指令允许使用的操作数的个数可能不同,指令类型和指令处理的操作数的类型也可能不同。具体在特征上可能存在以下差别:1)操作数在CPU中的存储方式(堆栈结构或寄存器);2)指令直接作用的操作数数目(常用的操作数个数为0,1,2,3);3)操作数的位置(寄存器-寄存器,寄存器-存储器,存储器-存储器);4)操作(操作类型,指令是否可以访问存储器);5)操作数的类型和长度。
衡量标准
ISA的效能通过以下因素衡量:程序执行指令时占用内存空间的大小;指令系统复杂程度,主要指指令执行所需要的译码数量和指令所执行任务的复杂性;指令长度;指令系统中指令的总数目。
- 指令一般越短越好,较短的指令占用较少的内存空间,并且提取指令速度更快。但采取短指令会限制指令的数量(受到能够编码的二进制数的位数限制),同样也会限制操作数的大小和数量。
- 固定长度的指令的译码容易但浪费空间,但固定长度的指令系统不表示必须使用固定数量的操作数。可以设计一个指令总长度固定的ISA,但可以允许其操作数域的位数根据需要改变,称为拓展操作码(expanding opcode)。
- 存储器的组成形势会影响指令的格式。如果存储器为16或32位字,如果不是按字节编址则很难访问到一个单一字符,因此有些16/32/64位机器也是按照字节编址。
- 存在多种不同类型的寻址方法;字节存储的小端大端位序问题;ISA需要多少寄存器并如何组织这些寄存器。
小端和大端位序问题
位端(endian)指的是计算机体系结构中的“位序”(byte order),即计算机存储一个多字节数据时,多个字节的排列方式。当今所有的计算机体系结构都是按字节编址,对于存储一个2字节整数,将低位的字节首先存放到低位地址,高位字节存放到高位地址,此时较低地址的字节就是数据的低位,这种方式称为小端(little-endian);对于低位地址存放高位数据的方式称为大端(big-endian)。大部分UNIX计算机和新式RISC体系结构为大端机器,PC计算机为小端机器。Intel总是使用小端方式设计机器,而Motorola总采用大端。有的CPU既可以处理小端问题也可以处理大端问题。
- 将32位16进制数据12345678存储在地址0的存储单元,每个十六进制数字需要半字节。对应两种不同的存储方式列出下表。
编址————> 00 01 10 11 大端位序 12 34 56 78 小端位序 78 56 34 12 - 大端位序存储方式更自然,便于阅读16进制编写的程序端。可以通过检查最高位的符号位判读数字正负,而小端需要知道数值长度,再跳过中间字节找到最高位。大端位序的机器存储整数和字符串采用相同次序,在某些字符串操作时更快。大部分位图映射格式图像都采用“最高位在字符串左边”的变换方法,即对于像素大于一个字节的数据可以直接按照大端位序处理,因此对于小端位序在处理较大图形对象时可能会性能受限。当对采用例如赫夫曼和LZW这类编码的压缩数据译码时,若数据采用大端位序存储,则实际的编码字可以被当作进入到某个查询表中的一个索引使用。
- 小端位序在高精度算术运算上速度更快更方便。大部分采用大端位序的体系结构都不允许计算机字按照非字地址边界的方法写数据字,例如一个字由2或4字节组成,写数据字时必须从偶数编号的字节地址开始,浪费空间。小端机器允许进行奇数地址的读写。
计算机网络都采用大端位序的体系结构。如果小端位序的机器要将整数数据(如网络设备地址)传送到网络上,必须将数据转换成网络要求的字节次序,从网络上接收数据时也需要将这些数据转换成本地表示形式。BMP图形格式是小端位序,如果在大端位序机器上查看BMP图形必须先反转数据位序。Adobe Photoshop采用大端格式,GIF是小端格式,JPEG是大端格式,Macpaint使用大端格式,PC Paintbrush是小端格式,RTP使用小端位序,Sun光栅文件是大端格式。WAV、AVI、TIFF、XWD等同时支持两种格式。
堆栈和寄存器
CPU数据存储方式是区分不同指令系统体系结构的最基本方法。包含:堆栈体系结构,累加器体系结构和通用寄存器(GPR)体系结构。
堆栈体系结构(stack architecture)的计算机使用一个堆栈来执行各种指令,指令的操作数隐含的存放在堆栈顶部。按照堆栈体系结构设计的机器通常具有好的编码密度和一个简单的表达式估值模型。但因为不能对堆栈进行随机访问,使得采用堆栈结构的机器很难产生高效率的编码。如果存储器速度快,使用堆栈体系结构较好。
- 累加器体系结构(Accumulator architecture)计算机,如MARIE,将其中一个操作数隐含在累加器中,最大限度地降低机器内部复杂性,并且允许使用非常短的指令。由于累加器只是临时存储,需要对存储器的访问非常频繁。
- 通用寄存器体系结构(general purpose register architecture)计算机,采用多个通用寄存器组,是当今计算机体系结构最广泛的模型。寄存器组的访问速度比存储器快得多,也方便编译器处理。由于硬件价格急剧下降,现在可以以较小成本增加大量数目的寄存器。如果存储器速度较慢,通常采用通用寄存器体系结构。但由于所有操作数必须加以命名,因此使用寄存器结构会产生较长指令,导致较长的取指和译码时间。对ISA设计人员来说,实现短指令是一个非常重要的目标。通用寄存器体系结构可以根据指令的操作数所处的位置分成三种类型。
- 存储器-存储器(memory-memory)体系结构可以有两个或三个操作数位于存储器内,允许有一条执行某种操作而不需要有任何操作数的指令存放在某个寄存器中。
- 寄存器-存储器(register-memory)体系结构采用混合方式,要求至少有一个操作数在寄存器中和一个操作数在存储器中。
- 装入-存储(load-store)体系结构需要在任何对数据的操作前把数据装入寄存器中。
Intel和Motorola的计算机属于寄存器-存储器体系结构,数字仪器公司的VAX计算机体系结构实行的是存储器-存储器操作,SPARC、MIPS、ALPHA和PowerPC都是装入-存储式体系结构计算机。
操作数的数目和指令长度
描述计算机体系结构的传统方法是制定每条指令中所包含的操作数,或者说地址的最大数目。
- MARIE采用固定长度的指令,包括4位操作码和12位操作数。在现在的计算机体系结构中,指令构成的格式有固定长度和可变长度两种。
- 固定长度(fixed length):会浪费一定存储空间,但执行速度更快。在采用指令层次的流水线结构时性能更好。
- 可变长度(variable length):译码复杂,但节省存储空间。
实际设计中,通常会采用两到三种不同的指令长度,这样可以有不同的位的指令组合形式,便于简化指令的区分和译码。指令的长度必须配合机器字的长度,如果指令的长度严格等于机器字的长度,则将指令存储到主存储器时,指令间的对齐问题就会非常完美。因为存储器要编址,因此采用实际机器字长度的1/4,1/2,2倍或3倍长度的指令会浪费不少的存储空间。可变长度的指令同样也会造成存储空间的浪费。
- 最常用的指令格式包括0,1,2或3个操作数。通常情况下,算术运算或逻辑运算需要2个操作数,如果将累加器作为一个隐藏的操作数,则两个操作数的操作可以按照一个操作数指令的方式执行。同理,使用一个堆栈结构可以允许有不带操作数的指令。
只有操作码 0地址 操作码+1个地址 通常只有一个存储器地址 操作码+2个地址 通常两个寄存器地址,或一个寄存器地址加上一个存储器地址 操作码+3个地址 通常是三个寄存器地址或寄存器和存储器的某种组合 对每种体系结构来说,每条指令允许的最大操作数的数目是有限制的。不带操作数的机器指令必须使用堆栈来执行在逻辑上需要一个或两个操作数的操作。对于两个操作数的操作,堆栈结构采用堆栈顶部最上面的两个元素作为操作数。如Add指令,将栈顶两个元素相加并弹出,再将求和结果压入堆栈顶部。对于操作次序不能交换的各种操作,同样对堆栈顶部最上面的两个数据元素按次序进行。这种组成结构对于反向波兰表达式(reverse Polish notation,RTN)编写的长算术表达式非常有效,逆波兰表示法将保持计算次序的括号都消除了。
Z = ( X × Y ) + ( W × U ) 3个操作数 Mult R1, X, Y; Mult R2, W, U; Add Z, R2, R1 2个操作数 Load R1, X; Mult R1, Y; Load R2, W; Mult R2, U; Add R1, R2; Store Z, R1 1个操作数 Load X; Mult Y; Store Temp; Load W; Mult U; Add Temp; Store Z 0个操作数 Push X; Push Y; Mult; Push W; Push U; Mult; Add; Pop Z 扩展操作码(expanding opcode)代表一种折衷方案,及要求有尽可能多的操作码,又要求采用尽可能短的操作码。设计思想是:选用短操作码,而有需要时可以有某种方法将操作码加长。如果使用短操作码就留给操作数大量的指令位,因此每条指令可以有2~3个操作数;若指令不需要操作数(如halt),或计算机使用堆栈操作,则指令所有位都可用于操作码,生成独特指令。假设计算机具有16位指令系统和16个寄存器,因此指令用4个二进制位就可以指定其中的某个寄存器。如果使用4位操作码,剩下12位作为存储器地址,就像MARIE一样,寻址范围可达2^12 = 4KB。但如果所有数据都被预先装入寄存器,则可以将12位地址分成3个寄存器地址。举例说明:用16位编码产生1)15条3地址指令,2)14条2地址指令,3)31条1地址指令,4)16条0地址指令。
扩展操作码的译码更复杂,对于例子而言,需要采用类似下面方式译码。15个3地址编码 0000 R1 R2 R3; ··· ; 1110 R1 R2 R3 14个2地址编码 1111 0000 R1 R2; ··· ; 1111 1101 R1 R2 31个1地址编码 1111 1110 0000 R1; ··· ; 1111 1111 1110 R1 16个0地址编码 1111 1111 1111 0000; ··· ; 1111 1111 1111 1111 1
2
3
4
5
6
7
8if ( leftmost four bits != 1111 )
Execute appropriate three-address instruction
else if ( leftmost seven bits != 1111 111 )
Execute appropriate two-address instruction
else if ( left most twelve bits != 1111 1111 1111 )
Execute appropriate one-address instruction
else
Execute appropriate zero-address instruction
指令类型
- 数据移动:可能有多种不同数据移动指令,如MOVER要求有两个寄存器操作数,而MOVE则使用一个寄存器操作数和一个存储器操作数。某些计算机体系结构如RISC,会限制将数据从存储器移出或将数据移到存储器,以加快运行速度。许多计算机有多种载入、存储和移动指令处理不同大小的数据,如LOADB处理字节数据,LOADW处理数据字。
- 算术运算:包括整数和浮点运算的各种指令,许多机器对不同大小的数据提供有不同的算术运算指令。
- 布尔逻辑运算:逻辑与、逻辑或、逻辑非、异或。
- 位操作(移位和循环换位):在某个特定数据字中对一些单独数据位进行置位和复位操作,包括各种算术移位指令、逻辑移位指令和循环移位指令。逻辑移位指令对数据简单移位,反向补零;算术移位指令左移时不移动符号位,右移时符号位右移;循环移位各数据位移出后再反向移入。
- 输入/输出:程序控制的I/O,中断驱动的I/O,直接存储器访问(DMA)。
- 控制转移:分支转移(branch)、跳过(skip)、进程调用(procedure call)。分支转移分为条件转移和无条件转移,跳过指令是没有指定地址的分支转移指令,进程调用时一些特殊的分支转移指令,自动存储程序返回的地址,有的机器将这个返回地址存放在存储器某个特殊存储单元,有的机器将这个地址存放在某个寄存器中,有的则压入堆栈。
- 专门用途:如字符串处理指令,高级语言支持指令,保护和标志位控制指令,高速缓存指令等。
寻址方式
MARIE指令有一个12位操作数域,这个12位二进制数可以表示操作数的存储器地址,也可以是一个指示物理存储器地址的指针,也可以解释成其他内容。这样可以形成不同的寻址方式(addressing mode),寻址方式是指定指令中操作数位置的方法。实际操作数的位置称为操作数的有效地址。
- 立即寻址(immediate addressing):指令中操作码后的数值会被立刻引用,例如Load 008会直接将数值8装入累加器AC中。不灵活。
- 直接寻址(direct addressing):指令之直接指定要饮用的数值的存储器地址,如Load 008将存储器地址为008的存储单元中的数值作为操作数装入累加器AC。
- 寄存器寻址(register addressing):采用寄存器而不是存储器指定操作数,指令的地址域包含一个寄存器的引用。
- 间接寻址(indirect addressing):地址域中的二进制数指定一个存储器地址,将该地址中的内容作为一个指针。例如Load 008,该操作表示在存储器地址为008的存储单元中存放的数值实际上是要用到的操作数的有效地址。如果008单元存放的数值是2A0,则2A0是真实地址,操作将地址为2A0的存储器单元中的内容装入AC。间接寻址也可用做寄存器。
- 变址寻址和基址寻址(indexed addressing):一个变址寄存器用于存储一个偏移量,将这个偏移量与操作数相加,产生实际的有效地址。如Load X中的操作数X采用变址寻址方式,假定R1为变址寄存器,如果R1中存放着1,则Load X寻找到的有效地址是X+1。基址寻址与变址寻址类似,但基址寻址使用基地址寄存器而不是变址寄存器,操作数域中的内容表示的是偏移量。这两种寻址方式在访问数组元素和字符串中字符时非常有效,大部分汇编语言都提供专门的变址寄存器。
- 堆栈寻址(stack addressing):操作数假定放在堆栈中。
- 其他寻址方式:间接变址寻址(同时采用变址和间接寻址),基址/偏移量寻址(先将一个偏移量加到某个特定的基址寄存器中,再于指定的操作数相加产生有效地址),自动增/减量寻址等。
- 举例:假设指令Load 800、寄存器R1中数值为800。存储器结构如下:
下表对于不同寻址方式给出实际装入累加器AC的值。地址 数值 800 900 ··· 900 1000 ··· 1000 500 ··· 1100 600 ··· 1600 700 寻址方式 获取操作数方法 装入AC的值 立即寻址 操作数数值直接包含在指令中 800 直接寻址 指令的地址域是操作数的有效地址 900 间接寻址 地址域的内容是实际操作数的地址 1000 变址寻址 地址域数值与寄存器中数值相加产生有效地址 700
指令流水线
计算机使用时钟脉冲精确控制各个操作步骤的顺序执行,有时可以使用额外的脉冲控制某个操作步骤中的小细节。现在所有的CPU都会将取指-译码-执行周期分为一些较小步骤,其中较小步骤可以并行执行,时间上的交叠可以加快CPU执行速度。这种方法称为流水线(pipeline)。
- 假设将取指-译码-执行周期分解为如下6个小步骤:S1取指令;S2操作码译码;S3计算操作数有效地址;S4取操作数;S5执行指令;S6存储结果,这是一个6级流水线,其中的每一个步骤称为流水线级(pipeline stage),将流水线逐级连接就构成指令执行的管道。假设现在有n条指令,k级流水线(本例的k=6)时钟周期时间为tp,即每级流水线需要tp时间。如果不采用流水线执行指令,则需要T = n × k × tp的总时间。如果采用流水线系统,一旦第一条指令的S1完成,该指令就会被送去执行S2,同时可以开始第二条指令的S1,当第二条指令执行S2时,第三条指令S1开始,第一条指令则执行S3,以此类推。因此执行第一条指令需要k × tp时间完成,之后的n - 1个任务每隔一个时钟周期就会从流水线中流出一个流水线级,因此剩下的总时间为(n - 1)×tp,利用k级流水线的总时间为T’=(k × tp)+(n - 1)×tp = (k + n - 1 )× tp。获得的加速比S = T/T’,当n较大时,k + n - 1与n近似,因此n较大时S = k。流水线级数越多,计算机运行速度越快,从某种意义上说这一点是对的,但流水线控制逻辑的数量和大小也会随之增加,拖慢系统速度。另外还存在一些条件会导致“流水线冲突”,这些情况会阻碍计算机实现每个时钟周期执行一条指令的目标。条件包括:资源冲突、数据关联、条件分支语句。
- 资源冲突(resource conflict):如果计算机正在将某个数值存放到存储器,同时又在从存储器中提取另一条指令,两个操作需要同时访问内存。通常解决方法是让存储指令继续执行,提取指令强制等待。某些冲突可以通过使用两条独立的通道来解决:一条通道从内存提取数据,另一条通道从内存提取指令。
- 数据关联(data dependency):一条指令尚未结束,后续指令要求使用该指令的执行结果作为操作数。可以添加专门的硬件检测某条指令的源操作数是否是流水线上游的指令的目标操作数,这种硬件通过在流水线中插入一个no-op指令(不执行操作),让计算机有足够时间解决冲突。
- 条件分支转移:假设有一个4级流水线,S1=取指,S2=译码和计算有效地址,S3=取操作数,S4=执行指令和存储结果(假定该体系结构可以并行提取数据和指令,实际上大部分存储器系统将操作数放到高速缓存中,这样取指令和取操作数可以同时进行)。令指令I3是一个条件分支转移语句,使CPU不再执行I4~I7,而是直接跳转到I8。
从表格可以看出,CPU对I4~I6仍然进行了取指和接下来的各种操作,但实际执行I3后不需要I4~I7。这带来的结果是从第六到第九个时钟周期期间都没有指令流出,直到第9个时钟周期流水线才再次装满,而理想情况下一旦流水线管道开始装入指令,每个时钟周期都应该有指令流出。一种解决方法是设计分支预测机构,利用合理的逻辑对下一条指令做最优预测,编译器试图通过重新安排机器代码的方式产生延迟的分支转移操作。另一种方法是对某个已知的条件分支转移的两条分支通道都开始执行取指操作,并将结果存储,等到分支语句的实际执行时,需要执行的真实分支路径已经被计算过。时钟周期 1 2 3 4 5 6 7 8 9 指令I1 S1 S2 S3 S4 指令I2 S1 S2 S3 S4 分支指令I3 S1 S2 S3 S4 指令I4 S1 S2 S3 指令I5 S1 S2 指令I6 S1 指令I8 S1 S2 S3 指令I9 S1 S2 - 并行执行:并行指令计算机(EPIC)是一种完全不同的体系结构,采用非常大的指令(如Itanium为128位),该指令可以规定几个需要并行执行的操作。EPIC的指令系统强烈取决于编译器,安排操作的负担从处理器转移到编译器。并行执行有几种级别,几乎所有计算机都不同程度应用了并行概念,指令均使用字作为操作数,两个极端情形为程序级的并行执行(PLP)和指令级的并行执行(ILP)。PLP指一个程序各部分可以同时在多台计算机执行,ILP是在时间上重叠执行指令,分为两种类型,第一种将一条指令分解为多个步骤(流水线),第二种处理器本身就可以在同一时刻执行多条指令。ILP还有超标量体系结构,超流水线体系结构,超长指令字(VLIW)体系结构。
ISA体系结构真实案例
- Intel体系结构:Intel使用的是小端、双地址的体系结构,采用可变长度指令系统,寄存器-存储器结构,操作数句长度可以是1,2,4字节。从8086到80486都是单级流水线体系结构,奔腾结构采用两条平行的5级流水线(U线和V线,不同级包括指令预取、指令译码、地址生成、指令执行、回写)执行,这些流水线都必须保持被填满状态,并行发出指令。奔腾II系列将两条流水线增加到12级,大部分新增加的流水线级用来支持Intel的MMX技术(Intel公司为处理多媒体数据对CPU体系结构功能的扩充)。奔腾III流水线增加到14级,奔腾IV增加到24级。Tntel处理器支持上述各种基本寻址方式,如8086有17种不同访问存储器方式。通常Intel体系结构各种寻址方式都保持向下兼容,但IA-64体系结构存储器只有一种寻址方式:寄存器间接寻址,遵循RISC设计思想,对特殊硬件需求减少到最少。
- MIPS体系结构:小端、按字编址、3地址、采用固定长度,是装入/存储式体系结构。MIPS限制只有固定长度的操作,操作数句必须具有相同的字节数。某些MIPS处理器如R2000和R3000,有5级流水线。R4000和R4400有8级流水线。R10000处理器流水线级数取决于执行指令需要经过的功能单元:整数指令为5级,装入/存储指令为6级,浮点运算为7级。R5000和R10000都属于超标量体系结构。MIPS共有5种类型指令:执行简单算术操作指令、数据移动指令、控制转移指令、多重循环指令、其他杂类指令。MIPS指令系统只提供基地址寻址方式,其他编址方式由编译器提供。MIPS指令分为4个域,一个操作码域,两个操作数地址域和一个结果地址域;具有三种类型指令格式,I类型(immediate)、R类型(register)和J类型(jump)。R类型包括一个6位操作码、5位源寄存器、5位目标寄存器、5位偏移量和6位功能代码;I类型指令包括6位操作码、5位源寄存器、5位目标寄存器或分支转移条件、16位立即分支转移位移量或地址位移量;J类型指令包括6位操作码、26位目标地址。
- Java虚拟机:与工作平台无关,实际运行在JVM(Java Virtual Machine)上,不同硬件机器的JVM不同,按照机器固有的指令集编写。JVM相当于解释程序:读取java字节码,将字节码解释成各种机器指令。对Java程序编译时,生成特殊字节码(bytecodes),这些字节码是JVM的输入源程序。编译语言如C、C++、Ada、FORTRAN、COBOL等通常具有很好的可执行性能,只能在特定的目标体系结构上运行。解释性编程语言如LISP、PhP和Tcl等与工作平台无关,但执行速度约比编译程序慢100倍。同时以两种形式存在的称为P代码语言,如Perl、Python和Java等,效率比编译程序语言慢5~10倍。Java虚拟机有4个寄存器,支持访问5各不同的主存储区区域,JVM是堆栈类型的机器,不提供通用寄存器。
专栏目录:计算机理论基础
此专栏的上一篇文章:计组与体系结构笔记(三):简单体系模型
此专栏的下一篇文章:计组与体系结构笔记(五):存储器相关
原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章原始出处(http://blog.forec.cn/2015/10/27/Computer-Organization-Architecture4/) 、作者信息(Forec)和本声明。