RISCV直接模式处理例外的实现问题与解决方案/PC的变化与SEPC的存储精确性问题/反相关代码导致的被迫按序执行的问题/内存别名问题

RISC-V

Posted by Bruce Lee on 2024-02-14

RISC-V世界的严格区分例外和中断

我们实现例外处理机制,使用的是RISC-V中的多个处理模式之一的直接模式.
就是所有的例外处理,都使用统一的例外处理入口地址,跳转到这个地址后,由这个地址里面的指令通过读取mcause寄存器的值,来选择跳转的不同的处理程序.
这是普遍例外的处理,中断也是一样的.
在RISC-V中,中断指处理器外部事件,例外指代所有事件

RISC-V直接模式处理例外的实现问题与解决方案

使用mcause和sepc全局寄存器

mcause寄存器是用来记录例外原因,例外原因是由别的地方设置的.这是一个全局变量.
sepc寄存器是记录发生例外的指令地址,这是为了在执行完例外处理后,能够返回指令地址重新加载指令运行.
毕竟我们尽量使计算机不要崩溃.
mcause和sepc设置在哪里,以及如何记录不同阶段发生的例外?
其实这里有限制,我们只检查发生在执行阶段的例外.同时,mcause和sepc就只记录执行阶段的地址.

在何处何时检查例外问题

通常情况下,是设计为在执行阶段中执行检查例外电路.这里衍生出另一个问题就是为何不再别的地方检查?
也可以在译码阶段检查,甚至是可以在全阶段检查,为每一个流水线上的指令提供例外发生的检查.但是这里是内部寄存器,硬件实现成本很高,所以针对每一条指令,我们只在这条指令的执行阶段执行例外检查.
细分下去,是在执行阶段中检查还是在执行阶段结束,指令到达了EX/MEM流水线寄存器后执行检查吗.我们一般选择在执行阶段中进行检查.因为这样,当出现了例外后,我们需要对之前的流水线指令清理,同时也需要对该指令进行清理.所以对IF/ID,ID/EX,EX/MEM流水线寄存器进行flush操作.此时流水线上出现了3个气泡.

硬件和操作系统协同

硬件主要体现在流水线上,接受例外原因,然后保存执行地址和例外原因,清理流水线,保存寄存器,然后跳转到给定的内存地址空间中,执行给定的例外处理程序.
操作系统是为了给定这些固定的程序流程,负责具体的处理不同的例外情况,要么直接中断程序的运行,或者最后返回程序重新执行.

在EX阶段中,PC的变化与SEPC的存储精确性问题

当指令达到ID/EX流水线寄存器中时,此时就已经开始执行EX流程的计算了.
与此同时,PC也从下一条指令取出地址,并且将地址数据变量保持了稳定!(放在IF/ID流水线寄存器前面!)
PC的值当然是PC+4!
此时发生了例外.PC不再是当初的PC,而是PC+4了.
如果我们不做处理,任由其走,将PC+4存储在SEPC中,那么这就是非精确中断(imprecise interrupts/exception),让操作系统去判断哪条指令引发例外.
RISC-V支持精确中断.

RISC-V支持精确中断的未来考虑

当流水线的深度加深之后,如果使用非精确例外会增加操作系统处理的难度和复杂度.
支持虚拟内存

指令并行的两种探究模式

第一种就是加深流水线,提高流水线的级数,正如当初引入流水线一眼,让更多的指令的在流水线的不同阶段同时被执行.
第二种就是提高隐藏的指令间并行.使用多发射技术.
可以看成使用了多条流水线,依据指令之间的关系,使得这个程序中的代码被送上多条流水线.但是这个逻辑上的多条流水线在实现上是被综合在一块的.

静态多发射和静态按序流水线调度中,编译器完成静态分支预测和代码调度
编译器帮助处理器尽可能减少或者消除冒险.

由多发射技术衍生出来的研究方法

第一个方向:指令调度
让本应该按序执行的程序代码,被送上了并行的执行逻辑.之前流水线上的并行,没有真正意义上的并行,本质上依然有一种前后关系.
关于如何让这些指令能够并行执行,那一定是需要指令之间具有并行性.
扩展指令的并行性:使用指令调度
指令调度在两个层面执行:编译器层面和硬件层面
如果指令调度只由编译器完成:静态按序流水线调度
如果只由硬件层面:动态乱序流水线调度

第二个:流水线
前面文章已经讲的很多了

编译层面的指令调度解决的是哪种类型并行方法

编译器常用的技术使用循环展开,寻找能够并行执行的指令.因为流水线经常遇见这种情况,前面停顿了,后面有一个独立的指令也会被停顿.
纯粹的静态发射技术,就是由编译器来决定哪些指令需要打包,发送到并行流水线上的同时执行.编译器对指令进行调度,重新排序.
加入一个双发射处理器,编译器在编译时,并不是全部的指令都能两两结合被打包发送到流水线上.所以编译器在面对无法同时执行的指令时,会在中间插入nop指令.使其顺序执行.
在包与包之间,如果遇见了冒险,处理器就会和之前描述的那样使用相同的技术去处理这个"单条大指令"之间的冒险

动态乱序流水线调度技术中,指令真正意义上的并行执行在哪些阶段?

编写程序是按照逻辑顺序进行执行的,但是代码中有些操作确实可以并行执行.
但是硬件或者编译器无法看出所有能够并行执行的或者无法识别并且抛弃出那些表面并行但是并不能并行执行的指令.
但是编译器还是要尽可能的消除的所有的冒险(我们显而已知或者简单的)

综上,我们的在动态多发射处理器中,在硬件层面进行并行分析的原理和真正并行时,指令的哪些部分是并行的.

我们的指令译码解码阶段是告诉硬件如何去理解该指令,也只有让硬件理解了该指令,才能作出判断.(当然了,也有通过添加硬件单元,来是处理器更加快速的识别指令类型)
所以我们的指令不管是并行的还是按序执行的,都必须按序解码和译码.

处理器在按序解码和译码之后,开始通过一系列的判断,将这些指令发送到对应的功能单元.

并行的真实性:

指令在功能单元中是真正能够得到并行操作的一个环节.这必须是基于能够并行操作的指令在所有的源操作数或者其他条件符合时,就会被执行.

如果没有达到执行标准,会被放入保留站等待.

重排序缓冲的存在意义

当执行完成后,依然需要依次写回.这是程序顺序指令和顺序思考的结果.我们并不能也不应该改写程序顺序执行的逻辑.
所以每一条指令在执行完成后,我们应该按序提交.

这种保守的处理方法也可以预防例外

RISC-V指令系统体系结构的真正强大之处

所有的指令的写回操作都是在最后一步完成,通过这个动态多发射处理器的实现来看,其RISC-V所提供的便利是非常大的,致使硬件实现上得到了很大的简洁性.

与动态多发射不同的是,静态多发射与传统的非发射和动态多发射的区别

非发射处理器的流水线设计,依然是IF->ID->EX->MEM->WB阶段,每一个阶段只有一个完整对应的功能单元
一条指令被流水线式的执行.

静态多发射不同的是,在ID阶段添加多个存储指令或者读写寄存器的端口,在EX阶段添加了多个功能单元.用于支持多条指令的同时执行.

这里的多条指令会首先由编译器进行分支预测和代码调度.将那些能够并行执行的指令打包成"单条大指令",一并送上流水线.
如果没有能够并行执行的指令,由编译器插入nop指令来替代.

静态多发射流水线处理单条大指令时,依然和非发射流水线是一样的:如果遇上"单条大指令"之间的控制冒险或者数据冒险,依然采用逻辑上的冒险检测,停顿,前递.

超长指令字设计思路在RISC-V的静态多发射体现

正如上面所说的,将发射指令包看成一条预先定义好,需要进行多种操作的指令,是符合超长指令字的设计思路.

反相关代码导致的被迫按序执行的问题

编程时,会想应该尽可能使用少的变量去完成更多的工作.但是这个其实是错误的.
其根源是流水线在执行时,多个连续的指令都用到了这个变量.数据冒险造成的停顿,nop指令都会影响处理器的效率.
如果这个变量在多个指令中被重复使用,但是有些是没有依赖的.这是反相关:名字复用(典型的就是寄存器),被迫导致的顺序排列执行,这并不是一种指令间真实的数据相关.
解决方法是在编译器层面作出动作:编译器会使用额外的寄存器,这个过程称为寄存器重命名.其目标是,除了真数据相关,消除指令间存在的其他数据相关.

为了能够得到等多的指令,进行数据冒险分析,编译器通常会将循环展开,以获得更多能够分析的代码.

指针的大量使用导致的内存别名问题

程序的代码中常使用指针,这种数据结构特别容易产生存储器别名,这会导致潜在的数据相关

上述中寄存重命名解决的就是使用了同一个变量完成多个不依赖的任务,但是也有一种可能,使用不同的指针去完成依赖/不依赖的任务,但是这里的特点是指针指向的内存地址都一样,这样子编译器很难使用额外的寄存器去提高性能.

推测式执行思想和流水线思想推动的处理器发展同时也引来的减低能效的问题

推测式执行思想就是一个挖掘程序的指令并行性,流水线就是将指令分而执行值,可以提高硬件资源的利用率.

技术发展,提供了更多的晶体管,产生新的结构,提高了性能,但是同时也减低了能效.

我们撞上了能耗墙,因此转向单芯片多处理器架构.所以我们无需设计更深的流水线或者采用更激进的推测机制
如果一味的设计更深流水线和采用更激进的推测机制,这会反而带来低效.

简单处理器和复杂处理器在考虑性能和能耗的情况下的设计需求

虽然复杂处理器的性能比简单处理器的性能高.但在相同的性能下,简单处理器比复杂处理器的能耗低.
在有能耗限制下,那么简单处理器所拥有的性能就比复杂处理器高.

静态多发射和静态按序流水线组合,动态多发设和静态按序流水线,动态多发射和动态调度流水线

静态多发射的指令发射包是由编译器来完成的,硬件不进行任何调度.
静态按序流水线中的每一个指令都严格按照先后顺序执行.
动态多发射的指令是在程序执行时,由硬件来完成指令调度,这样有更高效的执行方式.
动态调度流水线,可以单周期内承载多个并行指令的执行阶段,指令的执行没有严格的先后顺序,只要满足条件就可以执行.
静态多发射和静态按序流水线的组合能够组成一个静态多发射处理器
动态多发射和静态按序,动态调度流水线都可以进行组合设计处理器

流水线与指令系统的深度绑定

流水线是简单的 (X)

通过对流水线的级数,对指令本质的分析,加上预测技术,冒险检测技术,前递技术,逻辑控制单元设计等等,这些都需要非常细致.

对于流水线等结构设计,可以与工艺无关 (X)

工艺的进步,也促使了流水线的多个功能单元和动态流水线技术的发展.
延迟转移技术就是随着工艺的进步而显得多余了.

缺乏对指令系统设计的考虑反过来会影响流水线的实现

流水线设计的难度由指令系统的复杂性引起的.
可变的指令长度和不确定的执行时间会造成流水线各级不均衡,从而使得设计中的冒险检测逻辑变得特别复杂.该问题已经由intel的微操作和微流水线技术解决.
复杂的寻址模式会引起不同类型的问题:更新寄存器的寻址模式让冒险检测更加复杂.一些多次访问内存的寻址模式让流水线控制更加复杂.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !