前面几讲里,我从不同的部分为你讲解了CPU的功能。
在“指令”部分,我为你讲解了计算机的“指令”是怎么运算的,也就是我们撰写的代码,是怎么编程一条条的机器能够理解的指令的,以及是按照什么样的顺序运行的。
在“计算”部分,我为你讲解了计算机的“计算”部分是怎么执行的,数据的二进制表示是怎么样的,我们执行的加法和乘法又是通过什么样的电路来实现的。
然而,光知道在两部分还不能算是真正揭开了CPU的秘密,只有把“指令”和“计算”着两部分功能连通起来,我们才能构成一个真正完整的CPU。这一讲,我们就在前面知识的基础上,来看一个完整的CPU是怎么运转起来的。
前面讲计算机机器码的时候,我向你介绍过PC寄存器、指令寄存器,还介绍过MIPS体系结构的计算机所用到的R、I、J类指令。如果我们仔细看一看,可以看到,计算机每执行一条指令的过程,可以分解为这样几个步骤。
1.Fetch(取得指令),也就是从PC寄存器里找到对应的指令地址,根据指令地址从内存里把具体的指令,加载到指令寄存器中,然后把PC寄存器自增,好在未来执行下一条指令。
2.Decode(指令译码),也就是根据指令寄存器里面的指令,解析成要进行什么样的操作,是R、I、J中的哪一种指令,具体要操作哪些寄存器、数据或者内存地址。
3.Execute(执行指令),也就是实际运行对应的R、I、J这些特定的指令,进行算术逻辑操作、数据传输或者直接的地址跳转。
4.重复进行1 ~ 3 的步骤。
这样的步骤,其实就是一个永不停歇的“Fetch - Decode - Execute”的循环,我们把这个循环称之为指令周期(Instruction Cycle)。
在这个循环过程中,不同部分其实是由计算机中的不同组件完成的。不知道你还记不记得,我们在专栏一开始讲的计算机组成的五大组件?
在取指令的阶段,我们的指令是放在存储器里的,实际上,通过PC寄存器和指令寄存器取出指令的过程,是由控制器(Control Unit)操作的。指令的解码过程,也是由控制器进行的。一旦到了执行指令阶段,无论是进行算术操作、逻辑操作的R型指令,还是进行数据传输、条件分支的I型指令,都是由算术逻辑单元(ALU)操作的,也是由运算器处理的。不过,如果是一个简单的无条件地址跳转,那么我们可以直接在控制器里面完成,不需要用到运算器。
除了Instruction Cycle这个指令周期,在CPU里面我们还会提到另外两个常见的Cycle。一个叫Machine Cycle,机器周期或者CPU周期。CPU内部的操作速度很快,但是访问内存的速度确要慢很多。每一条指令都需要从内存里面加载而来,所以我们一般把从内存里面读取一条指令的最短时间,称为CPU周期。
还有一个是我们之前提过的Clock Cycle,也就是时钟周期以及我们机器的主频。一个CPU的周期,通常会由几个时钟周期累积起来。一个CPU周期的时间,就是这几个Clock Cycle的总和。
对于一个指令周期来说,我们取出一条指令,然后执行它,至少需要两个CPU周期,取出指令至少需要一个CPU周期,执行至少也需要一个CPU周期,复杂的指令则需要更多的CPU周期。
所以,我们说一个指令周期,包含多个CPU周期,而一个CPU周期包含多个时钟周期。
不少人会问,ALU就是运算器吗?在讨论计算机五大组件的运算器的时候,我们提到过好几个不同的相关名词,比如ALU、运算器、处理器单元、数据通路,它们之间到底是什么关系呢?
名字是什么其实并不重要,一般来说,我们可以认为,数据通路就是我们的处理器单元。它通常由两类原件组成。
第一类叫操作元件,也叫组合逻辑元件(Combinational Element),其实就是我们的ALU。在前面讲ALU的过程中可以看到,它们的功能就是在特定的输入下,根据下面的组合电路的逻辑,生成特定的输出。
第二类叫存储元件,也有叫状态元件(State Element)的。比如我们在计算过程中需要用到的寄存器,无论是通用寄存器还是状态寄存器,其实都是存储元件。
我们通过数据总线的方式,把它们连接起来,就可以完成数据的存储、处理和传输了,这就是所谓的建立数据通路了。
下面我们来说控制器。它的逻辑就没那么复杂了。我们可以把它看成知识机械的重复“Fetch - Decode - Execute”循环中的前两个步骤,然后把最后一个步骤,通过控制器产生的控制信号交给ALU去处理。
听起来是不是很简单?实际上,控制器的电路非常复杂。
一方面,所有CPU支持的指令,都会在控制器里面,被解析成不同的输出信号。我们之前说过,现在的Intel CPU支持2000个以上的指令。这意味着,控制器输出的控制信号,至少有2000种不同的组合。
运算器里的ALU和各种组合逻辑电路,可以认为是一个固定功能的电路。控制器“翻译”出来的,就是不同的控制信号。这些控制信号,告诉ALU去做不同的计算,。可以说正事控制器的存在,让我们可以“编程”来实现功能,让我们的存储程序型计算机名副其实。
那么,要向搭建出来整个CPU,我们需要在数字电路层面,实现这样一些功能。
首先,自然是我们之前已经讲解过的ALU了,它实际就是一个没有状态的,根据输入计算输出结果的第一个电路。
第二,我们需要有一个能够进行状态读写的电路元件,也就是我们的寄存器。我们需要有一个电路,能够存储到上一次的计算结果。这个计算结果并不一定要立刻拿到电路的下游去使用,但是可以在需要的时候拿出来用。常见的能够进行状态读写的电路,就有锁存器(Latch),以及我们后面要讲的D触发器(Data/Delay Flip-flop)的电路。
第三,我们需要有一个“自动”的电路,按照固定的周期,不停的实现PC寄存器的自增,自动的去执行“Fetch - Decode - Execute”的步骤。我们的程序执行,并不是靠人去拨动开关来执行指令的。我们希望有一个“自动”的电路,不停地去一条条执行指令。
我们看似写了各种复杂的高级程序进行各种函数调用、条件跳转,其实只是修改PC寄存器里面的地址。PC寄存器里面的地址一修改,计算机就可以加载一条新指令,往下运行。实际上,PC寄存器还有一个名字,就叫做程序计数器。顾名思义,就是随着时间变化,不断去数数,数的数变大了,就去执行一条新指令。所以,我们需要的就是一个自动数数的电路。
第四,我们需要有一个“译码”的电路。无论是对于指令进行decode,还是对于拿到的内存地址去获取对应的数据或者指令,我们都需要通过一个电路找到对应的数据。这个对应的自然就是“译码器”的电路了。
好了,现在我们把这四类电路,通过各种方式组合在一起,就能最终组成功能强大的CPU了。但是,要实现这四种电路中的中间两种,我们还需要时钟电路的配合。下一节,我们一起来看一看,这些基础的电路功能是怎么实现的,以及怎么把这些电路组合起来变成一个CPU。
好了,到这里,我们已经把CPU运转需要的数据通路和控制器介绍完了,也找出了需要完成这些功能,需要的4种基本电路。它们分别是,ALU这样的组合逻辑电路、用来存储数据的锁存器和D触发器电路、用来实现PC寄存器的计数器电路,以及用来解码和寻址的译码器电路。
虽然CPU已经是由几十亿个晶体管组成的及其复杂的电路,但是它仍然是由这样一个个基本功能的电路组成的。只要搞清楚这些电路的运行原理,你自然也就弄明白了CPU的工作原理。
这一讲,我们说CPU好像一个永不停歇的机器,一直在不停地读取下一条指令去运行。那为什么CPU还会有满载运行和Idle闲置的状态呢?