将陆续上传本人写的新书《自己动手写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所示。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9QzVaNDZtJGaoJzYwZ1RiZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zNzkjNyYTMyEDMxkDM0EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
其中涉及的宏定义如下,正是图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的值。
下一次将测试除法指令的实现效果。