支持RISC-V向量指令的汇编器设计与实现*

2021-01-06 08:16朱小龙孙海燕
计算机工程与科学 2020年12期
关键词:标量测试用例二进制

邓 平,朱小龙,孙海燕,任 怡

(国防科技大学计算机学院,湖南 长沙 410073)

1 引言

随着智能物联网AIOT(Artificial Internet Of Things)应用需求的日益增长、中美国际贸易战的影响以及企业本身对成本和技术创新的追求,RISC-V架构以其开放、简洁、模块化等特点得到了业界广泛关注。国内有多家企业宣布采用RISC-V架构设计相关芯片,包括华为、芯来科技、国防科技大学、中国科学院等。在芯片体系结构相关技术中,向量指令是提高芯片运算性能的重要手段之一。RISC-V联盟于2020年5月发布RISC-V向量扩展0.9版本[1]标准草案,给出了RISC-V体系结构的向量指令规范,为RISC-V体系结构[2]在更高性能的应用领域中的推广提供了支持。

编译器、汇编器等芯片编译工具链对任何一个硬件架构而言,都是构筑软件生态必不可少的组成部分,编译环境的好坏直接决定了该架构的用户数量和应用范围。目前,RISC-V的配套开发环境已经很成熟,但在支持向量指令这方面还不够完善。虽然RISC-V已包含向量指令规范,但在目前的开源工具链代码中并没有实现相关的向量指令,这在很大程度上影响了RISC-V的应用推广。本文的主要工作是基于RISC-V 0.9版本向量规范和GNU Binutils开源平台,讨论实现支持RISC-V向量指令的汇编器关键技术,设计并实现RISC-V向量汇编器。

2 技术背景

2.1 向量指令集的特点

RISC-V向量指令集是基于基础指令集的拓展,主要用于支持向量运算操作,提高硬件执行效率。在RISC-V 0.9版本中,向量指令遵循如表1所示的编码标准。向量指令[1]主要在标量浮点加载(LOAD-FP)、标量浮点存储(STORE-FP)和原子性存储器操作(AMO)指令的基础上进行拓展定义,另外新增了OP-V类的操作指令。向量的加载和存储编码利用了LOAD-FP和STORE-FP的12位立即数编码字段(表1第20至31位)[3]的一部分,同时第25位保留了标准向量掩码位(vm)作为条件域,大多数向量指令可以在条件域的掩码控制下无条件或有条件地执行。

向量指令的源操作数一般为标量或向量,运算结果可存于对应标量或向量寄存器中。除立即数外,向量指令中的标量操作数还可以是向量寄存器的0号元素,以及通用寄存器或浮点寄存器中存储的数据。按目前规范约定任何向量寄存器都可以用于保存标量。向量操作数或向量结果可能会占用一个或多个向量寄存器(向量寄存器组),向量寄存器组始终由组中编号最小的向量寄存器来指定。

2.2 GNU Binutils介绍

GNU[4]是一系列编译工具[5]的集合,统称为工具链GNU,其在开发应用程序和操作系统中具有重要作用。GNU包括GNU make、GCC(GNU Compiler Collection)、GDB(GNU Debugger)、GNU Binutils。对RISC-V的向量指令集扩展需要借助GNU Binutils工具来完成,同时在Binultils工具中进行一些与RISC-V平台对应的修改。采用GNU Binutils主要是因为包含Binutils的GNU支持绝大多数当前主流的主机操作系统,其GNU的变种基本都能适用于类Unix操作系统、Linux和Windows操作系统。由于GNU提供了一套完备的工具,故无须过多考虑与其他工具的整合问题。即使需要考虑,由于GNU是开放的,它支持多种开放的目标文件格式和调试格式,存在多种形式的开源C标准库可以使用,可以和编译器链接器完美整合。

Table 1 RISC-V vector instruction formats表1 RISC-V向量指令格式

GNU Binutils是一组二进制程序处理工具[6],包含最主要的汇编器as、ld、objdump、readelf等。在Linux环境下,二进制程序主要是指*.o文件和*.elf执行文件[7]。as是Binutils的汇编器工具,负责将汇编程序转化为目标机器指令;ld是链接器,可以将多个目标文件链接成一个可执行文件;objdump用于反汇编,将二进制文件转换为汇编代码;readelf用于显示elf格式可执行文件的信息。GNU Binutils包含的汇编器as和链接器ld等工具都使用二进制文件描述符库BFD(the Binary File Description library)[8]来操作目标文件和库,如图1所示。

Figure 1 Binary toolsets图1 二进制工具集

3 RISC-V向量汇编器关键技术

向量指令的描述和向量寄存器的描述是实现RISC-V向量汇编器的关键。

3.1 RISC-V向量指令的描述

汇编指令与二进制指令之间是一一对应的关系,即直译的过程。实现汇编器的重点在于定义好汇编指令集、二进制指令集,以及确定好两者之间的映射转换关系[9]。首要解决的问题是汇编指令对应的二进制指令的编码问题,RISC-V架构定义的标准指令集仅使用了少量的二进制指令编码空间,更多的编码空间被预留给用户作为扩展指令使用,本文将利用这些预留的编码空间对向量指令集[10]进行编码。

表2以指令vadc.vvm为例,描述了向量汇编指令及其对应的二进制编码,其中vd表示目的寄存器,rs1和vs2表示操作数寄存器,各占用5 bit。将rs1、vs2和vd所在域全部置成0,其他位域保持为默认编码后得到的二进制码就是这条汇编指令的校验值MATCH。当指令的rs1、vs2和vd对应位域值为0,其他位值全为1时,得到的二进制码为该指令的MASK掩码值。指令的MATCH和MASK用于一致性检查,以校验指令的正确性。表2中最后2行分别为vadc.vvm的MATCH和MASK。

3.2 向量寄存器的描述

在向量指令扩展[2]中,需将32个通用向量寄存器v0~v31添加到基本RISC-V的ISA中,每个通用向量寄存器都有一个固定状态位v-len。如果基本标量ISA不包含浮点,则还需添加浮点状态和控制寄存器fcsr,用来保存向量定点饱和标志寄存器vxsat和向量定点舍入模式寄存器vxrm的镜像。向量定点舍入模式寄存器vxrm具有2位读写舍入模式字段,用于指示定点指令的舍入模式,如四舍五入模式、取整模式等,这一特性反映在fcsr高位字段中。向量定点饱和标志寄存器vxsat保留单个读写位,该位用于指示定点指令是否需要为了适应目标格式而必须使输出值饱和,且vxsat位也反映在fcsr的高位字段中。将vxrm、vxsat等字段打包到fcsr,可以加速对上下文的保存/恢复。

对于向量类型控制状态寄存器vtype,指定其只能被vsetvl{i}[1]指令进行更新,其中i为vsetl{i}编码中高位字段(20~30位)表示的立即数,该寄存器可以提供默认类型用于解释向量寄存器文件内容,还可以对多个向量寄存器进行分组。向量长度寄存器vl只能通过vsetvl{i}和vsetvl指令进行更新,该寄存器可以保存一个无符号整数,且该整数用于指定可以被向量指令更新的元素个数。若执行向量指令时,索引值大于或等于vl寄存器中的值,将清零其目标向量寄存器组中的元素。当vstart寄存器中的值大于或等于vl寄存器中的值时,不会更新目标向量寄存器组中的任何元素。矢量启动索引控制状态寄存器vstart指定向量指令执行时首个被执行元素的索引。通常vstart仅由硬件在向量指令的陷阱上写入,其值表示执行陷阱的元素位置,以及在处理了可恢复陷阱之后应恢复执行的位置。

Table 2 Calculation method of MATCH and MASK of vector instruction表2 向量指令的MATCH、MASK的计算方法

4 RISC-V向量汇编器的实现与测试

4.1 RISC-V向量汇编器的实现

汇编器[11]的工作主要分为3部分:第1是计算各标号(lables)的内存地址,因为汇编器在进行指令转换时需要知道各个符号名(symbols)与地址的联系。第2是针对每条汇编语句解析出操作数、寄存器号和立即数数值等信息,然后转换为对应的二进制编码,再按照当前汇编指令模版拼装成对应机器码。最后汇编器将汇编结果输出到一个包含二进制机器指令、数据和一些薄记信息的目标文件中。RISC-V之前的汇编器已经完成了第1项工作,本文只需要针对向量扩展完成后2项工作即可。也就是说,在RISC-V向量扩展中,首先对一条指令进行拆分,将指令名称与寄存器/操作数分开;然后通过指令描述进行一致性检查校验,当未出现校验错误时,通过查找hash表识别指令并将其转换成二进制字符串,并根据对指令的描述检查寄存器/操作数是否符合操作规范,完成对该条指令的处理,且依照制定的法则将其转换成二进制格式;最后采用字符串拼接的方式打印出处理结果,其流程如图2所示。RISC-V的向量指令的添加也将依据上述步骤完成。

Figure 2 Flow chart of assembly instruction图2 汇编指令流程

4.1.1 向量寄存器描述

RISC-V联盟发表的关于向量扩展的0.9标准,新增了32个通用向量寄存器和5个控制和状态寄存器CSR。所有的通用向量寄存器定义在文件riscv-opc.c的结构体数组riscv_vpr_names_numeric[NVPR]中,并且将这些向量寄存器添加到hash表中,故该数组成员可在解析器初始化的过程中根据寄存器名字被索引到hash表中,从而便于程序查找、匹配和调用。向量寄存器的描述代码如下所示:

const char * const riscv_vpr_names_numeric[NVPR]=

{

"v0","v1","v2","v3","v4","v5","v6","v7","v8","v9","v10","v11","v12","v13","v14","v15","v16","v17","v18","v19","v20","v21","v22","v23","v24","v25","v26","v27","v28","v29","v30","v31"

};

控制和状态寄存器CSR类寄存器则定义在riscv-opc.h文件中。

4.1.2 向量指令描述

RISC-V现有指令集的所有指令和架构都声明在riscv-opc.h文件中,故向量指令集指令和架构也声明在该文件中,因此只需在riscv-opc.h文件中添加向量指令的MATCH、MASK和DECLARE_INSN便可完成声明。在添加向量指令和架构时可引用向量扩展0.9标准的向量指令,也可根据实际需求编写向量指令,3.1节已给出关于向量指令的MATCH和MASK的计算方法。

riscv-opc.c文件中定义了riscv_opcodes[ ],RISC-V指令集的所有指令都可在这个结构体数组中找到。需要将向量指令的相关信息按name,xlen,isa,operands,match,mask,match_func,pinfo的顺序在数组riscv_opcodes[ ]中声明,才能实现编译。向量指令的描述代码如下所示:

const struct riscv_opcode riscv_opcodes[]=

{

/*name,xlen,isa,operands,match,mask,match_func,pinfo.*/

{"vfmul.vf",0,{"V",0},"VD,VS,VT",

MATCH_VFMUL_VF|MASK_RM,MATCH_VFMUL_VF|MASK_RM,match_opcode,0},

{"vadd.vv",0,{"V",0},"VD,VS,VT",

MATCH_VADD_VV,MASK_VADD_VV,

match_opcode,0},

{"vadc.vvm",0{"V",0}"VD,VS,VT",

MATCH_VADC_VVM,MASK_VADC_VVM,

match_opcode,0},

{0,0,{0},0,0,0,0,0}

};

4.1.3 相关配置信息描述

match_opcode、match_never、match_rd_nonzero等match_*类函数,是RISC-V向量扩展中的关键函数,此类函数主要根据riscv_opcodes[ ]数组中对向量指令match_func部分的描述进行匹配。这些函数根据指令的MATCH、MASK及设定的一些需求规则来对*.s文件中的指令进行一致性检查,检查是否符合相关指令描述及约束规则,并根据match_*类函数返回的值判断一致性检查是否正确,若返回值错误则汇编器报错,并终止后续操作。

一致性检查结束后,riscv_ip函数会根据*.s文件中该指令的操作数、寄存器等信息判断是否符合指令的描述。比如:判断指令的源操作数是否为常数/寄存器,判断寄存器的区间是否符合要求,判断指令的其中一个操作数是否是地址表达式等。若满足条件,汇编器会将指令转换成二进制格式,并用INSERT_OPERAND函数将字符串拼接起来,形成最终的二进制机器指令。

4.2 测试结果

测试用例1选择了通用的向量加法指令,用于测试向量汇编到目标代码的正确性。测试用例2采用包含分支、循环及可向量化特性的for循环进行测试,用来对比标量与向量汇编输出。RISC-V指令集采用功能模块化的思想实现,故在测试时需通过-march选项来指定RISC-V所支持的目标模块化指令集的组合,本文主要完成了基于RISC-V的向量指令的实现,除使用-march选项实现基本整数指令子集“i”的强制添加,还需添加待实现的向量指令子集“v”。

测试用例1向量汇编指令测试。

.text

vadc.vvm v3,v5,v7

vadc.vvm v15,v1,v3

vadc.vvm v2,v6,v8

测试结果1(汇编与反汇编)如图3所示。

Figure 3 Test result of test case 1图3 测试用例1的测试结果

测试用例2for循环加法测试。

main()

{

inta[64];

intb[64];

intc[64];

for(inti=0;i<64;i++)

a[i]=b[i]+c[i];

printf("%d",a[31]);

}

标量汇编输出结果如下:

main:

lw a5,-20(s0)

slli a5,a5,2

addi a4,s0,-16

add a5,a4,a5

lw a4,-516(a5)

lw a5,-20(s0)

slli a5,a5,2

addi a3,s0,-16

add a5,a3,a5

lw a5,-772(a5)

add a4,a4,a5

lw a5,-20(s0)

slli a5,a5,2

addi a3,s0,-16

add a5,a3,a5

sw a4,-260(a5)

lw a5,-20(s0)

addi a5,a5,1

sw a5,-20(s0)

向量汇编输出结果如下:

main:

vlxw.v v12,(s0),v18

vsll.vi v12,v12,2

vadd.vx v13,v19,s0

vadd.vv v12,v13,v12

vlxw.v v13,(s0),v12

vlxw.v v12,(s0),v18

vsll.vi v12,v12,2

vadd.vx v14,v20,s0

vadd.vv v12,v14,v12

vlxw.v v14,(s0),v12

vadd.vv v13,v13,v14

vlxw.v v12,(s0),v18

vsll.vi v12,v12,2

vadd.vx v14,v21,s0

vadd.vv v12,v14,v12

vsxw.v v13,(s0),v12

vlxw.v v12,(s0),v18

vadd.vi v12,v12,1

vsxw.v v12,(s0),v12

从图3可以看出,测试用例1的反汇编结果与测试用例1描述的向量指令一致,故向量指令的目标代码生成正确。测试用例2采用标量运算时,需要进行64*3次数据访存和64次加法运算;如果采用向量运算,则仅需8*3次数据访存和8次向量加法运算。由此可见,增加向量运算可以提高编译器的加速比,故添加支持向量指令的汇编器生成能充分利用硬件资源的目标机器代码,从而实现性能提升。

综合以上结果分析,向量运算可以提升编译器性能,汇编器能支持向量指令对应于目标平台的目标代码生成,从而能够在目标平台上正确执行。

5 结束语

本文通过分析RISC-V向量指令、向量寄存器的特点及相关配置信息,基于GNU的Binutils汇编器设计并实现了支持RISC-V向量指令的汇编器,该汇编器可完成向量指令汇编和反汇编工作,其实现可为其他指令模块的扩展提供参考。由于向量运算可以有效地提高计算机的运算效率,减少不必要的硬件开销,故在RISC-V架构上实现向量指令集的扩展及支持向量指令的汇编器是一项极具意义的工作。

猜你喜欢
标量测试用例二进制
用二进制解一道高中数学联赛数论题
回归测试中测试用例优化技术研究与探索
基于SmartUnit的安全通信系统单元测试用例自动生成
一种高效的椭圆曲线密码标量乘算法及其实现
有趣的进度
二进制在竞赛题中的应用
一种灵活的椭圆曲线密码并行化方法
二进制宽带毫米波合成器设计与分析
应用动能定理解决多过程问题错解典析
基于依赖结构的测试用例优先级技术