计算机组成学习笔记(一)
1 计算机基本结构
1.1 电子计算机的兴起
-
现代电子计算机之父——冯·诺伊曼。
-
二战的弹道计算,人力已经不能满足需求,人们转向更为强大的计算设备。
-
ENIAC出现,世界上第一台通用电子计算机。
-
世界上第一台电子计算机是ABC。
-
冯·诺伊曼对ENIAC提出改进想法,但ENIAC已经开始制造,不能大规模改动,人们开始设计下一款改进版计算机。
-
冯·诺伊曼关于EDVAC的报告,独吞所有功劳。
-
EDVAC。
-
EDSAC是世界上第一台实用的冯·诺伊曼结构计算机。
-
ENIAC创始人单干,制造出UNIVAC,UNIVAC在选票计算中一夜成名。
-
UNIVAC出名后,计算机的商业价值被发现,大量公司进入计算机领域。
-
一些著名计算机。
1.2 冯·诺依曼结构的要点
- 重要思想:
- 控制计算机的程序,应该存放在存储设备中。
- 使用二进制。
- 五个部分:
- 运算器
- 控制器
- 存储器
- 输入设备
- 输出设备
-
冯·诺依曼结构的要点
-
冯·诺依曼结构的核心
-
CPU与主存
-
主存的组织形式
-
冯·诺依曼计算机类比
- ENIAC的方式,是指存储数据在内存,单独向CPU发送指令,非常符合直觉。
- 但是CPU处理的速度太快,发送指令相对太慢,CPU大部分时间都在等待。
- 因此,需要把指令与数据,不加区分的都放在主存,CPU自己调度执行。
-
冯·诺依曼结构的要点
1.3 计算机运行过程的类比
-
取指
-
CPU向主存发出请求,需要第一格的物品。
-
主存把第一格物品交给CPU。
-
CPU发现这个东西是任务单(指令),就把它放在用于存放任务单(指令)的位置。
-
取回后,更新下一次取的物品的位置,即第一格变成第二格。
-
-
译码:把指令拆分为几个可执行的步骤。
-
执行
-
回写
-
计算机执行指令的过程
1.4 计算机结构的简化模型
-
模型机
-
存储器
- 逻辑控制:读、写、完成等控制信号。
- 地址译码器.
- 地址寄存器MAR(Memory Address Register):存放CPU正在读出或即将写入写的存储单元的地址。
- 数据寄存器MDR(Memory Data Register):存放CPU正在读出或即将写入写的存储单元的数据。
-
控制器
-
控制器结构
-
控制器基本组成
- 指令寄存器(Instruction Register):存放正在执行或即将执行的指令。
- 程序计数器(Program Counter):下一条指令的存储单元地址,具有自动增量计数的功能。
- 地址寄存器MAR(Memory Address Register):存放CPU正在读出或即将写入写的存储单元的地址。
- 数据寄存器MDR(Memory Data Register):存放CPU正在读出或即将写入写的存储单元的数据。
- 指令译码:对指令寄存器(Instruction Register)中的指令进行译码,确定指令寄存器(Instruction Register)中存放的是哪一条指令。
- 控制电路:产生控制信号,在时序脉冲的同步下,控制各个部件的动作。
-
-
运算器
-
运算器结构
-
运算器基本组成
- 运算逻辑单元(ALU):完成算术运算与逻辑运算。
- 数据暂存器:ALU取数据与存储结果的部分。
- 标志寄存器(F):存放运算结果状态。
- 通用寄存器:临时存放数据。
-
-
CPU内部总线:在CPU内部各个部件之间,传输数据。
1.5 计算机执行指令的过程
- 取指:
- 控制器发出控制信号,将PC中的指令地址,通过内部总线,送到MAR中。
- MAR把数据发送到地址总线上。同时,控制电路对控制总线发出信号,表示这一次访问存储器的操作为读数据。
- 存储器的MAR,收到CPU的MAR发到地址总线的数据,发给地址译码器。
- 存储器的控制逻辑,收到控制总线上的读信号。
- 存储器通过地址译码器,找到对应地址的存储单元所存储的数据。
- 数据被送到存储器的MDR中保存。
- 存储器的控制逻辑,通过控制总线,向CPU发出准备好的信号。
- 同时,存储器的MDR中的数据,被发送到数据总线上。
- CPU收到控制总线上发过来的准备好的信号,就知道数据总线上,已经有了需要的数据。
- CPU的MDR,把数据总线上的数据保存起来。
- MDR中的指令,被发送到IR中。
- PC中的指令地址加1。
- 译码:
- IR把指令数据,发给指令译码部件。
- 得出指令内容。此处为加法,把R0寄存器中的数据,与地址为6的数据相加。
- 控制电路发出信号。
- 执行:
- 地址6发送到CPU的MAR中。
- 类似于取指,取到对应数据。
- 把两个需要相加的数,存入ALU的输入寄存器中。
- ALU进行计算。
- 回写:
- 控制电路发出控制信号,ALU的输入寄存器中的结果,写入R0寄存器。
1.6 计算机的输入与输出
1.7 冯诺依曼结构和具体实现
- CPU通过北桥,再到南桥,访问到BIOS里存储的指令,进行基本操作。
- 主存频繁访问,北桥与主存之间的传输压力很大,成为性能瓶颈。
- 因此,北桥中的主存控制器,移入CPU中,CPU与主存直接通信。但是,显示需求高时,通过北桥连接显卡性能依旧很低。
- 于是乎,又把北桥里连接显卡的PCIe控制器,再移入CPU中,北桥里只剩一些无足轻重的部分,可以直接移除,北桥退出历史舞台。CPU与主存、显卡、南桥(PCH)直接相连。
- 再把南桥里的部分,也移入CPU中,系统芯片诞生。
2 指令系统体系结构
2.1 设计自己的计算机
2.2 x86体系结构
- 8086只有16位,寻址能力不能满足需求,所以通过4个段寄存器扩展到20位。
2.3 x86指令简介
指令分类举例:
-
传送类指令
-
运算类指令
ADC
指令,会把标志寄存器(F)中的CF
标志位的值,也送到ALU中进行运算,之前计算的结果,会影响现在的计算结果。- 同样,
ADC
指令的计算,也会影响标志寄存器(F)中的CF
标志位的值。 ADD
指令与ADC
指令,都会根据计算结果,改变标志寄存器(F)中的CF
标志位的值。
-
转移类指令
DEC
指令,将CL寄存器中的值减1。JNZ(jump if not zero)
为条件转移指令,检查上一条指令的结果是否为0,即检查标志寄存器(F)中的ZF
标志位是否为0。
-
控制类指令
2.4 复杂的x86指令举例
- 假设事先已配置好了数据段寄存器DS为1000H。
- 这个程序的前两条指令实际是将数据段寄存器的内容传送到 附加段寄存器当中。只不过段寄存器之间不能直接传送,所以借用了AX。
- 然后在SI寄存器当中保存源串的偏移地址, 在DI寄存器当中放入目的串的偏移地址, 这样DS和SI这组寄存器就指向了源串。
- 而ES和DI这组寄存器就指向了目的串。
- 下一条指令
CLD
,这是确定传送的方向。 - 然后在CX寄存器当中存入3,然后才是这条串传送指令。
- 前面加上了重复前缀,这样的配置就相当于 连续执行了三次这条串传送指令。
- 传送即先读再写操作。
2.5 MIPS体系结构
- 复杂指令系统计算机:CISC(Complex Instruction Set Computer)—— 代表:x86。
- 精简指令系统计算机:RISC(Reduced Instruction Set Computer)—— 代表:MIPS。
- 流水线不会互锁的微处理器。
- MIPS固定了指令的长度,都是32个比特, 也就说MIPS中的一个word,我们要注意这和x86中一个word是16位是不同的。 固定的指令长度,大大简化了CPU从存储器中取指令的工作。 不用像x86 CPU那样需要判断每条指令的长度。
- MIPS采用了非常简单的寻址模式,相比于x86提供的复杂多样的寻址模式, 虽然给编程带来了不变,但是大大简化了CPU访问存储器的控制逻辑。
- MIPS指令的数量比较少,每条指令的工作也很简单, 基本上一条指令只完成一个操作,不像x86的指令,一条指令往往完成丰富的功能, 这样可以简化指令的执行过程。不但简化了CPU的控制逻辑,而且可以方便的实现各种让指令并行执行的技术,从而提高CPU的性能。
- 在MIPS指令系统中只允许
LOAD
和STORE
这两种指令访问存储器, 而不支持x86指令中这些让算术指令访问存储器的操作。 因为访存是一个相对复杂的工作, 这种限制就可以让运算指令的实现变得非常的简单. - 但是我们要注意,MIPS 的这些特点让直接使用MIPS指令进行编程变得非常的困难, 因此,想要有高效率的MIPS程序, 必须要有优秀的编译器的支持。
- 这些指令的操作数都不可以是存储器操作数。 要访问存储器,就必须使用专门的访存指令。
-
MIPS的寄存器编号用
$
符进行标记。 -
首先要用
LOAD
指令 将19号寄存器对应的地址加上偏移量12,因为MIPS当中一个字是32位,所以第三个元素与首地址之间的偏移是12, 将存储器中的这个字装入到8号寄存器中, 这就相当于将数组A中的第三个元素赋给了一个临时变量。 -
第二句是一个加法指令,将8号寄存器与18号寄存器相加, 并将结果存放在8号寄存器。这就
相当于将数组A的第三个元素与变量H相加,还存在这个临时变量中。
-
第三条指令是将8号寄存器中的数,也就是运算的结果存到以19号寄存器为首地址,偏移量为40的内存单元, 这就是数组A的第十个元素的位置。
2.6 MIPS指令简介
- 相比于X86指令所提供的动辄上千页的指令说明,MIPS指令 只用两页即可。
MIPS指令:
-
R型运算指令:
- R型指令总共包含六个域,其中最高位的opcode域是六个比特,最低位的funct域也是六个比特,中间的四个域,均为五个比特。
- 对于所有的R型指令,opcode域的值,均为零, 但这并不是说明R型指令只有一种,它还需要用funct域来更为精确的指定指令的类型。
- 所以说, 对于R型指令,实际上一共有12个比特操作码。
-
I型运算访存指令:
- I型指令的第一个域,也是opcode域,用于指定指令的类型,但它没有funct域,所以不同的I型指令,及opcode域是不一样的。
- I型指令与R型指令不同,它只有两个寄存器数域。
- 剩下的16位被整合成了一个完整的域,可以存放16位的立即数, 可以表示2的十六次方个不同的数值。
- 对一般的访存指令,我们需要 用一个寄存器,加上一个立即数来指示一个内存单元,那么这个立即数就是访存地址的偏移量,16位的立即数,可以访问正负32K的空间,对于一般的访存指令来说,就可以满足了。
- 而对于运算指令,虽然无法满足全部的需求,但是大多数情况下, 16位也可以使用了。
- 在这一点上, 就可以体现出x86这样的CISC指令系统的优势,对于x86指令来说, 如果它想使用更大宽度的立即数,它可以很容易的扩展,因为它的指令本来就没有限制长度,但是MIPS指令就不行。它的指令总长度就是32位的,再加上各个寄存器位域的使用, 所以I型指令最多只能使用十六位的立即数。
- 对于加法,如果我们想让其中的 源操作数是一个立即数的话,就可以用
addi
这个指令, 注意它和add
指令是不一样的。add
指令的操作数必须都是寄存器。 addi
和add
虽然只有一个字母的差别,但是他们指令格式是完全不一样的。
-
分支指令:
-
分支指令是用于改变控制流的指令,其实就相当于x86当中的转移指令。
-
条件分支指令
- 以beq指令为例,它共有三个操作数,前两个是寄存器操作数, 第三个操作数是存储器地址,也就说一个立即数,CPU会判断第一个寄存器当中的数,和第二个寄存器当中的数是否相等。如果相等就跳转到 LE所指向的寄存器单元取出下一条指令,否则, 顺序执行deq之后的那条指令。
- 需要注意,这里和X86的条件转移指令有很大的不同。MIPS没有标志寄存器,它就在一条指令当中即进行了比较,又完成了转移。我们还记得MIPS的全称,就是为了减少指令流水线的互锁,也就说要尽量避免不同指令之间相互的影响。而标志位这件事,很明显就是前一条指令运行的结果,可能会对后面的某一条指令产生影响,这是MIPS指令设计时 要尽量避免的。所以BEQ指令也很好的体现了MIPS的这一设计理念。
-
从条件分支指令的格式可以看出,目标地址只能使用十六位的位移量, 这是一个很大的局限,但是我们还得考虑如何充分发挥这十六位的作用。
-
如果以当前的PC寄存器 为基准,在MIPS中,指向下一条指令地址的寄存器称为PC,类似于x86中的IP寄存器。这个寄存器,是指向32位寄存地址的。 如果以它为基准,十六位位移量可以表示出,当前指令前后2的15次方字节这么一个范围。
-
但是我们要注意一点,MIPS的指令长度固定位32个比特,因此每条指令的位置, 一定会在四个字节对齐的地方,这样地址,最低两位肯定为0。
-
所以我们实际上可以用十六位的位移量去指示每四个字节为一个单位的地址。 这样就可以把目标地址的范围扩大四倍,可以达到前后128kB。
-
在这样的条件下,目标地址应该这么计算:
- 当分支条件不成立时,下一条指令的地址就等于当前的pc+4。
- 如果分支条件成立,那下一条指令的地址就等于已经加了4的pc, 再加上这个立即数乘以四。
-
非条件分支指令(J型)
- 相比于条件分支指令,有两个寄存器域用于比较条件,那如果我们不需要判断条件,我们就可以想办法扩大目标地址的范围。
- 想情况下是直接使用32位的地址, 但还是因为MIPS的指令长度固定为32位,而每条指令至少需要有opcode域,指示它指令类型。 这就占用了6个bit。那我们把剩下的26个bit全都用于目标地址,这就是J型指令。
- 在考虑到MIPS指令是四字节对齐的这个情况,对于这一行指令,下一条指令的地址的计算方法可以是将当前的pc加四之后, 取最高的四位,再加上J型指令编码中的26位, 然后在末尾填上两个零,虽然目标地址的范围还不能达到整个 4G的空间,但比之前的条件分支指令已经扩大了很多。
-
两种分支指令示例
-
非条件分支指令(R型)
- J型指令的目标地址可以是当前指令前后256MB的范围,那如果我们还想跳转到更远的地址, 应该怎么办呢?
- 有一个很简单的方法,就是两次调用J指令,第一条J指令尽可能跳到最远的地方, 然后在那个目标地址再放一条J指令,像接力一样再跳一次。 这个方法很简单,但是用起来不算太方便。
- 间接转移指令,MIPS中也可以用同样的方法, 这就是
jr
指令。 jr
指令有一个寄存器操作数, 可以把要转移的目标地址放到寄存器当中,这样就可以使用32位的目标地址了, 但是这样的指令显然无法用J型指令来实现, 那么需要新增一种指令类行吗?其实也不需要,我们就用原来的r型指令就可以很好的实现。 只用占用其中的一个寄存器位域,然后新增一种function的编码就可以了。
-