鄧 平,朱小龍,孫海燕,任 怡
(國防科技大學(xué)計(jì)算機(jī)學(xué)院,湖南 長沙 410073)
隨著智能物聯(lián)網(wǎng)AIOT(Artificial Internet Of Things)應(yīng)用需求的日益增長、中美國際貿(mào)易戰(zhàn)的影響以及企業(yè)本身對(duì)成本和技術(shù)創(chuàng)新的追求,RISC-V架構(gòu)以其開放、簡潔、模塊化等特點(diǎn)得到了業(yè)界廣泛關(guān)注。國內(nèi)有多家企業(yè)宣布采用RISC-V架構(gòu)設(shè)計(jì)相關(guān)芯片,包括華為、芯來科技、國防科技大學(xué)、中國科學(xué)院等。在芯片體系結(jié)構(gòu)相關(guān)技術(shù)中,向量指令是提高芯片運(yùn)算性能的重要手段之一。RISC-V聯(lián)盟于2020年5月發(fā)布RISC-V向量擴(kuò)展0.9版本[1]標(biāo)準(zhǔn)草案,給出了RISC-V體系結(jié)構(gòu)的向量指令規(guī)范,為RISC-V體系結(jié)構(gòu)[2]在更高性能的應(yīng)用領(lǐng)域中的推廣提供了支持。
編譯器、匯編器等芯片編譯工具鏈對(duì)任何一個(gè)硬件架構(gòu)而言,都是構(gòu)筑軟件生態(tài)必不可少的組成部分,編譯環(huán)境的好壞直接決定了該架構(gòu)的用戶數(shù)量和應(yīng)用范圍。目前,RISC-V的配套開發(fā)環(huán)境已經(jīng)很成熟,但在支持向量指令這方面還不夠完善。雖然RISC-V已包含向量指令規(guī)范,但在目前的開源工具鏈代碼中并沒有實(shí)現(xiàn)相關(guān)的向量指令,這在很大程度上影響了RISC-V的應(yīng)用推廣。本文的主要工作是基于RISC-V 0.9版本向量規(guī)范和GNU Binutils開源平臺(tái),討論實(shí)現(xiàn)支持RISC-V向量指令的匯編器關(guān)鍵技術(shù),設(shè)計(jì)并實(shí)現(xiàn)RISC-V向量匯編器。
RISC-V向量指令集是基于基礎(chǔ)指令集的拓展,主要用于支持向量運(yùn)算操作,提高硬件執(zhí)行效率。在RISC-V 0.9版本中,向量指令遵循如表1所示的編碼標(biāo)準(zhǔn)。向量指令[1]主要在標(biāo)量浮點(diǎn)加載(LOAD-FP)、標(biāo)量浮點(diǎn)存儲(chǔ)(STORE-FP)和原子性存儲(chǔ)器操作(AMO)指令的基礎(chǔ)上進(jìn)行拓展定義,另外新增了OP-V類的操作指令。向量的加載和存儲(chǔ)編碼利用了LOAD-FP和STORE-FP的12位立即數(shù)編碼字段(表1第20至31位)[3]的一部分,同時(shí)第25位保留了標(biāo)準(zhǔn)向量掩碼位(vm)作為條件域,大多數(shù)向量指令可以在條件域的掩碼控制下無條件或有條件地執(zhí)行。
向量指令的源操作數(shù)一般為標(biāo)量或向量,運(yùn)算結(jié)果可存于對(duì)應(yīng)標(biāo)量或向量寄存器中。除立即數(shù)外,向量指令中的標(biāo)量操作數(shù)還可以是向量寄存器的0號(hào)元素,以及通用寄存器或浮點(diǎn)寄存器中存儲(chǔ)的數(shù)據(jù)。按目前規(guī)范約定任何向量寄存器都可以用于保存標(biāo)量。向量操作數(shù)或向量結(jié)果可能會(huì)占用一個(gè)或多個(gè)向量寄存器(向量寄存器組),向量寄存器組始終由組中編號(hào)最小的向量寄存器來指定。
GNU[4]是一系列編譯工具[5]的集合,統(tǒng)稱為工具鏈GNU,其在開發(fā)應(yīng)用程序和操作系統(tǒng)中具有重要作用。GNU包括GNU make、GCC(GNU Compiler Collection)、GDB(GNU Debugger)、GNU Binutils。對(duì)RISC-V的向量指令集擴(kuò)展需要借助GNU Binutils工具來完成,同時(shí)在Binultils工具中進(jìn)行一些與RISC-V平臺(tái)對(duì)應(yīng)的修改。采用GNU Binutils主要是因?yàn)榘珺inutils的GNU支持絕大多數(shù)當(dāng)前主流的主機(jī)操作系統(tǒng),其GNU的變種基本都能適用于類Unix操作系統(tǒng)、Linux和Windows操作系統(tǒng)。由于GNU提供了一套完備的工具,故無須過多考慮與其他工具的整合問題。即使需要考慮,由于GNU是開放的,它支持多種開放的目標(biāo)文件格式和調(diào)試格式,存在多種形式的開源C標(biāo)準(zhǔn)庫可以使用,可以和編譯器鏈接器完美整合。
Table 1 RISC-V vector instruction formats表1 RISC-V向量指令格式
GNU Binutils是一組二進(jìn)制程序處理工具[6],包含最主要的匯編器as、ld、objdump、readelf等。在Linux環(huán)境下,二進(jìn)制程序主要是指*.o文件和*.elf執(zhí)行文件[7]。as是Binutils的匯編器工具,負(fù)責(zé)將匯編程序轉(zhuǎn)化為目標(biāo)機(jī)器指令;ld是鏈接器,可以將多個(gè)目標(biāo)文件鏈接成一個(gè)可執(zhí)行文件;objdump用于反匯編,將二進(jìn)制文件轉(zhuǎn)換為匯編代碼;readelf用于顯示elf格式可執(zhí)行文件的信息。GNU Binutils包含的匯編器as和鏈接器ld等工具都使用二進(jìn)制文件描述符庫BFD(the Binary File Description library)[8]來操作目標(biāo)文件和庫,如圖1所示。
Figure 1 Binary toolsets圖1 二進(jìn)制工具集
向量指令的描述和向量寄存器的描述是實(shí)現(xiàn)RISC-V向量匯編器的關(guān)鍵。
匯編指令與二進(jìn)制指令之間是一一對(duì)應(yīng)的關(guān)系,即直譯的過程。實(shí)現(xiàn)匯編器的重點(diǎn)在于定義好匯編指令集、二進(jìn)制指令集,以及確定好兩者之間的映射轉(zhuǎn)換關(guān)系[9]。首要解決的問題是匯編指令對(duì)應(yīng)的二進(jìn)制指令的編碼問題,RISC-V架構(gòu)定義的標(biāo)準(zhǔn)指令集僅使用了少量的二進(jìn)制指令編碼空間,更多的編碼空間被預(yù)留給用戶作為擴(kuò)展指令使用,本文將利用這些預(yù)留的編碼空間對(duì)向量指令集[10]進(jìn)行編碼。
表2以指令vadc.vvm為例,描述了向量匯編指令及其對(duì)應(yīng)的二進(jìn)制編碼,其中vd表示目的寄存器,rs1和vs2表示操作數(shù)寄存器,各占用5 bit。將rs1、vs2和vd所在域全部置成0,其他位域保持為默認(rèn)編碼后得到的二進(jìn)制碼就是這條匯編指令的校驗(yàn)值MATCH。當(dāng)指令的rs1、vs2和vd對(duì)應(yīng)位域值為0,其他位值全為1時(shí),得到的二進(jìn)制碼為該指令的MASK掩碼值。指令的MATCH和MASK用于一致性檢查,以校驗(yàn)指令的正確性。表2中最后2行分別為vadc.vvm的MATCH和MASK。
在向量指令擴(kuò)展[2]中,需將32個(gè)通用向量寄存器v0~v31添加到基本RISC-V的ISA中,每個(gè)通用向量寄存器都有一個(gè)固定狀態(tài)位v-len。如果基本標(biāo)量ISA不包含浮點(diǎn),則還需添加浮點(diǎn)狀態(tài)和控制寄存器fcsr,用來保存向量定點(diǎn)飽和標(biāo)志寄存器vxsat和向量定點(diǎn)舍入模式寄存器vxrm的鏡像。向量定點(diǎn)舍入模式寄存器vxrm具有2位讀寫舍入模式字段,用于指示定點(diǎn)指令的舍入模式,如四舍五入模式、取整模式等,這一特性反映在fcsr高位字段中。向量定點(diǎn)飽和標(biāo)志寄存器vxsat保留單個(gè)讀寫位,該位用于指示定點(diǎn)指令是否需要為了適應(yīng)目標(biāo)格式而必須使輸出值飽和,且vxsat位也反映在fcsr的高位字段中。將vxrm、vxsat等字段打包到fcsr,可以加速對(duì)上下文的保存/恢復(fù)。
對(duì)于向量類型控制狀態(tài)寄存器vtype,指定其只能被vsetvl{i}[1]指令進(jìn)行更新,其中i為vsetl{i}編碼中高位字段(20~30位)表示的立即數(shù),該寄存器可以提供默認(rèn)類型用于解釋向量寄存器文件內(nèi)容,還可以對(duì)多個(gè)向量寄存器進(jìn)行分組。向量長度寄存器vl只能通過vsetvl{i}和vsetvl指令進(jìn)行更新,該寄存器可以保存一個(gè)無符號(hào)整數(shù),且該整數(shù)用于指定可以被向量指令更新的元素個(gè)數(shù)。若執(zhí)行向量指令時(shí),索引值大于或等于vl寄存器中的值,將清零其目標(biāo)向量寄存器組中的元素。當(dāng)vstart寄存器中的值大于或等于vl寄存器中的值時(shí),不會(huì)更新目標(biāo)向量寄存器組中的任何元素。矢量啟動(dòng)索引控制狀態(tài)寄存器vstart指定向量指令執(zhí)行時(shí)首個(gè)被執(zhí)行元素的索引。通常vstart僅由硬件在向量指令的陷阱上寫入,其值表示執(zhí)行陷阱的元素位置,以及在處理了可恢復(fù)陷阱之后應(yīng)恢復(fù)執(zhí)行的位置。
Table 2 Calculation method of MATCH and MASK of vector instruction表2 向量指令的MATCH、MASK的計(jì)算方法
匯編器[11]的工作主要分為3部分:第1是計(jì)算各標(biāo)號(hào)(lables)的內(nèi)存地址,因?yàn)閰R編器在進(jìn)行指令轉(zhuǎn)換時(shí)需要知道各個(gè)符號(hào)名(symbols)與地址的聯(lián)系。第2是針對(duì)每條匯編語句解析出操作數(shù)、寄存器號(hào)和立即數(shù)數(shù)值等信息,然后轉(zhuǎn)換為對(duì)應(yīng)的二進(jìn)制編碼,再按照當(dāng)前匯編指令模版拼裝成對(duì)應(yīng)機(jī)器碼。最后匯編器將匯編結(jié)果輸出到一個(gè)包含二進(jìn)制機(jī)器指令、數(shù)據(jù)和一些薄記信息的目標(biāo)文件中。RISC-V之前的匯編器已經(jīng)完成了第1項(xiàng)工作,本文只需要針對(duì)向量擴(kuò)展完成后2項(xiàng)工作即可。也就是說,在RISC-V向量擴(kuò)展中,首先對(duì)一條指令進(jìn)行拆分,將指令名稱與寄存器/操作數(shù)分開;然后通過指令描述進(jìn)行一致性檢查校驗(yàn),當(dāng)未出現(xiàn)校驗(yàn)錯(cuò)誤時(shí),通過查找hash表識(shí)別指令并將其轉(zhuǎn)換成二進(jìn)制字符串,并根據(jù)對(duì)指令的描述檢查寄存器/操作數(shù)是否符合操作規(guī)范,完成對(duì)該條指令的處理,且依照制定的法則將其轉(zhuǎn)換成二進(jìn)制格式;最后采用字符串拼接的方式打印出處理結(jié)果,其流程如圖2所示。RISC-V的向量指令的添加也將依據(jù)上述步驟完成。
Figure 2 Flow chart of assembly instruction圖2 匯編指令流程
4.1.1 向量寄存器描述
RISC-V聯(lián)盟發(fā)表的關(guān)于向量擴(kuò)展的0.9標(biāo)準(zhǔn),新增了32個(gè)通用向量寄存器和5個(gè)控制和狀態(tài)寄存器CSR。所有的通用向量寄存器定義在文件riscv-opc.c的結(jié)構(gòu)體數(shù)組riscv_vpr_names_numeric[NVPR]中,并且將這些向量寄存器添加到hash表中,故該數(shù)組成員可在解析器初始化的過程中根據(jù)寄存器名字被索引到hash表中,從而便于程序查找、匹配和調(diào)用。向量寄存器的描述代碼如下所示:
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"
};
控制和狀態(tài)寄存器CSR類寄存器則定義在riscv-opc.h文件中。
4.1.2 向量指令描述
RISC-V現(xiàn)有指令集的所有指令和架構(gòu)都聲明在riscv-opc.h文件中,故向量指令集指令和架構(gòu)也聲明在該文件中,因此只需在riscv-opc.h文件中添加向量指令的MATCH、MASK和DECLARE_INSN便可完成聲明。在添加向量指令和架構(gòu)時(shí)可引用向量擴(kuò)展0.9標(biāo)準(zhǔn)的向量指令,也可根據(jù)實(shí)際需求編寫向量指令,3.1節(jié)已給出關(guān)于向量指令的MATCH和MASK的計(jì)算方法。
riscv-opc.c文件中定義了riscv_opcodes[ ],RISC-V指令集的所有指令都可在這個(gè)結(jié)構(gòu)體數(shù)組中找到。需要將向量指令的相關(guān)信息按name,xlen,isa,operands,match,mask,match_func,pinfo的順序在數(shù)組riscv_opcodes[ ]中聲明,才能實(shí)現(xiàn)編譯。向量指令的描述代碼如下所示:
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 相關(guān)配置信息描述
match_opcode、match_never、match_rd_nonzero等match_*類函數(shù),是RISC-V向量擴(kuò)展中的關(guān)鍵函數(shù),此類函數(shù)主要根據(jù)riscv_opcodes[ ]數(shù)組中對(duì)向量指令match_func部分的描述進(jìn)行匹配。這些函數(shù)根據(jù)指令的MATCH、MASK及設(shè)定的一些需求規(guī)則來對(duì)*.s文件中的指令進(jìn)行一致性檢查,檢查是否符合相關(guān)指令描述及約束規(guī)則,并根據(jù)match_*類函數(shù)返回的值判斷一致性檢查是否正確,若返回值錯(cuò)誤則匯編器報(bào)錯(cuò),并終止后續(xù)操作。
一致性檢查結(jié)束后,riscv_ip函數(shù)會(huì)根據(jù)*.s文件中該指令的操作數(shù)、寄存器等信息判斷是否符合指令的描述。比如:判斷指令的源操作數(shù)是否為常數(shù)/寄存器,判斷寄存器的區(qū)間是否符合要求,判斷指令的其中一個(gè)操作數(shù)是否是地址表達(dá)式等。若滿足條件,匯編器會(huì)將指令轉(zhuǎn)換成二進(jìn)制格式,并用INSERT_OPERAND函數(shù)將字符串拼接起來,形成最終的二進(jìn)制機(jī)器指令。
測試用例1選擇了通用的向量加法指令,用于測試向量匯編到目標(biāo)代碼的正確性。測試用例2采用包含分支、循環(huán)及可向量化特性的for循環(huán)進(jìn)行測試,用來對(duì)比標(biāo)量與向量匯編輸出。RISC-V指令集采用功能模塊化的思想實(shí)現(xiàn),故在測試時(shí)需通過-march選項(xiàng)來指定RISC-V所支持的目標(biāo)模塊化指令集的組合,本文主要完成了基于RISC-V的向量指令的實(shí)現(xiàn),除使用-march選項(xiàng)實(shí)現(xiàn)基本整數(shù)指令子集“i”的強(qiáng)制添加,還需添加待實(shí)現(xiàn)的向量指令子集“v”。
測試用例1向量匯編指令測試。
.text
vadc.vvm v3,v5,v7
vadc.vvm v15,v1,v3
vadc.vvm v2,v6,v8
測試結(jié)果1(匯編與反匯編)如圖3所示。
Figure 3 Test result of test case 1圖3 測試用例1的測試結(jié)果
測試用例2for循環(huán)加法測試。
main()
{
inta[64];
intb[64];
intc[64];
for(inti=0;i<64;i++)
a[i]=b[i]+c[i];
printf("%d",a[31]);
}
標(biāo)量匯編輸出結(jié)果如下:
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)
?
向量匯編輸出結(jié)果如下:
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的反匯編結(jié)果與測試用例1描述的向量指令一致,故向量指令的目標(biāo)代碼生成正確。測試用例2采用標(biāo)量運(yùn)算時(shí),需要進(jìn)行64*3次數(shù)據(jù)訪存和64次加法運(yùn)算;如果采用向量運(yùn)算,則僅需8*3次數(shù)據(jù)訪存和8次向量加法運(yùn)算。由此可見,增加向量運(yùn)算可以提高編譯器的加速比,故添加支持向量指令的匯編器生成能充分利用硬件資源的目標(biāo)機(jī)器代碼,從而實(shí)現(xiàn)性能提升。
綜合以上結(jié)果分析,向量運(yùn)算可以提升編譯器性能,匯編器能支持向量指令對(duì)應(yīng)于目標(biāo)平臺(tái)的目標(biāo)代碼生成,從而能夠在目標(biāo)平臺(tái)上正確執(zhí)行。
本文通過分析RISC-V向量指令、向量寄存器的特點(diǎn)及相關(guān)配置信息,基于GNU的Binutils匯編器設(shè)計(jì)并實(shí)現(xiàn)了支持RISC-V向量指令的匯編器,該匯編器可完成向量指令匯編和反匯編工作,其實(shí)現(xiàn)可為其他指令模塊的擴(kuò)展提供參考。由于向量運(yùn)算可以有效地提高計(jì)算機(jī)的運(yùn)算效率,減少不必要的硬件開銷,故在RISC-V架構(gòu)上實(shí)現(xiàn)向量指令集的擴(kuò)展及支持向量指令的匯編器是一項(xiàng)極具意義的工作。