:ID級(jí)提前判斷與沖刷機(jī)制詳解)
簡介這份PDF是電子科技大學(xué)2021年計(jì)算機(jī)體系結(jié)構(gòu)課程的實(shí)驗(yàn)四報(bào)告面向正在學(xué)習(xí)流水線CPU設(shè)計(jì)、需要攻克控制冒險(xiǎn)難題的高校學(xué)生與Verilog初學(xué)者。報(bào)告圍繞JUMP無條件跳轉(zhuǎn)及BNE、BEQ條件跳轉(zhuǎn)三類指令展開講解如何在ID級(jí)提前計(jì)算轉(zhuǎn)移地址與id_z信號(hào)、通過nop部件將IF級(jí)預(yù)取指令替換為空指令并給出轉(zhuǎn)移指令與數(shù)據(jù)冒險(xiǎn)疊加時(shí)的處理思路及電路結(jié)構(gòu)圖修改說明。資源包共1個(gè)PDF文件約1.34MB內(nèi)容含實(shí)驗(yàn)原理、目的、器材、步驟與仿真驗(yàn)證配有代碼片段和結(jié)構(gòu)圖便于對(duì)照復(fù)現(xiàn)。已有838人學(xué)習(xí)下載適合作為流水線CPU實(shí)驗(yàn)的參考范本與排錯(cuò)對(duì)照材料。1. 控制冒險(xiǎn)為什么一條跳轉(zhuǎn)指令能讓流水線“翻車”流水線 CPU 最反直覺的一點(diǎn)是指令還沒執(zhí)行完后面幾條就已經(jīng)被取進(jìn)來了。順序執(zhí)行時(shí)這沒問題可一旦遇到beq、bne、jump這類轉(zhuǎn)移指令程序流的走向在取指階段根本不知道IF 級(jí)卻已經(jīng)按“順序”把下一條指令搬進(jìn)了流水線。等 ID 級(jí)算出“要跳”那條被預(yù)取的指令已經(jīng)站在錯(cuò)誤的位置上——這就是控制冒險(xiǎn)也叫轉(zhuǎn)移相關(guān)。它解決的不是“跳不跳”的語義問題而是“跳轉(zhuǎn)決定來得太晚錯(cuò)誤指令已經(jīng)混進(jìn)來”的時(shí)序問題。搞懂它你才真正理解流水線里“預(yù)測—驗(yàn)證—沖刷”這套機(jī)制的底層邏輯也才能看懂為什么有的設(shè)計(jì)寧可多花一個(gè)比較器也不愿多停一個(gè)周期。2. 轉(zhuǎn)移條件為什么必須在 ID 級(jí)提前算出來2.1 單周期 CPU 的 Z 標(biāo)志在流水線里為什么“來不及”單周期 CPU 里條件轉(zhuǎn)移的判斷很直接ALU 算完Z 標(biāo)志結(jié)果是否為零自然就有了控制部件拿rsrtequ一判斷PC 該跳就跳。但流水線把“算”和“判”拆到了不同階段——ALU 在 EXE 級(jí)而轉(zhuǎn)移指令的后續(xù)指令在 IF 級(jí)就已經(jīng)進(jìn)來了。如果老老實(shí)實(shí)等 Z 標(biāo)志從 EXE 級(jí)傳回來流水線就得停一個(gè)周期等 Z 準(zhǔn)備好再?zèng)Q定 PC 走向。這個(gè)“停一個(gè)周期”的代價(jià)就是控制冒險(xiǎn)最樸素的解法暫停。暫停的邏輯是——轉(zhuǎn)移指令只可能改 PC不會(huì)改寄存器堆、不會(huì)寫內(nèi)存所以對(duì)轉(zhuǎn)移指令來說WMEM、WREG本來就是 0不需要封鎖它們只需要把 PC 和 IF 級(jí)按住一個(gè)周期。但暫停終歸是性能損失每遇到一條條件轉(zhuǎn)移就停一拍流水線的意義就被削掉一塊。2.2 把比較電路搬到 ID 級(jí)id_z 的表達(dá)式與硬件代價(jià)這份實(shí)驗(yàn)報(bào)告里采用的思路是把比較提前到 ID 級(jí)。ID 級(jí)本來就要從寄存器堆讀出id_a和id_b兩個(gè)操作數(shù)那干脆在這里加一個(gè)比較器直接判斷兩者是否相等產(chǎn)生id_z信號(hào)等價(jià)于提前拿到了rsrtequ。報(bào)告里給出的表達(dá)式是// ID 級(jí)提前比較兩個(gè)源寄存器產(chǎn)生轉(zhuǎn)移條件 // 寫法一直接相等比較 assign id_z (id_ra id_rb) ? 1b1 : 1b0; // 寫法二按位異或后歸約或全零即相等 assign id_z ~|(id_a ^ id_b);兩種寫法邏輯等價(jià)第二種用“異或再歸約或”的方式在硬件上往往映射成更緊湊的門級(jí)結(jié)構(gòu)——異或?yàn)?0 表示每一位都相同歸約或?yàn)?0 就說明整體相等。參數(shù)上id_a、id_b是 ID 級(jí)寄存器堆讀出的兩個(gè)源操作數(shù)id_z就是提前產(chǎn)生的轉(zhuǎn)移條件信號(hào)直接喂給控制部件去影響pcsource。這么做的收益很明確不用再等 EXE 級(jí)的 Z 標(biāo)志省掉了一個(gè)停頓周期。代價(jià)是“額外的硬件實(shí)現(xiàn)”——多一個(gè)比較器以及把id_z接進(jìn)控制通路的連線。這是一個(gè)典型的空間換時(shí)間取舍用一點(diǎn)組合邏輯面積換掉每條條件轉(zhuǎn)移指令的一個(gè)氣泡。我一般會(huì)這么理解這不是“更聰明”而是“把判斷挪到了數(shù)據(jù)本來就到齊的地方”ID 級(jí)恰好是操作數(shù)第一次同時(shí)可用的階段。2.3 轉(zhuǎn)移地址也在 ID 級(jí)算完2 個(gè)周期意味著什么光有條件還不夠轉(zhuǎn)移目標(biāo)地址也得提前。報(bào)告里明確寫了轉(zhuǎn)移條件在 ID 級(jí)計(jì)算得到轉(zhuǎn)移地址也在 ID 級(jí)完成偏移計(jì)算。于是整條轉(zhuǎn)移指令從取指到“決定去向”只需要 2 個(gè)時(shí)鐘周期受影響的后續(xù)指令只有 1 條。這個(gè)“2 個(gè)周期、1 條受影響指令”是整個(gè)方案的核心數(shù)字。它意味著當(dāng)轉(zhuǎn)移指令走到 ID 級(jí)、pcsource被確定的那一刻IF 級(jí)里正好躺著一條按順序預(yù)取的指令而這條指令就是唯一需要被處理掉的“錯(cuò)誤指令”。受影響指令數(shù)從“n-1 條”壓縮到 1 條沖刷的代價(jià)就被壓到了最小。如果轉(zhuǎn)移地址要拖到 EXE 級(jí)才算受影響的就是 2 條沖刷邏輯和性能損失都會(huì)翻倍——這也是報(bào)告在改進(jìn)建議里提到“其實(shí)還可以在 EXE 級(jí)完成”時(shí)語氣里帶著遺憾的原因EXE 級(jí)方案能 work但要多沖一條。3. 沖刷與空指令pcsource 如何讓 IF 級(jí)“換人”3.1 nop 部件的本質(zhì)用 pcsource 做二選一確定了“要跳”接下來的問題是怎么把 IF 級(jí)那條錯(cuò)誤指令處理掉。報(bào)告里的做法很干脆不搞復(fù)雜的沖刷隊(duì)列而是在 IF 級(jí)輸出處放一個(gè)二選一的“nop 部件”由pcsource直接控制。// 頂層模塊中根據(jù) pcsource 決定 IF 級(jí)送出的指令 // pcsource 01 或 10 表示 ID 級(jí)是轉(zhuǎn)移指令條件成立 / 無條件 // 此時(shí)強(qiáng)制送出空指令 32h0否則送正常預(yù)取指令 assign if_inst (pcsource 2b01 || pcsource 2b10) ? 32h0 : if_inst_org;if_inst_org是 IF 級(jí)原本取回的指令if_inst才是真正送進(jìn)流水線寄存器的指令。pcsource的編碼在這里承擔(dān)了“指令性質(zhì)”的語義01和10都代表 ID 級(jí)當(dāng)前是一條會(huì)改變 PC 的轉(zhuǎn)移指令00則代表普通指令。一旦命中轉(zhuǎn)移if_inst被替換成32h0也就是一條空指令nop它流進(jìn)流水線后不寫寄存器、不寫內(nèi)存等于什么都沒干。這個(gè)設(shè)計(jì)的巧妙之處在于“廢棄”和“暫?!北唤y(tǒng)一成了同一個(gè)動(dòng)作把錯(cuò)誤指令換成空指令??罩噶顣?huì)正常流過 IF、ID、EXE 各級(jí)只是不產(chǎn)生副作用所以不需要額外的清空流水線寄存器邏輯控制復(fù)雜度大幅下降。代價(jià)是這條空指令仍然占了一個(gè)周期的時(shí)間片——但相比“停一拍等 Z 標(biāo)志”它至少讓流水線繼續(xù)流動(dòng)而不是硬停。3.2 條件轉(zhuǎn)移與無條件轉(zhuǎn)移共用一條沖刷通路報(bào)告里把beq、bne歸為條件轉(zhuǎn)移jump歸為無條件轉(zhuǎn)移但兩者在“廢棄 IF 級(jí)指令”這件事上走的是同一條路。區(qū)別只在pcsource的取值來源條件轉(zhuǎn)移看id_z是否成立無條件轉(zhuǎn)移則直接認(rèn)定要跳。// Control_Unit 內(nèi)部由 id_z 和指令類型共同決定 pcsource // rsrtequ 即頂層傳入的 id_zID 級(jí)提前比較的結(jié)果 // 條件轉(zhuǎn)移id_z 成立 - pcsource 01跳 bpc // 無條件轉(zhuǎn)移直接 - pcsource 10跳 jpc // 普通指令pcsource 00這里rsrtequ就是id_z的別名控制部件拿它和指令譯碼結(jié)果一起判斷。條件成立或遇到j(luò)umppcsource就離開00IF 級(jí)的 nop 部件立刻把if_inst換成空指令同時(shí) PC 被改成bpc條件轉(zhuǎn)移目標(biāo)或jpc無條件轉(zhuǎn)移目標(biāo)。一條通路同時(shí)覆蓋兩類轉(zhuǎn)移硬件上省了一套沖刷邏輯這是設(shè)計(jì)上的合并取舍——代價(jià)是pcsource的編碼必須嚴(yán)格區(qū)分“跳 bpc”“跳 jpc”“不跳”三種狀態(tài)任何譯碼錯(cuò)誤都會(huì)直接表現(xiàn)為取錯(cuò)指令。提示pcsource同時(shí)驅(qū)動(dòng) PC 選擇和 IF 級(jí) nop 部件這兩處必須保持同一時(shí)鐘沿的語義一致否則會(huì)出現(xiàn)“PC 跳了但空指令沒插進(jìn)去”或“插了空指令但 PC 沒跳”的錯(cuò)位。4. 控制冒險(xiǎn)撞上數(shù)據(jù)冒險(xiǎn)前推結(jié)果能不能直接喂給比較器4.1 三類前推來源EXE、MEM、LOAD 的數(shù)據(jù)怎么進(jìn) ID 級(jí)比較控制冒險(xiǎn)很少單獨(dú)出現(xiàn)。報(bào)告里的測試序列刻意把條件轉(zhuǎn)移和數(shù)據(jù)冒險(xiǎn)疊在一起beq、bne要比較的寄存器可能正好是前面 ALU 指令或 LOAD 指令的目的寄存器。這時(shí)候 ID 級(jí)比較器拿到的id_a、id_b可能還是舊值直接比較就會(huì)得出錯(cuò)誤的轉(zhuǎn)移結(jié)論。好在實(shí)驗(yàn)三已經(jīng)把數(shù)據(jù)前推做到了 ID 級(jí)所以這里的比較可以直接用前推后的結(jié)果。報(bào)告把條件轉(zhuǎn)移遇到的數(shù)據(jù)冒險(xiǎn)分成三大類并在仿真里逐一驗(yàn)證前推來源典型場景對(duì) ID 級(jí)比較的影響EXE 級(jí) ALU 結(jié)果轉(zhuǎn)移指令緊跟 ALU 指令用exe_alu_result前推到 ID 級(jí)參與比較MEM 級(jí) ALU 結(jié)果中間隔了一條指令用mem_alu_result前推到 ID 級(jí)MEM 級(jí) LOAD 結(jié)果轉(zhuǎn)移指令依賴 LOAD 的目的寄存器用mem_mo訪存結(jié)果前推到 ID 級(jí)這張表背后的機(jī)制是ID 級(jí)比較器不是孤立工作的它的輸入必須掛到前推網(wǎng)絡(luò)上否則“提前比較”反而會(huì)提前比錯(cuò)。報(bào)告在結(jié)果分析里特別標(biāo)注了每一處bne、beq所取的操作數(shù)分別來自哪條前推線——mem_mo、exe_alu_result、mem_alu_result——這其實(shí)是在證明控制冒險(xiǎn)的解決必須建立在數(shù)據(jù)冒險(xiǎn)已解決的前提上兩者是耦合的。4.2 一個(gè)容易忽略的細(xì)節(jié)LOAD 型冒險(xiǎn)仍需暫停前推能解決大多數(shù)情況但 LOAD 指令是個(gè)例外。LOAD 的數(shù)據(jù)要到 MEM 級(jí)末尾才真正從內(nèi)存取回如果緊隨其后的指令在 EXE 級(jí)就要用這個(gè)數(shù)據(jù)前推也救不了只能暫停一個(gè)周期。報(bào)告里的測試序列第 3、4 條load r5,r1(2)和store r5,r2(3)就是這種情況STORE 進(jìn) ID 級(jí)時(shí)與 EXE 級(jí)的 LOAD 存在 LOAD 數(shù)據(jù)冒險(xiǎn)仿真里明確出現(xiàn)了一個(gè)紅色框標(biāo)注的暫停周期框內(nèi) ALU 輸出無效。這個(gè)細(xì)節(jié)說明控制冒險(xiǎn)的“提前比較”并沒有繞開數(shù)據(jù)冒險(xiǎn)的物理限制。ID 級(jí)比較器再早也得等操作數(shù)真正可用LOAD 的數(shù)據(jù)沒回來比較就只能等。所以完整的機(jī)制是“前推優(yōu)先、前推不了就暫?!笨刂泼半U(xiǎn)的沖刷邏輯疊加在這套數(shù)據(jù)冒險(xiǎn)處理之上而不是替代它。5. 避坑與排查仿真波形里那些讓人懷疑人生的瞬間5.1 現(xiàn)象轉(zhuǎn)移發(fā)生了但 IF 級(jí)還是進(jìn)了順序指令原因通常是pcsource只接到了 PC 選擇端沒接到 IF 級(jí)的 nop 部件或者兩處用了不同的信號(hào)名。解決方法是確認(rèn)if_inst的賦值表達(dá)式里判斷的就是驅(qū)動(dòng) PC 的那個(gè)pcsource且位寬、編碼完全一致別一個(gè)用2b01另一個(gè)用1b1。5.2 現(xiàn)象條件轉(zhuǎn)移該跳卻沒跳比較結(jié)果總是“不等”多半是 ID 級(jí)比較器沒接前推網(wǎng)絡(luò)拿到的是寄存器堆的舊值。排查時(shí)沿著id_a、id_b往上游追看它們是否經(jīng)過前推多路選擇器再對(duì)照仿真波形確認(rèn)比較時(shí)刻前推選擇信號(hào)是否已經(jīng)有效。報(bào)告里三類前推來源要逐一核對(duì)漏掉mem_mo這條線是最常見的翻車點(diǎn)。5.3 現(xiàn)象插入了空指令但流水線后續(xù)行為錯(cuò)亂空指令32h0必須被譯碼成“什么都不做”。如果控制部件把全零指令誤譯成某種有效操作空指令就會(huì)偷偷寫寄存器或?qū)憙?nèi)存。排查方法是單獨(dú)仿真一條32h0看WREG、WMEM是否都為 0不為 0 就說明譯碼的默認(rèn)分支沒兜住。5.4 現(xiàn)象LOAD 后緊跟轉(zhuǎn)移結(jié)果時(shí)對(duì)時(shí)錯(cuò)這是 LOAD 數(shù)據(jù)冒險(xiǎn)沒暫停導(dǎo)致的。LOAD 的數(shù)據(jù)在 MEM 級(jí)才回來ID 級(jí)比較器此時(shí)拿到的是無效值。解決方法是保留 LOAD 冒險(xiǎn)的暫停邏輯讓流水線停一拍再比較別指望前推能覆蓋所有情況。5.5 現(xiàn)象無條件轉(zhuǎn)移后 PC 對(duì)了但下一條執(zhí)行的是錯(cuò)誤指令jump的pcsource編碼如果和條件轉(zhuǎn)移混用同一個(gè)值nop 部件可能判斷正確但 PC 選錯(cuò)目標(biāo)bpc與jpc搞反。排查時(shí)把pcsource的三種取值和 PC 多路選擇器的輸入一一對(duì)照確認(rèn)01走bpc、10走jpc別讓編碼在兩層邏輯里各說各話。6. 設(shè)計(jì)取舍為什么“ID 級(jí)提前判斷”不是唯一答案把轉(zhuǎn)移判斷提前到 ID 級(jí)本質(zhì)是在“判斷時(shí)機(jī)”和“硬件復(fù)雜度”之間做了一次明確的交換。越早判斷受影響的后續(xù)指令越少?zèng)_刷代價(jià)越小但越早判斷需要的額外硬件比較器、前推到 ID 級(jí)的連線、nop 部件越多而且判斷所依賴的數(shù)據(jù)可能還沒準(zhǔn)備好反而要引入暫停。這份實(shí)驗(yàn)報(bào)告選擇 ID 級(jí)是因?yàn)樗咽苡绊懼噶顗旱搅?1 條同時(shí)復(fù)用了實(shí)驗(yàn)三已有的 ID 級(jí)前推網(wǎng)絡(luò)邊際成本最低。但這不是唯一解。報(bào)告在改進(jìn)建議里提到“其實(shí)還可以在 EXE 級(jí)完成”這句話背后是一個(gè)真實(shí)的取舍EXE 級(jí)方案不需要在 ID 級(jí)加比較器判斷邏輯可以復(fù)用 ALU 的零標(biāo)志硬件更省代價(jià)是轉(zhuǎn)移決定晚一個(gè)周期受影響指令變成 2 條沖刷邏輯要處理兩條錯(cuò)誤指令或者多停一拍。換句話說ID 級(jí)方案是“多花硬件換性能”EXE 級(jí)方案是“省硬件但多沖一條”。沒有絕對(duì)優(yōu)劣取決于你的流水線深度、轉(zhuǎn)移指令占比以及你愿意為每條轉(zhuǎn)移付出多少氣泡。再往遠(yuǎn)看一步真實(shí)處理器很少只靠“算完再?zèng)_”而是引入預(yù)測預(yù)測轉(zhuǎn)移發(fā)生或不發(fā)生先按預(yù)測取指等真正算出來再驗(yàn)證預(yù)測失敗才沖刷。報(bào)告里也提到了“預(yù)測轉(zhuǎn)移發(fā)生或預(yù)測轉(zhuǎn)移不發(fā)生的方法當(dāng)發(fā)現(xiàn)預(yù)測失敗或計(jì)算得轉(zhuǎn)移目標(biāo)地址時(shí)需要廢棄前面的錯(cuò)誤指令”。預(yù)測把“等待判斷”變成了“賭一把再驗(yàn)證”在轉(zhuǎn)移規(guī)律性強(qiáng)時(shí)收益巨大但預(yù)測失敗時(shí)的沖刷代價(jià)和預(yù)測器的面積開銷又是新的取舍。ID 級(jí)提前判斷更像是這個(gè)譜系里“不預(yù)測、但盡量早算”的中間態(tài)——它不賭只是把該算的算早一點(diǎn)。驗(yàn)證這套機(jī)制是否真的 work最直接的辦法就是像報(bào)告那樣構(gòu)造一條混合了數(shù)據(jù)冒險(xiǎn)和控制冒險(xiǎn)的指令序列在波形里逐拍核對(duì)轉(zhuǎn)移指令進(jìn) ID 級(jí)的那一拍pcsource是否及時(shí)翻轉(zhuǎn)、IF 級(jí)是否插入空指令、前推來源是否正確、LOAD 冒險(xiǎn)處是否真的停了一拍。我自己的習(xí)慣是每次改完流水線控制通路都強(qiáng)制把“轉(zhuǎn)移 前推 LOAD 暫?!边@三種情況疊在同一條測試序列里跑一遍波形因?yàn)閱为?dú)測控制冒險(xiǎn)往往一切正常一疊加數(shù)據(jù)冒險(xiǎn)就原形畢露。從那以后我每次動(dòng) PC 選擇或沖刷邏輯都強(qiáng)制走一遍這條混合序列省掉了很多事后回看波形的后悔藥。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取