ARM體系結(jié)構(gòu)之:流水線
處理器按照一系列步驟來執(zhí)行每一條指令。典型的步驟如下:
① 從存儲器讀取指令(fetch);
② 譯碼以鑒別它是屬于哪一條指令(dec);
③ 從指令中提取指令的操作數(shù)(這些操作數(shù)往往存在于寄存器中)(reg);
④ 將操作數(shù)進行組合以得到結(jié)果或存儲器地址(ALU);
⑤ 如果需要,則訪問存儲器以存儲數(shù)據(jù)(mem);
⑥ 將結(jié)果寫回到寄存器堆(res)。
并不是所有的指令都需要上述每一個步驟,但是,多數(shù)指令需要其中的多個步驟。這些步驟往往使用不同的硬件功能,例如,ALU可能只在第4步中用到。因此,如果一條指令不是在前一條指令結(jié)束之前就開始,那么在每一步驟內(nèi)處理器只有少部分的硬件在使用。
有一種方法可以明顯改善硬件資源的使用率和處理器的吞吐量,這就是當(dāng)前一條指令結(jié)束之前就開始執(zhí)行下一條指令,即通常所說的流水線(Pipeline)技術(shù)。流水線是RISC處理器執(zhí)行指令時采用的機制。使用流水線,可在取下一條指令的同時譯碼和執(zhí)行其他指令,從而加快執(zhí)行的速度。可以把流水線看作是汽車生產(chǎn)線,每個階段只完成專門的處理器任務(wù)。
采用上述操作順序,處理器可以這樣來組織:當(dāng)一條指令剛剛執(zhí)行完步驟①并轉(zhuǎn)向步驟②時,下一條指令就開始執(zhí)行步驟①。圖2.1說明了這個過程。從原理上說,這樣的流水線應(yīng)該比沒有重疊的指令執(zhí)行快6倍,但由于硬件結(jié)構(gòu)本身的一些限制,實際情況會比理想狀態(tài)差一些。
2.2.2 流水線的分類從Acorn Computer公司在1983~1985年間開發(fā)的第一個3µm器件,到ARM公司在1990~1995年間開發(fā)的ARM6和ARM7,ARM整數(shù)處理器核的組織結(jié)構(gòu)變化很小,這些處理器都是采用3級流水線,而這一時期CMOS工藝的發(fā)展,幾乎將特征尺寸減少了一個數(shù)量級。因此,核的性能提高很快,但基本的操作原理大部分沒有變化。
圖2.1 流水線的指令執(zhí)行過程
從1995年以來,ARM公司推出了幾個新的ARM核。它們采用5級流水線和哈佛架構(gòu),獲得了顯著的高性能。例如,ARM9增加了存儲器訪問段和回寫段,這使得ARM9的處理能力可達到平均1.1 Dhrystone1 MISP/MHz,與ARM7相比,指令吞吐量提高了約13%。
注意
在許多高性能處理器內(nèi)部,一級Cache一般都設(shè)置有兩個,其中,一個是指令Cache,另一個是數(shù)據(jù)Cache。這樣可以減少取指令和讀操作數(shù)的訪問沖突,這種結(jié)構(gòu)被稱為哈佛架構(gòu)。
把主存儲器分成兩個獨立編址的存儲器,一個專門存放指令,稱為指令存儲器,簡稱指存;另一個專門存放操作數(shù),稱為數(shù)據(jù)存儲器,簡稱數(shù)存。兩個存儲器可以同時訪問,這樣就解決了取指令和讀操作數(shù)的沖突。如果在此基礎(chǔ)上規(guī)定在執(zhí)行指令階段產(chǎn)生的運算結(jié)果只寫到通用寄存器中,不寫到主存,那么取指令、分析指令和執(zhí)行指令就可以同時進行。
ARM10更是把流水線增加到6級。ARM10的平均處理能力達到1.3 Dhrystone MISP/MHz,與ARM7相比,指令吞吐量提高了約34%。
注意
雖然ARM9和ARM10的流水線不同,但它們都使用了與ARM7相同的流水線執(zhí)行機制,因此ARM7上的代碼也可以在ARM9和ARM10上運行。
1.3級流水線ARM組織3級流水線ARM組織如圖2.2所示,其主要的組成如下:
① 處理器狀態(tài)寄存器堆(Rigister Bank)。它有兩個讀端口和一個寫端口,每個端口都可以訪問任意寄存器。另外還有附加的可以訪問PC的一個讀端口和一個寫端口。
注意
PC的附加寫端口可以在取指地址增加后更新PC,讀端口可以在數(shù)據(jù)地址發(fā)出之后從新開始取指。
② 桶形移位寄存器(Barrel Shifter)。它可以把一個操作數(shù)移位或循環(huán)移位任意位數(shù)。
③ ALU。完成指令集要求的算術(shù)或邏輯功能。
圖2.2 3級流水線ARM的組織
④ 地址寄存器(Address Register)和增值器(Incrementer)??蛇x擇和保存所用的存儲器地址并在需要時產(chǎn)生順序地址。
⑤ 數(shù)據(jù)輸出寄存器(data-out register)和數(shù)據(jù)輸入寄存器(data-in register)。用于保存?zhèn)鬏數(shù)酱鎯ζ骱蛷拇鎯ζ鬏敵龅臄?shù)據(jù)。
⑥ 指令譯碼器和相關(guān)的控制邏輯(instruction decode and control)。
例2.1顯示了一條單周期指令在流水線上的執(zhí)行過程。
【例2.1】
ADD r1,r2
指令在流水線上的執(zhí)行過程如圖2.3所示。
圖2.3 單周期指令在流水線上的執(zhí)行過程
在ADD指令中,需要訪問兩個寄存器操作數(shù),B總線上的數(shù)據(jù)移位后與A總線上的數(shù)據(jù)在ALU中組合,再將結(jié)果寫回寄存器堆。在指令執(zhí)行過程中,程序計數(shù)器的數(shù)據(jù)放在地址寄存器中,地址寄存器的數(shù)據(jù)送入增值器。然后將增值后的數(shù)據(jù)拷貝到寄存器堆的r15(程序計數(shù)器),同時還拷貝到地址寄存器,作為下一次取指的地址。
到ARM7為止的ARM處理器使用簡單的3級流水線,包括下列流水線級:
· 取指(fetch):從寄存器裝載一條指令。
· 譯碼(decode):識別被執(zhí)行的指令,并為下一個周期準備數(shù)據(jù)通路的控制信號。在這一級,指令占有譯碼邏輯,不占用數(shù)據(jù)通路。
· 執(zhí)行(excute):處理指令并將結(jié)果寫回寄存器。
圖2.4顯示了3級流水線指令執(zhí)行過程。
圖2.4 3級流水線
注意
在任一時刻,可能有3種不同的指令占有這3級中的每一級,因此,每一級中的硬件必須能夠獨立操作。
當(dāng)處理器執(zhí)行簡單的數(shù)據(jù)處理指令時,流水線使得平均每個時鐘周期能完成1條指令。但1條指令需要3個時鐘周期來完成,因此,有3個時鐘周期的延時(latency),但吞吐率(throughput)是每個周期一條指令。例2.2通過一個簡單的例子說明了流水線的機制。
【例2.2】
指令序列為:
ADD r1 r2
SUB r3 r2
CMP r1 r3
流水線指令序列如圖2.5所示。
圖2.5 流水線指令順序
在第一個周期,內(nèi)核從存儲器取出指令A(yù)DD;在第二個周期,內(nèi)核取出指令SUB,同時對ADD譯碼;在第三個周期,指令SUB和ADD都沿流水線移動,ADD被執(zhí)行,而SUB被譯碼,同時又取出CMP指令。可以看出,流水線使得每個時鐘周期都可以執(zhí)行一條指令。
當(dāng)執(zhí)行多條指令時,流水線的執(zhí)行不一定會如圖2.5那么規(guī)則,圖2.6顯示了有STR指令的流水線狀態(tài)。
圖2.6 含有存儲器訪問指令的流水線狀態(tài)
圖2.6中在單周期指令A(yù)DD后出現(xiàn)了一條數(shù)據(jù)存儲指令STR。訪問主存儲器的指令用陰影表示,可以看出在每個周期都使用了存儲器。同樣,在每一個周期也使用了數(shù)據(jù)通路。在執(zhí)行周期、地址計算和數(shù)據(jù)傳輸周期,數(shù)據(jù)通路都是被占用的。在譯碼周期,譯碼邏輯負責(zé)產(chǎn)生下一周期用到的數(shù)據(jù)通路的控制信號。
注意
對于STR這種存儲器訪問指令,實際是在地址計算時由譯碼邏輯產(chǎn)生下一周期數(shù)據(jù)傳輸所需要的數(shù)據(jù)通路控制信號。
在圖2.6中的指令序列中,處理器的每個邏輯單元在每個指令都是活動的??梢钥闯隽魉€的執(zhí)行與存儲器訪問密切相關(guān)。存儲器訪問限制了程序執(zhí)行必須花費的指令周期數(shù)。
ARM的流水線執(zhí)行模式導(dǎo)致了一個結(jié)果,就是程序計數(shù)器PC(對使用者而言為r15)必須在當(dāng)前指令執(zhí)行前計數(shù)。例如,指令在其第一個周期為下下條指令取指,這就意味著PC必須指向當(dāng)前指令的后8個字節(jié)(其后的第2條指令)。
當(dāng)程序中必須用到PC時,程序員要特別注意這一點。大多數(shù)正常情況下,不用考慮這一點,它由匯編器或編譯器自動處理這些細節(jié)。
例2.3顯示了流水線下程序計數(shù)器PC的使用情況。
【例2.3】
指令序列為:
0x8000 LDR pc,[pc,#0]
0x8004 NOP
0x8008 DCD jumpAdress
當(dāng)指令LDR處于執(zhí)行階段時,pc=address+8即0x8008。
2.5級流水線ARM組織所有的處理器都要滿足對高性能的要求。直到ARM7為止,在ARM核中使用的3級流水線的性價比是很高的。但是,為了得到更高的性能,需要重新考慮處理器的組織結(jié)構(gòu)。執(zhí)行一個給定的程序需要的時間由下式?jīng)Q定:
Tprog = (Ninst×CPI)/ fclk
式中:
Ninst:表示在程序中執(zhí)行的ARM指令數(shù);
CPI:表示每條指令的平均時鐘周期;
fclk:表示處理器的時鐘頻率。
因為對給定程序(假設(shè)使用給定的優(yōu)化集并用給定的編譯器來編譯)Ninst是常數(shù),所以,僅有兩種方法來提供性能。
第一,提高時鐘頻率。時鐘頻率的提高,必然引起指令執(zhí)行周期的縮短,所以要求簡化流水線每一級的邏輯,流水線的級數(shù)就要增加。
第二,減少每條指令的平均指令周期數(shù)CPI。這就要求重新考慮3級流水線ARM中多于1個流水線周期的實現(xiàn)方法,以便使其占有較少的周期,或者減少因指令相關(guān)造成的流水線停頓,也可以將兩者結(jié)合起來。
3級流水線ARM核在每一個時鐘周期都訪問存儲器,或者取指令,或者傳輸數(shù)據(jù)。只是抓緊存儲器不用的幾個周期來改善系統(tǒng)系統(tǒng)性能,效果是不明顯的。為了改善CPI,存儲器系統(tǒng)必須在每個時鐘周期中給出多于一個的數(shù)據(jù)。方法是在每個時鐘周期從單個存儲器中給出多于32位數(shù)據(jù),或者為指令或數(shù)據(jù)分別設(shè)置存儲器。
基于以上原因,較高性能的ARM核使用了5級流水線,而且具有分開的指令和數(shù)據(jù)存儲器。把指令的執(zhí)行分割為5部分而不是3部分,進而可以使用更高的時鐘頻率,分開的指令和數(shù)據(jù)存儲器使核的CPI明顯減少。
注意
分開的指令和數(shù)據(jù)存儲器。一般是分開的Cache連接到統(tǒng)一的指令和數(shù)據(jù)存儲器上。
在ARM9TDMI中使用了典型的5級流水線。ARM9TDMI的組織結(jié)構(gòu)如圖2.7所示。
5級流水線包括下面的流水線級:
· 取指(fetch):從存儲器中取出指令,并將其放入指令流水線。
· 譯碼(decode):指令被譯碼,從寄存器堆中讀取寄存器操作數(shù)。在寄存器堆中有3個操作數(shù)讀端口,因此,大多數(shù)ARM指令能在1個周期內(nèi)讀取其操作數(shù)。
· 執(zhí)行(execute):將其中一個操作數(shù)移位,并在ALU中產(chǎn)生結(jié)果。如果指令是Load或Store指令,則在ALU中計算存儲器的地址。
· 緩沖/數(shù)據(jù)(buffer/data):如果需要則訪問數(shù)據(jù)存儲器,否則ALU只是簡單地緩沖一個時鐘周期。
· 回寫(write-back):將指令的結(jié)果回寫到寄存器堆,包括任何從寄存器讀出的數(shù)據(jù)。
圖2.8顯示了5級流水線指令的執(zhí)行過程。
圖2.7 5級流水線的組織結(jié)構(gòu)
圖2.8 5級流水線
在程序執(zhí)行過程中,PC值是基于3級流水線操作特性的。5級流水線中提前1級來讀取指令操作數(shù),得到的值是不同的(PC+4而不是PC+8)。這產(chǎn)生的代碼不兼容是不容許的。但5級流水線ARM完全仿真3級流水線的行為。在取指級增加的PC值被直接送到譯碼級的寄存器,穿過兩極之間的流水線寄存器。下一條指令的PC+4等于當(dāng)前指令的PC+8,因此,未使用額外的硬件便得到了正確的r15。
3.6級流水線ARM組織在ARM10中,將流水線的級數(shù)增加到6級,使系統(tǒng)的平均處理能力達到了1.3Dhrystone MISP/MHz。圖2.9顯示了6級流水線上指令的執(zhí)行過程。
圖2.9 6級流水線
2.2.3 影響流水線性能的因素1.互鎖在典型的程序處理過程中,經(jīng)常會遇到這樣的情形,即一條指令的結(jié)果被用做下一條指令的操作數(shù)。如例2.4所示。
【例2.4】
有如下指令序列:
LDR r0,[r0,#0]
ADD r0,r0,r1 ;在5級流水線上產(chǎn)生互鎖
從例2.4中可以看出,流水線的操作產(chǎn)生中斷,因為第一條指令的結(jié)果在第二條指令取數(shù)時還沒有產(chǎn)生。第二條指令必須停止,直到結(jié)果產(chǎn)生為止。
2.跳轉(zhuǎn)指令跳轉(zhuǎn)指令也會破壞流水線的行為,因為后續(xù)指令的取指步驟受到跳轉(zhuǎn)目標計算的影響,因而必須推遲。但是,當(dāng)跳轉(zhuǎn)指令被譯碼時,在它被確認是跳轉(zhuǎn)指令之前,后續(xù)的取指操作已經(jīng)發(fā)生。這樣一來,已經(jīng)被預(yù)取進入流水線的指令不得不被丟棄。如果跳轉(zhuǎn)目標的計算是在ALU階段完成的,那么,在得到跳轉(zhuǎn)目標之前已經(jīng)有兩條指令按原有指令流讀取。
解決的辦法是,如果有可能最好早一些計算轉(zhuǎn)移目標,當(dāng)然這需要硬件支持;如果轉(zhuǎn)移指令具有固定格式,那么可以在解碼階段預(yù)測跳轉(zhuǎn)目標,從而將跳轉(zhuǎn)的執(zhí)行時間減少到單個周期。但要注意,由于條件跳轉(zhuǎn)與前一條指令的條件碼結(jié)果有關(guān),在這個流水線中,還會有條件轉(zhuǎn)移的危險。
盡管有些技術(shù)可以減少這些流水線問題的影響,但是,不能完全消除這些困難。流水線級數(shù)越多,問題就越嚴重。對于相對簡單的處理器,使用3~5級流水線效果最好。
顯然,只有當(dāng)所有指令都依照相似的步驟執(zhí)行時,流水線的效率達到最高。如果處理器的指令非常復(fù)雜,每一條指令的行為都與下一條指令不同,那么就很難用流水線實現(xiàn)。