上一讲,咱们讲解了时钟信号是怎么实现的,以及怎么利用这个时钟信号,来控制数据的读写,可使得咱们能把须要的数据“存储”下来。那么,这一讲,咱们要让计算机“自动”跑起来。性能优化
经过一个时钟信号,咱们能够实现计数器,这个会成为咱们的PC寄存器。而后,咱们还须要一个可以帮咱们在内存里面寻找指定数据地址的译码器,以及解析读取到的机器指令的译码器。
这样,咱们就能把全部学习到的硬件组件串联起来,变成一个CPU,实现咱们在计算机指令的执行部分的运行步骤。性能
咱们常说的PC寄存器,还有个名字叫程序计数器。下面咱们就来看看,它为何叫做程序计数器。学习
有了时钟信号,咱们能够提供定时的输入;有了D型触发器,咱们能够在时钟信号控制的时间点写入数据。咱们把这两个功能组合起来,就能够实现一个自动的计数器了。优化
加法器的两个输入,一个始终设置成1,另一个来自于一个D型触发器A。咱们把加法器的输出结果,写到这个D型触发器A里面。因而,D型触发器里面的数据就会spa
在固定的时钟信号为1的时候更新一次。设计
这样,咱们就有了一个每过一个时钟周期,就能固定自增1的自动计数器了。这个自动计数器,能够拿来当咱们的PC寄存器。事实上,PC寄存器的这个PC,
英文就是Program Counter,也就是 程序计数器的意思。3d
每次自增以后,咱们能够去对应的D型触发器里面取值,这也是咱们下一条须要运行指令的地址。前面第5讲咱们讲过,
同一个程序的指令应该要顺序地存放在内存里面。这里就和前面对应上了,顺序地存放指令,就是为了让咱们经过程序计数器就能定时地不断执行新指令。code
加法计数、内存取值,乃至后面的命令执行,最终其实都是由咱们一开始讲的时钟信号,来控制执行时间点和前后顺序的,这也是咱们须要时序电路最核心的缘由。blog
在最简单的状况下,咱们须要让每一条指令,从程序计数,到获取指令、执行指令,都在一个时钟周期内完成。若是PC寄存器自增地太快,程序就会出错。
由于前一次的运算结果尚未写回到对应的寄存器里面的时候,后面一条指令已经开始读取里面的数据来作下一次计算了。这个时候,若是咱们的指令使用一样的寄
存器,前一条指令的计算就会没有效果,计算结果就错了。内存
在这种设计下,咱们须要在一个时钟周期里,确保执行完一条最复杂的CPU指令,也就是耗时最长的一条CPU指令。这样的CPU设计,咱们称之为 单指令周期处理器(Single Cycle Processor)。
很显然,这样的设计有点儿浪费。由于即使只调用一条很是简单的指令,咱们也须要等待整个时钟周期的时间走完,才能执行下一条指令。在后面章节里咱们会讲到,经过流水线技术进行性能优化,能够减小须要等待的时间,这里咱们暂且说到这里。
如今,咱们的数据可以存储在D型触发器里了。若是咱们把不少个D型触发器放在一块儿,就能够造成一块很大的存储空间,甚至能够当成一块内存来用。像我如今手头这台电脑,
有16G内存。那咱们怎么才能知道,写入和读取的数据,是在这么大的内存的哪几个比特呢?
因而,咱们就须要有一个电路,来完成“寻址”的工做。这个“寻址”电路,就是咱们接下来要讲的译码器。
在如今实际使用的计算机里面,内存所使用的DRAM,并非经过上面的D型触发器来实现的,而是使用了一种CMOS芯片来实现的。不过,这并不影响咱们从基础原理方面来理解译码器。
在这里,咱们仍是能够把内存芯片,当成是不少个连在一块儿的D型触发器来实现的。
若是把“寻址”这件事情退化到最简单的状况,就是在两个地址中,去选择一个地址。这样的电路,咱们叫做 2-1选择器。我把它的电路实现画在了这里。
咱们经过一个反相器、两个与门和一个或门,就能够实现一个2-1选择器。经过控制反相器的输入是0仍是1,可以决定对应的输出信号,是和地址A,仍是地址B的输入信号一致。
一个反向器只能有0和1这样两个状态,因此咱们只能从两个地址中选择一个。若是输入的信号有三个不一样的开关,咱们就能从$2^3$,也就是8个地址中选择一个了。这样的电路,咱们就叫 3-8译码器。现代的计算机,若是CPU是64位的,就意味着咱们的寻址空间也是$2^{64}$,那么咱们就须要一个有64个开关的译码器。
因此说,其实译码器的本质,就是从输入的多个位的信号中,根据必定的开关和电路组合,选择出本身想要的信号。除了可以进行“寻址”以外,咱们还能够把对应的须要运行的指令码,一样经过译码器,找出咱们
指望执行的指令,也就是在以前咱们讲到过的opcode,以及后面对应的操做数或者寄存器地址。只是,这样的“译码器”,比起2-1选择器和3-8译码器,要复杂的多。
D触发器、自动计数以及译码器,再加上一个咱们以前说过的ALU,咱们就凑齐了一个拼装一个CPU必需要的零件了。下面,咱们就来看一看,怎么把这些零件组合起来,才能实现指令执行和算术逻辑计算的CPU。
1. 首先,咱们有一个自动计数器。这个自动计数器会随着时钟主频不断地自增,来做为咱们的PC寄存器。
2. 在这个自动计数器的后面,咱们连上一个译码器。译码器还要同时连着咱们经过大量的D触发器组成的内存。
3. 自动计数器会随着时钟主频不断自增,从译码器当中,找到对应的计数器所表示的内存地址,而后读取出里面的CPU指令。
4. 读取出来的CPU指令会经过咱们的CPU时钟的控制,写入到一个由D触发器组成的寄存器,也就是指令寄存器当中。
5. 在指令寄存器后面,咱们能够再跟一个译码器。这个译码器再也不是用来寻址的了,而是把咱们拿到的指令,解析成opcode和对应的操做数。
6. 当咱们拿到对应的opcode和操做数,对应的输出线路就要链接ALU,开始进行各类算术和逻辑运算。对应的计算结果,则会再写回到D触发器组成的寄存器或者内存当中。
这样的一个完整的通路,也就完成了咱们的CPU的一条指令的执行过程。在这个过程当中,你会发现这样几个有意思的问题。
是咱们以前在第6讲讲过的程序跳转所使用的条件码寄存器。那时,讲计算机的指令执行的时候,咱们说高级语言中的if…else,实际上是变成了一条cmp指令和一条jmp指令。
cmp指令是在进行对应的比较,比较的结果会更新到条件码寄存器当中。jmp指令则是根据条件码寄存器当中的标志位,来决定是否进行跳转以及跳转到什么地址。
不知道你当时看到这个知识点的时候,有没有一些疑惑,为何咱们的if…else会变成这样两条指令,而不是设计成一个复杂的电路,变成一条指令?到这里,咱们就能够解释了。
这样分红两个指令实现,彻底匹配好了咱们在电路层面,“译码-执行-更新寄存器“这样的步骤。
cmp指令的执行结果放到了条件码寄存器里面,咱们的条件跳转指令也是在ALU层面执行的,而不是在控制器里面执行的。这样的实现方式在电路层面很是直观,咱们不须要一个很是复杂的电路,
就能实现if…else的功能。
是关于咱们在第17讲里讲到的指令周期、CPU周期和时钟周期的差别。在上面的抽象的逻辑模型中,你很容易发现,咱们执行一条指令,其实能够不放在一个时钟周期里面,
能够直接拆分到多个时钟周期。
咱们能够在一个时钟周期里面,去自增PC寄存器的值,也就是指令对应的内存地址。而后,咱们要根据这个地址从D触发器里面读取指令,这个仍是能够在刚才那个时钟周期内。
可是对应的指令写入到指令寄存器,咱们能够放在一个新的时钟周期里面。指令译码给到ALU以后的计算结果,要写回到寄存器,又能够放到另外一个新的时钟周期。因此,执行一条计算机指令,
其实能够拆分到不少个时钟周期,而不是必须使用单指令周期处理器的设计。
由于从内存里面读取指令时间很长,因此若是使用单指令周期处理器,就意味着咱们的指令都要去等待一些慢速的操做。这些不一样指令执行速度的差别,也正是计算机指令有指令周期、
CPU周期和时钟周期之分的缘由。所以,现代咱们优化CPU的性能时,用的CPU都不是单指令周期处理器,而是经过流水线、分支预测等技术,来实如今一个周期里同时执行多个指令。
好了,今天咱们讲完了,怎么经过链接不一样功能的电路,实现出一个完整的CPU。
咱们能够经过自动计数器的电路,来实现一个PC寄存器,不断生成下一条要执行的计算机指令的内存地址。而后经过译码器,从内存里面读出对应的指令,
写入到D触发器实现的指令寄存器中。再经过另一个译码器,把它解析成咱们须要执行的指令和操做数的地址。这些电路,组成了咱们计算机五大组成部分里面的控制器。
咱们把opcode和对应的操做数,发送给ALU进行计算,获得计算结果,再写回到寄存器以及内存里面来,这个就是咱们计算机五大组成部分里面的运算器。
咱们的时钟信号,则提供了协调这样一条条指令的执行时间和前后顺序的机制。一样的,这也带来了一个挑战,那就是单指令周期处理器去执行一条指令的时间太长了。而这个挑战,也是咱们接下来的几讲里要解答的问题。