我们会继续上传新书《自己写CPU》(未公布)。今天是18片,我每星期试试4

5.5 改动OpenMIPS以实现逻辑、移位操作与空指令

为了实现逻辑、移位操作与空指令(当中nop、ssnop不用特意实现,能够觉得是特殊的逻辑左移指令sll),仅仅须要改动OpenMIPS的例如以下两个模块。

  • 改动译码阶段的ID模块。用以实现对上述指令的译码。
  • 改动运行阶段的EX模块,使其依照译码结果进行运算。

5.5.1 改动译码阶段的ID模块

首先给出例如以下宏定义,都在文件defines.v中定义,读者能够在本书附带光盘的Code\Chapter5_2文件夹下找到该文件。

`define EXE_AND  6'b100100          // and指令的功能码
`define EXE_OR 6'b100101 // or指令的功能码
`define EXE_XOR 6'b100110 // xor指令的功能码
`define EXE_NOR 6'b100111 // nor指令的功能码
`define EXE_ANDI 6'b001100 //andi指令的指令码
`define EXE_ORI 6'b001101 // ori指令的指令码
`define EXE_XORI 6'b001110 //xori指令的指令码
`define EXE_LUI 6'b001111 // lui指令的指令码 `define EXE_SLL 6'b000000 // sll指令的功能码
`define EXE_SLLV 6'b000100 //sllv指令的功能码
`define EXE_SRL 6'b000010 // sra指令的功能码
`define EXE_SRLV 6'b000110 //srlv指令的功能码
`define EXE_SRA 6'b000011 // sra指令的功能码
`define EXE_SRAV 6'b000111 //srav指令的功能码 `define EXE_SYNC 6'b001111 //sync指令的功能码
`define EXE_PREF 6'b110011 //pref指令的指令码
`define EXE_SPECIAL_INST 6'b000000 //SPECIAL类指令的指令码

对指令进行译码的前提是能推断出指令种类,这个过程如图5-15所看到的。当中op就是指令的26-31bit。即指令码,op2就是指令的6-10 bit,op3就是指令的0-5bit,即功能码,op4就是指令的16-20bit,定义例如以下。

  wire[5:0] op  = inst_i[31:26];    // 指令码
wire[4:0] op2 = inst_i[10:6];
wire[5:0] op3 = inst_i[5:0]; // 功能码
wire[4:0] op4 = inst_i[20:16];

首先根据指令码op进行推断,假设是SPECIAL类指令,再推断指令的6-10bit(即op2)是否为0,假设为0。那么再根据功能码op3的值,进行终于推断。确定指令类型。假设指令码op不为SPECIAL,那么就直接根据指令码op的值进行推断。

仅仅有在确定指令sll、srl、sra的时候有一点特殊,从图5-13可知这3条指令都是SPECIAL类指令,可是这3条指令还要求第21-25bit为0。并且第6-10bit为移位位数,所以这3条指令的推断过程是:推断指令的21-31bit是否全为0,假设全为0。那么再根据功能码op3进行终于推断。确定指令类型。

ID模块主要改动内容例如以下,完整的代码能够參考本书附带光盘Code\Chapter5_2文件夹下的id.v文件。

module id(
......
); wire[5:0] op = inst_i[31:26];
wire[4:0] op2 = inst_i[10:6];
wire[5:0] op3 = inst_i[5:0];
wire[4:0] op4 = inst_i[20:16];
reg[`RegBus] imm;
reg instvalid; always @ (*) begin
if (rst == `RstEnable) begin
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
wd_o <= `NOPRegAddr;
wreg_o <= `WriteDisable;
instvalid <= `InstValid;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
reg1_addr_o <= `NOPRegAddr;
reg2_addr_o <= `NOPRegAddr;
imm <= 32'h0;
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 //指令码是SPECIAL
case (op2)
5'b00000: begin
case (op3) //根据功能码推断是哪种指令
`EXE_OR: begin //or指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_OR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_AND: begin //and指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_AND_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_XOR: begin //xor指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_XOR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_NOR: begin //nor指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_NOR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SLLV: begin //sllv指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLL_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SRLV: begin //srlv指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRL_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SRAV: begin //srav指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRA_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SYNC: begin //sync指令
wreg_o <= `WriteDisable;
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
default: begin
end
endcase
end
default: begin
end
endcase
end
`EXE_ORI: begin //ori指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_OR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= {16'h0, inst_i[15:0]};
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_ANDI: begin //andi指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_AND_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= {16'h0, inst_i[15:0]};
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_XORI: begin //xori指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_XOR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= {16'h0, inst_i[15:0]};
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_LUI: begin //lui指令
reg_o <= `WriteEnable;
aluop_o <= `EXE_OR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= {inst_i[15:0], 16'h0};
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_PREF: begin //pref指令
wreg_o <= `WriteDisable;
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
end
default: begin
end
endcase //case op if (inst_i[31:21] == 11'b00000000000) begin
if (op3 == `EXE_SLL) begin //sll指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLL_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
imm[4:0] <= inst_i[10:6];
wd_o <= inst_i[15:11];
instvalid <= `InstValid;
end else if ( op3 == `EXE_SRL ) begin //srl指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRL_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
imm[4:0] <= inst_i[10:6];
wd_o <= inst_i[15:11];
instvalid <= `InstValid;
end else if ( op3 == `EXE_SRA ) begin //sra指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRA_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
imm[4:0] <= inst_i[10:6];
wd_o <= inst_i[15:11];
instvalid <= `InstValid;
end
end
end //if
end //always ...... endmodule

对任一条指令而言。译码工作的主要内容是:确定要读取的寄存器情况、要运行的运算、要写的目的寄存器等三个方面的信息。

以下对当中几个典型指令的译码过程进行解释。

1、and指令的译码过程

and指令译码须要设置的三个方面内容例如以下,or、xor、nor指令的译码过程能够參考and指令。

(1)要读取的寄存器情况:and指令须要读取rs、rt寄存器的值。所以设置reg1_read_o、reg2_read_o为1。默认通过Regfile模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是and指令中的rs,默认通过Regfile模块读port2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是and指令中的rt。

(2)要运行的运算:and指令要进行的是逻辑“与”操作。所以设置alusel_o为EXE_RES_LOGIC,设置aluop_o为EXE_AND_OP。

(3)要写入的目的寄存器:and指令须要将结果写入目的寄存器。所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址。默认是指令字的11-15bit,正是and指令中rd的位置。

2、andi指令的译码过程

andi指令译码须要设置的三个方面内容例如以下。xori指令的译码过程能够參考andi指令。

(1)要读取的寄存器情况:andi指令仅仅须要读取rs寄存器的值。所以设置reg1_read_o为1、reg2_read_o为0。

默认通过Regfile模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是andi指令中的rs。设置reg2_read_o为0。暗含使用马上数作为运算的操作数。imm就是指令中的马上数进行零扩展后的值。

(2)要运行的运算:andi指令要进行的是逻辑“与”操作。所以设置alusel_o为EXE_RES_LOGIC,设置aluop_o为EXE_AND_OP。这一点与and指令译码过程一样。

(3)要写入的目的寄存器:andi指令须要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit。在此须要改动。对andi指令而言,目的寄存器地址是指令字的16-20bit。

3、sllv指令的译码过程

sllv指令译码须要设置的三个方面内容例如以下。srlv、srav指令的译码过程能够參考sllv指令。

(1)要读取的寄存器情况:同and指令一样。设置reg1_read_o为1、reg2_read_o为1。

(2)要运行的运算:sllv指令要进行的是逻辑左移操作。所以设置alusel_o为EXE_RES_SHIFT。设置aluop_o为EXE_SLL_OP。

(3)要写入的目的寄存器:同and指令一样。设置wreg_o为WriteEnable。设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit。正是sllv指令中rd的位置。

4、lui指令的译码过程

OpenMIPS将lui指令转化为ori 指令来运行,例如以下。

lui rt,immediate   =   ori rt,$0,(immediate || 0^16)

也就是将指令中的马上数左移16bit。然后与$0寄存器进行逻辑“或”运算。须要设置的三个方面内容例如以下。

(1)要读取的寄存器情况:须要读取寄存器$0的值,所以设置reg1_read_o为1、reg2_read_o为0。默认通过Regfile模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,參考图5-10可知,正是0。设置imm为指令中的马上数左移16位的值。

(2)要运行的运算:是逻辑“或”操作。所以alusel_o赋值为EXE_RES_LOGIC,aluop_o赋值为EXE_OR_OP。

(3)要写入的目的寄存器:lui指令须要将结果写入目的寄存器,所以设置wreg_o为WriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,在此须要改动,对lui指令而言。目的寄存器地址是指令字的16-20bit。

5、sll指令的译码过程

sll指令译码须要设置的三个方面内容例如以下。srl、sra指令的译码过程能够參考sll指令。

(1)要读取的寄存器情况:sll指令仅仅须要读取rt寄存器的值,所以设置reg1_read_o为0、reg2_read_o为1。默认通过Regfile模块读port2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是sll指令中的rt。imm就是指令中的6-10bit的值。參考图5-11可知。正是移位位数sa的值。

(2)要运行的运算:sll指令要进行的是逻辑左移操作,所以设置alusel_o为EXE_RES_SHIFT。设置aluop_o为EXE_SLL_OP。

(3)要写入的目的寄存器:sll指令须要将结果写入目的寄存器。所以设置wreg_o为WriteEnable。设置wd_o为要写入的目的寄存器地址,等于指令字的11-15bit,正是sll指令中rd的位置。

5.5.2 改动运行阶段的EX模块

改动运行阶段EX模块的代码。主要改动内容例如以下,完整的代码能够參考本书光盘的Code\Chapter5_2文件夹下的ex.v文件。

module ex(
......
); reg[`RegBus] logicout; // 保存逻辑运算结果
reg[`RegBus] shiftres; // 保存移位运算结果 // 进行逻辑运算
always @ (*) begin
if(rst == `RstEnable) begin
logicout <= `ZeroWord;
end else begin
case (aluop_i)
`EXE_OR_OP: begin // 逻辑或运算
logicout <= reg1_i | reg2_i;
end
`EXE_AND_OP: begin // 逻辑与运算
logicout <= reg1_i & reg2_i;
end
`EXE_NOR_OP: begin // 逻辑或非运算
logicout <= ~(reg1_i |reg2_i);
end
`EXE_XOR_OP: begin // 逻辑异或运算
logicout <= reg1_i ^ reg2_i;
end
default: begin
logicout <= `ZeroWord;
end
endcase
end //if
end //always // 进行移位运算
always @ (*) begin
if(rst == `RstEnable) begin
shiftres <= `ZeroWord;
end else begin
case (aluop_i)
`EXE_SLL_OP: begin // 逻辑左移
shiftres <= reg2_i << reg1_i[4:0] ;
end
`EXE_SRL_OP: begin // 逻辑右移
shiftres <= reg2_i >> reg1_i[4:0];
end
`EXE_SRA_OP: begin // 算术右移
shiftres <= ({32{reg2_i[31]}}<<(6'd32-{1'b0,reg1_i[4:0]}))
| reg2_i >> reg1_i[4:0];
end
default: begin
shiftres <= `ZeroWord;
end
endcase
end //if
end //always // 根据alusel_i选择终于的运算结果
always @ (*) begin
wd_o <= wd_i;
wreg_o <= wreg_i;
case ( alusel_i )
`EXE_RES_LOGIC: begin
wdata_o <= logicout; // 选择逻辑运算结果为终于运算结果
end
`EXE_RES_SHIFT: begin
wdata_o <= shiftres; // 选择移位运算结果为终于运算结果
end
default: begin
wdata_o <= `ZeroWord;
end
endcase
end endmodule

上述代码主要是扩展了逻辑运算的过程,同一时候添加了进行移位运算的过程,最后,根据alusel_i的值,选择当中逻辑运算或移位运算的结果作为终于运算结果。

经过以上改动就实现了逻辑、移位和空指令,是不是非常easy、直观?将验证取得成效下一。

版权声明:本文博客原创文章。博客,未经同意,不得转载。

自己写CPU第五级(4)——逻辑、实现移动和空指令的更多相关文章

  1. 自己写CPU第五级(5)——测试逻辑、实现移动和空指令

    我们会继续上传新书<自己写CPU>(未公布),今天是19片,我每星期试试4 5.6 測试程序1--測试逻辑操作实现效果 编写例如以下測试程序用于检验逻辑操作指令是否实现正确,文件名称命名为 ...

  2. 自己动手写CPU之第五阶段(3)——MIPS指令集中的逻辑、移位与空指令

    将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第17篇.我尽量每周四篇 5.4 逻辑.移位操作与空指令说明 MIPS32指令集架构中定义的逻辑操作指令有8条:and.and ...

  3. 自己动手写CPU之第六阶段(2)——移动操作指令实现思路

    将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第21篇,我尽量每周四篇 6.2 移动操作指令实现思路 6.2.1 实现思路 这6条移动操作指令能够分为两类:一类是不涉及特殊 ...

  4. 自己动手写CPU之第九阶段(2)——载入存储指令说明2(lwl、lwr)

    将陆续上传新书<自己动手写CPU>.今天是第38篇,我尽量每周四篇,可是近期已经非常久没有实现这个目标了.一直都有事,不好意思哈. 开展晒书评送书活动,在q=%E4%BA%9A%E9%A9 ...

  5. 自己写CPU第九阶段(3)——加载存储指令说明2(swl、swr)

    我们会继续上传新书<q=%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E5%86%99CPU&ie=utf-8&src=se_lighten_quot ...

  6. 自己写CPU第九阶段(5)——实现负载存储指令2(改变运行阶段)

    我们会继续上传新书<自己动手写CPU>.今天是第42篇.我尽量每周四篇,可是近期已经非常久没有实现这个目标了,一直都有事.不好意思哈. 开展晒书评送书活动,在q=%E4%BA%9A%E9% ...

  7. 自己动手写CPU之第九阶段(4)——载入存储指令实现思路

    将陆续上传新书<自己动手写CPU>,今天是第40篇,我尽量每周四篇,可是近期已经非常久没有实现这个目标了,一直都有事,不好意思哈. 开展晒书评送书活动,在q=%E4%BA%9A%E9%A9 ...

  8. 自己动手写CPU之第五阶段(1)——流水线数据相关问题

    将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第15篇,我尽量每周四篇 上一章建立了原始的OpenMIPS五级流水线结构,可是仅仅实现了一条ori指令,从本章開始,将逐步完 ...

  9. 自己动手写CPU之第五阶段(2)——OpenMIPS对数据相关问题的解决措施

    将陆续上传本人写的新书<自己动手写CPU>(尚未出版).今天是第16篇.我尽量每周四篇 5.2 OpenMIPS对数据相关问题的解决措施 OpenMIPS处理器採用数据前推的方法来解决流水 ...

随机推荐

  1. HashMap源码解读(转)

    http://www.360doc.com/content/10/1214/22/573136_78188909.shtml 最近朋友推荐的一个很好的工作,又是面了2轮没通过,已经是好几次朋友内推没过 ...

  2. WPF换肤之二:可拉动的窗体

    原文:WPF换肤之二:可拉动的窗体 让我们接着上一章: WPF换肤之一:创建圆角窗体 来继续. 在这一章,我主要是实现对圆角窗体的拖动,改变大小功能. 拖动自绘窗体的步骤 首先,通过上节的设计,我们知 ...

  3. linux查看CPU和内存信息

    一 先来看看ps命令: 1.查看当前某个时间点的进程:ps命令就是最基本同时也是非常强大的进程查看命令.使用该命令可以确定有哪些进程正在运行和运行的状态.进程是否结束.进程有没有僵死. 哪些进程占用了 ...

  4. eclipse 重构(转)

    Eclipse中的重构类型        如果你看一下Eclipse的重构菜单,可以看到四部分.第一部分是撤销和重做.其他的三部分包含Eclipse提供的三种类型的重构. 第一种类型的重构改变代码的物 ...

  5. CMD经常使用的命令

    Win7Excuting订单 win+R.运行该快捷方式.下面3一个人必须知道: ping 它是用来检查网络是否通畅或者网络连接速度的命令. 作为一个生活在网络上的管理员或者黑客来说,ping命令是第 ...

  6. java 线程 新类库中的构件 countDownLatch 使用

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGlhbmdydWkxOTg4/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...

  7. 【ThinkingInC++】61、非成员运算符

    非成员运算符 当操作者的左侧是不同的类时.运算符重载不可能是正确的类中. IostreamOperatorOverloading.cpp /** * 书本:[ThinkingInC++] * 功能:非 ...

  8. Sql Server函数全解<四>日期和时间函数

    原文:Sql Server函数全解<四>日期和时间函数   日期和时间函数主要用来处理日期和时间值,本篇主要介绍各种日期和时间函数的功能和用法,一般的日期函数除了使用date类型的参数外, ...

  9. Unity--关于优化方面的那些事儿(一)

    近期做一个小项目,要求包的大小不能超过30M. 晚上做了个小实验,方法的确非常本,只是曾经非常多没懂的地方如今清晰了很多,我是菜鸟!希望本文章对大家有帮助,谢谢! 实验结果: 实验结果: 1.场景中仅 ...

  10. [java面试题]最长的回文字符串中出现确定

    <span style="font-family: Arial, Helvetica, sans-serif;">package com.wzw.util;</s ...