简介
最近在一生一芯项目中完成了5级流水线的实现,因为已经非常了解教材上的5级流水线结构了,所以实现起来比较容易,不过有些概念又重新回到了视野中,就是指令依赖。虽然5级流水线中的指令依赖已经很成熟的被解决了,但是在一般体系结构设计中,依赖分析依然是进行设计的重要基础,这篇文章主要针对指令依赖进行一系列的讨论。
指令依赖从何而来
流水线架构
流水线的方式听起来很简单明了,第一次在计算机组成原理课程上了解流水线体系结构时,我是这么认为的。各个组件负责各个不同的功能,似乎只是对计算机内部进行了协调(显然当初还是太天真了),而这明显是对流水线架构的错误理解。
在大四做毕业设计时,我选择实现一个5级流水线的MIPS处理器,然而在当时我依然对流水线一无所知,现在想起来似乎有些后怕,建议在决定做什么事情之前先把的基本理念搞懂 ->_ ->.
实际上,计算机的流水线的目标为
1. 每一条指令都经过几个不同的,但是确定的执行阶段。
2. 这些阶段在时间上按先后顺序进行。
3. 每个时钟周期都有一条指令进入流水线。
4. 尽可能的在每一个时钟周期都将冯诺依曼架构下的各个部件利用起来。
5. 每一个时钟周期都完成一条指令的执行(提交)。
仔细考虑一下,马上就发现了几个明显的问题。在传统意义的程序上,指令总是一条一条按顺序执行的,一条指令执行完毕,下一条指令才会执行。而在流水线架构下,指令虽然是一条条按顺序执行的,但是前一条指令还没有执行完成,下一条指令就被送入流水线执行了,这听起来是一个非常激进而且大胆的想法。当初我也非常震惊于此,这打破了传统编程定式,即在每一条指令执行时,之前的所有指令已经执行完毕。换言之,在每条指令执行时,程序先前的状态已经被确定。
一个非常直观的体现就是,如果下一条指令的源操作数是上一条指令的目的操作数,也就是下一条指令的运行依赖于上一条指令的结果。那么依赖于其他指令的指令应该如何执行?我想最大的疑问就是,它甚至是可以被执行的吗?讨论这个问题也就是讨论指令依赖。
依赖类型
写后读依赖:
这是典型的数据依赖,即下一条指令的数据依赖于上一条指令的执行结果。
在5级流水线中因为指令都是按顺序进入流水线的,对于每一条指令而言,如果保证指令的源操作数正确,那么毫无疑问,本条指令被正确执行的条件就已经满足了。
但是如果一条条的来斟酌一个常用指令集架构,比如RISC-V x86 等,还有一类比较特殊的指令就是分支指令。
我还记得毕业设计一开始打算流水线x86的部分指令集,但是在绞尽脑汁,费尽心思考虑了将近一周后,我逐渐放弃了这个想法,因为 x86 指令集实在是太过于复杂,指令的源操作数大多都可以是内存里的内容,x86 也同时作为一个 destructive instuction set(毁灭指令集) ,基本上可以说难以下手。后来发现毕业时在室友手里淘来的《计算机组成与设计》,从这时候开始,我才第一次接触到 RISC 指令集。x86 的跳转模式比较简单,大多数在跳转之前使用比较指令 compare 或者 test 等可以设置处理器的内部状态,之后再根据这些状态跳转到相应的位置,BLE BGE ……。
RISC 指令架构,比如MIPS 和 RISC-V ,两者基本上师出同门,则使用了完全不同的设计思想。首先分支指令不依靠处理器内部状态来实现,也就是说一条分支指令已经包含了分支的地址和分支的条件。所以在 MIPS 或者 RISC-V 中,要正确执行一条分支指令,只需知道指令的分支条件是否满足,还有正确的分支地址,这一类依赖通常被称为控制依赖。
控制依赖:
主要指分支指令的源操作数依赖于前一条指令的执行结果。
OK,这看起来似乎和写后读依赖没有什么区别,而我个人也认为如此,至少站在依赖层面,这其中似乎没有任何出入,也许要感谢 MIPS 和 RISC-V 的指令集设计,使这些指令在数据流上面基本保持一致。(MIPS 通常被称为 Microprocessors without pipeline Interlocks,即没有内部互锁的流水线处理器)
这看起来就是全部了,至少在执行指令时,我们已经满足了所有需要满足的条件,实际上在经典5级流水线中也确实如此 (在ysyx中解决了这两个依赖之后确实通过了 CPU-TEST)。如果一条一条的去翻RISC-V 指令集,会发现指令大多是这样的一个结构,一条指令要么是写了寄存器来改变程序状态,要么就是写了内存,或者是发生了分支来改变程序的状态。实际上前两者都可以归结于数据依赖,后者可以归结于控制依赖,依然感叹早在几十年前人们对处理器的研究就已经如此深刻,也许当时人们所碍于的只是工业上的滞后。在各种形式的课介绍从前计算机的发展时,可以明显感受到那是一个朝气蓬勃的年代,各种计算机百花齐放,不仅在性能方面,在各种研究方面也有许许多多的成果。也许给当初的人们今天的工业水平,也许可以达到如今人们难以想象的高度?(只是猜想)
最后,也许在处理器设计中还有其他的架构,也许会产生其他的各种各样的依赖形式。但是只要原则上不生产依赖,实际上就没有依赖(:->),也许花费太多时间来讨论学术上的术语,也许最终不会对我们有任何帮助,也许有一天也会有这么一个实实在在的背景,可以介绍一下其他的依赖方式(也许就在不远的将来?)。OK,我想在这里也可以很好的结束本文了。