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

来源:https://www.cnblogs.com/hez2010/archive/2020/05/15/12897743.html
-Advertisement-
Play Games

一點前言 多周期 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指令功能
000000add rd, rs, rt帶符號加法運算
000001sub rd, rs, rt帶符號減法運算
000010addiu rt, rs, immediate無符號加法運算
010000and rd, rs, rt與運算
010001andi rt, rs, immediate對立即數做 0 擴展後進行與運算
010010ori rt, rs, immediate對立即數做 0 擴展後做或運算
010011xori rt, rs, immediate對立即數做 0 擴展後做異或運算
011000sll rd, rt, sa左移指令
100110slti rt, rs, immediate比較指令
100111slt rd, rs, rt比較指令
110000sw rt, immediate(rs)存數指令
110001lw rt, immediate(rs)讀數指令
110100beq rs, rt, immediate分支指令,相等時跳轉
110101bne rs, rt, immediate分支指令,不等時跳轉
110110bltz rs, immediate分支指令,小於 0 時跳轉
111000j addr跳轉指令
111001jr rs跳轉指令
111010jal addr調用子程式指令
111111halt停機指令

控制單元

一個簡易的多周期 CPU 的數據通路圖如下:

三個 D 觸發器用於保存當前狀態,是時序邏輯電路,RST用於初始化狀態“000“,另外兩個部分都是組合邏輯電路,一個用於產生 下一個階段的狀態,另一個用於產生每個階段的控制信號。從圖上可看出,下個狀態取決於 指令操作碼和當前狀態;而每個階段的控制信號取決於指令操作碼、當前狀態和反映運算結果的狀態 zero 標誌和符號 sign標誌。

其中指令和數據各存儲在不同存儲器中,即有指令存儲器和數據存儲器。訪問存儲器時,先給出記憶體地址,然後由讀或寫信號控制操作。對於寄存器組, 給出寄存器地址(編號),讀操作時不需要時鐘信號,輸出端就直接輸出相應數據;而在寫操作時,在 WE使能信號為 1時,在時鐘邊沿觸發將數據寫入寄存器。

IR 指令寄存器目的是使指令代碼保持穩定,PC 寫使能控制信號PCWre,是確保PC 適時修改,原因都是和多周期工作的CPU有關。ADR、BDR、 ALUoutDR、DBDR四個寄存器不需要寫使能信號,其作用是切分數據通路,將大組合邏輯切分為若幹個小組合邏輯,大延遲變為多個分段小延遲。

各控制信號功能如下:

控制信號名狀態 0狀態 1
RST對於PC,初始化PC為程式首地址對於PC,PC接收下一條指令地址
PCWrePC不更改,另 外,除‘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無操作寫數據存儲器
IRWreIR(指令寄存器)不更改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]功能功能
000Y=A+B加法運算
001Y=A-B減法運算
010Y=B<<A左移運算
011Y=A∨B或運算
100Y=A∧B與運算
101Y=(A<B) ? 1 : 0無符號比較
110Y=(((A<B)&&(A[31] == B[31])) ||
((A[31]==1&& B[31] == 0))) ? 1 : 0
帶符號比較
111Y=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

控制信號

不同控制信號根據不同的操作碼得到,因此可以列出對於不同操作碼的各控制信號的真值表:

OpPCWreALUSrcAALUSrcBDBDataSrcRegWreWrRegDSrcInsMemRWmRDmWRIRWreExtSelPCSrcRegDstALUOp
add0000111XX1X0010000
sub0000111XX1X0010001
addiu0010111XX110001000
and0000111XX1X0010100
andi0010111XX100001100
ori0010111XX100001011
xori0010111XX100001111
sll0100111XX1X0010010
slti0010111XX110001110
slt0000111XX1X0010110
sw001X0X1X11100XX000
lw00111111X110001000
beq000X0X1XX1100(Zero=0) 01(Zero=1)XX001
bne000X0X1XX1100(Zero=1) 01(Zero=0)XX001
bltz000X0X1XX1100(Sign=0) 01(Sign=1)XX001
j0XXX0X1XX1X11XXXXX
jr0XXX0X1XX1X10XXXXX
jal0XXX101XX1X1100XXX
halt1XXX0X1XX1XXXXXXXX

控制信號不僅僅取決於操作碼,還取決於當前的狀態。各控制信號實現如下:

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

完結撒花。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 公司自動開始用釘釘後,企業的相關信息化軟體開始使用釘釘上的應用程式。與銷售公司相關的就是CRM系統。 CRM系統中客戶是私人的,如果想多個人同時負責,需要添加客戶負責人。由於公司的特殊性質,客戶特別多,經常會有人要求增加客戶負責人。每天都有幾個小時在做這樣的工作。釘釘消息、添加負責人,太繁瑣了。 學 ...
  • 互聯網時代,軟體開發所使用的的模式也在逐漸變化。 如今,各行各業都追求信息化,努力跟上時代的步伐,可是這天天跟電腦、跟信息化打交道的軟體業卻其實跟互聯網沒半點關係。因為,互聯網是以人為中心,走的是高流量高流水的模式;而軟體業做的是產品,一個項目即是一個產品,產品之間無法複製通用。 有人說,不一直都 ...
  • 0.前言 上一章簡單介紹了一下ORM框架,並手寫了一個類似ORM的工具類。這一章將介紹一個在C 世界里大名鼎鼎的ORM框架——Entity Framework的Core版。 Entity Framework 非Core版目前已經更新到了6代,這是一款經過檢驗的ORM框架。在這裡簡單介紹一下Entit ...
  • 淺析微軟的網關項目 Intro 最近微軟新開了一個項目 "ReverseProxy" ,也叫做 YARP(A Reverse Proxy) 官方介紹如下: YARP is a reverse proxy toolkit for building fast proxy servers in .NET ...
  • C#實現FTP傳送文件 簡介: 接上文實現對FTP的傳送文件,此文和上文可以說是如出一轍,不過此文是通過cmd進行建立連接的,建立連接後也是通過以下幾個步驟實現操作。建立文件的層級結構如上文,這裡就不啰嗦了。C#實現FTP上傳資料 1.主方法進行調用: this.ftpOperation.Uploa ...
  • 一:背景 1. 講故事 下決心做好自媒體到現在有一個月了,關註我的兄弟應該知道我產出了不少文章,號里的粉絲也多起來了,我也盡最大努力做到有問必回,現在是基礎的、高深的問題都接踵而來,可我也只是一隻小菜鳥,想飛也飛不動了(┬_┬),昨天號里有位朋友被面試官問到可空類型的原理,回答的不好,面試官也是,面 ...
  • Magicodes.IE Csv導入導出 說明 本章主要說明如何使用Magicodes.IE.Csv進行Csv導入導出. 主要步驟 1.安裝包Magicodes.IE.Csv 2.使用Magicodes.IE.Csv導出Csv 通過如下代碼片段我們將導出的內容通過相應的特性做出相應的處理. Expo ...
  • exec函數族 fork()函數創建子進程後,子進程往往要調用一種e x e c函數以執行另一個程式。當進程調用一種exec函數時,該進程完全由新程式代換,而新程式則從其 ma i n函數開始執行。 因為調用exec並不創建新進程,所以前後的進程ID並未改變。exec只是用另一個新程式替換了當前進程 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...