verilog实现多周期处理器之——(四)逻辑,移位操作与空指令的添加
逻辑,移位操作与空指令的添加
- 综述
- ID模块的修改
- EX模块的修改
- 仿真验证
- I-型指令
- lui
- ori
- andi
- xori
- xor&nor
- R-型指令
- or
- and
- 移位类指令
- sll
- srl
- sra
- sllv
- srlv
- srav
- ssnop、nop
综述
文章引于自己动手写CPU之第五阶段(3)——MIPS指令集中的逻辑、移位与空指令
指令描述参考与博文[BOOK],感谢博主的分享!!!
由于那个指令具有不同的指令格式,但一共就只有三种,因此这里将其进行区分,进行译码以及执行相关代码需要进行修改。这里笔者直接加入的指令,具体的指令作用在书中或者博文中都很明确,有需要可以自行翻阅查看。
照例笔者会将自己对于代码的理解以及相关的必要知识记录总结在对应的目录下
ID模块的修改
首先是要确定指令的类型,这里对对应的指令进行区分,使用的方式是首先判断op,然后若是op下有对应的指令,依次同理进行判断。注意这里有一个例外情况就是使用inst_i[31:21]
进行判断,这里是单独分开进行判断的。具体见代码。由于具体的指令,使用的寄存器数量不同,对于每一个数据的处理也是不同的,具体可以在书中找到对应的操作,然后对应代码中查看。
`timescale 1ns / 1ps
`include "defines.v"
module id(input wire rst ,input wire [`InstAddrBus] pc_i ,input wire [`InstBus] inst_i ,//读取regfile的值input wire [`InstBus] reg1_data_i ,input wire [`InstBus] reg2_data_i ,//输出到regfile的信息output reg reg1_read_o ,output reg reg2_read_o ,output reg [`RegAddrBus] reg1_addr_o ,output reg [`RegAddrBus] reg2_addr_o ,//送到执行阶段的信息output reg [`AluOpBus] aluop_o ,output reg [`AluSelBus] alusel_o ,output reg [`RegBus] reg1_o , //送到执行阶段的源操作数output reg [`RegBus] reg2_o , //送到执行阶段的源操作数output reg [`RegAddrBus] wd_o ,output reg wreg_o ,
//处于执行阶段的指令运行的运算结果input wire [`RegAddrBus] ex_wd_i , //执行阶段的目的寄存器地址input wire ex_wreg_i , //是否要写入数据标志 input wire [`RegBus] ex_wdata_i , //执行阶段送到访存的数据//处于访存阶段的指令运行的运算结果input wire [`RegAddrBus] mem_wd_i ,input wire mem_wreg_i ,input wire [`RegBus] mem_wdata_i );
//取得指令的指令码,功能码
//用于ori指令只需要判断21-31 bit的值,即可判断是否是ori指令
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 @ (*)
beginif(rst == `RstEnable)beginaluop_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 ;endelse beginaluop_o <= `EXE_NOP_OP ;alusel_o <= `EXE_RES_NOP ;wd_o <= inst_i[15:11] ;wreg_o <= `WriteDisable ;instvalid <= `InstInValid ;reg1_read_o <= 1'b0 ;reg2_read_o <= 1'b0 ; reg1_addr_o <= inst_i[25:21] ;//默认通过Regfile读取端口1的寄存器地址reg2_addr_o <= inst_i[20:16] ;//默认通过Regfile读取端口2的寄存器地址 imm <= `ZeroWord ;case(op)`EXE_SPECIAL_INST :begincase(op2) 5'b0000_0:begincase(op3)`EXE_OR:beginwreg_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:beginwreg_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:beginwreg_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:beginwreg_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:beginwreg_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:beginwreg_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:beginwreg_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:beginwreg_o <= `WriteEnable;aluop_o <= `EXE_NOP_OP;alusel_o <= `EXE_RES_SHIFT;reg1_read_o <= 1'b0;reg2_read_o <= 1'b1;instvalid <= `InstValid;enddefault:beginendendcaseenddefault:beginendendcaseend`EXE_ORI: //判断op的值是进行opi指令begin wreg_o <= `WriteEnable ; //ori 指令需要将结果写入目的寄存器,所以wreg_o 为 WriteEnable aluop_o <= `EXE_OR_OP ; //运算的子类型是逻辑“或”运算 alusel_o <= `EXE_RES_LOGIC ; //运算类型是逻辑运算reg1_read_o <= 1'b1 ; //需要通过Regfile的读端口1读取寄存器reg2_read_o <= 1'b0 ; //不需要通过Regfile的读端口2读取寄存器imm <= {16'h0,inst_i[15:0]} ; //指令执行需要的立即数 wd_o <= inst_i[20:16] ; //执行指令要写入的目的寄存器地址 instvalid <= `InstValid ; //ori指令是有效的end`EXE_ANDI:beginwreg_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:beginwreg_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:beginwreg_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:beginwreg_o <= `WriteDisable ;aluop_o <= `EXE_NOP_OP ; alusel_o <= `EXE_RES_NOP ;reg1_read_o <= 1'b0 ;reg2_read_o <= 1'b0 ;instvalid <= `InstValid ; enddefault: beginendendcaseif(inst_i[31:21] == 11'b0000_0000_000) begin if(op3 == `EXE_SLL)beginwreg_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 ;endelse if(op3 == `EXE_SRL)beginwreg_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 ;endelse if(op3 == `EXE_SRA) beginwreg_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 ;endendend
end/*********************************************************************************
*************************** 第二阶段:确定进行运算的源操作数1***************************
*********************************************************************************/
//regfile读端口1的输出值
always @ (*)
beginif(rst == `RstEnable)reg1_o <= `ZeroWord;else // 对于源操作数,若是目前端口的读取得寄存器数据地址是 执行阶段的要写入的目的寄存器 ,那么直接将执行的结果作为reg1_o的值。//这个数相当于是要写入的数据if((reg1_read_o == 1'b1) && (ex_wreg_i == 1'b1) && (ex_wd_i == reg1_addr_o) )reg1_o <= ex_wdata_i; else//对于要是目的寄存器,我们要读取的寄存器其实是最终访存要写入的寄存器,那么访存的数据就直接作为源操作数进行处理if((reg1_read_o == 1'b1) && (mem_wreg_i == 1'b1) && (mem_wd_i == reg1_addr_o) )reg1_o <= mem_wdata_i ;else if(reg1_read_o == 1'b1)reg1_o <= reg1_data_i;else if(reg1_read_o == 1'b0)reg1_o <= imm; //立即数else reg1_o <= `ZeroWord;
end/*********************************************************************************
*************************** 第三阶段:确定进行运算的源操作数2***************************
*********************************************************************************/
//regfile读端口2的输出值
always @ (*)
beginif(rst == `RstEnable)reg2_o <= `ZeroWord;else // 对于源操作数,若是目前端口的读取得寄存器数据地址是 执行阶段的要写入的目的寄存器 ,那么直接将执行的结果作为reg2_o的值。//这个数相当于是要写入的数据if((reg2_read_o == 1'b1) && (ex_wreg_i == 1'b1) && (ex_wd_i == reg2_addr_o) )reg2_o <= ex_wdata_i; else//对于要是目的寄存器,我们要读取的寄存器其实是最终访存要写入的寄存器,那么访存的数据就直接作为源操作数进行处理if((reg2_read_o == 1'b1) && (mem_wreg_i == 1'b1) && (mem_wd_i == reg2_addr_o) )reg2_o <= mem_wdata_i ;else if(reg2_read_o == 1'b1)reg2_o <= reg2_data_i;else if(reg2_read_o == 1'b0)reg2_o <= imm; //立即数else reg2_o <= `ZeroWord;
end
endmodule
EX模块的修改
执行阶段对于不同数,以及不同的信息进行处理。新建寄存器用于保存移位寄存器的结果,根据指令类型对象对相应的数据进行输出。
`timescale 1ns / 1ps
`include "defines.v"
module ex(input wire rst ,//译码送至执行阶段的数据信息input wire [`AluOpBus] aluop_i ,input wire [`AluSelBus] alusel_i ,input wire [`RegBus] reg1_i ,input wire [`RegBus] reg2_i ,input wire [`RegAddrBus] wd_i ,input wire wreg_i ,//执行结果output reg [`RegAddrBus] wd_o ,output reg wreg_o ,output reg [`RegBus] wdata_o );
//保存逻辑运算的结果
reg [`RegBus] logicout ;//保存位移运算结果
reg [`RegBus] shiftres ;// 移动操作的结果
reg [`RegBus] moveres ;/*********************************************************************************
*************** 依据aluop_i指示的运算子类型进行运算,
*********************************************************************************/always @ (*)
beginif( rst == `RstEnable)logicout <= `ZeroWord;else begincase(aluop_i)`EXE_OR_OP:beginlogicout <= reg1_i | reg2_i;end`EXE_AND_OP:beginlogicout <= reg1_i & reg2_i;end`EXE_NOR_OP: //逻辑或与非beginlogicout <= ~(reg1_i | reg2_i);end`EXE_XOR_OP:beginlogicout <= reg1_i ^ reg2_i;enddefault:logicout <= `ZeroWord;endcase;end
endalways @ (*)
beginif(rst == `RstEnable)shiftres <= `ZeroWord;else case(aluop_i)`EXE_SLL_OP: //逻辑左移shiftres <= reg2_i << reg1_i[4:0];`EXE_SRL_OP:shiftres <= reg2_i >> reg1_i[4:0];`EXE_SRA_OP:shiftres <= ( {32{ reg2_i[31]} } << (6'd32 - {1'b0,reg1_i[4:0] } ) ) | reg2_i >> reg1_i[4:0];default:beginshiftres <= `ZeroWord;end endcase
end/*********************************************************************************
*************** 第二阶段:依据alusel_i指示的运算类型,确定wdata_o的值
*********************************************************************************/
always @ (*)
beginwd_o <= wd_i; //wd_o等于wd_i,要写入的寄存器地址wreg_o <= wreg_i; //wreg_o等于wreg_i,表示是否要写入目的寄存器case(alusel_i)`EXE_RES_LOGIC:beginwdata_o <= logicout; //wdata_o中存放逻辑运算运算结果end`EXE_RES_SHIFT:beginwdata_o <= shiftres; //wdata_o中存放位移运算运算结果end`EXE_RES_MOVE:beginwdata_o <= moveres; //指令为EXE_RES_MOVEenddefault:wdata_o <= `ZeroWord;endcase
end
endmodule
仿真验证
测试逻辑操作
首先分析测试数据。OpenMIPS将指令转换为ori指令来执行。lui指令使用立即数(扩展后的立即数)做或运算。得到0x01010000
之后进行两次或运算,将数据分别写入寄存器1与寄存器2中。
接下来是使用寄存器中的数据进行或运算,将结果送入1寄存器。
然后使用立即数与寄存器中的数据进行与运算,结果送入寄存器3,在使用寄存器1与寄存器3中的数据相与结果送寄存器1,然后使用寄寄存器1中的数据与立即数相异或,之后分别使用寄存器中的数据进行两次运算即可。
I-型指令
lui
lui: 把立即数加载到寄存器高位。
ori
逐位逻辑操作指令
andi
逐位逻辑操作指令
xori
逐位逻辑操作指令
异或运算指令使用方法为:xori rt, rs, immediate
指令作用为:rt <- rs XOR zero_extended(immediate)。将地址为rs的通用寄存器的值。与指令中马上数进行零扩展后的值进行逻辑“异或”运算,运算结果保存到地址为rt的通用寄存器中。
xor&nor
逐位逻辑操作指令
R-型指令
or
逐位逻辑操作指令
and
逐位逻辑操作指令
得到的数据与书中预期一致。
测试移位操作与空指令
这里笔者不一一分析,这里指出一个问题,是在第六行的空指令,加上会多一个时钟周期,不加会少一个时钟周期。自己看仿真图即可理解。这里依据给出的源文件,期间分析应该有五个时钟周期的延时。
简单分析,在复位后,第五个时钟上升沿,寄存器2开始有数据,然后更新,之后的四个时钟周期,对寄存器1,5,8进行操作,因此加上其本身,一个维持五个时钟周期。书中是维持了四个时钟周期,是不对的。
不加时候的仿真截图
加上之后的
这里书中给出的是四个时钟周期的延时。这里上边已经进行了分析。
移位类指令
sll
当功能码是6’b000000,表示是sll指令,逻辑左移
指令使用方法为:sll rd, rt, sa
指令作用为:rd <- rt << sa (logic),将地址为rt的通用寄存器的值,向左移sa位。空出来的位置使用0填充。结果保存到地址为rd的通用寄存器中。
srl
当功能码是6’b000010,表示是srl指令。逻辑右移
指令使用方法为:srl rd, rt, sa
指令作用为:rd <- rt >> sa (logic),将地址为rt的通用寄存器的值,向右移sa位,空出来的位置使用0填充,结果保存到地址为rd的通用寄存器中。
sra
当功能码是6’b000011。表示是sra指令,算术右移
指令使用方法为:sra rd, rt, sa
指令作用为:rd <- rt >> sa (arithmetic),将地址为rt的通用寄存器的值,向右移sa位。空出来的位置使用rt[31]的值填充,结果保存到地址为rd的通用寄存器中。
sllv
当功能码是6’b000100。表示是sllv指令,逻辑左移
指令使用方法为:sllv rd, rt, rs
指令作用为:rd <- rt << rs[4:0](logic)。将地址为rt的通用寄存器的值。向左移位,空出来的位置使用0填充,结果保存到地址为rd的通用寄存器中。移位位数由地址为rs的寄存器值的0-4bit确定。
srlv
当功能码是6’b000110,表示是srlv指令。逻辑右移
指令使用方法为:srlv rd, rt, rs
指令作用为:rd <- rt >> rs[4:0](logic),将地址为rt的通用寄存器的值,向右移位,空出来的位置使用0填充。结果保存到地址为rd的通用寄存器中。
移位位数由地址为rs的寄存器值的0-4bit确定。
srav
当功能码是6’b000111,表示是srav指令,算术右移
指令使用方法为:srav rd, rt, rs
指令作用为:rd <- rt >> rs[4:0](arithmetic),将地址为rt的通用寄存器的值,向右移位。空出来的位置使用rt[31]填充,结果保存到地址为rd的通用寄存器中。
移位位数由地址为rs的寄存器值的0-4bit确定。
总结来说。这六条移位操作指令能够分为两种情况:sllv、srav、srlv这3条指令的助记符最后有“v”。表示移位位数是通过寄存器的值确定的,sll、sra、srl这3条指令的助记符最后没有“v”,表示移位位数就是指令中6-10bit的sa的值。
ssnop、nop
空操作:
nop:相当于 sll zero,zero,o,
ssnop: equals sll zero,zero,1. 这个指令不得与其它指令同时发送,这样就保证了其运行要花费至少一个时钟周期。这在简单的流水线的CPU上无关紧要,但在复杂些的实现上对于实现强制的延时很有用。
另外,MIPS32指令集架构中还定义了sync、pref这2条指令,当中sync指令用于保证载入、存储操作的顺序,对于OpenMIPS而言,是严格依照指令顺序运行的,载入、存储操作也是依照顺序进行的,所以能够将sync指令当作nop指令处理,在这里将其归纳为空指令。pref指令用于缓存预取,OpenMIPS没有实现缓存,所以也能够将pref指令当作nop指令处理,此处也将其归纳为空指令。
verilog实现多周期处理器之——(四)逻辑,移位操作与空指令的添加相关推荐
- verilog实现多周期处理器之——(二)第一条指令ori的实现
本博文希望对于OpenMIPS第一条指令ori加以实现并总结.会加入一些基本的理论以及博主的学习记录. 流水与五级流水 什么是流水:拆分,并行.将多条指令的执行相互重叠起来.就构成了流水,这样充分利用 ...
- verilog实现多周期处理器之——目录及总述
本系列博文将使用verilog语言,实现兼容MIPS32指令集架构的处理器--OpenMIPS,MIPS是典型的RSIC处理器.主要依据雷思磊老师的<自己动手写CPU>,袁春风老师主编的& ...
- 《自己动手写CPU》第五章--逻辑、移位操作与空指令的实现
5.1流水线数据相关问题 流水线中经常有一些被称为"相关"的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些"相关"会降低流水线的性能.流水 ...
- 逻辑、移位操作与空指令的实现
逻辑.移位操作和空指令的实现 1. 流水线数据相关的问题 流水线上经常会有一些被称为"相关"的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些"相关& ...
- 第五章逻辑、移位操作与空指令的实现
上一章建立了原始的OpenMIPS五级流水线结构,但是只实现了一条ori指令,从本章开始,将逐步完善.本章首先讨论了流水线数据相关问题,然后修改OpenMIPS以解决问题.接着对逻辑.移位操作和空指令 ...
- 自己动手写CPU(3)逻辑、移位操作与空指令
自己动手写CPU(3)逻辑.移位操作与空指令 指令说明 MIPS32指令集架构中定义的逻辑操作指令有8条: and.andi.or.ori.xor.xori.nor.lui,其中 ori指令已经实现. ...
- verilog实现多周期处理器之——(六)简单算数操作指令的实现
实现的指令说明 这里解释有符号扩展与无符号扩展,有符号数扩展符号位.也就是1,无符号数扩展0.也就是在前面补满零或1 R-型指令 加减比较指令 add.addu.sub.sub.slt.sltu 这6 ...
- verilog实现多周期处理器之——(五)移动操作(通用数据传送)指令的实现
本文参考作者 自己动手写CPU之第六阶段(1)--移动操作指令说明 自己动手写CPU之第六阶段(2)--移动操作指令实现思路 本文会添加笔者自己的思路以及理解在其中. 指令说明 这6条指令都是R类型指 ...
- verilog实现多周期处理器之——(三)数据相关问题及其解决
本文于自己动手写CPU之第五阶段--流水线数据相关问题 "相关"问题 流水线中常常有一些被称为"相关"的情况发生,它使得指令序列中下一条指令无法依照设计的时钟周 ...
最新文章
- 开源!开源!我写的Anto.exe C#代码自动生成工具.欢迎下载。。
- golang 接口_「实战」助力数据库开发之接口篇 - Golang 连接 Greenplum
- SM30里如何输入物料号自动带出物料描述
- 遥感计算机分类实验的难点,8-遥感实验.doc
- Python学习之 !/usr/bin/python 和 !/usr/bin/env python区别
- php 图片不让下载,php让图片可以下载的方法
- 学python分析双色球_我通过使用Python分析了80多个工作拒绝而学到的东西
- css中设置微软雅黑时,在ie6css中有些样式会失效的解决办法
- SM2258XT量产工具教程,SM2258XT_Q0816A+B05 B16 B17的正确开卡步骤
- 如何制定人生目标和实现目标
- Java 开发实例(第3篇),绘制迷宫1 生成迷宫地图
- HTAP的下一步?SoTP初探(上):从“大”数据到“小”而“宽”数据
- 7年了,终于拉开窗帘,看窗外,世界依然美好,这个世界,我还在
- SVN checkout 之后图标(绿色勾之类的)没有显示出来的问题
- SIAL跻身世界三大食品展的成功哲学
- 自动下载RDS MySQL备份文件
- 如何去除IDEA中xml文件的屎黄色背景
- 机器学习(七) 自编码器
- Components of Ryu
- 论文翻译:Observation of Gravitational Waves from a Binary Black Hole Merger(2016的PRL,诺奖论文)
热门文章
- tcpdf中文解决方案
- ORACLE数据库多表关联查询效率问题解决方案
- 我不断收到“ Uncaught SyntaxError:意外令牌o”
- 如何在PHP中使用cURL连接到Tor隐藏服务?
- 使用节点或Express返回JSON的正确方法
- win11u盘安装报错怎么办 windows11u盘安装报错的解决方法
- win11 P85主板能正常安装吗 windows11使用p85主板的安装的步骤方法
- 弹出页面,弹出框,$(‘‘).modal({});模态框
- axure html尺寸,Axure 原型 | 教你使用自适应视图构建界面
- win10卸载db2_如何在Linux下干净卸载db2数据库