自己动手写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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是add指令中的rs,默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是slt指令中的rs。默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit。正是mult指令中的rs。默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是mul指令中的rs,默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址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。
为了实现简单算术指令,今天改动了译码阶段,下一次将改动运行阶段,敬请关注。