蜂鳥E200的EXU單元
蜂鳥E200系列CPU是兩級流水線架構,其譯碼、執行、傳遞、寫回功能全部處于流水線的第二級
這些功能使用執行單元EXU完成,EXU功能如下
- 将IFU通過IR寄存器發送給EXU的指令進行譯碼與派遣(會在下面介紹)
- 通過譯碼得出的操作數寄存器索引讀取寄存器組
- 維護指令的資料相關性
- 将指令派遣給不同的運算單元執行
- 傳遞指令
- 将指令的運算結果寫回寄存器組
譯碼
經典五級流水線結構中,取指-譯碼-執行分為三個階段進行,通過譯碼讓CPU擷取指令讀取/寫回的操作數寄存器索引、指令類型、指令操作資訊等。目前高性能處理器普遍采用在每個運算單元前配置亂序發射隊列的方式,将指令的相關性解除,從發射隊列中發射出來時讀取通用寄存器組,再送給運算單元進行計算
蜂鳥E203中的譯碼器
譯碼器子產品儲存在core目錄下的e203_exu_decode.v檔案
完全由組合邏輯編寫
可以在某種程度上了解為一個超大型的case語句
module e203_exu_decode(
input [`E203_INSTR_SIZE-1:0] i_instr,//來自IFU的32位指令
input [`E203_PC_SIZE-1:0] i_pc,//IFU目前指令對應的PC值
......
input i_misalgn,//取指非對齊異常标志
input i_buserr,//取指存儲器通路錯誤标志位
......//省略一堆譯碼得到的資訊
output dec_ilegl,//非法指令标志
......
);
//内容省略,總體上就是
//1. 正常對32位和16位的指令進行譯碼
//2. 根據後面所連接配接的器件(ALU等)定義總線和寄存器
//3. 使用n輸入并行多路選擇器根據不同的指令分組,将它們的資訊複用到單路的dec_info總線上
//4. 對指令後接的操作數或立即數進行譯碼并輸出到後面的器件
//5. 根據16位或32位指令的具體情況生成立即數和寄存器索引
//6. 譯碼出不同的非法指令情形
endmodule
整數通用寄存器組
蜂鳥E200中定義了該子產品用于實作RISC-V架構中的整數通用寄存器組
由于E200屬于單發射、按順序每次寫回一條指令的微架構,是以該子產品隻需要支援最多兩個讀端口和一個寫端口
子產品相關代碼儲存在e203_exu_regfile.v檔案中
可以通過配置config.v更改通用寄存器的位數
端口邏輯
-
寫端口
通過将輸入的結果寄存器索引和各自的寄存器号進行比較,産生寫使能信号,被使能的通用寄存器即将寫資料寫入寄存器
-
讀端口
每個讀端口都是一個純粹的并行多路選擇器,使用讀操作數的寄存器索引作為選擇信号,使用專用寄存器讀取寄存器索引信号,當且僅當執行讀操作數的時候這個專用寄存器才會被調用,可以減少讀端口的動态反轉功耗
簡而言之就是在讀端口放了個門衛大爺,隻有需要讀取的時候大爺才會給寄存器索引資料開門(把寄存器索引存入該專用寄存器)
代碼片段如下
module e203_exu_regfile(
input [`E203_RFIDX_WIDTH-1:0] read_src1_idx,
input [`E203_RFIDX_WIDTH-1:0] read_src2_idx,
output [`E203_XLEN-1:0] read_src1_dat,
output [`E203_XLEN-1:0] read_src2_dat,
input wbck_dest_wen,
input [`E203_RFIDX_WIDTH-1:0] wbck_dest_idx,
input [`E203_XLEN-1:0] wbck_dest_dat,
output [`E203_XLEN-1:0] x1_r,
input test_mode,
input clk,
input rst_n
);
wire [`E203_XLEN-1:0] rf_r [`E203_RFREG_NUM-1:0];//這裡使用二維數組定義寄存器組,具體長度就能更改了
wire [`E203_RFREG_NUM-1:0] rf_wen;
`ifdef E203_REGFILE_LATCH_BASED //{
//這裡使用DFF實作通用寄存器
//因為如果使用鎖存器就必須将寫端口的DFF專門寄存一個時鐘周期(latch設計),防止鎖存器帶來的寫端口-讀端口鎖存器穿通
wire [`E203_XLEN-1:0] wbck_dest_dat_r;
sirv_gnrl_dffl #(`E203_XLEN) wbck_dat_dffl (wbck_dest_wen, wbck_dest_dat, wbck_dest_dat_r, clk);
wire [`E203_RFREG_NUM-1:0] clk_rf_ltch;
`endif//}
genvar i;//使用參數化的generate文法生成寄存器組的邏輯
generate //{
for (i=0; i<`E203_RFREG_NUM; i=i+1) begin:regfile//{
if(i==0) begin: rf0
//x0這裡是常數0,不需要産生寫邏輯
assign rf_wen[i] = 1'b0;
assign rf_r[i] = `E203_XLEN'b0;
`ifdef E203_REGFILE_LATCH_BASED //{
assign clk_rf_ltch[i] = 1'b0;
`endif//}
end
else begin: rfno0
//通過對寫寄存器的索引号和寄存器号進行比較産生寫使能——典型的&運算
assign rf_wen[i] = wbck_dest_wen & (wbck_dest_idx == i) ;
`ifdef E203_REGFILE_LATCH_BASED //{
//如果是使用鎖存器的配置則人為明确地為每個通用寄存器配置一個門控時鐘以節省功耗
//這裡就是門控時鐘的例化
e203_clkgate u_e203_clkgate(
.clk_in (clk ),
.test_mode(test_mode),
.clock_en(rf_wen[i]),
.clk_out (clk_rf_ltch[i])
);
//在這裡例化鎖存器實作通用寄存器
sirv_gnrl_ltch #(`E203_XLEN) rf_ltch (clk_rf_ltch[i], wbck_dest_dat_r, rf_r[i]);
`else//}{
//如果不使用鎖存器則例化DFF
//在這裡自動插入門控時鐘以節省功耗
sirv_gnrl_dffl #(`E203_XLEN) rf_dffl (rf_wen[i], wbck_dest_dat, rf_r[i], clk);
`endif//}
end
end//}
endgenerate//}
//每個讀端口都是一個純粹的并行多路選擇器,多路選擇器的選擇信号即讀操作數的寄存器索引
assign read_src1_dat = rf_r[read_src1_idx];
assign read_src2_dat = rf_r[read_src2_idx];
endmodule
CSR寄存器
RISC-V架構中定義了控制和狀态寄存器CSR(Control and Status Register),用于配置或記錄一些運作的狀态。這些寄存器都位于核心内部,使用自己獨立的位址編碼空間,與存儲器尋址無關,它們可以被看作“核心的外設控制寄存器”
使用專用的CSR讀寫指令來通路CSR寄存器
相關源代碼位于e203_exu_csr.v檔案下,嚴格按照RISC-V架構定義實作了各個CSR寄存器的具體功能
代碼片段如下:
module e203_exu_csr(
input csr_ena,//CSR使能信号,來自ALU
input csr_wr_en,//CSR寫操作标志位
input csr_rd_en,//CSR讀操作标志位
input [12-1:0] csr_idx,//CSR寄存器位址索引
......
output [`E203_XLEN-1:0] read_csr_dat,//讀出資料
input [`E203_XLEN-1:0] wbck_csr_dat,//寫入資料
......
);
......
//以MTVEC寄存器為例
wire sel_mtvec = (csr_idx == 12'h305);///對CSR寄存器索引進行**譯碼判斷**是否選中mtvec
wire rd_mtvec = csr_rd_en & sel_mtvec;
`ifdef E203_SUPPORT_MTVEC //{
wire wr_mtvec = sel_mtvec & csr_wr_en;
wire mtvec_ena = (wr_mtvec & wbck_csr_wen);//mtvec使能信号
wire [`E203_XLEN-1:0] mtvec_r;
wire [`E203_XLEN-1:0] mtvec_nxt = wbck_csr_dat;
//例化生成寄存器DFF
sirv_gnrl_dfflr #(`E203_XLEN) mtvec_dfflr (mtvec_ena, mtvec_nxt, mtvec_r, clk, rst_n);
wire [`E203_XLEN-1:0] csr_mtvec = mtvec_r;
`else//}{
//向量表基位址是可配置的參數,不支援軟體寫入
wire [`E203_XLEN-1:0] csr_mtvec = `E203_MTVEC_TRAP_BASE;
`endif//}
//對于讀位址不存在的CSR寄存器,傳回資料0;寫位址不存在的CSR寄存器,忽略此寫操作
//這是為了對應RISC-V要求的不産生異常
assign csr_mtvec_r = csr_mtvec;
......
//生成CSR讀操作所需的讀資料,本質上該邏輯是使用與-或方式實作的并行多路選擇器
assign read_csr_dat = `E203_XLEN'b0
//| ({`E203_XLEN{rd_ustatus }} & csr_ustatus )
| ({`E203_XLEN{rd_mstatus }} & csr_mstatus )
| ({`E203_XLEN{rd_mie }} & csr_mie )
| ({`E203_XLEN{rd_mtvec }} & csr_mtvec )
| ({`E203_XLEN{rd_mepc }} & csr_mepc )
| ({`E203_XLEN{rd_mscratch }} & csr_mscratch )
| ({`E203_XLEN{rd_mcause }} & csr_mcause )
| ({`E203_XLEN{rd_mbadaddr }} & csr_mbadaddr )
| ({`E203_XLEN{rd_mip }} & csr_mip )
| ({`E203_XLEN{rd_misa }} & csr_misa )
| ({`E203_XLEN{rd_mvendorid}} & csr_mvendorid)
| ({`E203_XLEN{rd_marchid }} & csr_marchid )
| ({`E203_XLEN{rd_mimpid }} & csr_mimpid )
| ({`E203_XLEN{rd_mhartid }} & csr_mhartid )
| ({`E203_XLEN{rd_mcycle }} & csr_mcycle )
| ({`E203_XLEN{rd_mcycleh }} & csr_mcycleh )
| ({`E203_XLEN{rd_minstret }} & csr_minstret )
| ({`E203_XLEN{rd_minstreth}} & csr_minstreth)
| ({`E203_XLEN{rd_counterstop}} & csr_counterstop)// Self-defined
| ({`E203_XLEN{rd_mcgstop}} & csr_mcgstop)// Self-defined
| ({`E203_XLEN{rd_itcmnohold}} & csr_itcmnohold)// Self-defined
| ({`E203_XLEN{rd_mdvnob2b}} & csr_mdvnob2b)// Self-defined
| ({`E203_XLEN{rd_dcsr }} & csr_dcsr )
| ({`E203_XLEN{rd_dpc }} & csr_dpc )
| ({`E203_XLEN{rd_dscratch }} & csr_dscratch)
;
endmodule
執行
五級流水線架構中的執行需要譯碼之後執行,根據指令的具體操作類型将指令配置設定給不同的運算單元執行,常見的運算單元如下:
- 算術邏輯運算單元(ALU):負責普通邏輯運算、加減法運算、移位運算等
- 整數乘法單元:主要負責有符号數或無符号整數的乘法
- 整數除法單元:主要負責有符号數或無符号整數的除法
- 浮點運算單元(FPU):比較複雜,通常會分成多個不同的獨立運算單元
對于其他具有特殊指令的處理器核,會相應增加特殊的運算單元(比如可以在處理器旁挂載DSP等硬體加速電路)
指令發射順序
發射(Issue)或者說派遣(Dispatch)并不是經典五級流水線中的常見概念,但多用于各類RISC架構CPU,RISC-V中也使用了這一定義
發射:指令經過譯碼之後被派發到不同的運算單元執行的過程
發射和派遣可以混用,蜂鳥E200處理器流水線中使用派遣(Dispatch)作為定義
根據每個周期一次能發射的指令個數,可分為單發射和多發射處理器。
特别地,在一些高端的超标量處理器核中,流水線級數很多,使得派遣和發射有不同的含義:派遣表示指令經過譯碼之後被派發到不同的運算單元的等待隊列中這一過程;發射則表示指令從運算單元的等待隊列中發射到運算單元開始執行的過程。
根據發射、執行、寫回順序不同,往往分成以下流派:
-
順序發射、順序執行、順序寫回
性能比較低,硬體實作最簡單,面積最小
往往在最簡單流水線的處理器核中使用
-
順序發射、亂序執行、順序寫回
在指令的執行階段由不同的運算單元同時執行不同的指令,這樣規避了不同運算處理時間不同的問題,提高處理性能;最終寫回時還是要按照順序寫回,是以很多時候ALU要等待其他指令先寫回而将其運算單元本身的流水線停滞
具有比較好的性能,面積稍大一些
-
順序發射、亂序執行、亂序寫回
在上述亂序執行的基礎上讓運算單元亂序寫回,又分成了幾個不同的方法
-
重排序緩存法
使用Re-Orde Buffer(ROB)重排序緩存将ALU執行的結果暫存,最後由ROB順序寫回寄存器組
存在面積過大、動态功耗較大的問題
但是性能很好,實作方案很典型、成熟
-
實體寄存器組法
使用一個統一的實體寄存器組動态管理邏輯寄存器組的映射關系,ALU執行完畢後就将結果亂序寫回實體寄存器組,實體寄存器組和邏輯寄存器組之間的映射關系可以改變
控制複雜,功耗有所優化
-
直接亂序寫回法
讓沒有資料相關性的執行結果直接寫回寄存器組,有資料相關性的執行結果順序寫回
隻對部分程式有優化,需要增設電路
- 其他方法
-
-
順序派遣、亂序發射、亂序執行、亂序寫回
往往在高性能的超标量處理器中使用這種架構。
基本上可以看作上面所有高性能操作的融合體
分支解析
取指階段的分支預測功能對于帶條件的分支指令,由于器條件解析需要進行操作數運算,是以需要在執行階段進行運算并判斷該分支指令是否真的需要跳轉,并按照之前規定的分支預測算法進行對比執行,如果預測錯誤很可能需要進行流水線沖刷、造成性能損失
一般為了減少性能損失,會在比較靠前的流水線位置進行分支解析
蜂鳥E200系列的指令發射派遣
蜂鳥E200系列CPU的發射和派遣實際上指的是同一個行為:即指令經過譯碼之後被派發到不同的運算單元執行的總過程
該部分使用Dispatch子產品和ALU子產品共同完成
Dispatch子產品負責向ALU子產品轉發派遣任務
ALU部分負責傳遞子產品和前級的接口
蜂鳥E200系列的派遣特點如下:
- 所有指令必須被派遣給ALU,通過ALU與傳遞子產品接口進行傳遞;如果是長指令,也需要通過ALU進一步發送至相應的長指令運算單元
- 實際的派遣功能發生在ALU内部。因為譯碼部分已經根據指令的運算單元進行了初步分組并譯碼出了其相應的訓示信号,可以按照其訓示信号将指令派遣給相應的運算單元
- 在派遣子產品中處理流水線沖突問題,包括資源沖突和資料相關性沖突,并在某些特殊情況下将流水線的派遣點阻塞
流水線沖突、長指令和OITF處理
流水線沖突包括資源沖突和資料沖突兩類,這兩種沖突都會導緻流水線阻塞。蜂鳥E203采用了兩種方法分别處理資源沖突和資料沖突
資料沖突
資料沖突顧名思義,就是由于資料相關性引起的沖突
蜂鳥E203采用巧妙的方法處理資料沖突:将所有指令分成兩類,将資料相關性分為三類,通過長指令拼接和流水線沖刷的方式進行處理
詳細内容在下面的長指令和OITF處理部分給出
資源沖突
資料沖突的概念在之前已經給出,這裡介紹一下資源沖突
資源沖突通常發生在指令派遣給不同的執行單元進行執行的過程中,當一個指令被執行時耗費的時鐘周期較長,此後又有其他指令被派發給同一個硬體子產品進行處理的情況下便會出現資源沖突的情況——後續的指令需要等待前一個指令完成操作後将硬體子產品釋放出來後才能得到執行。
蜂鳥E203的接口實作采用了嚴謹的valid-ready握手接口,一旦某個子產品出現了資源沖突,它便會輸出ready=0的信号,即使另一側valid=1,也無法完成握手,是以前一級子產品無法進行配置設定指令,将會進入等待狀态,直到ready=1
長指令和OITF處理
蜂鳥E203将所有需要執行的指令分為兩類:
-
單周期執行指令
蜂鳥E203的傳遞和寫回功能均處于流水線的第二級,單周期執行指令在這一級就完成了傳遞和寫回
-
多周期執行指令
這種指令通常需要多個時鐘周期才能完成執行并寫回,是以也稱為“後傳遞長流水線指令”,簡稱為長指令
長指令的執行過程比較特殊
為了在很多時鐘周期後傳遞長指令,需要先檢測出資料相關性,蜂鳥E203采用了一個稱為OITF(Outstanding Instructions Track FIFO長指令追蹤隊列)的子產品檢測與長指令相關的RAW和WAW相關性
之是以不檢測WAR相關性,是因為E203是按序派遣、按序寫回的微架構,在派遣時就已經從寄存器組中讀取了源操作數,是以寫回Regfile操作不會發生在讀取Regfile源操作數之前。
言歸正傳,OITF本質上是一個普通的FIFO(廢話),它的源碼在rtl/e203/core/e203_exu_oitf.v中可以檢視
在派遣點,每派遣一個長指令,便會在OITF中配置設定一個表項,在這個表項中會存儲該長指令的源操作數寄存器索引和結果寄存器索引
在寫回點,每次按序寫回一個長指令後,就會将此指令在OITF中的表象去除——他就從FIFO中退出了
綜上所述,OITF本質上儲存了已經被派遣但是尚未寫回的長指令資訊
為簡單起見,這裡就不附錄相關代碼了,感興趣的讀者可以自行翻閱源碼
資源沖突的解決思路
蜂鳥E203采用了阻塞流水線的解決思路,并沒有将長指令的結果直接快速旁路給後續的待派遣指令來解決資料沖突,也沒有增加更多硬體子產品處理資源沖突,這是因為蜂鳥E203的設計思路秉承“小面積”,放棄了更高的性能,轉而實作較高的性能-面積比。如果設計高性能的CPU,則顯然不能簡單地使用這種思路
ALU子產品
蜂鳥E203的ALU單元位于EXU之下,主要包括5個子子產品,它們共性同一份實際的運算資料通路,是以主要資料通路的面積開銷隻有一份
- 普通ALU:主要負責邏輯運算、加減法、移位運算等通用的ALU指令
- 訪存位址生成:主要負責Load、Store和“A”擴充指令的位址生成、“A”擴充指令的微操作拆分和執行
- 分支預測解析:主要負責Branch和Jump指令的結果解析和執行
- CSR讀寫控制:主要負責CSR讀寫指令的執行
- 多周期乘除法器:主要負責乘法和除法指令的執行
普通ALU
位于rtl/e203/core/e203_exu_alu_rglr.v
該子產品完全由組合邏輯電路構成(也就是說這玩意在FPGA裡可以隻占用一點點LUT),它本身并沒有運算資料通路,其主要邏輯根據普通ALU的指令類型發起對共享運算資料通路的操作請求,并從共享的運算資料通路中取回運算結果
訪存位址生成
該子產品簡稱AGU(Adress Generation Unit),位于rtl/e203/core/e203_exu_alu_lsuagu.v
相關内容會在存儲器架構部分詳細介紹
分支預測解析
位于rtl/e203/core/e203_exu_alu_bjp.v
BJP(Branch and Jump resolve)子產品是分支跳轉指令進行傳遞的主要依據,可以檢視傳遞部分進行了解
CSR讀寫控制
該子產品主要負責CSR讀寫指令的執行,位于rtl/e203/core/e203_exu_alu_csrctrl.v
這個子產品也是完全由組合邏輯組成,其根據CSR讀寫指令的類型産生讀寫CSR寄存器子產品的控制信号
代碼片段如下:
`include "e203_defines.v"
module e203_exu_alu_csrctrl(
//握手接口
input csr_i_valid, // valid信号
output csr_i_ready, // ready信号
input [`E203_XLEN-1:0] csr_i_rs1,
input [`E203_DECINFO_CSR_WIDTH-1:0] csr_i_info,
input csr_i_rdwen,
output csr_ena, // CSR讀寫使能信号
output csr_wr_en, // CSR寫操作訓示信号
output csr_rd_en, // CSR讀操作訓示信号
output [12-1:0] csr_idx, // CSR寄存器的位址索引
input csr_access_ilgl,
input [`E203_XLEN-1:0] read_csr_dat, // 讀操作從CSR寄存器子產品讀出的資料
output [`E203_XLEN-1:0] wbck_csr_dat, // 寫操作寫入CSR寄存器子產品的資料
`ifdef E203_HAS_CSR_NICE//{
input nice_xs_off,
output csr_sel_nice,
output nice_csr_valid,
input nice_csr_ready,
output [31:0] nice_csr_addr,
output nice_csr_wr,
output [31:0] nice_csr_wdata,
input [31:0] nice_csr_rdata,
`endif//}
//CSR寫回/傳遞接口
output csr_o_valid, // valid信号
input csr_o_ready, // ready信号
// 為了非對齊lst和AMO指令使用的特殊寫回接口
output [`E203_XLEN-1:0] csr_o_wbck_wdat,
output csr_o_wbck_err,
input clk,
input rst_n
);
`ifdef E203_HAS_CSR_NICE//{
// If accessed the NICE CSR range then we need to check if the NICE CSR is ready
assign csr_sel_nice = (csr_idx[11:8] == 4'hE);
wire sel_nice = csr_sel_nice & (~nice_xs_off);
wire addi_condi = sel_nice ? nice_csr_ready : 1'b1;
assign csr_o_valid = csr_i_valid
& addi_condi; // Need to make sure the nice_csr-ready is ready to make sure
// it can be sent to NICE and O interface same cycle
assign nice_csr_valid = sel_nice & csr_i_valid &
csr_o_ready;// Need to make sure the o-ready is ready to make sure
// it can be sent to NICE and O interface same cycle
assign csr_i_ready = sel_nice ? (nice_csr_ready & csr_o_ready) : csr_o_ready;
assign csr_o_wbck_err = csr_access_ilgl;
assign csr_o_wbck_wdat = sel_nice ? nice_csr_rdata : read_csr_dat;
assign nice_csr_addr = csr_idx;
assign nice_csr_wr = csr_wr_en;
assign nice_csr_wdata = wbck_csr_dat;
`else//}{
wire sel_nice = 1'b0;
assign csr_o_valid = csr_i_valid;
assign csr_i_ready = csr_o_ready;
assign csr_o_wbck_err = csr_access_ilgl;
assign csr_o_wbck_wdat = read_csr_dat;
`endif//}
//從Info Bus中取出相關資訊
wire csrrw = csr_i_info[`E203_DECINFO_CSR_CSRRW ];
wire csrrs = csr_i_info[`E203_DECINFO_CSR_CSRRS ];
wire csrrc = csr_i_info[`E203_DECINFO_CSR_CSRRC ];
wire rs1imm = csr_i_info[`E203_DECINFO_CSR_RS1IMM];
wire rs1is0 = csr_i_info[`E203_DECINFO_CSR_RS1IS0];
wire [4:0] zimm = csr_i_info[`E203_DECINFO_CSR_ZIMMM ];
wire [11:0] csridx = csr_i_info[`E203_DECINFO_CSR_CSRIDX];
//生成操作數1,如果使用立即數則選擇立即數,否則選擇源寄存器1
wire [`E203_XLEN-1:0] csr_op1 = rs1imm ? {27'b0,zimm} : csr_i_rs1;
//根據指令的資訊生成讀操作訓示信号
assign csr_rd_en = csr_i_valid &
(
(csrrw ? csr_i_rdwen : 1'b0) // the CSRRW only read when the destination reg need to be writen
| csrrs | csrrc // The set and clear operation always need to read CSR
);
//根據指令的資訊生成寫操作訓示信号
assign csr_wr_en = csr_i_valid & (
csrrw // CSRRW always write the original RS1 value into the CSR
| ((csrrs | csrrc) & (~rs1is0)) // for CSRRS/RC, if the RS is x0, then should not really write
);
//生成通路CSR寄存器的位址索引
assign csr_idx = csridx;
//生成送到CSR寄存器子產品的CSR讀寫使能信号
assign csr_ena = csr_o_valid & csr_o_ready & (~sel_nice);
//生成寫操作寫入CSR寄存器子產品的資料
assign wbck_csr_dat =
({`E203_XLEN{csrrw}} & csr_op1)
| ({`E203_XLEN{csrrs}} & ( csr_op1 | read_csr_dat))
| ({`E203_XLEN{csrrc}} & ((~csr_op1) & read_csr_dat));
endmodule
多周期乘除法器
蜂鳥E200系列使用了兩種乘除法解決方案
對于蜂鳥E203,它配置了低性能小面積的多周期乘除法器,而對于其他性能較高的裝置,則使用了高性能的單周期乘法器和獨立的除法器
常用的多周期乘除法器和除法器實作,一般采用下面的理論實作:
- 有符号整數乘法:使用常用的Booth編碼産生部分積,然後使用疊代的方法,每個周期使用加法器對部分積進行累加,經過多個周期的疊代後得到最終的乘積,進而實作多周期乘法器
- 有符号整數除法:使用常用的加減交替法,然後使用疊代的方法,每個周期使用加法器産生部分餘數,經過多個周期的疊代後得到最終商和玉樹,進而實作多周期除法器
兩個子產品的理論内容可參考數電教材或相關書籍
因為兩個子產品都以加法器為核心并使用一組寄存器儲存部分積或部分餘數,是以在蜂鳥E203中使用了資源複用——将多周期乘除法器合并作為ALU的一個子單元,二者共享資料通路中的加法器,經過多個周期完成乘法或者除法操作
多周期乘除法器MDV子產品位于rtl/e203/core/e203_exu_alu_muldiv.v
同時蜂鳥E203對乘除法進行了以下優化:
- 乘法操作中,為了減少所需周期數,采用了基4(Radix-4)的Booth編碼,并對無符号乘法進行一位符号擴充後統一當作有符号數進行運算,是以需要17個疊代周期
- 除法操作中,使用了普通的加減交替法,同樣對于無符号乘法統一進行一位符号擴充後當作有符号數進行運算,需要33個疊代周期。此外,由于加減交替法所得結果存在1比特精度的問題,還需要額外的1個時鐘周期判斷是否需要進行商和餘數的矯正,還有額外2個周期的商和餘數矯正,最終才能得到準确的除法結果
- MDV子產品隻進行運算控制,沒有自己的加法器,加法器與其他ALU子單元複用共享的運算資料通路
- MDV子產品也沒有自己的寄存器,寄存器與AGU單元複用
綜上所述,MDV實際上隻是一個狀态機,其乘法實作需要17個疊代周期,除法實作需要最多36個周期,采用了典型的“速度換面積”思想
運算資料通路
事實上ALU真正用于計算的子產品是資料通路,位于rtl/e203/core/e203_exu_alu_dpath.v
它被動接受其他ALU子單元的請求來進行具體運算,然後将計算結果傳回非其他子單元運算資料通路
可以說ALU的其他子單元隻是一套針對不同指令選擇不同邏輯的狀态機(控制系統),而資料主要經過的運算資料通路才是ALU的運算核心,整個ALU是類似“衆星捧月”的結構——占據面積最大的運算資料通路在中間,資料流經過時會被周邊的狀态機挑選,或單次通過(普通ALU)或反複通過并輸出不同結果到寄存器(多周期乘除法器),這就使得ALU面積大大縮小
高性能乘除法運算
除了小面積的多周期乘除法器外,其他型号的蜂鳥E200還配備了高性能的單周期乘法器和獨立的除法器
高性能乘法器會被部署在流水線第二級,除法器則仍然使用多周期除法器,但不再與ALU複用共享的運算資料通路,而是作為長指令擁有單獨的除法器單元,同樣部署在流水線第二級
浮點單元
蜂鳥E200系列支援RISC-V的“F”和“D”擴充子集,可以處理單精度和雙精度浮點指令
浮點指令由FPU支援,如果配置了FPU,則FPU作為長指令擁有獨立的運算單元,并且FPU還具有獨立的通用浮點寄存器組。包含F和D擴充子集的子產品要求包含32個通用浮點寄存器,其中如果僅包含F的話,浮點指令子集通用浮點寄存器的寬度為32位,僅包含D的浮點指令子集通用浮點寄存器的寬度為64位
蜂鳥E200系列的FPU支援以下功能
- 獨立的時鐘門控
- 獨立電源域
- 單雙精度浮點指令複用資料通路
但是開源的蜂鳥E203并沒有配備FPU(悲)
總結
這部分簡要介紹了蜂鳥E203的執行單元中譯碼和執行兩個環節
寫回、傳遞還有其他的一些單元因為涉及到篇幅會在之後的寫回、傳遞、存儲器相關博文中介紹
EXU部分是蜂鳥E203的核心環節,代碼量也很多,是以需要反複了解