中山大学计算机组成原理多周期CPU实验

中山大学2016计算机组成原理多周期CPU实验

实验老师给我们罗列了很细致的控制线路图~然而一些细节部分,博主并没有完全按照老师给出的图做,也许也会有些bug,欢迎大家指出,多多交流~


实验要求

基本上来说与单周期CPU设计实验基本相同,不同点是多周期增加了多状态问题的考虑。

  1. PC和寄存器组必须使用时钟触发,这是必须的。
  2. 指令存储器和数据存储器存储单元宽度一律使用8位,即一个字节的存储单位。不能使用32位作为存储器存储单元宽度。
  3. 控制器部分可以考虑用控制信号真值表方法(有共性部分)与用case语句方法逐个产生各指令其它控制信号相配合。当然,还可以用其它方法,自己考虑。

实验内容

设计一个多周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:(说明:操作码按照以下规定使用,都给每类指令预留扩展空间,后续实验相同。)

这里写图片描述这里写图片描述

实验原理

  1. 取指令(IF):根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,当然得到的“地址”需要做些变换才送入pc。
  2. 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
  3. 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
  4. 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
  5. 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。

实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的CPU。

多周期CPU指令处理过程

MIPS指令的三种格式

其中,
op:为操作码;
rs:为第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F;
rt:为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
rd:为目的操作数寄存器,寄存器地址(同上);
sa:为位移量(shift amt),移位指令用于指定移多少位;
funct:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能;
immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Laod)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address:为地址。

多周期CPU状态转移图

状态的转移有的是无条件的,例如从IF状态转移到ID就是无条件的;有些是有条件的,例如ID 或 EXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。

多周期CPU控制部件的原理结构图

图3是多周期CPU控制部件的电路结构,三个D触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态zero标志。

多周期CPU数据通路和控制线路图

图4是一个简单的基本上能够在单周期上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出地址,然后由读或写信号控制操作。对于寄存器组,读操作时,给出寄存器地址(编号),输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发写入。图中控制信号功能如表1所示,表2是ALU运算功能表。
特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,pc写使能控制信号PCWre,是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUout、ALUM2DR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延迟变为多个分段小延迟。

这里写图片描述这里写图片描述

相关部件及引脚说明:

  • Instruction Memory:指令存储器,
    Iaddr,指令地址输入端口
    DataIn,存储器数据输入端口
    DataOut,存储器数据输出端口
    RW,指令存储器读写控制信号,为1写,为0读

  • Data Memory:数据存储器,
    Daddr,数据地址输入端口
    DataIn,存储器数据输入端口
    DataOut,存储器数据输出端口
    RD,数据存储器读控制信号,为1读
    WR,数据存储器写控制信号,为1写

  • 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输出1,否则输出0

这里写图片描述

实验分析与设计

  1. 实验步骤
    这里写图片描述

  2. 实验分析
    解构多周期CPU实验的大体需求:制作一个简单的多周期CPU,并编写一段测试程序,测试其是否正常运行。该实验主题部分为设计CPU。分析CPU的模块构成,及各种信号的输入输出及功能,各模块间的关联等,得出大致的关联;
    本次实验的难点主要是控制单元、寄存器模块与存储器模块的设计,以及通过组合逻辑处理不同周期的指令。
    根据老师给出的多周期CPU状态转移图和多周期CPU控制部件的原理结构图,我设计了一个三位控制信号state,用于控制IF,ID,EXE,MEM,WB的状态转移。其余各模块按功能进行设计。
    分析实验要求,可以得出不同指令及不同状态下的各使能信号值如下表:

    这里写图片描述

  3. 实验设计
    根据多周期CPU数据通路和控制线路图,将多周期CPU分为以下模块:MCC顶层模块,Control Unit,PC,Instruction Memory,Register File,ALU,Data Memory,Extend Unit,IR。

MCC顶层模块:
①各种输入输出信号:MCC输入信号 包括clk, reset, inPC(即初始PC)
②声明control unit, PC, register file, instruction memory, ALU, sign extend,IR模 块;
③为某些固定来源的信号进行赋值 (如右图)
顶层模块中的各下属模块设计如下:

    //Control Unit
     CU ControlUnit(clk, op, zero, ExtSel, WrRegDSrc, ALUM2Reg, PCWre, InsMemRW, IRWre, RegWre, DataRD, DataWR, ALUSrcA, ALUSrcB, ID, EXE, RegDst, ALUOp, PCSrc, state);

     //Fetch PC
     pc PC(inPC, clk, reset, PCWre, PCSrc, j, RegData1, Ext_Immediate, CurPC, IAddr);

     //Fetch Instruction
     insmem Ins_Mem(IAddr, InsMemRW, InsOut);

     assign op = MIPSins[31:26];
     assign rs = MIPSins[25:21];
     assign rt = MIPSins[20:16];
     assign rd = MIPSins[15:11];
     assign Immediate = MIPSins[15:0];
     assign addr = MIPSins[25:0];
     assign sa = MIPSins[10:6];
     assign DAddr = result;
     assign Data_In = RegData2;

     //Register File
     regfile Reg_File(clk, CurPC, result, Data_Out, rt, rs, rd, RegDst, WrRegDSrc, RegWre, ALUM2Reg, ID, RegData1, RegData2, WrRegData, WrReg);

     //ALU
     alu ALU(RegData1, RegData2, Ext_Immediate, sa, ALUOp, ALUSrcA, ALUSrcB, EXE, zero, ALU_A, ALU_B, result);

     //Data Memory
     datamemory Data_Memory(DAddr, Data_In, Data_Out, DataWR, DataRD); 

     //sign, zero, extend
     extend Extend(Immediate, Ext_Immediate, ExtSel);

     //IR
     ir IR(clk, IRWre, InsOut, CurPC, MIPSins, j);

Control Unit:
控制单元的输入输出如下图,它由时钟信号clk驱动,受传入的MIPS指令op段以及zero 信号控制。我将state状态转移功能写入了control unit,用三位使能信号控制状态转换。控制单元的主要功能有两个,一是根据控制信号state 和op进行状态转移;二是根据 op 返回不同指令所对应的各种使能信号的的值。

module CU(
    input clk,
    input [5:0] op,
    input zero,
    output reg ExtSel,
    output reg WrRegDSrc,
    output reg ALUM2Reg,
    output reg PCWre, InsMemRW, IRWre, RegWre,
    output reg DataRD, DataWR,
    output reg ALUSrcA, ALUSrcB,
    output reg ID, EXE,
    output reg [1:0] RegDst,
    output reg [2:0] ALUOp,
    output reg [1:0] PCSrc,
    output reg [2:0] state
    );

下面是state状态转移的组合逻辑设计:

always@(posedge clk) begin
            case(state)
                3'b000: begin
                    IRWre = 0;
                    ID = 1;
                    state = 3'b001;
                end
                3'b001: begin
                    ID = 0;
                    case(op)
                        6'b110100: begin  //beq
                            EXE = 1;
                            state = 3'b101;
                        end
                        6'b110000, 6'b110001: begin //sw lw
                            EXE = 1;
                            state = 3'b010;
                        end
                        6'b111000, 6'b111001, 6'b111010: begin // j jr jal
                            PCWre = 0;
                            RegWre = 0;
                            IRWre = 1;
                            state = 3'b000;
                            DataRD = 0;
                            DataWR = 0;
                            ALUOp = 000;
                        end
                        6'b111111: begin //halt
                            state = 3'b000;
                            PCWre = 0;
                            RegWre = 0;
                            IRWre = 0;
                            ALUSrcB = 0;
                            ALUM2Reg = 0;
                            RegWre = 0;
                            ExtSel = 0;
                            PCSrc = 0;
                            RegDst = 0;
                        end
                        default: begin //剩余的操作
                            EXE = 1;
                            state = 3'b110;
                        end
                    endcase
                end
                3'b110: begin
                    EXE = 0;
                    RegWre = 1;
                    state = 3'b111;
                end 
                3'b101: begin
                    EXE = 0;
                    PCWre = 0;
                    IRWre = 1;
                    state = 3'b000;
                end
                3'b010: begin
                    EXE = 0;
                    if(op == 6'b110000) // sw
                        DataWR = 1;
                    else 
                        DataRD = 1;   //lw
                    state = 3'b011;
                end
                3'b011: begin
                    if(op == 6'b110001) begin//lw
                        DataRD = 0;
                        RegWre = 1;
                        state = 3'b100;
                    end else begin //sw
                        DataWR = 0;
                        PCWre = 0;
                        IRWre = 1;
                        state = 3'b000;
                    end
                end
                3'b100: begin
                    RegWre = 0;
                    PCWre = 0;
                    IRWre = 1;
                    state = 3'b000;
                end
                3'b111: begin
                    RegWre = 0;
                    PCWre = 0;
                    IRWre = 1;
                    state = 3'b000;
                end
            endcase
        end
  1. 当state为000时,fetch instruction,这是任何指令都会执行的步骤,IF执行后,自动跳转至state001状态instruction decode
  2. 执行001instruction decode之后,不同指令跳转的状态不同,如,beq跳转到101EXE状态,sw, lw跳转到010EXE状态
  3. 而j,jal,jr,halt指令跳转回 000IF状态 ,halt后使能信号变化与j,jr,ja不l同,所以另开一个case
  4. 其余操作都跳转到110EXE状态
  5. 110状态为EXE,下一状态跳转到111WB
  6. 101状态为beq指令的EXE,下一状态跳转到000IF
  7. 010状态为sw,lw的EXE,下一状态为011MEM数据存储器读写
  8. 011状态为MEM,下一状态分两种情况,lw则跳转到100WB写回寄存器,sw 则直接跳转回000读取下一条指令
  9. state100是lw的write back操作,将数据写回寄存器,下一个状态就是001 Instruction Fetch

  10. state111也是Write Back,适用于addu等操作,下一个状态也是000 IF

下面是部分指令的使能信号共同变化设计:

always@ (negedge clk) begin
    if ((state == 3'b101) || (state == 3'b111) || (state == 3'b100)
             ||(state == 3'b011 && op == 6'b110000) ||
             (state == 3'b001 && (op == 6'b111000 
             || op == 6'b111001 || op == 6'b111010)) )
            PCWre = 1;
        if (state == 3'b001 && op == 6'b111010)
            RegWre = 1;
        end

//首先,101EXE,111WB,100WB,sw指令的011MEM,j,jal,jr,halt指令的001ID状态后,需要读取下一条指令,所以设定PC写使能信号为1;而jal的001ID状态后需要将寄存器写使能信号设定为1,用于$31存储下一条主程序指令地址。
接下来是各条指令的使能信号不同变化设定:(*以下所有使能信号的变化均参照前面的使能信号功能表,真值表及数据通路图)

always@(op or zero) begin
       case(op)
            6'b000000: begin // addu√
                    ALUSrcA = 0;
                    ALUSrcB = 0;     
                    ALUM2Reg = 0;    
                    PCSrc = 0;       
                    RegDst = 2;
                    ALUOp = 3'b000;     
                    WrRegDSrc = 1;
            end

            6'b000001: begin // subu√
                     ALUSrcA = 0;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                PCSrc = 0;
                RegDst = 2;
                ALUOp = 3'b001;
                WrRegDSrc = 1;
            end

            6'b000010: begin // addiu√
                     ALUSrcA = 0;
                ALUSrcB = 1;
                ALUM2Reg = 0;
                PCSrc = 0;
                RegDst = 1;
                ALUOp = 3'b000;
                WrRegDSrc = 1;
                ExtSel = 1;
            end

            6'b010000: begin // or√
                    ALUSrcA = 0;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                PCSrc = 0;
                RegDst = 2;
                ALUOp = 3'b101;
                WrRegDSrc = 1;
            end

            6'b010001: begin // and√
                     ALUSrcA = 0;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                PCSrc = 0;
                RegDst = 2;
                ALUOp = 3'b110;
                WrRegDSrc = 1;
            end

            6'b010010: begin // ori√
                     ALUSrcA = 0;
                ALUSrcB = 1;
                ALUM2Reg = 0;
                ExtSel = 0;//原来是1;
                PCSrc = 0;
                RegDst = 1;
                ALUOp = 3'b101;
                WrRegDSrc = 1;
            end

            6'b011000: begin // sll√
                     ALUSrcA = 1;
                ALUSrcB = 0;
                ALUM2Reg = 0;
                ExtSel = 0;
                PCSrc = 0;
                RegDst = 2;
                ALUOp = 3'b100;
                WrRegDSrc = 1;
            end

                6'b100110: begin // sltu√
                     ALUSrcA = 0;
                     ALUSrcB = 0;
                     ALUM2Reg = 0;
                     PCSrc = 0;
                     RegDst = 2;
                     ALUOp = 3'b010;
                     WrRegDSrc = 1;
                end

            6'b100111: begin // sltiu√
                     ALUSrcA = 0;
                ALUSrcB = 1;
                ALUM2Reg = 0;
                PCSrc = 0;
                RegDst = 1;
                ALUOp = 3'b010;
                WrRegDSrc = 1;
                     ExtSel = 1;
            end

            6'b110000: begin // sw√
                     ALUSrcA = 0;
                ALUSrcB = 1;
                ExtSel = 1;//原来是2;
                PCSrc = 0;
                ALUOp = 3'b000;
            end
            6'b110001: begin // lw√
                     ALUSrcA = 0;
                ALUSrcB = 1;
                ALUM2Reg = 1;
                ExtSel = 1;//原来是2;
                PCSrc = 0;
                RegDst = 1;
                ALUOp = 3'b000;
                WrRegDSrc = 1;
            end

            6'b110100: begin // beq√
                ExtSel = 1;//原来是2;
                     ALUSrcA = 0;
                ALUSrcB = 0;
                PCSrc = zero;
                ALUOp = 3'b001;
            end
            6'b111000: begin // j
                PCSrc = 3;
            end

            6'b111001: begin // jr
                PCSrc = 2;
            end

            6'b111010: begin // jal
                PCSrc = 3;
                RegDst = 0;
                WrRegDSrc = 0;
            end

            6'b111111: begin // halt
            end
        endcase

PC:
PC信号代表着instruction address,由时钟信号clk驱动,并受写使能信号PCWre,数据选择信号PCSrc控制,其状态变化选择如下。

always@(posedge clk) begin
        if(reset) begin
            CurPC <= inPC;
        end
     end 

     always@(posedge clk) begin
            if(PCWre) begin
                case(PCSrc)
                    2'b00:
                        CurPC = CurPC + 4;
                    2'b01:
                        CurPC = CurPC + 4 + (Ext_Immediate << 2);
                    2'b10:
                        CurPC = rs_Data;
                    2'b11:
                        CurPC = j;
                endcase
            end
      end

      always@(CurPC) begin
            IAddr = CurPC;

Instruction Memory:
指令存储器模块用于读取指令,由写使能信号InsMemRW驱动,测试指令从loadins.txt文件按八位读取保存的32位指令。代码如下:

module insmem(
    input [31:0] IAddr,
    input InsMemRW,
    output reg [31:0] InsOut
    );

     reg [7:0] loadins[607:0];
     initial begin
        $readmemb("loadins.txt", loadins);
     end
     always @ (IAddr or InsMemRW) //(IAddr or InsMemRW)
        if(InsMemRW) begin
            InsOut[31:24] = loadins[IAddr];
            InsOut[23:16] = loadins[IAddr + 1];
            InsOut[15:8] = loadins[IAddr + 2];
            InsOut[7:0] = loadins[IAddr + 3];
        end
endmodule

(*我设计的指令保存是高位储存在前,低位在后)

Register File:
寄存器模块主要用于寄存器的读写,由时钟信号clk驱动,输出值由WrRegDSrc和ALUM2Reg两个信号进行选择,操作的寄存器数由RegDst信号选择,实现代码如下:

reg [31:0] register [0:31];

     initial begin
        register[0] = 0;
     end

     always @(posedge clk) begin
            case(RegDst)
                2'b00:
                    WrReg = 31;
                2'b01:
                    WrReg = rt;
                2'b10:
                    WrReg = rd;
            endcase

            assign RegData1 = register[rs];
            assign RegData2 = register[rt];

            if(WrRegDSrc == 0)
                WrRegData = CurPC + 4;
            else if(WrRegDSrc == 1 && ALUM2Reg == 0)
                WrRegData = result;
            else 
                WrRegData = Data_Out; //数据存储器中数据

            if((WrReg != 0)&&(RegWre == 1)) begin
                register[WrReg] <= WrRegData;
            end

ALU:
ALU模块主要对输入的两个值进行逻辑运算,我将两个输入值的选择模块ADR、BDR 也并了进来,ALU的输出值将输出到数据存储器或写回寄存器中。在IR模块的state 状态转移中,指令有EXE步骤的才需要调用ALU模块。因此我在control unit中设定 了一个EXE信号,用于驱动ALU模块。ALU的加、减、与、或、移位等功能的选择 通过三位的ALUOp选择信号控制。具体代码如下:

    assign zero = (result == 0)? 1: 0;  
    always@ (posedge EXE)begin
        if(ALUSrcA == 0)
            ALU_A = RegData1;
        else
            ALU_A = sa;
        if(ALUSrcB == 0)
            ALU_B = RegData2;
        else
            ALU_B = Ext_Immediate;

        case (ALUOp)
            3'b000: // Y = A + B
                result = ALU_A + ALU_B;
            3'b001: // Y = A - B
                result = ALU_A - ALU_B;
            3'b010: // Y =(A < B)?1: 0
                result = ALU_A < ALU_B;
            3'b011: // Y = A >> B
                result = ALU_B >> ALU_A;
            3'b100: // Y = A << B
                result = ALU_B << ALU_A;
            3'b101: // Y = A | B; 
                result = ALU_A | ALU_B;
            3'b110: // Y = A ∧ B
                result = ALU_A & ALU_B;
            3'b111: // Y = A ⊕ B
                result = ALU_A ^ ALU_B;
        endcase

Data Memory:
Data Memory模块主要功能为数据存储器的读写,由数据读写信号DataRD和DataWR 驱动,同样按八位存储32位的数据。输入的信号为内存的访问地址DAddr,和写入内
存的数据Data_in,输出为内存中读取的数据Data_out,具体代码如下:

    reg [7:0] DataMem[0:607];

    always @ (posedge DataRD) begin
        Data_Out[31:24] <= DataMem[DAddr + 3];
        Data_Out[23:16] <= DataMem[DAddr + 2];
        Data_Out[15:8] <= DataMem[DAddr + 1];
        Data_Out[7:0] <= DataMem[DAddr];
    end

    always @ (posedge DataWR) begin
        DataMem[DAddr + 3] <= Data_In[31:24];
        DataMem[DAddr + 2] <= Data_In[23:16];
        DataMem[DAddr + 1] <= Data_In[15:8];
        DataMem[DAddr] <= Data_In[7:0];

Extend:
扩展模块包括sign-extend,zero-extend两种功能,由ExtSel选择,只要传入的Immediate 或ExtSel有变化即可触发,输出的是零位扩展后或是符号位扩展后的数字。

     always @ (Immediate or ExtSel) begin
         case(ExtSel)
             0: begin //zero extend
                 Ext_Immediate[31:16] <= 0;
                 Ext_Immediate[15:0] <= Immediate[15:0];
             end
             1: begin //sign extend
                 if(Immediate[15] == 0)
                     Ext_Immediate[31:16] <= 0;
                 else
                     Ext_Immediate[31:16] <=16'hFFFF;
                 Ext_Immediate[15:0] <= Immediate[15:0];
             end
         endcase

IR:
IR指令寄存器,用时钟信号clk驱动,目的是使指令代码保持稳定。同时我将j,jr跳转指令的赋值在IR中实现,具体代码如下:

     reg [31:0] tempPC;
     assign addr = InsOut[27:2];

    always @(posedge clk) begin
        if(IRWre) begin
            MIPSins <= InsOut;
            tempPC <= CurPC + 4;
        end
    end

    always @(negedge clk) begin
        tempPC <= CurPC + 4;
    end

     assign j[31:28] = tempPC[31:28];
     assign j[27:2] = InsOut[25:0];
     assign j[1:0] = 0;

实验结果及分析

首先设置顶层模块的测试文件MCC_test_file:

    initial begin
        // Initialize Inputs
        clk = 0;
        inPC = 32'b00000000000000000000000000000000;
        reset = 1;
        #20;
        reset = 0;
    end

    always begin
        #10 clk = !clk;
    end 

初始指令地址CurPC设定为0,reset置1保持20ns时间用于完成初始设置,设定时钟clk周期为10ns.
输入txt文件中存储的如下测试指令,对多周期CPU进行测试。

这里写图片描述这里写图片描述这里写图片描述

实验结果如下:

这里写图片描述

这里写图片描述

第一条指令进行寄存器与立即数的加法运算。由上波形图可知,rt代表寄存器1,rs传入的是0寄存器,ALU_A传入的是0寄存器的值0,ALU_B传入的是立即数8,经过加法运算后result为8,写入的寄存器WrReg也是$1,写入值WrRegData是8,符合设计预估执行结果,第一条指令执行成功。

这里写图片描述

这里写图片描述

第二条指令执行的是$$2 = $0 | 2,由上图可知,rs代表寄存器$2,rt代表寄存器$0,ALU_A传入的是0寄存器的值0,ALU_B传入的是立即数2,经过逻辑或运算后,result为2,写入的寄存器WrReg也是$2,写入值WrRegData是2,符合设计预估执行结果,第二条指令执行成功。

这里写图片描述

这里写图片描述

第三条指令执行的是$$3 = $1 + $2 = 10,由上图可知,rs代表寄存器$1,rt代表寄存器$2,rd代表寄存器$3,ALU_A传入的是$1的值8,ALU_B传入的是$1的值2,经过加运算后,result为10,写入的寄存器WrReg是$3,写入值WrRegData是10,符合设计预估执行结果,第三条指令执行成功。

这里写图片描述

这里写图片描述

第四条指令执行的是$$4 = $3 - $1 = 2,由上图可知,rs代表寄存器$3,rt代表寄存器$1,rd代表寄存器$4,ALU_A传入的是$3的值10,ALU_B传入的是$1的值8,经过减运算后,result为2,写入的寄存器WrReg是$4,写入值WrRegData是2,符合设计预估执行结果,第四条指令执行成功。

这里写图片描述

这里写图片描述

第五条指令执行的是 $$5 = $3 & $2 = 2,由上图可知,rs代表寄存器$3,rt代表寄存器$2,rd代表寄存器$5,ALU_A传入的是$3的值10,ALU_B传入的是$2值2,经过逻辑与运算后,result为2,写入的寄存器WrReg是$5,写入值WrRegData是2,符合设计预估执行结果,第五条指令执行成功。

这里写图片描述

这里写图片描述

第六条指令执行的是$$6 = $1 | $2 = 8,由上图可知,rs代表寄存器$1,rt代表寄存器$2,rd代表寄存器$6,ALU_A传入的是$1的值8,ALU_B传入的是$2值2,经过逻辑或运算后,result为10,写入的寄存器WrReg是$6,写入值WrRegData是10,符合设计预估执行结果,第六条指令执行成功。

这里写图片描述

这里写图片描述

第七条指令执行的是跳转至子程序指令0x00000038,同时将主程序下一条指令的地址0x0000001C保存到寄存器$$31中。由图7.1可知,信号j代表的,是传入的MIPS指令读取的addr高位补PC+4的前四位,低位补两个0获取的跳转指令地址0x00000038,curPC为当前指令地址0x00000018,波形图显示,下一个clk上升沿到来时,curPC变化为0x00000038,这代表着成功跳转到了子程序第一条指令。同时,写入的寄存器WrReg是$31,写入值WrRegData是0x0000001C,这代表着主程序的下一条指令地址0x00000018 + 4成功地存储到寄存器$31中。符合设计预估执行结果,第七条指令执行成功。

这里写图片描述
这里写图片描述
这里写图片描述
第八条指令执行的是mem[$$1 + 4] = mem[12] = $1 = 8,即将$1中存储的8按八位存入内存地址12中,由图8.1可知,Ext_Immediate代表的是字符扩展后的立即数4,rs代表寄存器$1,ALU_A的传入值为$1存储的8,ALU_B的传入值为字符扩展后的立即数4,运算结果result为12,DAddr代表的内存地址也为12,Data_in代表存入的数据为$1的值8。由图8.2可知8成功地存储到了数据存储器的第12位地址,符合设计预估执行结果,第八条指令执行成功。(在data memory模块设计中,因为我将低8位先传入,所以00001000存储在前面)

这里写图片描述

这里写图片描述

第九条指令执行的是$$11 = mem[$1 + 4] = mem[12] = 8,即读取内存地址12中存储的数据并写入寄存器$11中。由上图可知,rs代表的是寄存器$1,rt代表的是寄存器$11,ALU_A的传入值是$1中存储的8,ALU_B传入值是符号位扩展后的立即数Ext_Immediate值4,运算结果result为12,读取的内存地址DAddr也是12,内存中读出的值Data_Out为8,写入的寄存器WReg为11,写入的值WrRegData也正好是内存中读出的8,符合设计预估执行结果,证明第九条指令执行成功。

这里写图片描述

这里写图片描述
第十条指令执行的是跳转回主程序的下一条指令,即跳回0x0000001C。由前面jal指令可知,寄存器$$31存储了主程序的下一条指令地址0x0000001C,rs代表$31寄存器,读取$31的值Reg Data1的值正是0x0000001C,当前指令地址CurPC为0x00000040,而下一个clk上升沿到来时,CurPC即刻变化为0x0000001C,即主程序的下一条指令地址,说明成功跳转回到主程序,符合设计预估执行结果,第十条指令执行成功。

这里写图片描述

这里写图片描述
第十一条指令执行的是 7=( 1< 2)?1:0=0rs 1,rt代表寄存器 2rd 7,ALU_A传入的是 18ALUB 2值2,经过大小比较运算后,判断出8!< 2,result为0,写入的寄存器WrReg是$7,写入值WrRegData是0,符合设计预估执行结果,第十一条指令执行成功。

这里写图片描述

这里写图片描述
第十二条指令执行的是 8=( 1< 2)?1:0=1rs 2,rt代表寄存器 1rd 8,ALU_A传入的是 22ALUB 1值8,经过大小比较运算后,判断出2 <8,result为0,写入的寄存器WrReg是$8,写入值WrRegData是1,符合设计预估执行结果,第十二条指令执行成功。

这里写图片描述
这里写图片描述
第十三条指令执行的是 9=( 1<5)? 1 : 0 = 0,由上图可知,rs代表寄存器 1rt 9,ALU_A传入的是$$1的值8,ALU_B传入的是立即数5,经过大小比较运算后,判断出8! < 5,result为0,写入的寄存器WrReg是$9,写入值WrRegData是0,符合设计预估执行结果,第十三条指令执行成功。

这里写图片描述
这里写图片描述
第十四条指令执行的是 10=( 2 < 8)?1 : 0 = 1,由上图可知,rs代表寄存器 2rt 10,ALU_A传入的是$$2的值2,ALU_B传入的是立即数8,经过大小比较运算后,判断出2 < 8,result为1,写入的寄存器WrReg是$10,写入值WrRegData是1,符合设计预估执行结果,第十四条指令执行成功。

这里写图片描述
这里写图片描述
第十五条指令执行的是 $$2 = $2 << 2 = 8,由上图可知,rt代表寄存器$2,rd代表寄存器$2,sa代表移位数为2,ALU_A传入的是移位数2,ALU_B传入的是寄存器$2的值2,经过移位运算后,结果result为8,WrReg写入的寄存器是$2,写入的值WrReg Data是8,符合设计预估执行结果,因此第十五条指令执行成功。

这里写图片描述
这里写图片描述
第十六条指令执行的是PC = ($$1 == $2)?PC -4:PC + 4,由上图可知,rs代表寄存器$1;rt代表寄存器$2;Ext_Immediate 表示符号扩展后的CurPC移位数是-2(即向右移两位,CurPC = CurPC - 4;ALU_A传入的是$1的值8,ALU_B传入的是$2值8,经过比较运算后,判断出$1 == $2,result为0,观察图形可知,CurPC在下一个clk上升沿到来时从0x00000030变为0x0000002C,即跳转回上一条指令,符合设计预估执行结果,证明第十六条指令执行成功。

这里写图片描述
这里写图片描述
第十七条指令执行的是 $$2 = $2 << 2 = 32,由上图可知,rt代表寄存器$2,rd代表寄存器$2,sa代表移位数为2,ALU_A传入的是移位数2,ALU_B传入的是寄存器$2的值2,经过移位运算后,结果result为32,WrReg写入的寄存器是$2,写入的值WrReg Data是32,符合设计预估执行结果,因此第十七条指令执行成功。

这里写图片描述
这里写图片描述
第十八条指令执行的是PC = ($$1 == $2)?PC -4:PC + 4,由上图可知,rs代表寄存器$1;rt代表寄存器$2;Ext_Immediate 表示符号扩展后的CurPC移位数是-2(即向右移两位,CurPC = CurPC - 4;ALU_A传入的是$1的值8,ALU_B传入的是$2值32,经过比较运算后,判断出$1!= $2,result不为0,观察图形可知,CurPC在下一个clk上升沿到来时从0x00000030变为0x00000034,即CurPC = CurPC + 4,正常跳转到下一条指令,符合设计预估执行结果,证明第十八条指令执行成功。

这里写图片描述
这里写图片描述
第十九条指令执行的是跳转到指令0x00000044,信号j代表的,是传入的MIPS指令读取的addr高位补PC+4的前四位,低位补两个0获取的跳转指令地址0x00000044,curPC为当前指令地址0x00000034,波形图显示,下一个clk上升沿到来时,curPC变化为0x00000044,这代表着成功跳转到了指令0x00000044。符合设计预估执行结果,因此第十九条指令执行成功。

这里写图片描述
这里写图片描述
第二十条指令执行的是停机指令,停机后,多周期CPU停止运行,各使能信号处于非驱动状态,如图所示。符合设计预估执行结果,第二十条指令执行成功。

综上,这个简单的多周期CPU输入的测试指令情况全部正常,满足了本次实验的基本功能需求,实验成功。

实验心得

(1)本次实验最大的难点就是control unit的设计,尤其是用组合逻辑实现指令的多周期变化,后来通过查询资料了解到,可以通过设置state, EXE, ID之类的状态转移信号进行是能控制,实现状态转移,根据不同指令的op code设计不同的选择case,一个state完结后将state信号设置为下一个state的值,从而实现状态的转移。
(2)在各个模块的设计中也遇到了许多问题,首先就是对于j,jal型指令的错误理解。指令中的addr[27..2]并不是一个完整的地址,它只有26位,需要在高位补充PC+4的最高四位,并固定末位补全两个0,才能凑成一个32位地址,一开始粗心直接当成32位进行赋值,仿真结果提示出现严重错误,无法出现波形图才意识到问题,进行修改。
(3)接下来遇到的问题就是register file根本无法输出数据,经过检查,发现是时钟clk上升沿驱动总是与寄存器写使能信号上升沿错开,后修改为在时钟上升沿,寄存器写使能信号为1时即可赋值,才成功获取寄存器的输出数据。
(4)接下来的遇到的问题是lw指令一直无法输出数据,在data memory模块检查了很久,使能信号变化都是正常的,时钟驱动修改了许多遍,仍然找不到问题。按照我的测试指令设计,lw指令前有一条sw指令,先在内存地址第12位存储数据,lw再从第12位读取数据。单从波形图看,我认为sw指令是正常的,后来经同学帮忙检查发现,直接在memory文件中观察时,sw指令中我的数据并不是从内存第12位开始存储的,而是后移了两个字节,lw读取第12位自然读取不到数据,后来经过检查,才发现是ALU模块中,驱动信号EXE选择了下降沿驱动,导致result输出慢了半个周期,所以存储地址也后移了半个字,将ALU中的EXE修改为上升沿驱动后,问题解决。
(5)还有一个问题就是一开始对于IR模块的设计要求没有细致理解,直接忽略,后来才意识到它缓存指令的功能,设计出一个独立模块。
(6)这次试验中还有一些粗心的错误,有很多次模块无法正常输出数据,而后发现模块信号输入也不正常,经过检查才发现忘记对某些模块的输入信号进行赋值。
(7)检查了最久的一个粗心错误就是j指令的跳转失败,不断修改模块中跳转指令的驱动及赋值方式,后来才发现是测试指令地址写错,跳转到的是无指令地址,因此无法正常执行。

  • 10
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值