将陆续上传本人写的新书《自己动手写CPU》,今天是第33篇,我尽量每周四篇
亚马逊的销售地址如下,欢迎大家围观呵!
http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4
在当当、京东、互动、北发等网上书店均有!
除法指令的实现过程有点长,分两篇博文介绍,今天是第二篇。
7.12.2 修改译码阶段的ID模块
译码阶段的ID模块要增加对除法指令的分析,根据图7-15给出的指令格式可知,除法指令都是SPECIAL类指令,可以依据功能码确定是哪一种指令,确定指令的过程如图7-19所示。
其中涉及的宏定义如下,正是图7-15中各个指令的功能码。在本书附带光盘Code\Chapter7_3目录下的defines.v文件中可以找到这些定义。
`define EXE_DIV 6'b011010 `define EXE_DIVU 6'b011011
修改译码阶段的ID模块如下。完整代码位于本书附带光盘Code\Chapter7_3目录下的id.v文件。
module id( ...... ); ...... assign stallreq = `NoStop; always @ (*) begin if (rst == `RstEnable) begin ...... end else 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 case (op2) 5'b00000: begin case (op3) ...... `EXE_DIV: begin //div指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_DIV_OP; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_DIVU: begin //divu指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_DIVU_OP; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end ......
这2条除法指令的译码过程都是相似的,简要说明如下。
(1)因为最终结果是写入HI、LO寄存器,不需要写通用寄存器,所以赋值wreg_o为WriteDisable。
(2)因为要读取两个通用寄存器的值,所以设置reg1_read_o、reg2_read_o为1'b1,读取的是图7-15中地址为rs、rt的寄存器的值。
(3)alusel_o的值保持为默认值EXE_RES_NOP。
(4)设置aluop_o的值与具体指令对应。
7.12.3 修改执行阶段的EX模块
参考图7-17可知, EX模块需要增加部分接口,增加的接口如表7-6所示。 EX模块的代码主要修改如下。完整代码位于本书附带光盘Code\Chapter7_3目录下的ex.v文件。
module ex( ...... // 新增来自除法模块的输入 input wire[`DoubleRegBus] div_result_i, input wire div_ready_i, ...... // 新增到除法模块的输出 output reg[`RegBus] div_opdata1_o, output reg[`RegBus] div_opdata2_o, output reg div_start_o, output reg signed_div_o, output reg stallreq ); ...... reg stallreq_for_div; // 是否由于除法运算导致流水线暂停 ...... /**************************************************************** ******* 第一段:输出DIV模块控制信息,获取DIV模块给出的结果 ****** *****************************************************************/ always @ (*) begin if(rst == `RstEnable) begin stallreq_for_div <= `NoStop; div_opdata1_o <= `ZeroWord; div_opdata2_o <= `ZeroWord; div_start_o <= `DivStop; signed_div_o <= 1'b0; end else begin stallreq_for_div <= `NoStop; div_opdata1_o <= `ZeroWord; div_opdata2_o <= `ZeroWord; div_start_o <= `DivStop; signed_div_o <= 1'b0; case (aluop_i) `EXE_DIV_OP: begin //是div指令 if(div_ready_i == `DivResultNotReady) begin div_opdata1_o <= reg1_i; //被除数 div_opdata2_o <= reg2_i; //除数 div_start_o <= `DivStart; //开始除法运算 signed_div_o <= 1'b1; //有符号除法 stallreq_for_div <= `Stop; //请求流水线暂停 end else if(div_ready_i == `DivResultReady) begin div_opdata1_o <= reg1_i; div_opdata2_o <= reg2_i; div_start_o <= `DivStop; //结束除法运算 signed_div_o <= 1'b1; stallreq_for_div <= `NoStop; //不再请求流水线暂停 end else begin div_opdata1_o <= `ZeroWord; div_opdata2_o <= `ZeroWord; div_start_o <= `DivStop; signed_div_o <= 1'b0; stallreq_for_div <= `NoStop; end end `EXE_DIVU_OP: begin //是divu指令 if(div_ready_i == `DivResultNotReady) begin div_opdata1_o <= reg1_i; div_opdata2_o <= reg2_i; div_start_o <= `DivStart; signed_div_o <= 1'b0; //无符号除法 stallreq_for_div <= `Stop; end else if(div_ready_i == `DivResultReady) begin div_opdata1_o <= reg1_i; div_opdata2_o <= reg2_i; div_start_o <= `DivStop; signed_div_o <= 1'b0; stallreq_for_div <= `NoStop; end else begin div_opdata1_o <= `ZeroWord; div_opdata2_o <= `ZeroWord; div_start_o <= `DivStop; signed_div_o <= 1'b0; stallreq_for_div <= `NoStop; end end default: begin end endcase end end /**************************************************************** ********** 第二段:暂停流水线 ******* *****************************************************************/ always @ (*) begin stallreq = stallreq_for_madd_msub || stallreq_for_div; end ...... /**************************************************************** ********** 第三段:修改HI、LO寄存器写信息 ******* *****************************************************************/ always @ (*) begin if(rst == `RstEnable) begin whilo_o <= `WriteDisable; hi_o <= `ZeroWord; lo_o <= `ZeroWord; ...... end else if((aluop_i == `EXE_DIV_OP) || (aluop_i == `EXE_DIVU_OP)) begin whilo_o <= `WriteEnable; hi_o <= div_result_i[63:32]; lo_o <= div_result_i[31:0]; ......
上面的代码可以分为三段理解。
(1)第一段:如果是div指令,并且DIV模块没有声明除法结束(即div_ready_i等于DivResultNotReady),那么输出被除数、除数、除法开始信号、有符号除法等信息到DIV模块,设置div_start_o为DivStart,以指示DIV模块开始除法运算,同时,设置stallreq_for_div为Stop,表示由于除法运算请求流水线暂停。反之,如果DIV模块声明除法结束(即div_ready_i等于DivResultReady),那么设置div_start_o为DivStop,以指示DIV模块停止除法运算,同时,设置stallreq_for_div为NoStop,表示不是由于除法运算请求流水线暂停。
divu指令的执行过程与div指令类似。
(2)第二段:给出暂停流水线请求信号stallreq的值,目前已实现的乘累加、乘累减、除法指令都会请求流水线暂停,所以stallreq等于stallreq_for_madd_msub与stallreq_for_div进行逻辑“或”运算的结果。
(3)第三段:由于除法指令要将最终结果写入HI、LO寄存器,所以在第三段给出了对HI、LO寄存器的写信息。其中div_result_i就是DIV模块计算出来的除法结果,高32位存储的是余数,低32位存储的是商。
7.12.4 修改OpenMIPS模块
因为添加了DIV模块,并且修改了EX模块的接口,所以要修改OpenMIPS顶层模块,以将这些新增模块、接口连接起来,连接关系如图7-17所示。完整代码可以参考本书附带光盘Code\Chapter7_3目录下的openmips.v文件,书中只给出DIV模块的例化语句,如下。div div0( .clk(clk), .rst(rst), .signed_div_i(signed_div), .opdata1_i(div_opdata1), .opdata2_i(div_opdata2), .start_i(div_start), .annul_i(1'b0), .result_o(div_result), .ready_o(div_ready) );
这里需要说明一点,DIV模块的输入接口annul_i在目前固定为0,表示不会有取消除法指令的情况发生,但是在后续章节,当我们实现异常处理的时候,会重新确定DIV模块的输入接口annul_i的值。