手寫一個簡易的多週期 MIPS CPU
一點前言
多週期 CPU 相比單週期 CPU 以及流水線的實現來說其實寫起來要麻煩那麼一些,但是相對於流水線以及單週期 CPU 而言,多週期 CPU 除了能提升主頻之外似乎並沒有什麼卵用。不過我的課題是多週期 CPU 那麼就開始吧。
多週期 CPU
不同於單週期 CPU,多週期 CPU 指的是將整個 CPU 的執行過程分成幾個階段,每個階段用一個時鐘去完 成,然後開始下一條指令的執行,而每種指令執行時所用的時鐘數不盡相同,這就是所謂的多週期CPU。
CPU在處理指令時,一般需要經過以下幾個階段:
(1) 取指令(IF):根據程式計數器 PC 中的指令地址,從儲存器中取出一條指令,同時,PC 根據指令字長度自動遞增產生下一條指令所需要的指令地址,但遇到“地址轉移”指令 時,則控制器把“轉移地址”送入 PC,當然得到的“地址”需要做些變換才送入 PC。
(2) 指令譯碼(ID):對取指令操作中得到的指令進行分析並譯碼,確定這條指令需要完成的操作,從而產生相應的操作控制訊號,用於驅動執行狀態中的各種操作。
(3) 指令執行(EXE):根據指令譯碼得到的操作控制訊號,具體地執行指令動作,然後轉移到結果寫回狀態。
(4) 儲存器訪問(MEM):所有需要訪問儲存器的操作都將在這個步驟中執行,該步驟給出儲存器的資料地址,把資料寫入到儲存器中資料地址所指定的儲存單元或者從儲存器中得 到資料地址單元中的資料。
(5) 結果寫回(WB):指令執行的結果或者訪問儲存器中得到的資料寫回相應的目的暫存器中。
這也就意味著一條 CPU 指令最長需要 5 個時鐘週期才能執行完畢,至於具體需要多少週期則根據指令的不同而不同。
MIPS 指令集的設計為定長簡單指令集,這為 CPU 的實現帶來了極大的方便。
指令集
MIPS 指令分為三種:R、I 和 J,三種指令有不同的儲存方式:
其中,
- op:操作碼;
- rs:第1個源運算元暫存器,暫存器地址(編號)是00000~11111,00~1F;
- rt:第2個源運算元暫存器,或目的運算元暫存器,暫存器地址(同上);
- rd:目的運算元暫存器,暫存器地址(同上);
- sa:位移量(shift amt),移位指令用於指定移多少位;
- funct:功能碼,在暫存器型別指令中(R型別)用來指定指令的功能;
- immediate:16位立即數,用作無符號的邏輯運算元、有符號的算術運算元、資料載入(Load)/資料儲存(Store)指令的資料地址位元組偏移量和分支指令中相對程式計數器(PC)的有符號偏移量;
- address:地址。
在執行指令的過程中,需要在不同的時鐘週期之間進行狀態轉移:
本簡易 CPU 姑且只實現以下指令:
OpCode | 指令 | 功能 |
---|---|---|
000000 | add rd, rs, rt | 帶符號加法運算 |
000001 | sub rd, rs, rt | 帶符號減法運算 |
000010 | addiu rt, rs, immediate | 無符號加法運算 |
010000 | and rd, rs, rt | 與運算 |
010001 | andi rt, rs, immediate | 對立即數做 0 擴充套件後進行與運算 |
010010 | ori rt, rs, immediate | 對立即數做 0 擴充套件後做或運算 |
010011 | xori rt, rs, immediate | 對立即數做 0 擴充套件後做異或運算 |
011000 | sll rd, rt, sa | 左移指令 |
100110 | slti rt, rs, immediate | 比較指令 |
100111 | slt rd, rs, rt | 比較指令 |
110000 | sw rt, immediate(rs) | 存數指令 |
110001 | lw rt, immediate(rs) | 讀數指令 |
110100 | beq rs, rt, immediate | 分支指令,相等時跳轉 |
110101 | bne rs, rt, immediate | 分支指令,不等時跳轉 |
110110 | bltz rs, immediate | 分支指令,小於 0 時跳轉 |
111000 | j addr | 跳轉指令 |
111001 | jr rs | 跳轉指令 |
111010 | jal addr | 呼叫子程式指令 |
111111 | halt | 停機指令 |
控制單元
一個簡易的多週期 CPU 的資料通路圖如下:
三個 D 觸發器用於儲存當前狀態,是時序邏輯電路,RST用於初始化狀態“000“,另外兩個部分都是組合邏輯電路,一個用於產生 下一個階段的狀態,另一個用於產生每個階段的控制訊號。從圖上可看出,下個狀態取決於 指令操作碼和當前狀態;而每個階段的控制訊號取決於指令操作碼、當前狀態和反映運算結果的狀態 zero 標誌和符號 sign標誌。
其中指令和資料各儲存在不同儲存器中,即有指令儲存器和資料儲存器。訪問儲存器時,先給出記憶體地址,然後由讀或寫訊號控制操作。對於暫存器組, 給出暫存器地址(編號),讀操作時不需要時鐘訊號,輸出端就直接輸出相應資料;而在寫操作時,在 WE使能訊號為 1時,在時鐘邊沿觸發將資料寫入暫存器。
IR 指令暫存器目的是使指令程式碼保持穩定,PC 寫使能控制訊號PCWre,是確保PC 適時修改,原因都是和多週期工作的CPU有關。ADR、BDR、 ALUoutDR、DBDR四個暫存器不需要寫使能訊號,其作用是切分資料通路,將大組合邏輯切分為若干個小組合邏輯,大延遲變為多個分段小延遲。
各控制訊號功能如下:
控制訊號名 | 狀態 0 | 狀態 1 |
---|---|---|
RST | 對於PC,初始化PC為程式首地址 | 對於PC,PC接收下一條指令地址 |
PCWre | PC不更改,另 外,除‘000’狀態之外,其餘狀態慎改PC的值。 | PC更改,另外,在‘000’狀態時,修改PC的值合適。 |
ALUSrcA | 來自暫存器堆 data1 輸出 | 來自移位數sa,同時,進行(zeroextend)sa,即 {{27{1'b0},sa} |
ALUSrcB | 來自暫存器堆 data2 輸出 | 來自 sign或 zero 擴充套件的立即數 |
DBDataSrc | 來自ALU運算結果的輸出 | 來自資料儲存器(Data MEM)的輸出 |
RegWre | 無寫暫存器組暫存器 | 暫存器組暫存器寫使能 |
WrRegDSrc | 寫入暫存器組暫存器的資料來自 PC+4(PC4) | 寫入暫存器組暫存器的資料來自ALU 運算結果或儲存器讀出的資料 |
InsMemRW | 寫指令儲存器 | 讀指令儲存器(Ins. Data) |
mRD | 儲存器輸出高阻態 | 讀資料儲存器 |
mWR | 無操作 | 寫資料儲存器 |
IRWre | IR(指令暫存器)不更改 | IR 暫存器寫使能。向指令儲存器發出讀指令程式碼後,這個訊號也接著發出,在時鐘上升沿,IR 接收從指令儲存器送來的指令程式碼。 |
ExtSel | 零擴充套件 | 符號擴充套件 |
PCSrc[1..0] | 00:PC<-PC+4 01:PC<-PC+4+((sign-extend)immediate<<2) 10:PC<-rs 11:PC<-{PC[31:28], addr[27:2],2'b00} |
RegDst[1..0] | 寫暫存器組暫存器的地址,來自: 00:0x1F($31) 01:rt 欄位 10:rd 欄位 11:未用 |
ALUOp[2..0] | ALU 8種運算功能選擇(000-111) |
相關部件及引腳說明
Instruction Memory:指令儲存器
- Iaddr,指令地址輸入埠
- DataIn,儲存器資料輸入埠
- DataOut,儲存器資料輸出埠
- RW,指令儲存器讀寫控制訊號,為0 寫,為 1讀
Data Memory:資料儲存器
- Daddr,資料地址輸入埠
- DataIn,儲存器資料輸入埠
- DataOut,儲存器資料輸出埠
- /RD,資料儲存器讀控制訊號,為 0 讀
- /WR,資料儲存器寫控制訊號,為0 寫
Register File:暫存器組
- Read Reg1,rs 暫存器地址輸入埠
- Read Reg2,rt 暫存器地址輸入埠
- Write Reg,將資料寫入的暫存器,其地址輸入埠(rt、rd)
- Write Data,寫入暫存器的資料輸入埠
- Read Data1,rs 暫存器資料輸出埠
- Read Data2,rt 暫存器資料輸出埠
- WE,寫使能訊號,為1 時,在時鐘邊沿觸發寫入
IR: 指令暫存器,用於存放正在執行的指令程式碼
ALU: 算術邏輯單元
- result,ALU運算結果
- zero,運算結果標誌,結果為 0,則 zero=1;否則 zero=0
- sign,運算結果標誌,結果最高位為0,則 sign=0,正數;否則,sign=1,負數
ALU
ALU 為算術邏輯運算單元,功能如下:
ALUOp[2..0] | 功能 | 功能 |
---|---|---|
000 | Y=A+B | 加法運算 |
001 | Y=A-B | 減法運算 |
010 | Y=B<<A | 左移運算 |
011 | Y=A∨B | 或運算 |
100 | Y=A∧B | 與運算 |
101 | Y=(A<B) ? 1 : 0 | 無符號比較 |
110 | Y=(((A<B)&&(A[31] == B[31])) || ((A[31]==1&& B[31] == 0))) ? 1 : 0 | 帶符號比較 |
111 | Y=A⊕B | 異或 |
模組設計
符號定義
為了更加明晰程式程式碼,並避免因二進位制程式碼書寫錯誤導致的問題,對狀態碼、操 作碼等做出如下定義:
```verilog `define ALU_OP_ADD 3'b000 `define ALU_OP_SUB 3'b001 `define ALU_OP_SLL 3'b010 `define ALU_OP_OR 3'b011 `define ALU_OP_AND 3'b100 `define ALU_OP_LT 3'b101 `define ALU_OP_SLT 3'b110 `define ALU_OP_XOR 3'b111 `define OP_ADD 6'b000000 `define OP_SUB 6'b000001 `define OP_ADDIU 6'b000010 `define OP_AND 6'b010000 `define OP_ANDI 6'b010001 `define OP_ORI 6'b010010 `define OP_XORI 6'b010011 `define OP_SLL 6'b011000 `define OP_SLTI 6'b100110 `define OP_SLT 6'b100111 `define OP_SW 6'b110000 `define OP_LW 6'b110001 `define OP_BEQ 6'b110100 `define OP_BNE 6'b110101 `define OP_BLTZ 6'b110110 `define OP_J 6'b111000 `define OP_JR 6'b111001 `define OP_JAL 6'b111010 `define OP_HALT 6'b111111 `define PC_NEXT 2'b00 `define PC_REL_JUMP 2'b01 `define PC_REG_JUMP 2'b10 `define PC_ABS_JUMP 2'b11 `define STATE_IF 3'b000 `define STATE_ID 3'b001 `define STATE_EXE_AL 3'b110 `define STATE_EXE_BR 3'b101 `define STATE_EXE_LS 3'b010 `define STATE_MEM 3'b011 `define STATE_WB_AL 3'b111 `define STATE_WB_LD 3'b100 ```控制單元
狀態轉移
```verilog always @(posedge CLK or negedge RST) begin if (!RST) State <= `STATE_IF; else begin case (State) `STATE_IF: State <= `STATE_ID; `STATE_ID: begin case (OpCode) `OP_ADD, `OP_SUB, `OP_ADDIU, `OP_AND, `OP_ANDI, `OP_ORI, `OP_XORI, `OP_SLL, `OP_SLTI, `OP_SLT: State <= `STATE_EXE_AL; `OP_BNE, `OP_BEQ, `OP_BLTZ: State <= `STATE_EXE_BR; `OP_SW, `OP_LW: State <= `STATE_EXE_LS; `OP_J, `OP_JAL, `OP_JR, `OP_HALT: State <= `STATE_IF; default: State <= `STATE_EXE_AL; endcase end `STATE_EXE_AL: State <= `STATE_WB_AL; `STATE_EXE_BR: State <= `STATE_IF; `STATE_EXE_LS: State <= `STATE_MEM; `STATE_WB_AL: State <= `STATE_IF; `STATE_MEM: begin case (OpCode) `OP_SW: State <= `STATE_IF; `OP_LW: State <= `STATE_WB_LD; endcase end `STATE_WB_LD: State <= `STATE_IF; default: State <= `STATE_IF; endcase end end ```控制訊號
不同控制訊號根據不同的操作碼得到,因此可以列出對於不同操作碼的各控制訊號的真值表:
Op | PCWre | ALUSrcA | ALUSrcB | DBDataSrc | RegWre | WrRegDSrc | InsMemRW | mRD | mWR | IRWre | ExtSel | PCSrc | RegDst | ALUOp |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
add | 0 | 0 | 0 | 0 | 1 | 1 | 1 | X | X | 1 | X | 00 | 10 | 000 |
sub | 0 | 0 | 0 | 0 | 1 | 1 | 1 | X | X | 1 | X | 00 | 10 | 001 |
addiu | 0 | 0 | 1 | 0 | 1 | 1 | 1 | X | X | 1 | 1 | 00 | 01 | 000 |
and | 0 | 0 | 0 | 0 | 1 | 1 | 1 | X | X | 1 | X | 00 | 10 | 100 |
andi | 0 | 0 | 1 | 0 | 1 | 1 | 1 | X | X | 1 | 0 | 00 | 01 | 100 |
ori | 0 | 0 | 1 | 0 | 1 | 1 | 1 | X | X | 1 | 0 | 00 | 01 | 011 |
xori | 0 | 0 | 1 | 0 | 1 | 1 | 1 | X | X | 1 | 0 | 00 | 01 | 111 |
sll | 0 | 1 | 0 | 0 | 1 | 1 | 1 | X | X | 1 | X | 00 | 10 | 010 |
slti | 0 | 0 | 1 | 0 | 1 | 1 | 1 | X | X | 1 | 1 | 00 | 01 | 110 |
slt | 0 | 0 | 0 | 0 | 1 | 1 | 1 | X | X | 1 | X | 00 | 10 | 110 |
sw | 0 | 0 | 1 | X | 0 | X | 1 | X | 1 | 1 | 1 | 00 | XX | 000 |
lw | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | X | 1 | 1 | 00 | 01 | 000 |
beq | 0 | 0 | 0 | X | 0 | X | 1 | X | X | 1 | 1 | 00(Zero=0) 01(Zero=1) | XX | 001 |
bne | 0 | 0 | 0 | X | 0 | X | 1 | X | X | 1 | 1 | 00(Zero=1) 01(Zero=0) | XX | 001 |
bltz | 0 | 0 | 0 | X | 0 | X | 1 | X | X | 1 | 1 | 00(Sign=0) 01(Sign=1) | XX | 001 |
j | 0 | X | X | X | 0 | X | 1 | X | X | 1 | X | 11 | XX | XXX |
jr | 0 | X | X | X | 0 | X | 1 | X | X | 1 | X | 10 | XX | XXX |
jal | 0 | X | X | X | 1 | 0 | 1 | X | X | 1 | X | 11 | 00 | XXX |
halt | 1 | X | X | X | 0 | X | 1 | X | X | 1 | X | XX | XX | XXX |
控制訊號不僅僅取決於操作碼,還取決於當前的狀態。各控制訊號實現如下:
ALUSrcA:EXE 階段 LS、SLL
```verilog ALUSrcA = ((State == `STATE_EXE_AL || State == `STATE_EXE_BR || State == `STATE_EXE_LS) && OpCode == `OP_SLL) ? 1 : 0; ```ALUSrcB:EXE 階段 ADDIU、ANDI、ORI、XORI、SLTI、LW、SW
```verilog ALUSrcB = ((State == `STATE_EXE_AL || State == `STATE_EXE_BR || State == `STATE_EXE_LS) && (OpCode == `OP_ADDIU || OpCode == `OP_ANDI || OpCode == `OP_ORI || OpCode == `OP_XORI || OpCode == `OP_SLTI || OpCode == `OP_LW || OpCode == `OP_SW)) ? 1 : 0; ```RegWre:ID 階段 JAL,或 WB 階段 LD
```verilog RegWre = ((State == `STATE_ID && OpCode == `OP_JAL) || (State == `STATE_WB_AL || State == `STATE_WB_LD)) ? 1 : 0; ```WrRegDSrc:ID 階段 JAL
```verilog WrRegDSrc = (State == `STATE_ID && OpCode == `OP_JAL) ? 0 : 1; ```mRD:MEM 或 WB 階段 LW
```verilog mRD = ((State == `STATE_MEM || State == `STATE_WB_LD) && OpCode == `OP_LW) ? 1 : 0; ```mWR:MEM 階段 SW
```verilog mWR = (State == `STATE_MEM && OpCode == `OP_SW) ? 1 : 0; ```IRWre:IF 階段
```verilog IRWre = (State == `STATE_IF) ? 1 : 0; ```ExtSel:EXE 階段 ANDI、ORI、XORI
```verilog ExtSel = ((State == `STATE_EXE_AL || State == `STATE_EXE_BR || State == `STATE_EXE_LS) && (OpCode == `OP_ANDI || OpCode == `OP_ORI || OpCode == `OP_XORI)) ? 0 : 1; ```PCSrc:IF 或 ID 階段 JR 為 PC_REG_JUMP,IF 或 ID 階段 J、JAL 為 PC_ABS_JUMP,EXE 階段 BEQ、BNE、BLTZ 為 PC_REL_JUMP,否則均為 PC_NEXT
```verilog if ((State == `STATE_IF || State == `STATE_ID) && OpCode == `OP_JR) PCSrc = `PC_REG_JUMP; else if ((State == `STATE_IF || State == `STATE_ID) && (OpCode == `OP_J || OpCode == `OP_JAL)) PCSrc = `PC_ABS_JUMP; else if ((State == `STATE_EXE_AL || State == `STATE_EXE_BR || State == `STATE_EXE_LS) && (OpCode == `OP_BEQ && Zero) || (OpCode == `OP_BNE && !Zero) || (OpCode == `OP_BLTZ && Sign)) PCSrc = `PC_REL_JUMP; else PCSrc = `PC_NEXT; ```RegDst:ID 階段 JAL 為 b00,WB 階段 ADDIU、ANDI、ORI、XORI、SLTI、LW 為 b01,否則均為 b10
```verilog if (State == `STATE_ID && OpCode == `OP_JAL) RegDst = 2'b00; else if ((State == `STATE_WB_AL || State == `STATE_WB_LD) && (OpCode == `OP_ADDIU || OpCode == `OP_ANDI || OpCode == `OP_ORI || OpCode == `OP_XORI || OpCode == `OP_SLTI || OpCode == `OP_LW)) RegDst = 2'b01; else RegDst = 2'b10; ```ALUOp:根據真值表即可得出
```verilog case (OpCode) `OP_ADD, `OP_ADDIU, `OP_SW, `OP_LW: ALUOp = `ALU_OP_ADD; `OP_SUB, `OP_BEQ, `OP_BNE, `OP_BLTZ: ALUOp = `ALU_OP_SUB; `OP_SLL: ALUOp = `ALU_OP_SLL; `OP_ORI: ALUOp = `ALU_OP_OR; `OP_AND, `OP_ANDI: ALUOp = `ALU_OP_AND; `OP_SLTI, `OP_SLT: ALUOp = `ALU_OP_SLT; `OP_XORI: ALUOp = `ALU_OP_XOR; endcase ```PCWre:ID 階段 J、JAL、JR,或 EXE 階段 BEQ、BNE、BLTZ,或 MEM 階段 SW,或 WB 階段。另外,為保證在每條指令最初階段的時鐘上升沿 PC 發生改變,需要在上一條指令的最後一個下降沿將 PCWre 設定為 1,這樣才能保證 PC 在每條指令最開始的時鐘上升沿改變。
```verilog always @(negedge CLK) begin case (State) `STATE_ID: begin if (OpCode == `OP_J || OpCode == `OP_JAL || OpCode == `OP_JR) PCWre <= 1; end `STATE_EXE_AL, `STATE_EXE_BR, `STATE_EXE_LS: begin if (OpCode == `OP_BEQ || OpCode == `OP_BNE || OpCode == `OP_BLTZ) PCWre <= 1; end `STATE_MEM: begin if (OpCode == `OP_SW) PCWre <= 1; end `STATE_WB_AL, `STATE_WB_LD: PCWre <= 1; default: PCWre <= 0; endcase end ```邏輯算術運算單元
該模組是一個32位的ALU單元,會根據控制訊號對輸入的運算元進行不同的運算,例如加、減、與、或等。
```verilog module ALU( input [2:0] ALUOp, input [31:0] A, input [31:0] B, output Sign, output Zero, output reg [31:0] Result ); always @(*) begin case (ALUOp) `ALU_OP_ADD: Result = (A + B); `ALU_OP_SUB: Result = (A - B); `ALU_OP_SLL: Result = (B << A); `ALU_OP_OR: Result = (A | B); `ALU_OP_AND: Result = (A & B); `ALU_OP_LT: Result = (A < B) ? 1 : 0; `ALU_OP_SLT: Result = (((A < B) && (A[31] == B[31])) || ((A[31] && !B[31]))) ? 1 : 0; `ALU_OP_XOR: Result = (A ^ B); endcase $display("[ALU] calculated result [%h] from a = [%h] aluOpCode = [%b] b = [%h]", Result, A, ALUOp, B); end assign Zero = (Result == 0) ? 1 : 0; assign Sign = Result[31]; endmodule ```暫存器組
該模組為一個32位而擁有32個寄存的暫存器組。暫存器組接受 InstructionMemory 的輸入,輸出對應暫存器的資料,從而實現讀取暫存器裡的資料的功能。
```verilog module RegisterFile( input CLK, input RST, input WE, input [4:0] ReadReg1, input [4:0] ReadReg2, input [4:0] WriteReg, input [31:0] WriteData, output [31:0] ReadData1, output [31:0] ReadData2 ); reg [31:0] register[1:31]; integer i; assign ReadData1 = ReadReg1 == 0 ? 0 : register[ReadReg1]; assign ReadData2 = ReadReg2 == 0 ? 0 : register[ReadReg2]; always @(negedge CLK or negedge RST) begin if (!RST) begin for (i = 1; i < 32; i = i + 1) begin register[i] = 0; end end else if (WE && WriteReg) begin register[WriteReg] <= WriteData; $display("[RegisterFile] wrote data [%h] into reg $[%d]", WriteData, WriteReg); end end endmodule ```符號擴充套件單元
該元件有兩個功能:符號擴充套件和零擴充套件,輸入的擴充套件方法和待擴充套件的資料,輸出擴充套件後的資料。
```verilog module SignZeroExtend( input ExtSel, // 0 - 0 extend, 1 - sign extend input [15:0] Immediate, output [31:0] DataOut ); assign DataOut[15:0] = Immediate[15:0]; assign DataOut[31:16] = (ExtSel && Immediate[15]) ? 16'hFFFF : 16'h0000; endmodule ```指令儲存器
把指令集以二進位制的形式寫成一個檔案,然後在指令儲存器中讀進來,以讀檔案的方式把指令儲存到記憶體中,實現指令的讀取。
```verilog module InstructionMemory( input RW, input [31:0] IAddr, output reg [31:0] DataOut ); reg [7:0] memory[0:95]; initial begin $readmemb(`MEMORY_FILE_PATH, memory); end always @(IAddr or RW) begin if (RW) begin DataOut[31:24] = memory[IAddr]; DataOut[23:16] = memory[IAddr + 1]; DataOut[15:8] = memory[IAddr + 2]; DataOut[7:0] = memory[IAddr + 3]; $display("[InstructionMemory] Loaded instruction [%h] from address [%h]", DataOut, IAddr); end end endmodule ```資料儲存單元
資料儲存單元負責存取資料,且由時鐘下降沿出發寫操作。實現為1位元組8位的大端方式儲存。
```verilog module DataMemory( input CLK, input mRD, input mWR, input [31:0] DAddr, input [31:0] DataIn, output [31:0] DataOut ); reg [7:0] memory[0:127]; assign DataOut[7:0] = mRD ? memory[DAddr + 3] : 8'bz; assign DataOut[15:8] = mRD ? memory[DAddr + 2] : 8'bz; assign DataOut[23:16] = mRD ? memory[DAddr + 1] : 8'bz; assign DataOut[31:24] = mRD ? memory[DAddr] : 8'bz; always @(negedge CLK) begin if (mWR) begin memory[DAddr] <= DataIn[31:24]; memory[DAddr + 1] <= DataIn[23:16]; memory[DAddr + 2] <= DataIn[15:8]; memory[DAddr + 3] <= DataIn[7:0]; $display("[DataMemory] saved data [%h] into address [%h]", DataIn, DAddr); end end endmodule ```程式計數器
在時鐘上升沿處給出下條指令的地址,或在重置訊號下降沿處將PC歸零。
PC的下一條指令可能是當前 PC+4,也可能是跳轉指令地址,還有可能因為停機而不變。 因此還需要設計一個選擇器來選擇下一條指令地址的計算方式,為此建立了 JumpPCHelper用於計算 j 指令的下一條 PC 地址,和 NextPCHelper 用於根據指令選擇不同的計算方式。
```verilog module PC( input CLK, input RST, input PCWre, input [31:0] PCAddr, output reg [31:0] NextPCAddr ); initial NextPCAddr = 0; always @(posedge CLK or negedge RST) begin if (!RST) NextPCAddr <= 0; else if (PCWre || !PCAddr) NextPCAddr <= PCAddr; end endmodule module JumpPCHelper( input [31:0] PC, input [25:0] NextPCAddr, output reg [31:0] JumpPC); wire [27:0] tmp; assign tmp = NextPCAddr << 2; // address * 4 always @(*) begin JumpPC[31:28] = PC[31:28]; JumpPC[27:2] = tmp[27:2]; JumpPC[1:0] = 0; end endmodule module NextPCHelper( input RST, input [1:0] PCSrc, input [31:0] PC, input [31:0] Immediate, input [31:0] RegPC, input [31:0] JumpPC, output reg [31:0] NextPC); always @(RST or PCSrc or PC or Immediate or RegPC or JumpPC) begin if (!RST) NextPC = PC + 4; else begin case (PCSrc) `PC_NEXT: NextPC = PC + 4; `PC_REL_JUMP: NextPC = PC + 4 + (Immediate << 2); `PC_REG_JUMP: NextPC = RegPC; `PC_ABS_JUMP: NextPC = JumpPC; default: NextPC = PC + 4; endcase end end endmodule ```選擇器
資料選擇,用於資料儲存單元之後的選擇,這裡需要二選一和三選一資料選擇器。
```verilog module Selector1In2#( parameter WIDTH = 5 )( input Sel, input [WIDTH-1:0] A, input [WIDTH-1:0] B, output [WIDTH-1:0] Y); assign Y = Sel ? B : A; endmodule module Selector1In3#( parameter WIDTH = 5 )( input [1:0] Sel, input [WIDTH-1:0] A, input [WIDTH-1:0] B, input [WIDTH-1:0] C, output reg [WIDTH-1:0] Y); always @(Sel or A or B or C) begin case (Sel) 2'b00: Y <= A; 2'b01: Y <= B; 2'b10: Y <= C; default: Y <= 0; endcase end endmodule ```指令暫存器
用時鐘訊號 CLK 驅動,採用邊緣觸發寫入指令二進位制碼。
```verilog module IR( input CLK, input IRWre, input [31:0] DataIn, output reg [31:0] DataOut ); always @(posedge CLK) begin if (IRWre) begin DataOut <= DataIn; end end endmodule ```資料延遲處理
這部分模組用於切割資料通路。
```verilog module XDR( input CLK, input [31:0] DataIn, output reg [31:0] DataOut ); always @(negedge CLK) DataOut <= DataIn; endmodule ```CPU
有了以上各個模組,一個簡單的 CPU 基本就完成了,最後再將他們串起來即可。
完結撒花。