這邊使用 5 stage的架構實現。 (應該把branch留到第三個stage)
here: click here
這邊沒有特別處理control hazard的問題一率採用stall,BHT (Branch history table) 沒時間做。
data為instruction cache中的測試資料已二進位格式讀入。
data2為register file中的測試資料已二進位格式讀入。
data3為data cache中的測試資料已二進位格式讀入。
以下指令測試,均由上述檔案讀入指令後進行運算,並透過verilog中提供的 $moniter將經過運算後的儲存單元列印在終端中觀察。
這邊將SW以及LW合併再一起測試。
Instruction cache中的指令
Register file中的資料 (這邊的17行是16,因為要從0開始)
第一行為LW,將資料從data cache的第十六個位置讀出,讀出後將其放入register file中的第19個位置。
第二行為SW,將資料從register file中的第19個位置取出,取出後搬運至data cache中的第0個位置。
下圖為data cache更改前的值
下圖為更改後
會將SW & LW放在一起測試,是為了順便測試 SW以及LW中產生的data hazard,原本在先執行LW後接著執行SW會發生data hazard,因為SW的資料會是舊的資料,所以我在lock unit中去檢查不同stage的opcode,只要發生先LW後SW的情況就產生對應的控制訊號線,再從MEM_WB將要寫回register file中的data接回data cache,透過lock unit產生的控制訊號來選擇要寫入的值。
Instruction cache中的指令
Register file中的資料 (這邊的16行是15,因為要從0開始)
第一行為ADD,將兩筆數值reg[10000] 以及reg[10001] 相加後存入reg[01111]。
第二行為SUB,將兩筆數值reg[10001] 以及reg[01111] 相加後存入reg[01111]。
第三行為XOR,將兩筆數值reg[01111] 以及reg[01111] 做互斥或後存入reg[01111]。
第四行為OR,將兩筆數值reg[10000] 以及reg[10001] 做或後存入reg[01111]。
第五行為AND,將兩筆數值reg[01111] 以及reg[10110] 做AND存入reg[01111]。
在reg[01111]中的值依序為32'b00 -> 32'b11 -> 32'b01 -> 32'b00 -> 32'b11 -> 32'b11
一開始的值
ADD
SUB
XOR
OR
AND
這邊也順帶測試了Forwarding Unit是否能正常運作,去處理data hazard的問題,因為第一個指令會將結果寫進reg[01111],但是要到第五個stage才會寫回,所以如果這邊沒有Forwarding Unit的話另一個處置方式就是lock著buffer等待資料寫回,但這樣太沒效率,所以透過Forwarding Unit讓指令能不需要等待。
這邊的Forwarding Unit比對的兩個位址分別是ALU_MEM & MEM_WB後的write back addr如果跟運算使用到的位址相同,會先以ALU_MEM stage的位址選擇再來才是MEM_WB stage,因為在pipline中越靠前的stage表示越新的狀態。
Instruction cache中的指令
Register file中的資料 (這邊的12行是11,因為要從0開始)
第一行為ADDI,將立即值(000000111110) 與reg[01011] 相加後放入reg[01100]。
第二行為SLTIU (無號),比對reg[01100] 是否小於立即值(000100000000),如果小於則將reg[01100] 設置為32'b1否則不做任何事。
第三行為ORI,將reg[01100] 與立即值(100000000010) 做OR後放入reg[01100]。
第四行為SLTI (有號),比對reg[01100] 是否小於立即值(100000000001),如果小於則將reg[01100] 設置為32'b1否則不做任何事。
第五行為XORI,將reg[01100] 與立即值(000000001110) 做XOR後放入reg[01100]。
第六行為ANDI,將reg[01100] 與立即值(000000111100) 做AND後放入reg[01100]。
初始值
ADDI
SLTIU
ORI
SLTI
XORI
ANDI
這裡的六種指令都對單一暫存器單元進行運算,也順帶測試Forwarding Unit是否正常運作。
因為ALU運算單元內只有單純的dataSource1與dataSource2,透過ALUop[4]來決定進入dataSource2的資料是rs2還是立即值,所以相同模式的指令,例如ADD與ADDI,只要某一個測試通過另一個也能保證順利執行。
問題!! verilog === and s1 ^ s2
Instruction cache中的指令
Register file中的資料 (這邊的22行是21,因為要從0開始)
Time 30, deocde讀到BNE指令
有點複雜所以請先看下面兩張圖
上圖是在Time 30時從data cache讀出的BEQ指令,還未進行解碼。
在Time 35時抓入BEQ的下一個指令,而這時BEQ則在下一個stage進行解碼,這時因為BEQ比較的兩個值分別為BEQ的前兩個指令(上圖的SLLI以及SRLI),這時因為SRLI就算做了Forwarding 也沒辦法在不stall的情況下完成,所以lock unit會去檢查,如果發生hazard就產生訊號去Freeze PC & IF_ID & DEC_ALU。
如果Branch指令比較的暫存器單元位址與前面指令寫回的位址相同就會產生一個stall讓ALU有時間將正確的值運算完。
這時,如果BEQ條件成立代表要進行branch,所以branch unit會發送一個訊號回instruction cache將剛剛誤抓的指令(time 35 pcAddr = 1100) 覆蓋掉,所以在time 45時會看到romdataout為0。
Time 55 成功跳至目標位址繼續執行
Instruction cache中的指令
data cache中的資料
第一行為LW,將資料從data cache[00011]讀出後,放到reg[00101]。
第二行為ADD,將reg[00101] 與 reg[00111] 相加後,放到reg[00100]。
Register file中的資料
上圖5為ADD運算完後結果的目的地,而6則是LW將資料從data cache搬過來的目的地。
上圖為lock unit偵測到ADD使用的兩個資料有位址與LW的目的地相同,所以送出lock將DEC_ALU & IF_ID & PC這三個buffer先凍結,等到LW將正確的資料讀出後再繼續進行。
結果正確,如果沒有透過lock unit檢查,則ADD指令相加的資料就會是舊的資料,造成錯誤。
這邊比較麻煩的地方是因為Forwarding Unit,在選擇資料時的優先順序是先ALU_MEM再MEM_WB,因為正常的情況在pipeline中越靠前的指令代表越新的狀態,但是在LW這個指令卻要優先選擇MEM_WB的資料,因為前面的會是一個中途加入的空指令,為了等待資料從記憶體讀出來,所以正確的資料會在MEM_WB中。
透過lock unit產生相應的控制訊號送至IF_ID之後隨著pipeline前進後,進入Forwarding unit來決定要選擇哪一比資料。
Instruction cache中的指令
第一行為LW,將資料從data cache[10010]複製至reg[11010]
第二行為BNE,比對reg[11010] 與 reg[11011],如果不同就跳至PC + 4 + 20(圖中的第8行) 執行。
data cache中的資料
Register file中的資料
觀察上圖可以發現在LW還未將資料載入至reg[11010] (圖中第27行),這時資料的值不相同,所以如果在LW還未將資料從data cache複製過來時,就進行BNE就會發生錯誤,因為這時候的值還不是最新的。
因為我將Branch系列的指令提早到stage 2處理,所以這邊至少需要stall 兩個clock,因為資料最快也要等待stage 4結束後才會從data cache讀出,所以只能等待。
我有試過將資料一從data cache出來時,就透過Forward的方式送至branch unit,但是這樣必須透過拉長單一clock的時間才能完成,但是在衡量下後認為stall兩個clock會比拉長單一clock時間更有效率,所以採用stall兩個clock。
這邊資料剛從inst cache讀出進入IF_ID。
lock unit發現hazard,所以stall兩個clock等待資料出來
比對後發現不符合BNE條件,所以繼續從被凍結的PC開始執行
如果將資料換成不一樣
符合BNE條件,所以洗掉上一個誤抓的指令,跳轉至目標指令繼續執行
這邊也會遇到類似LW & ADD的問題,處理方也差不多。
待測試
先從github上將risc-v toolchain的原始碼複製一份下來,然後編譯以及設定一些相關參數。
然後就會得到可以支援risc-v的各式工具。
這邊主要使用riscv64-unknow 編譯錯誤 應該編譯32bits 版本待修改~
修改中…