流水线数据通路和控制
五级流水线从左到右流动过程中存在两个特殊情况:
在写回阶段,它将结果写回在位于数据通路中段的寄存器堆中。
在选择下一个PC值时,在自增PC值与MEM阶段的分支地址进行选择。
在译码阶段寄存器读取,写回阶段寄存器写回。
指令存储器只在指令的五个阶段中的一个阶段被使用,而在其他四个阶段中允许被其他指令共享。为了保留其他四个阶段中的指令的值,必须把从指令存储器读取的数据保存在寄存器中。所有的指令都会在每一个时钟周期里从一个流水线寄存器前进到下一个寄存器中。寄存器的名称由分开的两个阶段的名称来命名。
在写回阶段的最后没有流水线寄存器。所有的指令都必须都必须更新处理器中的某些状态,如寄存器堆,存储器或PC。因此,单独的流水线寄存器对于已经被更新的状态来说是多余的。例如,加载指令将它的结果放入32个寄存器中的一个,此后任何需要该数据的指令只需要简单地读取相应的寄存器即可。
当然每条指令都会更新PC,无论是通过自增还是通过将其设置为分支目标地址。PC可以看作一个流水线寄存器:它给IF阶段提供数据。
1 取指,使用PC中的地址从存储器读取指令,将指令放入IF/ID流水线寄存器,PC地址自增。
2 指令译码和读寄存器堆 将64位符号扩展的立即数字段和两个将要读取的寄存器编号 都与PC地址一起存储在ID/EX流水线寄存器中。
3 执行或地址计算 加载指令从ID/EX流水线寄存器读取一个寄存器的值和一个符号扩展的立即数。并使用ALU部件将它们相加,被存储在EX/MEM流水线寄存器中。
4 存储器访问 根据地址读取数据,并将数据存入MEM/WB流水线寄存器中。
5 写回 从MEM/WB流水线寄存器中读取数据,并写入寄存器堆中。
存储指令不进行写回。对于存储指令需要在ID阶段读取的寄存器信息传递给MEM阶段,然后写入存储器。
关键点: 在流水线数据通路设计中的每一个逻辑部件(指令存储器,寄存器读端口,ALU,数据存储器,寄存器写端口)只能在单个流水线阶段中被使用。 否则会发生结构冒险,
结构冒险:
- CPU在同一个时钟周期,同时在运行两条计算机指令的不同阶段。但是这两个不同的阶段,可能会用到同样的硬件电路。
- 访存和取指令,都要进行内存数据的读取。
我们的内存,只有一个地址译码器的作为地址输入,那就只能在一个时钟周期里面读取一条数据,没办法同时执行第1条指令的读取内存数据和第4条指令的读取指令代码
- 解决方案:
-
把我们的内存分成两部分,让它们各有各的地址译码器。这两部分分别是存放指令的程序内存和存放数据的数据内存
-
这样把内存拆成两部分的解决方案,在计算机体系结构里叫做哈佛结构。
每个流水线寄存器都需要保存当前阶段和后序阶段所需的部分指令信息
我们需要在加载指令的流水线寄存器中保留目标寄存器编号,就像存储指令为了在mem阶段的使用而将寄存器值从id/ex中传递到EX/MEM流水线寄存器那样。加载指令为了在WB阶段使用而将寄存器编号从id/EX传递到ex/MEM传递到mem/WB流水线寄存器。
数据冒险:前递与停顿
sub x2 x1 x3
and s12 x2,x5
or x13 x6 x2
add x14 x2 x2
sd x15 100(x2)
后四条指令都相关于第一条指令得到的存放在寄存器x2中的结果。后续的指令都是从x2中读取数据。x2寄存器在第五个时钟周期被写入,因此当第五个时钟周期前的值不可用。
最后一个潜在的冒险,一个寄存器同意同一时钟周期既读取又写入。
将EX阶段产生的操作数传递出出去。该数据可能是ALU或有效地址的计算结果。这意味着一个指令试图在EX阶段使用的寄存器是一个WB阶段要写入的寄存器。我们将数据作为ALU的输入。
sub x2,x1,x3.
and x12,x2,x5
and指令位于EX阶段,sub指令位于MEM阶段时被检测到。 属于1a冒险。
相关性检测
在sub和or指令存在2b类型的冒险。
在sub和add指令之间不是冒险,因为add指令的id阶段寄存器堆已经可以提供x2的正确值了。
sub与sd指令不存在数据冒险。
检查流水线寄存器在EX和MEM阶段的控制字段REGwire信号是否有效。因为不是所有寄存器都会写回寄存器。
RISCV要求每次使用X0寄存器作为操作数时认为操作数值为0. 如果流水线以X0为目标寄存器。addi x0,x1,2。 我们希望避免前递非零的结果值。
各流水线寄存器之间的相关关系 随着时间向前移动,因此可以通过前递在流水线寄存器中找到的结果。 以提供and指令或者or指令所需的ALU输入。流水线寄存器中的值表示所需的值在被写入寄存器堆之前就是可用的。
通过在ALU的输入添加多选器再辅以适当的控制,就可以在存在数据冒险的情况下全速运行流水线。
前递有助于解决存储指令与其他指令相关时造成的冒险,因为sd指令再MEM阶段只使用一个数据值。加载指令紧跟存储指令之后的情况。 因为sd指令在EMEM阶段所使用的数据会及时保存在ld指令MEM/WB寄存器中,为了实现这个操作,需要在啊存储器访问阶段加入前递。
数据冒险和停顿
当一条指令试图在加载指令写入一个寄存器之后读取整个寄存器,前递不能解决此处的冒险。 流水线阻塞
在第四个时钟周期时,数据还正在存储器中被读取,但此时ALU已经在为下一条指令执行操作了。当加载指令后跟着一条读取加载指令结果的指令时,流水线必须被阻塞以消除这种指令带来的冒险。
前递+ 冒险检测单元 检测加载指令
该单元在ID流水线阶段操作。从而可以在加载指令和相关加载指令结果的指令之间加入一个流水线阻塞。
这个单元检测加载指令
因为ld指令和and指令之间的相关性 是逆向的,所以不能通过前递来解决。这种指令组合会导致冒险检测单元产生一个停顿。
如果冒险检测单元,检测到了加载指令需要读取数据存储器,解析下来在EX阶段的加载指令的目标寄存器与ID阶段的指令中的某一个源寄存器相匹配。如果条件成立,指令停顿一个时钟周期。在一个时钟周期后,前递逻辑就可以处理这个相关并继续执行程序了。
如果处于ID阶段的指令被停顿,那么在IF阶段中的指令也一定要停顿。否则已经取到的指令就会丢失。只需要简单的禁止PC及粗气和IF/ID流水线寄存器的改变,就可以阻止这两条指令的执行。如果这些寄存器被保护,在IF阶段的指令就会继续使用相同的PC值取指令,同时在ID阶段的寄存器就会继续使用IF/ID流水线寄存器中相同的字段 读寄存器。
EX阶段开始的流水线后半部分必须执行没有任何效果的指令,空指令NOP。
总结
在流水线中加入空指令。
通过识别ID阶段的冒险,我们可以将ID/EX流水线寄存器中EX、MEM和WB控制字段设置为0,向流水线插入气泡 NOP.
在控制字均为0的情况下,不会有寄存器或者存储器被写入数据。
and指令所在的流水线执行槽变成了NOP指令,并且在and指令之后的指令都延迟一个时钟周期。