将陆续上传本人写的新书《自己动手写CPU》(尚未出版),今天是第22篇,我尽量每周四篇
6.3 修改OpenMIPS以实现移动操作指令
6.3.1 HI、LO寄存器的实现
在HILO模块中实现HI、LO寄存器,HILO模块的接口描述如表6-1所示。
HILO模块的代码如下,源文件是本书附带光盘Code\Chapter6目录下的hilo_reg.v。整个代码很简单:在时钟上升沿,如果复位信号无效,那么就判断输入的写使能信号we是否为WriteEnable,如果是WriteEnable,那么就将输入的hi_i、lo_i的值,作为HI、LO寄存器的新值,并通过hi_o、lo_o接口输出。
module hilo_reg( input wire clk, input wire rst, // 写端口 input wire we, input wire[`RegBus] hi_i, input wire[`RegBus] lo_i, // 读端口 output reg[`RegBus] hi_o, output reg[`RegBus] lo_o ); always @ (posedge clk) begin if (rst == `RstEnable) begin hi_o <= `ZeroWord; lo_o <= `ZeroWord; end else if((we == `WriteEnable)) begin hi_o <= hi_i; lo_o <= lo_i; end end endmodule
6.3.2 修改译码阶段的ID模块
在译码阶段要增加对移动操作指令的分析,根据图6-1给出的移动操作指令格式可知,这6条指令都是SPECIAL类指令,且6-10bit均为0,需要依据0-5bit的功能码确定指令,确定指令的过程如图6-6所示。
其中涉及的宏定义如下,正是图6-1中各个指令的功能码。在本书附带光盘Code\Chapter6目录下的defines.v文件可以找到这些宏定义。
`define EXE_MOVZ 6'b001010 `define EXE_MOVN 6'b001011 `define EXE_MFHI 6'b010000 `define EXE_MTHI 6'b010001 `define EXE_MFLO 6'b010010 `define EXE_MTLO 6'b010011
译码阶段的ID模块主要修改如下。完整代码位于本书附带光盘Code\Chapter6目录下的id.v文件。
module id( ...... ); 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]; ...... always @ (*) begin ...... aluop_o <= `EXE_NOP_OP; alusel_o <= `EXE_RES_NOP; wd_o <= inst_i[15:11]; // 默认目的寄存器地址wd_o wreg_o <= `WriteDisable; instvalid <= `InstInvalid; reg1_read_o <= 1'b0; reg2_read_o <= 1'b0; reg1_addr_o <= inst_i[25:21]; // 默认的reg1_addr_o reg2_addr_o <= inst_i[20:16]; // 默认的reg2_addr_o imm <= `ZeroWord; case (op) `EXE_SPECIAL_INST: begin // 是SPECIAL类指令 case (op2) 5'b00000: begin // op2为5'b00000 case (op3) ...... `EXE_MFHI: begin // mfhi指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_MFHI_OP; alusel_o <= `EXE_RES_MOVE; reg1_read_o <= 1'b0; reg2_read_o <= 1'b0; instvalid <= `InstValid; end `EXE_MFLO: begin // mflo指令 wreg_o <= `WriteEnable; aluop_o <= `EX E_MFLO_OP; alusel_o <= `EXE_RES_MOVE; reg1_read_o <= 1'b0; reg2_read_o <= 1'b0; instvalid <= `InstValid; end `EXE_MTHI: begin // mthi指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MTHI_OP; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; instvalid <= `InstValid; end `EXE_MTLO: begin // mtlo指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MTLO_OP; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; instvalid <= `InstValid; end `EXE_MOVN: begin // movn指令 aluop_o <= `EXE_MOVN_OP; alusel_o <= `EXE_RES_MOVE; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; //reg2_o的值就是地址为rt的通用寄存器的值 if(reg2_o != `ZeroWord) begin wreg_o <= `WriteEnable; end else begin wreg_o <= `WriteDisable; end end `EXE_MOVZ: begin // movz指令 aluop_o <= `EXE_MOVZ_OP; alusel_o <= `EXE_RES_MOVE; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; //reg2_o的值就是地址为rt的通用寄存器的值 if(reg2_o == `ZeroWord) begin wreg_o <= `WriteEnable; end else begin wreg_o <= `WriteDisable; end end ...... endmodule
有如下几点说明。
(1)除mthi、mtlo外的其余4条移动操作指令的运算类型alusel_o均为EXE_RES_MOVE。
(2)指令mthi、mtlo需要修改HI、LO寄存器,但是不需要修改通用寄存器,所以在其译码结果中,wreg_o为WriteDisable。另外,设置reg1_read_o为1,表示需要通过Regfile模块读端口1读取通用寄存器的值,默认读取地址就是指令21-25bit的值,正是mthi、mtlo指令中的rs。读出的值作为要写入HI或LO寄存器的数据。
(3)movz指令的译码过程需要读取rs、rt寄存器的值,所以设置reg1_read_o、reg2_read_o均为1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是movz指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是movz指令中的rt。所以,reg2_o的值就是读取到的地址为rt的寄存器的值,如果该值为0,那么设置wreg_o为WriteEnable,表示要将地址为rs的寄存器的值赋给地址为rd的寄存器,反之,wreg_o为WriteDisable,表示不赋值。
(4)movn指令的译码过程与movz指令类似,只是wreg_o为WriteEnable的条件与movz正好相反。
6.3.3 修改执行阶段
1、修改EX模块
译码阶段的结果会传递到执行阶段,执行阶段据此进行计算。考虑到执行阶段需要读写HI、LO寄存器,另外还要解决HI、LO寄存器带来的数据相关问题,所以需要给EX模块增加如表6-2所示的接口。各接口对外连接关系可以参考图6-5。
EX模块的代码修改如下。完整代码位于在本书附带光盘Code\Chapter6目录下的ex.v文件中。
module ex( ...... // HILO模块给出的HI、LO寄存器的值 input wire[`RegBus] hi_i, input wire[`RegBus] lo_i, // 回写阶段的指令是否要写HI、LO,用于检测HI、LO寄存器带来的数据相关问题 input wire[`RegBus] wb_hi_i, input wire[`RegBus] wb_lo_i, input wire wb_whilo_i, // 访存阶段的指令是否要写HI、LO,用于检测HI、LO寄存器带来的数据相关问题 input wire[`RegBus] mem_hi_i, input wire[`RegBus] mem_lo_i, input wire mem_whilo_i, // 处于执行阶段的指令对HI、LO寄存器的写操作请求 output reg[`RegBus] hi_o, output reg[`RegBus] lo_o, output reg whilo_o, ...... ); reg[`RegBus] logicout; // 逻辑操作的结果 reg[`RegBus] shiftres; // 移位操作的结果 reg[`RegBus] moveres; // 移动操作的结果 reg[`RegBus] HI; // 保存HI寄存器的最新值 reg[`RegBus] LO; // 保存LO寄存器的最新值 ...... /**************************************************************** ** 第一段:得到最新的HI、LO寄存器的值,此处要解决数据相关问题 ** *****************************************************************/ always @ (*) begin if(rst == `RstEnable) begin {HI,LO} <= {`ZeroWord,`ZeroWord}; end else if(mem_whilo_i == `WriteEnable) begin {HI,LO} <= {mem_hi_i,mem_lo_i}; // 访存阶段的指令要写HI、LO寄存器 end else if(wb_whilo_i == `WriteEnable) begin {HI,LO} <= {wb_hi_i,wb_lo_i}; // 回写阶段的指令要写HI、LO寄存器 end else begin {HI,LO} <= {hi_i,lo_i}; end end /**************************************************************** ************ 第二段:MFHI、MFLO、MOVN、MOVZ指令 ************* *****************************************************************/ always @ (*) begin if(rst == `RstEnable) begin moveres <= `ZeroWord; end else begin moveres <= `ZeroWord; case (aluop_i) `EXE_MFHI_OP: begin // 如果是mfhi指令,那么将HI的值作为移动操作的结果 moveres <= HI; end `EXE_MFLO_OP: begin // 如果是mflo指令,那么将LO的值作为移动操作的结果 moveres <= LO; end `EXE_MOVZ_OP: begin // 如果是movz指令,那么将reg1_i的值作为移动操作的结果 moveres <= reg1_i; end `EXE_MOVN_OP: begin // 如果是movn指令,那么将reg1_i的值作为移动操作的结果 moveres <= reg1_i; end default : begin end endcase end end /**************************************************************** *** 第三段:依据运算类型alusel_i的值,确定wdata_o的值 *** *****************************************************************/ always @ (*) begin wd_o <= wd_i; wreg_o <= wreg_i; case ( alusel_i ) `EXE_RES_LOGIC: begin wdata_o <= logicout; end `EXE_RES_SHIFT: begin wdata_o <= shiftres; end `EXE_RES_MOVE: begin // 移动操作指令的alusel_i为EXE_RES_MOVE wdata_o <= moveres; end default: begin wdata_o <= `ZeroWord; end endcase end /**************************************************************** * 第四段,如果是MTHI、MTLO指令,那么需要给出whilo_o、hi_o、lo_i的值 * *****************************************************************/ always @ (*) begin if(rst == `RstEnable) begin whilo_o <= `WriteDisable; hi_o <= `ZeroWord; lo_o <= `ZeroWord; end else if(aluop_i == `EXE_MTHI_OP) begin whilo_o <= `WriteEnable; hi_o <= reg1_i; lo_o <= LO; // 写HI寄存器,所以LO保持不变 end else if(aluop_i == `EXE_MTLO_OP) begin whilo_o <= `WriteEnable; hi_o <= HI; // 写LO寄存器,所以HI保持不变 lo_o <= reg1_i; end else begin whilo_o <= `WriteDisable; hi_o <= `ZeroWord; lo_o <= `ZeroWord; end end endmodule
上面修改的代码可以分为四段理解。
(1)第一段代码的作用是得到最新的HI、LO寄存器的值,首先判断当前处于访存阶段的指令是否要写HI、LO寄存器,即mem_whilo_o是否为WriteEnable,如果是,那么访存阶段的指令要写入的值就是HI、LO寄存器的最新值,如果不是,那么再判断当前处于回写阶段的指令是否要写HI、LO寄存器,如果是,那么回写阶段的指令要写入的值就是HI、LO寄存器的最新值,如果不是,那么从HILO模块输入的值hi_i、lo_i就是HI、LO寄存器的最新值。
(2)第二段代码的作用是针对不同的移动操作指令,确定moveres的值,变量moveres存储的是移动操作指令的结果。
(3)第三段代码的作用是依据运算类型alusel_i的值,将不同的运算结果赋给wdata_o,如果是移动操作指令,那么alusel_i为EXE_RES_MOVE,此时将moveres的值赋给wdata_o。
(4)第四段代码的作用是确定是否要写HI、LO寄存器,如果是mthi、mtlo寄存器,那么要写HI、LO寄存器,所以设置输出信号whilo_o为WriteEnable。具体地说,有如下两种情况。
- 如果是mthi指令,那么表示要写HI寄存器,所以hi_o等于reg1_i的值,参考译码阶段的ID模块可知reg1_i的值就是在译码阶段读出的地址为rs的寄存器的值,另外,LO的值保持不变,所以lo_o等于LO。
- 如果是mtlo指令,那么表示要写LO寄存器,所以lo_o等于reg1_i的值,参考译码阶段的ID模块可知reg1_i的值就是在译码阶段读出的地址为rs的寄存器的值,另外,HI的值保持不变,所以hi_o等于HI。
2、修改EX/MEM模块
参考图6-5,EX模块新增加的输出接口whilo_o、hi_o、lo_o连接到EX/MEM模块,需要给EX/MEM模块添加如表6-3所示接口。
EX/MEM模块的代码修改如下。完整代码位于本书附带光盘Code\Chapter6目录下的ex_mem.v文件。主要修改的部分使用加粗、斜体表示,作用是将执行阶段得到的对HI、LO寄存器的写信息传递到访存阶段。
module ex_mem( input wire clk, input wire rst, // 来自执行阶段的信息 input wire[`RegAddrBus] ex_wd, input wire ex_wreg, input wire[`RegBus] ex_wdata, input wire[`RegBus] ex_hi, input wire[`RegBus] ex_lo, input wire ex_whilo, // 送到访存阶段的信息 output reg[`RegAddrBus] mem_wd, output reg mem_wreg, output reg[`RegBus] mem_wdata, output reg[`RegBus] mem_hi, output reg[`RegBus] mem_lo, output reg mem_whilo ); always @ (posedge clk) begin if(rst == `RstEnable) begin mem_wd <= `NOPRegAddr; mem_wreg <= `WriteDisable; mem_wdata <= `ZeroWord; mem_hi <= `ZeroWord; mem_lo <= `ZeroWord; mem_whilo <= `WriteDisable; end else begin mem_wd <= ex_wd; mem_wreg <= ex_wreg; mem_wdata <= ex_wdata; mem_hi <= ex_hi; mem_lo <= ex_lo; mem_whilo <= ex_whilo; end end endmodule
6.3.4 修改访存阶段
1、修改MEM模块
参考图6-5,EX/MEM模块新增加的输出接口mem_whilo、mem_hi、mem_lo连接到访存阶段的MEM模块,需要给MEM模块添加如表6-4所示接口。
MEM模块的代码修改如下。对应本书附带光盘Code\Chapter6目录下的mem.v文件。主要修改的部分使用加粗、斜体表示,作用是将对HI、LO寄存器的写信息传递到MEM/WB模块,后者会将这些信息传递到回写阶段。
module mem( input wire rst, // 来自执行阶段的信息 input wire[`RegAddrBus] wd_i, input wire wreg_i, input wire[`RegBus] wdata_i, input wire[`RegBus] hi_i, input wire[`RegBus] lo_i, input wire whilo_i, // 访存阶段的结果 output reg[`RegAddrBus] wd_o, output reg wreg_o, output reg[`RegBus] wdata_o, output reg[`RegBus] hi_o, output reg[`RegBus] lo_o, output reg whilo_o ); always @ (*) begin if(rst == `RstEnable) begin wd_o <= `NOPRegAddr; wreg_o <= `WriteDisable; wdata_o <= `ZeroWord; hi_o <= `ZeroWord; lo_o <= `ZeroWord; whilo_o <= `WriteDisable; end else begin wd_o <= wd_i; wreg_o <= wreg_i; wdata_o <= wdata_i; hi_o <= hi_i; lo_o <= lo_i; whilo_o <= whilo_i; end end endmodule
2、修改MEM/WB模块
参考图6-5,MEM模块新增的输出接口whilo_o、hi_o、lo_o连接到MEM/WB模块,需要给MEM/WB模块添加如表6-5所示接口。
MEM/WB模块的代码修改如下。对应本书附带光盘的Code\Chapter6目录下的mem_wb.v文件。主要修改的部分使用加粗、斜体表示,作用是将对HI、LO寄存器的写信息传递到回写阶段。
module mem_wb( input wire clk, input wire rst, // 访存阶段的结果 input wire[`RegAddrBus] mem_wd, input wire mem_wreg, input wire[`RegBus] mem_wdata, input wire[`RegBus] mem_hi, input wire[`RegBus] mem_lo, input wire mem_whilo, // 送到回写阶段的信息 output reg[`RegAddrBus] wb_wd, output reg wb_wreg, output reg[`RegBus] wb_wdata, output reg[`RegBus] wb_hi, output reg[`RegBus] wb_lo, output reg wb_whilo ); always @ (posedge clk) begin if(rst == `RstEnable) begin wb_wd <= `NOPRegAddr; wb_wreg <= `WriteDisable; wb_wdata <= `ZeroWord; wb_hi <= `ZeroWord; wb_lo <= `ZeroWord; wb_whilo <= `WriteDisable; end else begin wb_wd <= mem_wd; wb_wreg <= mem_wreg; wb_wdata <= mem_wdata; wb_hi <= mem_hi; wb_lo <= mem_lo; wb_whilo <= mem_whilo; end end endmodule
6.3.5 修改回写阶段
参考图6-5,MEM/WB模块输出的对HI、LO寄存器的写信息将直接送到HILO模块,包括:wb_whilo、wb_hi、wb_lo,后者据此修改HI、LO寄存器的值。
6.3.6 修改OpenMIPS顶层模块
由于本章增添了HILO模块,而且对流水线中的多个模块都增加了接口,所以需要修改OpenMIPS顶层模块,在其中将各个模块新增加的接口按照图6-5所示的关系连接起来。因为很好理解,所以具体代码不在书中罗列,读者可以参考本书附带光盘Code\Chapter6目录下的openmips.v文件。
自己动手写CPU之第六阶段(3)——移动操作指令的实现