我们在前面的文章中,讨论了计算机架构、计算机指令,今天我们将更深入,带你接触到计算的核心–处理器。 我们将会以最基础的RISC-V指令集为例,从无到有,循序渐进地搭建计算处理器,带你深入了解处理器的结构以及其运作。废话不多说,咱直接开始。
概述
计算机是根据指令来执行对应操作的,尽管指令的类型不一样,但是我们可以将其执行任务的相似的过程进行归纳合并,对不同的部分进行具体分析。
对几乎所有指令,计算机前两步都是一样的:
- 程序计数器(PC)发送到指令所在存储的单元,并从中取出指令
- 根据指令的某些字段,选择读取一或两个寄存器。对于ld指令,只需要读取一个寄存器,但对于大部分指令需要读取两个寄存器。
这两个步骤之后,后续的操作则取决于指令的类别。幸运的是,对于内存访问型指令、算术逻辑指令以及分支指令,他们的行为大体上是一致的。
所有的指令都在读取寄存器后,都需要用到ALU(算术逻辑单元)。内存访问型的指令,需要ALU来计算内存访问地址,算术逻辑指令则进行对应的运算,条件分支指令则需要判断相等条件是否成立(用ALU进行减法)。
在ALU之后,操作不同了。内存访问型的,需要读取或者写入数据到存储器中( sd);运算逻辑型指令(add)或加载指令(ld),则需要将ALU的运算结果或者存储器中的数据写回寄存器。对于条件分支指令,我们会根据ALU的比较结果,要么改变一个指令的地址,要么对PC经行自增(PC= PC+4)
模块拆分
在有了大体的概念之后,我们现在对处理器模块根据指令的功能,经行拆分,逐个击破
指令的获取
所有的指令的获取,是处理器执行任务的第一步,这一部分主要与程序计数器(Program Counter, PC),指令存储器、加法器组成。
指令存储器中存有将高级语言转换成许多台条指令;PC是一个64-bit的寄存器,每周期都会被修改,因而不需要写信号进行控制,它是指令的地址,或者说指令存储器的索引;因为计算机是串行的,因而每个时钟周期后,PC都需要增加,将ALU功能固定,始终执行加法操作。
在RISC架构中,每条指令占用连续的4个字节(32位),而现代计算机结构是采用字节寻址的,因而一般情况下,下一条指令的位置就是当前的位置加4
R型指令(算术逻辑指令)
常用的算术逻辑指令有:add,sub,and,or。 它们都需要对三个寄存器进行操作,例如add x1, x2, x3 将寄存器2和3的数据求和后,写回寄存器1中。
在CPU中,有一个叫寄存器文件(Register File)的高速存储单元,它由若干(RISC-V有32个)寄存器构成。它的存在主要是为了加速运算时的数据访问和数据的传递。
R型指令都有3个寄存器操作对象,对应到架构上。寄存器文件会有两个输出(读操作)和一个输入(写操作),因而需要有6个接口,三个关于数据,以及三个与数据对应的要进行操作的寄存器地址。 (2^5=32)
从寄存器文件中去读的文件,会做为ALU的两个输入,根据指令类型执行对应操作,将结果写回寄存器文件中。
数据访问
现在考虑数据访问和存储。 如 ld x1, offset(x2), sd x1 offset(x2)。
该类型指令根据基地址计算偏移从而得到内存的索引(基地址+指令中的12位有符号的偏移地址)。不论时访问还是存储,都需要计算内存索引,因而都需要用到ALU. 如果是ld, 则需要将内存的数据载入到寄存器中,如果是sd则需要将寄存器中数据存到内存中。
此外,我们还需要将12bit的偏移扩展到64位(有符号),扩展符号的目的是为了保持偏移量的正负,可以允许访问基址前后的地址;扩展到64位,是因为允许访问的内存范围会更大,同时因为地址总线位64位,因而需要与基地址对齐。
条件分支
beq 指令(beq x1, x2, offset) , 有3个操作对象,有两个是寄存器,用于比较是否相等;剩下一个12bit的偏移,用于计算相对于当前分支指令的分支的目标地址。
分支目的地址有当前分支指令的地址(PC)加上指令中偏移量;同时还需要对偏移经行左移1位(相当于x2),这样可以避免最低位不为零的情况,避免处理非对齐地址的复杂性。如果条件成立,分支目的地址会成为新的PC;如果没有,那么PC自增
到此,我们已经将所有的模块介绍了一遍了,将其拼接起来,就可以得到完成的初级版处理器
总结
我们先整体然后到局部,分析了处理器针对不同指令需要实现各个功能以及对应的相应模块。
值得注意的是,由于我们是根据部分表指令搭建的处理器,因为功能还一定完整;同时此处描述的只是原型,我们后续还会加上控制单元的部分,以采用及流水线结构。
以及对应的相应模块。
值得注意的是,由于我们是根据部分表指令搭建的处理器,因为功能还一定完整;同时此处描述的只是原型,我们后续还会加上控制单元的部分,以采用及流水线结构。