전공 이야기/Digital System Design

#4 MIPS Pipeline Processor Design (2) - ID, EX, MEM, WB

[감자] 2023. 12. 26. 19:53

1.3 ID Stage

 

 

 ID_Stage는 현재의 Instruction이 들어오면 해독하여 Opcode, Rs, Rt, Rd 등으로 구분짓는 역할을 한다. Opcode는 Control unit으로 전송되어 각 명령어에 맞는 Control signal을 제어하며, 나머지 Rs, Rt, Rd의 값을 받아 내부의 레지스터 파일을 이용하여 레지스터를 새로운 값으로 갱신하거나, 레지스터 번호에 맞는 데이터 값을 추출한다. I-type instruction의 경우, [15:0] instruction을 sign-extended로 보내 32비트로 만들어준다.

 

 

 또한 beq instruciton이 들어오게 될 경우 먼저 두 값이 같은지를 ID stage에서 판별하고 Branch equal 신호를 보내 Stall 여부를 미리 결정하는 역할도 한다.

 

module ID_Stage(
    input wire clk,
    input wire rst,
    input wire [31:0] IFtoID_PC,
    input wire [31:0] IFtoID_inst,
    input wire [4:0] writeReg,
    input wire [31:0] writeData,
    input wire RegWrite,
   
    input wire [31:0] EX_ALUresult, MEM_ALUresult,
    input wire [1:0] ForwardA, ForwardB,
   
    output wire [31:0] IDtoEX_ReadData1,
    output wire [31:0] IDtoEX_ReadData2,
    output wire [31:0] IDtoEX_Imm,
    output wire [5:0] IFtoID_Op,    //31:26
    output wire [4:0] IFtoID_Rs,    //25:21
    output wire [4:0] IFtoID_Rt,    //20:16
    output wire [4:0] IFtoID_Rd,    //15:11
    output wire [5:0] funct,
    output wire [31:0] Branch_Addr,
    output wire branch_equal,
   
    output wire [31:0] output_8, output_9, output_10, output_11, output_12, output_13, output_14, output_15
);
    // Register File
wire [31:0] RD1, RD2, zero, at, v0, v1, a0, a1, a2, a3, t0, t1, t2, t3, t4, t5, t6, t7,
            s0, s1, s2, s3, s4, s5, s6, s7, t8, t9, k0, k1, gp, sp, fp, ra;
wire [4:0] RR1, RR2, WR;
wire [31:0] WD;
wire Control_RegWrite;
 
wire [15:0] imm16;
wire [31:0] imm32; // using sign extension
wire [31:0] sll_out;
reg [31:0] fwd_RD1, fwd_RD2;
 
// Instruction Decode
    assign RR1 = IFtoID_inst[25:21];
    assign RR2 = IFtoID_inst[20:16];  
    assign WR = writeReg;
    assign WD = writeData;
    assign Control_RegWrite = RegWrite;
   
    // calculate branch address
    Shifting SHIFT (.Din(imm32), .shamt(5'd2), .left(1'b1), .Dout(sll_out));
    Adder ADD_addr (.operandA(IFtoID_PC), .operandB(sll_out), .sum(Branch_Addr));
   
    RegisterFile u0 (
        .RD1(RD1), .RD2(RD2),
        .zero(zero), .at(at), .v0(v0), .v1(v1), .a0(a0), .a1(a1), .a2(a2), .a3(a3),
        .t0(t0), .t1(t1), .t2(t2), .t3(t3), .t4(t4), .t5(t5), .t6(t6), .t7(t7),
        .s0(s0), .s1(s1), .s2(s2), .s3(s3), .s4(s4), .s5(s5), .s6(s6), .s7(s7),
        .t8(t8), .t9(t9), .k0(k0), .k1(k1), .gp(gp), .sp(sp), .fp(fp), .ra(ra),
        .RR1(RR1),
        .RR2(RR2),
        .WR(WR),
        .WD(WD),
        .WriteReg(Control_RegWrite),
        .clk(clk),
        .rst(rst)
    );
   
    SignExt u1 (
        .Y(imm32),
        .X(imm16)
    );
   
    always @(ForwardA, RD1, MEM_ALUresult, EX_ALUresult) begin
        case(ForwardA)
          default: fwd_RD1 <= RD1;
          1: fwd_RD1 <= MEM_ALUresult;
          2: fwd_RD1 <= EX_ALUresult;
        endcase
    end
 
always @(ForwardB, RD2, MEM_ALUresult, EX_ALUresult) begin
        case(ForwardB)
          default: fwd_RD2 <= RD2;
          1: fwd_RD2 <= MEM_ALUresult;
          2: fwd_RD2 <= EX_ALUresult;
        endcase
    end
 
    // register file output
    assign IDtoEX_ReadData1 = (WR == RR1)? WD:RD1;
    assign IDtoEX_ReadData2 = (WR == RR2)? WD:RD2;
    assign IDtoEX_Imm = imm32;
    assign imm16 = IFtoID_inst[15:0];
   
    assign IFtoID_Op = IFtoID_inst[31:26];  
    assign IFtoID_Rs = IFtoID_inst[25:21];
    assign IFtoID_Rt = IFtoID_inst[20:16];
    assign IFtoID_Rd = IFtoID_inst[15:11];
    assign funct = IFtoID_inst[5:0];
   
    assign output_8 = t0;
    assign output_9 = t1;  
    assign output_10 = t2;
    assign output_11 = t3;
    assign output_12 = t4;
    assign output_13 = t5;  
    assign output_14 = t6;
    assign output_15 = t7;
   
    assign branch_equal = (fwd_RD1 == fwd_RD2);
   
endmodule

 

 

1.3.1 Register file

Register file 동작 설명

 Register file은 instruction을 읽고 해당 레지스터 번호를 찾아 데이터 값을 읽어내거나 데이터를 갱신하는 역할을 한다. 추가적으로 t0, t1, t2, t3 레지스터의 경우 자주 사용하는 레지스터 파일이기에 직접 값을 확인할 수 있도록 output으로 연결하였다.

 

module RegisterFile(RD1, RD2, zero, at, v0, v1, a0, a1, a2, a3, t0, t1, t2, t3, t4, t5, t6, t7,
                    s0, s1, s2, s3, s4, s5, s6, s7, t8, t9, k0, k1, gp, sp, fp, ra,
                    RR1, RR2, WR, WD, WriteReg, clk, rst);
                   
    output [31:0] RD1, RD2, zero, at, v0, v1, a0, a1, a2, a3, t0, t1, t2, t3, t4, t5, t6, t7,
                  s0, s1, s2, s3, s4, s5, s6, s7, t8, t9, k0, k1, gp, sp, fp, ra;
    input [4:0] RR1, RR2, WR;
    input [31:0] WD;
    input clk, rst, WriteReg; // control signal
   
    wire [31:0] WSel;
    wire [31:0] Q [31:0];
   
    Decoder_5to32   U0 (.Y(WSel), .X(WR), .En(WriteReg));
           Mux_32   U1 (.Y(RD1), .Sel(RR1),
                        .X0(Q[0]),   .X1(Q[1]),   .X2(Q[2]),   .X3(Q[3]),
                        .X4(Q[4]),   .X5(Q[5]),   .X6(Q[6]),   .X7(Q[7]),
                        .X8(Q[8]),   .X9(Q[9]),   .X10(Q[10]), .X11(Q[11]),
                        .X12(Q[12]), .X13(Q[13]), .X14(Q[14]), .X15(Q[15]),
                        .X16(Q[16]), .X17(Q[17]), .X18(Q[18]), .X19(Q[19]),
                        .X20(Q[20]), .X21(Q[21]), .X22(Q[22]), .X23(Q[23]),
                        .X24(Q[24]), .X25(Q[25]), .X26(Q[26]), .X27(Q[27]),
                        .X28(Q[28]), .X29(Q[29]), .X30(Q[30]), .X31(Q[31])
                       );
                       
           Mux_32   U2 (.Y(RD2), .Sel(RR2),
                        .X0(Q[0]),   .X1(Q[1]),   .X2(Q[2]),   .X3(Q[3]),
                        .X4(Q[4]),   .X5(Q[5]),   .X6(Q[6]),   .X7(Q[7]),
                        .X8(Q[8]),   .X9(Q[9]),   .X10(Q[10]), .X11(Q[11]),
                        .X12(Q[12]), .X13(Q[13]), .X14(Q[14]), .X15(Q[15]),
                        .X16(Q[16]), .X17(Q[17]), .X18(Q[18]), .X19(Q[19]),
                        .X20(Q[20]), .X21(Q[21]), .X22(Q[22]), .X23(Q[23]),
                        .X24(Q[24]), .X25(Q[25]), .X26(Q[26]), .X27(Q[27]),
                        .X28(Q[28]), .X29(Q[29]), .X30(Q[30]), .X31(Q[31])
                       );
   
    dff Reg0  (.Q(Q[0]),  .D(WD),  .En(WSel[0]), .clk(clk), .rst(rst));
    dff Reg1  (.Q(Q[1]),  .D(WD),  .En(WSel[1]), .clk(clk), .rst(rst));
    dff Reg2  (.Q(Q[2]),  .D(WD),  .En(WSel[2]), .clk(clk), .rst(rst));
    dff Reg3  (.Q(Q[3]),  .D(WD),  .En(WSel[3]), .clk(clk), .rst(rst));
    dff Reg4  (.Q(Q[4]),  .D(WD),  .En(WSel[4]), .clk(clk), .rst(rst));
    dff Reg5  (.Q(Q[5]),  .D(WD),  .En(WSel[5]), .clk(clk), .rst(rst));
    dff Reg6  (.Q(Q[6]),  .D(WD),  .En(WSel[6]), .clk(clk), .rst(rst));
    dff Reg7  (.Q(Q[7]),  .D(WD),  .En(WSel[7]), .clk(clk), .rst(rst));
    dff Reg8  (.Q(Q[8]),  .D(WD),  .En(WSel[8]), .clk(clk), .rst(rst));
    dff Reg9  (.Q(Q[9]),  .D(WD),  .En(WSel[9]), .clk(clk), .rst(rst));
    dff Reg10 (.Q(Q[10]), .D(WD),  .En(WSel[10]), .clk(clk), .rst(rst));
    dff Reg11 (.Q(Q[11]), .D(WD),  .En(WSel[11]), .clk(clk), .rst(rst));
    dff Reg12 (.Q(Q[12]), .D(WD),  .En(WSel[12]), .clk(clk), .rst(rst));
    dff Reg13 (.Q(Q[13]), .D(WD),  .En(WSel[13]), .clk(clk), .rst(rst));
    dff Reg14 (.Q(Q[14]), .D(WD),  .En(WSel[14]), .clk(clk), .rst(rst));
    dff Reg15 (.Q(Q[15]), .D(WD),  .En(WSel[15]), .clk(clk), .rst(rst));
    dff Reg16 (.Q(Q[16]), .D(WD),  .En(WSel[16]), .clk(clk), .rst(rst));
    dff Reg17 (.Q(Q[17]), .D(WD),  .En(WSel[17]), .clk(clk), .rst(rst));
    dff Reg18 (.Q(Q[18]), .D(WD),  .En(WSel[18]), .clk(clk), .rst(rst));
    dff Reg19 (.Q(Q[19]), .D(WD),  .En(WSel[19]), .clk(clk), .rst(rst));
    dff Reg20 (.Q(Q[20]), .D(WD),  .En(WSel[20]), .clk(clk), .rst(rst));
    dff Reg21 (.Q(Q[21]), .D(WD),  .En(WSel[21]), .clk(clk), .rst(rst));
    dff Reg22 (.Q(Q[22]), .D(WD),  .En(WSel[22]), .clk(clk), .rst(rst));
    dff Reg23 (.Q(Q[23]), .D(WD),  .En(WSel[23]), .clk(clk), .rst(rst));
    dff Reg24 (.Q(Q[24]), .D(WD),  .En(WSel[24]), .clk(clk), .rst(rst));
    dff Reg25 (.Q(Q[25]), .D(WD),  .En(WSel[25]), .clk(clk), .rst(rst));
    dff Reg26 (.Q(Q[26]), .D(WD),  .En(WSel[26]), .clk(clk), .rst(rst));
    dff Reg27 (.Q(Q[27]), .D(WD),  .En(WSel[27]), .clk(clk), .rst(rst));
    dff Reg28 (.Q(Q[28]), .D(WD),  .En(WSel[28]), .clk(clk), .rst(rst));
    dff Reg29 (.Q(Q[29]), .D(WD),  .En(WSel[29]), .clk(clk), .rst(rst));
    dff Reg30 (.Q(Q[30]), .D(WD),  .En(WSel[30]), .clk(clk), .rst(rst));
    dff Reg31 (.Q(Q[31]), .D(WD),  .En(WSel[31]), .clk(clk), .rst(rst));
   
    assign zero = Q[0];
    assign at = Q[1];
    assign v0 = Q[2];
    assign v1 = Q[3];
    assign a0 = Q[4];
    assign a1 = Q[5];
    assign a2 = Q[6];
    assign a3 = Q[7];
    assign t0 = Q[8];
    assign t1 = Q[9];
    assign t2 = Q[10];
    assign t3 = Q[11];
    assign t4 = Q[12];
    assign t5 = Q[13];
    assign t6 = Q[14];
    assign t7 = Q[15];
    assign s0 = Q[16];
    assign s1 = Q[17];
    assign s2 = Q[18];
    assign s3 = Q[19];
    assign s4 = Q[20];
    assign s5 = Q[21];
    assign s6 = Q[22];
    assign s7 = Q[23];
    assign t8 = Q[24];
    assign t9 = Q[25];
    assign k0 = Q[26];
    assign k1 = Q[27];
    assign gp = Q[28];
    assign sp = Q[29];
    assign fp = Q[30];
    assign ra = Q[31];   
      
endmodule

 

 

 

 

1.3.2 Sign-Extended

 

Sign-Extended 동작 설명

 Sign-Extended는 I-type instruction에서 사용하며, 명령어의 [15:0]bit를 받아 32bit로 확장하는 역할을 한다.

 

module SignExt(Y, X);
    output [31:0] Y;
    input [15:0] X;
   
    assign Y[15:0] = X;
    assign Y[31:16] = {16{X[15]};
 
endmodule

 

 

 

 

 

 

1.4 EX Stage

 

EX_Stage 동작 설명

 

 EX_Stage는 ID_Stage로부터 전달된 데이터를 처리하는 과정이다. 이 단계에서는 ALUOp 제어 신호를 이용하여 ALU 제어 장치가 ALU가 수행할 연산을 결정하고, ALU에서 필요한 계산을 실행한 후, 그 결과를 다음 Stage로 전달한다. 데이터 하자드(Data Hazard)가 발생하여 Forwarding이 필요한 경우, Forwarding Unit은 Mux 선택 신호를 전달하여 ALU에서 연산을 수행할 데이터를 결정한다. 그 후 해당 데이터를 ALU로 전달하여 연산을 수행한다.

 

module EX_Stage(
    // datapath input
    input wire [31:0] IDtoEX_ReadData1,
    input wire [31:0] IDtoEX_ReadData2, // Read Data from Register File
    input wire [31:0] IDtoEX_Imm, // Sign extended immediate value
    input wire [4:0] IDtoEX_Rt,
    input wire [4:0] IDtoEX_Rd, // Destination Register
   
    // instruction input
    input wire [5:0] funct, // Function code
   
    // control inputs
    input wire [1:0] ALUop,
    input wire ALUSrc,
    input wire RegDst,
// Forwarding inputs
    input wire [31:0] EXtoMEM_ALUresult,
    input wire [31:0] WB_ALUresult,
   
    // forwarding control
    input wire [1:0] ForwardA, ForwardB,
   
    // datapath output
    output wire [31:0] ALUresult,
    output wire [31:0] EX_ReadData2,
    output wire [4:0] RegDest // Write data destination
);
    wire [31:0] ALUSrc_out, SL2_out;
    wire [3:0]  ALUcontrol;
    wire [31:0] FWA_out, FWB_out;
 
    // ALU Control
    ALU_Control alu_control (.ALUop(ALUop), .funct(funct), .ALUctrl(ALUcontrol));
 
    // ALU
    ALU alu (
        .operandA(FWA_out), .operandB(ALUSrc_out),
        .aluControl(ALUcontrol),
        .result(ALUresult)
    );
 
    // ALU Source MUX
    Mux_2 mux_ALUSrc (.X0(FWB_out), .X1(IDtoEX_Imm), .sel(ALUSrc), .Y(ALUSrc_out));
   
   
    // Forward A
    Mux_4 mux_FWA (
        .X0(IDtoEX_ReadData1), .X1(WB_ALUresult), .X2(EXtoMEM_ALUresult), .X3(/*None*/),
        .sel(ForwardA),
        .Y(FWA_out)
    );
 
    // Forward B
    Mux_4 mux_FWB (
        .X0(IDtoEX_ReadData2), .X1(WB_ALUresult), .X2(EXtoMEM_ALUresult), .X3(/*None*/),
        .sel(ForwardB),
        .Y(FWB_out)
    );
 
    // Select Destination Register for writing data
    Mux5_2 mux_RegDst (.X0(IDtoEX_Rt), .X1(IDtoEX_Rd), .sel(RegDst), .Y(RegDest));
   
    assign EX_ReadData2 = FWB_out;
endmodule

 

 

1.4.1 ALU (Arithmetic Logic Unit)

 

ALU 동작 설명

 

ALU Control Unit으로부터 ALU Control 신호를 전달받아 제어신호에 해당하는 연산을 진행하는 조합회로이다. 현재 구현된 연산과 ALU Control 신호의 관계는 다음과 같다.

 

ALU Control Line Operation
0000 and
0001 or
0010 add
0110 sub
0111 slt
1100 nor
module ALU(
    input wire [31:0] operandA,
    input wire [31:0] operandB,
    input wire [3:0]  aluControl,
    output reg [31:0] result
);
    always @(operandA or operandB or aluControl) begin
        casex (aluControl)
            4'b0000: result = operandA & operandB; // and
            4'b0001: result = operandA | operandB; // or
            4'b0010: result = operandA + operandB; // add
            4'b011x: begin
                         result = operandA - operandB; // sub or slt
                         if(aluControl[0] == 1) // slt
                            if(operandA < operandB) result = 32'd1;
                            else result = 32'd0;
                     end
            4'b1100: result = ~(operandA | operandB); // nor
            default: result = 32'h0;
        endcase
    end
endmodule

 

 

 

1.4.2 ALU Control

 

 ALU Control Unit 동작 설명

 

ALUOp 제어 신호와 Instruciton 중 funct 영역을 이용하여 ALU에서 실행할 연산을 정해주는 제어 신호를 ALU에 전달한다. Instruction별로 ALUOp와 funct 코드에 따른 ALU control line은 다음과 같다.

 

Instructoin ALUop funct ALUcontrol
lw 0 0 X X X X X X 0 0 1 0
sw 0 0 X X X X X X 0 0 1 0
beq 0 1 X X X X X X 0 1 1 0
add 1 0 1 0 0 0 0 0 0 0 1 0
sub 1 0 1 0 0 0 1 0 0 1 1 0
and 1 0 1 0 0 1 0 0 0 0 0 0
or 1 0 1 0 0 1 0 1 0 0 0 1
slt 1 0 1 0 1 0 1 0 0 1 1 1

 

module ALU_Control(
    input wire [1:0] ALUop,
    input wire [5:0] funct,
    output reg [3:0] ALUctrl
    );
    always @(ALUop or funct) begin
      case (ALUop)
        2'b00:  ALUctrl = 4'b0010; // lw, sw
        2'b01:  ALUctrl = 4'b0110; // beq
        2'b10:  if (funct == 6'b100000) // add
                    ALUctrl = 4'b0010;
                else if (funct == 6'b100010) // sub
                    ALUctrl = 4'b0110;
                else if (funct == 6'b100100) // and
                    ALUctrl = 4'b0000;
                else if (funct == 6'b100101) // or
                    ALUctrl = 4'b0001;
                else if (funct == 6'b101010) // slt
                    ALUctrl = 4'b0111;
                else if (funct == 6'b100111) // nor
                    ALUctrl = 4'b0000;
                else
                    ALUctrl = 4'b0010;
        default: ALUctrl = 4'd0;
      endcase
    end

 

 

 

 

 

 

1.5 MEM Stage

 

MEM_Stage 동작 설명

 

MEM_Stage에서는 데이터 메모리에 접근하여 메모리에 데이터를 쓰거나 읽는 작업을 수행하는 단계이다. lw, sw와 같은 Instruction에 의해서 실행되는 영역이며, 메모리 접근이 없는 경우 ALU의 결과를 그대로 다음 Stage로 넘겨준다.

 

module MEM_Stage(
    // system clock
    input wire clk,
 
    // datapath input
    input wire [31:0] EXtoMEM_ALUresult,
    input wire [31:0] EXtoMEM_ReadData2,
    input wire [4:0] EXtoMEM_RegDest,
   
    // control input
    input wire MemWrite,
    input wire MemRead,
 
    // datapath output
    output wire [31:0] MEM_ReadData,
    output wire [31:0] MEM_ALU_result,
    output wire [4:0] MEM_RegDest,
   
    // memory output
    output wire [31:0] output_memory8, output_memory9, output_memory10, output_memory11,
    output wire [31:0] output_memory12, output_memory13, output_memory14, output_memory15
);
 
    // Data Memory
    DataMemoryUnit Data_MEM (
        .clk(clk),
        .memWrite(MemWrite),
        .memRead(MemRead),
        .address(EXtoMEM_ALUresult),
        .writeData(EXtoMEM_ReadData2),
        .readData(MEM_ReadData),
        .memory8(output_memory8), .memory9(output_memory9), .memory10(output_memory10), .memory11(output_memory11),
        .memory12(output_memory12), .memory13(output_memory13), .memory14(output_memory14), .memory15(output_memory15)
    );
 
    // select signal of PC source
    assign MEM_RegDest = EXtoMEM_RegDest;
    assign MEM_ALU_result = EXtoMEM_ALUresult;
 
endmodule

 

 

1.5.1 Data Memory

 

 Data Memory 동작 설명

 

 Data Memory는 프로그램을 실행하면서 사용되는 데이터들이 저장되어있는 메모리이다. ALU에서 계산된 메모리 주소에 접근하여 메모리에 데이터를 읽어오거나 해당 메모리에 데이터를 쓰는 동작을 한다. 현재 프로젝트에서는 프로세서의 동작에 초점을 두고 구현하고 있기 때문에 메모리는 레지스터들을 이용하여 간단하게 구현하였다. lw, sw instruction만 구현하였고 ALU에서 구현한 연산에 의해 메모리는 Byte Address가 아닌 Word Address 방식으로 접근하도록 구현하였다.

 

module DataMemoryUnit(
    input wire clk,
    input wire memWrite,
    input wire memRead,
    input wire [31:0] address,
    input wire [31:0] writeData,
    output reg [31:0] readData,
    output wire [31:0] memory8, memory9, memory10, memory11,
    output wire [31:0] memory12, memory13, memory14, memory15
);
 
    reg [31:0] memory [0:1023];
 
    always @(negedge clk) begin
        if (memWrite)
            memory[address] <= writeData;
        if (memRead)
            readData <= memory[address];
    end
   
    initial begin
        memory[8] = 32'h0000000a;
        memory[9] = 32'h0000000b;
        memory[10] = 32'h0000000c;
        memory[11] = 32'h0000000d;
        memory[12] = 32'h0000000e;
        memory[13] = 32'h0000000f;
        memory[14] = 32'h00000003;
        memory[15] = 32'h00000006;
    end
   
    assign memory8 = memory[8];
    assign memory9 = memory[9];
    assign memory10 = memory[10];
    assign memory11 = memory[11];
    assign memory12 = memory[12];
    assign memory13 = memory[13];
    assign memory14 = memory[14];
    assign memory15 = memory[15];
 
endmodule

 

 

 

 

 

 

1.6 WB Stage

 

WB_Stage 동작 설명

 

 WB_Stage에서는 Instruction 실행이 완료되고 결과를 Register File에 쓰는 단계이다. 연산결과 또는 메모리로부터 읽어온 데이터를 목적지에 해당하는 레지스터에 업데이트한다.

 

module WB_Stage(
    // datapath input
    input wire [31:0] MEMtoWB_ReadData,
    input wire [31:0] MEMtoWB_ALU_result,
    input wire [4:0] MEMtoWB_RegDest,
 
    // control input
    input wire MemtoReg,
 
    // datapath output
    output wire [31:0] WB_WriteReg,
    output wire [4:0] WB_RegDest
    );
   
    Mux_2 mux_WB
    (
        .X0(MEMtoWB_ReadData), .X1(MEMtoWB_ALU_result),
        .sel(MemtoReg),
        .Y(WB_WriteReg)
    );
 
    assign WB_RegDest = MEMtoWB_RegDest;
   
endmodule