将陆续上传本人写的新书《自己动手写CPU》,今天是第30篇,我尽量每周四篇
亚马逊的销售地址如下,欢迎大家围观呵!
http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4
China-pub的销售地址如下:
http://product.china-pub.com/3804025
北发的销售地址如下:
http://book.beifabook.com/Product/BookDetail.aspx?Plucode=712123950&extra=0_s25960657
7.8 修改OpenMIPS以实现乘累加、乘累减指令
7.8.1 修改译码阶段的ID模块
译码阶段的ID模块要添加对乘累加、乘累减指令的分析,根据图7-11给出的指令格式可知,这4条指令都是SPECIAL2类指令,可以依据功能码确定是哪一种指令,确定指令的过程如图7-13所示。
其中涉及的宏定义如下,正是图7-13中各个指令的功能码。在本书附带光盘Code\Chapter7_2目录下的defines.v文件中可以找到这些定义。
`define EXE_MADD 6'b000000 `define EXE_MADDU 6'b000001 `define EXE_MSUB 6'b000100 `define EXE_MSUBU 6'b000101
译码阶段的ID模块主要修改内容如下。完整代码请参考本书光盘Code\Chapter7_2目录下的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_SPECIAL2_INST: begin // SPECIAL2类指令 case ( op3 ) ...... `EXE_MADD: begin // madd指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MADD_OP; alusel_o <= `EXE_RES_MUL; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_MADDU: begin // maddu指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MADDU_OP; alusel_o <= `EXE_RES_MUL; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_MSUB: begin // msub指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MSUB_OP; alusel_o <= `EXE_RES_MUL; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_MSUBU: begin // msubu指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MSUBU_OP; alusel_o <= `EXE_RES_MUL; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end default: begin end endcase //EXE_SPECIAL_INST2 case ...... endmodule
这4条指令的译码过程都是相似的。简单说明如下。
(1)因为最终结果都是写入HI、LO寄存器,而不是写入通用寄存器,所以设置wreg_o为WriteDisable。
(2)因为都要读取两个寄存器的值,所以设置reg1_read_o、reg2_read_o为1‘b1,默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。
(3)运算类型alusel_o的值都设置为EXE_RES_MUL,不过由于没有要写的通用寄存器,所以此处的alusel_o的值并没有作用,也可以设置为EXE_RES_NOP。
(4)运算子类型aluop_o的值设置为与具体指令对应。
7.8.2 修改执行阶段的EX模块
参考图7-12可知,EX模块要增加4个接口,含义如表7-2所示。
EX模块的代码主要修改如下。完整代码请参考本书附带光盘Code\Chapter7_2目录下的ex.v文件。
module ex( ...... // 增加的输入接口 input wire[`DoubleRegBus] hilo_temp_i, input wire[1:0] cnt_i, ...... // 增加的输出接口 output reg[`DoubleRegBus] hilo_temp_o, output reg[1:0] cnt_o, output reg stallreq ); ...... wire[`RegBus] opdata1_mult; wire[`RegBus] opdata2_mult; wire[`DoubleRegBus] hilo_temp; reg[`DoubleRegBus] hilo_temp1; reg stallreq_for_madd_msub; ...... /**************************************************************** *********** 第一段:计算乘法结果 ********* *****************************************************************/ //(1)取得乘法操作的被乘数,指令madd、msub都是有符号乘法,如果第一个 // 操作数reg1_i是负数,那么取reg1_i的补码作为被乘数,反之,直接 // 使用reg1_i作为被乘数 assign opdata1_mult = (((aluop_i == `EXE_MUL_OP) || (aluop_i == `EXE_MULT_OP) || (aluop_i == `EXE_MADD_OP) || (aluop_i == `EXE_MSUB_OP))&& (reg1_i[31] == 1'b1)) ? (~reg1_i + 1) : reg1_i; //(2)取得乘法操作的乘数,指令madd、msub是有符号乘法,如果第二个 // 操作数reg2_i是负数,那么取reg2_i的补码作为乘数,反之,直接 // 使用reg2_i作为乘数 assign opdata2_mult = (((aluop_i == `EXE_MUL_OP) || (aluop_i == `EXE_MULT_OP) || (aluop_i == `EXE_MADD_OP) || (aluop_i == `EXE_MSUB_OP))&& (reg2_i[31] == 1'b1)) ? (~reg2_i + 1) : reg2_i; //(3)得到临时乘法结果,保存在变量hilo_temp中 assign hilo_temp = opdata1_mult * opdata2_mult; //(4)对临时乘法结果进行修正,最终的乘法结果保存在变量mulres中,有两种情况: // A、如果是有符号乘法运算madd、msub,那么需要修正临时乘法结果,如下: // A1、如果被乘数与乘数,两者一正一负,那么需要对临时乘法结果 // hilo_temp求补码,作为最终的乘法结果,赋给变量mulres。 // A2、如果被乘数与乘数同号,那么hilo_temp的值就作为mulres // 的值。 // B、如果是无符号乘法运算maddu、msubu,那么hilo_temp的值就作为 // 最终的乘法结果,赋给变量mulres。 always @ (*) begin if(rst == `RstEnable) begin mulres <= {`ZeroWord,`ZeroWord}; end else if ((aluop_i == `EXE_MULT_OP) || (aluop_i == `EXE_MUL_OP) || (aluop_i == `EXE_MADD_OP) || (aluop_i == `EXE_MSUB_OP)) begin if(reg1_i[31] ^ reg2_i[31] == 1'b1) begin mulres <= ~hilo_temp + 1; end else begin mulres <= hilo_temp; end end else begin mulres <= hilo_temp; end end /**************************************************************** *********** 第二段:乘累加、乘累减 ********* *****************************************************************/ // MADD、MADDU、MSUB、MSUBU指令 always @ (*) begin if(rst == `RstEnable) begin hilo_temp_o <= {`ZeroWord,`ZeroWord}; cnt_o <= 2'b00; stallreq_for_madd_msub <= `NoStop; end else begin case (aluop_i) `EXE_MADD_OP, `EXE_MADDU_OP: begin // madd、maddu指令 if(cnt_i == 2'b00) begin // 执行阶段第一个时钟周期 hilo_temp_o <= mulres; cnt_o <= 2'b01; hilo_temp1 <= {`ZeroWord,`ZeroWord}; stallreq_for_madd_msub <= `Stop; end else if(cnt_i == 2'b01) begin // 执行阶段第二个时钟周期 hilo_temp_o <= {`ZeroWord,`ZeroWord}; cnt_o <= 2'b10; hilo_temp1 <= hilo_temp_i + {HI,LO}; stallreq_for_madd_msub <= `NoStop; end end `EXE_MSUB_OP, `EXE_MSUBU_OP: begin // msub、msubu指令 if(cnt_i == 2'b00) begin // 执行阶段第一个时钟周期 hilo_temp_o <= ~mulres + 1 ; cnt_o <= 2'b01; stallreq_for_madd_msub <= `Stop; end else if(cnt_i == 2'b01)begin // 执行阶段第二个时钟周期 hilo_temp_o <= {`ZeroWord,`ZeroWord}; cnt_o <= 2'b10; hilo_temp1 <= hilo_temp_i + {HI,LO}; stallreq_for_madd_msub <= `NoStop; end end default: begin hilo_temp_o <= {`ZeroWord,`ZeroWord}; cnt_o <= 2'b00; stallreq_for_madd_msub <= `NoStop; end endcase end end /**************************************************************** *********** 第三段:暂停流水线 ********* *****************************************************************/ // 目前只有乘累加、乘累减指令会导致流水线暂停,所以stallreq就直接等于 // stallreq_for_madd_msub的值 always @ (*) begin stallreq = stallreq_for_madd_msub; end ...... /**************************************************************** *********** 第四段:修改HI、LO寄存器的写信息 ******** *****************************************************************/ always @ (*) begin if(rst == `RstEnable) begin whilo_o <= `WriteDisable; hi_o <= `ZeroWord; lo_o <= `ZeroWord; end else if((aluop_i == `EXE_MSUB_OP) || (aluop_i == `EXE_MSUBU_OP)) begin whilo_o <= `WriteEnable; hi_o <= hilo_temp1[63:32]; lo_o <= hilo_temp1[31:0]; end else if((aluop_i == `EXE_MADD_OP) || (aluop_i == `EXE_MADDU_OP)) begin whilo_o <= `WriteEnable; hi_o <= hilo_temp1[63:32]; lo_o <= hilo_temp1[31:0]; ...... endmodule
上述代码可以分为四段理解。
(1)第一段:计算从通用寄存器中读出的两个寄存器的乘法结果,保存在mulres中。
(2)第二段:以乘累加指令为例进行讲解。乘累减指令与此类似。
- 如果cnt_i为2‘b00,表示是乘累加指令的第一个执行周期,此时将乘法结果mulres通过接口hilo_temp_o输出到EX/MEM模块,以便在下一个时钟周期使用。同时,设置变量stallreq_for_madd_msub为Stop,表示乘累加指令请求流水线暂停。
- 如果cnt_i为2‘b01,表示是乘累加指令的第二个执行周期,此时EX模块的输入hilo_temp_i就是上一个时钟周期得到的乘法结果,所以将hilo_temp_i与HI、LO寄存器的值相加,得到最终运算结果,保存到变量hilo_temp1中。同时,设置变量stallreq_for_madd_msub为NoStop,表示乘累加指令执行结束,不再请求流水线暂停。最后,设置cnt_o为2‘b10,而不是直接设置为2‘b00,目的是:如果因其它原因导致流水线保持暂停,那么由于cnt_o为2‘b10,所以EX阶段不再计算,从而防止乘累加指令重复运行。
(3)第三段:给出信号stallreq的值,目前只有乘累加、乘累减指令会导致流水线暂停,所以stallreq就直接等于变量stallreq_for_madd_msub的值。
(4)第四段:由于乘累加、乘累减指令要将最终结果写入HI、LO寄存器,所以在第四段给出了对HI、LO寄存器的写信息。
7.8.3 修改EX/MEM模块
参考图7-12可知,EX/MEM模块要增加4个接口,含义如表7-3所示。
EX/MEM模块的代码主要修改如下。完整代码位于本书附带光盘Code\Chapter7_2目录下的ex_mem.v文件。
module ex_mem( ...... // 来自控制模块的信息 input wire[5:0] stall, ...... // 增加的输入接口 input wire[`DoubleRegBus] hilo_i, input wire[1:0] cnt_i, ...... // 增加的输出接口 output reg[`DoubleRegBus] hilo_o, output reg[1:0] cnt_o ); // 在流水线执行阶段暂停的时候,将输入信号hilo_i通过输出接口hilo_o送出、 // 输入信号cnt_i通过输出接口cnt_o送出。其余时刻,hilo_o为0,cnt_o // 也为0。 always @ (posedge clk) begin if(rst == `RstEnable) begin ...... hilo_o <= {`ZeroWord, `ZeroWord}; cnt_o <= 2'b00; end else if(stall[3] == `Stop && stall[4] == `NoStop) begin ...... hilo_o <= hilo_i; cnt_o <= cnt_i; end else if(stall[3] == `NoStop) begin ...... hilo_o <= {`ZeroWord, `ZeroWord}; cnt_o <= 2'b00; end else begin hilo_o <= hilo_i; cnt_o <= cnt_i; end end endmodule
7.8.4 修改OpenMIPS模块
因为上面为EX、EX/MEM模块添加了接口,所以需要修改OpenMIPS模块,以将这些接口连接起来,连接关系如图7-12所示,具体代码不在书中列出,读者可以参考本书附带光盘Code\Chapter7_2目录下的openmips.v文件。
代码下载地址http://download.csdn.net/detail/leishangwen/7858701