学习事物是怎样工作的有其内在价值:处理器是如何工作的对于我们普通人来说一直是个秘密,我们将从零开始构建一个流水线处理器,为了实现这一处理器的软硬件,我们有大量的前提知识要学习,包括:指令系统、硬件设计背景知识(hcl)、以及流水线的通用原理。学习完这些内容以后我们才开始YY一个我们自己的86处理器。
本章内容
※ YY一个指令集Y86:各种状态、指令集、编码、编程规范、异常处理;
※数字硬件设计背景:处理器的基本构件,如何连接操作,介绍hcl语言;
※流水线原理,如何实现高效的五个步骤;如何处理冒险与冲突;
※开始实现我们自己的Y86;
笔记
一、基础知识部分
1、介绍一个精简的Y86指令集
①状态码
②所支持的指令
说明:
1] 字段 fn 指明是某个整数操作:OPL、数据移动条件:cmovXX、分支条件:jXX;
2] 图中最后的pushl、popl指明不需要访问任何寄存器0xF表示;
3] xxmovl中:i代表立即数,r代表寄存器,m代表存储器;
4] OPL代表的是:add sub and xor;jXX代表:jmp、jle、jl、je、jne、jge、jg。
【疑惑:不允许:存储器——存储器 ;立即数——存储器 为什么?】
③一条普通指令的编码:
其他具体的编码如下:
根据功能进行最优化的存储方式,比如rrmovl与条件传送有相同的code编码,这是因为其行为相当于无条件传送。
!我们还需要访问寄存器,为了访问的便利我们给寄存器编号,统一存放在CPU的一个寄存器文件中。就如学生时代的学号一样,唯一、明确,也像收监的犯罪嫌疑人,你从入狱的那一天起将没有名字,没有身份,只是一个简化的号码。
举例说明:rmmovl %esp , 0x12345(%edx)
rmmovl ——>4 0
esp|edx—③—>4 2
0x12345——>00,01,23,45(小端法存储)
合成到最后的指令就是:[40] [42] [45] [23] [01] [00]
任何一个指令序列都是一个唯一的编码
④异常
状态码如下:
⑤我们写一段代码试一试
翻译成x86和y86汇编形式,基本上没什么不同
(特别注意:pushl 将栈指针esp减去4,并将内容写入存储器中。当我们执行push esp时,我们约定:首先雅鹿esp的原始值,然后压入减去4的esp的值)
2、逻辑设计和硬件控制语言
我们将会学到:计算对位进行组合的逻辑;存储位的存储原理和更新时钟信号三部分内容。
① 对单个为进行操作:逻辑门
② 组合逻辑用HCL表示:一个逻辑门肯定实现不了很多功能,我们来组合一下
用于检测两个Bit是否相等:
当 a = b = 0时:上与逻辑为0,下与逻辑为1 或逻辑输出eq为1;
当a = b = 1时:上与逻辑为1,下与逻辑为0 或逻辑输出eq为1;
多路复用器:根据s的值选择是输出a或者b
当 s = 0时,上与为b, 下与为0,或逻辑输出out = b;
当 s = 1时, 上与为0, 下与为a,或逻辑输入out = a;
四路复用器:
字级组合电路:单个的bit位并没太大用途
多路复用组合电路:使用32个MUX组合而成,只是非门是统一的一个
算术逻辑单元:ALU
③ 集合关系
在处理器设计中,很多时候需要将一个信号与许多可能匹配的信号做比较,以此来检验正在处理的信号是否属于某一类指令代码;
④ 存储器和时钟:存储原理(组合电路本身不能存储信息,只是一个时间序列电路)
分类:
1] 时钟存储器(寄存器):存储单个位或字;
2] 随机访问存储器(存储器):存储多个字;(虚拟存储系统,寄存器文件,)
硬件寄存器和程序寄存器的区别:
硬件寄存器:在硬件中寄存器直接将输入和输出连接到电路的其他部分;
程序寄存器:CPU中位数不多的可寻址的寄存器,地址就是寄存器ID。
a.硬件寄存器:当时钟信号处于高频上升阶段时,才将输入的信号加载
b.寄存器文件
有两个读:A、B端口,和一个写:W端口。允许同时进行多个读写操作,可以同时读两个寄存器和写一个寄存器的值。srcX、desW代表地址,valX、valW代表要数据。如我们要访问3号寄存器ebx的值,就将srcA=3,然后valA就是要访问的数据。
c.随机访问存储器
假如我们要访问一个地址address上的数据,就将该地址传入address中,同时设置write=0,那么在data out上的数据就是我们要访问的值了。
3 流水线通用原理
以自动化洗车设备为例,车子需要按照一定的速度通过流水线,增加了一部分处理量。也就是不用等前面的车子洗完,就可以开入另一辆车子了;但这个系统有时候也会增加延迟,比如你只需要给车子打蜡,但也必须要经过喷水、抹干阶段。
①未加入流水线的系统:
②加入流水线的:
通过将指令分成了3个阶段,我经过一个120ps周期,每条指令就行进下一个阶段
③流水线操作详细说明:
说明:详细说明240-360的时钟周期的故事
1] 点①时钟开始上升之前(239):阶段A中计算的指令I2已经到达第一个流水线寄存器的输入,图中蓝色区域表示。但是该流水线寄存器Reg的值仍然是I1中的深灰色区域;指令I1在阶段B中计算的值已经到达第二个流水线流水线寄存器的输入Comb logic B,深灰色表示;
2] 点②时钟开始进入上升阶段(241):两个组合逻辑输入:A和B,将结果放入到流水线寄存器中,形成图2的形式。蓝色Reg和深灰色Reg,并将组合逻辑A设置成发起指令I3计算;
3] 点 ③阶段信号开始传播,信号通过不同的速率通过各个不同的部分;
4] 点④360来到之前,指令I3完成组合逻辑A处的加载,形成浅灰色区域。
④流水线的局限性:
1] 不一致的划分:由于阶段的延迟并不一样,这样空闲的空间就增加了延迟;
2] 流水线过深,收益下降;
⑤带反馈的流水线系统:许多指令前后相关,如何建立带反馈的指令
二、从零开始构建我们的Y86处理器
1、一个Y86的顺序实现(简易方式)
为了要实现一个通用的框架,我们通常要找到这个框架的层次结构,使得许多不同的指令可以共享相同的硬件,这是降低复杂度的一个有效的方法。
①跟踪一块指令序列的执行:
②以 subl %edx, %ebx为例:
③SEQ硬件结构图:
(注:为什么是逆序从下往上写的?我们以后讲流水线的时候再讲解。)
④SEQ的时间和执行顺序:
一条重要的原则是:处理器从来不需要为了完成一条指令的执行而去读该指令更新了的状态。
比如我们之前讲到的push %esp指令,如果是分步执行,先将esp-4,然后将更新后的值作为写地址效率就太低了。我们的做法是在执行阶段计算出valE的值,然后在访存和写会阶段同时完成更新寄存器和存储器的操作。
我们再来看一段代码:
我们重点来看一下,第3和第4条指令的执行:
我们来简单的讲解一下执行的过程:
1] 在时钟周期3开始的时候点①状态元素保存的是周期2执行irmovl时的更新后的状态,用浅灰色表示。点②:随着时钟周期开始上升时,地址0x00c载入pc中,这样就取出了addl指令,值沿着Combinational Logic流动,充满了浅蓝色区域。在这个时候ebx就是加了以后的0x300新值,以及pc的新值0x00e。但是指令的状态仍然保留的是irmovl指令设置的值。
2] 时钟周期4:点③开始的时候,会更新程序计数器、寄存器文件、和条件码寄存器。也就是addl更新的状态。同样的会取出je指令,用深灰表示,ZF=0不会选择分支。在④的时候,pc产生新值0x013,Combinational Logic已经被更新过,但是状态还是保持的是addl指令的设置的值。
我们再重申一下原则:通过时钟周期来控制元素的更新,通过组合逻辑来传播,每次时钟周期由低到高时,处理器开始执行一条新指令。处理器从来不需要为了完成一条指令的执行而去读该指令更新了的状态。
⑤Seq阶段的实现:
1] 取指阶段
说明:第一个字节为Split,字节0标号为:icode和ifun;1-5字节为Align:寄存器指示符和常数字的组合。
2] 译码和写回阶段:这两个阶段都需要访问寄存器文件
同时支持两个读,A、B;和两个写:E、M;
3] 执行阶段
根据ALU fun信号设置是否进行,加减乘除操作。ALU的输出就是valE
4] 访存阶段
5] 更新PC阶段
2.Y86的流水线实现
我们说过,我们的目的是建立一个流水线化的处理器Y86,学了这么久的基础知识,也建立了一个简化的Seq模型,我们终于可以开始我们的内容了,首先我们来优化一下简易设计中的seq,我们称之为seq+:
①SEQ+:重新安排计算pc阶段
SEQ+中没有单独的硬件寄存器来存放pc,而是通过pIconde、pCnd等寄存器计算pc的值。
设计原则:只要处理器能够正确的执行机器语言,我们不需要程序猿可见状态进行编码,只要能产生正确的值就可以了。
详细的seq+图如下:
②插入流水线寄存器的pipe-处理器结构:
说明:图中以深蓝色区域表示的就是我们加入的5个流水线寄存器,白色方块代表不同的字段;
F : 保存pc预测值;
D :位于取指和译码之间,保存最新的指令信息,即将由译码阶段处理;
E :位于译码和执行之间,保存最新译码指令和从寄存器读出的值,即将由执行阶段处理;
M :位于执行和访存之间,保存最新执行指令的结果,条件分支和分支目标,即将由访存处理;
W :位于访存和反馈之间,提供给寄存器文件写,完成ret指令,向pc提供返回地址。
③ 对信号进行重新排列和标号
在pipe-中有4个标号为stat的白色区域,就是不同指令的状态码:D_stat、E_stat、M_stat、W_stat来区分。而小写的:f、d、e、m、w指的是流水阶段。
④ 预测一下一个pc:除了分支和ret指令外,我们基本上能够用valC和valP的值来预测下一条指令的地址,这样就可以通过之前学到的流水线技术使得每个周期执行一条指令。
⑤ 流水线的冒险和处理方法
什么是流水线的冒险?一条指令更新后面指令会用到的值。
当irmovl $3, %eax在第六个时钟周期的时候,写回阶段eax还未得到正确的值3,而流水线的系统中,addl %edx, %eax已经需要使用eax的值,这时侯得到的就是错误的值。
冒险的分类:1)数据冒险;2)控制冒险;3)加载\使用数据冒险
我们之前讲到流水线的反馈中,提到过如果两条相邻数据存在关联该如何处理成为了一个问题。特别是在使用流水线技术的情况下,如果一条指令要修改一个寄存器的内容,而下一条指令又刚刚需要引用该处的内容。如何使得两条指令执行正确。
方法一:暂停
将指令阻滞在译码阶段,相当于加入一条nop指令,直到获得正确的值,才让其他指令继续执行。优点是实现容易,缺点是性能不佳
方法二:转发
图中可以看出,我们不是在被动的等待irmovl执行完毕,而是在addl指令需要用到eax值的时候,我们将irmovl的W阶段的中间值,W_valE=3转发到addl指令中去,赋值给eax。
5个用作源的有:e_valE、m_valM、M_valE、W_valM、W_valE;两个目的:valA、valB
有一类比较特殊的数据冒险:加载\使用数据冒险,我们特别讲一下?
上面的两条指令,我们具体来看一下流水线的执行过程:
我们执行0x018指令,要在第八个时钟周期的时候才能获得eax的值,而0x01e指令在第七个时钟周期就需要这个eax,我们不可能将这个值从第八个周期传回第七个周期。就像不能穿越时间线回到过去一样。遇到这种情况我们的处理方式是:使用暂停和转发相结合的方法,确切的讲就是暂停addl执行的一个时钟周期,使得在周期8的时候实现转发。
⑥ 异常处理
异常分为:halt指令、有非法指令和功能码组合的指令、访问非法地址
处理方法:有多条指令引起异常,由流水线最深的指令优先级最高
多条分支中有异常,取消预测指令
出现异常,根据状态码的stat值,禁止其他指令更新程序状态
⑦ PIPE各个阶段的实现:
1] PC选择和取指阶段
PC选择器必须要选择出正确的pc值,一般来说有三种方式可选:当预测错误分支进入访存,选择M_valA中的valP;当ret指令进入写回阶段时,选择W _valM;其他情况下选择F_predPC。
2] 译码和写回阶段:
疑问:对于转发逻辑和不同的转发源还不明白;对Sel+Fwn的具体功能不明确
3] 执行阶段:
4] 访存阶段
⑧ 流水线控制逻辑
1]特殊情况的处理
ret指令实际的处理过程是在取指阶段反复的取出ret指令后面的指令,在译码阶段插入气泡;
当分支结果还未计算出来的时候,保持预测指令执行在FD阶段,这样可以防止,如果预测错误,预测指令不会修改程序状态。如果决定不执行分支,取消指令的执行不会带来错误的影响;实现的方法是根据stat的值:禁止执行阶段中的指令设置条件码;向存储阶段插入气魄,以禁止写入;当写回阶段有异常的时候,暂停写回。
2]流水线的控制机制:加入气泡和暂停
当暂停stall=1的时候,状态保持先前的值不变;
当bubble=1的时候,用nop覆盖当前的状态
3]控制条件的组合
我们需要处理的情况为组合B:加载指令设置寄存器esp的值,而ret指令将esp用作源操作数,因为它必须从栈中弹出返回地址,流水线控制应将ret指令阻滞在译码阶段。
(注:系统分析的重要性,仅仅运行正确其实并不可靠)
4]最终控制模型:
⑨ 未完成的工作:与存储器的接口和多周期指令。
2016-12-25日晚,阅读完成