Computer Architecture Project 2
Team Work
陳縕儂-Report Writing、測試 呂鈺達-Coding、測試
王紹丞-測試
Implementation of Pipelined CPU + Data Cache
Project 1 IF_ID.v
利用 clk 的 posedge 控制,並且判斷當 write_i 為 1 的時候,才將 input 的值 assign 給 output。
由於 instruction 中有 beq 和 jump,所以若為此兩個 instruction,可能會先 fetch 到錯誤的 instruction(因為先 fetch beq 的下一個 instruction,當最後發現 taken 時,則需要 flush 掉 Latch IF_ID 中的值)。故加一判斷,當 flush_i 為 1 時,將 output 全寫為 0。
而 Latch IF/ID 的值,只有在 clk 的 posedge 才能做更新。因為可能會有一種狀況,就是此時 經過判斷後,flush_i 的值變為 1,我們必須到下一個 cycle 才將存的內容 flush 掉,否則會 產生錯誤。所以不能讓 flush_i 改變則改變其中的值。
ID_EX.v
利用 clk 的 posedge 控制,直接將 input 的值 assign 給 output 即可。
比較特別的地方是要將 ALUSrc、ALUOp、RegDst 三個 control signal 從傳進來的 control 中分 開(因為原本是將所有的 control signal 合在一起傳進來),但 assign 到 output 時要將之分開。
EX_MEM.v
Group Members
資工三 B94902032 陳縕儂 B94902063 呂鈺達 B94902076 王紹丞
如同 Latch ID_EX 一樣,都只要在 clk 的 posedge 將 input 值 assign 給 output 即可,並且也 要將 MemRead 和 MemWrite 兩個 control signal 分開。
MEM_WB.v
如同上述兩個 Latch 一樣,都只要在 clk 的 posedge 將 input 值 assign 給 output 即可,並且 也要將 RegWrite 和 MemtoReg 兩個 control signal 分開。
Eq.v
是為了要提早判斷 instruction beq 的結果為 taken 或 non-taken,所以加此 component 判斷 data1_i 和 data2_i 兩者是否相等,若相等則 data_o 輸出 1,否則輸出 0。當此結果為 1 以 及此 instruction 為 beq 時,代表 taken,所以就要 flush 掉已存在 IF_ID 裡面的 data 了。
Adder.v
data_o 輸出 data1_in 和 data2_in 的和,是為了計算當 beq 為 taken 時的 PC 值。
PC.v
使用一個 control signal(write_i),並且判斷當 write_i 為 1 的時候才可以對 PC 做寫入的動作。
此目的是要 stall cycle 時,可以不要更改 PC 的值,讓下一個 fetch 到的 instruction 依然為同 樣的 instruction,進而達成 stall cycle 的目的。每次 PC 都是要在 clk 的 posedge 才可以更改,
若 write_i 被改為 0 的話,要在下一個 cycle 才能更改 PC 的值。
Signed_Extend.v
將 data_i 的最高位重複 16 次,然後和 data_i 自己 concatenation 起來,達到 sign extend 的 目的。
MUX_2.v, MUX5_2.v & MUX8_2.v
根據 input 的 bit 數將不同 bit 的 component 分開成不同的 module,並由 select_i 控制,若 為 0,data_o 則輸出 data1_i,否則為 data2_i。
MUX_3.v
由 select_i 控制並決定 output data 的值,如下表所示:
select_i data_o
00 data1_i
01 data2_i
10 data3_i
ShiftLeftTwo32.v & ShiftLeftTwo26.v
利用 concatenation 來達到 shift 的結果,只要將部分的 input 和 0 結合即可完成。
ALU.v
根據 instruction 並且決定 ALUCtrl 的值,控制 ALU 要做的 operation 為何,如下表所示:
ALUCtrl Instruction Action
000 AND data1_i & data2_i
001 OR data1_i | data2_i
010 ADD data1_i + data2_i
011 MUL data1_i * data2_i
110 SUB data1_i - data2_i
ALU_Control.v
由 ALUOp_i 和 funct_i 控制,ALUOp_i 為 0 則為 R-type,為 1 則為 ADDI,如下表:
ALUOp_i funct_i Instruction Action
10
100100 AND ALUCtrl = 000 100101 OR ALUCtrl = 001 100000 ADD ALUCtrl = 010 011000 MUL ALUCtrl = 011 100010 SUB ALUCtrl = 110 00
LW ALUCtrl = 010 SW ALUCtrl = 010 ADDI ALUCtrl = 010
01 BEQ ALUCtrl = 110
將之分為三種 types,分別是 R-type、I-type,以及 branch。由於每種情況要使 ALU 做不同 的加減乘除運算,所以依照應該做的運算去設定 ALUCtrl 的值(Ex.LW、SW、ADDI 要做加法,
BEQ 要做減法,都依照這些去設定 ALUCtrl 的值)。
Control.v
由於一開始的 PC 值是由 control signal 中的 Jump 和 Branch 來決定,所以若沒有將此 signal 的值歸零的話,可能會造成一開始 PC 的值就錯誤。所以我在開始之前,將所有 signal 的值 都設為 0,這樣才能得到 PC 的正確值。
由 Op_i 控制,決定下列 control signal 的值,設定如下表:
Instruction R-type LW SW Branch Jump ADDI Op_i 000000 100011 101011 000100 000010 001000
RegDst_o 1 0 0 X X 0
Jump_o 0 0 0 0 1 0
Branch_o 0 0 0 1 X 0
MemRead_o 0 1 0 0 0 0
MemtoReg_o 0 1 1 X X 0
ALUOp_o 00 00 00 01 X 0
MemWrite_o 0 0 1 0 0 0
ALUSrc_o 0 1 1 0 X 1
RegWrite_o 1 1 0 0 0 1
Hazard_Detection_Unit.v
判斷此時的 instruction 是否為 lw 並且與前一個 instruction 有 dependency,則必須 stall 一 cycle,要做以下的設定:
PC_Write_o = 0; stall cycle 後,避免更新 PC 的值,因為要使 PC 停留在同一個 instruction 繼續做。
IF_ID_o = 0; 避免 Latch IF/ID 將下一個 instruction 往下傳。
Mux8_o = 1; 此時是要 stall cycle,故將 signal 設為 0,避免它做任何事。
若不為上述狀況的 hazard,可以藉由 forwarding 來完成,故都不需要 stall cycle。(不用考慮 beq 和前面一個 instruction 有 dependency 的狀況)
Forwarding_Unit.v
用於 R-type 或是 lw 的 forwarding,分成以下兩種狀況:
1. EX hazard
先判斷前一個 instruction 為 R-type(可以藉由 control signal RegWrite 判斷),並且藉由此 時在 ID 的 instruction 和MEM 的 instruction 兩者作判斷,若相同的話,代表有 dependency,
而由於此時前一個 instruction 所作的加減乘除值已經計算出來,所以可以直接從 stage MEM 中取出 ALU 的計算結果當作 forwarding 的值來用。
2. MEM hazard
不在情況 1 之中的結果,接著判斷前兩個 instruction 是否為 lw 或是 R-type,並且藉由 此時在 ID 中的 instruction 以及 WB 中的 instruction 兩者做判斷,若相同的話,代表此 時的 instruction 和前兩個 instruction 有 dependency,不管是 lw 或是 R-type,此時的結 果都已經計算完畢,所以可以從 stage WB 中 mux5 的結果取出 forwarding 的值來計算。
新增(修改)的 Component & Detailed Cache Controller dcache_data_sram.v
將原本 1K 的 cache size 改成 256B,並且給定適當的 index bit。
dcache_tag_sram.v
此檔案做了和 dcache_data_sram.v 一樣的修改以外,也將一個 tag 的 bit 數改為 26 個(24-bit 的 tag 加上 valid bit 和 dirty bit)。
dcache_top.v
cache 的主要架構都在此 component 中,如下圖所示:
讀出 hit 和 data 的值
以上兩行 code 可以讀出 hit 和 data 的值。
從 32-bit 到 255-bit 讀出 p1 傳來的 address 中的 data
在 cache 中總共有 256-bit,我現在要把 p1 address 所指向的 32-bit data 讀出來。
做法如下:
1. 先算出 p1 的 offset 與開頭會是 cache 中這 256bit 的第幾個 bit(long_offset)。
2. 將 cache 的 256bits 往右 shift long_offset 這麼多個 bits,我們可以發現要取的 32 個 bit 就是 shift 之後的 0-31。
3. 將這 32-bit 取出即可。
以下寫出詳細的 code 內容
Wire 的接線部分
此時 cpu 傳來一組 32-bit 的 data 要存到 cache 中,我們的做法如下:
1. 先用一個 mask(bin_window)將 cache 256-bit 上要存放這 32-bit 的區間歸 0。
Ex. 這 32-bit 要放在第 128-159,則 bin_window = 11111….1000….0111…..1(第 128-159 是 0)。
2. 將此 32-bit 平移到該位置,再拿去跟原本的 256-bit 做 or 運算,就可以將 cache 的值更 新。
這裡要注意的是,我們在平移的時候需要先做 bit extension。
例如當我在將 32-bit 的 data 往左 shift long_offset = 127 那麼多 bit 時,若沒有做 bit extension 則無法得到正確的值,最後的 data 會是 0錯誤值!
以下詳細列出此部分的 code,如下:
assign hit = (sram_tag == p1_tag && sram_valid)?1:0;
assign r_hit_data[255:0] = sram_cache_data[255:0];
assign long_offset = {{27{1'b0}}, p1_offset} << 3;
reg [255:0] temp_r;
always@(p1_offset or r_hit_data or long_offset) begin temp_r = r_hit_data[255:0] >> long_offset;
p1_data[31:0] = temp_r[31:0];
end
IF_ID.v, ID_EXE.v, EXE_MEM.v & MEM_WB.v
在 Project1 中我們沒有將初始值歸零。然而在 Project2 中,若不事先歸零,則會使得 mem stall 訊號呈現”x”,讓 latch 無法將 data 往下傳(因為 stall 訊號是”x”),因此必須將 rst 的 signal 傳入每個 latch 才能讓 latch 的初值是 0。
CPU.v
宣告許多 wire 變數,將 input 和 output 的值屬於同一條線的利用 assign 接起來(如圖所示),
詳細過程不贅述。如此一來,即完成此 pipeline 的 CPU。
reg [255:0] bin_window;
reg [255:0] bin_value;
reg [255:0] temp_value;
// write data : 32-bit to 256-bit
always@(p1_offset or r_hit_data or p1_data_i or long_offset) begin bin_window = ~({{224{1'b0}}, {32{1'b1}}} << long_offset);
bin_value = {{244{1'b0}}, p1_data_i} << long_offset;
temp_value = r_hit_data[255:0] & bin_window[255:0];
w_hit_data[255:0] = temp_value | bin_value;
end
Implementation of TestBench
除了之前所新增的一些規定的 data,像是 cycle counts、pipeline stall…之類的,再 output 一 些 cache 的 data 內容, output 一些資訊讓我們知道每個 cycle 的結果,可以幫助 debug。
Cycle counts 此時 counter 的值 CPU Start 此時 start 的值
Pipeline Stall 藉由 IF_ID_Write 判斷此時是否要 stall cycle,若為 0 則代表要 stall。
並且還要加一判斷,當此時要 flush 時,代表原本 fetch 的 data 被 flush 掉,等同於 stall 一 cycle。所以也要 output Stall 為 1。
Pipeline Flush 此時 IF_ID_Flush 的值
當 count 為 150 時將所有的 cache 中的 data 存到 memory 中。
在每個 cycle 中傳入的 address 以及 hit 或 miss 的結果都 output 出來,並且 write back 時也 output 之。
Problems and solutions of this project
在 implement 這個 Project 時,由於已經有大部分的了解,以及上一個 project 的實作經驗,
所以較為容易上手,途中沒有遇到甚麼太大的問題,主要有以下兩個:
原本的 latch 中,沒有將 signal 的值歸 0,所以可能在一開始的時候,mem stall 的 signal 為”x”,由於沒有正常的值存在,所以無法將 data 往下傳。
解決方法是在所有的 latch 上加上一個 signal(rst),將之傳入 latch,並且使 latch 在開始 的時候的初始值為 0。
在平移 32-bit 並且跟原本的 256-bit 作 or 運算,若原本的 data 無法 shiftlong_offset 的 bit 數時,則會得到 data 的值為 0。
解決方法是在平移的時候先做 bit extension,這樣才會得到正確的數值。
由於測資存在上次 project 中所忽略的狀況(beq 可能產生 hazard 但無法取到正確的值)。
我們有針對此問題去討論,覺得可以 stall cycle 或是由前面已經算出來的值接到 ID stage 去 forwarding beq 的正確結果,都可以達到最後判斷正確的情況。最後助教說不需要考 慮此問題,所以我們就沒有多加了,但是我們還是有針對這樣的問題去討論過,使我們 對 computer 的 architecture 瞭解更透徹☺。