首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

处理器结构体系-3

处理器结构体系-3

本帖最后由 look_w 于 2017-9-22 21:31 编辑

流水线的局限性

1、不一致的划分
之前的是一个理想的流水线化的系统,每个阶段需要的时间都相同。而实际系统通过各阶段的延迟一般是不同的。且运行时钟的速率是由最慢阶段的延迟限制的。(即系统吞吐量受最慢阶段的速度所限制)

2、流水线过深,收益反而下降
例如,我们把计算分成6个阶段,每个阶段需要50ps。在每对阶段之间插入流水线寄存器就得到了一个六阶段流水线。
这个系统的最小时钟周期为50+20=70ps,吞吐量为14.29GIPS。性能比3阶段流水提高了14.29/8.33=1.71倍。由于通过流水线寄存器的延迟,吞吐量并没有加倍。这个延迟成了流水线吞吐量的一个制约因素。

为了提高时钟频率,现代处理器采用了很深的(15或更多的阶段)流水线。

分支预测
流水线化设计的目的就是每个时钟周期都发射一条新指令,要做到这一点,我们必须在取出当前指令之后,马上确定下一条指令的位置。
但如果取出的指令是条件分支指令,要到几个周期后,也就是指令通过执行阶段之后,我们才能知道是否要选择分支。类似的,如果取出的指令是ret,要到指令通过访存阶段,才能确定返回地址。

对条件转移来说,我们既可以预测选择了分支,那么新PC值应为valC,也可以预测没有选择分支,那么新PC值应为valP。
对ret指令,可能的返回值几乎是无限的,因为返回地址位于栈顶的字,其内容可以是任意的。在设计中,我们不会试图对返回地址做任何预测。只是简单地暂停处理新指令,直到ret指令通过写回阶段。

无论哪种情况,我们都必须以某种方式来处理预测错误的情况,因为此时已经取出并部分执行了错误的指令。
(流水线惩罚待写)

流水线冒险
使用流水线技术,当相邻指令间存在相关时会导致出现问题。
这些相关有:
1、数据相关:下一条指令会用到这一条指令计算出的结果
2、控制相关:一条指令要确定下一条指令的位置,例如在执行跳转、调用或返回指令时。
这些相关可能会导致流水线产生计算错误,称为冒险

暂停来避免数据冒险暂停(stalling)是避免冒险的一种常用技术。让一条指令停顿在译码阶段,直到产生它的源操作数的指令通过了写回阶段,这样我们的处理器就能避免数据冒险。
暂停技术就是让一组指令阻塞在它们所处的阶段,而允许其他指令继续通过流水线
【例】
irmovl  $10, %edx
irmovl  $3, %eax
addl   %edx, %eax
halt

当对addl指令译码之后,暂停控制逻辑发现了对两个源寄存器的数据冒险。(其发现前面的执行、访存或写回阶段中至少有一条指令会更新寄存器%edx或%eax 我们addl下一阶段就要取%eax和%edx的值,但却不能保证其是更新过的值)
暂停控制逻辑就在执行阶段中插入一个气泡,并在下个周期重复对addl的译码
它再次发现对两个源寄存器的冒险,就在执行阶段中插入一个气泡,并在下个周期重复对addl的译码。
实际上,机器是动态地插入3条nop指令。(插到执行阶段,而不是从取指开始)
irmovl  $10, %edx
irmovl  $3, %eax
bubble
bubble
bubble
addl   %edx, %eax
halt
(这个过程就像,排队的时候前面的人前进了一步,但这时有另一个人插在了你前面的空缺中,你的位置保持不动,但前面的人都前进了一步。不断的有空缺,但不断地有人插入,你就一直在原地不动)
当确定前面的指令已经更新过了我们要的两个寄存器的值,则addl开始前行。

但是这样的解决方案得到的性能并不好,一条指令更新一个寄存器,紧跟其后的指令就使用被更新的寄存器,像这样的情况不胜枚举。这会导致流水线暂停长达三个周期,严重降低了整体的吞吐量

转发来避免数据冒险
在译码阶段从寄存器文件中读入源操作数,但是对这些源寄存器的写有可能要在写回阶段才能进行。与其暂停直到写完成,不如简单地将要写的值传到流水线寄存器E作为源操作数
(即,我们不必等到irmovl  $10, %edx和irmovl  $3, %eax 完成对寄存器的写更新之后再继续addl,而是在addl译码阶段发现需要%edx、%eax值,译码逻辑不从寄存器文件中去读,而是用前面阶段未写入寄存器的值。)
这种将结果直接从一个流水线阶段传到较早阶段的技术称为数据转发



在周期4中,译码阶段逻辑发现有在访存阶段中对寄存器%edx未进行的写,还发现在执行阶段中正在计算寄存器%eax的新值。它用这些值,而不是从寄存器文件中读出的值,作为valA和valB的值。

加载/使用数据冒险
有一类数据冒险不能单纯用转发来解决,因为存储器读(访存阶段)在流水线发生的比较晚
例:
mrmovl  0(%edx), %eax
addl  %ebx, %eax
halt
指令mrmovl读取存储器0(%edx)处的值,发生在访存阶段,而此时指令addl已经在执行阶段了!其已经读取了%eax的值了。即由于mrmovl指令获取的操作数值比较晚,来不及发送给后面需要用的指令了。
我们可以将暂停和转发结合起来,避免加载/使用数据冒险。(既然是来不及发送给后面的指令,那就让后面的指令暂停几个周期,再发送)

当mrmovl指令通过执行阶段时,流水线控制逻辑发现译码阶段中的指令(addl)需要从存储器中读出的结果。它会将译码阶段中的addl指令暂停一个周期,导致执行阶段中插入一个气泡。 mrmovl指令从存储器中读出的值可以从访存阶段转发到译码阶段中的addl指令。

这种用暂停来处理加载/使用冒险的方法称为加载互锁加载互锁和转发技术结合起来足以处理所有可能类型的数据冒险

异常处理
异常可以由程序执行从内部产生,也可以由某个外部信号从外部产生。
简单的三种内部异常:
1、halt指令
2、非法指令
3、访问非法地址
(还有一些外部异常:网口收到新包、用户点击鼠标等)

在简化的ISA模型中,当处理器遇到异常时,会停止,设置适当的状态码,且应该是到异常指令之前的所有指令都已经完成,而其后的指令都不应该对程序员可见的状态产生任何影响。
在一个更完整的设计中,处理器会继续调用异常处理程序,这是操作系统的一部分。

★一般地,通过在流水线结构中加入异常处理逻辑,我们会在每个流水线寄存器中包括一个状态码Stat。如果一条指令在其处理器中于某个阶段产生了一个异常,这个状态字段就被设置成指示异常的种类。
异常状态和该指令的其他信息一起沿着流水线传播,直到它到达写回阶段。在此,流水线控制逻辑发现了异常,并停止执行。

异常事件不会对流水线中的指令流有任何影响,除了会禁止流水线中后面的指令更新程序员的可见状态(条件码寄存器和存储器),直到异常指令到达最后的流水线阶段。
因为指令到达写回阶段的顺序与它们在非流水化的处理器中执行的顺序相同,所以我们可以保证第一条遇到异常的指令会第一个到达写回阶段,此时程序执行会停止,流水线寄存器(W写回)中的状态码会被记录为程序状态。
返回列表