RISC-V处理器核设计优化与扩展指令集实现(二)

2.1 处理器微架构与流水线概述

根据每周期处理的指令的数目可以把处理器分为标量处理器和超标量处理器。超标量处理器每周期可执行多条指令。根据处理器执行方式的不同,处理器微架构可分为按序执行和乱序执行两种模式。按序执行模式要求按照指令的取出顺序执行,而乱序执行模式可根据指令准备情况灵活安排执行顺序,使先准备好的指令优先执行。只有在流水线前级指令多于一条时,才有可能采用乱序执行模式。根据处理器处理指令的能力,可将处理器微架构分为单发射按序执行、多发射顺序执行和多发射乱序执行。在处理器微架构中,常见的功能部件包括Cache、整数计算单元以及浮点计算单元等。这些部件经历了广泛的应用和优化。现代处理器设计更注重整体架构以及对关键功能部件的优化和扩展。

2.1.1 流水线技术

无论哪一种处理器微架构都离不开流水线技术。流水线技术的核心在于将原有的复杂组合逻辑分解成由寄存器锁存的连续流水级,使得多个流水级可以同时工作而无需等待前面的逻辑执行完毕。在CPU设计中,经典的流水线架构是基于RISC指令集的五级流水线:取指、译码、执行、访存和写回。图2-1中,流水级从左到右执行,每级流水的时钟周期在其上标出。

RISC-V处理器核设计优化与扩展指令集实现(二)_第1张图片

图2-1 划分五级流水线

流水级的划分不应当仅仅关注功能的划分,还要关注当前流水级的时间开销,因为流水线的时钟时延取决于延时最大的那个流水级,即:

因此,设计较为均衡的流水线时序也是架构设计的重点。合理划分流水级可以大大降低流水级周期,进而提高处理器的主频。例如,五级流水线中最大的流水级的时钟周期是5ns,其余流水级时钟周期是2ns,那么这个处理器的最高工作频率为200MHz,如果将五级流水线进一步划分,将时钟周期最大的一级拆成了两个2.5ns流水级,那么处理器的最高工作频率便是250MHz。

流水线划分越精细,主频越高,但并不是流水级越多越好。这是因为在流水线运行的过程中,会执行分支指令。分支指令控制程序流。在一个没有采用分支预测的处理器中,需要等到执行级计算出分支跳转结果以后才能确定分支指令的目标地址或者分支指令是否跳转。在一个采用静态分支预测的处理器中,如果分支预测失误,那么流水级中分支指令执行之前的指令都是无效的,会在流水线中插入气泡,一旦分支指令预测错误,那么流水级越深,引入的分支预测失败开销越大。采用动态分支预测技术会大大减小分时预测失误率。所以,虽然流水级变深可以进一步提高处理器的主频,但却会引入更大的分支预测失败代价,并不是流水级越深越好。

2.1.2 冒险的处理

处理器流水线运行时会产生冒险。冒险的来源一般来说有三种途径,分别是结构冒险、数据冒险和控制冒险,这三种冒险普遍存在于各类处理器架构中。每种类型的冒险都有对应的解决方式,且标量和超标量、单发射和多发射以及顺序执行和乱序执行的冒险处理方式会有所不同。本节接下来会介绍三种不同的冒险在不同的处理器架构中的解决方法。

(1) 结构冒险

结构冒险是由于硬件资源不足导致的冒险。例如在一个多发射的处理器中,每个周期发射多条指令,这几个指令可能是不同类型的也可能同种类型的。假如执行级执行单元不足,就会引起同种类型指令的执行等待或者推迟执行其它类型的指令。又比如,无论在单发射还是多发射、顺序执行还是乱序执行的处理器中,寄存器堆的访问是多端口并行的,但是也可能两条指令要写同一个端口导致的写端口冲突。这些情况是可以通过增加硬件资源、访问通道进一步提高硬件资源的并行性来解决。

(2) 数据冒险

数据冒险指的是在流水线中,某条指令的执行与其邻近指令的数据之间存在相关性,为保证指令的正确执行而引起流水线阻塞的一种冒险。具体来讲相关性可分为三种:

1) 先写后读(Read After Write),即RAW相关。

2) 先读后写(Write After Read),即WAR相关。

3) 先写后写(Write After Write),即WAW相关。

无论是单发射按序执行处理器,还是多发射乱序执行处理器,RAW相关性都是存在的,因此这种指令之间的相关性也叫做真相关,例如:

指令1:add x5, x0, x1

指令2:add x18, x5, x4

第一条指令的目的寄存器是x5,第二条指令有一个源寄存器是x5,这样第二条指令的执行必须等到第一条指令将目的寄存器写回才能继续执行,也就是第二条指令停在了访问寄存器x5这一步。在单发射按序执行五级流水线CPU中,假如第二条由于与第一条指令存在RAW相关性而暂时停止执行,那么该条指令及其后续指令都不能沿着流水线执行了,引起了流水线的停顿。当第一条指令写回后,此时已经过了两个时钟周期,流水线也停顿了两个时钟周期。解决RAW相关性的方法是使用前馈机制,即在第一条指令完成执行阶段而下一条指令刚好需要访问寄存器进入执行阶段时,将第一条指令的执行结果提前反馈给第二条指令,这样,第二条指令无需因为流水线停顿而等待两个时钟周期才能继续执行。当然,这要求前面的指令是可以一个时钟周期执行完的。

WAR和WAW指令相关性在单发射按序执行按序退休的流水线中是不存在的,这是由于按序执行且按序退休的指令保证了在WAR相关性出现时不会发生先写后读,且在WAW相关性中目标寄存器/地址获得的都是最新的内容。但是在某些单发射按序执行的流水线架构中,Load和Store指令对存储器的访问是乱序进行的,需要处理这两种相关。WAR相关性要求Store指令不能发生在Load指令之前;WAW相关性表明可以只将存在WAW相关性的后一条Store指令的数据写入存储器,减少访问存储器的频率。

在多发射乱序执行的处理器中就要特别关注指令的WAR相关和WAW相关性。这是因为在多发射乱序执行的处理器微架构中,取指级每周期取得两条及以上的指令,如果两条指令之间存在WAR相关,则需考虑解决如何让先序指令读的是架构寄存器(RISC-V指令集中规定的32个寄存器)中的旧值而不是后序指令新写入的值;若这两条指令之间存在WAW相关,则需考虑解决乱序执行以后如何保证架构寄存器中获得的是后一条指令写的值。指令的WAR和WAW这两种相关存在于多发射乱序执行机制中,其本质是由于在多发射乱序执行微架构中,原有架构寄存器数目不足以支持乱序执行,因此现代处理器设计技术引入了寄存器重命名技术。

现代高性能处理器都是超标量乱序执行的处理器。应用场景的多样化使得应用程序更加负责,编译后的指令流也更加庞大,虽然编译器可以对编译出来的指令顺序重排以消除一定数量的指令的相关性,但归根到底指令的相关性是不可能消除的,因此,必须在处理器微架构中从硬件层面实现重命名技术。

RISC-V处理器核设计优化与扩展指令集实现(二)_第2张图片

图2-2 寄存器重命名

以三发射乱序执行处理器为例,在图2-2左侧为原始程序,其中第一条指令和第二条指令之间存在关于寄存器X4的WAR相关,第一条和第三条指令之间存在关于寄存器X6的WAW相关。在重命名技术中,会从空闲物理寄存器表中为每一条指令的目的寄存器分配一个新的物理寄存器,维护架构寄存器和其对应的物理寄存器之间的映射关系表,假设某时刻的重命名表如图2-2中间所示。在图2-2右侧,为三条指令分别分配了物理寄存器P9、P10和P11。

重命名后,存在WAR相关性的第一条指令会读其源寄存器X4所对应的老的物理寄存器P3,第二条指令会将执行结果写回到为其分配的物理寄存器P10,所以这两条指令可以并行执行;存在WAW相关性的第一条指令会将执行结果写回为其分配的物理寄存器P9,第三条指令会将执行结果写回到为其分配的物理寄存器P11,这两条指令也可以并行执行。处理器每周期都会给存在目的寄存器的指令分配新的物理寄存器,图2-2中就是为原始程序中存在目的寄存器的指令分配新物理寄存器后的结果。重命名完成后,指令之间已经不存在WAR和WAW两种假相关,实现指令的并行和乱序执行。

在处理器微架构中实现重命名技术的关键在于实现重命名表和空闲物理寄存器的管理。

(3) 控制冒险

处理器流水线的控制冒是由分支指令导致的,因此也叫做分支冒险。分支指令是控制程序流执行方向的,例如当执行到程序段某个位置要转而去执行别的位置的指令,而这一条指定不在当前指令的相邻位置。在RISC-V指令集架构中,根据分支指令执行的特点可以将分支指令分为一下几种:

1) 有条件直接跳转指令,如beq,bne;

2) 无条件直接跳转指令,如jmp;

3) 无条件间接跳转指令,如jalr指令;

4) 函数调用和返回指令,call指令调用函数,ret指令实现函数返回。

类型4)是一种特殊的无条件间接跳转指令,RISC-V体系下,不存在带条件的间接跳转指令。下面依次说明这几种分支指令对处理器中指令执行的影响。

对1)型指令,假设流水线在完成该指令的取指后,还要到执行单元计算出条件表达式的值以确定是否需要跳转,如果不需要跳转,那么流水线可以正常运行;如果分支指令的计算结果为跳转,那么分支指令就会重定向PC,从该跳转地址重新开始取指,因此在分支条件计算出来之前进行取指和译码的指令都是无效的。

对于2)型指令,由于不需要分支条件计算,只需要将指令中的立即数做扩展就可以得到跳转的目标地址,因此如果在流水线中可以根据jmp指令的立即数提前计算得到跳转目标地址的话,就可以实现该类型指令快速跳转,而无需等到执行级计算出来以后再进行PC的重定向。

对于3)型指令,也不需要计算分支跳转的条件,但是由于间接跳转指令需要访问寄存器,因此无法在流水线前端就计算出来跳转的目标地址,要在寄存器访问级完成,或得到寄存器值以后,再加立即数获得最终的跳转地址。

对于4)型指令,本质上也是直接跳转指令,只不过其跳转目标地址指向一个函数。例如在函数的调用和返回时,会分别调用call指令和ret指令使程序进入函数和退出函数。函数调用和返回指令在使用嵌套函数时十分常用,因为在逐层调用子函数得出结果以后还要逐层返回到主函数,如果不做处理,每次函数的跳转都要等到执行级计算出结果以后才能正确跳转,造成极大的性能损失。

因此,处理器流水线设计,往往不等到计算出分支条件或者分支跳转的最终目标地址以后才决定分支指令是跳转还是不跳转。为了提高执行效率,往往在取指完成后,就根据指令的跳转信息和分支历史信息预测指令跳转与否,这就是分支预测技术。

对于1)型分支指令采用BHT(Branch History Table,分支历史表)和BTB(Branch Target Buffer,分支目标缓冲)来进行分支预测。对于2)和3)类型的分支指令,由于一定是跳转的,通过分支目标缓冲部件可以提前预测。对于4)类型的分支指令,可以通过RAS(Return Address Stack,返回地址堆栈)来加速函数的调用与返回。

结构冒险、指令/数据冒险和控制冒险是处理器流水线中的三大冒险。无论哪种类型的处理器架构,设计中最重要的就是解决流水线中冒险问题。本文节接下来会重点研究RISC-V体系下的单发射按序执行和多发射乱序执行这两种流水线架构。其中,前者以开源玄铁E906为研究对象,适用于低功耗的嵌入式处理器场景;后者以开源玄铁C910为研究对象,适用于高性能处理器领域。

2.2 玄铁E906处理器流水线技术介绍

2.2.1 E906流水线架构概述

玄铁E906是一个款基于RISC-V指令集的高性能嵌入式处理器,是阿里平头哥嵌入式处理器产品线的最高性能的通用处理器,以较低的面积和功耗成本取得相对较高的性能指标[42]。

RISC-V处理器核设计优化与扩展指令集实现(二)_第3张图片

图2-3 玄铁E906 五级流水示意图

玄铁E906采用的是五级流水线架构:取指、译码、执行、访存和写回,如图2-3所示。下面分别介绍这五级流水线:

(1) 取指阶段:访问指令缓存或者指令总线,获取指令,同时访问分支预测模块进行分支预测。

(2) 译码阶段:访问动态分支预测器和返回地址堆栈,发起分支预测跳转,同时进行指令译码,读寄存器堆,处理数据相关性和数据前馈;

(3) 执行阶段:完成单周期整型计算指令和多周期乘除法指令的执行、存储/加载指令地址计算和跳转指令的处理。其中整型的计算包括普通的算术指令和逻辑指令;

(4) 内存访问阶段:利用执行阶段产生的Load/Store指令的目标地址访问数据缓存或者数据总线;

(5) 写回阶段:将指令写回寄存器堆。

2.2.2 E906流水线架构分析

虽然玄铁E906采用五级流水线简单结构,但是其Coremark可以达到3.0,与ARM STM32系列相当。本节深入探讨E906核心的流水线架构,基于2.1节对于流水线和冒险的分析,研究其以五级流水线达到较高性能的设计方法。

首先E906取指级采用Cache而不是简单的紧耦合存储,Cache使用随机替换算法,未命中时会从指令总线中读取数据或者对Cache进行填充。采用指令Cache的结构可以加速指令的获取。取指模块优先在Cache中读取指令,从而避免访问下级存储引入的延迟,这可以理解为对取指级资源的一种结构改进,避免由于取指级访问下级延迟过高引起的结构冒险。

玄铁E906在取指级处理分支指令引入的控制冒险。对于条件跳转指令,引入了BHT和BTB的分支预测机制;对于函数调用和返回指令,引入了最多支持四层函数调用返回的RAS。当分支预测命中时,分支预测部件会直接重定向PC,这样就不用等到执行单元执行完毕再做分支跳转方向的判断和分支跳转目标地址的计算,减少了分支冒险开销。

由于在32位RISC-V架构中,基本指令为32bit,压缩指令为16bit,一个字定义为32bit。玄铁E906拓展了取指带宽,在取指级每周期取48bit的指令,16bit指令被标记为半字指令,并且每16bit指令都会根据RISC-V指令function字段的高两位是否为“11”来判断这是不是32bit指令的一部分。

RISC-V处理器核设计优化与扩展指令集实现(二)_第4张图片

图2-4 玄铁E906 取指过程图

如图2-4所示,取指级每周期从指令Cache取出的三个半字指令分别用h0~h2表示。假设h0~h2全部是有效指令,图中二叉树向右分叉表示当前节点的是一个32bit指令,向左分叉表示当前节点是一个16bit指令。从h0开始判断,如果h0是一个16bit指令,那么再判断h1;如果h1也是一个16bit指令,再判断h2,如果h2也是一个16bit的指令的话,就会产生三条指令,这三条指令会被存储在指令缓冲中。以此类推,会产生h0是一条16bit指令同时h1和h2组成一个32bit的指令、h0和h1组成一个32bit指令同时h2是一个16bit指令的有效情况。当h0和h1组成了一个32bit的指令时,如果h2是一个32bit指令,那么需要等到下个周期再获得指令的下半部分,h2会被暂存起来与下一周期的半字节指令一起参与判断。凡是有效的指令都会被存放在指令缓冲中,指令缓存每周期输出一条指令给译码级进行译码,缓解了取指模块和后级流水线处理速度不匹配问题。

在玄铁E906译码级,对取指级指令缓存输出的指令进行译码后,根据指令要访问的寄存器号,访问寄存器堆获取源操作数,同时如果当前执行的指令和前序指令之间存在RAW相关性,就把前序指令的执行结果旁路给当前指令,缓解了单周期执行指令的RAW冒险问题。

E906执行阶段依据指令的类型设置不同的执行单元避免结构冒险。包括基本算术逻辑单元、多周期的整数乘法和除法单元、Load/Store指令的访存地址计算单元和分支指令计算单元,如图2-5所示。译码单元对指令进行译码后就得出指令的类型并且获得了指令的源操作数,根据指令不同类型将指令分别送入执行级对应的功能单元执行。

RISC-V处理器核设计优化与扩展指令集实现(二)_第5张图片

图2-5 玄铁E906 执行级功能部件

E906还实现了数据Cache,加速数据访存操作,在Load/Store地址计算单元实现访存地址计算。在写回阶段,将没有发生例外的指令的目的寄存器写回到寄存器堆的对应位置。

2.3 玄铁C910处理器流水线技术介绍

2.3.1 玄铁C910简介

C910为平头哥开源的一款RISC-V高性能处理器。该处理器为64位双核架构,支持RV64GC指令集,主要面向性能要求比较高的场景,如自动驾驶、边缘服务器、移动智能终端以及5G基站等。

图2-6是开源双核C910架构示意图。每个处理器核心为三发射乱序执行结构,处理器核外围继承了RISC-V兼容的中断和调试框架以及电源管理部件。两个核心之间支持多核一致性处理,核内私有一级指令和数据Cache,多核之间通过一致性管理单元共享L2 Cache,外部总线使用AXI总线协议,多核一致性协议兼容AMBA ACE总线协议。

RISC-V处理器核设计优化与扩展指令集实现(二)_第6张图片

图2-6 玄铁C910 架构图

图2-7显示了更为详细C910流水线分级信息,给出在玄铁C910流水线架构中的主要数据流方向。以下对其流水线关键模块简要介绍:

IFU(Instruction Fetch Unit),取指单元,分为三级流水线。完成取指、预译码以及分支预测。每周期最多取出三条指令,每条指令可以是16bit的压缩指令或者32bit指令。设置了指令缓存以处理IFU与IDU之间的速度不均衡问题。

IDU(Instruction Decode Unit),译码单元,分为三级流水线。完成指令的译码、重命名、派遣、发射以及寄存器访问。IDU可以将指令译码为微操作指令,最多对三条指令译码。最多对四条微操作指令进行重命名,之后派遣到指令发射队以及后续流水线。共有8个发射队列,发射队列将准备好操作数的最老的指令优先发射出去,访问物理寄存器获取操作数后,再送入对应执行单元。

EXU(Execution Unit),执行单元。其中IU(Integer Unit)负责整数相关算术和逻辑指令、分支指令的执行,FPU(Float Point Unit)负责浮点算术运算的执行,LSU(Load Store Unit)负责访存指令的执行。

RTU(Retire Unit),退休单元。维护了物理寄存器、ROB(Re-Order Buffer)以及备份重命名表。备份重命名表用于在出现分支预测以及异常时恢复位于IDU单元的重命名表。没有发生异常的指令从ROB退休,每周期最多退休三条指令。

RISC-V处理器核设计优化与扩展指令集实现(二)_第7张图片

图2-7 玄铁C910 架构图

2.3.2 C910取指和分支预测

C910的取指以及分支预测功能是在IFU单元中实现的。该单元总共分为三级流水线:IF(Instruction Fetch)、IP(Instruction Pack)和IB(Instruction Buffer),分别完成取指、指令预译码以及指令缓冲,如图2-8所示。

RISC-V处理器核设计优化与扩展指令集实现(二)_第8张图片

图2-8 玄铁C910 IFU流水线架构图

(1) IF流水级

第一级流水IF中,根据PC访问指令缓存,缓存的访问方式为VIPT(Virtual Index Physical Tag)。如果缓存命中就会取出128bit的指令。为了减少指令缓存未命中的概率,C910采用了指令预取技术。同时在第一级流水线实现了条件跳转指令和无条件跳转指令的分支预测,使用PC地址部分位访问L0 BTB(L0 Branch Target Buffer),如果命中则直接零延时跳转,若未命中则要访问BHT获得指令的跳转情况,并访问BTB获取分支目标地址。L0 BTB是为了加速分支指令的跳转,其存储的分支目标地址是BTB的子集,当L0 BTB未命中时,就要更新,当检测到分支预测错误时,两个BTB都要更新。

(2) IP流水级

第二级流水IP中,主要对指令进行预译码,通过预译码就能知道指令的类型。例如,通过预译码能够区分出间接跳转指令和函数调用返回指令。预译码完成后将指令信息打包发送给后续流水级。

(3) IB流水级

第三级流水中,使用Ind-BTB(Indirect-BTB)模块实现对间接跳转指令的分支预测。这是一个专门为间接跳转指令设计的分支预测模块,由于RISC-V的间接跳转指令是无条件的,总是执行跳转,对于间接跳转指令总是预测命中然后访问Ind-BTB获取跳转目标地址。此外,C910使用RAS对call指令和ret指令的跳转地址进行记录,函数调用时入栈,函数返回时出栈,并且支持最多12层的函数嵌套。

在第二级流水线指令预译码完成后,如果没有特殊情况,就会被存储在第三级流水中的Instruction Buffer中。为了加速循环指令块的执行,C910在第三级流水线加入Loop Buffer来加速循环指令的运行,在检测到后向跳转指令时会将这个循环块存入到Loop Buffer中,而无需到取指级重新取指。这样带来的好处是避免因在IP级检测到后向跳转指令造成的流水线气泡。

C910还在IB级处理了取指对齐问题。由于RISC-V指令集中同时存在16bit和32bit的指令,因此要实现每周期取出三条指令,就要实现指令的对齐。如图2-9所示,C910每周期取出128bit指令,这些指令被分为了8个半字,即8个16bit大小的指令块h0~h7。

RISC-V处理器核设计优化与扩展指令集实现(二)_第9张图片

图2-9 每周期取出的指令块

在IB级的Instruction Buffer和Loop Buffer中,使用一种类似于二叉树遍历的方法来查找要发射的三条指令。如图2-10所示,从h0开始查找,二叉树向左分支表示这是一个16bit的指令,向右分支表示这是一个32bit的指令,例如h0和h1是一个32bit指令,h2是一个16bit的指令,这时候还要判断h3的情况来选出最后一条指令。从h0~h7中选出3条指令的情况一共有8种。使用这种方法完成了取指对齐。

RISC-V处理器核设计优化与扩展指令集实现(二)_第10张图片

图2-10 指令对齐算法

总之,IFU单元向后级流水线输送指令的途径有三种:第一种来自于Loop Buffer,该缓存存储预译码后可以短循环加速的指令;第一种来自于Instruction Buffer,该缓存存储预译码后的常规指令;三是来自于后级流水的回填或旁路。

2.3.3 C910 译码、重命名和发射

C910的译码、重命名以及发射功能是在IDU单元中实现的。该单元共分为4级流水线:ID(Instruction Decode)、IR(Instruction Rename)、IS(Instruction Issue)和RF(Register File),分别完成指令的译码、重命名、发射和寄存器访问,如图2-11所示。

RISC-V处理器核设计优化与扩展指令集实现(二)_第11张图片

图2-11 玄铁C910 IDU流水线架构图

(1) ID流水级

玄铁C910是三发射的处理器。如前所述,IFU输送三条指令给后续的流水线,ID级完成相应指令的译码工作。指令译码被分解为微操作,即指令包含的最小操作。分解微操作时分为三种类型:第一种是normal类型,这种类型的指令只能被分解为一个微操作;第二种是short类型,这种类型的指令可以分解为两个微操作;第三种是long类型的,可以分解出四个及以上微操作。

从图2-11可以看出,C910 ID级实现了三组译码器,对三条指令并行译码。第一组译码器实现第一条指令的译码,包含normal、short以及long类型,还有特殊类型未在图中标出;第二组和第三组译码器分别岁第二条和第三条指令进行译码。由于ID级后续的流水线最多处理四个微操作,因此只在第一组译码器设置long类型的译码,因为如果第一条指令是long类型的话,后续的指令在当前周期就无法译码了。

(2) IR流水级

IR级完成寄存器的重命名表维护和指令的预派遣。预派遣即将重名命之后的指令信息准备,以传递给后级流水线及ROB。重命名是乱序执行的基础,重命名之后,指令之间的WAR和WAW相关性消除,从而可以实现指令的乱序执行;派遣则记录了指令的顺序以及相关信息,为指令的有序退休提供条件。IR级的输入为来自ID级的四条微操作指令。重命名的实现依赖于重命名表的维护和空闲物理寄存器的管理,下面以整数型寄存器重命名为例分别介绍C910中这两种机制的实现方法,浮点寄存器重名机制类似,在此不赘述。

1) C910的重命名表

在C910中,采用了两个重名表,一个位于IDU单元的IR级流水线,另一个位于RTU单元。在IR级流水线中,当为四条指令的目的寄存器分配新的物理寄存器时,会将当前指令目的寄存器映射的旧的物理寄存器号和当前新分配的物理寄存器号一并派遣到ROB记录。由于分支预测机制的存在,当前指令处于推测执行状态,即有可能出现分支预测失败冲刷流水线的情况,分支预测失败时,旧的物理寄存器号用于恢复推测执行之前架构寄存器和物理寄存器的映射关系;新的物理寄存器号用于在指令提交后更新位于RTU中的重命名表,该表只记录提交后指令的目的架构寄存器对应的物理寄存器,因此不存在推测执行状态。

在IR级重命名表中,记录了架构寄存器X0~X31对应的物理寄存器,有12个读端口(每条指令有两个源寄存器和一个目的寄存器,最多4条指令)和4个写端口(用于恢复来自RTU重命名表中四条微操作指令对应的旧物理寄存器号)。物理寄存器共有96个,有标志位表明该物理寄存器是被某条指令写回,图2-12是一种假定的对应关系。

RISC-V处理器核设计优化与扩展指令集实现(二)_第12张图片

图2-12 玄铁C910的重命名表

假设在译码后,ID级输送了如下四条指令给IR级:

指令1:sub x1, x2, x4;

指令2:sub x3, x1, x4;

指令3:sub x4, x1, x4;

指令4:sub x1, x1, x4;

在重命名机制中,每周期为存在目的寄存器的指令分配一个空闲的物理寄存器,假设当前周期给四条指令的目的寄存器分配的物理寄存器号分别为:6、7、8和9,则该程序段存在2.1.2节中所述的三种指令相关性冒险:

相关性1:指令1需要写寄存器X1,指令2需要读寄存器X1,因此指令1和指令2之间存在RAW相关性,指令2需要等待指令1执行并写回其结果。

相关性2:指令1读寄存器X4,指令3写寄存器X4,因此指令1和指令3之间存在WAR相关性。

相关性3:指令1和指令4都要写寄存器X1,因此指令1和指令4之间存在WAW相关性。

针对相关性1,C910在IR级设置相关性检测电路进行快速前馈,即如果两条指令之间存在RAW相关的话,在这种情况下就需要引入旁路操作。每条指令都需要与其前序指令的源寄存器号进行判断,以决定是否需要进行快速前馈,例如第三条指令的两个源寄存器需要与第一条和第二条指令的目的寄存器比较RAW相关性。如图2-13所示,使用多位异或比较两个寄存器号,并通过编码判断旁路哪条指令的目的寄存器,如果第三条指令的某个源寄存器和前两条指令的目的寄存器均相同,需要将第二条指令的目的寄存器旁路给第三条指令的源寄存器。旁路后,后序指令就会在前序指令写回其目的寄存器对应的物理寄存器后访问该物理寄存器。由于当前周期给指令1的目的寄存器X1分配的物理寄存器号为6,指令2访问源寄存器X1时,会直接去物理寄存器6访问。

 

RISC-V处理器核设计优化与扩展指令集实现(二)_第13张图片

图2-13 Inst2的RAW先惯性判断

针对相关性2,由于指令1读寄存器X4使用的物理寄存器为从重命名表中读出的旧的物理就的物理寄存器4,但指令3使用的是当前新分配的空闲物理寄存器8,因此这两条指令的执行不会冲突。

针对相关性3,指令1和指令4同时写寄存器X1,但是这两条指令的目的寄存器X1分别映射到了物理寄存器6和9,因此这两条指令的执行不会冲突。

重命名表更新时只记录架构寄存器所映射的最新的物理寄存器,因此相关性3出现时,指令1的目的寄存器X1新分配的物理寄存器不会被更新到当前周期的重命名表中。因此,完成当前周期四条指令的重命名后,重命名表为如图2-14所示。

RISC-V处理器核设计优化与扩展指令集实现(二)_第14张图片

图2-14 更新后的重命名表

除了引入了相关性处理的数据旁路,玄铁C910还在重命名表中引入了零延时mov指令的通路,需要满足三个条件:

条件1:mov指令的源寄存器和目的寄存器不同。

条件2:mov指令的目的寄存器不是X0。

条件3:mov指令的目的寄存器和后序指令的源寄存器存在RAW相关性。

如果条件1不满足,为mov指令的目的寄存器分配新的物理寄存器会更新其源寄存器映射的物理寄存器,导致后序指令的源寄存器无法读到正确的值;条件2指明在mov指令的目的寄存器为X0时,是没有必要执行该指令的,因为X0寄存器的值始终为零;条件3指明当后序指令需要读mov指令的目的寄存器,即存在RAW相关性时,将从重命名表中读出的mov指令源寄存器映射的物理寄存器号旁路给后续指令的源寄存器,实现了零延时跳转。将mov指令的源操作数传递后后续指令后,mov指令无需执行,消除了mov指令。

2) C910的空闲物理寄存器管理

空闲物理寄存器管理机制实现每周期分配四个空闲物理寄存器,使IR级流水线实现重命名。空闲物理寄存器的管理使用队列实现。玄铁C910一共有96个整数物理寄存器,因此这个队列的地址宽度为7bit,队列中存放的内容是空闲的物理寄存器号而不是物理寄存器的实现。队列用于在IR级重命名时候给存在目的寄存器的指令分配新的物理寄存器。将空闲物理寄存器号写入空闲物理寄存器管理队列有三种情况:

情况1:指令正常提交时,释放的物理寄存器号。

情况2:指令出现分支预测错误时,为被流水线冲刷的指令分配的物理寄存器号。

情况3:指令出现异常或中断时,为其后指令分配的物理寄存器号。

处理器在复位时,空闲物理寄存器队列处于写满状态,即全部物理寄存器都处于空闲状态。由于队列深度为96,在执行写操作时,要判断加上当前写数目后写指针是否超过95,如果超过,则从0位置开始写。在计算空闲物理寄存器数目时,有两种情况:第一种是写指针超过最大写边界95,假设某时刻读写指针如图2-15 (a)所示,此时空闲物理寄存器数目为写指针加96减读指针,即93;第二种情况是写指针没有超过边界95,假设某时刻读写指针如图2-15 (b)所示,此时空闲物理寄存器数目为写指针减读指针,即92。

RISC-V处理器核设计优化与扩展指令集实现(二)_第15张图片

图2-15 空闲物理寄存器数目计算

重命名表和空闲物理寄存器管理队列是实现重命名技术的核心。空闲物理寄存器管理队列每周期为IR流水级中存在目的寄存器的指令分配新的物理寄存器,如果空闲物理寄存器数目不足,则会触发IR级重命名的停顿,直到空闲物理寄存器数目足够,分配新的物理寄存器给IR级进行寄存器重命名。

(3) IS流水级

IS级实现指令的派遣,并把在IR级完成寄存器重命名的指令发送到不同的发射队列。

1) 指令派遣

派遣工作处理过程中需要处理“type_stall”停顿,其产生的原因是玄铁C910的每个指令队列只有两个创建端口,也就是同一个发射队列一个周期只能写进两个待发射的微操作指令,如果要写入的同一队列的微操作数目多于两个,就会产生这种停顿。产生“type_stall”停顿时,玄铁C910使用控制信号控制IS级下周期使用的微操作来源,控制信号的产生有三种情况,每种情况下IS级获得的微操作如表2-1所示(使用“uop”表示微操作)。

表2-1 IS下周期获得的微操作

Tab. 2-1 Micro-operations acquired by IS stage in next cycle

下周期IS级的微操作 type_stall且uop3有效 type_stall且uop3无效 未发生type_stall
uop0 uop2 uop2 IR-uop0
uop1 uop3 IR-uop1 IR-uop1
uop2 IR-uop0 IR-uop2 IR-uop2
uop3 IR-uop1 IR-uop3 IR-uop3

表中未标注“IR-”的均为当前周期IS级剩余的微操作。可以看到在派遣的时候如果发生了type_stall,会优先把上周期未能成功派遣的指令派遣。在C910的发射队列中,AIQ类型的指令支持一个周期发射四个微操作,因此若当前周期发射的微操作均为AIQ类型则不会引起停顿。同时,由于在RISC-V指令集中大多数的指令都是不需要被分解的normal类型的指令,此时IR级仅分解出3条微操作,IS级也只需要处理3个微操作的发射,这样如果发生了type_stall,IS当前周期剩余的一条指令可以在下一个周期和来自IR级的三个指令合并在一起处理,且不会超过IS级的最大负载,这种情况下也不会发生停顿。

派遣的时候还会创建数据给RTU中的ROB用于指令的退休、精确异常和备份恢复。创建的信息包括指令的ID、PC偏移、对应的物理寄存器号以及完成次数信息等。完成次数是需要特别关注的,这是由C910在流水线派遣时引入的指令折叠机制引起的。指令折叠是将连续的几个ALU类型的指令或者连续的几个浮点算术指令合并到一个ROB的表项之中,这样ROB只需要使用一个表项来记录连续几条相同类型的数据,能够减少存储空间,其管理方法是根据完成次数信息来确定该表项是否要退休。

2) 指令发射

IS级会完成对指令的发射操作。一共有8个发射队列(或叫保留站),如表2-2所示。每个发射队列有两个写端口和一个读端口,队列的深度为8~12,即可以存储8~12条指令。

经过重名后,指令的WAW和WAR相关性已经消除,因此只要指令的源操作数已经准备好,并且是当前指令队列中最老的指令,那么该指令就可以发射到后续RF流水级进行操作数访问,之后将指令送入对应端口的执行单元,实现乱序执行。为了识别当前指令队列中最老的指令,每个发射队列都会有年龄向量表示每个指令与该队列其它指令的年龄关系。以ALU指令队列AIQ为例,其深度为8,两个写端口具备优先级,因此会先从端口0写入指令1再从端口2写入指令2,该指令队列里会有一个8bit的表项有效向量表示当前AIQ队列中哪些表项已经被写入有效的指令。

表2-2 8个发射端口分配

Tab. 2-2 Allocation of 8 issue pipe

编号 端口 指令信息 发射端口
0 AIQ0 整数ALU、DIV和SPECIAL指令 Pipe0
1 AIQ1 整数ALU和MUL指令 Pipe1
2 BIQ 跳转指令 Pipe2
3 LSIQ 访存指令的地址部分和FENCE指令 Pipe3
4 LSIQ 访存指令的地址部分和FENCE指令 Pipe4
5 SDIQ STORE指令的数据部分 Pipe5
6 VIQ0 浮点ALU、MUL和DIV指令 Pipe6
7 VIQ1 浮点ALU和浮点MUL指令 Pipe6

当新指令被发送到指令队列AIQ时,会根据8bit的表项有效向量来建立当前指令与其它指令的年龄关系,组成一个年龄矩阵,在AIQ中矩阵大小为8行8列,矩阵的每一行表示发射队列表项位置,每一列表示当前表项中的指令与其它指令的年龄关系。

因此指令1从端口0写入AIQ时,其在年龄矩阵中的向量就是指令队列当前的8bit表项有效向量,指令2从端口1写入AIQ时,其在年龄矩阵中的向量为指令队列当前的8bit有效向量与仅在指令1写入AIQ队列表项位置为1的8bit向量的“或”。假设指令1写入时,指令队列当前的8bit表项有效向量为“00100011”,指令1写入表项0,指令2写入表项1,那么此时年龄矩阵的状态如图2-16所示。

RISC-V处理器核设计优化与扩展指令集实现(二)_第16张图片

图2-16 更新后的年龄矩阵

由于此时,8bit表项有效向量的最后两位为1,表明在指令1和2写入之前,表项6和7已经有有效的指令写入,那么AIQ发射的最老的指令应该根据表项6和7中指令的年龄向量选择一条最老的指令发射,进入RF级访问源操作数,之后送入对应执行单元,实现指令的乱序执行。

(4) RF流水级

IDU的RF流水级负责指令源操作数的数据访问和前馈操作。在IS级的指令队列中会维护的年龄向量,表示指令的新旧,控制优先发送的指令。指令被发射后会访问其源寄存器操作数。此时,如果指令之间不存在相关性就在RF访问物理寄存器获取源操作数;另一个途径就是快速前馈,如前所述在IR级会检测指令之间的RAW相关,这个前馈的操作访问寄存器就是在RF流水级完成的。

2.3.4 C910执行、写回和退休

后级流水线包括指令的执行、写回和退休,流水示意图如图2-17所示。

RISC-V处理器核设计优化与扩展指令集实现(二)_第17张图片

图2-17 玄铁C910 后端流水线架构图

(1) EX流水级

执行级是分为8个执行端口的,分别对应着表2-2中的8个指令的队列,当发射队列将年龄最老的指令发射出来并且指令的操作数都已经访问或者旁路好以后,就可以发射到每个指令队列所对应的执行单元。如图2-17所示,执行端口0~2连接到LSU执行单元的,执行单元支持两条Load/Store指令的发射;执行端口3~5连接到ALU/BJU执行单元,执行整型计算;执行端口6~7连接到浮点执行单元(开源C910预留了向量的执行接口,但是并未真正实现,因此这里只代表浮点运算单元)。由于每个执行单元执行的指令类型以及负责度不同,执行级的流水级也不是固定的,简单的ALU类型的算术逻辑指令在一个周期就可以写回结果,但是浮点运算和多周期的乘除法单元的指令计算则最多4个时钟周期才能写回。

(2) RT流水级

在指令在派遣阶段会把指令信息依次、按序写入ROB表项,由于在C910派遣的时候最多存在4条微操作指令,因此ROB包含四个写端口,同时,C910每周期最多退休3条指令,因此ROB有3个读端口。指令执行完后写回,并置位ROB中指令完成标志位,等待退休。如前所述,ROB中有标志位指示表项中指令的完成情况或者完成次数,对于单条指令的ROB表项,只要该条指令完成了就可以准备退休,对于指令折叠的情况则需要将折叠的全部指令执行完毕以后才可以退休ROB表项。

在C910中,ROB表项的深度为64,表项的管理采用循环队列机制:地址宽度为7bit,最高位为辅助位,用于产生ROB的写满与读空条件:

写满条件:辅助位不同,读写指针的低6位相同。

读空条件:辅助位相同,读写指针低6位相同。

ROB写表项时,会先判断是否满足折叠条件,如果满足折叠条件,会将满足折叠条件的几条指令写入同一表项,表项内部不记录指令的具体内容,只记录第一条指令的信息,并用PC的偏移表明其它指令与第一条指令的相对关系。在没有发生异常的情况下,ROB检查读指针rd_ptr、rd_ptr+1和rd_ptr+2的表项内容,如果满足退休条件则每周期最多退休3条指令,ROB的读指针加3,以检查下一周期是否有满足退休条件的指令。如果指令发生分支预测错误、中断或异常,在ROB标记该指令并向IF级发出PC重定向请求。由于创建ROB表项信息时,是按照取出指令的顺序来创建的,在指令退休时也是依次检查指令是否满足退休条件来决定是否退休该指令,所以指令在ROB中指令的退休是按序的。

指令在C910中经历了顺序取指、乱序执行和按序退休的过程。

2.4 本章小结

本章介绍了单发射顺序执行处理器和超标量处理器中广泛使用的流水线技术,以及在流水线中必须处理的结构、数据、以及控制冒险。其中,结构冒险可通过增加硬件资源来解决。数据冒险则涉及到不同体系结构下相关性处理的方法,其中RAW相关性是真相关,在各种处理器体系结构中都需要进行处理;而WAW和WAR相关性是假相关,在超标量乱序执行的处理器体系结构中需用寄存器重命名技术处理。最后,为减轻分支或控制冒险的影响,现代处理器一般采用分支预测技术。本章分别研究了两个开源玄铁处理器核——单发射顺序执行的玄铁E906和多发射乱序执行的玄铁C910,并结合2.1节的流水线设计和冒险处理技术,分析了这两个具体实例中的设计方法。

你可能感兴趣的:(risc-v)