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