首页 > 代码库 > 自己动手写CPU之第七阶段(2)——简单算术操作指令实现过程
自己动手写CPU之第七阶段(2)——简单算术操作指令实现过程
将陆续上传本人写的新书《自己动手写CPU》,今天是第25篇,我尽量每周四篇
亚马逊的预售地址如下,欢迎大家围观呵!
http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4
China-pub的预售地址如下:
http://product.china-pub.com/3804025
7.2 简单算术操作指令实现思路
虽然简单算术操作指令的数目比较多,有15条,但实现方式都是相似的,与前几章逻辑、移位操作指令的实现方式也很类似,不需要增加新的模块、新的接口,只需要修改流水线译码阶段的ID模块、执行阶段的EX模块即可。实现思路如下。
(1)修改流水线译码阶段的ID模块,添加对上述简单算术操作指令的译码,给出运算类型alusel_o、运算子类型aluop_o、要写入的目的寄存器地址wd_o等信息,同时根据需要读取地址为rs、rt的通用寄存器的值。
(2)修改流水线执行阶段的EX模块,依据传入的信息,进行运算,得到运算结果,确定最终要写目的寄存器的信息(包含:是否写、写入的目的寄存器地址、写入的值),并将这些信息传递到访存阶段。
(3)上述信息会一直传递到回写阶段,最后修改目的寄存器。
7.3 修改OpenMIPS以实现简单算术操作指令
7.3.1 修改译码阶段的ID模块
在译码阶段要增加对简单算术操作指令的分析,分析的前提是能判断出指令种类,根据图7-1至7-4可以给出如图7-5所示的确定指令种类的过程。
其中涉及的宏定义如下,正是图7-5中各个指令的指令码或功能码。在本书附带光盘Code\Chapter7_1目录下的defines.v文件中可以找到这些宏定义。
`define EXE_SLT 6'b101010 `define EXE_SLTU 6'b101011 `define EXE_SLTI 6'b001010 `define EXE_SLTIU 6'b001011 `define EXE_ADD 6'b100000 `define EXE_ADDU 6'b100001 `define EXE_SUB 6'b100010 `define EXE_SUBU 6'b100011 `define EXE_ADDI 6'b001000 `define EXE_ADDIU 6'b001001 `define EXE_CLZ 6'b100000 `define EXE_CLO 6'b100001 `define EXE_MULT 6'b011000 `define EXE_MULTU 6'b011001 `define EXE_MUL 6'b000010 ...... `define EXE_SPECIAL_INST 6'b000000 `define EXE_REGIMM_INST 6'b000001 `define EXE_SPECIAL2_INST 6'b011100
修改ID模块的代码如下,完整代码位于本书附带光盘Code\Chapter7_1目录下的id.v文件。
module id( ...... ); ...... 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 // op等于SPECIAL case (op2) 5'b00000: begin // op2等于5'b00000 case (op3) ...... `EXE_SLT: begin // slt指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SLT_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_SLTU: begin // sltu指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SLTU_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_ADD: begin // add指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_ADD_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_ADDU: begin // addu指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_ADDU_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_SUB: begin // sub指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SUB_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_SUBU: begin // subu指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SUBU_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_MULT: begin // mult指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MULT_OP; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end `EXE_MULTU: begin // multu指令 wreg_o <= `WriteDisable; aluop_o <= `EXE_MULTU_OP; reg1_read_o <= 1'b1; reg2_read_o <= 1'b1; instvalid <= `InstValid; end default: begin end endcase // end case op3 end default: begin end endcase // end case op2 end ...... `EXE_SLTI: begin // slti指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SLT_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; imm <= {{16{inst_i[15]}}, inst_i[15:0]}; wd_o <= inst_i[20:16]; instvalid <= `InstValid; end `EXE_SLTIU: begin // sltiu指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_SLTU_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; imm <= {{16{inst_i[15]}}, inst_i[15:0]}; wd_o <= inst_i[20:16]; instvalid <= `InstValid; end `EXE_ADDI: begin // addi指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_ADDI_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; imm <= {{16{inst_i[15]}}, inst_i[15:0]}; wd_o <= inst_i[20:16]; instvalid <= `InstValid; end `EXE_ADDIU: begin // addiu指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_ADDIU_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; imm <= {{16{inst_i[15]}}, inst_i[15:0]}; wd_o <= inst_i[20:16]; instvalid <= `InstValid; end `EXE_SPECIAL2_INST: begin // op等于SPECIAL2 case ( op3 ) `EXE_CLZ: begin // clz指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_CLZ_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; instvalid <= `InstValid; end `EXE_CLO: begin // clo指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_CLO_OP; alusel_o <= `EXE_RES_ARITHMETIC; reg1_read_o <= 1'b1; reg2_read_o <= 1'b0; instvalid <= `InstValid; end `EXE_MUL: begin // mul指令 wreg_o <= `WriteEnable; aluop_o <= `EXE_MUL_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 end default: begin end endcase //case op ...... endmodule
对任一条指令而言,译码工作的主要内容是:确定要读取的寄存器情况、要执行的运算、要写的目的寄存器等三个方面的信息。下面对其中几个典型指令的译码过程进行解释。
1、add指令的译码过程
add指令译码需要设置的三个方面内容如下,addu、sub、subu指令的译码过程可以参考add指令。
(1)要读取的寄存器情况:add指令需要读取rs、rt寄存器的值,所以设置reg1_read_o、reg2_read_o为1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是add指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是add指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。
(2)要执行的运算:add指令是算术运算中的加法操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETIC,aluop_o赋值为EXE_ADD_OP。
(3)要写入的目的寄存器:add指令需要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,正是add指令中的rd。
2、addi指令的译码过程
addi指令译码需要设置的三个方面内容如下,addiu、subi、subiu指令的译码过程可以参考addi指令。
(1)要读取的寄存器情况:addi指令只需要读取rs寄存器的值,所以设置reg1_read_o为1、reg2_read_o为0。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是addi指令中的rs。设置reg2_read_o为0,表示使用立即数作为参与运算的第二个操作数。imm就是指令中的立即数进行符号扩展后的值。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是imm的值。
(2)要执行的运算:addi指令是算术运算中的加法操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETIC,aluop_o赋值为EXE_ADDI_OP。
(3)要写入的目的寄存器:addi指令需要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置要写入的目的寄存器地址wd_o是指令中16-20bit的值,正是addi指令中的rt。
3、slt指令的译码过程
slt指令译码需要设置的三个方面内容如下,sltu指令的译码过程可以参考slt指令。
(1)要读取的寄存器情况:slt指令需要读取rs、rt寄存器的值,所以设置reg1_read_o、reg2_read_o为1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是slt指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是slt指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。
(2)要执行的运算:slt指令是算术运算中的比较操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETIC,aluop_o赋值为EXE_SLT_OP。
(3)要写入的目的寄存器:slt指令需要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令11-15bit的值,正是slt指令中的rd。
4、slti指令的译码过程
slti指令译码需要设置的三个方面内容如下,sltiu指令的译码过程可以参考slti指令。
(1)要读取的寄存器情况:slti指令只需要读取rs寄存器的值,所以设置reg1_read_o为1、reg2_read_o为0。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是slti指令中的rs。设置reg2_read_o为0,表示使用立即数作为运算的第二个操作数。imm就是指令中的立即数进行符号扩展后的值。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是imm的值。
(2)要执行的运算:slti指令是算术运算中的比较操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETIC,aluop_o赋值为EXE_SLT_OP。
(3)要写入的目的寄存器:slti指令需要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置要写入的目的寄存器地址wd_o是指令中16-20bit的值,正是slti指令中的rt。
5、mult指令的译码过程
mult指令译码需要设置的三个方面内容如下,multu指令的译码过程可以参考mult指令。
(1)要读取的寄存器情况:mult指令需要读取rs、rt寄存器的值,所以设置reg1_read_o、reg2_read_o为1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是mult指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是mult指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。
(2)要执行的运算:mult指令是乘法操作,并且乘法结果不需要写入通用寄存器,而是写入HI、LO寄存器,所以此处将alusel_o保持为默认值EXE_RES_NOP,aluop_o赋值为EXE_MULT_OP。
(3)要写入的目的寄存器:mult指令不需要写通用寄存器,所以设置wreg_o为WriteDisable。
6、mul指令的译码过程
mul指令译码需要设置的三个方面内容如下。
(1)要读取的寄存器情况:mul指令需要读取rs、rt寄存器的值,所以设置reg1_read_o、reg2_read_o为1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是mul指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是mul指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。
(2)要执行的运算:mul指令是乘法操作,并且乘法结果是写入通用寄存器,所以此处将alusel_o赋值为EXE_RES_MUL,aluop_o赋值为EXE_MUL_OP。
(3)要写入的目的寄存器:mul指令需要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,正是mul指令中的rd。
7、clo指令的译码过程
clo指令译码需要设置的三个方面内容如下。clz指令的译码过程可以参考clo指令。
(1)要读取的寄存器情况:clo指令只需要读取rs寄存器的值,所以设置reg1_read_o为1、reg2_read_o为0。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是clo指令中的rs。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值。
(2)要执行的运算:clo指令是算术运算中的计数操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETIC,aluop_o赋值为EXE_CLO_OP。
(3)要写入的目的寄存器:clo指令需要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,正是clo指令中的rd。
为了实现简单算术指令,今天修改了译码阶段,下一次将修改执行阶段,敬请关注!