手寫一個簡易的多週期 MIPS CPU

一點前言

多週期 CPU 相比單週期 CPU 以及流水線的實現來講其實寫起來要麻煩那麼一些,可是相對於流水線以及單週期 CPU 而言,多週期 CPU 除了能提高主頻以外彷佛並無什麼卵用。不過個人課題是多週期 CPU 那麼就開始吧。設計

多週期 CPU

不一樣於單週期 CPU,多週期 CPU 指的是將整個 CPU 的執行過程分紅幾個階段,每一個階段用一個時鐘去完 成,而後開始下一條指令的執行,而每種指令執行時所用的時鐘數不盡相同,這就是所謂的多週期CPU。code

CPU在處理指令時,通常須要通過如下幾個階段:blog

(1) 取指令(IF):根據程序計數器 PC 中的指令地址,從存儲器中取出一條指令,同時,PC 根據指令字長度自動遞增產生下一條指令所須要的指令地址,但遇到「地址轉移」指令 時,則控制器把「轉移地址」送入 PC,固然獲得的「地址」須要作些變換才送入 PC。ip

(2) 指令譯碼(ID):對取指令操做中獲得的指令進行分析並譯碼,肯定這條指令須要完成的操做,從而產生相應的操做控制信號,用於驅動執行狀態中的各類操做。內存

(3) 指令執行(EXE):根據指令譯碼獲得的操做控制信號,具體地執行指令動做,而後轉移到結果寫回狀態。input

(4) 存儲器訪問(MEM):全部須要訪問存儲器的操做都將在這個步驟中執行,該步驟給出存儲器的數據地址,把數據寫入到存儲器中數據地址所指定的存儲單元或者從存儲器中得 到數據地址單元中的數據。it

(5) 結果寫回(WB):指令執行的結果或者訪問存儲器中獲得的數據寫回相應的目的寄存器中。io

這也就意味着一條 CPU 指令最長鬚要 5 個時鐘週期才能執行完畢,至於具體須要多少週期則根據指令的不一樣而不一樣。table

MIPS 指令集的設計爲定長簡單指令集,這爲 CPU 的實現帶來了極大的方便。class

指令集

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 基本就完成了,最後再將他們串起來便可。

完結撒花。

相關文章
相關標籤/搜索