基于FPGA的M2M異構(gòu)虛擬化系統(tǒng)(二)
exe_mem_reg
本模塊完成EXE和MEM兩個(gè)階段之間的信號(hào)流水。本模塊的時(shí)序圖如下。
圖 22 exe_mem_reg時(shí)序圖
mem_stage
本模塊完成對(duì)數(shù)據(jù)Cache的讀寫。模塊的對(duì)外接口符合Wishbone總線標(biāo)準(zhǔn)。本模塊的主要時(shí)序如下圖。
圖 23 mem_stage時(shí)序圖
mem_wb_reg
本模塊完成MEM和WB兩個(gè)階段之間的信號(hào)流水。本模塊的時(shí)序圖如下。
圖 24 mem_wb_reg時(shí)序圖
wb_stage
本模塊完成寫回指令的寄存器堆修改操作。本模塊的時(shí)序圖如下。
圖 25 wb_stage時(shí)序圖
except
本模塊完成流水線中的中斷及異常處理。為了完成精確中斷,即產(chǎn)生異常的指令前已經(jīng)在流水線中的指令完成執(zhí)行,而在異常指令后的指令不允許完成執(zhí)行(不修改CPU狀態(tài)),才能響應(yīng)異常。因此,在實(shí)現(xiàn)精確中斷時(shí),需要對(duì)流水線中的指令進(jìn)行跟蹤,所有的異常或中斷信號(hào)將延遲到流水線的特定階段(Writeback)進(jìn)行響應(yīng),并且對(duì)不同類型的異常信號(hào),中斷程序的返回地址不同。本模塊的主要時(shí)序圖如下。
圖 26 except時(shí)序圖
4.1.2.2Cache模塊詳細(xì)設(shè)計(jì)方案
功能描述
本模塊實(shí)現(xiàn)指令Cache和數(shù)據(jù)Cache。其中,指令Cache和數(shù)據(jù)Cache的映射策略都采用直接映射方式。指令Cache只讀,數(shù)據(jù)Cache的寫策略為寫通過(主存和Cache里的數(shù)據(jù)時(shí)鐘保持一致)。
- 子模塊列表
|
|
|
Instruction Cache top |
|
Data Cache top |
詳細(xì)設(shè)計(jì)
ic_top
本模塊的時(shí)序圖如下。
圖 27 ic_top時(shí)序圖
4.1.2.3動(dòng)態(tài)翻譯硬件模塊詳細(xì)設(shè)計(jì)方案
功能描述
為了提高動(dòng)態(tài)翻譯效率,我們?cè)贑PU中增加了硬件加速模塊。動(dòng)態(tài)翻譯硬件加速包括以下部分:
在QS-I CPU的ALU模塊中增加x86 flag寄存器(MIPS架構(gòu)中沒有flag標(biāo)志寄存器),軟件可通過mtc0,mfc0兩條指令來訪問flag寄存器。這樣x86的算術(shù)邏輯或比較指令(如add, sub, cmp等),以及條件跳轉(zhuǎn)指令(如ja, jb, jg等)有效地得到了硬件支持,使得軟件的翻譯效率大大提高。
在QS-I CPU外增加了JLUT(Jump address Lookup Table),即跳轉(zhuǎn)地址查表。通過CAM(Content Address Memory)的硬件支持,跳轉(zhuǎn)指令的翻譯效率將比完全基于軟件的翻譯方式提高一個(gè)數(shù)量級(jí)。在QS-I中將新增4條用戶指令campi, ramri, camwi, camwi用于軟件對(duì)JLUT的訪問。
- 子模塊列表
|
|
|
|
|
JLUT top |
|
|
SPC is stored in CAMs, and it will take less than two clock cycles to get address of the CAMs content specified. |
|
|
TPC is stored in ubiquitous RAMs. |
詳細(xì)設(shè)計(jì)
如下方的JLUT詳細(xì)設(shè)計(jì)圖所示,JLUT模塊與QS-I CPU之間通過campi, camwi, ramwi, ramwi四條指令進(jìn)行交互。
campi用于CAM的查表,camwi用于CAM的寫操作,ramwi用于RAM的寫操作,RAMRI用于RAM的讀操作。
4條指令的格式如下。
Instruction |
Format |
Usage |
campi |
opcode, rs, 5’h0, rd, 5’h0, func |
campi rd, rs |
camwi |
opcode, rs, rt, 5’h0, 5’h0, func |
camwi rt, rs |
ramwi |
opcode, rs, rt, 5’h0, 5’h0, func |
ramwi rt, rs |
ramri |
opcode, rs, 5’h0, rd, 5’h0, func |
ramri rd, rs |
圖 28 JLUT詳細(xì)設(shè)計(jì)圖
4.2.動(dòng)態(tài)翻譯詳細(xì)設(shè)計(jì)方案
二進(jìn)制翻譯技術(shù)能夠把一種體系結(jié)構(gòu)的二進(jìn)制程序翻譯成另一種體系結(jié)構(gòu)的二進(jìn)制程序,以在新的體系結(jié)構(gòu)下運(yùn)行。二進(jìn)制翻譯主要有三類:解釋執(zhí)行、靜態(tài)翻譯及動(dòng)態(tài)翻譯。
在系統(tǒng)總體框架圖中,二進(jìn)制翻譯層可運(yùn)行不同的翻譯程序,以在不同的體系之間進(jìn)行轉(zhuǎn)換,如x86到MIPS、ARM到MIPS、x86到ARM等。本部分挑選了8086到MIPS的動(dòng)態(tài)翻譯作為實(shí)現(xiàn)原型。
4.2.1.二進(jìn)制翻譯介紹
二進(jìn)制翻譯可以分為三大類:解釋執(zhí)行、靜態(tài)翻譯和動(dòng)態(tài)翻譯。
解釋執(zhí)行的流程是:取指、解析、執(zhí)行。它對(duì)源機(jī)器代碼進(jìn)行實(shí)時(shí)解釋并執(zhí)行,然后繼續(xù)下一條指令。系統(tǒng)不對(duì)已解釋的指令進(jìn)行保存或緩存。在這個(gè)框架下,不能對(duì)代碼進(jìn)行優(yōu)化。這種翻譯技術(shù)能取得高度兼容性,但執(zhí)行效率很低。
靜態(tài)翻譯是先將源可執(zhí)行文件轉(zhuǎn)換成目標(biāo)機(jī)器可執(zhí)行文件,然后運(yùn)行在目標(biāo)機(jī)器上。這是離線翻譯,因此有充足的時(shí)間對(duì)代碼進(jìn)行優(yōu)化,以提高代碼的執(zhí)行效率。但靜態(tài)翻譯很難做到正確性,如代碼的自修改問題,執(zhí)行過程中有些跳轉(zhuǎn)值只能在運(yùn)行時(shí)才能獲知等問題。
解釋執(zhí)行是實(shí)時(shí)翻譯,靜態(tài)翻譯是離線翻譯,動(dòng)態(tài)翻譯就像是兩者的折中。它不像解釋執(zhí)行那樣對(duì)每條指令進(jìn)行翻譯并馬上執(zhí)行,也不像靜態(tài)翻譯那樣將指令完全翻譯好之后才執(zhí)行。它每次對(duì)一個(gè)基本塊進(jìn)行翻譯并執(zhí)行,然后取另一個(gè)塊。一個(gè)基本塊一般包含多條算術(shù)類型指令,最后是一條控制流(Control Flow)類型指令。已翻譯的塊可進(jìn)行緩存或保存。動(dòng)態(tài)翻譯只對(duì)將要執(zhí)行的代碼進(jìn)行翻譯,且能很好地解決代碼自修改問題。
4.2.2.二進(jìn)制翻譯策略選擇
本項(xiàng)目采取的是軟硬協(xié)同動(dòng)態(tài)翻譯策略,將源二進(jìn)制代碼進(jìn)行翻譯,當(dāng)遇到控制流類型指令,如跳轉(zhuǎn)指令,系統(tǒng)調(diào)用等,翻譯過程掛起,將已翻譯的指令序列作為一個(gè)基本塊,然后運(yùn)行基本塊。當(dāng)基本塊執(zhí)行完以后,會(huì)跳到下一處執(zhí)行。若下一處已翻譯過,則繼續(xù)執(zhí)行,否則暫停執(zhí)行以進(jìn)行翻譯,如此過程循環(huán)。完整的流程如下圖所示。
圖 29 x86程序翻譯執(zhí)行流程
基本塊執(zhí)行時(shí)有硬件模塊輔助,如圖 12所示。硬件模塊管理跳轉(zhuǎn)緩存,緩存的基本項(xiàng)為對(duì)。程序執(zhí)行到跳轉(zhuǎn)指令時(shí),程序向跳轉(zhuǎn)緩存發(fā)送SPC,得到相應(yīng)的TPC,再跳至TPC繼續(xù)執(zhí)行生成塊。簡(jiǎn)單的示例如圖 30所示。源程序從塊A開始執(zhí)行,到末尾時(shí),需要跳轉(zhuǎn)到塊C。翻譯后執(zhí)行,執(zhí)行完塊A’后將要跳轉(zhuǎn),此時(shí)的跳轉(zhuǎn)地址是SMEM中地址,即SPC,要轉(zhuǎn)換成相應(yīng)的TPC,該TPC就由跳轉(zhuǎn)緩存中尋找。
圖 30 SMEM與TMEM的映射
4.2.3.8086程序的載入
首先,由系統(tǒng)向服務(wù)器發(fā)送命令,命令格式為x86 *.com,它包含在自定義傳輸協(xié)議中,類型碼為86,要求.com文件僅使用一個(gè)段,大小限制為64KB。服務(wù)器找到所指定的文件,并將其傳送給系統(tǒng),系統(tǒng)將其存放在內(nèi)存中。至此,完成8086可執(zhí)行程序的載入。
4.2.4.標(biāo)志寄存器處理
8086中有個(gè)標(biāo)志寄存器FLAGS,而MIPS中沒有與之相對(duì)應(yīng)的標(biāo)志寄存器,解決辦法有二,軟件模擬實(shí)現(xiàn)或硬件提供支持。
軟件模擬指的是,當(dāng)一條8086指令執(zhí)行后,會(huì)影響哪些標(biāo)志位,然后用軟件方法將其模擬出來,使兩者的結(jié)果一致。如執(zhí)行add ax, bx對(duì)溢出位的影響。模擬時(shí),將ax移到MIPS的$t0寄存器的低16位,將bx移到MIPS的$t1寄存器的低16位,然后對(duì)$t0和$t1做加法,結(jié)果放到$t0,相對(duì)應(yīng)的指令為add $t0, $t0, $t1。結(jié)果是否溢出則要查看$t0的第16位。最后,還要將溢出位存放至標(biāo)志寄存器的對(duì)應(yīng)位。這中間還要涉及移位運(yùn)算、位運(yùn)算等,所需代價(jià)很大,但有個(gè)好處是無需對(duì)硬件平臺(tái)做改動(dòng),使硬件平臺(tái)更為純粹。
若采用提供硬件支持,那么硬件平臺(tái)需稍做修改,增加一個(gè)類FLAGS寄存器。仍以上面的add ax, bx為例。將ax、bx分別放到$t0、$t1的高16位,然后進(jìn)行相加,是否溢出的結(jié)果會(huì)自動(dòng)保存到新添加的類FLAGS寄存器里,因而軟件層面無需再做處理。此種做法,增加了硬件工作,但大大簡(jiǎn)化了軟件的操作。8086的FLAGS有多個(gè)標(biāo)志位,若要完全實(shí)現(xiàn),那么對(duì)本身的硬件平臺(tái)改動(dòng)會(huì)比較大,因此,我們只選擇了其中幾個(gè)進(jìn)行實(shí)現(xiàn),如Z、O、C、S等。
4.2.5.寄存器映射
MIPS有32個(gè)通用寄存器,從0號(hào)到31號(hào),每個(gè)寄存器為32位。8086的通用寄存器有8個(gè):AX、CX、DX、BX、SP、BP、SI和DI。這8個(gè)通用寄存器都是16位,AX、CX、DX和BX還可以分成兩個(gè)8位寄存器,高8位和低8位,如AX可分為AH和AL。此外,段寄存器有CS、DS、ES和SS,都是16位。還有IP寄存器和FLAGS寄存器,也都是16位。
因?yàn)镸IPS的寄存器數(shù)量比8086的寄存器多,可以采用直接映射,一個(gè)8086寄存器對(duì)應(yīng)一個(gè)MIPS寄存器,而不需要對(duì)寄存器進(jìn)行置換,簡(jiǎn)化了寄存器的管理。[!--empirenews.page--]
Table 1 寄存器映射
8086 |
MIPS |
AX |
s0(R16) |
CX |
s1(R17) |
DX |
s2(R18) |
BX |
s3(R19) |
SP |
s4(R20) |
BP |
s5(R21) |
SI |
s6(R22) |
DI |
s7(R23) |
CS |
t4(R12) |
DS |
t5(R13) |
ES |
t6(R14) |
SS |
t7(R15) |
IP |
t8(R24) |
除了這些寄存器外,還給8086提供了3個(gè)輔助寄存器,t0、t1和t2。t0一般作為參數(shù)寄存器或返回值寄存器,t1和t2一般作為臨時(shí)寄存器。這幾個(gè)寄存器的存在有很大的必要性。
4.2.6.上下文切換
從翻譯流程可以看出,整個(gè)過程有兩個(gè)運(yùn)行環(huán)境。翻譯過程是在MIPS環(huán)境中,執(zhí)行則是在MIPS的8086虛擬環(huán)境中。但硬件只有MIPS的一套寄存器,8086的寄存器是映射到MIPS的寄存器。在切換運(yùn)行環(huán)境時(shí),要先保存當(dāng)前寄存器組,再載入新的寄存器組。方式可以有兩種。一種是將寄存器組保存至堆棧,另一種是將寄存器組保存至內(nèi)存。這里將其保存至內(nèi)存,因?yàn)檫@更利于調(diào)試,可將寄存器值調(diào)出來查看。
上下文切換實(shí)現(xiàn):
/**
* context switch
* @param type - 0:從x86切換到mips,other:從mips切換到x86
*/
void _contextSwitch(int type) {
if (type == _MIPS_TYPE) {
_saveRegisters(_X86_TYPE);
_loadRegisters(_MIPS_TYPE);
} else {
_saveRegisters(_MIPS_TYPE);
_loadRegisters(_X86_TYPE);
}
}
上下文切換的流程及實(shí)現(xiàn)如下。
圖 34 寄存器組切換
4.2.7.字節(jié)順序與邊界對(duì)齊問題
字節(jié)序(Byte Order)一般有兩種:大端序(big-endian)和小端序(little-endian)。大端序是將最高有效字節(jié)(MSB,Most Significant Byte)存放至低地址,小端序則是將最低有效字節(jié)(LSB,Least Significant Byte)存放至低地址。MIPS采用的是大端序,而8086使用的是小端序。因此,在二進(jìn)制翻譯中,必須要處理這種差異。
8086在訪問內(nèi)存時(shí),如果是字節(jié)操作,那么翻譯時(shí),可以使用對(duì)應(yīng)的MIPS字節(jié)操作指令,如lb,sb等。字節(jié)的操作不會(huì)有字節(jié)序問題,處理多字節(jié)時(shí)會(huì)有字節(jié)序問題。8086訪問多字節(jié)時(shí),不能使用相應(yīng)的MIPS指令,而應(yīng)拆分讀取,最后再拼湊。過程如圖 35所示。
圖 35 字節(jié)序問題解決
4.2.8.堆棧處理
MIPS有一個(gè)堆棧寄存器$sp,8086則是使用一個(gè)堆棧段寄存器SS與一個(gè)堆棧指針寄存器SP,實(shí)際地址為段基址加偏移地址。本設(shè)計(jì)MIPS和8086各自有自己的堆??臻g。8086堆棧操作對(duì)象有寄存器、立即數(shù)、內(nèi)存等。這里以寄存器壓棧為例,出棧情況類似。壓棧時(shí),首先要計(jì)算出絕對(duì)地址,然后將寄存器保存。計(jì)算地址方法:(SS << 4) + IP,是一個(gè)20位地址,尋址1M。push AX時(shí),是對(duì)一個(gè)16位寄存器壓棧,由上面的字節(jié)序分析可知,要拆分成兩個(gè)字節(jié)壓棧。這里為了簡(jiǎn)便,直接對(duì)32位MIPS寄存器進(jìn)行壓棧,因?yàn)?086寄存器只占用MIPS寄存器的高16位。這里,絕對(duì)地址是放在輔助寄存器里的,因此要對(duì)該輔助寄存器進(jìn)行保存,以防止要壓棧的寄存器就是該輔助寄存器而造成壓棧失敗。我們是將該輔助寄存器保存在MIPS的堆棧中。
4.2.9.操作數(shù)分析
8086的操作數(shù)類型比較多樣,有寄存器、立即數(shù)、內(nèi)存等。每個(gè)類型的位寬還可以變化,有8位、16位之分。MIPS比較規(guī)則,寄存器為32位,立即數(shù)為16位。兩者之間的轉(zhuǎn)換是翻譯的重要方面。
寄存器如果是16位,則可以直接映射到對(duì)應(yīng)MIPS寄存器的高16位。如果是8位寄存器,不管是高8位還是低8位,一般都要移出至輔助寄存器的高8位。移到高8位的原因是為了運(yùn)算時(shí)能產(chǎn)生正確的標(biāo)志位。運(yùn)算完后再將運(yùn)算結(jié)果搬回8086寄存器。
立即數(shù)有3種情況:8位,16位及由8位符號(hào)擴(kuò)展來的16位立即數(shù)。8位及8位符號(hào)擴(kuò)展可直接以字節(jié)訪問內(nèi)存得到,16位立即數(shù)則要注意字節(jié)序問題,上面已分析過。
內(nèi)存方面涉及到內(nèi)存地址及所指向的內(nèi)存內(nèi)容。內(nèi)存地址的拼接會(huì)在下文詳細(xì)分析,8086有多種段寄存器與偏移的組合方式。取內(nèi)存內(nèi)容時(shí)也會(huì)遇到字節(jié)序的問題。
操作數(shù)類型示意圖如下。
圖 36 操作數(shù)類型
4.2.10.二進(jìn)制翻譯及代碼生成
這部分主要解析8086指令,將其拆分,并生成相應(yīng)的MIPS代碼。在具體解析指令之前,先研究下8086二進(jìn)制機(jī)器碼的特點(diǎn),抽出公共特征。以下幾張表均來自于[6]。
Table 2 oo修飾位說明
oo |
功能 |
00 |
如果mmm=110,那么位移量在操作碼后面,否則沒有使用位移量 |
01 |
操作碼后面是8位有符號(hào)的位移量 |
10 |
操作碼后面是16位有符號(hào)的位移量 |
11 |
mmm指定一個(gè)寄存器而不是一種尋址方式 |
Table 3 16位寄存器/存儲(chǔ)器(mmm)字段描述
mmm |
16位寄存器 |
000 |
DS:[BX+SI] |
001 |
DS:[BX+DI] |
010 |
SS:[BP+SI] |
011 |
SS:[BP+DI] |
100 |
DS:[SI] |
101 |
DS:[DI] |
110 |
SS:[BP] |
111 |
DS:[BX] |
Table 4寄存器字段(rrr)的分配
rrr |
w=0 |
w=1 |
000 |
AL |
AX |
001 |
CL |
CX |
010 |
DL |
DX |
011 |
BL |
BX |
100 |
AH |
SP |
101 |
CH |
BP |
110 |
DH |
SI |
111 |
BH |
DI |
Table 5 對(duì)段寄存器的寄存器字段(rrr)的分配
rrr |
段寄存器 |
000 |
ES |
001 |
CS |
010 |
SS |
011 |
DS |
對(duì)oo字段的解析如下。當(dāng)oo為00的時(shí),mmm索引存儲(chǔ)器有點(diǎn)變化。當(dāng)mmm為110b時(shí),并不是按照表3來索引SS:[BP],而是DS+16位偏移量,其余情況則是按照表3,偏移量為0。當(dāng)oo為01時(shí),偏移量為8位有符號(hào)數(shù)。當(dāng)oo為10時(shí),偏移量為16位有符號(hào)數(shù)。根據(jù)oo和mmm來計(jì)算內(nèi)存地址的過程如下。
圖 37 計(jì)算內(nèi)存地址
rrr字段的實(shí)現(xiàn)。當(dāng)w為1時(shí),rrr對(duì)應(yīng)的是16位寄存器,可直接映射到MIPS寄存器的索引值。當(dāng)w為0時(shí),rrr對(duì)應(yīng)的是8位寄存器,需要對(duì)其重新標(biāo)號(hào),將AL作為0開始標(biāo)號(hào)。代碼實(shí)現(xiàn)中,寄存器對(duì)應(yīng)的宏如下,其中包含了段寄存器。
#define _X86_AL 0
#define _X86_CL 1
#define _X86_DL 2
#define _X86_BL 3
#define _X86_AH 4
#define _X86_CH 5
#define _X86_DH 6
#define _X86_BH 7
#define _X86_AX _MIPS_S0 // 0 16
#define _X86_CX _MIPS_S1 // 1 17
#define _X86_DX _MIPS_S2 // 2 18
#define _X86_BX _MIPS_S3 // 3 19
#define _X86_SP _MIPS_S4 // 4 20
#define _X86_BP _MIPS_S5 // 5 21
#define _X86_SI _MIPS_S6 // 6 22
#define _X86_DI _MIPS_S7 // 7 23
#define _X86_CS _MIPS_T4 // 8 12
#define _X86_DS _MIPS_T5 // 9 13
#define _X86_ES _MIPS_T6 // 10 14
#define _X86_SS _MIPS_T7 // 11 15
#define _X86_IP _MIPS_T8 // 12 24
8086二進(jìn)制機(jī)器碼的主要字段已解析,還有個(gè)別字段。
Table 6 其他字段
字段 |
說明 |
d |
運(yùn)算方向 |
w |
是否為字 |
s |
是否符號(hào)擴(kuò)展 |
disp |
位移量 |
data |
數(shù)據(jù),如立即數(shù) |
翻譯時(shí)將二進(jìn)制機(jī)器碼拆分成各個(gè)字段,然后根據(jù)其語義生成對(duì)應(yīng)的MIPS指令。一條8086指令一般要翻譯成多條MIPS指令,其中一條為核心指令,其余為輔助指令。如加法指令中,將內(nèi)容移出至輔助寄存器都是輔助指令,最終的加法是核心指令。一般,核心指令對(duì)應(yīng)MIPS的一條指令,因此,可將其抽象出來,實(shí)現(xiàn)函數(shù)如下。
/**
* x86運(yùn)算與mips對(duì)應(yīng)關(guān)系
*/
int _keyGenerate(int type, int rt, int rd, int **target) {
int instruction;
switch (type) {
case _X86_ADD0:
instruction = _gen_add(rd, rt, rd);
break;
case _X86_AND0:
instruction = _gen_and(rd, rt, rd);
break;
case _X86_OR0:
instruction = _gen_or(rd, rt, rd);
break;
case _X86_SUB0:
instruction = _gen_sub(rd, rt, rd);
break;
case _X86_XOR0:
instruction = _gen_xor(rd, rt, rd);
break;
case _X86_MOV3:
instruction = _gen_add(rt, _MIPS_ZERO, rd);
break;
case _X86_CMP0:
instruction = _gen_sub(rd, rt, _MIPS_ZERO);
break;
default:
break;
}
*(*target)++ = instruction;
return instruction;
}