verilog实现多周期处理器之——(四)逻辑,移位操作与空指令的添加

综述

文章引于自己动手写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 @ (*)
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]		;
			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 :
					begin
						case(op2) 
							5'b0000_0:
								begin
									case(op3)
										`EXE_OR:
											begin
												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
												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
												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
												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
												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
												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
												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
												wreg_o 		<= `WriteEnable;
												aluop_o 	<= `EXE_NOP_OP;
												alusel_o 	<= `EXE_RES_SHIFT;
												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:		//判断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:
					begin
						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
						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
						wreg_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
						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
			if(inst_i[31:21] == 11'b0000_0000_000) 
				begin 
					if(op3 == `EXE_SLL)
						begin
							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
								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
									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
end

/*********************************************************************************
***************************	第二阶段:确定进行运算的源操作数1***************************
*********************************************************************************/
//regfile读端口1的输出值
always @ (*)
begin
	if(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 @ (*)
begin
	if(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 @ (*)
begin
	if( rst == `RstEnable)
		logicout <= `ZeroWord;
	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:logicout <= `ZeroWord;
			endcase;
		end
end

always @ (*)
begin
	if(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:
				begin
				 	shiftres <= `ZeroWord;
				end 
		endcase
end

/*********************************************************************************
***************	第二阶段:依据alusel_i指示的运算类型,确定wdata_o的值
*********************************************************************************/
always @ (*)
begin
	wd_o <= wd_i;				//wd_o等于wd_i,要写入的寄存器地址
	wreg_o <= wreg_i;			//wreg_o等于wreg_i,表示是否要写入目的寄存器
	case(alusel_i)
		`EXE_RES_LOGIC:
			begin
				wdata_o <= logicout;		//wdata_o中存放逻辑运算运算结果
			end
		`EXE_RES_SHIFT:
			begin
				wdata_o <= shiftres;		//wdata_o中存放位移运算运算结果
			end
		`EXE_RES_MOVE:
			begin
				wdata_o <= moveres;			//指令为EXE_RES_MOVE
			end
		default:
			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指令处理,此处也将其归纳为空指令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ty_xiumud

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值