一點前言
多周期 CPU 相比單周期 CPU 以及流水線的實現(xiàn)來說其實寫起來要麻煩那么一些,,但是相對于流水線以及單周期 CPU 而言,多周期 CPU 除了能提升主頻之外似乎并沒有什么卵用,。不過我的課題是多周期 CPU 那么就開始吧,。
多周期 CPU
不同于單周期 CPU,多周期 CPU 指的是將整個 CPU 的執(zhí)行過程分成幾個階段,,每個階段用一個時鐘去完 成,,然后開始下一條指令的執(zhí)行,而每種指令執(zhí)行時所用的時鐘數(shù)不盡相同,,這就是所謂的多周期CPU,。
CPU在處理指令時,一般需要經(jīng)過以下幾個階段:
(1) 取指令(IF):根據(jù)程序計數(shù)器 PC 中的指令地址,,從存儲器中取出一條指令,,同時,PC 根據(jù)指令字長度自動遞增產(chǎn)生下一條指令所需要的指令地址,,但遇到“地址轉(zhuǎn)移”指令 時,,則控制器把“轉(zhuǎn)移地址”送入 PC,當然得到的“地址”需要做些變換才送入 PC,。
(2) 指令譯碼(ID):對取指令操作中得到的指令進行分析并譯碼,,確定這條指令需要完成的操作,,從而產(chǎn)生相應的操作控制信號,用于驅(qū)動執(zhí)行狀態(tài)中的各種操作,。
(3) 指令執(zhí)行(EXE):根據(jù)指令譯碼得到的操作控制信號,,具體地執(zhí)行指令動作,然后轉(zhuǎn)移到結(jié)果寫回狀態(tài),。
(4) 存儲器訪問(MEM):所有需要訪問存儲器的操作都將在這個步驟中執(zhí)行,,該步驟給出存儲器的數(shù)據(jù)地址,把數(shù)據(jù)寫入到存儲器中數(shù)據(jù)地址所指定的存儲單元或者從存儲器中得 到數(shù)據(jù)地址單元中的數(shù)據(jù),。
(5) 結(jié)果寫回(WB):指令執(zhí)行的結(jié)果或者訪問存儲器中得到的數(shù)據(jù)寫回相應的目的寄存器中,。
這也就意味著一條 CPU 指令最長需要 5 個時鐘周期才能執(zhí)行完畢,至于具體需要多少周期則根據(jù)指令的不同而不同,。
MIPS 指令集的設計為定長簡單指令集,,這為 CPU 的實現(xiàn)帶來了極大的方便。
指令集
MIPS 指令分為三種:R,、I 和 J,,三種指令有不同的存儲方式:
其中,
op:操作碼,; rs:第1個源操作數(shù)寄存器,,寄存器地址(編號)是00000~11111,00~1F,; rt:第2個源操作數(shù)寄存器,,或目的操作數(shù)寄存器,寄存器地址(同上),; rd:目的操作數(shù)寄存器,,寄存器地址(同上); sa:位移量(shift amt),,移位指令用于指定移多少位,; funct:功能碼,在寄存器類型指令中(R類型)用來指定指令的功能,; immediate:16位立即數(shù),,用作無符號的邏輯操作數(shù)、有符號的算術(shù)操作數(shù),、數(shù)據(jù)加載(Load)/數(shù)據(jù)保存(Store)指令的數(shù)據(jù)地址字節(jié)偏移量和分支指令中相對程序計數(shù)器(PC)的有符號偏移量,; address:地址。
在執(zhí)行指令的過程中,,需要在不同的時鐘周期之間進行狀態(tài)轉(zhuǎn)移:
本簡易 CPU 姑且只實現(xiàn)以下指令:
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 對立即數(shù)做 0 擴展后進行與運算 010010 ori rt, rs, immediate 對立即數(shù)做 0 擴展后做或運算 010011 xori rt, rs, immediate 對立即數(shù)做 0 擴展后做異或運算 011000 sll rd, rt, sa 左移指令 100110 slti rt, rs, immediate 比較指令 100111 slt rd, rs, rt 比較指令 110000 sw rt, immediate(rs) 存數(shù)指令 110001 lw rt, immediate(rs) 讀數(shù)指令 110100 beq rs, rt, immediate 分支指令,,相等時跳轉(zhuǎn) 110101 bne rs, rt, immediate 分支指令,不等時跳轉(zhuǎn) 110110 bltz rs, immediate 分支指令,,小于 0 時跳轉(zhuǎn) 111000 j addr 跳轉(zhuǎn)指令 111001 jr rs 跳轉(zhuǎn)指令 111010 jal addr 調(diào)用子程序指令 111111 halt 停機指令
控制單元
一個簡易的多周期 CPU 的數(shù)據(jù)通路圖如下:
三個 D 觸發(fā)器用于保存當前狀態(tài),,是時序邏輯電路,,RST用于初始化狀態(tài)“000“,另外兩個部分都是組合邏輯電路,,一個用于產(chǎn)生 下一個階段的狀態(tài),另一個用于產(chǎn)生每個階段的控制信號,。從圖上可看出,,下個狀態(tài)取決于 指令操作碼和當前狀態(tài);而每個階段的控制信號取決于指令操作碼,、當前狀態(tài)和反映運算結(jié)果的狀態(tài) zero 標志和符號 sign標志,。
其中指令和數(shù)據(jù)各存儲在不同存儲器中,即有指令存儲器和數(shù)據(jù)存儲器,。訪問存儲器時,,先給出內(nèi)存地址,然后由讀或?qū)懶盘柨刂撇僮?。對于寄存器組,, 給出寄存器地址(編號),讀操作時不需要時鐘信號,,輸出端就直接輸出相應數(shù)據(jù),;而在寫操作時,在 WE使能信號為 1時,,在時鐘邊沿觸發(fā)將數(shù)據(jù)寫入寄存器,。
IR 指令寄存器目的是使指令代碼保持穩(wěn)定,PC 寫使能控制信號PCWre,,是確保PC 適時修改,,原因都是和多周期工作的CPU有關。ADR,、BDR,、 ALUoutDR、DBDR四個寄存器不需要寫使能信號,,其作用是切分數(shù)據(jù)通路,,將大組合邏輯切分為若干個小組合邏輯,大延遲變?yōu)槎鄠€分段小延遲,。
各控制信號功能如下:
控制信號名 狀態(tài) 0 狀態(tài) 1 RST 對于PC,,初始化PC為程序首地址 對于PC,PC接收下一條指令地址 PCWre PC不更改,,另 外,,除'000’狀態(tài)之外,其余狀態(tài)慎改PC的值,。 PC更改,,另外,,在'000’狀態(tài)時,修改PC的值合適,。 ALUSrcA 來自寄存器堆 data1 輸出 來自移位數(shù)sa,,同時,進行(zeroextend)sa,,即 {{27{1'b0},sa} ALUSrcB 來自寄存器堆 data2 輸出 來自 sign或 zero 擴展的立即數(shù) DBDataSrc 來自ALU運算結(jié)果的輸出 來自數(shù)據(jù)存儲器(Data MEM)的輸出 RegWre 無寫寄存器組寄存器 寄存器組寄存器寫使能 WrRegDSrc 寫入寄存器組寄存器的數(shù)據(jù)來自 PC+4(PC4) 寫入寄存器組寄存器的數(shù)據(jù)來自ALU 運算結(jié)果或存儲器讀出的數(shù)據(jù) InsMemRW 寫指令存儲器 讀指令存儲器(Ins. Data) mRD 存儲器輸出高阻態(tài) 讀數(shù)據(jù)存儲器 mWR 無操作 寫數(shù)據(jù)存儲器 IRWre IR(指令寄存器)不更改 IR 寄存器寫使能,。向指令存儲器發(fā)出讀指令代碼后,這個信號也接著發(fā)出,,在時鐘上升沿,,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,存儲器數(shù)據(jù)輸入端口 DataOut,,存儲器數(shù)據(jù)輸出端口 RW,,指令存儲器讀寫控制信號,為0 寫,,為 1讀
Data Memory:數(shù)據(jù)存儲器
Daddr,,數(shù)據(jù)地址輸入端口 DataIn,存儲器數(shù)據(jù)輸入端口 DataOut,,存儲器數(shù)據(jù)輸出端口 /RD,,數(shù)據(jù)存儲器讀控制信號,為 0 讀 /WR,,數(shù)據(jù)存儲器寫控制信號,,為0 寫
Register File:寄存器組
Read Reg1,rs 寄存器地址輸入端口 Read Reg2,,rt 寄存器地址輸入端口 Write Reg,,將數(shù)據(jù)寫入的寄存器,其地址輸入端口(rt,、rd) Write Data,,寫入寄存器的數(shù)據(jù)輸入端口 Read Data1,rs 寄存器數(shù)據(jù)輸出端口 Read Data2,,rt 寄存器數(shù)據(jù)輸出端口 WE,,寫使能信號,為1 時,,在時鐘邊沿觸發(fā)寫入
IR: 指令寄存器,,用于存放正在執(zhí)行的指令代碼
ALU: 算術(shù)邏輯單元
result,ALU運算結(jié)果 zero,,運算結(jié)果標志,,結(jié)果為 0,,則 zero=1;否則 zero=0 sign,,運算結(jié)果標志,,結(jié)果最高位為0,則 sign=0,,正數(shù),;否則,sign=1,,負數(shù)
ALU
ALU 為算術(shù)邏輯運算單元,功能如下:
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 異或
模塊設計
符號定義
為了更加明晰程序代碼,,并避免因二進制代碼書寫錯誤導致的問題,,對狀態(tài)碼、操 作碼等做出如下定義:
`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
控制單元
狀態(tài)轉(zhuǎn)移
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
控制信號
不同控制信號根據(jù)不同的操作碼得到,,因此可以列出對于不同操作碼的各控制信號的真值表:
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
控制信號不僅僅取決于操作碼,,還取決于當前的狀態(tài)。各控制信號實現(xiàn)如下:
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:根據(jù)真值表即可得出
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 發(fā)生改變,,需要在上一條指令的最后一個下降沿將 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
邏輯算術(shù)運算單元
該模塊是一個32位的ALU單元,,會根據(jù)控制信號對輸入的操作數(shù)進行不同的運算,例如加,、減,、與、或等,。
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 的輸入,輸出對應寄存器的數(shù)據(jù),,從而實現(xiàn)讀取寄存器里的數(shù)據(jù)的功能,。
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
符號擴展單元
該組件有兩個功能:符號擴展和零擴展,輸入的擴展方法和待擴展的數(shù)據(jù),,輸出擴展后的數(shù)據(jù),。
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
指令存儲器
把指令集以二進制的形式寫成一個文件,然后在指令存儲器中讀進來,,以讀文件的方式把指令存儲到內(nèi)存中,,實現(xiàn)指令的讀取。
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
數(shù)據(jù)存儲單元
數(shù)據(jù)存儲單元負責存取數(shù)據(jù),,且由時鐘下降沿出發(fā)寫操作,。實現(xiàn)為1字節(jié)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
程序計數(shù)器
在時鐘上升沿處給出下條指令的地址,,或在重置信號下降沿處將PC歸零,。
PC的下一條指令可能是當前 PC+4,也可能是跳轉(zhuǎn)指令地址,,還有可能因為停機而不變,。 因此還需要設計一個選擇器來選擇下一條指令地址的計算方式,為此創(chuàng)建了 JumpPCHelper用于計算 j 指令的下一條 PC 地址,和 NextPCHelper 用于根據(jù)指令選擇不同的計算方式,。
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
選擇器
數(shù)據(jù)選擇,,用于數(shù)據(jù)存儲單元之后的選擇,這里需要二選一和三選一數(shù)據(jù)選擇器,。
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 驅(qū)動,,采用邊緣觸發(fā)寫入指令二進制碼。
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
數(shù)據(jù)延遲處理
這部分模塊用于切割數(shù)據(jù)通路,。
module XDR(
input CLK,
input [31:0] DataIn,
output reg [31:0] DataOut
);
always @(negedge CLK) DataOut <= DataIn;
endmodule
CPU
有了以上各個模塊,,一個簡單的 CPU 基本就完成了,最后再將他們串起來即可,。
完結(jié)撒花,。