雷思磊
(酒泉衛(wèi)星發(fā)射中心,酒泉 735000)
開源處理器Rocket的自定義指令研究與測試
雷思磊
(酒泉衛(wèi)星發(fā)射中心,酒泉 735000)
Rocket是基于RISC-V指令集架構(gòu)的開源處理器,其實現(xiàn)了RISC-V的三條自定義指令custom0、custom1、custom2,在分析Rocket中自定義指令的處理過程后,編寫測試程序,驗證了自定義指令的實現(xiàn)。
Rocket;RoCC;RISC-V
引 言
RISC-V是加州大學伯克利分校(University of California at Berkeley,以下簡稱UCB)設計并發(fā)布的一種開源精簡指令集架構(gòu),其目標是成為指令集架構(gòu)領域的Linux,應用覆蓋IoT(Internet of Things)設備、桌面計算機、高性能計算機等領域[1]。RISC-V自2014年正式發(fā)布以來,得到了包括谷歌、IBM、Oracle等在內(nèi)的眾多企業(yè),以及包括劍橋大學、蘇黎世聯(lián)邦理工大學、印度理工學院、中國科學院在內(nèi)的眾多知名學府與研究機構(gòu)的關注和參與,圍繞RISC-V的生態(tài)環(huán)境逐漸完善,并涌現(xiàn)了眾多開源處理器及SoC采用RISC-V架構(gòu)。Rocket就是采用RISC-V指令集的開源處理器,本文研究分析了Rocket處理器對于RISC-V中自定義指令的實現(xiàn)原理,并進行了測試。
Rocket是UCB設計的一款基于RISC-V指令集、5級流水線、單發(fā)射順序執(zhí)行的64位處理器,主要特點有:
① 支持MMU,支持分頁虛擬內(nèi)存,所以可以移植Linux操作系統(tǒng);
② 具有兼容IEEE 754-2008標準的FPU;具有分支預測功能,具有BPB(Branch Prediction Buff)、BHT(Branch History Table)、RAS(Return Address Stack)。
③ Rocket是采用Chisel(Constructing Hardware in an Scala Embedded Language)編寫的,這也是UCB設計的一種開源硬件編程語言,是Scala語言的領域特定應用,可以充分利用Scala的優(yōu)勢,將面向?qū)ο?object orientation)、函數(shù)式編程(functional programming)、類型參數(shù)化(parameterized types)、類型推斷(type inference)等概念引入硬件編程語言,從而提供更加強大的硬件開發(fā)能力。Chisel除了開源之外,還有一個優(yōu)勢就是使用Chisel編寫的硬件電路可以通過編譯得到對應的Verilog設計,還可以得到對應的C++模擬器。Rocket使用Chisel編寫,就可以很容易得到對應的軟件模擬器[2]。
RISC-V指令集架構(gòu)是一個靈活可擴展的架構(gòu),其中定義了4個自定義指令:custom0、custom1、custom2、custom3。其使用方法如下:
customX rd, rs1, rs2, funct
其中rs1、rs2是源操作數(shù)寄存器編碼,rd是目的寄存器編碼,funct是具體的操作類型編碼。其二進制指令格式如圖1所示。通過opcode的編碼來區(qū)分custom0、custom1、custom2、custom3。xd、xs1、xs2分別表示rd、rs1、rs2對應的通用寄存器是否需要訪問(讀或者寫)。在Rocket處理器中,默認實現(xiàn)了custom0、custom1、custom2指令,其通過RoCC(Rocket Custom Coprocessor)模塊執(zhí)行這三條自定義指令。
圖1 customX指令的二進制格式
RoCC是在Rocket處理器中設計的協(xié)處理器,用來執(zhí)行自定義指令,從而有助于實現(xiàn)特定運算的加速執(zhí)行。RoCC與其他模塊的連接關系如圖2所示[3],在實際應用中可以有多個RoCC模塊,分別執(zhí)行不同的自定義指令。
圖2 RoCC與Rocket中其余模塊的連接關系
從圖2中可以發(fā)現(xiàn),RoCC與Core、L1 DCache、FPU、PageTable Walker、L2 Bus都有接口連接,其中與Core、L1 DCache之間的接口是基本接口,可分為如下3組:
① Core Control:用來在RoCC與Rocket Core之間傳遞狀態(tài)信息,比如RoCC正在執(zhí)行指令,處于busy狀態(tài),就會通過CC_Busy傳遞給Rocket Core。圖2中以CC開始的信息就是Core Control接口的內(nèi)容。
② Register Mode:用來在RoCC與Rocket Core之間傳遞指令信息,比如源寄存器的值、指令內(nèi)容等。圖2中以Core開始的信息就是Register Mode接口的內(nèi)容。
③ Memory Mode:用來在RoCC與L1 DCache之間傳遞數(shù)據(jù),圖2中以MEM開始的信息就是Memory Mode接口的內(nèi)容。
其余的接口都是可選的擴展接口,可分為如下4組:
① Control Status Register:用來使得運行在Rocket Core上的Linux可以獲取RoCC的狀態(tài)信息,圖2中以CSR開始的信息就是Control Status Register接口的內(nèi)容。
② PageTable Walker:RoCC可以使用該接口進行虛擬地址到物理地址的轉(zhuǎn)換,圖2中以PTW開始的信息就是PageTable Walker接口的內(nèi)容。
③ Float Point Unit:RoCC可以使用該接口向FPU收發(fā)數(shù)據(jù),圖2中以FPU開始的信息就是Float Point Unit接口的內(nèi)容。
④ Uncached TileLink:RoCC可以使用該接口訪問L2 Cache,圖2中以UTL開始的信息就是Uncached TileLink接口的內(nèi)容。
上述7組接口中,最基本的就是Register Mode接口,處理器發(fā)送指令給RoCC以及RoCC返回響應信息都是通過該接口實現(xiàn)的,該接口包括兩個部分:Rocket Core發(fā)送給RoCC的指令信息(即Core Cmd)、RoCC返回給Rocket Core的結(jié)果信息(即Core Resp)。其中Core Cmd的定義如下:
//指令域的定義,共32bit
class RoCCInstruction extends Bundle{
val funct = Bits(width = 7)
val rs2 = Bits(width = 5)
val rs1 = Bits(width = 5)
val xd = Bool()
val xs1 = Bool()
val xs2 = Bool()
val rd = Bits(width = 5)
val opcode = Bits(width = 7)
}
class RoCCCommand(implicit p: Parameters) extends CoreBundle()(p) {
val inst = new RoCCInstruction
//在上面定義了RoCCInstruction類
val rs1 = Bits(width = xLen)
val rs2 = Bits(width = xLen)
val status = new MStatus
}
上文定義的類RoCCCommand就是Core Cmd,是Rocket Core發(fā)送給RoCC的指令,其中包括源寄存器rs1、rs2的值,以及對應的指令信息,后者通過RoCCInstruction類實現(xiàn),其內(nèi)容與圖1中的結(jié)構(gòu)是一一對應的。Core Resp的定義如下:
class RoCCResponse(implicit p: Parameters) extends
CoreBundle()(p) {
val rd = Bits(width = 5)
val data = Bits(width = xLen)
}
其中包括要寫入的目的寄存器地址、要寫入的數(shù)據(jù)。
Rocket處理器默認實現(xiàn)了3個RoCC,分別是AccumulatorExample、TranslatorExample、CharacterCountExample,均在rocc.scala中定義,其作用分別如下:
① AccumulatorExample:是一個累加器的例子,驗證了RoCC與Rocket Core、L1 DCache的接口是否正常,用來處理指令custom0。
② TranslatorExample:通過計算一個虛擬地址對應的物理地址,驗證了RoCC與PageTable Walker的接口是否正常,用來處理指令custom1。
③ CharacterCountExample:通過計算一個數(shù)據(jù)塊中的特定字符的數(shù)量,驗證RoCC的Uncache TileLink接口是否正常,用來處理指令custom2。
Rocket Core在譯碼的時候需要判斷是否是自定義指令,如果是,那么會通過Core Cmd送出自定義指令,由RoccCommandRouter這個類分析判斷是哪一條自定義指令,從而送入對應的RoCC進行處理并將結(jié)果返回給Rocket Core,如圖3所示。
圖3 RoccCommandRouter類進行RoCC指令與處理結(jié)果的分發(fā)
限于篇幅,本文重點對AccumulatorExample的實現(xiàn)加以分析和測試。當Rocket Core處理器在譯碼的時候發(fā)現(xiàn)是指令custom0,那么就會將相應的參數(shù)通過Core Cmd發(fā)送給AccumulatorExample這個RoCC,AccumulatorExample依據(jù)發(fā)送過的參數(shù)中funct的值進行具體處理,funct代表了具體的操作類型, AccumulatorExample支持的操作類型有:
① 將通用寄存器的值寫入RoCC。AccumulatorExample在RoCC中定義了4個內(nèi)部寄存器,可以將Rocket Core中某個通用寄存器的值寫到這4個內(nèi)部寄存器中。指令格式如下:
custom0 rd, rs1, rs2, 0
rd沒有使用,可以任意;rs1為要讀取的通用寄存器的編號;rs2的值等于0~3,表示RoCC內(nèi)部寄存器的編號;最后的funct為0。
② 將RoCC內(nèi)部寄存器的值寫入通用寄存器。指令格式如下:
custom0 rd, rs1, rs2, 1
rd為要寫入的通用寄存器編號;rs1沒有使用,可以任意;rs2的值等于0~3,表示RoCC內(nèi)部寄存器的編號;最后的funct為1。
③ 從L1 DCache加載數(shù)據(jù)到RoCC內(nèi)部寄存器。指令格式如下:
custom0 rd, rs1, rs2, 2
rd沒有使用,可以任意;rs1為通用寄存器的編號,該通用寄存器中存儲的是數(shù)據(jù)的物理地址;rs2的值等于0~3,表示要加載到的RoCC內(nèi)部寄存器的編號;最后的funct為2。
④ 將通用寄存器的值與RoCC內(nèi)部寄存器的值相加,結(jié)果保存到RoCC內(nèi)部寄存器。指令格式如下:
custom0 rd, rs1, rs2, 3
rd沒有使用,可以任意;rs1為通用寄存器的編號;rs2的值等于0~3,表示要執(zhí)行累加操作的RoCC內(nèi)部寄存器的編號;最后的funct為3。
本節(jié)通過Rocket自帶的程序測試RoCC的功能是否正確,試驗環(huán)境為Ubuntu14.04。
5.1 編譯得到對應的模擬器
使用如下指令從Github上下載Rocket對應的代碼,并編譯得到相應的GCC編譯器等工具。
$ git clone https://github.com/ucb-bar/rocket-chip.git
$ cd rocket-chip
$ git submodule update - -init
$ cd riscv-tools
$ git submodule update - -init - -recursive
$ export RISCV=/opt/riscv
$ ./build.sh
然后進入emulator目錄,編譯得到帶RoCC功能的C++模擬器,如下:
$ cd emulator
$ make CONFIG=RoCCExampleConfig
在emulator目錄下得到C++模擬器emulator-rocketchip-RoccExampleConfig。
5.2 修改Proxy Kernel
在Rocket處理器中有一個寄存器mstatus,其中有一個XS域,該域的值只有為非零的時候,才可以執(zhí)行自定義指令,否則會出現(xiàn)無效指令異常。默認情況下該域的值為零,需要通過程序修改該值。
Proxy Kernel(簡稱pk)是一個輕量級的應用程序執(zhí)行環(huán)境,能夠在其上執(zhí)行ELF程序[4]。對于使用C編寫的應用程序,可以在pk中執(zhí)行,此時處理器首先執(zhí)行pk,準備好硬件環(huán)境,然后pk加載應用程序。
本文的測試程序為C代碼,使用pk作為其執(zhí)行環(huán)境,從而可以直接修改pk的代碼,使得pk在準備硬件環(huán)境的時候就設置mstatus寄存器的XS域為一個非零值。具體過程就是修改pk目錄下minit.c中的mstatus_init函數(shù),在其中添加如下語句:
ms = INSERT_FIELD(ms, MSTATUS_XS, 3);
然后重新編譯得到新的pk。
5.3 測試程序
使用C語言編寫測試程序如下,該程序是在Rocket-chip提供的測試程序之上修改的,原測試程序經(jīng)過測試有一定問題,第三個測試沒有通過。
#include
#include
#include
int main() {
uint64_t x = 123, y = 456, z = 0;
//************** 測試一**************
//加載x的值到RoCC的內(nèi)部寄存器2
asm volatile ("custom0 x0, %0, 2, 0" : : "r"(x));
//讀取RoCC內(nèi)部寄存器2的值,保存到變量z
asm volatile ("custom0 %0, x0, 2, 1" : "=r"(z));
//驗證z是否等于x
assert(z == x);
//************** 測試二**************
//將變量y的值與RoCC內(nèi)部寄存器2的值相加,結(jié)果存儲 //到RoCC內(nèi)部寄存器2
asm volatile ("custom0 x0, %0, 2, 3" : : "r"(y));
//讀出RoCC內(nèi)部寄存器2的值,保存到變量z
asm volatile ("custom0 %0, x0, 2, 1" : "=r"(z));
//驗證z是否等于x+y
assert(z == x+y);
//************** 測試三**************
//測試三與測試二的過程是一致的,但是在測試三中是從 //L1 DCache中獲取變量x的值
//使用custom1指令,獲取變量x的物理地址,保存到通用 //寄存器x0
asm volatile ("custom1 x0, %0, 2, 0" : : "r"(&x));
//從L1 DCache中加載地址為x0的數(shù)據(jù),保存到RoCC內(nèi) //部寄存器2
asm volatile ("custom0 x0, x0, 2, 2");
//將變量y的值與RoCC內(nèi)部寄存器2的值相加,結(jié)果存儲 //到RoCC內(nèi)部寄存器2
asm volatile ("custom0 x0, %0, 2, 3" : : "r"(y));
//讀出RoCC內(nèi)部寄存器2的值,保存到變量z
asm volatile ("custom0 %0, x0, 2, 1" : "=r"(z));
//驗證z是否等于x+y
assert(z == x+y);
printf("success! ");
}
上述測試程序可以分為三個部分,測試了AccumulatorExample、TranslatorExample兩個RoCC,custom0、custom1兩條自定義指令。使用gcc編譯得到對應的ELF程序,使用前文得到的C++模擬器進行仿真測試,如下:
./emulator-rocketchip-RoccExampleConfig +max-cycles=10000000 +dramsim pk ../../test/rocctest/a.out
最后輸出success,表示測試成功。
[1] Waterman A.The RISC-V Instruction Set Manual,Volume I:User-Level ISA,Version 2.1,2016.
[2] Chisel 2.2 Tutorial[EB/OL].[2016-12].https://chisel.eecs.berkeley.edu/2.2.0/chisel-tutorial.
[3] Anuj Rao.The RoCC Doc V2:An Introduction to the Rocket Custom Coprocessor Interface[EB/OL].[2016-12].https://docs.google.com/document/d/1CH2ep4YcL_ojsa3BVHEW-uwcKh1FlFTjH_kg5v8bxVw/edit.
[4] RISC-V Proxy Kernel[EB/OL].[2016-12].https://github.com/riscv/riscv-pk/tree/f892b43a2bb1c2405b9941aaefdb25 e3b4efe1f1.
雷思磊(工程師),主要研究方向為處理器架構(gòu)、嵌入式處理器應用等。
Custom Instruction Research and Verification of Open Source Processor Rocket
Lei Silei
(Jiuquan Satellite Launch Center,Jiuquan 735000,China)
The Rocket is an open source processor based on RISC-V instruction set architecture,which implements the RISC-V three custom instructions including custom0,custom1 and custom2.After analyzing the principle of Rocket,the program is written to verify the implementation of the custom instructions.
Rocket;RoCC;RISC-V
TP368.1
A
士然
2016-12-12)