1. 一些参数定义
- ELEN:一个向量元素的最大位宽,须为2的指数。
- VLEN:一个单独的向量寄存器的宽度,须为2的指数,最宽为,VLEN ELEN。
- LMUL:组合在一起的向量寄存器的数量。
vector register group:将一组向量寄存器视为一条vector指令的一个操作数。
- SEW:实际一个向量元素的宽度。
- VL:一条指令可以操作的向量元素的数量,VLMAX=LMUL*VLEN/SEW,由于SEWmin=8,LMULmax=8,因此 VLMAX 等于VLEN。
- AVL:一个应用程序要处理的向量元素的个数。
2. Vector扩展编程模型
向量扩展在RISC-V基础标量指令集的基础上增加了32个向量寄存器和7个非特权CSR寄存器。CSR寄存器如下表所示,关键的是vtype和vl寄存器。
mstatus寄存器中添加了一个向量上下文状态域 VS(mstatus[10:9]),类似于其中的浮点上下文状态域 FS。当 VS 关闭时,任何试图执行向量指令、或访问向量CSR寄存器的操作都会引起非法指令异常。
当存在 hypervisor 扩展时,vsstatus寄存器也需要添加一个向量上下文状态域。当V=1时,需要mstatus和vsstatus中的VS均为1,否则执行vector指令或访问vector csr寄存器会引起异常。
2.1 vtype 寄存器(Vector typer register)
- 只读,XLEN-wide,只能通过 vset{i}vl{i} 指令更新;
- 用于解释向量寄存器组的内容,指明了每个向量寄存器中的元素是如何组织的、多个向量寄存器是如何组合的,以及masked-off元素和超出向量长度的元素在一个向量结果中如何被处理。
- 如下图所示,包括五个域,其中[XLEN-2:8]为0。
- vsew[2:0](Vector Selected Elememt Width):用于设置动态向量元素宽度,可设置8~64位宽;
- vlmul[2:0](Vector Length MULtiplier):可以组合多个向量寄存器将其视为一个寄存器,该字段用于表示组合的寄存器的数量。
- ,vlmul是个有符号数。
- 基础地可以设为1,2,4,8(最大值),此外也可以设为分数1/2,1/4,1/8,表示只占用一个向量寄存器的一部分。
- 分组的寄存器必须是连续的几个寄存器,例如LMUL为2时,一个寄存器组由Vn和Vn+1构成。
一个向量操作的源和目的操作数可能具有不同的元素宽度,但元素数量是相同的。
一个向量操作有效的LUML是有目的操作数决定的。
- vta/vma:vector tail agnostic/vector mask agnostic,“agnostic”表示在指令执行过程中不必须保持寄存器中该部分的旧值。
- 这两位分别表示在向量指令执行过程中对于寄存器中的tail元素和非活跃masked-off元素的处理方式(覆盖写为1 or 保留旧值 or 其他方式)。如果置为1则表示不必须保持旧值。
- mask类型指令的目标数据的 tail 元素总是按 tail-agnostic 的方式写入,无论 vta 的值是什么。
agnostic 主要是考虑到寄存器重命名,如果没有这个方式,则每次必须把旧物理寄存器中的所有元素都复制到新物理寄存器中,但 inactive 和 tail 部分可能并不需要。
- vill:对vtype寄存器进行非法配置时置位(比如SEW或LMUL配置为不支持的值),置位时任何依赖于vtype的向量指令的执行都会导致非法指令异常。
向量元素索引划分:在一条向量指令执行过程中,要操作的目标向量元素可以分为三部分,如下图所示(划分的单位是一个向量元素)。
- prestart:元素索引低于 vstart 寄存器起始值 的向量元素,这部分内容不会引起异常也不会更新目标寄存器。
- body:元素索引大于等于 vstart 寄存器起始值,且小于 vl 寄存器中设定的当前向量长度的向量元素。这部分内容又可分为两部分:active 和 inactive部分:
- active 部分指 mask 使能的元素位,会引发异常,并且会更新目标寄存器。
- inactive 指 mask 未使能的元素位,inactive 内容不会引起异常且只在 vtype.vma 为1时更新目标寄存器(向目标寄存器中 inactive 部分写入 1)。
- tail:超出vl指示的向量个数的向量元素部分。
- 不更新目标寄存器,除非 vtype.vta=1,即 tail agnostic 时,此时向目标寄存器中这部分写入 1 或者指令结果(在 mask-producing 指令下,除了mask loads)。
2.2 vl 寄存器(vector length register)
- 这里的 “length” 指的是向量元素个数;
- 只读,XLEN-width,只能被 vset{i}vl{i} 指令、fault-only-first 向量 load指令及其变种更新;
- 用于指示一条指令需要处理的向量元素的个数。
2.3 vstart 寄存器(vector start index CSR)
- 指示一条要执行的向量指令首元素的索引;
- 通常只能被硬件通过一条向量指令的 trap 写,vstart 值表示发生 trap 的元素,指示 trap 处理结束后应该从哪个元素继续执行;
- 所有的向量指令根据 vstart 寄存器所给的元素数开始执行,每次向量指令执行结束后 vstart 被重置为0(包括 vset{i}vl{i} 指令);
3. 向量元素到向量寄存器状态的映射
根据当前的 VLEN、SEW 和 LMUL 配置将不同宽度的向量元素打包到一个向量寄存器中,小端模式。
- LMUL=1
- LMUL<1:只有第一个LMUL*VLEN/SEW元素有用,其余元素位被视为 tail;
- LMUL>1:从该组编号最低的寄存器开始打包。
- 混合宽度操作的映射:当对宽度不同的向量进行操作时,通过修改 vtype 保持 (即vlmax) 一致,即保证每个向量组最多包含的元素个数相同。
混合宽度操作映射举例:
在下例中,同时对宽度为 8b、16b、32b 和 64b 的元素打包,VLEN=128b。
可以看到,通过对每个宽度的元素设置不同的 SEW 和 LMUL 值,打包后每个宽度的元素个数均为8。
4. vector masking
许多向量指令都支持 masking,masked off 的元素不会导致异常,目标向量寄存器对应的元素根据 mask-undisturbed 或 mask-agnostic 机制处理,根据 vtype.vma 确定采用哪种机制。
用于控制 mask 向量执行的 mask 值由向量寄存器 v0 提供。寄存器 v0 的一位控制一个向量元素,元素数量不会超过 v0 宽度,因为 v0 宽度 VLEN 即为 VLMAX的最大值。
指令编码中的 vm 位(vinst[25])指示 mask 是否使能:
vm=0 时表示 mask 使能,即只对 v0.mask[i]==1 的元素进行指令操作。
在汇编指令上体现如下:
5. 配置设置指令(vsetvli/vsetivli/vsetvl)
一个通用的处理大量元素的方法是“stripmining”,即通过循环迭代的方式,每次迭代处理一定数量的元素,直到所有元素处理完毕。RISC-V的向量扩展为这种方法提供了直接的、合适的支持。应用程序指定要处理的元素总数作为 vl 的候选值,硬件基于 vtype 的设置计算,通过一个通用目的寄存器返回硬件每次迭代处理的元素个数(即实际存储在 vl 的值)。
基于上述思想,RISC-V 提供了一组指令用于快速配置 vl 和 vtype 的值以满足应用程序的需求。vset{i}vl{i} 指令基于参数设置 vtype 和 vl CSR寄存器,并将 vl 的新值写回 rd(vl 即为本次迭代要处理的元素数量)。
指令格式如下图所示:
- 新的 vtype 配置位于指令编码中的立即数域(vsetvli 和 vsetivli 指令中)或者 rs2(vsetvl指令)。
- AVL 编码含义以对 vl 的配置影响如下:
- vl 配置约束如下:
- 如果 AVL ≤ VLMAX,vl = AVL;
- 如果 AVL < (2 * VLMAX),ceil(AVL/2)≤ vl ≤ VLMAX;
- 如果 AVL ≥ (2 * VLMAX),vl = VLMAX;
- 在相同输入的 AVL 和 VLMAX 值下,每次给出的 vl 是相同的;
stripmining 过程举例:
6. 向量 load 和 store
masked 的向量 load 不会更新向量寄存器组中的非活跃元素,除非开启 mask-agnostic 机制;masked 的向量 store 则只会更新活跃的memory element。
6.1 指令编码
- rs1:基址寄存器
- rs2:stride 寄存器
- vs2:地址偏移寄存器
- vs3:保存 store data 的寄存器
- vd:load 数据的目的寄存器
- vm:指示 masking 是否使能,为 0 时使能
- width[2:0]:指示存储元素的 size
- mew:memory elememt 宽度
- mop[1:0]:指示访存地址模式
- nf[2:0]:指示每个段的区域数,用于 Vector load/store Segment 指令。
- lumop[4:0]/sumop[4:0]:编码 unit-stride 指令的变种指令
unit-stride 和 constant-strided 方式直接将EEW编码为指令中静态传输的数据的宽度,以减少在混合宽度元素访存过程中 vtype 变化的次数。indexed 方式则。。。。
6.2 vertor load/store 地址模式
支持 unit-stride 、constant-strided 和 indexed 三种地址模式,基址寄存器和步幅来自通用寄存器组,在指令编码中通过rs1和rs2指定。在指令编码中通过 mop[1:0] 指示地址模式。
- unit-stride:访问从基址(rs1)指示的连续的元素,具体可以分为四种(unit-stride load,whole register load,mask load,fault-only-first)。
- constant-strided:从基址元素(rs1)开始,访问固定增量地址的元素,地址偏移量存储在rs2中;
- 支持 0 / 负 步幅。
- indexed:类似于constant-stride,但地址偏移量存储在向量寄存器组中。指令执行时有一组数据向量寄存器组和一组偏移量向量寄存器组。可分为 ordered 和 unordered 模式,对于 unordered 指令,不保证元素的访问顺序。(PS.上述两种地址模式均不必保证访问顺序)
unit-stride 地址模式又通过指令码中的 lumop(load 指令)和 sumop(store 指令)具体指定。
6.3 向量 load/store 宽度编码
向量 load 和 store 指令直接在指令中编码了一个 EEW(字段width[2:0]),相应的 EMUL=(EEW/SEW)*LMUL,如果EMUL超出合法范围(大于8或小于1/8)则指令编码无效。
其中,unit-stride 和 constant-stride 方式直接为数据值使用指令中的 EEW/EMUL 编码。而 indexed 方式则为索引值使用这一编码,数据值则使用 vtype 中的SEW/LMUL 编码。