• 手写一个简易的多周期 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指令功能
    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 基本就完成了,最后再将他们串起来即可。

    完结撒花。

  • 相关阅读:
    Silverlight4 打印 生成文件过大解决
    清理 Visual Studio 工具箱 的冗杂控件(第三方控件卸载不完全)
    SQL2005SP4生成数据库脚本视图依赖顺序错误问题
    SQL Server 2005 企业版没有 Management Studio管理工具
    Excel Reader 轻量级
    开发托管ActiveX或第三方程序托管插件时调试问题解决方法
    Entity Framework 从数据库生成模型丢失数据库文档不完美解决方案
    今天写了个很蛋疼的sql语句
    WinForm 内嵌 Office 文档 解决方案测试(非DSOFRAME 纯C#代码,网上独一份)
    生产者、消费者问题之闹钟
  • 原文地址:https://www.cnblogs.com/hez2010/p/12897743.html
Copyright © 2020-2023  润新知