异常类型以及精确异常的处理
异常有点像中断,处理完还要回到原来的状态,所以需要对之前的状态进行保存。本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内的值跳转后还是在系统调用指令处继续异常。