risc-v处理器设计

绪论

为了更好的学习和理解RISC-V处理器设计,在阅读了计算机组成与设计软硬件接口RISC-V这本书后,我决定自己动手写一个简单的CPU,下面我将分享整个设计的过程,本过程是我个人学习的一个记录,仅供参考,我的代码水平不高,作为一名跨专业考生我只是学习了半年的的verilog,代码质量肯定不是很高,顺便一提,整个CPU基本都是我根据书上的图来搭建的,可能代码写的有些冗余? 因为做完以后我发现有些步骤确实有点多余,但逻辑应该是没问题的,基本的32I指令都能跑通。

我的学习过程是先以单周期CPU开始,在设计完单周期CPU后在进行5阶流水线CPU的设计,我的5阶流水线CPU中解决了数据冒险和控制冒险,其中数据冒险用forwarding来实现,还有一张load—use型数据冒险用stall技术来实现。控制冒险我做的比较简单,就是静态分支预测,我使用的是Nottaken。

整个设计中每行代码都是我自己手打的,我写代码的经验不是很丰富,我自己也感觉我在设计的过程中有些地方做的有亿点点复杂,在今后的设计中我定会吸取教训,变得更加老练。我目前的CPU设计只是简单的一小步,在写完这篇报告后,我就要去学习总线,chache,串口,gpio等一些外围设备了,再之后就是学习一下脚本语言,我在github上看到别人写的程序都是脚本话的,验证起来很方便,而我自己验证的过程很笨很原始,就是把不同种类的指令,在一个反汇编网站上转成16进制机器码,写入txt文件在读取, 这种方法确实费时费力,希望在今后能学好一门脚本语言。

最后,我还是力荐这本书,计算机组成与设计-软/硬件接口,RISC-V版。我在这本书中真正的学会了很多知识,大家可以可以在youtube上找 朱宗贤老师的这们课的讲解,讲的很好。

第一章:单周期CPU设计

单周期的CPU设计很简单,我做这个的过程就是照着书上的几张图片,直接就完成了整个数据通路,控制信号生成的搭建,看懂CPU原理图对我们的设计真的十分重要,照着图,把每一个组件都搭建好,最后连线就可以完成。我在接下来的阐述中也是以这个为前提的。
​​​​​​单周期CPU结构图
来看这张图,揭示了单周期CPU的结构,分别是PC寄存器,指令内存,寄存器堆,ALU单元,数据内存,以及一些控制单元,通过分组我们可以把他对应成为CPU的五个阶段,分别是取指,译码,执行,访存,写回。

在一开始设计代码的时候我最大的问题是不知道什么时候用组合逻辑,什么时候用时序逻辑,这个问题其实在单周期阶段还好,到了流水线CPU阶段,更是一直困扰了我许久,这个其实要说也很简单,但我觉得还是要自己在设计的时候多经历点坑自己理解才算是真正的理解了这种简单CPU的机制。在这里插入图片描述
这是我划分后的结构 ,接下来我们按顺序一个一个完成设计

①:pc_reg

我们看到,pc_reg是用来计算下一个pc的地址,并且在每个时钟上升沿,把下一个PC的地址发出去给IF,那么就引出了个两个变量,next_pc和now_pc

​
module pc_reg(
    input                   clk,
    input                   rst_n,
//Unconditonal jump:jal jalr
    input                   jump_flag_i,
    input   [31:0]          jump_addr_i,
//conditional branch  :beq bne blt bg
    input                   Branch, 
    input   [31:0]          branch_addr_i,
    output  reg [31:0]      now_pc
);

reg [31:0]  next_pc;

always @(*)begin
    if(jump_flag_i) begin
        next_pc = jump_addr_i;
    end
    else if(Branch)begin
        next_pc = branch_addr_i;
    end
    else begin
        next_pc = now_pc+4;
    end
end 

always@ (posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        now_pc <= 32'h0;
    end
    else begin
        now_pc <= next_pc;
    end

end
endmodule


②:IF阶段

IF阶段根据pc_reg所输出的pc值,将其传递给inst_rom,读inst_rom,取得指令

里还是写成两个不同的控制信号好一些。

​
module IF(
    input   rst_n,
    //from pc_reg;
    input   [31:0]  pc_if_i,

    //from inst_rom
    input   [31:0]   inst_if_i,

    //to inst_rom
    output  reg [31:0]  pc_if_o,            // as address to access mem
    output  reg         mem_en,
    // to ex
    output  reg [31:0]  inst_if_o      //data   from mem
);
always@(*) begin
    if(!rst_n)begin
        pc_if_o = 32'h0;
        inst_if_o = 32'h0;
        mem_en = 1'b0;
    end
    else begin
        pc_if_o = pc_if_i;
        inst_if_o = inst_if_i;
        mem_en = 1'b1;
    end
end

endmodule

​

③:ID阶段

此过程主要是搞清楚,RISC-V指令的译码过程,首先,要知道译码是干什么,我理解的译码,就是通过翻译输入的机器指令,来生成后续阶段的控制信号的。我觉得是单周期CPU设计过程中最关键的过程。
​​在这里插入图片描述

可以看到共有R-type, I-type, S-type, SB-type, U-type, UJ-type t六种类型的指令,指令的类型相同,说明指令的每个阶段操作类似,但功能不一定类似,生成的控制信号也就不一定一样。为了更好的不同的ALU控制和控制信号,我把这六种指令又细分了一下,也就是根据Opcode,进一步划分,划分结果如下
在这里插入图片描述
在译码阶段,我们主要干四件事,1、:控制信号生成 2:读寄存器堆3:立即数扩展。4:ALU控制信号(让ALU做不同的事)
1:控制信号的生成,通过risc-v的汇编指令的opcode,也就是instruction[6:0],我们基本知道这个指令是用来干什么的,具体我们可以看图3,也就是说根据opcode,我们就能生成控制信号

// control
assign MemRead_id_o  = (instr_id_i[6:0]   == `INST_TYPE_IL);
assign MemtoReg_id_o = (instr_id_i[6:0]   == `INST_TYPE_IL);
assign ALUOp    = ((instr_id_i[6:0]  == `INST_TYPE_IL) || (instr_id_i[6:0] == `INST_TYPE_S) || (instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0] == `INST_TYPE_U)) ? 2'b00:
                (instr_id_i[6:0] == `INST_TYPE_SB) ? 2'b01 :
                (instr_id_i[6:0] == `INST_TYPE_R)||(instr_id_i[6:0] == `INST_TYPE_II) ? 2'b10 : 2'b11;
assign MemWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_S);
assign ALUSrc = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_SB);
assign RegWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_IL)|| (instr_id_i[6:0] == `INST_TYPE_U)||(instr_id_i[6:0] == `INST_TYPE_II)||(instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0]== `INST_TYPE_JAL); 

2:读寄存器堆:当寄存器地址是0是,读得的值为0,读操作一定是组合逻辑,写操作一定是时序逻辑


//reg read
assign rd_data1 = (reg_addr1==5'd0)?  0:regs[reg_addr1];
assign rd_data2 = (reg_addr2==5'd0)?  0:regs[reg_addr2];

endmodule

3:立即数扩展:不同的指令类型,立即数的类型也不同
在这里插入图片描述

// immgen
assign R_type = (instr_id_i[6:0] == `INST_TYPE_R);
assign I_type = (instr_id_i[6:0] == `INST_TYPE_II) ||(instr_id_i[6:0] == `INST_TYPE_IL)||(instr_id_i[6:0] == `INST_TYPE_JALR) ;
assign S_type = (instr_id_i[6:0] == `INST_TYPE_S);
assign SB_type= (instr_id_i[6:0] == `INST_TYPE_SB);
assign U_type = (instr_id_i[6:0] == `INST_TYPE_U);
assign UJ_type= (instr_id_i[6:0] == `INST_TYPE_JAL);

assign I_imme = {{20{instr_id_i[31]}},instr_id_i[31:20]};
assign S_imme = {{20{instr_id_i[31]}},instr_id_i[31:25],instr_id_i[11:7]};
assign SB_imme= {{20{instr_id_i[31]}},instr_id_i[7],instr_id_i[30:25],instr_id_i[11:8],1'b0};
assign U_imme = {instr_id_i[31:12],12'b0};
assign UJ_imme= {{12{instr_id_i[31]}},instr_id_i[19:12],instr_id_i[20],instr_id_i[30:21],1'b0};

assign imme = I_type?   I_imme  :
              S_type?   S_imme  :
              SB_type?  SB_imme :
              U_type?   U_imme  :
                UJ_type?  UJ_imme :32'd0;


4、ALU控制信号
根据不同的instruction[6:0],在控制信号中可以生成不同的ALUOp,而不同的ALUop可以初步确定是如何使用ALU的,在根据进一步的fun3,fun7,就能确定ALUCtrl,从而传递到ex阶段去,让ALU执行不同的功能

                                                                           
//ALUCtrl
always@(*) begin
    if(!rst_n)begin
        ALUCtrl_id_o = 4'b0000;
    end
    else begin
    case(ALUOp)
        2'b00:ALUCtrl_id_o = 4'b1000; // load or store , so use add 
        2'b01:begin    //condition branch 
            case(fun3_id_o)
                3'b000:ALUCtrl_id_o = 4'b0001;//beq,  branch if equal
                3'b001:ALUCtrl_id_o = 4'b0010;//bne,  branch if not equal
                3'b100:ALUCtrl_id_o = 4'b0011;//blt,  branch if less than
                3'b101:ALUCtrl_id_o= 4'b0100;//bge, branch if greater than
                3'b110:ALUCtrl_id_o= 4'b0101;//bltu
                3'b111:ALUCtrl_id_o= 4'b0110;//bgeu
                default : ALUCtrl_id_o= 4'b0000; // do nothing
            endcase
        end
        2'b10:begin//R_type  or II_type
            case(fun3_id_o)
                3'b000:begin
                    if(instr_id_i[6:0] == `INST_TYPE_R)begin
                    ALUCtrl_id_o = (fun7[5] == 0)? 4'b1000:4'b1001;   //fun7==0 so add ,fun7=1 so sub 
                    end
                    else begin// II_TYPE
                    ALUCtrl_id_o= 4'b1000;  //addi
                    end
                end
                3'b001:ALUCtrl_id_o= 4'b1010;     // sll  slli
                3'b100:ALUCtrl_id_o= 4'b1011;   //   xor  xori
                3'b101:begin
                    if(fun7[5])
                    ALUCtrl_id_o = 4'b1100;  //  sra  srai
                    else
                    ALUCtrl_id_o = 4'b1101;   // srl  srli
                end
                3'b110:ALUCtrl_id_o = 4'b1110;  // or   ori
                3'b111:ALUCtrl_id_o = 4'b1111;  //and  andi
                default : ALUCtrl_id_o = 4'b0000; // do nothing
            endcase
        end
        default : ALUCtrl_id_o = 4'b0000; // do nothing
    endcase
    end
end

整个ID阶段代码如下

`include "defines.v"
module ID(
    input   rst_n,
    input   [31:0]  pc_id_i,
    input   [31:0]  instr_id_i,
    //from  regsfile
    input   [31:0]  rd_data1_id_i,
    input   [31:0]  rd_data2_id_i,



    //to pc_reg
    output          jump_flag_id_o,
    output  [31:0]  jump_addr_id_o,
    output  [31:0]  branch_addr_id_o,
    //to    regsfile  
    output  [4:0]   reg_addr1_id_o,   //rs1
    output  [4:0]   reg_addr2_id_o,  //rs2
    //to  ex
    output  [6:0]       opcode_id_o,
    output  [31:0]      op1_id_o,
    output  [31:0]      op2_id_o,
    output  [2:0]       fun3_id_o,
    output  [4:0]       rdregaddr_id_o,//rd
    output  reg [3:0]   ALUCtrl_id_o,
    output              MemWrite_id_o,
    output              MemRead_id_o,
    output              MemtoReg_id_o,
    output              RegWrite_id_o

);
wire R_type; // AND SUB SLI XOR SRL SRA OR AND         ALU
wire I_type;//addi slli xori srli srai ori andi  jarl
wire S_type;// sb sd sh sw
wire SB_type; //  condition branch
wire U_type; //upper inmmediate format
wire UJ_type;  // uncondition branch

wire    [31:0] R_imme;
wire    [31:0] I_imme;
wire    [31:0] S_imme;
wire    [31:0] SB_imme;
wire    [31:0] U_imme;
wire    [31:0] UJ_imme;

wire    [6:0]   fun7;
wire    [1:0]   ALUOp;

wire    [31:0]  imme;

assign reg_addr1_id_o = ((R_type == 1'b1)||(I_type == 1'b1)||(S_type == 1'b1)||(SB_type ==1'b1))?  instr_id_i[19:15]:5'b0;  //rs1
assign reg_addr2_id_o = ((R_type == 1'b1)||(S_type == 1'b1)||(SB_type == 1'b1))?  instr_id_i[24:20]:1'b0;    //rs2
assign fun3_id_o      = instr_id_i[14:12];
assign fun7           = instr_id_i[31:25];
assign rdregaddr_id_o   = instr_id_i[11:7];  //rd
assign opcode_id_o        = instr_id_i[6:0];
// immgen
assign R_type = (instr_id_i[6:0] == `INST_TYPE_R);
assign I_type = (instr_id_i[6:0] == `INST_TYPE_II) ||(instr_id_i[6:0] == `INST_TYPE_IL)||(instr_id_i[6:0] == `INST_TYPE_JALR) ;
assign S_type = (instr_id_i[6:0] == `INST_TYPE_S);
assign SB_type= (instr_id_i[6:0] == `INST_TYPE_SB);
assign U_type = (instr_id_i[6:0] == `INST_TYPE_U);
assign UJ_type= (instr_id_i[6:0] == `INST_TYPE_JAL);

assign I_imme = {{20{instr_id_i[31]}},instr_id_i[31:20]};
assign S_imme = {{20{instr_id_i[31]}},instr_id_i[31:25],instr_id_i[11:7]};
assign SB_imme= {{20{instr_id_i[31]}},instr_id_i[7],instr_id_i[30:25],instr_id_i[11:8],1'b0};
assign U_imme = {instr_id_i[31:12],12'b0};
assign UJ_imme= {{12{instr_id_i[31]}},instr_id_i[19:12],instr_id_i[20],instr_id_i[30:21],1'b0};

assign imme = I_type?   I_imme  :
              S_type?   S_imme  :
              SB_type?  SB_imme :
              U_type?   U_imme  :
                UJ_type?  UJ_imme :32'd0;

// control
assign MemRead_id_o  = (instr_id_i[6:0]   == `INST_TYPE_IL);
assign MemtoReg_id_o = (instr_id_i[6:0]   == `INST_TYPE_IL);
assign ALUOp    = ((instr_id_i[6:0]  == `INST_TYPE_IL) || (instr_id_i[6:0] == `INST_TYPE_S) || (instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0] == `INST_TYPE_U)) ? 2'b00:
                (instr_id_i[6:0] == `INST_TYPE_SB) ? 2'b01 :
                (instr_id_i[6:0] == `INST_TYPE_R)||(instr_id_i[6:0] == `INST_TYPE_II) ? 2'b10 : 2'b11;
assign MemWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_S);
assign ALUSrc = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_SB);
assign RegWrite_id_o = (instr_id_i[6:0] == `INST_TYPE_R) || (instr_id_i[6:0] == `INST_TYPE_IL)|| (instr_id_i[6:0] == `INST_TYPE_U)||(instr_id_i[6:0] == `INST_TYPE_II)||(instr_id_i[6:0] == `INST_TYPE_JALR)||(instr_id_i[6:0]== `INST_TYPE_JAL); 

assign op1_id_o = (jump_flag_id_o==1)?          pc_id_i:rd_data1_id_i;
assign op2_id_o = (jump_flag_id_o==1)?            32'h4:
                            (ALUSrc==1)? rd_data2_id_i : imme;
// unconditional jump , so jump .
assign jump_flag_id_o = (instr_id_i[6:0]   ==  `INST_TYPE_JALR)|(instr_id_i[6:0]     ==  `INST_TYPE_JAL);
assign jump_addr_id_o = (instr_id_i[6:0]   ==  `INST_TYPE_JALR)? op1_id_o + imme:
                        (instr_id_i[6:0]     ==  `INST_TYPE_JAL)? pc_id_i + imme :32'h0;
assign branch_addr_id_o = (instr_id_i[6:0]     ==  `INST_TYPE_SB)? pc_id_i + imme :32'h0;

//ALUCtrl
always@(*) begin
    if(!rst_n)begin
        ALUCtrl_id_o = 4'b0000;
    end
    else begin
    case(ALUOp)
        2'b00:ALUCtrl_id_o = 4'b1000; // load or store , so use add 
        2'b01:begin    //condition branch 
            case(fun3_id_o)
                3'b000:ALUCtrl_id_o = 4'b0001;//beq,  branch if equal
                3'b001:ALUCtrl_id_o = 4'b0010;//bne,  branch if not equal
                3'b100:ALUCtrl_id_o = 4'b0011;//blt,  branch if less than
                3'b101:ALUCtrl_id_o= 4'b0100;//bge, branch if greater than
                3'b110:ALUCtrl_id_o= 4'b0101;//bltu
                3'b111:ALUCtrl_id_o= 4'b0110;//bgeu
                default : ALUCtrl_id_o= 4'b0000; // do nothing
            endcase
        end
        2'b10:begin//R_type  or II_type
            case(fun3_id_o)
                3'b000:begin
                    if(instr_id_i[6:0] == `INST_TYPE_R)begin
                    ALUCtrl_id_o = (fun7[5] == 0)? 4'b1000:4'b1001;   //fun7==0 so add ,fun7=1 so sub 
                    end
                    else begin// II_TYPE
                    ALUCtrl_id_o= 4'b1000;  //addi
                    end
                end
                3'b001:ALUCtrl_id_o= 4'b1010;     // sll  slli
                3'b100:ALUCtrl_id_o= 4'b1011;   //   xor  xori
                3'b101:begin
                    if(fun7[5])
                    ALUCtrl_id_o = 4'b1100;  //  sra  srai
                    else
                    ALUCtrl_id_o = 4'b1101;   // srl  srli
                end
                3'b110:ALUCtrl_id_o = 4'b1110;  // or   ori
                3'b111:ALUCtrl_id_o = 4'b1111;  //and  andi
                default : ALUCtrl_id_o = 4'b0000; // do nothing
            endcase
        end
        default : ALUCtrl_id_o = 4'b0000; // do nothing
    endcase
    end
end
endmodule 
 
 

这是寄存器堆代码(包含写回)


module regsfile(
    input           clk,
    input           rst_n,
//from  id
    input   [4:0]   reg_addr1,  //rs1
    input   [4:0]   reg_addr2,  //rs2
    input   RegWrite,
    input   [4:0]   rd,
    input   [31:0]  writeregdata,


    output  [31:0]  rd_data1,
    output  [31:0]  rd_data2
//from mem
);
//to id
reg [31:0] regs[31:0];
//write back
always@(posedge clk or rst_n) begin
    if(~rst_n) begin
        regs[0]  <= 32'b0;
        regs[1]  <= 32'b0;
        regs[2]  <= 32'b0;
    end
    else if(RegWrite) begin
        regs[rd] <= writeregdata;
    end
    else begin
        regs[rd] <= regs[rd];
    end
end
//reg read
assign rd_data1 = (reg_addr1==5'd0)?  0:regs[reg_addr1];
assign rd_data2 = (reg_addr2==5'd0)?  0:regs[reg_addr2];

endmodule

③ :EX阶段

EX阶段主要进行ALU的运算,根据不同的ALUCtrl,让ALU进行不同类型的计算


module EX(
    input   [31:0]  op1_ex_i,
    input   [31:0]  op2_ex_i,
    input   [2:0]   fun3_ex_i,
    input   [3:0]   ALUCtrl_ex_i,
    input   [31:0]  rd_data2_ex_i,
    input   MemWrite_ex_i,

    output   reg    Branch,// conditional branch predict: 1 meanns successed, 0 means failed
    output   reg    [31:0]  writememdata_ex_o,
    output   reg    [31:0]  ALUresult_ex_o  // as address  if load or store

);
wire [31:0] ALUA;
wire [31:0] ALUB;
wire [31:0] storedata;
assign ALUA = op1_ex_i;
assign ALUB = op2_ex_i;
assign storedata =rd_data2_ex_i;

always @(*) begin
    if(MemWrite_ex_i)begin
    case(fun3_ex_i)
    3'b000:writememdata_ex_o = {24'b0,storedata[7:0]};//sb
    3'b001:writememdata_ex_o = {16'b0,storedata[15:0]};//sh
    3'b010:writememdata_ex_o =  storedata ;        //sw
    default:writememdata_ex_o= writememdata_ex_o;
    endcase
    end
    else begin
    writememdata_ex_o =32'h0;
    end
end

always @ (*) begin
        ALUresult_ex_o = 32'h0;
        Branch = 1'b0;
        case(ALUCtrl_ex_i)
        4'b0001:begin    //beq
            ALUresult_ex_o = 32'h0;
            Branch=(ALUA == ALUB);
            end
        4'b0010:begin     //bnq
            ALUresult_ex_o = 32'h0;
            Branch=(ALUA != ALUB);
        end
        4'b0011:begin      //blt
            ALUresult_ex_o = 32'h0;
            Branch=(ALUA < ALUB);
        end
        4'b0100:begin      //bge
            ALUresult_ex_o = 32'h0;
            Branch=(ALUA > ALUB);
        end        
        4'b0000:ALUresult_ex_o = ALUA + ALUB;  // load  store jalr  utype
        4'b1000:ALUresult_ex_o = ALUA + ALUB;      //add   addi
        4'b1001:ALUresult_ex_o = ALUA - ALUB;       //sub
        4'b1010:ALUresult_ex_o = ALUA << ALUB;     //left shift
        4'b1011:ALUresult_ex_o = ALUA ^ ALUB;     //xor xori
        //
        4'b1100:ALUresult_ex_o = ALUA >> ALUB;   //sra srai

        4'b1101:ALUresult_ex_o = ALUA >> ALUB;  //srl  srli 
        4'b1110:ALUresult_ex_o = ALUA | ALUB;   //or  ori 
        4'b1111:ALUresult_ex_o = ALUA & ALUB;   //and  andi
        default : begin
            ALUresult_ex_o = 32'h0;
            Branch= 1'b0;
        end
        endcase
    end
endmodule

可以看到我在这里对要存入内存的数据进行了处理(比如说SW,SH,SB,都是对一个数进行不同的处理后在存入内存,其实这个也可以写在译码阶段,也可以写在MEM阶段,但是这里我写在了EX阶段。
其实一开始我是写在MEM阶段,因为我觉得既然是存数据,就在MEM阶段处理就好了,这在单周期中没有影响,在流水线阶段由于要使用forwarding结束,有可能你要存的数据产生了数据冒险,也就是还没有写入,在设计中我尽量都让forwarding信号来到EX阶段进行处理,所以也就把这个放到了EX阶段。

④:MEM阶段

MEM阶段将从ID传来的读写内存控制信号传给data_ram,并将读到的数据根据指令lb lw lh 的不同来进行处理。

module MEM(
   // from id
    input   MemWrite_mem_i,
    input   MemRead_mem_i,
    input   [31:0]  ALUresult_mem_i,          //as address , if load or store
    input   [31:0]  writememdata_mem_i,
    input   [2:0]   fun3_mem_i,
    // from data_memory
    input   [31:0]  readdata32_mem_i,


    // to data_memory
    output   MemWrite_mem_o,
    output   MemRead_mem_o,
    output   [31:0]  ALUresult_mem_o,          //as address , if load or store
    output   [31:0]  writememdata_mem_o,

    //to wb
    output  reg     [31:0] readdata

);
//readmemdata after process
always@(*)begin 
    if(MemRead_mem_i) begin
        case(fun3_mem_i)
        3'b000:readdata = {{24{readdata32_mem_i[7]}},readdata32_mem_i[7:0]};//lb
        3'b001:readdata = {{16{readdata32_mem_i[15]}},readdata32_mem_i[15:0]};//lh
        3'b010:readdata = readdata32_mem_i;//lw
        3'b100:readdata = {24'b0,readdata32_mem_i[7:0]};//lbu
        3'b101:readdata = {16'b0,readdata32_mem_i[15:0]};//lhu
        endcase
    end
end
assign MemWrite_mem_o = MemWrite_mem_i;
assign MemRead_mem_o  = MemRead_mem_i;
assign ALUresult_mem_o= ALUresult_mem_i;
assign writememdata_mem_o=writememdata_mem_i;

endmodule

⑤:WB阶段

首先要有一个选择器,如果指令需要写回寄存器的话,来确定是从内存读出的值写回寄存器还是通过ALU运算的结果写回寄存器

module muxwb(
    input MemtoReg,

    input [31:0] readdata,
    input [31:0] ALUresult,
    
    output [31:0] writeregdata
);

assign writeregdata = MemtoReg ? readdata : ALUresult;

endmodule

在regsfile文件中写入寄存器,这个模块同样被用在ID阶段,但那时是用来读寄存器

module regsfile(
    input           clk,
    input           rst_n,
//from  id
    input   [4:0]   reg_addr1,  //rs1
    input   [4:0]   reg_addr2,  //rs2
    input   RegWrite,
    input   [4:0]   rd,
    input   [31:0]  writeregdata,


    output  [31:0]  rd_data1,
    output  [31:0]  rd_data2
//from mem
);
//to id
reg [31:0] regs[31:0];
//write back
always@(posedge clk or rst_n) begin
    if(~rst_n) begin
        regs[0]  <= 32'b0;
        regs[1]  <= 32'b0;
        regs[2]  <= 32'b0;
    end
    else if(RegWrite) begin
        regs[rd] <= writeregdata;
    end
    else begin
        regs[rd] <= regs[rd];
    end
end
//reg read
assign rd_data1 = (reg_addr1==5'd0)?  0:regs[reg_addr1];
assign rd_data2 = (reg_addr2==5'd0)?  0:regs[reg_addr2];

endmodule

结语

本次单周期的设计基本结束,我把从pc_reg,if,rom,id,regsfile,ex,mem,ram
,muxwb,wb的都展示在上面了,再次声明,仅供个人学习记录,我目前还是个新手,代码质量可能一般,请见谅。
后续我会更新testbench,以及五级流水线的设计及验证。


版权声明:本文为qq_48528105原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>