国产精品天干天干,亚洲毛片在线,日韩gay小鲜肉啪啪18禁,女同Gay自慰喷水

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

【轉(zhuǎn)】 從零開始制作自己的指令集架構(gòu)

2023-07-01 15:25 作者:Bili_394329148  | 我要投稿

?從零開始制作自己的指令集架構(gòu)

王金戈

微軟中國 軟件研發(fā)

本文,我們要做一件大膽的事情,從零開始實(shí)現(xiàn)一個(gè)全新的指令集架構(gòu),以此深入理解處理器的工作原理。

指令集發(fā)展歷史概況

開始我們的創(chuàng)造之旅前,先了解一下歷史上的指令集架構(gòu)都有哪些。

一個(gè)處理器支持的指令和指令的字節(jié)級(jí)編碼稱為它的指令集架構(gòu)(Instruction Set Architecture, ISA)。

最為我們熟知的就是x86架構(gòu),因?yàn)槲覀內(nèi)粘K玫膫€(gè)人電腦就采用了x86架構(gòu)的處理器。目前世界上最大的兩個(gè)處理器制造商Intel和AMD都有基于x86架構(gòu)的一系列產(chǎn)品。從Intel i386處理器開始,x86架構(gòu)進(jìn)入32位時(shí)代,稱為IA32架構(gòu)(Intel Architecture 32bit)。后來,32位也不能滿足我們的需求了,Intel開始進(jìn)軍64位處理器領(lǐng)域,提出IA64架構(gòu)。但是,這個(gè)架構(gòu)并不是我們現(xiàn)在在用的64位處理器,而是一個(gè)與x86完全無關(guān)的新的處理器架構(gòu),不保持向后兼容。雖然可以實(shí)現(xiàn)很高的性能,但是由于兼容性不好,市場反應(yīng)冷淡。于此同時(shí),AMD公司抓住機(jī)會(huì),率先提出了x86-64處理器架構(gòu),支持64位的同時(shí)保持向后兼容,一舉在與Intel的市場競爭中占據(jù)了主動(dòng)權(quán)。當(dāng)然,Intel也不會(huì)執(zhí)迷不悟,他們果斷放棄了IA64,開始轉(zhuǎn)向x86-64架構(gòu),并逐步收回喪失的市場份額。后來,雖然AMD將自己的架構(gòu)命名為AMD64,Intel將自己的架構(gòu)命名為Intel64,但人們?nèi)匀涣?xí)慣性地將它們統(tǒng)稱為x86-64。

Y86指令集

為了致敬偉大的x86指令集架構(gòu),我們將自己的指令集架構(gòu)命名為Y86。其實(shí)呢,Y86的設(shè)計(jì)理念完全借鑒x86,相當(dāng)于一個(gè)簡化的x86架構(gòu)。

要想從頭設(shè)計(jì)一個(gè)指令集架構(gòu),需要先規(guī)定指令集和指令集編碼,然后將每個(gè)指令劃分為幾個(gè)階段分步執(zhí)行,每個(gè)階段只需要做簡單的一兩項(xiàng)工作,之后,將硬件設(shè)備結(jié)合適當(dāng)?shù)倪壿嬰娐穼?shí)現(xiàn)指令每個(gè)階段的工作。下面我們?cè)敿?xì)講解具體的實(shí)現(xiàn)過程。

指令集及其編碼

對(duì)于一個(gè)簡易的指令集來說,不需要太多的指令,能實(shí)現(xiàn)基本的數(shù)據(jù)轉(zhuǎn)移和流程控制就夠了。下圖列出了Y86指令集中包含的所有指令,以及每個(gè)指令的編碼。

這些都是非?;镜闹噶?,不過看起來有些奇怪,這是因?yàn)槲覀儼褁86中的movl指令替換成了四個(gè)獨(dú)立的指令rrmovlirmovl、rmmovlmrmovl,每個(gè)指令指明了操作數(shù)的來源,這樣就避免了各種尋址方式的麻煩。

可以看到,各個(gè)指令的長度從1字節(jié)到6字節(jié)不等,這樣編碼可以減少程序代碼占用的空間。第1個(gè)字節(jié)的高4位作為指令編碼,用來區(qū)分不同的指令,低4位要么是0,要么是fnfn稱為功能代碼,用來區(qū)分不同的操作。如下圖所示,不同的功能碼在不同的指令中有不同的含義。在運(yùn)算指令中,分別代表加、減、與和異或;在分支跳轉(zhuǎn)指令中,分別代表不同的跳轉(zhuǎn)條件;在條件轉(zhuǎn)移指令中,分別代表不同的轉(zhuǎn)移條件。

第2個(gè)字節(jié),對(duì)于大部分指令來說存放的是寄存器標(biāo)識(shí)符,請(qǐng)看下圖:

每個(gè)寄存器與一個(gè)數(shù)字一一對(duì)應(yīng),F(xiàn)代表無寄存器操作數(shù)。

最后,有些指令還包含四個(gè)字節(jié)的立即數(shù)。

舉一個(gè)例子來幫助我們更好地理解指令編碼。例如對(duì)于如下指令

rmmovl %esp, 0x12345(%edx)

對(duì)應(yīng)的編碼為

40 42 45 23 01 00

其中,從左到右,40是指令編碼,42分別是寄存器%esp對(duì)應(yīng)的4和寄存器%edx對(duì)應(yīng)的2,45230100是偏移量0x12345在小端機(jī)器上的表示。

硬件控制語言HCL

處理器的各個(gè)硬件設(shè)備(比如ALU、程序計(jì)數(shù)器)之間通常需要特定功能的邏輯電路來連接,在設(shè)計(jì)階段,我們使用一種結(jié)構(gòu)化的語言來描述這些邏輯關(guān)系。

HCL(Hardware Control Language)是一種類似C的硬件控制語言,用于描述處理器的控制邏輯。

舉一個(gè)簡單的例子,對(duì)于如下所示的組合邏輯電路:

可以用HCL語言表示為

e = (a && !(b||c)) || (!d && !(b||c))

這句話描述了輸出和輸入的邏輯關(guān)系,無論多么復(fù)雜的組合電路,都可以用最基本的與或非門來實(shí)現(xiàn)。HCL在后面將會(huì)有大量的應(yīng)用。

存儲(chǔ)器和時(shí)鐘

細(xì)心的讀者可能會(huì)注意到,上一段話講到“無論多么復(fù)雜的組合電路”。為什么特別強(qiáng)調(diào)組合電路呢,因?yàn)檫€有另一種電路——時(shí)序電路。

大家應(yīng)該都有基本的電路知識(shí),組合電路只是完成了一個(gè)函數(shù)的功能,不同的輸入導(dǎo)致不同的輸出,電路本身并不存儲(chǔ)任何信息。而時(shí)序電路就不一樣了,它可以存儲(chǔ)信息,而且在時(shí)鐘信號(hào)的控制下對(duì)輸入做出反應(yīng)。

接下來,重點(diǎn)來了。在處理器中有兩種存儲(chǔ)設(shè)備:

  • 時(shí)鐘寄存器(簡稱寄存器) 存儲(chǔ)單個(gè)位或字。時(shí)鐘信號(hào)控制寄存器加載輸入值。

  • 隨機(jī)訪問存儲(chǔ)器(簡稱存儲(chǔ)器) 存儲(chǔ)多個(gè)字,由地址選擇讀寫哪個(gè)字。這里所說的存儲(chǔ)器可以分為兩種:處理器的虛擬存儲(chǔ)器系統(tǒng)和寄存器文件。前者是通常意義上的內(nèi)存系統(tǒng),后者才是我們指令集中8個(gè)寄存器標(biāo)識(shí)符對(duì)應(yīng)的通用寄存器。

下圖為寄存器的工作原理,寄存器輸出一直保持在當(dāng)前狀態(tài),直到時(shí)鐘上升沿,新的輸入將成為當(dāng)前的寄存器狀態(tài)。

寄存器文件可以看成這樣一個(gè)功能塊:

它有兩個(gè)讀端口和一個(gè)寫端口,支持讀寫同時(shí)操作。值得注意的是,寄存器文件的讀操作是即時(shí)的,而寫操作是基于時(shí)鐘的。也就是說,讀出的值valA和valB隨時(shí)根據(jù)srcA和srcB的變化而變化,而要寫入的值valW只在clock的上升沿才能寫入。仔細(xì)想想,寄存器文件的讀寫特性好像和寄存器是完全一樣的,只不過是多了一個(gè)選址操作。

指令的分階段執(zhí)行

雖然宏觀上來看,指令已經(jīng)是程序不可分割的基本元素。但在處理器中,一條指令的執(zhí)行還是要分多個(gè)階段,這樣才可以提高硬件的處理效率。在Y86架構(gòu)中,我們將每個(gè)指令的執(zhí)行分為6個(gè)階段。

  1. 取指:從PC中取出當(dāng)前要執(zhí)行的指令,并按照指令編碼對(duì)其分解,得到icode、ifun、rA、rB、valC等值。

  2. 譯碼:根據(jù)rA、rB取出對(duì)應(yīng)寄存器的值valA、valB。

  3. 執(zhí)行:ALU在不同指令下執(zhí)行不同的操作,包括簡單運(yùn)算、地址加減等等,運(yùn)算結(jié)果為valE,運(yùn)算時(shí)會(huì)對(duì)條件碼產(chǎn)生影響。

  4. 訪存:從存儲(chǔ)器讀取數(shù)據(jù)或向存儲(chǔ)器寫入數(shù)據(jù)。讀出的值為valM。

  5. 寫回:將前面生成的結(jié)果寫回寄存器文件。

  6. 更新PC:將PC設(shè)置成下一條指令的地址。

這些步驟現(xiàn)在看起來雜亂無章,不知有何用處。但仔細(xì)分析,可以看到,每個(gè)階段只做與一兩個(gè)硬件相關(guān)的事情,由輸入決定輸出,完全可以在一個(gè)時(shí)鐘周期內(nèi)做完。而各個(gè)階段之間的聯(lián)系就是各種信號(hào)的輸入和輸出,比如,譯碼階段的輸出valA可以作為執(zhí)行階段的輸入,而執(zhí)行階段的輸出又可以作為寫回階段的輸入,這樣就可以用簡單的組合電路把這些硬件單元連接起來,實(shí)現(xiàn)我們需要的功能。

為了大家更清楚地理解各個(gè)階段的作用,我們用一個(gè)例子來詳細(xì)說明。

上圖分別為OPl rA, rBrrmovl rA, rB、irmovl V, rB這三個(gè)指令的分階段執(zhí)行過程。在取指階段中,M表示存儲(chǔ)器,M1[PC]表示以PC為基址從存儲(chǔ)器中取出1字節(jié)數(shù)據(jù)。由于各個(gè)指令長短不一,因此取指階段做的事情也不盡相同。在該階段最后,會(huì)計(jì)算出PC的新值valP。譯碼階段是從寄存器文件中取出寄存器的值,用R[rA]來表示寄存器rA的值。執(zhí)行階段對(duì)于OPl指令來說會(huì)設(shè)置狀態(tài)碼CC,而后兩個(gè)指令則不會(huì)對(duì)狀態(tài)碼產(chǎn)生影響。訪存階段在這三個(gè)指令中都沒有涉及。最后的更新PC階段將valP的值賦值給PC。

當(dāng)我讀到這里的時(shí)候,我有很大的疑問:不是說每個(gè)階段只做一件簡單的事情嗎,但是不同的指令在同一個(gè)階段做的事情似乎各不相同。比如剛才的三個(gè)指令,在執(zhí)行階段只有OPl指令會(huì)設(shè)置狀態(tài)碼,而另外兩個(gè)不會(huì),這是為什么?包括書中后面舉的其它例子,更新PC階段并不一定是把valP的值賦值給PC,有些指令比如call和ret,它們會(huì)將valC的值或valM的值賦值給PC,這又是怎么做到的?

大家是否也想到了這些問題呢?很顯然,每個(gè)階段對(duì)不同的指令有不同的響應(yīng)是很自然的事情,不然怎么適應(yīng)各個(gè)指令的不同功能呢。我們前面提到的HCL硬件控制語言,就是要完成這個(gè)任務(wù),控制每個(gè)指令在每個(gè)階段要完成的任務(wù)。

好了,在詳細(xì)說明如何用HCL控制邏輯之前,先給出完整的硬件結(jié)構(gòu)圖。

我們要注意圖中不同顏色的方塊和不同粗細(xì)的線條,它們代表著不同的意思。綠色塊代表基本的硬件單元,比如ALU、寄存器文件、PC,基本上我們都已經(jīng)接觸過。灰色方塊將是我們下一步研究的重點(diǎn),它們是HCL描述的組合邏輯電路,用于連接綠色塊并實(shí)現(xiàn)特定的選擇或邏輯運(yùn)算。白色圓圈并沒有特殊的含義,只是用來標(biāo)識(shí)信號(hào)線的名稱。圖中還有三種線條,粗實(shí)線表示寬度為字長的信號(hào)線,細(xì)實(shí)線表示寬度為1個(gè)字節(jié)或更窄的信號(hào)線,而虛線表示單個(gè)位的信號(hào)線。

圖中從下到上分別是剛才介紹的取指、譯碼(寫回)、執(zhí)行、訪存和更新PC階段。由于譯碼和寫回階段都是對(duì)寄存器文件的操作,因此它們?cè)趫D中畫在了同一個(gè)位置。用圓圈標(biāo)出的信號(hào)就是前文提到的各個(gè)階段產(chǎn)生的中間值,這些值通常在不同指令中擔(dān)任著不同的角色,因此會(huì)出現(xiàn)一個(gè)信號(hào)分叉為兩個(gè)信號(hào)的情況。例如圖中valA產(chǎn)生之后分為兩條線,一條通向ALUB控制邏輯,另一條通向Data控制邏輯。再例如圖中valM產(chǎn)生之后分為兩條線,一條通向New PC控制邏輯,另一條通向寄存器文件的輸入端。我們需要明白的是,一個(gè)信號(hào)分為兩個(gè)信號(hào),意味著兩個(gè)接收端都可以讀取到該信號(hào)的值,但讀取到該值并不意味著使用該值,接收端的控制邏輯決定是否使用該值,下文將會(huì)詳細(xì)敘述。

SEQ的狀態(tài)改變周期

上一張圖的標(biāo)題我沒做解釋,其實(shí)是留了個(gè)疑問。SEQ的意思是Sequential(順序的),“SEQ硬件結(jié)構(gòu)”就是說“順序的硬件結(jié)構(gòu)”或者“硬件結(jié)構(gòu)的順序?qū)崿F(xiàn)”。什么!!難道還有其它方式的實(shí)現(xiàn)?答案是當(dāng)然的,我們留到后面再揭開謎底。SEQ的硬件結(jié)構(gòu)使得指令必須按順序一個(gè)接一個(gè)地執(zhí)行,下一條指令的開始必須晚于上一條指令的結(jié)束。這就導(dǎo)致處理器效率極其低下,因?yàn)橐粋€(gè)指令必須在一個(gè)時(shí)鐘周期內(nèi)通過所有階段,而由于電路延遲的固有因素,通過所有階段需要的時(shí)間存在下限,也就使得時(shí)鐘周期不能無限縮短。然而,為什么一個(gè)指令必須在一個(gè)時(shí)鐘周期內(nèi)通過所有階段呢?

因?yàn)閷?duì)于時(shí)序邏輯電路,比如SEQ中的存儲(chǔ)器、寄存器文件、CC和程序計(jì)數(shù)器,它們只在時(shí)鐘信號(hào)的上升沿寫入數(shù)據(jù)。當(dāng)前個(gè)指令結(jié)束,下個(gè)指令開始的時(shí)候,時(shí)鐘信號(hào)上升沿觸發(fā)這幾個(gè)硬件單元的更新。如果在下一個(gè)時(shí)鐘周期上升沿到來之前,需要更新的新值還沒有產(chǎn)生,這個(gè)指令就相當(dāng)于沒執(zhí)行或執(zhí)行了一半。因此時(shí)鐘周期不能降得太低,否則將造成指令執(zhí)行紊亂。

下圖展示了兩個(gè)指令周期的過程中,由時(shí)鐘控制的各個(gè)硬件單元的狀態(tài)改變。

可以看到,圖中將四個(gè)時(shí)序邏輯電路之外的其它部分作為一個(gè)組合邏輯電路的整體來看待。當(dāng)周期3開始時(shí),組合邏輯電路開始運(yùn)行,直到周期3結(jié)束前,所有結(jié)果都已得出,準(zhǔn)備寫入存儲(chǔ)器等設(shè)備。當(dāng)周期4開始時(shí),存儲(chǔ)器、寄存器文件、CC和程序計(jì)數(shù)器的值被更新,同時(shí),這些新值被組合邏輯電路讀取并開始計(jì)算結(jié)果,如此循環(huán)往復(fù)。因此,每個(gè)時(shí)鐘周期SEQ的狀態(tài)改變一次。

SEQ的各階段實(shí)現(xiàn)

前文給出的SEQ硬件結(jié)構(gòu)圖只是一個(gè)大概的實(shí)現(xiàn),有些細(xì)節(jié)并沒有給出?,F(xiàn)在,我們一個(gè)階段一個(gè)階段地分析SEQ的具體實(shí)現(xiàn)。

取指階段

指令從內(nèi)存中取出后按字節(jié)分為了兩部分:Split和Align。Split又分為icode和ifun。Align分為rA、rB和valC,這些都很容易理解。重點(diǎn)在于PC增加的邏輯。PC增加多少要根據(jù)本條指令的長短來決定,而本條指令的長短又在于指令中是否包含寄存器標(biāo)識(shí),以及是否包含常數(shù)valC,圖中的兩個(gè)組合電路Need valC和Need rigids就是用來做這個(gè)判斷。

以Need rigids為例,它的HCL語言描述如下:

bool need_rigids = ? ?icode in { IRRMOVL, IOPL, IPUSHL, IPOPL, IIRMOVL, IRMMOVL, IMRMOVL };

意即,當(dāng)icode等于括號(hào)中7種指令碼之一時(shí),need_rigids為真。也就是說這7種指令中包含寄存器標(biāo)識(shí)。同理,need_valC也可以用這個(gè)枚舉的方法確定,只需要查前面的指令集編碼表,找到包含valC的指令,放在括號(hào)里面就行了。

當(dāng)need_rigids和need_valC都確定了之后,PC increment將按如下公式計(jì)算新的PC值,其實(shí)就是加上了該條指令的長度:

newPC = oldPC + 1 + need_rigids + 4*need_valC

現(xiàn)在我們明白了,灰色方框代表的組合電路可以用HCL語言來描述。而實(shí)際電路中這些HCL語句將通過綜合成為真正的組合邏輯電路。在這里,HCL是一種很好的抽象,將原理與具體的實(shí)現(xiàn)相分離,方便我們的設(shè)計(jì)。

譯碼和寫回階段

這兩個(gè)階段都與寄存器文件的讀寫相關(guān)。從取指階段得到的信號(hào)icode、rA和rB在這里作為輸入信號(hào),經(jīng)過一些組合電路生成寄存器文件的輸入。我們的目的是,在譯碼階段,對(duì)于那些需要使用特定寄存器的命令,從寄存器文件中取出這些寄存器的值,地址由srcA和srcB來決定,結(jié)果輸出為valA和valB;在寫回階段,將執(zhí)行階段的結(jié)果valE或訪存階段的結(jié)果valM寫回特定的寄存器,寄存器的地址由dstE和dstM來決定。以組合電路srcA為例,它的HCL表述為:

int srcA = [ ? ?icode in { IRRMOVL, IRMMOVL, IOPL, IPUSHL } : rA; ? ?icode in { IPOPL, IRET } : RESP; ? ?1 : RNONE; ? ?#Don't need register];

方括號(hào)類似C語言中的switch語句,當(dāng)?shù)谝粋€(gè)分號(hào)前的條件滿足時(shí)返回rA,后面的兩個(gè)條件不再考慮;否則再判斷第二個(gè)條件是否滿足,滿足則返回RESP;否則返回RNONE,表示不需要讀取寄存器文件。從中可以看出,在譯碼階段,當(dāng)指令為第一個(gè)分號(hào)前的四種時(shí),將讀取rA寄存器的值并放入結(jié)果valA;當(dāng)指令為第二個(gè)分號(hào)前的兩種時(shí),將讀取RESP寄存器的值并放入結(jié)果valA;否則,不必讀取任何寄存器。

與srcA類似的還有srcB、dstE和dstM三個(gè)組合邏輯電路,它們的HCL表述可以從SEQ的硬件結(jié)構(gòu)和指令集編碼中分析得出,不再一一敘述。

執(zhí)行階段

ALU需要兩個(gè)操作數(shù)和一個(gè)alufun信號(hào),alufun信號(hào)用于指明ALU對(duì)兩個(gè)操作符執(zhí)行怎樣的邏輯運(yùn)算(加、減、與、異或)。

以第一個(gè)操作數(shù)aluA為例,它的HCL描述如下:

int aluA = [ ? ?icode in { IRRMOVL, IOPL } : valA; ? ?icode in { IIRMOVL, IRMMOVL, IMRMOVL } : valC; ? ?icode in { ICALL, IPUSH } : -4; ? ?icode in { IRET, IPOPL } : 4; ? ?# Other instructions don't need ALU ];

可以看出,操作數(shù)aluA有時(shí)取valA,有時(shí)取valC,有時(shí)取-4或4,完全決定于指令類型。

alufun信號(hào)的HCL描述如下:

int alufun = [ ? ?icode == IOPL : ifun; ? ?1 : ALUADD; ];

僅當(dāng)指令為IOPL指令(即運(yùn)算指令)時(shí),alufun由ifun決定,其它情況下ALU全部當(dāng)做加法器來使用。這也就不難理解為什么剛才aluA會(huì)取-4或4,因此此時(shí)aluA作為加法器的一個(gè)加數(shù),而另一個(gè)加數(shù)從圖中可以看到只能來自于valB,雖然valB在譯碼階段的HCL我們并沒有給出,不過可以告訴大家valB在這四種情況下的輸出都是RESP。因此對(duì)于ICALL和IPUSH來說是為了讓棧指針esp-4,對(duì)于IRET和IPOPL來說是為了讓棧指針esp+4。

訪存階段

Mem read和Mem write決定當(dāng)前指令對(duì)存儲(chǔ)器是讀操作還是寫操作。Mem addr和Mem data決定讀寫操作的地址和數(shù)據(jù)。以Mem addr為例,HCL描述如下:

int mem_addr = [ ? ?icode in { IRMMOVL, IPUSHL, ICALL, IMRMOVL } : valE; ? ?icode in { IPOPL, IRET } : valA; ? ?# Other instructions don't need address ];

更新PC階段

新的PC值來源可以從valC、valM和valP中選擇,New PC的HCL描述如下:

int new_pc = [ ? ?# Call. Use instruction constant ? ?icode == ICALL : valC; ? ?# Taken branch. Use instruction constant ? ?icode == IJXX && Cnd : valC; ? ?# Completion of RET instruction. Use value from stack ? ?icode == IRET : valM; ? ?# Default. Use incremented PC ? ?1 : valP; ];

流水線的一般原則

到此為止,我們的前奏剛剛落幕,終于要步入正題了。(這個(gè)前奏的確有點(diǎn)長,哈哈。)

在“SEQ的狀態(tài)改變周期”中埋下了一個(gè)伏筆,現(xiàn)在我們來揭開謎底。由于SEQ的時(shí)鐘頻率太低,我們需要想些辦法來提高時(shí)鐘頻率。通??梢韵氲絻煞N途徑,一是縮短每條指令的執(zhí)行時(shí)間,二是讓多條指令同時(shí)執(zhí)行。方法一不可行,因?yàn)槊織l指令的執(zhí)行時(shí)間很難壓縮,這是由電路的固有性質(zhì)決定的。因此只能采用方法二,即流水線技術(shù)。

先來用一個(gè)形象的比喻來形容流水線技術(shù)。有一種帶傳送帶的自助餐廳,食物擺在傳送帶上經(jīng)過顧客,顧客可以隨意取走自己喜歡的食品。如果我們把一盤食物當(dāng)做一條指令,而傳送帶兩旁的顧客當(dāng)做指令執(zhí)行的各個(gè)階段,那么SEQ的實(shí)現(xiàn)就相當(dāng)于每次只往傳送帶上放一盤食物,當(dāng)這盤食物走到傳送帶盡頭后再放下一盤食物,如果餐館真這么做的話顧客恐怕都要餓死了。實(shí)際情況是,食物一盤接一盤地放在傳送帶上,每個(gè)顧客送走這一盤食物馬上迎來下一盤食物,效率大大提高。

處理器架構(gòu)的流水線技術(shù)也是這樣,每個(gè)階段都有一條指令正在執(zhí)行,6個(gè)階段就會(huì)有6條指令同時(shí)執(zhí)行,將吞吐量提高為SEQ時(shí)的6倍。這樣是不是感覺非常給力呢,不過,事情遠(yuǎn)沒有想象中那么簡單,最直接的問題是多個(gè)指令間會(huì)不會(huì)相互干擾?

我們回顧一下SEQ的硬件結(jié)構(gòu)圖,不同階段間經(jīng)常有跨階段的連線,比如取指階段得到的valC直接連接到了更新PC階段的New PC。這在流水線情況下會(huì)出問題,因?yàn)楹竺娴闹噶顣?huì)覆蓋前面指令產(chǎn)生的valC,因此,當(dāng)先前的指令到達(dá)更新PC階段再回頭取valC的值時(shí),已經(jīng)不是當(dāng)初自己在譯碼階段生成的值了。怎么辦呢?

解決方案也很容易想到,把每條指令后面有可能用到的值都保存下來不就行了。相當(dāng)于每個(gè)階段多加一套寄存器,在階段開始時(shí)將這些寄存器的值更新為當(dāng)前指令配套的值。在流水線技術(shù)中,這些插入到各個(gè)階段間的寄存器稱為流水線寄存器。

現(xiàn)在我們的處理器架構(gòu)更新為PIPE-(Pipeline-,減號(hào)表示非最終版本),如下圖所示。

與SEQ相比有兩處變化,一是將更新PC階段和取指階段放在了一起,在取指之前更新PC;二是每兩個(gè)階段間插入了流水線寄存器。這些流水線寄存器是基于時(shí)鐘更新的,每個(gè)時(shí)鐘周期的開始將會(huì)更新這些寄存器中的數(shù)據(jù),相當(dāng)于把當(dāng)前指令的狀態(tài)傳遞到了下一個(gè)階段。

流水線冒險(xiǎn)

現(xiàn)在大功告成了嗎?還沒有。當(dāng)我們仔細(xì)分析PIPE-的時(shí)候我們會(huì)發(fā)現(xiàn)仍然存在一些問題。雖然流水線寄存器隔離了各個(gè)指令之間的數(shù)據(jù)共享,但是多個(gè)指令之間仍然存在依賴,包括兩個(gè)方面:

數(shù)據(jù)依賴:前一條指令寫入的寄存器或存儲(chǔ)器正好是后一條指令需要讀取的寄存器或存儲(chǔ)器。在PIPE-中,當(dāng)后一條指令在譯碼階段讀寄存器的時(shí)候,前一條指令才剛剛到執(zhí)行階段,因此新值還沒有寫入寄存器,如果此時(shí)后一條指令直接讀寄存器的話,讀到的是舊值,這就違反了代碼順序執(zhí)行的規(guī)則。

控制依賴:當(dāng)一條指令是jump、call或return時(shí),下一條指令的地址是無法提前確定的,它依賴于當(dāng)前指令的執(zhí)行結(jié)果。因此流水線很可能需要中斷。

這些依賴可能導(dǎo)致流水線產(chǎn)生計(jì)算錯(cuò)誤,這種現(xiàn)象稱為流水線冒險(xiǎn)。我們先來考慮數(shù)據(jù)冒險(xiǎn)。下圖畫出了一段代碼的分階段執(zhí)行過程。

irmovl $3, %eaxaddl %edx, %eax之間插入了三個(gè)空指令。這樣的話,前者執(zhí)行完寫回階段,后者才開始執(zhí)行譯碼階段,保證了讀取寄存器前已經(jīng)寫入完畢。不發(fā)生數(shù)據(jù)冒險(xiǎn)。

再看下圖。

現(xiàn)在去掉了一個(gè)空指令,情況立馬惡化。指令0x006的寫回階段和指令0x00e的譯碼階段同時(shí)發(fā)生,但由于寫回寄存器的操作直到第7周期的開始才會(huì)生效,因此譯碼階段讀出的值仍然是舊值,出現(xiàn)數(shù)據(jù)冒險(xiǎn)現(xiàn)象。

如果把剩下的兩個(gè)空指令也去掉,結(jié)果可想而知,肯定會(huì)發(fā)生更嚴(yán)重的數(shù)據(jù)冒險(xiǎn),我們?cè)诖瞬辉衮?yàn)證。接下來考慮如何避免數(shù)據(jù)冒險(xiǎn)。

仍然有兩種解決方案:

暫停:與插入nop空指令類似,處理器自動(dòng)向可能發(fā)生數(shù)據(jù)冒險(xiǎn)的代碼間插入bubble,使當(dāng)前正在執(zhí)行的指令暫停一個(gè)時(shí)鐘周期。

如上圖所示,當(dāng)addl指令執(zhí)行到譯碼階段時(shí),檢測到將會(huì)發(fā)生數(shù)據(jù)冒險(xiǎn),于是插入一個(gè)bubble,addl指令在譯碼階段重復(fù)一個(gè)時(shí)鐘周期。

如果把所有nop都去掉,仍然可以用插入bubble的方法解決數(shù)據(jù)冒險(xiǎn),只不過需要插入多個(gè)bubble而已,如下圖所示。

轉(zhuǎn)發(fā):暫停有一點(diǎn)很不好,它會(huì)降低程序執(zhí)行效率,因?yàn)榧尤肓撕芏酂o用的指令,純粹在浪費(fèi)時(shí)間。而轉(zhuǎn)發(fā)可以更充分地利用每一個(gè)周期的時(shí)間。

仍然以剛才的代碼段為例講解轉(zhuǎn)發(fā)如何起作用。

如圖,當(dāng)addl到譯碼階段的時(shí)候,irmovl到寫回階段,由于還沒有寫入寄存器,因此讀取數(shù)據(jù)時(shí)發(fā)生數(shù)據(jù)冒險(xiǎn)。不過,我們可以用一個(gè)巧妙的方法避免這個(gè)冒險(xiǎn)。既然寫回階段需要等到下個(gè)周期開始才能寫入寄存器,那不如直接把要寫入的值轉(zhuǎn)發(fā)給譯碼階段,這樣的話譯碼階段也不需要再從寄存器讀了,直接拿轉(zhuǎn)發(fā)來的值用就行了。

接下來,如果是prog3代碼段呢?

prog3和prog2的區(qū)別在于少了一個(gè)nop指令,這就導(dǎo)致當(dāng)addl到譯碼階段的時(shí)候irmovl指令才到訪存階段。不過似乎對(duì)轉(zhuǎn)發(fā)并沒有影響,因?yàn)閕rmovl指令并不操作內(nèi)存,在下一個(gè)階段將要寫入寄存器的值現(xiàn)在已經(jīng)產(chǎn)生了,就是M_valE(需要注解一下,M_valE的意思是M階段的流水線寄存器中保存的valE的值,請(qǐng)查看前面的PIPE-硬件結(jié)構(gòu)圖),所以直接把M_valE轉(zhuǎn)發(fā)給譯碼階段就行了。

再接下來,如果是prog4代碼段呢?

現(xiàn)在,一個(gè)nop指令都沒有了,irmovl后面緊跟著addl,當(dāng)addl到譯碼階段的時(shí)候irmovl才到執(zhí)行階段??墒橇钊梭@訝的是,仍然可以轉(zhuǎn)發(fā)。首先,我們可以發(fā)現(xiàn)最后需要的寄存器的值就是在執(zhí)行階段經(jīng)過計(jì)算得出的。其次,我們要考慮到執(zhí)行階段得出結(jié)果需要一定時(shí)間,這個(gè)時(shí)間會(huì)不會(huì)導(dǎo)致不能按時(shí)轉(zhuǎn)發(fā)到譯碼階段呢?答案是否定的。因?yàn)樽g碼階段即使很早拿到這個(gè)值,也會(huì)等到下一個(gè)周期開始才把它寫入執(zhí)行階段的流水線寄存器。因此只要在下個(gè)周期開始之前計(jì)算出這個(gè)值就可以了,而這個(gè)條件是永遠(yuǎn)都能得到滿足的。

有沒有感覺到很神奇呢?竟然可以用轉(zhuǎn)發(fā)在不降低程序效率的條件下解決數(shù)據(jù)冒險(xiǎn)問題,簡直太棒了。可是任何事情都不是完美的,剛才的例子只是irmovl后面跟addl且兩者使用同一個(gè)寄存器,而實(shí)際程序有非常多種可能的組合,是不是轉(zhuǎn)發(fā)可以解決所有的問題?我們看下面這個(gè)例子。

prog5代碼段的0x018和0x01e兩行代碼稱為加載/使用數(shù)據(jù)冒險(xiǎn),mrmovl將數(shù)據(jù)從存儲(chǔ)器加載到寄存器%eax,然后緊接著addl使用寄存器%eax的值。仍然用轉(zhuǎn)發(fā),將mrmovl執(zhí)行階段的值轉(zhuǎn)發(fā)給addl,卻得到了錯(cuò)誤的結(jié)果。其實(shí)原因很容易想到,因?yàn)閙rmovl指令需要到訪存階段才能獲取到正確的值并賦值給%eax,因此再從執(zhí)行階段轉(zhuǎn)發(fā)到譯碼階段已經(jīng)完全不可行了。如何解決這個(gè)問題呢?我們可以把暫停和轉(zhuǎn)發(fā)兩種方式結(jié)合起來,先暫停一個(gè)周期,然后mrmovl到了訪存階段就可以把值正確地轉(zhuǎn)發(fā)給addl了。

好了,解決了這么多問題,終于可以給出我們的最終版硬件結(jié)構(gòu)圖了。

比PIPE-增加的內(nèi)容就是為了解決數(shù)據(jù)冒險(xiǎn)問題而增加的轉(zhuǎn)發(fā)電路,轉(zhuǎn)發(fā)的接收方基本都在譯碼階段。

更完善的設(shè)計(jì)

任何事情都講究完美,我們現(xiàn)在得到的PIPE其實(shí)還不夠完美,有些關(guān)鍵細(xì)節(jié)沒有考慮到。

異常處理:處理器非常重要的一個(gè)方面就是異常處理。很多指令執(zhí)行過程中都可能發(fā)生各種各樣的異常,比如訪問存儲(chǔ)器時(shí)無效的地址、無效的指令的編碼等等。當(dāng)程序發(fā)生異常時(shí),應(yīng)該立即中止程序,從外面來看的效果應(yīng)該是正好停在異常發(fā)生的位置:即前面的代碼已經(jīng)完全執(zhí)行,而后面的代碼完全沒有執(zhí)行??雌饋砗芎唵蔚氖虑樵赑IPE中并不那么容易實(shí)現(xiàn),因?yàn)榱魉€中有多個(gè)指令同時(shí)執(zhí)行,如果某個(gè)指令在某個(gè)階段發(fā)生了異常,此時(shí)很可能后面的代碼已經(jīng)執(zhí)行了一部分,要想得到完全沒執(zhí)行的效果,就要消除掉已經(jīng)產(chǎn)生的影響,這需要加強(qiáng)控制邏輯的功能。

控制冒險(xiǎn):上一節(jié)流水線冒險(xiǎn)中我們提到了控制依賴,它會(huì)導(dǎo)致控制冒險(xiǎn)。當(dāng)執(zhí)行到條件跳轉(zhuǎn)指令時(shí),需要做分支預(yù)測,一旦預(yù)測錯(cuò)誤,就需要消除已經(jīng)執(zhí)行的若干條指令,重新執(zhí)行正確分支的指令。當(dāng)執(zhí)行到子函數(shù)返回指令時(shí),需要從存儲(chǔ)器中取出返回地址,因此下一條指令直到訪存階段才能開始執(zhí)行。這些特殊情況都需要我們特殊考慮,并在控制邏輯中實(shí)現(xiàn)。

如果詳細(xì)講解這兩部分的具體實(shí)現(xiàn),又會(huì)花很多篇幅,有興趣的朋友可以訪問這本書的官網(wǎng)進(jìn)一步了解。

與真實(shí)指令集架構(gòu)的差距

本文講述了Y86指令集架構(gòu)的設(shè)計(jì)過程,雖然敘述已經(jīng)足夠粗略,可還是寫了這么長的篇幅。然而如果與真實(shí)的指令集架構(gòu)(比如x86)的復(fù)雜度相比那又真是小巫見大巫了。我們只規(guī)定了一個(gè)非常簡單的指令集,并完成了一個(gè)簡易的實(shí)現(xiàn)。而真實(shí)的指令集會(huì)包含非常多的指令,包括一些多周期的指令,比如浮點(diǎn)數(shù)運(yùn)算指令,這些指令無法在一個(gè)周期內(nèi)完成,因此需要一些額外的硬件單元的支持。Y86中的存儲(chǔ)器被我們看做是理想的存儲(chǔ)單元,我們認(rèn)為數(shù)據(jù)的存取操作都可以在一個(gè)時(shí)鐘周期內(nèi)完成。然而CPU速率與內(nèi)存速率其實(shí)相差上千倍,通常需要多級(jí)緩存構(gòu)成一個(gè)復(fù)雜的存儲(chǔ)器層次結(jié)構(gòu)才能加快存取效率?,F(xiàn)代處理器還采用了多發(fā)射和亂序執(zhí)行技術(shù),已經(jīng)不是Y86中所描述的一個(gè)階段一個(gè)階段地執(zhí)行了,而是多條指令同時(shí)執(zhí)行,而且與它們?cè)诖a中的先后順序無關(guān)。近些年,處理器向多核方向發(fā)展,多個(gè)核具有更強(qiáng)的處理能力,也使指令在代碼級(jí)別的并行執(zhí)行成為潮流。今后,處理器會(huì)采用哪些新技術(shù)我們無從得知,但一定會(huì)變得越來越復(fù)雜。不過萬變不離其宗,理解了處理器和指令集的基本原理,我們可以看透一切,再復(fù)雜的系統(tǒng)也是從基本形式一步步擴(kuò)展得到的,把握核心才是最關(guān)鍵的。

編輯于 2021-05-13 21:07

評(píng)論千萬條,友善第一條


24 條評(píng)論

默認(rèn)

最新

飛天小石頭

這個(gè)是深入理解計(jì)算機(jī)系統(tǒng)書上的內(nèi)容吧?

2021-10-02

王金戈

作者

是的

2021-10-02

mushroom

全文照抄

2021-11-03

黃猩猩

點(diǎn)贊收藏退出 一氣呵成

2020-03-11

Lim Shaw

師兄太牛了,寒武紀(jì)架構(gòu)部需要你

2020-03-16

王金戈

作者

2020-03-16

Micro

大佬您好,請(qǐng)問“取指、譯碼、執(zhí)行、訪存、寫回”這五個(gè)過程和“發(fā)射”這個(gè)過程是什么樣的關(guān)系呢?是先發(fā)射,然后取指、譯碼、執(zhí)行、訪存、寫回嗎?跪謝大佬??!

02-06

Micro

王金戈

奧奧,我打字打錯(cuò)了,是被發(fā)送到不同的運(yùn)算器上。

02-07

Micro

王金戈

奧奧好的,謝謝大佬,我剛才查了下,說是 指令發(fā)射,指的是指令從譯碼階段被派發(fā)到不用運(yùn)算器上進(jìn)入執(zhí)行階段。大佬你覺得這么說對(duì)嘛

02-06

尚戈繼

cs.utexas.edu/users/wit?這篇ppt和博主的博文可以互相補(bǔ)充。

2021-06-20

fine就fine在他喵

因此時(shí)鐘周期不能提得太高,否則將造成指令執(zhí)行紊亂。
原文這一句不太懂

,周期高是指一個(gè)周期時(shí)間長嗎?時(shí)間長的話不是應(yīng)該不容易出問題嘛,

2021-05-13

fine就fine在他喵

王金戈

2021-05-13

王金戈

作者

感謝提醒!原文這里的確寫錯(cuò)了,現(xiàn)在已經(jīng)改正,應(yīng)該是“不能降得太低”。

2021-05-13

待星殘羽

收藏 & 吃灰

2020-05-05

馮曉蔥

這必須贊!?。?!

2020-04-21

笨拙的泥匠

永遠(yuǎn)也沒有看完的的csapp

2020-04-10

深度人工dazed

請(qǐng)問你的csapp是第三版英文的嘛?

2020-03-16

王金戈

作者

深度人工dazed

電子版嗎?沒有

2020-03-17

深度人工dazed

王金戈

請(qǐng)問你有第3版的英文版嘛?

2020-03-16

cache

我還以為

2020-03-11


【轉(zhuǎn)】 從零開始制作自己的指令集架構(gòu)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
论坛| 武义县| 正镶白旗| 水城县| 郴州市| 东宁县| 灵石县| 雷山县| 旺苍县| 承德市| 班玛县| 玛曲县| 仙居县| 通山县| 南平市| 专栏| 娱乐| 福清市| 宣武区| 枣强县| 南华县| 淮滨县| 铁力市| 博野县| 漯河市| 泗阳县| 安图县| 安达市| 廉江市| 寻乌县| 千阳县| 方山县| 白河县| 宁夏| 吉木萨尔县| 汝州市| 东光县| 志丹县| 阿克陶县| 丹东市| 齐齐哈尔市|