一點前言 多周期 CPU 相比單周期 CPU 以及流水線的實現來說其實寫起來要麻煩那麼一些,但是相對於流水線以及單周期 CPU 而言,多周期 CPU 除了能提升主頻之外似乎並沒有什麼卵用。不過我的課題是多周期 CPU 那麼就開始吧。 多周期 CPU 不同於單周期 CPU,多周期 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 | 異或 |
模塊設計
符號定義
為了更加明晰程式代碼,並避免因二進位代碼書寫錯誤導致的問題,對狀態碼、操 作碼等做出如下定義:
`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
控制單元
狀態轉移
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
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
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
RegWre = ((State == `STATE_ID && OpCode == `OP_JAL) || (State == `STATE_WB_AL || State == `STATE_WB_LD)) ? 1 : 0;
WrRegDSrc:ID 階段 JAL
WrRegDSrc = (State == `STATE_ID && OpCode == `OP_JAL) ? 0 : 1;
mRD:MEM 或 WB 階段 LW
mRD = ((State == `STATE_MEM || State == `STATE_WB_LD) && OpCode == `OP_LW) ? 1 : 0;
mWR:MEM 階段 SW
mWR = (State == `STATE_MEM && OpCode == `OP_SW) ? 1 : 0;
IRWre:IF 階段
IRWre = (State == `STATE_IF) ? 1 : 0;
ExtSel:EXE 階段 ANDI、ORI、XORI
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
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
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:根據真值表即可得出
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 在每條指令最開始的時鐘上升沿改變。
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單元,會根據控制信號對輸入的操作數進行不同的運算,例如加、減、與、或等。
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 的輸入,輸出對應寄存器的數據,從而實現讀取寄存器里的數據的功能。
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
符號擴展單元
該組件有兩個功能:符號擴展和零擴展,輸入的擴展方法和待擴展的數據,輸出擴展後的數據。
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
指令存儲器
把指令集以二進位的形式寫成一個文件,然後在指令存儲器中讀進來,以讀文件的方式把指令存儲到記憶體中,實現指令的讀取。
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位的大端方式存儲。
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 用於根據指令選擇不同的計算方式。
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
選擇器
數據選擇,用於數據存儲單元之後的選擇,這裡需要二選一和三選一數據選擇器。
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 驅動,採用邊緣觸發寫入指令二進位碼。
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
數據延遲處理
這部分模塊用於切割數據通路。
module XDR(
input CLK,
input [31:0] DataIn,
output reg [31:0] DataOut
);
always @(negedge CLK) DataOut <= DataIn;
endmodule
CPU
有了以上各個模塊,一個簡單的 CPU 基本就完成了,最後再將他們串起來即可。
完結撒花。