萌新的RiscV学习之流水线结构的概述-7
之前写完了单周期的指令 目前朝着流水线迈进
由于涉及学业机密 就不展示代码了
主要展示学习过程和一些想法
由于时钟周期必须满足所有指令中最坏的情况,所以不能使用那些缩短常用指令执行时间而不改变最坏情况的实现技术。因此,单周期实现违反了第 章中师速经常性事件这一设计原则。
流水线是一种能使多条指令重叠执行的实现技术。目前流水线技术广泛应用。
同样的原则也可用于处理器,即采用流水线方式执行指令。 RISC-V 指令执行通常包含
五个步骤:
1.从存储器中取出指令。
2.读寄存器并译码指令。
3.执行操作或计算地址。
4.访问数据存储器中的操作数(如有必要)。
5.将结果写入寄存器(如有必要)。
为了使讨论具体化,我们先建立一个流水线。在本例和本章的其余部分,我们只考虑这七条指令:双字载入 (l d) 、双字存储 (sd) 、加 (add) 、减 (sub) 、与 (and) 、或 (or)相等就跳转 (be q)指令。本例将单周期指令执行(每条指令执行需要一个时钟周期)与流水线指令执行的平均执行时间进行对比。假设在本例中主要功能单元的操作时间为:指令或数据存储器访问为200 ps, ALU 操作为 200 s, 寄存器堆的读或写为 IOO 。在单周期模型中,每条指令的执行需要一个时钟周期,所以时钟周期必须满足最慢的指令。
暂定的两种逻辑之间进行对比
流水线
一个是程序顺序执行
一个是程序按照流水线的形式执行
我们观察到在我们假定的这段区间中,总是以最长的一段一个步骤的时间 作为我们的步长 。
计算机流水线阶段也受限于最慢的阶段,要么是ALU操作,要么是存储器访问。同时我们假设写寄存器发生在时钟周期的前半段,读寄存器堆操作发生在时钟周期的后半段。
流水线在乎的是最长的一个阶段 需要处理多久 他把简单的事情 分成了很多步
而顺序执行在乎的最长的一整个执行流程
RiscV 仿佛天生为流水线而生的一般
第一点 : RiscV指令长度相同,x86会有1个字节到15个字节不等,在现代设计流水线的过程中,主要将x86转化成类似流水线的结构
第二点 : RiscV只有几种指令格式,源寄存器和目标寄存器的位置相同。
第三点 :存储器操作数只出现在RiscV的load或store
流水线冒险
在流水线中 会出现一种情况 在下一个时钟周期中下一条指令无法执行。
结构冒险
如上所述, RISC-V 指令系统是面向流水线设计的,这使得设计人员在设计流水线时很容易避免结构冒险。然而,假设图 4-25 的流水线结构只有一个而不是两个存储器,那么如果有第四条指令,则会发生第一条指令从存储器取数据的同时第四条指令从同一存储器取指令,流水线会发生结构冒险。
数据冒险
由于一个步骤必须等待另一个步骤完成而导致的流水线停顿叫 数据冒险
在计算机流水线中,数据冒险源于一条指令依赖千前面一条尚在流水线中的指令(这种关系在洗衣例子中并不存在)。例如,假设有一条加法指令,它后面紧跟着一条使用加法的和的减法指令 (X 19):
add x19 , x0 , x1
sub x2 , x19 ,x3
在不做任何干预的情况下,这一数据冒险会严重地阻碍流水线。 add 指令直到第五个阶段才写结果,这将浪费三个时钟周期。尽管可以尝试通过编译器来消除这些冒险,但结果并不令人满意。这些依赖经常发生,并且导致的延迟太长,所以不可能指望编译器将我们从这个困境中解救出来。
一种基本的解决方案是: 不需要等待指令完成就可以尝试解决数据冒险。对于上面的代码序列,一旦ALU计算出加法的和,就可以将其作为减法的输入。作为前递或旁路。
同时我们假设写寄存器堆操作发生在时钟周期的前半段,读寄存器堆操作发生在时钟周期的后半段。本章后面将一直莲循这个假设
这是我们初步想要实现的流水线方案
现在以add指令为基础进行下一步的实现
IF示取指令阶段,方框表示指令存储器;
ID 表示指令译码/读寄存器阶段,虚线框表示正在被读的寄存器堆;
EX 表示执行阶段,图中图形表示 ALU;
MEM 表示存储器访问阶段,方框表示数据存储器;
WB 表示写回阶段,虚线表示被写入的寄存器堆。
add指令的效果是 先从指令寄存器中读取出指令的地址 ,然后寄存器根据指令的地址得到指令(译码) 然后将指令传给寄存器,读寄存器号得到相应的数据 ,再把数据传递给ALU进行合适的执行计算。MEM是Datamemory 部分 用到datamemory
接下来的WB 表示写回。
1.取出指令, PC 自增。
2.从寄存器堆读出两个寄存器 x2 x3, 同时主控制单元在此步骤计算控制信号。
3.根据部分操作码确定 ALU 的功能,对从寄存器堆读出的数据进行操作。
4.将 ALU 的结果写入寄存器堆中的目标寄存器 (x1)。
阴影表示该单元被指令使用。因为 add 指令不访问数据存储器,所以 MEM没有阴影。寄存器堆或存储器右半部分为阴影表示该阶段它们被读,而左半部分为阴影表示该阶段它们被写。因此, ID 的右半部分在第二阶段为阴影,因为寄存器堆被读,而 WB 的左半部分在第五阶段为阴影,因为寄存器堆被写
下面介绍一下前递的逻辑
我们通过前递的概念 将add计算得到的结果 传递给下一步马上需要执行的sub中
这种形式看上去是很巧妙的,但是当我们需要的是另一种比如说load装载一个数的时候 必须等到从datamemory中读出来
这样的话又和R型指令的前递会产生冲突了
所以我们把这种冒险叫作载入-使用型数据冒险。 正式叫法是流水线停顿 ,通俗而言叫气泡。
当一条 load 指令之后紧跟着一条需要使用其结果的 型指令时,即使使用前递也需要停顿。如果不停顿,从存储器访问阶段的输出到执行阶段的输入这条路径意味着时间倒流,这是不可能的。该图实际是一个示意图,因为直到 sub 指令被取出并译码后才知道是否需要停顿。
第三种产生的冒险被称为控制冒险。 出现这种情况的原因是: 需要根据一条指令的结果做出决定,而其他指令正在执行。
这里的有问题的点在于 类似所说的分支指令 我们拿到之后必须进行合适的判断后才能确定下一条的指令究竟来自哪里 。
一种可能的解决方案是在取出分支指令后立即停顿,一直等到流水线确定分支指令的结果并知道要从哪个地址取下一条指令为止。
每遇到条件分支指令就停顿以避免控制冒险的流水线。本例假定条件分支指令发生跳转,并且分支目标地址处的指令是 or 指令。分支指令后会插入一个周期的停顿或气泡。我们将在 4.8 节中看到,实际中产生一次停顿的过程要更复杂。这种方法对性能的影响与插入一个气泡是一样的。
假设我们对于分支指令的处理都是暂停一小段时间 然会读取 这会导致速率会严重下降
我们可以通过计算机特有的预测功能。
其实也没有讲的很明白 大体说了一下思路。