一. 流水线
1. 更深的流水线
- 流水线的级数越多,意味着流水线被切得越细,每一级流水线内容纳的硬件逻辑便越少,意味能够运行到更高的主频。
- 由于每一级流水线都由寄存器组成,更多的流水线级数要消耗更多的寄存器,以及更多的面积开销。这是流水线加深的负面意义。
- 由于每一级流水线需要进行握手,流水线最后一级的反压信号可能会一直串扰到最前一级造成严重的时序问题,需要使用 些比较高级的技巧来解决此类反压时序问题。
- 预测失败,需要将所有预取的错误指令流全部丢弃掉,重新取正确的指令流,这个过程叫作“流水线冲刷( Pipeline Flush )”。
2. 乱序
关于处理器的乱序问题,这里是有关于Tomasulo算法的问题,以我目前的理解
保留站:数据的源操作数未准备好的时候,在保留站等待,当准备好就开始执行。
ROB:ROB保证了乱序执行的问题,实际的乱序执行,并不是指令乱序结束,是执行的时候,即EX段是乱序的,但最后的WB还是通过ROB保证了顺序。
3. 数据冲突
三种数据冲突为WAW,WAR,RAW,关于数据冲突解决的方法依然是Tomasulo算法。
==RAW==:
是真的数据相关,必须在前一个数据计算后才能进行使用,此处使用“动态调度”的方法来解决
- 一方面采用数据旁路传播(Data Bypass and Forward )技术,尽可能让前序指令的计算结果更快地旁路传播给后序相关指令的操作数。
- 另一方面尽可能地让后序相关指令在等待的过程中不阻塞流水线,而让其他无关的指令继续顺利执行。
- 早期的 Tomasulo 算法中通过保留站可以达到这两方面的功效,但是保留站由于保存了操作数,无法做到很大的深度(否则面积和时序的开销巨大〉。
- 最新的高性能处理器普遍采用在每个运算单元前配置乱序发射队列 Issue Queue) 的方式,发射队列仅追踪 RAW 相关性,而并不存放操作数,因此可以做到很深(譬16 个表项〉。在发射队列中的指令一旦相关性解除之后,再从发射队列中发射出来读取物理寄存器组(Physical Register File ),然后发送给运算单元开始计算。
==WAW、WAR==:
通过ROB的存在控制WB部分,保证了WAW和WAR的正确。
4. E200的流水线
蜂鸟 E200 处理器核的流水线的按序主体是位于第一级的“取指”和位于第二级的“执行”和“写回”,因此我们非严谨地定义蜂鸟 E200 处理器核的流水线深度为二级。
5. IFU
==如何进行快速寻址:==
即使是片上的SRAM也需要几个周期取指令,为了加快速度,通常使用ITCM和I-cache的方法
ITCM Clnstruction Tightly Coupled Memory)
- 指令紧精合存储器,是指配置一段较小容量(一般几十阻)的存储器(通常使用SRAM ),用于存储指令,且在物理上离处理器核很近而专属于处理器核,因此能够取得很小的访问延迟(通常 个时钟周期〉
- ITCM 的优点是实现非常简单,容易理解,且能保证实时’性
- ITCM 的缺点是由于使用地址区间寻址,因此无法像缓存( Cache )那样映射无限大的存储器空间;同时为了保证足够小的访问延迟,无法将容 做到很大(否则无法一个时钟周期访问出来 SRAM 或芯片无法容纳过大的 SRAM ),因此 ITCM 只能用于存放容量大小有限的关键程序指令
==如何处理非对齐指令==
SRAM取指一次是32位,所以如果没有对齐,需要取两次然后拼接。
-
普通指令非对齐:使用剩余缓存( Leftover Buffer) 保存上次取指令后没有用完的比特位,供下次使用。
假设从 ITCM 中取出 32 位的指字,但是只用到了它的低 16 位,这种情形可能是由于两种原因造成的。
1.只需要使用此次取出的 32 位中的低 16 位和上一次取出的高 16 位组成了一条32 位指令。
2、这个指令长度本身就是 16 位宽,因此只需要取出的低 16 位。
那么对于此次没有使用到的 16 位,则可以暂存于剩余缓存中,待下个周期取出下32 位的指令字之后,就可以马上拼接出新的完整 32 位指令字。 - 分支跳转指令非对齐:对于分支跳转指令而言,如果跳转的目标地址与 32 位地址边界不对齐,且需要取出一个32 位的指令字,上述剩余缓存也无济于事了(因为剩余缓存只有在按顺序取指时,才能提前预存上次没有用完的指令宇〉。对此,常见的实现方式是使用多体(Bank )化的 SRAM进行指令存储。以常见的奇偶交错方式为例,使用两块 32 位宽的 SRAM 交错地进行存储,两个连续的 位指令字将会被分别存储在两块不同的 SRAM 这样对于地址不与 32位对齐的指令,则一个周期可以同时访问两块 SRAM 取出两个连续的 32 位指令字,然后各取其中一部分进行拼接成为真正需要的 32 位指令。
对于BPB,如果两条条件分支指向同一个条目(因为无法覆盖全部地址),则称为:别名重合(Aliasing)
6. 分支预测
这个部分有篇博客讲的非常清楚:
https://blog.csdn.net/edonlii/article/details/8754724
或者https://www.cnblogs.com/TaigaCon/p/7791303.html
摘取其中一段介绍BHT:
例如,n = 2。这意味着过去的2次分支情况被保存在一个2位的移位寄存器中。因此可能有4种不能的分支历史情况:00, 01, 10, 11。其中0表示未发生跳转,1表示发生了分支跳转。现在,设计一个模式历史表(pattern history table),有4个条目,对应于2n= 4种可能的分支历史情况。4中历史情况的每一种都在模式历史表对应于一个2位饱和计数器。分支历史寄存器用于选择哪个饱和计数器供现在使用。如果分支历史寄存器是00,那么选择第一个饱和计数器;如果分支历史寄存器是11,那么选择第4个饱和计数器。
假定,例如条件跳转每隔2次执行就发生一次,即分支情况的历史串行是001001001...。在这种情况下,00对应的饱和计数器将是状态“强选择”(strongly taken),表明在两个0之后必然是出现一个1。01对应的饱和计数器将是状态“强不选择”(strongly not taken),表示在01之后必然是出现一个0。这也同样适用于10状态。而11状态从未使用,因为不可能出现连续两个1。
2级自适应预测器的一般规则是n位分支历史寄存器,可以预测在所有n周期以内出现的所有的重复串行的模式。
==关于BTB==:是指使用容量有限的缓存保存最近执行过的分支指 PC 值,以及它们的跳转目标地址。
对于后续要取指的每条PC值,将其与 BTB 中存储的各个 PC 值进行比较,如果出现匹配,则预测这是分支指令,并使用其对应存储的跳转目标地址作为预测的跳转地址
7. 分支跳转指令
一共八条
指令 | 描述 |
---|---|
jal | 无条件直接跳转。“jal x5, offset”,20位立即数作为offset,offset*2 + 当前PC,得到目标address。PC+4放入结果寄存器 |
jal | 无条件间接跳转。"jal x1,x6,offset",12位立即数作为offset,目标address = [x6] + offset*2 |
beq | 相等则跳转 address = PC + offset*2 |
bne | 不相等跳转 |
blt | 第一个数小于第二个 (有符号) |
bltu | 第一个数小于第二个 (无符号) |
bge | 第一个数大于等于第二个 (有符号) |
bgeu | 第一个数大于等于第二个 (无符号) |
jal用于子程序调用,然后jalr用于从子程序返回
8. E200利用RISC架构的优势
- 规整的指令编码格式:整齐的取指,不会产生横跨现象
-
指令长度指示码放于低位:对于压缩指令,取指逻辑在
仅取到 16 位指令字时就可以进行译码判断,当前指令是16位长还是 32 位长,而无需等待另外一半 16 位指令字取到之后,才开始译码
image - 简单的分支跳转指令:见第7节
- 没有分支延迟槽指令:RISC-V 架构放弃了分支延迟槽,RlSC-V 架构的制定者认为放弃分支延迟槽的得大于失。因为现代的高性能处理器的分支预测算法精度己经非常高,可以有强大的分支预测电路,保证处理器能够准确地预测跳转执行达到高性能。
-
提供明确的静态分支预测依据:静态分支预测是一种最简单的预测技术,但是静态分支预测往往固定地预测向后跳转(或者向前跳转〉为需要跳转( Taken)。 如果软件实际执行中未必如此,则会造成预测失败。
RlSC 架构中明确规定,编译器生成的代码应该尽量优化,使得向后跳转的分支指令比向前跳转的分支指令有更大的概率进行跳转。 -
提供明确的 RAS 依据:RISC-V架构中明确规定,如果使用 jal 指令且目标寄存器索引值 rd 等于 xl1或者 x5 ,则属于需 进行 RAS 压找;如果使用jalr 指令,则按照使用的寄存器值的不同,明确规定了相应的 RAS 压栈或者出栈行为
(注意:图中的此表示 xl1或者 x5)
image