Bonky Zhu
If someone is able to show me that what I think or do is not right, I will happily change, for I seek the truth, by which no one was ever truly harmed. It is the person who continues in his self-deception and ignorance who is harmed.

计算机组成课设 – 实现一个五级流水的CPU

这里我们实现的是五级流水的 CPU 的一条指令,ori指令

ori 指令

ori 指令格式如下:

image-20191223003814409

作用是将指令中的 16 位立即数 immediate 进行无符号扩展至 32 位,然后与索引为 rs 的通用寄存器的值进行逻辑或运算,运算结果保存到索引为 rt 的通用寄存器中。

无符号扩展

image-20191223004015681

通用寄存器

ori 指令中的 rs 和 rt 就是通用寄存器的索引,例如:当 rs 为 5'b00011 时,就表示通用寄存器 $3。

建立五级流水线结构

流水线模型可以简单描述为以下:信号在寄存器之间传递,每传递到一级都会引起相应的组合逻辑电路变化,对这种模型进行抽象描述就是寄存器传输级(抽象程度,更底层还有门级)。一个简单的表示如下:

image-20191223005254717

ori 指令在流水线中的处理过程了可以表示如下:

image-20191223005109450

图中深色部分对应的是D触发器,深色部分之间的部分对应组合逻辑。各个阶段完成的主要工作如下。

  • 取指:取出指令存储器中的指令,PC 值递增,准备取下一条指令。
  • 译码:对指令进行译码,依据译码结果 ,从 32 个通用寄存器中取出源操作数,有的指令要求两个源操作数都是寄存器的值,比如 or 指令,有的指令要求其中一个源操作数是指令立即数的扩展,比如 ori 指令,所以这里有两个复用器,用于依据指要求,确定参与运算的操作数,最终确定的两个操作数会送到执行阶段。
  • 执行阶段:依据译码阶段送入的源操作数、操作码,进行运算,对于 ori 指令而言 ,就是进行逻辑”或“运算,运算结果传递到访存阶段。
  • 访存阶段:对于 ori 指令,在访存阶段没有任何操作,直接将运算结果向下传递到回写阶段。
  • 回写阶段:将运算结果保存到目的寄存器。

结构图如下:

image-20191223010258832

宏定义

//全局
`define RstEnable 1'b1  //复位有效
`define RstDisable 1'b0 //复位无效  
`define ZeroWord 32'h00000000   //32位的数值0
`define WriteEnable 1'b1    //使能写
`define WriteDisable 1'b0   //禁止写
`define ReadEnable 1'b1 //使能读
`define ReadDisable 1'b0    //禁止读
`define AluOpBus 7:0    //译码输出运算子类型的长度
`define AluSelBus 2:0   //译码输出运算类型的长度   
`define InstValid 1'b0  //指令有效
`define InstInvalid 1'b1    //指令无效
`define Stop 1'b1   
`define NoStop 1'b0
`define InDelaySlot 1'b1
`define NotInDelaySlot 1'b0
`define Branch 1'b1
`define NotBranch 1'b0
`define InterruptAssert 1'b1
`define InterruptNotAssert 1'b0
`define TrapAssert 1'b1
`define TrapNotAssert 1'b0
`define True_v 1'b1 //逻辑"真"
`define False_v 1'b0    //逻辑"假"
`define ChipEnable 1'b1 //芯片使能
`define ChipDisable 1'b0    //芯片禁止


//指令
`define EXE_ORI 6'b001101   //指令 ori 的指令码
`define EXE_NOP 6'b000000   


//AluOp
`define EXE_OR_OP   8'b00100101
`define EXE_ORI_OP  8'b01011010


`define EXE_NOP_OP    8'b00000000

//AluSel
`define EXE_RES_LOGIC 3'b001

`define EXE_RES_NOP 3'b000


//指令存储器inst_rom
`define InstAddrBus 31:0    //ROM的地址总线宽度
`define InstBus 31:0    //ROM的数据总线宽度
`define InstMemNum 131071   //ROM实际大小
`define InstMemNumLog2 17   //ROM实际使用的地址总线


//通用寄存器regfile
`define RegAddrBus 4:0  //寄存器地址线宽度
`define RegBus 31:0 //寄存器数据宽度
`define RegWidth 32 //寄存器的宽度
`define DoubleRegWidth 64   
`define DoubleRegBus 63:0
`define RegNum 32
`define RegNumLog2 5    //寻址寄存器位数
`define NOPRegAddr 5'b00000


实现

取指阶段

image-20191223110142622

PC 模块

接口描述如下

image-20191223011542985

核心代码如下:

always @ (posedge clk) begin
  if (ce == `ChipDisable) begin
    pc <= 32'h00000000;
  end else begin    //每次加4
    pc <= pc + 4'h4;
  end
end

always @ (posedge clk) begin
  if (rst == `RstEnable) begin
    ce <= `ChipDisable;
  end else begin
    ce <= `ChipEnable;
  end
end


IF/ID 模块

接口描述如下

image-20191223011613427

核心代码:

always @ (posedge clk) begin
  if (rst == `RstEnable) begin
    id_pc <= `ZeroWord; //复位的时候pc为0
    id_inst <= `ZeroWord;   //复位的时候指令也为0,实际就是空指令
  end else begin
    id_pc <= if_pc; //其余时刻向下传递取指阶段的值
    id_inst <= if_inst;
  end
end


译码阶段

Regfile 模块

image-20191223110101637

接口描述如下

image-20191223105745046

image-20191223105754039

核心代码

//定义寄存器
reg[`RegBus]  regs[0:`RegNum-1];

//写操作
always @ (posedge clk) begin
  if (rst == `RstDisable) begin
    if((we == `WriteEnable) && (waddr != `RegNumLog2'h0)) begin
      regs[waddr] <= wdata; 
    end
  end
end

//读操作,读端口1
always @ (*) begin
  if(rst == `RstEnable) begin
      rdata1 <= `ZeroWord;  //如果为置位,则为0
  end else if(raddr1 == `RegNumLog2'h0) begin
      rdata1 <= `ZeroWord;  //零号寄存器
  end else if((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin
      rdata1 <= wdata;
  end else if(re1 == `ReadEnable) begin
      rdata1 <= regs[raddr1];   //读取寄存器的值
  end else begin
      rdata1 <= `ZeroWord;
  end
end

//读操作,读端口2,同上
always @ (*) begin
  if(rst == `RstEnable) begin
      rdata2 <= `ZeroWord;
  end else if(raddr2 == `RegNumLog2'h0) begin
      rdata2 <= `ZeroWord;
  end else if((raddr2 == waddr) && (we == `WriteEnable) 
                && (re2 == `ReadEnable)) begin
      rdata2 <= wdata;
  end else if(re2 == `ReadEnable) begin
      rdata2 <= regs[raddr2];
  end else begin
      rdata2 <= `ZeroWord;
  end
end


其中读操作的理解如下:

  • 当复位信号有效时,第一个读寄存器端口的输出始终为 0。
  • 当复位信号无效时,如果读取的是 \$0, 那么直接给出 0(MIPS32架构规定$0 的值只能为 0,所以不要写入)
  • 如果第一个读寄存器端口要读取的目标寄存器与要写入的目的寄存器是同一个寄存器,那么直接将要写入的值作为第一个读寄存器端口的输出。
  • 如果上述情况都不满足,写入对应寄存器。
  • 第一个读寄存器端口不能使用时,直接输出 0。

ID 模块

ID模块的作用是对指令进行译码,得到最终运算的类型、子类型、源操作数1、源操作数2、要写入的目的寄存器地址等信息,其中运算类型指的是逻辑运算、移位运算、算术运算等,子类型指的是更加详细的运算类型,比如:当运算类型是逻辑运算时,运算子类型可以是逻辑或运算、逻辑与运算、逻辑异或运算等。

image-20191223111728424

接口描述如下

image-20191223111709677

image-20191223112254541

核心代码如下

//取得指令的指令码,功能码
//对千 ori 指令只需通过判断第 26-31bit 的值,即可判断是否是 ori 指令
wire[5:0] op = inst_i[31:26];
wire[4:0] op2 = inst_i[10:6];
wire[5:0] op3 = inst_i[5:0];
wire[4:0] op4 = inst_i[20:16];

//保存立即数
reg[`RegBus]    imm;

//指示指令是否有效 
reg instvalid;

//进行指令的译码
always @ (*) begin  
  if (rst == `RstEnable) begin
    //置位操作
    aluop_o <= `EXE_NOP_OP;
    alusel_o <= `EXE_RES_NOP;
    wd_o <= `NOPRegAddr;
    wreg_o <= `WriteDisable;
    instvalid <= `InstValid;
    reg1_read_o <= 1'b0;
    reg2_read_o <= 1'b0;
    reg1_addr_o <= `NOPRegAddr;
    reg2_addr_o <= `NOPRegAddr;
    imm <= 32'h0;           
  end else begin
    //相当于初始化
    aluop_o <= `EXE_NOP_OP;
    alusel_o <= `EXE_RES_NOP;
    wd_o <= inst_i[15:11];
    wreg_o <= `WriteDisable;
    instvalid <= `InstInvalid;     
    reg1_read_o <= 1'b0;
    reg2_read_o <= 1'b0;
    reg1_addr_o <= inst_i[25:21];
    reg2_addr_o <= inst_i[20:16];       
    imm <= `ZeroWord;           
    case (op)
      `EXE_ORI: begin   //根据 op 的值判斯是否是 ori 指令
        //需要写入目的寄存器,所以为WriteEnable
        wreg_o <= `WriteEnable;     
        //子类型为OR,运算类型为逻辑运算
        aluop_o <= `EXE_OR_OP;
        alusel_o <= `EXE_RES_LOGIC;
        //需要从端口1读,所以为1,不需要从端口2读所以为0
        reg1_read_o <= 1'b1;    
        reg2_read_o <= 1'b0;
        //将立即数扩展到32位
        imm <= {16'h0, inst_i[15:0]};
        //目的寄存器地址
        wd_o <= inst_i[20:16];
        instvalid <= `InstValid;    
      end                            
      default:  begin
      end
    endcase       //case op         
  end       //if
end         //always

//确定源操作数1,reg1_read_o 用于判断比特串是寄存器地址还是立即数
always @ (*) begin
  if(rst == `RstEnable) begin
    reg1_o <= `ZeroWord;
  end else if(reg1_read_o == 1'b1) begin
    reg1_o <= reg1_data_i;
  end else if(reg1_read_o == 1'b0) begin
    reg1_o <= imm;
  end else begin
    reg1_o <= `ZeroWord;
  end
end

//确定源操作数2
always @ (*) begin
  if(rst == `RstEnable) begin
    reg2_o <= `ZeroWord;
  end else if(reg2_read_o == 1'b1) begin
    reg2_o <= reg2_data_i;
  end else if(reg2_read_o == 1'b0) begin
    reg2_o <= imm;
  end else begin
    reg2_o <= `ZeroWord;
  end
end


ID/EX 模块

image-20191223130229133

接口描述:

image-20191223130256989

核心代码如下:

always @ (posedge clk) begin
  if (rst == `RstEnable) begin
    ex_aluop <= `EXE_NOP_OP;
    ex_alusel <= `EXE_RES_NOP;
    ex_reg1 <= `ZeroWord;
    ex_reg2 <= `ZeroWord;
    ex_wd <= `NOPRegAddr;
    ex_wreg <= `WriteDisable;
  end else begin        
    ex_aluop <= id_aluop;
    ex_alusel <= id_alusel;
    ex_reg1 <= id_reg1;
    ex_reg2 <= id_reg2;
    ex_wd <= id_wd;
    ex_wreg <= id_wreg;     
  end
end


执行阶段的实现

EX 模块

image-20191223130601740

接口描述:

image-20191223130646341

核心代码如下:

//保存逻辑运算的结果
reg[`RegBus] logicout;

//依据 aluop_i 指示的运算子类型进行运算(这里只实现了OR)
always @ (*) begin
  if(rst == `RstEnable) begin
    logicout <= `ZeroWord;
  end else begin
    case (aluop_i)
      `EXE_OR_OP:   begin
        //进行或运算
        logicout <= reg1_i | reg2_i;
      end
      default:  begin
        logicout <= `ZeroWord;
      end
    endcase
  end    //if
end      //always

//依据 alusel_i,选择一个运算结果作为最终结果(这里只有逻辑运算)
always @ (*) begin
  wd_o <= wd_i;         
  wreg_o <= wreg_i;
  case ( alusel_i ) 
    `EXE_RES_LOGIC: begin
      //把逻辑运算结果输出
      wdata_o <= logicout;
    end
    default:    begin
      wdata_o <= `ZeroWord;
    end
  endcase
end 


EX/MEM 模块

image-20191223133829208

接口描述:

image-20191223133925017

核心代码如下:

always @ (posedge clk) begin
  if(rst == `RstEnable) begin
    mem_wd <= `NOPRegAddr;
    mem_wreg <= `WriteDisable;
    mem_wdata <= `ZeroWord; 
  end else begin
    mem_wd <= ex_wd;
    mem_wreg <= ex_wreg;
    mem_wdata <= ex_wdata;          
  end    //if
end      //always


访存/回写阶段的实现

MEM 模块

image-20191223134410945

接口描述如下:

image-20191223134431808

核心代码如下:

//因为 OR 指令不需要访问存储器所以直接传递给下一个就 OK 了
always @ (*) begin
  if(rst == `RstEnable) begin
    wd_o <= `NOPRegAddr;
    wreg_o <= `WriteDisable;
    wdata_o <= `ZeroWord;
  end else begin
    wd_o <= wd_i;
    wreg_o <= wreg_i;
    wdata_o <= wdata_i;
  end    //if
end      //always


MEM/WB 模块

屏幕快照 2019-12-23 下午2.00.26

接口描述如下:

image-20191223140202156

image-20191223140228972

核心代码如下:

always @ (posedge clk) begin
  if(rst == `RstEnable) begin
    wb_wd <= `NOPRegAddr;
    wb_wreg <= `WriteDisable;
    wb_wdata <= `ZeroWord;  
  end else begin
    wb_wd <= mem_wd;
    wb_wreg <= mem_wreg;
    wb_wdata <= mem_wdata;
  end    //if
end      //always


顶层模块 OpenMIPS 的实现

顶层模块 OpenMIPS 主要内容就是对上面实现的流水线各个 阶段的模块进行实例化、连接,连接关系就如图所示。

2019-12-22-170259

当然,OpenMIPS 是一个大黑盒,具体我们可以这么表述:

image-20191223142313759

核心代码如下:

//定义黑盒里面的每根线
wire[`InstAddrBus] pc;
....

//实例化每个模块
pc_reg pc_reg0(
  .clk(clk),
  .rst(rst),
  .pc(pc),
  .ce(rom_ce_o)         
);
...


验证

指令存储器 ROM

image-20191223142842231

接口描述如下:

image-20191223142913722

核心代码如下:

//定义一个数组,大小是 InstMemNum,元素宽度是 InstBus
reg[`InstBus]  inst_mem[0:`InstMemNum-1];

//使用文件 inst_rom.data 初始化指令存储器
initial $readmemh ( "inst_rom.data", inst_mem );

always @ (*) begin
  if (ce == `ChipDisable) begin
    inst <= `ZeroWord;
  end else begin
    inst <= inst_mem[addr[`InstMemNumLog2+1:2]];//解释见下面
  end
end


上面的代码有个很费解的地方:addr[InstMemNumLog2+1:2] 这个地方。其中 lnstMemNun1Log2 是指令存储器的实际地址宽度。这么做的原因是因为每次给出指令地址是基于字节的,所以要右移两位使用。(就比如我要读0xC,对应的 inst_mem 是第三个元素,0xC 除以 4 等于 0x3)

image-20191223144943340

最小 SOPC 的实现

image-20191223145232089

具体实现就是定义线,然后把线连起来就可以了

//定义黑盒里面的每根线
wire[`InstAddrBus] inst_addr;
....

//实例化每个模块
openmips openmips0(
  .clk(clk),
  .rst(rst),
  .rom_addr_o(inst_addr),
  .rom_data_i(inst),
  .rom_ce_o(rom_ce)
);
...


建立 Test Bench 文件

核心代码如下:

`include "defines.v"
`timescale 1ns/1ps

module openmips_min_sopc_tb();
  reg     CLOCK_50;
  reg     rst;

  //每隔lOns, CLOCK_50信号翻转,所以一个周期是20ns,对应50MHz
  initial begin
    CLOCK_50 = 1'b0;
    forever #10 CLOCK_50 = ~CLOCK_50;
  end

  //最初时刻,复位信号有效,在第195ns,复位信号无效,最小SOPC开始运行,1000ns后停止
  initial begin
    rst = `RstEnable;
    #195 rst= `RstDisable;
    #1000 $stop;
  end

  //实例化
  openmips_min_sopc openmips_min_sopc0(
        .clk(CLOCK_50),
        .rst(rst)   
    );

endmodule

Share

You may also like...

发表评论