CPU设计实战—异常处理指令

异常类型以及精确异常的处理

异常有点像中断,处理完还要回到原来的状态,所以需要对之前的状态进行保存。本CPU主要实现对以下异常的处理:

1.外部硬件中断

2.复位异常

3.系统调用异常(发生在译码阶段)

4.溢出异常(发生在执行阶段)

5.自陷指令异常(发生在执行阶段因为需要进行条件判断)

6.无效指令异常(发生在译码阶段)

为了不破坏原程序的执行,异常指令之前的指令都要确保执行完成,而异常指令和其之后的指令需要取消。这要求异常发生的循序和指令发生的顺序相同。可会有例外吗?

由于流水线的特性,不同指令的异常可能发生在不同阶段。例如:加载指令在访存阶段发生未对齐异常,而下一个指令是无效指令在译码阶段发生异常。无效指令先发生异常,顺序错误。如何解决?

先发生的异常不立即处理,先进行标记,所有的异常统一留到访存阶段进行处理

一、异常处理过程

 异常发生之后进行的处理:

如果发生,先看当前是否处于异常处理阶段(根据状态寄存器的EXL字段);

如果正在异常处理,接着判断是否处于中断阶段,是的话则不处理此中断(异常处理过程会禁止中断);

不是处于中断异常则保存异常原因,进入异常处理例程即可。

如果没有正在进行异常处理:

1.先保存异常原因(Cause寄存器的Execode字段)

2.将保存异常返回地址到EPC(这时候需要判断是否为延迟槽指令?PC-4,BD=1:PC,BD=0)

4.转移到异常处理例程(最后就是根据异常类型确定PC的值是哪一个入口地址)

 EXL表示当前是否在异常状态EPC保存异常返回地址BD是当发生异常的指令在分支延迟槽时被置位

 如果异常指令是延迟槽指令,那么也必须执行上一条转移指令(PC-4)

二、实现思路

我们先在译码(系统调用,无效,异常返回)执行阶段(自陷,溢出)判断是否有相关异常发生并传递给访存阶段对不同异常结合CP0寄存器进行具体判断。如果需要处理,就转移到异常处理入口地址(在异常向量表中有定义),同时需要清除回写阶段前的所有寄存器

自陷异常

通过条件判断是否发生异常,如果发生就进入异常处理例程

 系统调用异常

当我们想要切换工作模式(比如获得更高权限进入内核模式),通过调用系统异常指令来实现修改工作模式状态位:即CP0中Status的UM字段。但是本CPU没有实现其他模式。

系统异常返回

作用是返回异常出现之前的指令,异常结束,具体操作为:

1.EPC寄存器(存储异常指令地址)的值给到PC

2.清除异常状态(EXL字段清零)

三 具体实现思路

1.在各个阶段(译码阶段需要判断延迟槽)收集异常状态将信号(异常判断信号与异常地址)传送到访存阶段

2.访存阶段判断异常是否处理(需要根据CP0中的一些寄存器):

需要处理:进入异常地址(如果是异常返回指令则进入EPC的地址);清除之前阶段;修改CP0(进入异常状态等)

四 各个阶段具体实现

1.PC阶段

如果异常到来就使用新的PC值

	  end else if(stall[1] == `NoStop) begin
		  id_pc <= if_pc;
		  id_inst <= if_inst;

2.各个寄存器阶段

如果异常来临把所有输出信号赋初始值

		end else if(flush == 1'b1 ) begin
			ex_aluop <= `EXE_NOP_OP;
			ex_alusel <= `EXE_RES_NOP;
			ex_reg1 <= `ZeroWord;
			ex_reg2 <= `ZeroWord;
			ex_wd <= `NOPRegAddr;
			ex_wreg <= `WriteDisable;
			ex_excepttype <= `ZeroWord;
			ex_link_address <= `ZeroWord;
			ex_inst <= `ZeroWord;
			ex_is_in_delayslot <= `NotInDelaySlot;
	    ex_current_inst_address <= `ZeroWord;	
	    is_in_delayslot_o <= `NotInDelaySlot;	

3.译码阶段

对指令进行译码,如果是异常指令保存状态:如系统调用异常返回,自陷指令在执行阶段还要进行条件判断所以这里不记录

然后把异常状态都保存到一个信号中

//exceptiontype的低8bit留给外部中断,第9bit表示是否是syscall指令
  //第10bit表示是否是无效指令,第11bit表示是否是trap指令
  assign excepttype_o = {19'b0,excepttype_is_eret,2'b0,
  						instvalid, excepttype_is_syscall,8'b0};

4.执行阶段

判断执行阶段里的异常状态:如溢出自陷指令条件判断并把相关信号输出:

assign excepttype_o = {excepttype_i[31:12],ovassert,trapassert,excepttype_i[9:8],8'h00};

5.访存阶段

主要目的是进一步判断是否要发生异常类型(结合CP0中的信号判断)以及具体哪个异常

什么时候发生异常:复位,异常和暂停情况不处理异常,所以取指地址不为0才发生异常

区分是哪个异常类型(不能只根据传过来的异常类型,因为有些还需要CP0中的额外条件):

中断异常:当处于中断或者异常情况下不处理中断

发生中断的依据是: Cause 寄存器的IP字段不为0,且Status寄存器中相应的中断掩码字段IM也不为0,另外,Status 寄存器的EXL字段为0,表示不处于异常处理过程中,Status 寄存器的IE字段为1,表示中断使能。

其他异常类型没有额外条件,只要之前异常触发了就可以。

			
			if(current_inst_address_i != `ZeroWord) begin
				if(((cp0_cause[15:8] & (cp0_status[15:8])) != 8'h00) && 
(cp0_status[1] == 1'b0) && (cp0_status[0] == 1'b1)) begin
					excepttype_o <= 32'h00000001;        //interrupt
				end else if(excepttype_i[8] == 1'b1) begin
			  	excepttype_o <= 32'h00000008;        //syscall
				end else if(excepttype_i[9] == 1'b1) begin
					excepttype_o <= 32'h0000000a;        //inst_invalid
				end else if(excepttype_i[10] ==1'b1) begin
					excepttype_o <= 32'h0000000d;        //trap
				end else if(excepttype_i[11] == 1'b1) begin  //ov
					excepttype_o <= 32'h0000000c;
				end else if(excepttype_i[12] == 1'b1) begin  //返回指令
					excepttype_o <= 32'h0000000e;
				end
			end

 具体每个异常类型对应的输出回顾译码和执行阶段做的定义:

 到这一步才算完成对异常类型的判断!!!

下面是根据不同的异常类型做不同的操作,主要是给修改PC以及寄存器

6.根据不同的异常类型修改CP0的值(主要记载返回地址和返回原因)

CP0中重要寄存器作用:

1.EPC是异常程序计数器(Exception Program Counter),用来存储异常返回地址,一般情况下,存储发生异常的指令的地址,但是,如果发生异常的指令位于延迟槽中,那么EPC存储的是前一条转移指令的地址。该寄存器可读、可写。

2.cause寄存器的BD字段
当发生异常的指令处于分支延迟槽(Branch DelaySlot)时,该字段被置为1。ExeCode是表示异常原因

3.Status寄存器的EXL字段

表示是否处于异常级( Exception Level), 当异常发生时,会设置本字段为1,表示处理器处于异常级,此时,处理器会进入内核模式下工作,并且禁止中断。

中断指令:

依据发生异常的指令是否位于延迟槽中,保存返回地址设置EPC寄存器的值,以及Status 寄存器的BD字段,如果位于延迟槽中,那么设置EPC寄存器为上一条指令的地址,Status 寄存器的BD字段为1,反之,设置EPC寄存器为发生异常指令的地址,Status寄存器的BD字段为0。另外,设置异常状态设置Status 寄存器的EXL字段为I,表示处于异常级,中断禁止。最后,保存返回原因设置Cause寄存器的ExcCode字段5'b00000,表示异常原因是中断。

系统调用异常:

分两种情况。
(1)如果Status寄存器的EXL字段为0,那么依据发生异常的指令是否位于延迟槽中,设置EPC寄存器的值,以及Status寄存器的BD字段。如果位于延迟槽中,那么设置EPC寄存器为上一条指令的地址,Status 寄存器的BD字段为1,反之,设置EPC寄存器为发生异常指令的地址,Status 寄存器的BD字段为0。然后,设置Status寄存器的EXL字段为1,表示处于异常级,中断禁止。最后,设置Cause寄存器的ExcCode字段为5"b01000,表示异常原因是系统调用指令syscall,参考第10章的表10-7。
(2)如果Status寄存器的EXL字段为1,表示当前已经处于异常级了,又发生了新的异常,那么只需要将异常原因保存到Cause寄存器的ExcCode字段,此处设置为5'b01000,表示异常原因是系统调用指令syscall。

3.无效指令异常
与系统调用异常的处理过程类似,只是设置Status寄存器的ExcCode字段为5"b01010,表示异常原因是无效指令。
4.自陷异常
与系统调用异常的处理过程类似,只是设置Status寄存器的ExcCode字段为5'b01101,表示异常原因是自陷. 
5.溢出异常
与系统调用异常的处理过程类似,只是设置Status寄存器的ExcCode字段为5'b01100,表示异常原因是溢出。
6.异常返回指令eret
清除Status寄存器的IE字段,表示中断允许。

6.控制模块

根据异常类型给出下一个PC应有的值,这个值根据异常处理入口例程;

如果是异常返回地址,则跳转到异常返回地址

	always @ (*) begin
		if(rst == `RstEnable) begin
			stall <= 6'b000000;
			flush <= 1'b0;
			new_pc <= `ZeroWord;
		end else if(excepttype_i != `ZeroWord) begin
		  flush <= 1'b1;
		  stall <= 6'b000000;
			case (excepttype_i)
				32'h00000001:		begin   //interrupt
					new_pc <= 32'h00000020;
				end
				32'h00000008:		begin   //syscall
					new_pc <= 32'h00000040;
				end
				32'h0000000a:		begin   //inst_invalid
					new_pc <= 32'h00000040;
				end
				32'h0000000d:		begin   //trap
					new_pc <= 32'h00000040;
				end
				32'h0000000c:		begin   //ov
					new_pc <= 32'h00000040;
				end
				32'h0000000e:		begin   //eret
					new_pc <= cp0_epc_i;
				end
				default	: begin
				end
			endcase 						
		end else if(stallreq_from_ex == `Stop) begin
			stall <= 6'b001111;
			flush <= 1'b0;		
		end else if(stallreq_from_id == `Stop) begin
			stall <= 6'b000111;	
			flush <= 1'b0;		
		end else begin
			stall <= 6'b000000;
			flush <= 1'b0;
			new_pc <= `ZeroWord;		
		end    //if
	end      //always

7.定时中断的结构实现

将时钟中断输出作为一个中断信号输入,这样就可以处理时钟中断了,从而验证异常相关指令是否实现正确。
 

8、测试程序

系统调用异常测试程序

 当指令根据系统调用异常的指示入口地址跳转到这里后,需要自己写代码去进行在EPC内进行PC+4的操作,不然等异常返回指令后PC按照EPC内的值跳转后还是在系统调用指令处继续异常。

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值