501-处理器的设计步骤
# 501-处理器的设计步骤
处理器或者说是 CPU,是现代计算机当中最为复杂的一个部件,不过,这并不意味着我们就做不了这件事情,如果要设计一个简单,但是能工作的处理器,也没有那么地神秘。在这一节,我们就一起来探索处理器是如何设计出来的。
要设计一个处理器,可以分为如下几个步骤:首先,是要分析指令系统,指令系统,是在处理器设计之前,就由软件和硬件的设计人员共同协商决定的, 通过分析指令系统,我们可以得出指令所要操作的数据,需要通过怎样的一个电路的结构, 这就是数据通路;
在我们得到这样的需求之后,我们就可以为数据通路选择合适的集成电路组件, 比如说加法器、减法器、寄存器等等;
选好了合适的组件之后,我们就按照最开始分析出的需求, 把这些组件连接起来,就构成了完整的数据通路。 但是仅有数据通路是不够的,我们还需要控制这个数据通路应该如何工作;
因此,第四步,是要分析每条指令的实现,以确定控制数据通路工作的控制信号;
最后,是把这些控制信号汇总起来,形成完整的控制逻辑,也可以称之为控制器。
那我们还是以 MIPS 的指令系统为例进行讲解。 当然,整个 MIPS 指令系统的指令还是太多了, 所以我们进行一个大幅度的简化,在我们要讲解的这个简化版本当中,包括这样几条指令。 我们提供了无符号的加法和减法指令各一条, 它们都是 R 型指令,然后还提供了一条逻辑运算指令, 这个指令其中一个操作数是立即数,所以它的指令格式是 I 型的。 这三条都是运算类指令,操作数要么是寄存器,要么是立即数, 因此我们还需要有访问存储器的指令,这就是 Load 指令和 Store 指令, 它可以在寄存器和存储器之间传送操作数。
最后是一条分支指令,我们提供了一条条件分支指令, 那我们首先就要分析这样一个指令系统,对于数据通路的设计有怎么样的需求。
首先我们通过对指令的各个位域进行分解,来看这些指令的含义。 对于 R 型指令,它一共分为 6 个位域,最高的 6 个比特,称为操作码, 接下去连续 3 个位域都是 5 个比特,各自标明了一个寄存器的编号, 然后 5 个比特在完整的 MIPS 指令系统当中,是用作标记移位的数量, 在我们简化后的版本当中没有使用,因此我们可以看作是保留的位域。
最后 6 个比特是功能位域, 因此,当我们取得一条 R 型指令之后,就可以将它分解为这样 6 组控制信号。 与之类似的是 I 型指令,只不过 I 型指令的 32 个二进制位只会被切分为 4 组信号, 而且我们要注意,这些指令的编码都是从存储器当中取得的, 因此,我们首先需要一个存放指令的存储器,对于指令存储器 来说,它不需要支持写入的功能,只要可读就可以了, 而且我们希望对这个存储器,外界给它 32 位的地址, 它就会给出对应的 32 位的数据,那么这个 32 位地址又从哪里来呢? 所以这就是我们另一个需求,我们需要有一个存放指令地址的 32 位寄存器, 称为 PC,也就是程序计数器,
有了这两个组件,我们就可以取得想要的指令了。 然后我们再从指令的操作来分析其他的需求, 首先来看加法和减法指令, 这两条指令的主体功能,都是选择两个不同的寄存器,对它进行加法和减法的运算,然后让结果存到另一个寄存器当中去。 因此,首先我们需要有一组存放数据的通用寄存器,每个寄存器都是 32 位的,而且我们可以约定,这一组寄存器总共有 32 个, 那这样一组通用的寄存器,我们就称为寄存器堆。从加法和减法指令的操作,我们还可以看出, 在运算时,我们需要同时读取两个寄存器的内容,这两个寄存器分别由指令位域当中的 rs 和 rt 这两个域所指定。在运算完成后,我们还需要写入另一个寄存器, 这个寄存器的编号由 rd 或者 rt 来进行指定, 那么对于这个加法和减法运算,它都是由 rd 位域指定的。 而对于立即数的运算,运算结果需要改写的寄存器是由 rt 这个位域指定的。 而立即数运算指令的操作数,除了 rs 所指定 的寄存器外,另一个操作数是一个立即数, 其中 16 位是直接填写在指令位域当中的,但是我们的运算需要是 32 位的, 因此,我们还需要一个功能,就是将 16 位的立即数扩展到 32 位,对于这个运算, 我们需要的扩展方法是零扩展,也就是将高 16 位都填 0,从而构成一个 32 位的数。 那这三条都是运算指令,因此我们还需要支持不同的运算类型。
在这里我们可以看出,我们需要提供加法、减法和逻辑或三种功能。 因此,我们还需要一个这样的运存器,这个运算器的操作数可以是两个寄存器, 也可以是一个寄存器加一个扩展后的立即数,这些就是运算指令的需求了。
# 访存指令
然后我们再来看访存指令,对于 LOAD 指令来说,它需要从存储器当中读出一个字, 而这个字所在的存储单元的地址,是由一个寄存器的内容加上一个立即数来决定的,取出这个字之后, 会把它存放到寄存器堆当中,由 rt 所指定的寄存器。
与之相对的还有 STORE 操作,STORE 操作,则是将 rt 所指定的寄存器的内容,传送到内存的指令的存储单元中。
那对于这两条访存指令,我们又有什么需求呢?首先我们需要一个能够存放数据的存储器, 这个存储器既要可读,也要可写,它的地址输入,以及输入和输出的数据,都应该是 32 位的。 另外我们从这个地址的计算方法还可以看出,我们也需要将 16 位的立即数扩展到 32 位, 但扩展方法是符号扩展,也就是将这个立即数作为低 16 位, 并将其最高位复制到高 16 位当中去,从而形成一个 32 位的立即数, 那这就是访存指令的主要需求。
# 分支指令
最后我们来看分支指令, 对于分支指令,首先要判断两个寄存器当中的内容是否相等, 如果相等,那就将指令位域中立即数的这一部分经过一个简单的变化,加到 PC 上,从而得到新的 PC, 这个变化的规则和原因,在介绍 MIPS 指令系统时,都已经讲解了,所以在这里, 我们可以简化地认为,就是将当前的 PC 加上了一个立即数,从而得到了一个新的 PC。
但如果刚才那次比较的结果是不相等, 那就直接将当前的 PC 加上 4,从而产生新的 PC。 因此,分支指令的需求,首先是要能够比较两个寄存器的内容,并判断是否相等,然后还需要 PC 寄存器支持两种自增的方式, 一种是加 4,一种是加一个指定的立即数, 当然,对于 PC 加 4 这个需求,前面介绍的其他指令也都是需要的。
# 小结
那我们再把刚才分析的这些需求整理一下。 首先我们需要一个算数逻辑单元,也就是 ALU,它要支持 加法、减法、逻辑或和比较相等这样的操作; 它有两个 32 位的输入,可以来自寄存器,也可以来自扩展后的立即数; 然后我们还需要一个立即数的扩展部件,可以将一个 16 位的立即数扩展为 32 位, 而且扩展方式可以是零扩展,也可以是符号扩展; 我们还需要一个程序计数器,这是一个 32 位的寄存器, 由时钟控制,而且我们还要给它支持两种加法运算, 要么加 4,要么加一个立即数,这样的需求我们可以用 ALU 来实现,也可以只给它配上简单的加法器。
除此之外,我们还需要两个带有存储功能的组件, 一个是寄存器堆,一个是存储器,对于寄存器堆, 我们一共需要 32 个 32 位的寄存器, 需要支持同时读出两个寄存器,和写入一个寄存器, 这样的寄存器堆特征,我们称为“两读一写”。 而对于存储器,我们需要一个只读的指令存储器,地址和数据都是 32 位的, 还需要一个可读写的数据存储器,地址和数据也都是 32 位的。 我们从这个需求本身来看,实际上提供一个可读写的存储器,就可以满足指令和数据的需求, 而且这也是符合冯诺依曼结构对于统一的一个存储器的要求。
那么在现代计算机当中,内存就是这样一个指令和数据统一存放的存储器, 但是在处理器内部,现代的设计往往会设置高速缓存, 也就是 Cache 这样的部件,用来保存内存当中的一部分数据。 那么高速缓存这个结构,是会被分成指令和数据两个部分,因此在这里,我们也选择了分开的结构, 但我们要注意的是,这个存储器我们实际上对应的是 CPU 当中的高速缓存,而不是整个计算机当中的内存。 我们再来分别看一看这两个组件的具体需求。
这就是一个寄存器堆的结构示意图, 内部有 32 个 32 位的寄存器,它有三组数据接口, 其中 busA 和 busB 是两组 32 位的数据输出接口, busW 是一组 32 位的数据输入接口, 这也体现了“两读一写”的特性。那么如何对寄存器堆进行读写呢? 那就需要来看几组读写控制信号。首先是 Ra,这是一个 5 位的信号, 5 位的信号,正好可以选择编号 0-31 的寄存器, 因此,寄存器堆会根据 Ra 的输入,选择 对应编号的寄存器,将其内容放到 busA 信号上; 然后是 Rb 信号,同样,寄存器堆也会根据 Rb 的输入, 选中其对应编号的寄存器,将内容放到 busB 信号上。 这样外界将两个编号分别放到 Ra 和 Rb 的信号输入中,寄存器堆就会将对应寄存器的内容,分别放到 busA 和 busB 的信号线上, 这就完成了同时读取两个寄存器的功能。
而对于写,稍微复杂一些,首先,将要写的寄存器的编号通过 Rw 信号先输入,在时钟的上升沿,如果写使能信号是有效的, 也就是 WriteEnable 信号等于 1, 那么寄存器堆就会将 busW 信号上的内容存入 Rw 信号所指定的寄存器, 这就是寄存器堆所提供的写的功能。
我们需要注意的是,寄存器堆的“写"是在时钟上升沿- 完成的, 但是寄存器堆的"读"是不受时钟控制的,也就是说,在任何时候,就让 Ra 或者 Rb 发生了变化,那对应的 busA 和 busB 的信号就会发生变化。
然后我们来看存储器,存储器有两组数据接口信号,有 32 位的数据输入和 32 位的数据输出。 那存储器的读写实际上和寄存器堆有些类似,只要我们给入一个地址, 存储器就会将对应的存储单元中的数送到数据输出信号上。 而与寄存器堆不同在于,我们只给入一组地址信号, 而不是寄存器堆所提供的两组寄存器编号的输入。 从这个意义上,我们也可以说,这个存储器是一个易读易写的存储器。 另一个方面,我们给入的地址信号是 32 位的, 所以理论上,这个存储器可以达到 2 的 32 次方那么大, 当然这只是一个理想化的情况。 那么对于存储器的写,我们也提供了一个写使能的信号, 那在时钟上升沿到来的时候,如果写使能信号为 1,那么存储器就会将数据输入信号的内容存入地址信号所指定的存储单元。 同样我们也需要注意,存储器的”写“操作,是在时钟上升沿的时候发生的, 而存储器的”读“操作,则不受时钟信号的控制。只要输入的地址信号发生 变化,经过很短的访问时间,数据输出信号就会随之发生变化。
那现在,我们就完成了指令系统的分析, 得出了对数据通路的需求,并且为数据通路选择了合适的组件, 之后就可以开始着手建立数据通路的工作了。
在这一节,我们一起学习了设计一个处理器所需要的主要步骤, 并且构造了一个简化后的指令系统,并对其进行了细节的分析, 从而完成了设计处理器的准备工作。