處理器取指
每條指令在存儲器空間中所處的位址稱為它的指令PC,取址指處理器核将指令(按照其指令PC值對應的存儲器位址)從存儲器中讀取出來的過程
取址的目标如下:
- 快速取址
- 連續取址
可能面對的問題如下:
- 指令的編碼寬度不相等,導緻PC位址與位址邊界無法對齊
- 分支跳轉指令執行後,可能導緻跳轉到另一個不連續的PC處,取址時需要從新的PC值對應的存儲器位址讀出指令
- 處理器會按順序執行非分支跳轉指令,需要按順序從存儲器中讀取指令
傳統RISC架構處理器的解決方案
- 連續不斷地從存儲器中順序讀取出非分支跳轉指令,即使是位址不對齊的32位指令,也應該能每個周期讀出一條完整指令
- 能夠快速判斷在分支跳轉指令中是否跳轉,如果需要跳轉,則從新的PC位址處快速取出指令,力求每個周期讀出一條完整指令
下面以傳統RISC架構處理器介紹取址步驟
對于取指速度的優化
優化取指速度需要保證存儲器的讀延遲越小越好,但常見的存儲器會存在不同程度的延遲
為了讓處理器核能以最快速度取址,常使用以下兩種方法取址
-
ITCM(Instruction Tightly Coupled Memory)指令緊耦合寄存器
配置一段較小容量的存儲器(通常為幾十KB,使用SRAM),用于存儲指令
實體上需要離核心很近且專屬于處理器核來取地很小的通路延遲(一般為一個時鐘周期)
優點:1. 實作非常簡單;2. 能保證明時性
缺點:1. 使用位址區間尋址,無法像緩存一樣映射無限大的存儲器空間;2. 容量受限
-
I-Cache(Instruction Cache)指令緩存
利用軟體程式的時間局限性和空間局部性,将外部指令存儲器空間動态映射到容量有限的指令緩存中,将通路指令存儲器的平均延遲降低到最小
優點:1. 延遲确定,可以用于實時性要求較高的應用場景;2. 結構、實作複雜
缺點:1. 緩存容量有限,通路緩存不确定性較大,可能造成緩存不命中;2. 無法保證處理器反應速度的實時性
對于非對齊指令取指的優化
處理器取指的一個目标是連續不斷,争取每個時鐘周期都能取出一條指令,源源不斷地為後續執行提供指令流,不出現空閑的時鐘周期
一般上述兩種優化取指速度的方法都會使用SRAM,他的讀端口寬度固定,n位的SRAM在一個時鐘周期隻能讀出一個n位的資料,但如果一條n位的指令被存儲于位址不對齊的位置,則意味着需要分2個時鐘周期才能讀出一條指令,一般使用以下方法來處理非對齊指令
普通指令
使用剩餘緩存儲存上次取指後沒有用完的比特位,供下次使用
例如:從ITCM中取出一個32位的指令字,但隻用到了它的低16位,則
- 隻需要使用此次取出的32位中低16位指令和之前取出32位中高16位指令組成一個32位指令,再進行執行
- 指令長度本身就是16位,将其暫存在剩餘緩存中,等待下一個周期取出下一個32位指令字後再拼接出完整指令執行
分支跳轉指令
如果分支跳轉指令的目标位址與32位位址邊界不對齊,且需要取出一個32位的指令字,則剩餘緩存的解決方案失效!
這種情況下常使用多體化的SRAM進行指令存儲
常見的形式為奇偶交替:使用兩塊32位寬的SRAM交錯地進行存儲,将兩個32位指令字分别存儲在兩塊不同的SRAM中,這樣就可以在一個時鐘周期内通路兩塊SRAM并取出兩個連續的32位關鍵字,然後拼接形成真正的32位指令
分支指令處理
分支指令類型
-
無條件跳轉/分支指令
無需判斷條件就一定會發生跳轉的指令
還存在以下分類
-
無條件直接跳轉/分支
使用立即數計算得到跳轉位址的指令
RISC-V中的JAL指令就是無條件直接跳轉指令,該指令使用編碼在指令字中的20位立即數作為偏移量,将其乘2後與目前指令所在位址相加就得到了最終的跳轉目标位址
-
無條件間接跳轉/分支
使用寄存器索引的操作數計算得到跳轉位址的指令
RISC-V中的JALR指令就是無條件間接跳轉指令,該指令使用編碼在指令字中的12位立即數作為偏移量,與基位址寄存器(其中索引的操作數)相加得到最終的跳轉目标位址
-
-
帶條件跳轉/分支指令
判斷條件決定是否跳轉的指令
存在以下分類
-
帶條件直接跳轉/分支
使用立即數計算得到跳轉位址的指令
-
帶條件間接跳轉/分支
用寄存器索引的操作數計算得到跳轉位址的指令
上面兩個類型和無條件跳轉/分支指令類似,但是都多出判斷條件的這一部分。RISC-V架構中沒有帶條件間接跳轉指令
理論上指令隻有在執行階段完成後才能解析出最終的跳轉結果,如果在取指期間暫停,直到執行階段完成才繼續取指,會浪費大量時鐘周期,造成流水線斷流。是以處理器會采用分支預測技術,會預測跳轉的方向和跳轉的目标位址
-
分支預測
1. 靜态分支預測
不依賴任何執行過的指令資訊和曆史資訊,憑借目前分支指令本身的資訊進行預測
最簡分支預測:總是預測分支指令不會發生跳轉,如果執行階段發現需要跳轉,則沖刷流水線重新取指,會造成兩個時鐘周期的流水線延遲
分支延遲槽:每一條分支指令後面緊跟的一條或若幹條指令不受分支跳轉的影響,不管分支是否跳轉,後面的指令都一定會被執行。分支延遲槽中的指令永遠被執行而不用被丢棄重取,它不會受到沖刷流水線的影響
BTFN預測(Back Taken,Forward Not Taken):對向後跳轉預測為跳,向前跳轉預測為不跳,比較常見
2. 動态分支預測
依賴已經執行過的曆史資訊和分支指令本身的資訊綜合進行方向預測
一比特飽和計數器:最簡單的動态預測器,每次分支指令執行後就會使用此計數器記錄上次的方向,采用下一次分支指令永遠采用上次記錄的方向作為本次的預測
兩比特飽和計數器:最常見的動态預測器,采用FSM的方式進行預測。
目前狀态=強不需要跳轉 或 弱不需要跳轉,則預測該指令方向為 不需要跳轉
目前狀态=弱需要跳轉 或 強需要跳轉,則預測該指令方向為 需要跳轉
如果預測出錯,則反向更改目前狀态:從 強需要跳轉 要出錯連續2次才能變為變為 弱不需要跳轉,是以具有一定的切換緩沖,其在複雜程式流中預測精度一般比簡單的一比特飽和計數器更高
但是使用該方案可能會導緻别名重合:使用多個兩比特飽和計數器負責不同分支指令的預測,會導緻大量空間占用,是以隻能采用有限個計數器組成計數器表格,但表項數目有限但指令衆多,是以很多不同的分支會不可避免地指向相同的表項
解決這個問題一般采用動态分支預測算法:采用不同的表格組織方式(控制表格大小)和索引方式(控制别名重合問題)來提高預測精準率,常見算法如下:
- 一級預測器
将有限個兩比特飽和計數器組織成一維表格,稱為預測器表格。直接使用PC值的一部分進行索引
“一級預測器”指的是其索引僅僅采用指令本身的PC值
優點:簡單易行
缺點:索引機制過于簡單導緻預測精度不高
- 兩級預測器
又稱為相關預測器
對于每條分支,将有限個兩比特飽和計數器組織成PHT(Pattern History Table),使用該分支跳轉的曆史作為PHT的索引
隻需要n個bit就能索引2n個表項
分支曆史又可以分為局部曆史(每個分支指令自己的跳轉曆史)和全局曆史(所有分支指令的跳轉曆史)
局部分支預測器采用分立的局部曆史緩存,每個緩存有自己對應的PHT,對于每條分支指令,會先索引其對應的局部曆史緩存,再使用局部曆史緩存中的曆史值所引導對應的PHT
全局分支預測器使用所有分支指令共享的全局曆史緩存。這個解決方案節省資源但隻有在PHT容量非常大時才能展現出其優勢,且PHT容量越大,優勢越明顯
常見的全局預測算法有:
- Gshare算法:将分支指令PC值的一部分和共享的全局曆史緩存進行異或,使用運算的結果作為PHT的索引
- Gselect算法:将分支指令PC值的一部分和共享的全局曆史緩存進行拼接,使用運算的結果作為PHT的索引
- 預測位址
分支目标位址需要在執行階段計算後才能得到分支的目标位址,這些任務無法在一個周期内完成,在連續取下一條指令前,甚至連譯碼判斷目前指令是否屬于分支指令都無法及時地在一個周期内完成,是以為了連續不斷地取指,需要預測分支的目标位址,常見技術如下
- BTB(Branch Target Buffer分支目标緩存):使用容量有限的緩存儲存最近執行過的分支指令的PC值及它們的跳轉目标位址。對于後續需要取指的每條PC值,将其與BTB中存儲的各個PC值進行比較,如果出現比對則預測這是一條分支指令,使用其對應存儲的跳轉目标位址作為預測的跳轉位址
優點:最簡單快捷
缺點:1. BTB容量與時序、面積難以平衡;2. 對于間接跳轉/分支指令的預測效果并不理想
- RAS(Return Address Stack傳回堆棧位址):使用容量有限的硬體堆棧(FIFO)來存儲函數調用的傳回位址
間接分支/跳轉指令多用于函數調用/傳回,這兩者成對出現,是以可以在函數調用時PC+=4或2,将其順序執行的下一條指令的PC值壓入RAS堆棧,等到函數傳回時将其彈出,隻要程式正常執行,RAS就能提供較高的預測準确率。不過由于RAS深度有限,出現多次函數嵌套則可能堆棧溢出,影響準确率
優點:正常情況下準确率高
缺點:出現函數嵌套時難以處理
- Indirect BTB(間接BTB):專門為間接分支/跳轉指令設計的BTB,它通過進階的索引方法進行比對,結合BTB和動态兩級預測器的技術
優點:預測成功率很高
缺點:硬體開銷非常大
- 其它擴充技術
RISC-V架構對取指硬體的簡化
規整的指令編碼格式
RISC-V指令集編碼十分規整,可以快速譯碼得到指令類型及其使用的操作數寄存器索引或立即數
指令長度訓示碼放在低位
RISC-V提供可選的壓縮指令子集C,如果支援此子集就會有32位和16位指令混合交織在一起的情形
所有RISC-V指令編碼的最低幾位專門用于編碼表示指令的長度,将指令長度訓示碼放在指令的最低位,友善取指邏輯在順序取指的過程中以最快速度譯碼出指令的長度,化簡硬體設計。取指邏輯在僅取到16位指令字時就可以進行譯碼判斷目前指令長度而無需等待另外一半16位指令字的取指
此外,由于16位的壓縮指令子集是可選的,假設處理器不支援此壓縮指令子集而僅支援32位指令,甚至可以将指令字的低2位忽略不存儲(因為其肯定固定為11),進而節省I-Cache的開銷
換句話說,RISC-V的變長指令集為譯碼提供友善
簡單的分支跳轉指令
RISC-V架構中存在2條無條件跳轉指令JAL和JALR;存在6條帶條件分支指令BEQ、BNE、BLT、BLTU、BGE、BGEU,這些指令和普通運算指令一樣,直接使用兩個整數操作數,然後對其進行比較,如果比較的條件滿足時則會跳轉。
這些指令使用12位有符号數作為偏移量,有如下計算公式:
偏 移 量 ∗ 2 + 當 前 指 令 所 在 地 址 = 目 标 地 址 偏移量*2+目前指令所在位址=目标位址 偏移量∗2+目前指令所在位址=目标位址
16位的壓縮指令子集中指令能夠一一對應32位的标準指令
沒有分支延遲槽
RISC-V砍掉了分支延遲槽,節省了這一器件的面積
提供明确的靜态分支預測依據和RAS依據
RISC-V架構中明确規定編譯器生成的代碼應該盡量優化,使向後跳轉的分支指令比向前跳轉的分支指令有更大機率進行跳轉,是以硬體層面可以更好地和軟體比對,最大化提高靜态預測的準确率
并且規定
如果使用JAL指令且目标寄存器索引值rd=x1或rd=x5,則屬于需要進行RAS壓棧;如果使用JALR指令,則按照使用的寄存器值(rs1和rd)的不同,明确規定相應的RAS壓棧/出棧行為,軟體編譯器必須按照此原則生成彙編代碼
蜂鳥E200處理器的取指實作
E200系列處理器核的取指子系統由ITCM、BIU和核心内部取指令單元IFU完成
IFU設計思路
功能如下:
- 對取回的位址進行簡單譯碼(Mini-Decode)
- 簡單的分支預測(Simple-BPU)
- 生成取指的PC
- 根據PC位址通路ITCM或BIU
為了進行快速、連續不斷的取址,做了以下優化:
- 假定絕大多數取指發生在ITCM中,主要使用ITCM進行指令的存儲以滿足實時性的要求
- ITCM使用單周期通路的SRAM
-
對于從外部存儲器中讀取指令的特殊情況,IFU可以通過BIU使用系統存儲接口通路外部存儲器
這種情況下無法做到單周期通路,但這種情況很少,是以不做優化
- 要求軟體應當利用絕大多數取指發生在ITCM中的假定進行設計
-
IFU直接将取回的指令在同一個周期内進行部分譯碼,如果顯示目前指令為分支跳轉指令,則IFU直接在同一個周期内進行分支預測
這個優化涉及Mini-Decode和Simple-BPU兩個子產品,會在後面詳細介紹
- 由于同一個周期内完成ITCM内取址、部分譯碼、分支預測、生成下一條待取指令的PC等操作,處理器主頻會受到一定影響
- 采用最簡單的靜态預測機制
- 對向後跳轉的條件分支指令預測為真的跳轉,向前的指令則不跳轉
Mini-Decode子產品
源碼儲存在/rtl/e203/core/e203_ifu_minidec.v檔案
此處的譯碼不會完整譯出指令的是以資訊,隻需要譯出IFU所需的部分指令資訊,包括此指令是屬于普通指令還是分支跳轉指令、分支跳轉指令的類型和細節
子產品内部例化調用一個完整的decode子產品,但是将其不相關的輸入信号接零、輸出信号懸空,進而讓綜合工具能将完整子產品中的無關邏輯優化掉,這就是Mini-Decode。這樣可以避免同時維護兩份Decode子產品導緻出錯
源碼如下:
module e203_ifu_minidec(
//
// The IR stage to Decoder
input [`E203_INSTR_SIZE-1:0] instr,//取回的指令輸入該子產品進行部分譯碼
//
// The Decoded Info-Bus
output dec_rs1en,
output dec_rs2en,
output [`E203_RFIDX_WIDTH-1:0] dec_rs1idx,
output [`E203_RFIDX_WIDTH-1:0] dec_rs2idx,
output dec_mulhsu,
output dec_mul ,
output dec_div ,
output dec_rem ,
output dec_divu ,
output dec_remu ,
output dec_rv32,//訓示16/32位指令長度
output dec_bjp,//訓示普通/分支跳轉指令
output dec_jal,//屬于JAL指令?
output dec_jalr,//屬于JALR指令?
output dec_bxx,//屬于Bxx帶條件分支指令?
output [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,
output [`E203_XLEN-1:0] dec_bjp_imm
);
//例化調用完整的Decode子產品
e203_exu_decode u_e203_exu_decode(
.i_instr(instr),
.i_pc(`E203_PC_SIZE'b0),//不相關輸入信号接0
.i_prdt_taken(1'b0),
.i_muldiv_b2b(1'b0),
.i_misalgn (1'b0),
.i_buserr (1'b0),
.dbg_mode (1'b0),
.dec_misalgn(),//不相關輸出信号懸空
.dec_buserr(),
.dec_ilegl(),
.dec_rs1x0(),
.dec_rs2x0(),
.dec_rs1en(dec_rs1en),
.dec_rs2en(dec_rs2en),
.dec_rdwen(),
.dec_rs1idx(dec_rs1idx),
.dec_rs2idx(dec_rs2idx),
.dec_rdidx(),
.dec_info(),
.dec_imm(),
.dec_pc(),
.dec_mulhsu(dec_mulhsu),//其他信号正常連接配接
.dec_mul (dec_mul ),
.dec_div (dec_div ),
.dec_rem (dec_rem ),
.dec_divu (dec_divu ),
.dec_remu (dec_remu ),
.dec_rv32(dec_rv32),
.dec_bjp (dec_bjp ),
.dec_jal (dec_jal ),
.dec_jalr(dec_jalr),
.dec_bxx (dec_bxx ),
.dec_jalr_rs1idx(dec_jalr_rs1idx),
.dec_bjp_imm (dec_bjp_imm )
);
endmodule
Simple-BPU機制
用于進行簡單的分支預測,源碼位于/rtl/e203/core/e203_ifu_litebpu.v
總體上分成三個部分:
- JAL:直接跳
- Bxx:後跳前不跳
- JALR:使用rs1索引的操作數作為基位址,根據操作數的不同再分成三個小部分
- x0:直接使用x0+偏移位址立即跳轉
- x1:不需占用寄存器讀端口,直接擷取寄存器中的值,進行RAW相關性判斷
- xn:需占用寄存器讀端口,先判斷讀端口空閑,再進行RAW相關性判斷
所有的PC共享同一個加法器,在這一階段生成加法器的兩個操作數,不管前面的分支如何,總是使用立即數表示的偏移量作為一個操作數;使用三種情況下從regfile或其本身PC讀出的操作值作為另一個操作數
源碼如下:
module e203_ifu_litebpu(
//目前PC
input [`E203_PC_SIZE-1:0] pc,
//mini-decoded得出資訊
input dec_jal,
input dec_jalr,
input dec_bxx,
input [`E203_XLEN-1:0] dec_bjp_imm,
input [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,
//IR索引和用于檢查相關性的OITF狀态
input oitf_empty,
input ir_empty,
input ir_rs1en,
input jalr_rs1idx_cam_irrdidx,
//送到加法器的操作數1和2
output bpu_wait,
output prdt_taken,
output [`E203_PC_SIZE-1:0] prdt_pc_add_op1,
output [`E203_PC_SIZE-1:0] prdt_pc_add_op2,
input dec_i_valid,
//讀寄存器的rs1
output bpu2rf_rs1_ena,
input ir_valid_clr,
input [`E203_XLEN-1:0] rf2bpu_x1,
input [`E203_XLEN-1:0] rf2bpu_rs1,
input clk,
input rst_n
);
// 簡單的分支預測指令
// 所有指令共享同一個加法器
// * Bxxx:總是向後跳轉,向前不跳轉。基于目前PC位址和偏移量計算出目标位址
// * JAL: JAL将無需預測,直接跳轉
// * JALR跳轉目标計算所需的基位址來自其rs1索引的操作數,需要從通用寄存器組中讀取,e203根據rs1的索引值不同采取不同方案
// JALR的rs1 == x0 :直接使用常數0+偏移位址,無條件跳轉
// JALR的rs1 == x1 :進行特别加速,将x1從處于EXU的寄存器組中直接拉線取出(不需要占用寄存器組的讀端口)
// 需要判定目前的EXU指令沒有寫回x1且EXU中的OITF必須為空,防止出現RAW相關性
// JALR的rs1 != x0 或 x1 :目标位址在執行階段譯碼,這裡将rs1 != x0或x1的情況統稱為rs1=xn
// 需要使用Regfile的第一個讀端口(Read Port 1)從Regfile中讀取出xn,在使用前一定要判斷第一個讀端口是否空閑 // 且不存在資源沖突。為了防止正在處于EXU中執行的指令需要寫回xn造成RAW資料相關性,還要Simple-BPU判定目前EXU中 // 沒有任何指令
// 處理Bxx的跳轉
// 如果立即數表示的偏移量為負數(最高位為1),意味着向後跳轉,預測為需要跳轉;否則不跳轉
assign prdt_taken = (dec_jal | dec_jalr | (dec_bxx & dec_bjp_imm[`E203_XLEN-1]));
// 處理JARL的跳轉索引号
// 在JARL的rs1值為x1或xn時可能存在相關性
wire dec_jalr_rs1x0 = (dec_jalr_rs1idx == `E203_RFIDX_WIDTH'd0);//判定索引号是x0
wire dec_jalr_rs1x1 = (dec_jalr_rs1idx == `E203_RFIDX_WIDTH'd1);//判定索引号是x1
wire dec_jalr_rs1xn = (~dec_jalr_rs1x0) & (~dec_jalr_rs1x1);//判斷索引号是其他寄存器xn
//處理JALR的跳轉
//判斷JALR的rs1索引号是x1的情況下是否存在RAW資料相關性
wire jalr_rs1x1_dep = dec_i_valid & dec_jalr & dec_jalr_rs1x1 & ((~oitf_empty) | (jalr_rs1idx_cam_irrdidx));
//判斷JALR的rs1索引号是xn的情況下是否存在RAW資料相關性
//OITF不為空或IR寄存器中存在指令的情況下可能出現RAW相關性
//如果OITF非空,則可能有長指令正在執行,其結果可能會寫回x1;如果IR寄存器中的指令的寫回目标寄存器的索引号為x1,意味着一定存在RAW相關性
wire jalr_rs1xn_dep = dec_i_valid & dec_jalr & dec_jalr_rs1xn & ((~oitf_empty) | (~ir_empty));
//如果隻依賴IR階段(OITF非空)那麼當IR處于清空中或他沒有使用rx1索引,那麼也可以判斷不存在相關性
//OITF非空,意味着可能有長指令正在執行,其結果可能會寫回xn;如果IR寄存器中存在指令,意味着可能寫回xn,這裡采用保守估計
wire jalr_rs1xn_dep_ir_clr = (jalr_rs1xn_dep & oitf_empty & (~ir_empty))&(ir_valid_clr|(~ir_rs1en));
//需要使用Regfile的第一個端口來讀取xn的值,在此之前判斷第一個讀端口是否空閑且不存在資源沖突,如果沒問題則将第一個讀端口的使能置高
wire rs1xn_rdrf_r;
wire rs1xn_rdrf_set = (~rs1xn_rdrf_r) & dec_i_valid & dec_jalr & dec_jalr_rs1xn & ((~jalr_rs1xn_dep) | jalr_rs1xn_dep_ir_clr);
wire rs1xn_rdrf_clr = rs1xn_rdrf_r;
wire rs1xn_rdrf_ena = rs1xn_rdrf_set | rs1xn_rdrf_clr;
wire rs1xn_rdrf_nxt = rs1xn_rdrf_set | (~rs1xn_rdrf_clr);
sirv_gnrl_dfflr #(1) rs1xn_rdrf_dfflrs(rs1xn_rdrf_ena, rs1xn_rdrf_nxt, rs1xn_rdrf_r, clk, rst_n);
//征用第一個讀端口的使能信号,該信号将加載和IR寄存器位于同一級别的rs1索引寄存器進而讀取Regfile
assign bpu2rf_rs1_ena = rs1xn_rdrf_set;
//如果存在x1或xn的RAW相關性,則将bpu_wait拉高,阻止IFU生成下一個PC,等待相關性解除
//就性能而言,大多數情況下x1或xn依賴于EXU的ALU指令,需要等待1個周期ALU執行完畢寫回Regfile後才會拉低bpu_wait進而取指,流水 線中會出現一個周期的空泡性能損失;如果x1/xn和EXU中的指令沒有資料相關性則不會造成性能損失
assign bpu_wait = jalr_rs1x1_dep | jalr_rs1xn_dep | rs1xn_rdrf_set;
//為了節省面積,所有PC均共享同一個加法器
//此處生成分支預測器進行PC計算所需的操作數,将他們送給共享的加法器進行計算
// 生成加法器的操作數1:如果是Bxx或JAL指令則使用它本身的PC;如果是JALR指令則分三種情況
// 1. x0:使用常數0
// 2. x1:使用從Regfile中硬連線出來的x1值
// 3. xn:使用從Regfile第一個讀端口讀出的xn值
assign prdt_pc_add_op1 = (dec_bxx | dec_jal) ? pc[`E203_PC_SIZE-1:0]
: (dec_jalr & dec_jalr_rs1x0) ? `E203_PC_SIZE'b0
: (dec_jalr & dec_jalr_rs1x1) ? rf2bpu_x1[`E203_PC_SIZE-1:0]
: rf2bpu_rs1[`E203_PC_SIZE-1:0];
// 生成加法器的操作數2:使用立即數表示的偏移量
assign prdt_pc_add_op2 = dec_bjp_imm[`E203_PC_SIZE-1:0];
endmodule
PC生成
PC生成邏輯子產品用于産生下一個待取指令的PC
該子產品源代碼存放在/rtl/e203/core/e203_ifu_ifetch.v檔案
蜂鳥e200将PC生成分為了四種情況
-
複位後第一次取指
預設使用CPU-TOP頂層輸入信号
pc_rtvec
訓示的值作為第一次取指的PC值
通過在SoC頂層內建時将此信号賦不同值來控制PC的複位預設值
-
順序取指
根據目前指令是16位還是32位來判斷自增值
16位:PC=PC+2
32位:PC=PC+4
-
分支指令取指
使用Simple-BPU預測跳轉的目标位址
-
來自EXU的流水線沖刷
使用EXU送來的新PC值
// 控制PC自增
wire [2:0] pc_incr_ofst = minidec_rv32 ? 3'd4 : 3'd2;
wire [`E203_PC_SIZE-1:0] pc_nxt_pre;
wire [`E203_PC_SIZE-1:0] pc_nxt;
// 控制跳轉取PC
wire bjp_req = minidec_bjp & prdt_taken;
// 所有PC計算共享同一個加法器來節省面積
// 選擇加法器的輸入
wire ifetch_replay_req;
wire [`E203_PC_SIZE-1:0] pc_add_op1 =
`ifndef E203_TIMING_BOOST//}
pipe_flush_req ? pipe_flush_add_op1 :
dly_pipe_flush_req ? pc_r :
`endif//}
ifetch_replay_req ? pc_r :
bjp_req ? prdt_pc_add_op1 :
ifu_reset_req ? pc_rtvec :
pc_r;
wire [`E203_PC_SIZE-1:0] pc_add_op2 =
`ifndef E203_TIMING_BOOST//}
pipe_flush_req ? pipe_flush_add_op2 :
dly_pipe_flush_req ? `E203_PC_SIZE'b0 :
`endif//}
ifetch_replay_req ? `E203_PC_SIZE'b0 :
bjp_req ? prdt_pc_add_op2 :
ifu_reset_req ? `E203_PC_SIZE'b0 :
pc_incr_ofst ;
// 順序取指的信号,在正常情況下順序取指
assign ifu_req_seq = (~pipe_flush_req_real) & (~ifu_reset_req) & (~ifetch_replay_req) & (~bjp_req);
assign ifu_req_seq_rv32 = minidec_rv32;
assign ifu_req_last_pc = pc_r;
// 加法器計算下一條待取指令的PC初步值
assign pc_nxt_pre = pc_add_op1 + pc_add_op2;
`ifndef E203_TIMING_BOOST//}
// 出現流水線沖刷的情況下廢棄計算得到的新PC值,否則正常使用計算值
assign pc_nxt = {pc_nxt_pre[`E203_PC_SIZE-1:1],1'b0};
`else//}{
assign pc_nxt =
pipe_flush_req ? {pipe_flush_pc[`E203_PC_SIZE-1:1],1'b0} :
dly_pipe_flush_req ? {pc_r[`E203_PC_SIZE-1:1],1'b0} :
{pc_nxt_pre[`E203_PC_SIZE-1:1],1'b0};
`endif//}
......
// 産生下一條待取指令的PC值
sirv_gnrl_dfflr #(`E203_PC_SIZE) pc_dfflr (pc_ena, pc_nxt, pc_r, clk, rst_n);
通路ITCM和BIU
蜂鳥E203支援16位壓縮指令子集,在32位與16位指令交錯的情況下,IFU使用位于/rtl/e203/core/e203_ifu_ift2icb.v的非對齊通路邏輯子產品來進行處理
蜂鳥E200采用剩餘緩存技術來處理非對齊指令取指
- IFU固定取指32位
- 如果通路ITCM,由于ITCM是由SRAM構成的,上次通路後的輸出值将一直儲存,這一過程稱為Hold-up,利用這一特點省略一個64位寄存器的開銷:ITCM的SRAM(64位)輸出為一個與64位位址區間對齊的資料,這裡稱為Lane,由于CPU取指位寬32位,會連續兩次或多次通路同一個Lane,這裡第二次通路利用Hold-up特點,直接讀取其保持不變的輸出來避免重複打開SRAM
- 如果順序取指時一個32位指令非對齊地跨越了64位邊界,則會将SRAM目前輸出的最高16位存入16位寬的剩餘緩存,然後開始正常的拼接取指(參考之前所說使用剩餘緩存儲存上次取指後沒有用完的比特位供下次使用)。是以可以隻用一個周期的ITCM通路來取回32位指令
- 對于分支跳轉指令或流水下沖刷情況下的取指,需要連續發起兩次ITCM讀操作,這種情況下的性能損失無可避免
// 處理非對稱取指情況使用FSM控制
wire req_need_2uop_r;
wire req_need_0uop_r;
localparam ICB_STATE_WIDTH = 2;
// State 0: 空閑狀态下沒有特殊的取指請求
localparam ICB_STATE_IDLE = 2'd0;
// State 1: 等待響應狀态(等待非對齊讀取操作的第一次讀取狀态)
localparam ICB_STATE_1ST = 2'd1;
// State 2: 第一次和第二次讀取之間進行等待
localparam ICB_STATE_WAIT2ND = 2'd2;
// State 3: 等待非對齊讀取操作的第二次讀取狀态
localparam ICB_STATE_2ND = 2'd3;
wire [ICB_STATE_WIDTH-1:0] icb_state_nxt;
wire [ICB_STATE_WIDTH-1:0] icb_state_r;
wire icb_state_ena;
wire [ICB_STATE_WIDTH-1:0] state_idle_nxt ;
wire [ICB_STATE_WIDTH-1:0] state_1st_nxt ;
wire [ICB_STATE_WIDTH-1:0] state_wait2nd_nxt;
wire [ICB_STATE_WIDTH-1:0] state_2nd_nxt ;
wire state_idle_exit_ena ;
wire state_1st_exit_ena ;
wire state_wait2nd_exit_ena ;
wire state_2nd_exit_ena ;
// 定義一些通用的變量,等待使用
wire icb_sta_is_idle = (icb_state_r == ICB_STATE_IDLE );
wire icb_sta_is_1st = (icb_state_r == ICB_STATE_1ST );
wire icb_sta_is_wait2nd = (icb_state_r == ICB_STATE_WAIT2ND);
wire icb_sta_is_2nd = (icb_state_r == ICB_STATE_2ND );
......
// 狀态當且僅當需要退出執行階段時進行轉換
assign icb_state_ena =
state_idle_exit_ena | state_1st_exit_ena | state_wait2nd_exit_ena | state_2nd_exit_ena;
// 選擇不同資料線入口的多選器
assign icb_state_nxt =
({ICB_STATE_WIDTH{state_idle_exit_ena }} & state_idle_nxt )
| ({ICB_STATE_WIDTH{state_1st_exit_ena }} & state_1st_nxt )
| ({ICB_STATE_WIDTH{state_wait2nd_exit_ena}} & state_wait2nd_nxt)
| ({ICB_STATE_WIDTH{state_2nd_exit_ena }} & state_2nd_nxt )
;
// 選取信号
sirv_gnrl_dfflr #(ICB_STATE_WIDTH) icb_state_dfflr (icb_state_ena, icb_state_nxt, icb_state_r, clk, rst_n);
......
// 加載剩餘緩存的使能信号
assign leftover_ena = holdup2leftover_ena // 順序取指跨界時加載目前ITCM輸出的低16位
| uop1st2leftover_ena; // 非順序取指跨界時發起兩次讀操作,第一次讀操作後加載輸出的高16位
assign leftover_nxt =
// ({16{holdup2leftover_sel}} & holdup2leftover_data[15:0])
//| ({16{uop1st2leftover_sel}} & uop1st2leftover_data[15:0])
put2leftover_data[15:0]
;
assign leftover_err_nxt =
(holdup2leftover_sel & 1'b0)
| (uop1st2leftover_sel & uop1st2leftover_err)
;
// 實作剩餘緩存的寄存器
sirv_gnrl_dffl #(16) leftover_dffl (leftover_ena, leftover_nxt, leftover_r, clk);
sirv_gnrl_dfflr #(1) leftover_err_dfflr(leftover_ena, leftover_err_nxt, leftover_err_r, clk, rst_n);
蜂鳥e203的取指單元IFU、指令緊耦合寄存器ITCM、總線接口單元BIU分開實作,IFU使用ICB協定,相關協定接口存放在/rtl/e203/core/e203_ifu_ift2icb.v
基本上,IFU有兩個ICB接口,一個64位的用于通路ITCM,一個32位的用于通路BIU
CPU會根據IFU通路的位址區間進行判斷是要用ITCM-ICB還是BIU-ICB進行通路
比較判斷的代碼片段如下
// 使用比較邏輯判斷高位基位址與ITCM基位址是否相等,如果相等則進行通路
assign ifu_icb_cmd2itcm = (ifu_icb_cmd_addr[`E203_ITCM_BASE_REGION] == itcm_region_indic[`E203_ITCM_BASE_REGION]);
assign ifu2itcm_icb_cmd_valid = ifu_icb_cmd_valid & ifu_icb_cmd2itcm; // 允許通路ITCM
assign ifu2itcm_icb_cmd_addr = ifu_icb_cmd_addr[`E203_ITCM_ADDR_WIDTH-1:0];
assign ifu2itcm_icb_rsp_ready = ifu_icb_rsp_ready; // 準備信号
// 判斷如果沒有落在ITCM區域則通路BIU
assign ifu_icb_cmd2biu = 1'b1
`ifdef E203_HAS_ITCM //{
& ~(ifu_icb_cmd2itcm)
`endif//}
;
wire ifu2biu_icb_cmd_valid_pre = ifu_icb_cmd_valid & ifu_icb_cmd2biu;// 允許通路BIU
wire [`E203_ADDR_SIZE-1:0] ifu2biu_icb_cmd_addr_pre = ifu_icb_cmd_addr[`E203_ADDR_SIZE-1:0];
assign ifu2biu_icb_rsp_ready = ifu_icb_rsp_ready; // 準備信号
ITCM的特殊點
E200采用資料寬度64位的單口SRAM組成,其大小和基位址可以通過
config.v
中的宏定義參數配置
64位的SRAM在實體大小上比32位的SRAM面積更緊湊,且同一時鐘頻率下可減少動态功耗