李金鳳 ,于德明 ,郭瑞華
(沈陽化工大學信息工程學院,遼寧 沈陽 110027)
RISC-V 指令集架構(gòu)的出現(xiàn)引起了業(yè)內(nèi)的廣泛關(guān)注,并產(chǎn)生了許多基于RISC-V 指令集架構(gòu)的處理器設(shè)計成果。比如,UCB 設(shè)計研發(fā)的標量處理器Rocket 以及超標量處理器BOOM,Vector Blox 公司設(shè)計的標量處理器ORCA,由蘇黎世聯(lián)邦理工大學和波羅尼亞大學聯(lián)合設(shè)計的超低功耗處理器RI5CY等等[1]。在國內(nèi)也同樣涌現(xiàn)出了諸多RISC-V 架構(gòu)相關(guān)的研究成果,例如芯來科技公司的蜂鳥E200 系列,平頭哥公司的無劍、玄鐵等等。然而,國內(nèi)針對RISC-V 的開源處理器的研究使用5 級流水線且加入64 位擴展集的設(shè)計方案較少,大多數(shù)的設(shè)計方案僅考慮功能方面的實現(xiàn),對于性能的優(yōu)化略有不足[2]。基于以上背景,課題組在分析了RISC-V 指令集的基礎(chǔ)上,采用五級流水線設(shè)計RISC-V 處理器,并在Linux 系統(tǒng)上搭建了相應(yīng)的運行環(huán)境。最終完成的基于RISC-V 指令集的64 位五級流水處理器及其運行環(huán)境成功運行了CoreMark 測試程序,CoreMark 測試結(jié)果為3.38 CoreMark/MHz。
本處理器采用經(jīng)典的五級流水線,五級分別稱為取指(Instruction Fetch, lF)、譯碼(Instruction Decode, ID)、執(zhí)行(Execute, EXE)、訪存(memory,MEM)、寫回(Write back, WB)。處理器整體框圖如圖1所示。

圖1 處理器整體框圖
取指階段主要包括指令計數(shù)器模塊和取指模塊,主要作用是在取指后將指令地址與指令傳輸?shù)阶g碼級。譯碼階段主要包括譯碼模塊和通用寄存器模塊,主要作用是將指令譯碼,使處理器明確本條指令的運算類型、源寄存器、源操作數(shù)、目標寄存器等信息。執(zhí)行階段主要包括算術(shù)邏輯運算模塊、控制與狀態(tài)寄存器模塊、訪存地址生成模塊、乘除法器模塊。主要功能是根據(jù)譯碼結(jié)果執(zhí)行相應(yīng)的運算或者操作。訪存階段主要包括存儲器訪問地址生成模塊AGU(Address Generation Unit)、存儲器訪問控制模塊LSU (Load Store Unit),主要作用是對存儲器訪問指令執(zhí)行完成后生成的訪存地址進行存儲器讀取操作,并將數(shù)據(jù)從存儲器中讀出或者寫入存儲器[3]。寫回階段主要功能部件就是寄存器寫端口,主要作用就是將指令運算的結(jié)果寫回到存儲器或者寄存器。
在RISC-V 指令集中分支跳轉(zhuǎn)指令一般分為無條件分支跳轉(zhuǎn)指令和有條件分支跳轉(zhuǎn)指令。而無條件分支跳轉(zhuǎn)指令又分為無條件直接分支跳轉(zhuǎn)指令和無條件間接分支跳轉(zhuǎn)指令,有條件分支跳轉(zhuǎn)指令只有一種,即有條件直接分支跳轉(zhuǎn)指令[4]。
通常在流水線中,取指階段以及譯碼階段無法對有條件跳轉(zhuǎn)指令做出判斷,決定是否跳轉(zhuǎn),只能等待指令執(zhí)行結(jié)束,才能判斷出指令是否滿足跳轉(zhuǎn)的條件。在這個過程中處理器將出現(xiàn)較長時間的延遲,嚴重影響處理器的性能[5]。所以為了解決這一問題,將分支指令的處理提前到譯碼階段,這樣做可以極大地降低處理分支指令時產(chǎn)生的延遲對后續(xù)流水線的影響。分支跳轉(zhuǎn)指令處理模塊如圖2所示。

圖2 分支跳轉(zhuǎn)指令處理模塊
該模塊首先使用LOGIC 模塊根據(jù)BFUN3 信號判斷跳轉(zhuǎn)指令的類別,然后計算出跳轉(zhuǎn)指令的地址,最后將生成的這兩個信號通過一個多路選擇器傳輸給PC,最終實現(xiàn)分支跳轉(zhuǎn)指令。
流水線的冒險主要包括三個方面:結(jié)構(gòu)冒險、數(shù)據(jù)冒險、控制冒險。
結(jié)構(gòu)冒險是指流水線中硬件資源的沖突,解決資源沖突可以通過復(fù)制硬件資源或者流水線停頓等待硬件資源的方法解決[6]。
數(shù)據(jù)冒險是指流水線在傳播過程中,不同指令可能在同一時刻對同一寄存器產(chǎn)生讀寫信號,指令先后順序的不同可能造成讀寫數(shù)據(jù)發(fā)生錯誤,即產(chǎn)生數(shù)據(jù)冒險[7]。在本處理器中主要會引起數(shù)據(jù)相關(guān)沖突的是先寫后讀(Read After Write, RAW),也稱為真相關(guān)[8]。RAW 必須等到先序指令執(zhí)行完成,再進行后續(xù)操作,這將造成流水線停頓。本文采用數(shù)據(jù)旁路傳播技術(shù)來處理該問題。具體實現(xiàn)思路如圖3 所示。通過數(shù)據(jù)旁路,將先序指令的數(shù)據(jù)通路生成的中間數(shù)據(jù)直接轉(zhuǎn)發(fā)到ALU 的輸出端。圖中,指令4 中ALU 的輸入端與前三條指令的中間數(shù)據(jù)使用數(shù)據(jù)旁路連接,具體實現(xiàn)如圖4所示。

圖3 使用數(shù)據(jù)旁路解決數(shù)據(jù)冒險

圖4 數(shù)據(jù)旁路模塊
使用數(shù)據(jù)旁路可以解決大部分RAW 數(shù)據(jù)冒險,但是對于LW 指令后緊跟R 型指令或者I 型運算指令(即Load-use 數(shù)據(jù)冒險)的情況只能使用前插入空操作的方式來解決。具體的實現(xiàn)需要在load 指令前插入空操作指令NOP,同時還需要讓PC 以及IF/ID 的寄存器狀態(tài)保持在一個周期。
控制冒險也稱為分支冒險。因為取到的指令或者指令地址的變化與流水線的預(yù)期不符,從而導(dǎo)致指令不能在預(yù)定的時鐘周期內(nèi)完成[9]。本文采用流水線阻塞的方式來處理控制冒險。在IF/ID 的寄存器中插入一個氣泡(NOP)強制改寫寄存器中的內(nèi)容。當分支未被選中時(在ID 期間確定),提取未選中指令,繼續(xù)進行。當在ID 期間確定選中該分支時,則在分支目標處重新開始提取,該分支后面的所有指令停頓1個時鐘周期。
乘法器使用了2 位Booth 算法來實現(xiàn),2 位Booth算法見公式(1):
由于在硬件中所有的操作數(shù)都是以補碼的形式存在,如果單純使用補碼乘法算法,則需要特地挑出第N個部分積,并使用補碼減法操作,這就需要實現(xiàn)一個額外的狀態(tài)機來控制,從而增加了硬件設(shè)計復(fù)雜度。但是,使用2 位Booth 算法可以顯著減少加法計算的次數(shù)[10]。
采用2 位Booth 算法,使用移位加策略實現(xiàn)64 位的乘法操作需要32 個時鐘周期,并且不支持流水操作,即第一條乘法全部完成之后才能開始計算下一條。為了實現(xiàn)全流水、4 個時鐘周期延遲的定點乘法指令,就需要使用將各個部分積并行地加在一起的方式進行計算,而不是串行迭代累加。因此,在使用Booth 算法的基礎(chǔ)上,使用不需要等待進位信號的保留進位加法器,并且使用華萊士樹結(jié)構(gòu)。最終完成的乘法器結(jié)構(gòu)如圖5所示。

圖5 乘法器結(jié)構(gòu)
圖5 中的第(1)部分采用2 位Booth 算法得到16個部分積,其中P 為128 位,是部分積的主體,C 為1位,表示對被乘數(shù)取反。第(2)部分采用類似矩陣轉(zhuǎn)置連線方式,將16 個128 位部分積轉(zhuǎn)置為128 個16位等寬的數(shù),用作華萊士樹每處的輸入。第(3)部分為保留進位加法器搭建的華萊士樹結(jié)構(gòu),是64 個1 位的華萊士樹的集合。由于兩個64 位的由符號數(shù)相乘得到的最大數(shù)值不超過126 位,所以最高位處的華萊士樹向高位的進位直接忽略。
除法器不能像前面乘法器一樣使用多個加法器加速乘法的運算,因為除法的每次迭代都需要知道前面減法結(jié)果的符號,而乘法卻可以直接生成64 個部分積。所以除法器采用循環(huán)移位的方式進行設(shè)計。除法計算流程如圖6 所示。
(3)加強對農(nóng)業(yè)循環(huán)經(jīng)濟的相關(guān)延伸,促進多種模式的循環(huán)發(fā)展。目前很多地區(qū)發(fā)展循環(huán)經(jīng)濟的模式比較單一,僅僅停留在單項或者雙向流通的過程之中,應(yīng)該加大宣傳力度,推廣新的發(fā)展模式,讓更多的農(nóng)民了解并使用,從而實現(xiàn)農(nóng)業(yè)循環(huán)經(jīng)濟的更加充分發(fā)展。

圖6 除法計算流程
根據(jù)上述流程,最終實現(xiàn)的除法器結(jié)構(gòu)如圖7所示。

圖7 除法器結(jié)構(gòu)
由于在物理實現(xiàn)上存在差異,處理器與內(nèi)存的運行速度一直存在著較大的差距。如果程序有較多地依賴訪存結(jié)果的數(shù)據(jù),那么這個差距會極大地影響處理器的性能。為了減小這一差距對處理器性能的影響,通常將存儲結(jié)構(gòu)劃分為四個不同的層次:高速緩存(Cache)、寄存器、IO、內(nèi)存[11]。在與處理器距離越近的位置采用存儲密度較小的電路,通過犧牲內(nèi)存容量來提升訪問速度。高速緩存就是距離處理器最近的存儲結(jié)構(gòu),通常容量設(shè)計得較小以獲得更快的訪問速度。設(shè)計的Cache模塊結(jié)構(gòu)如圖8所示。

圖8 Cache模塊結(jié)構(gòu)
為了避免共享Cache 產(chǎn)生的結(jié)構(gòu)沖突,設(shè)計了獨立的指令Cache(I-cache)和數(shù)據(jù)Cache(D-cache),I-cache 只用來取指,D-cache只用來訪存。
I-cache 的行內(nèi)偏移(Offset)大小為4 bit,索引(Index)大小為7 bit,標簽(Tag)大小為21 bit。更新方式使用寫直通。采用Tag 和數(shù)據(jù)(Data)同步訪問的形式來降低Cache 命中情況下的執(zhí)行周期數(shù),其中Data Array 采用同步SRAM 實現(xiàn),容量大小為4 KB,Cache Line 的大小為16 byte,路數(shù)為兩路,組索引(Set Index)大小為128 bit。Tag Array 采用寄存器實現(xiàn),路數(shù)為兩路,Set Index 大小為128 bit,內(nèi)容為大小為1 bit的有效位(Valid)以及大小為21 bit的Tag。
D-cache 的結(jié)構(gòu)與I-cache 大致相同,但是由于訪存的需要,D-cache 采用了寫回的更新方式,所以在Tag Array中還含有1 bit大小的臟塊(Dirty)用來標識Cache Line 是否被修改。
完成處理器的硬件部分設(shè)計后,要想讓處理器運行程序還需要完成相應(yīng)的運行環(huán)境設(shè)計。程序的運行都需要運行環(huán)境的支持,包括加載、銷毀程序以及提供程序運行時的各種動態(tài)鏈接庫等。本設(shè)計中運行環(huán)境主要包括基礎(chǔ)環(huán)境以及輸入輸出兩部分。其中,基礎(chǔ)環(huán)境主要負責程序的運行,輸入輸出主要負責處理器與外圍設(shè)備的連接。
在處理器運行客戶程序之前,將需要執(zhí)行的程序代碼編譯成可執(zhí)行文件。但是不能使用GCC 的默認選項直接編譯,因為默認選項會根據(jù)Linux 的運行環(huán)境將代碼編譯成運行在Linux 下的可執(zhí)行文件。但此時的處理器并不能為客戶程序提供GNU/Linux的運行環(huán)境,在處理器中無法正確運行上述可執(zhí)行文件。所以,需要采用交叉編譯的方式來生成處理器可運行的文件。
1)計算:作為程序最基本的需求,所有計算相關(guān)的代碼(順序語句、分支、循環(huán)、函數(shù)調(diào)用等)都會被編譯器編譯成功能等價的指令序列,最終在CPU 上執(zhí)行。
2)內(nèi)存申請:某些程序需要在運行時動態(tài)地申請內(nèi)存來使用,通過實現(xiàn)內(nèi)存的動態(tài)管理相應(yīng)的庫函數(shù)來實現(xiàn)。
3)結(jié)束運行:一般程序都會有結(jié)束運行的時候,通過RISC-V 指令集的Ebreak 指令實現(xiàn)程序結(jié)束的庫函數(shù)。
4)打印信息:輸出是程序的另一個基本需求,通過實現(xiàn)輸出字符的庫函數(shù)putch()來實現(xiàn)。
對于RISC-V 架構(gòu),輸入輸出是通過內(nèi)存映射(Memory Mapping I/O, MMIO)實現(xiàn)的。此外,使用DPI(Direct Programming Interface)-C 機制將硬件電路中寄存器的值傳輸給仿真環(huán)境,使用DPI-C 機制額外的好處就是不用修改硬件電路。
使用通用異步收發(fā)傳輸器(Universal Asynchronous Receiver/Transmitter, UART)來實現(xiàn)處理器與外圍設(shè)備的通信。實時時鐘(Real Time Clock, RTC)用來產(chǎn)生時鐘信號。
采用C語言編寫測試集對RV64I的所有指令進行測試。測試在Linux 環(huán)境下進行,使用虛擬板卡以及開源編譯器Verilator 進行編譯仿真[12]。大致流程為:1)通過運行環(huán)境將測試集內(nèi)的程序加載到虛擬內(nèi)存中,CPU 讀取并運行程序至結(jié)束語句,仿真結(jié)果顯示“pass!”表明測試程序運行成功;2)最后遍歷測試集內(nèi)的所有程序,完成對處理器以及運行環(huán)境的功能測試。圖9 給出了乘法指令的仿真波形圖,此時運行的指令為MUL 乘數(shù)X 與乘數(shù)Y 為0x11f42438,最終計算結(jié)果res 為0x14255a47fdfcc40,可以看出,在加入了Booth 算法以后,乘法指令的運行過程得到簡化,并且運行結(jié)果正確。圖10 給出了除法指令的仿真波形圖,運行的指令為DIV,除數(shù)divisor 為2,被除數(shù)dividend 為0x375F00,最終結(jié)果為0x1baf80。圖11給出了測試集內(nèi)包括上述指令的所有程序的仿真測試結(jié)果。這些仿真結(jié)果表明,CPU 通過了測試集內(nèi)的所有測試程序的測試。

圖9 乘法指令仿真波形圖

圖10 除法指令仿真波形圖

圖11 仿真測試結(jié)果
這里用CoreMark 測試程序來進行性能測試。CoreMark 測試程序包含了大部分處理器工作時的常用算法,所以其測試結(jié)果在處理器領(lǐng)域中是處理器性能評價的重要參考指標[13]。其跑分的計算方法為:首先,使用宏定義ITERATIONS 參數(shù)確定程序循環(huán)執(zhí)行的次數(shù),然后除以程序運行的總時長Total time,計算出來的每秒執(zhí)行CoreMark 程序的循環(huán)次數(shù),再除以處理器的主頻,就得出CoreMark 程序的跑分,單位是CoreMark/MHz。CoreMark 測試程序的測試結(jié)果如圖12 所示。可以計算出最終得分為:

圖12 CoreMark測試程序的運行結(jié)果
(Iteration/Total time)/50M=3.38 CoreMark/MHz。
表1 給出了處理器關(guān)鍵技術(shù)對照情況。相比較而言,本處理器性能優(yōu)于大多數(shù)5 級開源流水線處理器。

表1 處理器關(guān)鍵技術(shù)對照表
課題組基于RISC-V 指令集搭建了5 級流水線處理器,從分支跳轉(zhuǎn)指令的處理、乘除法運算處理、冒險問題的處理、存儲結(jié)構(gòu)等方面優(yōu)化處理器性能,并搭建了針對RISC-V 指令集的運行環(huán)境。最終完成的處理器及其運行環(huán)境在虛擬環(huán)境下通過了功能驗證并完成了CoreMark 性能測試。結(jié)果表明,該處理器在性能方面優(yōu)于大部分同流水線深度的開源項目。該處理器還可以從超標量、多發(fā)射、動態(tài)指令預(yù)測、非對齊指令等方面繼續(xù)優(yōu)化,從而進一步提升性能。