riscv-v-spec-1.0(矢量指令) 学习理解(1-5 & 18 segment)

1.Introduction

  引用计算机体系结构中的一句话:执行可向量化应用程序最高效方法就是向量处理器。向量化的目的主要是为了去除程序中的loop,以减少不必要的指令开销。并且向量化可以将加载和存储的过程做到流水化,比较好的掩藏存储器延时,下面举个例子说明向量化的好处:
   for (i = 0 ; i <64 ; i++) {
        Y[i] = a * X[i]; 
    }
    
    //将该程序转换成riscv汇编:
    li a0,0 //用于loop中的条件
    li a1,64 //用于loop中的条件比较值
    li t0,COEF_A  //COEF_A为常量a
    li s0,BASE_ADDR1 //BASE_ADDR1为LW基地址
    li s1,BASE_ADDR2 //BASE_ADDR2为SW基地址
    loop:
    lw a2,0(s0)
    mul a3,a2,t0
    sw a3,0(s1)
    addi s0,s0,4
    addi s1,s1,4 
    addi a0,a0,1
    bne a0,a1,loop
    
    //将该程序转换成riscv-v汇编:
    //VLEN=512
    li t0,COEF_A
    li s0,BASE_ADDR1 //BASE_ADDR1为LW基地址
    li s1,BASE_ADDR2 //BASE_ADDR2为SW基地址
    vsetvli t1,t2,e32,m4
    vle32 v1,(s0)
    vmul.vx v2,v1,t0
    vsw32 v2,(s1)
    
    由上面的例子可以看出:
    1.纯标量汇编指令比向量化的riscv汇编指令,多出了(5 + 7*64 - 7)= 446条需要执行的指令。
    2.对于纯标量汇编指令,每次执行mul前后,都需要执行lw,sw。这个加载和存储的过程是不能流水起来的,而向量化的vl,vs指令,是可以做到流水化的,将整个取向量的时间分散在单个元素上,可以很好的掩藏存储器延迟。

2.Implementation-defined Constant Parameters

向量扩展spec定义了2个parameters(以下参数均需要满足:位宽为2的幂):
1.ELEN:单个向量元素的最大位宽,ELEN>=8。(我的理解是:该参数应该根据硬件中ALU能处理的最大位宽决定)。//2030 4bit
2.VLEN:通用向量寄存器的位宽。
riscv中其他通用架构寄存器位宽命名:
XLEN:INT类型通用寄存器位宽。
FLEN:FLOAT类型通用寄存器位宽。

基向量扩展spec要求VLEN>=128。这是权衡后的一个比较折中的值,尽管设置较大的VLEN可以减少短向量指令的条带挖掘代码,但是设置较大的VLEN意味着较大的硬件register的开销。并且riscv-v-spec提供了LMUL寄存器,该寄存器的值可以对向量寄存器进行分组。比如通用向量寄存器一共32个,设置LMUL为8,就可以将全部的通用寄存器分成4组,每组包含了8个连续的向量寄存器,这种分组的方式增加了向量寄存器组中元素的个数,同样减少了条带挖掘(strip mining)的代码。
这里先解释一下条带挖掘的含义:条带挖掘技术是为了解决实际应用程序的向量长度长于硬件单次处理支持的最大长度的问题,条带挖掘代码是指生成一段代码,使得每个向量运算处理元素的长度都是小于或等于最大向量长度的。下面以计算机体系结构中的一段C程序来进一步解释条带挖掘代码:
//假设一共要处理n个元素,vlmax为循环内每次最大处理的长度
st = 0 ;//元素起始索引
vl = (n % vlmax) ; //得到不规则部分的元素数量
for (j =0 ; j <= (n/vlmax) ; j++) {
    for (i = st ; i <(st + vl) ; i++) {
        Y[i] = a * X[i] + Y[i] ; 
    }
    st = st +vl ; //计算下一次开始的元素索引
    vl = vlmax ; //将每次处理的元素长度恢复成最大的向量长度
}

3.Vector Extension Programmer’s Model

   向量扩展向基标量RISC-V ISA中增加了32个向量寄存器(v0-v31)以及7个无特权的CSRS(控制和状态寄存器),分别是vstart、vxsat、vxrm、vcsr、vl、vtype、vlenb。向量寄存器的位宽为固定的VLEN宽度。
    riscv 架构规定了一些只在机器模式下支持的寄存器,机器模式是riscv中硬件线程执行时的最高权限模式。机器模式对内存,I/O和一些对于启动和配置系统来说必要的底层功能有着完全的使用权。通常用户写的程序都在用户模式执行,用户态具有最低级别的权限。当用户程序需要使用一些底层硬件设备或者执行出现了异常又或者外围设备对正在执行的处理器发起了中断请求,那么cpu就会由用户态切换至内核态,也就是切换到机器模式下,将cpu的控制权,转交给内核程序。
riscv把程序执行时出现的异常,或者外部中断统称为陷阱(陷阱其实很好理解,因为不管是异常还是中断,cpu都会由用户态陷入内核态,这个陷入内核的过程就可以理解成踩入了陷阱里,要经过一些其他的操作,才能爬出陷阱,恢复正常行走)。当遇到陷阱时,首先要将当前的pc保存下来,方便之后进行恢复。然后清空异常指令之前的流水线。接下来根据对应的陷阱类型,切换到对应的程序入口,开始执行内核程序( 内核程序也是由一条条指令组成的,同样需要在流水线上执行)。等内核程序执行完成后,在重新把cpu的控制权转交给用户程序,从之前保存的pc指针开始重新取指,执行。
在重新回到riscv规定了一些只支持机器模式相关的寄存器的话题。mstatus寄存器是机器模式下的状态寄存器,其中mstatus[10:9]是向量上下文状态域(一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,所以进程的切换实质上就是被中止运行进程与待运行进程上下文的切换)。
mstatus.vs域同mstatus.fs类似。当mstatus.vs域被写成0时,试图执行向量指令或者访问向量寄存器均会引发非法指令异常。当mstatus.vs域被设置为初始状态或者干净的状态,只要执行了相关的指令改变了vpu(vector processor unit)状态,该域就会被改写成dirty的状态。
misa寄存器用于指示当前处理器的架构特性,该寄存器高两位用于表示当前处理器所支持的架构位数;该寄存器低26位用于指示当前处理器所支持的不同模块化指令集。riscv架构文档规定misa寄存器可读可写,从而允许处理器可以动态的配置某些特性。misa.v表示vector扩展指令集域,即使misa.v域被清0,mstatus.vs域也存在。这样设计可以简化mstatus.vs的处理。

3.1.vstart

vstart为可读可写寄存器,该寄存器规定了一条向量指令中第一个被执行的元素的索引。
vstart寄存器通常在向量指令执行的过程中产生了陷阱被写入。该寄存器记录了进入陷阱时向量指令操作元素的索引,以便跳出陷阱之后能够继续执行剩下的元素。 所有向量指令都根据vstart中指定的元素索引开始执行。执行的过程保证该索引之前的目的寄存器的元素不被干扰,在执行的末尾,将vstart寄存器的值复位到0。当向量指令产生非法指令异常时,vstart寄存器将不会被改写。所以vstart寄存器被改写,只能是程序执行时出现了可恢复的同步异常,或者外部产生中断的情况。
思考假如不能通过vstart存储异常时的元素索引,那么在执行向量指令过程中发生的可恢复异常,必须要等到这条向量指令执行完,才能进入异常处理程序。这就要求向量指令必须是原子的,增加了控制复杂度,并且对于一些长延时的指令,比如load,将会导致响应中断的时间特别的长。
若vstart索引值大于vl的值,说明vstart指向的元素索引已经超过了当前所有元素的范围,该指令不会执行,并且同时会把vstart寄存器复位到0。
vstart可写的bit位,根据VLMAX确定,在vl部分已经描述过。

3.2.vxsat

vxsat为可读可写寄存器,该寄存器不仅有独立的寄存器地址,并且在vcsr寄存器中也有对应的域。该寄存器有效表示输出结果做了饱和截位以适应目的寄存器格式。比如当运算发生正溢出时,保留结果为能取到的最大正值;当运算发生负溢出时,保留结果为负数最小值。

3.3.vxrm[1:0]

vxrm[1:0]为可读可写寄存器,该寄存器不仅有独立的寄存器地址,并且在vcsr寄存器中也有对应的域。该寄存器控制定点舍入模式,一共四种模式,分别是round-to-nearest-up(rnu)、round-to-nearest-even(rne)、round-down(rdn)、round-to-odd(rod)。(问题:可否解释一下定点数在内存中的存放格式)
vxrm[1:0]寄存器通过单条csrwi指令写入值。
假如源操作数是v,有低d bit数据要被截掉,那么做完rounding-mode之后的最终结果应该是(v>>d)+r,r就是根据不同的rounding mode得到的增量值。
rnu:向距离近的方向进行舍入,当距离与两边都相等时,向上舍入。
rne:向距离近的方向进行舍入,当距离与两边都相等时,向偶数方向舍入。
rdn:向下舍入,直接取移位后的值。
rod:舍入到奇数值方向。
其中,v[d-1]表示权重位。当v[d-1]=0,表示距离舍的方向更近;当v[d-1]=1且v[d-2:0]=0时,距离舍入两个方向距离均相等;当v[d-1]=1,且v[d-2:0] != 0时,表示距离入的方向更近。

3.4.vcsr

vcsr[2:0]寄存器为可读可写寄存器,该寄存器由vxrm[1:0],以及vxsat组成。
思考为什么vxsat和vxrm既有单独的寄存器地址,也在vcsr中占有对应的域:
 riscv spec中fflags和frm寄存器也采用的这样的设计。这类状态寄存器,希望方便快速的读写,比如有时候希望一条指令能把这两个寄存器的值同时读出来。又或者写这两个寄存器,如果要同时写,就得先做移位拼接在写,那有的时候只想改变其中一个寄存器的值,也做移位在拼接然后写的操作就比较慢,因此单独写对应的地址就显得尤为方便快速。考虑到适应不同的需求,这类状态寄存器就设计为既有各自单独的寄存器存储空间,又集中在一个寄存集中划分各自的域。

3.5.vl

vl寄存器为只读寄存器,该寄存器存储着一个无符号整数,用来规定一条向量指令需要更新多少个元素。
该寄存器只能被vsetvli、vsetvl指令以及fault-only-first矢量读取指令的变体进行更新。
目的寄存器中元素索引大于等于vl的元素,将不会被修改。如果vstart大于等于vl,那么目的寄存器的任何元素都不会被修改。
vl的位宽由最小元素组成的最大向量长度决定。最小的元素位宽至少是8bit,最大的分组设置为LMUL等于8,那么VLMAX=LMUL * (VLEN / SEW) = VLEN。也就是说vl的位宽,直接由VLEN的大小决定。

3.6.vtype

vtype为只读寄存器,位宽同通用整型架构寄存器位宽(XLEN)。该寄存器提供了默认值用于解析向量寄存器中的内容,并且只能通过vsetvl{i}指令进行更新(这样做的目的是使得维护vtype寄存器的状态简单化)。
vtype寄存器用于解析向量寄存器文件中的内容、决定单个向量寄存器中元素的组成以及决定多个向量寄存器是如分组的。
vtype寄存器有五个域,分别是vill,vma,vta,vsew[2:0],vlmul[2:0]。
3.6.1.vill
当先前的vsetvl{i}指令试图写入一个不被支持的值到vtype寄存器,vtype的vill域置1,同时vtype的其他域均清0。后面其他依赖于vtype寄存器执行的指令,将会引发非法指令异常。
3.6.2.vma &vta
这两个域修改目标尾部元素和目标被屏蔽元素在向量指令执行过程中的行为。在向量指令执行过程中,尾部元素和被屏蔽元素,不会接收新的运算结果。
vma:vector mask agnostic,用于控制目的寄存器被屏蔽元素的行为。0:undisturbed 1:agnostic
vta:vector tail agnostic,用于控制目的寄存器尾部元素的行为。0:undisturbed 1:agnostic
采用undisturbed策略,那么目的寄存器相应元素集合,将保持原来的值。
采用agnostic策略,那么目的寄存器相应元素集合,既可以保持原值,也可以全部写1(spec上说全部写1而不是全部写0,是为了避免软件开发者依赖于这部分值,虽然我现在也没明白为啥写1就可以避免了。。。)。
那么采用agnostic策略的好处是什么呢?举个例子:对于超标量的流水线,会采用寄存器重命名的方式,来避免WAW以及WAR这两类hazard。那程序的逻辑寄存器会映射到物理寄存器,映射后的对应关系会更新到重命名映射表中。那对于undisturbed策略,需要目的寄存器相应的元素保持原来的值。那么在用新的物理寄存器重命名时,还需要根据重命名映射表,查到原有的映射关系,再把这部分元素的值先读出来,写到重命名后的对应元素位置。这种方式对于压根儿不关心尾部元素集合或者被屏蔽元素集合的值的后续操作,就既降低了性能,又增加了不必要的功耗。对于普通的in-order流水线,可以采用这种undisturbed的策略。对于超标量的流水线,使用agnostic策略就显得更加明智。
3.6.3.vsew[2:0]
vsew[2:0]:vector selected element width,用于动态的设置元素位宽。
一个向量寄存器被分成(VLEN/SEW)个元素。SEW=8*(2^vsew)。
eg:假设VLEN=128,那么当vsew等于0时(SEW=8),表示一个向量寄存器含有16(128/8)个元素。
SEW的值应该跟随LMUL的变化而变化(如果现在你不理解这句话,先带着问题往后看)。
3.6.4.vlmul[2:0](有符号数)
LMUL:for vector register grouping,LMUL=2^(vlmul[2:0])。
多个向量寄存器可以组在一起,这样单条向量指令可以操作多个向量寄存器(这里多个向量寄存器组成一组,成为单条向量指令的一组操作数)。之前讲到过条带挖掘,其实就是一组多维的嵌套loop,切割任务,将任务划分为规则的部分和不规则的部分。对长向量的处理,如果硬件支持的单次处理的向量长度比较短,就需要loop多次,但是循环本身就增加了指令个数,还增加了一些划分的运算,因此对处理器性能的提高不是很理想。这里就解释了通过LMUL配置,增加组内寄存器个数,也就是增加了一条向量指令可操作的元素个数,避免了条带挖掘代码的出现,或者减少了条带挖掘的次数。
if(LMUL>=1):表示组合在一起的向量寄存器个数(LMUL=1、2、4、8)。
if(LMUL 是一个小数):用于减少一个向量寄存器中被使用的位宽。(LMUL=1/2、1/4、1/8)。设置LMUL为小数通常是用于混合位宽的向量指令操作。举个例子:比如源操作数向量用到的LMUL设置为1,计算后的元素位宽拓宽了2倍,保持元素个数不变的情况下,LMUL也需要增大两倍,也就是需要两个向量寄存器才能够存储下所有的向量运算结果。那假设源操作数本身只需要占据一个向量寄存器的一半,但是LMUL不能为小数,也就是占据小于等于1个向量寄存器宽度的都使用LMUL=1,那么这种情况再遇到位宽扩展指令,还是以刚才的例子就需要两个向量寄存器。但是很有可能扩展了的运算结果刚好只需要占据一个向量寄存器。这样设计就造成了向量寄存器的浪费。
spec要求必须支持LMUL>=SEW(lmul1min)/SEW(lmul1max)的配置。其中SEW(lmul1min)表示在LMUL为1的条件下必须支持的最小元素位宽;SEW(lmul1max)表示在LMUL为1的条件下必须支持的最大元素位宽。因为SEW(lmul1max)是可以设置为与VLEN相等的,假如LMUL<SEW(lmul1min)/SEW(lmul1max),那么将不能保证一个寄存器剩余部分的位宽至少包含一个完整的元素。
VLMAX = LMUL * (VLEN/SEW),表示单条向量指令能够操作的最大元素数量。
掩码寄存器只包含在一个单独的向量寄存器中,与LMUL设置值无关。

3.7.vlenb

该寄存器为只读寄存器,表示以字节为单位的向量寄存器长度,vlenb=VLEN/8。vlenb是一个设计时常量,增加这个寄存器是为了减少一些需要直接用到vlenb的程序的额外计算指令的开销。

4.Mapping of Vector Elements to Vector Register State

这里spec上面举例描述的非常详细了,总结一下:
1.元素以小端模式进行存放,也就是元素的数据低位放在向量寄存器的低位(低字节放在低地址)。
2.当LMUL=1时,元素按照顺序,从寄存器最低有效位开始摆放,元素摆放填满整个向量寄存器。
3.当LMUL<1时,元素按照顺序,从寄存器最低有效位开始摆放,只填LMUL * (VLEN / SEW)个元素,剩余的高位未被填充的位置,按照tail部分处理。
4.当LMUL>1时,元素依然按照顺序,从寄存器最低有效位开始摆放,当索引最小的向量寄存器被填满,就按照顺序开始填下一个向量寄存器。
5.当LMUL>1时。允许SEW>VLEN,也就是一个元素可以占多个向量寄存器,元素填充方式依然和前述一样。

针对混合位宽操作解释一下:混合位宽操作比如扩位宽操作(源操作数元素位宽小于目的操作数元素位宽),或者缩位宽操作(源操作数元素位宽大于目的操作数元素位宽)。但是无论位宽怎么变,源操作数元素的个数和目的操作数元素的个数肯定是相等的。那么这就需要动态配置LMUL随着元素位宽(SEW)的变化而变化也就是说需要保持SEW/LMUL为常数。LMUL设置的值大,可以减少向量指令取指和分发的开销。
向量掩码只占据一个向量寄存器。它的存储与SEW和LMUL的配置无关。每个向量元素具有对应的1bit向量掩码。

5.Vector Instruction Formats

向量扩展指令在已有的三类指令LOAD-FP、STORE-FP、AMO编码的基础上,重新定义了一些域 。并且新增了一类指令OP-V。
向量扩展指令可以有标量或者矢量源操作数,也能产生标量或者矢量的结果。

5.1.标量操作数

标量操作数可以是立即数、整数通用寄存器、浮点通用寄存器,或者从矢量寄存器元素0获得。
标量运算结果可以写到整数通用寄存器、浮点通用寄存器,也可以写到矢量寄存器元素0的位置。
任意矢量寄存器都可以用于存储标量,与LMUL的设置无关。

5.2.矢量操作数

矢量操作数通过有效元素位宽(EEW)和有效分组模式(EMUL)来决定一个矢量寄存器组中元素的大小和位置。
默认情况下,大多数指令的大多数操作数EEW=SEW,EMUL=LMUL。
有一些指令的源操作数和目的操作数有相同数量的元素,但是却不是同样的位宽。所以EEW可能与SEW不同,EMUL也可能和LMUL不同。但是EEW/EMUL=SEW/LMUL。如果源操作向量寄存器与目的向量寄存器有重叠的部分,那么必须满足以下三个约束之一:
1.目的向量寄存器的EEW与源向量寄存器的EEW相等(这句话我的理解是一一对应,目的寄存器与源寄存器完全相同)。
2.目的向量寄存器EEW比源向量寄存器的EEW小,但是交叠部分只在源寄存器组最低索引部分。
eg:vnsrl.wi v0,v0,3  #SEW = (2*SEW) >> SEW,当前LMUL=1
该指令为narrow逻辑右移指令,源操作数EEW=2*SEW,目的操作数EEW=SEW。当前源寄存器组为v0,v1组成,目的寄存器为v0。当前目的寄存器和源寄存器的交叠部分就发生在源寄存器的低索引部分,这种交叠不会有问题,但是倘若把目的寄存器由v0换成了v1,那么当源寄存器v0在执行时,就覆盖了v1中的部分元素,也就是运算结果覆盖了还没有执行的元素的内容,这就会导致运算结果出错。
3.目的向量寄存器EEW比源向量寄存器的EEW大,首先源向量寄存器LMUL必须大于等于1,并且交叠部分只在目的寄存器组最高索引部分。
eg:vext.vf4 v0,v6 #Zero-extend SEW/4 source to SEW destination,当前LMUL=8
该指令为0扩展指令,该指令为了使数据匹配对应的格式位宽。源操作数EEW=SEW/4,目的操作数EEW=SEW。
src                    6 7
dst 0 1 2 3  4 5 6 7 
src中的v6寄存器将会扩展到目的寄存器的v0-3处。src中v7的第一个1/4的部分扩展到v4,第二个1/4的部分扩展到v5,第三个1/4的部分扩展到v6,此时v6里面的源操作数早已被用掉,因此该覆盖不会有问题,v7中最后1/4就覆盖整个v7。假如该指令变成vext.vf4 v4,v6,那么会出现源寄存器中的元素还没被执行,就被覆盖的错误情况。 
以上三条约束,均是为了保证还没有被操作的源不要被运算结果所覆盖。示意图如下:

在这里插入图片描述

5.3.矢量掩码

很多向量指令都支持掩码执行。被遮罩的元素不会产生异常。目的向量寄存器被遮罩的元素根据vtype.vma进行操作。遮罩的需求来源于循环中的if语句,本来单纯的循环,并且循环内元素没有dependency,是可以靠编译器进行向量化的。但是有一类循环内插入了条件语句,要当条件满足时,才能执行后续的操做,这种情况,如果指令集架构上没支持,那么编译器就不能成功将代码向量化。
eg:
for (i = 0 ; i <64 ; i++) {
    if(X[i] != 0) {
       Y[i] = a * X[i]; 
       }
 }
将上诉代码转换为带有遮罩的向量指令:
vmul.vx v2,v1,t0,v0.t
在基向量扩展中,用来控制掩码向量指令执行的掩码值通过向量寄存器v0提供。一个v0寄存器就足够,是因为首先掩码寄存器中的元素都是1bit,其次是一条向量指令中能操作的最大元素个数由最大分组和最小元素位宽决定,前面已经分析过最大能取到的元素个数实际上就是VLEN大小。因此刚好一个向量寄存器就可以表示所有向量指令的掩码。riscv-v-spec中定义了一些掩码操做的指令,这些指令,源寄存器和目的寄存器中的元素都是掩码,并且这些指令不会受LMUL和SEW的影响。
这里掩码在指令里,也可以看作操作数。只是受限于指令编码的位宽限制,没有多余的域再去规定掩码操作数的寄存器索引了,所以当前的spec上将v0作为掩码寄存器,任何掩码指令得到的掩码结果,在被需要掩码的指令使用之前,需要将掩码值写到v0寄存器中。
5.3.1.掩码编码
用v0[i]和指令编码中的vm域联合表示指令掩码。
指令编码的vm域为1:表示无掩码的向量操作。
指令编码的vm域为0:如果v0.mask[i]=1,表示对应的向量寄存器元素要被执行。若v0.mask[i]=0,那么对应的元素不被操做,目的寄存器对应的元素按照vma的配置执行相应的策略。

5.4.元素定义

向量指令执行期间的元素索引,可以分成四个没有交集的子集。
prestart elememts:元素索引小于vstart寄存器中的初始值,对应的元素集合。该元素集合不产生异常,也不会更新目的向量寄存器;
active elements:表示在向量指令执行过程中,在向量长度设置范围内,且掩码有效的元素集合。该元素集合可以产生异常并且也可以更新目的向量寄存器;
inactive elements:表示在向量指令执行过程中,在向量长度设置范围内,但是掩码无效的严肃集合。该元素集合不产生异常,vma=0的设置下,不更新目的向量寄存器;vma=1的设置下,被掩码掩蔽的元素将被写1。
tail elemememts:表示在向量执行过程中,超过了向量设置的长度。该元素集合不产生异常,vta=0的设置下,不更新目的向量寄存器;vta=1的设置下,tail元素将被写1。当分组因子LMUL<1时,超过LMAX的元素也纳入tail元素集合中。
除上述定义的子集外。inactive和active的集合还可以统称body element。该集合在prestart elements之后,在tail elements之前。

18.Exception Handling

异常和中断从广义上来看,都叫做异常。RISC-V 架构规定,在处理器的程序执行过程中, 一旦遇到异常发生,则终止当前的程序流,处理器被强行跳转到一个新的 PC 地址。该过程在 RISC-V 的架构中定义为“陷阱(trap)”, 字面含义为“跳入陷阱”,更加准确 的意译为“进入异常”(摘自手把手教你设计CPU)。在一条向量指令执行的过程中遇到了陷阱,需要将当前指令pc保存在*epc中,并且需要记录当前遇到异常时的元素索引到vstart,以便退出异常服务程序后能恢复原先执行的程序。
如果发生异常时,只记录指令的pc指针,没有用vstart记录发生异常时的元素索引,那么需要保证该指令的执行是原子的,这加大了设计控制难度。并且向量指令设计为原子的,在有些long latency的指令执行过程中,会导致中断的响应非常缓慢。(思考假如指令不是原子的,那么出现异常直接被打断,又没有保存被打断时的索引,那么下一次从这一条指令重新开始执行会有什么问题)
中断通常是由外设(中断源)产生的,而异常通常是由程序执行过程中遇到的异常。因此中断是一种外因,通常不能精确定位到某条指令引起,因为外设发起中断的时间是偶然的,程序执行过程中遇到中断,任何指令都可能碰到,这些倒霉的指令只是一个背锅侠,因此称这种中断为异步异常。异常的产生通常是内因,比如某条指令解码的时候出错,或者执行的时候进行了除0的操做,这些异常都可以被精确的定位到某一条指令。并且同样的程序执行n遍,都是能复现的。因此称这种异常为同步异常。
对于异步异常,还可以被细分为精确异步异常和非精确异步异常。精确和不精确的区分主要在于进入异常的程序能否精确区分到某条指令的边界。比如某条指令之前的指令都执行完了,而该条指令之后的指令都没有执行,外部的中断就是一种精确异步异常。而非精确异步异常是指当前处理器的状态可能是一个很模糊的状态,没法明确的根据一条指令划界。对于非精确异步异常举个例子,比如写存指令,访问存储器需要一定的时间,但是为了流水线高效率的执行,通常写存指令发出去,没等到写的响应有没有正确响应,后续的指令就在继续执行。那么有可能出现等访问完成,发现出现了异常,此时已经过去了很多条指令了,难以精准确的定位到某一条指令。
异常可能发生在处理器流水线的各个阶段,为了保证处理器按照指令先后顺序处理异常,需要将异常的处理放在commit阶段。

18.1.Precise vector traps

精确的向量陷阱具有如下要求:
1.所有比陷阱指令旧的指令都已经完成了commit过程。
2.所有比陷阱指令新的指令都应该被flush掉,不允许新指令更改处理器的状态。
3.陷阱指令中,所有小于vstart元素索引的元素操做都完成了commit。
4.陷阱指令中,所有大于等于vstart元素索引的元素都不会被执行。但是如果操做重新开始该指令,可以将处理器恢复到正确的状态,那么该条约束可以放松。

举个例子,对于具有幂等性的存储区域进行操做,就可以允许发生trap对应的索引之后的元素改变存储器的状态,而不具有幂等性的存储区域就不能允许大于等于vsart索引的元素更新存储器状态。幂等性是指多次执行一个操做与执行一次该操做得到的结果相同,该操做就可以说是幂等的。
跳出异常服务程序时,需要从vstart记录的元素索引开始。这是因为比如有些指令源寄存器与目的寄存器有交叠,那么可能vstart之前的源寄存器内容已经被目的寄存器覆盖了,那么重新执行这部分元素,将会得到错物的结果。

18.2.Imprecise vector traps

非精确向量陷阱,spec中写的是比*epc新的指令可能已经commit,比*epc老的指令有可能还在执行。对这个地方我是有疑问的,因为即使是超标量流水线,也会经历一个inorder-outoforder-inorder的过程,也就是真正commit的时候,指令肯定是会按照程序指令顺序,依次改变处理器状态,怎么会出现比*epc老的指令还在执行。鉴于这部分理解的有分叉,我就不多描述了,希望能和大家一起讨论清楚。

最后

由于最近visio没法儿用,所以有些流程本来可以画图的部分只能文字描述,大家先凑合看。
  • 26
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值