「BUAA-CO」P7_MIPS微系统(异常中断)


MIPS微系统设计方案

设计概述

​ 本文设计的是由Verilog实现的MIPS微系统,该微系统支持53条MIPS汇编指令,为了实现该功能,笔者设计了IFU,GRF,NPC,CMP,EXT,ALU,D_Reg,E_Reg,M_Reg,W_Reg,MCU, HCU,MDU,BE,DE,CP0,Bridge,TC等关键模块。为实现微系统,我们需要对P6中CPU的结构层次进行一定的调整——将P6设计的CPU单独封装成一个模块,然后再设计Bridge、Timer0、Timer1三个外设,和CPU处于同一层次上。此外我们还需要在CPU的M流水级设计一个CP0模块用于中断异常处理。结构层次如下所示——

实现指令说明

我们将本CPU实现的指令分为以下几类:

  • calc_R: add,sub,addu, subu, and, or, nor, xor, slt, sltu

  • calc_I: addi,addiu, andi, ori, xori, slti, sltiu

  • shift: sll, sra, srl

  • shiftv: sllv, srav, srlv

  • load: lw, lh, lhu, lb, lbu

  • store: sw, sh, sb

  • B类:beq,bne,bgtz,blez,bgez,bltz

  • J类:j, jal, jr, jalr

  • 特殊:lui

  • md类:mult, multu, div, divu

  • mf类:mfhi, mflo

  • mt类:mthi, mtlo

  • CP0相关指令:mfc0,mtc0

  • 异常中断返回: eret

工程模块定义

该部分我们不再介绍P6中已经实现的模块,只介绍新增模块CPU(封装P6CPU),CP0,Bridge,TC(用于实例化Timer1,Timer0)。但是原有模块需要根据异常处理的实现需要进行微调。

CPU模块

该模块用于封装P6中的已经设计好的CPU(包括新模块CP0)

信号名 方向 位宽 描述
lk I 1 时钟信号
reset I 1 同步复位信号
inter_out I 1 外部输入的中断信号
inter_T0 I 1 Timer0产生的中断信号
inter_T1 I 1 Timer1产生的中断信号
i_inst_rdata I 32 i_inst_addr队形的32位指令
m_data_addr I 32 m_data_addr 对应的 32 位数据
i_inst_addr O 32 需要进行取指操作的流水级 PC(一般为 F 级)
m_data_addr O 32 数据存储器待写入地址
m_data_wdata O 32 数据存储器待写入数据
m_data_byteen O 4 字节使能信号(相当于之前的MenWrite信号)
m_inst_addr O 32 M级PC
w_grf_we O 1 grf 写使能信号
w_grf_addr O 5 grf 中待写入寄存器编号
w_grf_wdata O 32 grf 中待写入数据
w_inst_addr O 32 W 级 PC
spj_7f20 O 32 响应外部中断的标记寄存器

CP0模块

该模块位于CPU模块中,主要用于获取外部中断信息和内部异常信息,进行判断后输出异常/中断请求。同时该模块中设有四个寄存器——SR,Cause,EPC和PRIP。

信号名 方向 位宽 描述 产生来源和机制
clk I 1 时钟信号
reset I 1 复位信号
A1 I 5 读 CP0 寄存器编号 执行 mfc0 指令时产生
A2 I 5 写 CP0 寄存器编号 执行 mtc0 指令时产生
D_in I 32 CP0 寄存器的写入数据 执行 mtc0 指令时产生
pc I 32 中断/异常时的 PC(M级pc)
ExcCode_in I 5 中断/异常的类型 异常功能部件
BD_in I 1 分支延迟槽指令标志 M_BD输入
HWInt I 6([7:2]) 6 个设备中断 外部设备
WE I 1 CP0 寄存器写使能 执行 mtc0 指令时产生
EXLClr I 1 置 0 SR 的EXL 位 Eret_M控制信号输入
req O 1 异常/中断请求 由 CP0 模块确认响应异常/中断
EPC_out O 32 EXC 寄存器输出 CP0 模块中的EPC寄存器
D_out O 32 CP0 寄存器的输出数据 执行 mfc0 指令时产生

Bridge模块

该模块为系统桥,实际上是区分地址的组合逻辑模块,用于CPU和DM、Timer1、Timer0之间的的数据交换。

信号名 方向 位宽 描述
A_in I 32 写入/读取的外设单元的地址
WD_in I 32 写入外设单元的数据
byteen I 4 写入外设单元的使能
DM_RD I 32 DM读取值的输入
T1_RD I 32 Timer1读取值的输入
T0_RD I 32 Timer0读取值的输入
A_out O 32 写入/读取的外设单元的地址
WD_out O 32 写入外设单元的数据
RD_out O 32 外设单元的读取值输出
DM_WE O 4 DM写入使能
T1_WE O 1 Timer1写入使能
T0_WE O 1 Timer2写入使能

TC模块

信号名 方向 位宽 描述
clk I 1 时钟信号
reset I 1 复位信号
addr I 30([31:2]) Timer写入地址
WE I 1 Timer写入使能
Din I 32 Timer写入数据
Dout O 32 Timer读取数据
IRQ O 1 中断请求

重要机制实现方法

CP0响应机制

CP0是检测异常和中断的重要部件,异常和中断通过以下接口传入——

  • 中断信息通过 HWInt[7:2] 接口进入,其中HWInt[2]连接Timer0的终端请求,HWInt[3]连接Timer1的终端请求,HWInt[4]连接外部的中断请求。

  • 异常信息通过ExcCode_inBD_in两个接口传入,ExcCode_in传入的是异常代码(ADEL,ADES,RI,OV),BD_in传入的是延迟槽指令标志(有效则表示当前指令为延迟槽指令)。

检测到中断或者异常时,CP0会进行判断并响应,决定是否将req(CP0向CPU发出的中断请求)置1。判断逻辑如下——

wire inter_req  = (|(HWInt & IM)) & IE & (!EXL);//中断有效判断
wire exc_req    = (ExcCode_in != 5'd0) & (!EXL);//异常有效判断
assign req      = inter_req | exc_req;//

当req有效,CP0还需要完成以下任务——

  • EXL置1
  • 将M级PC存入EPC(延迟槽指令中存入EPC-4)
  • 如果当前响应中断,ExcCode寄存器写入0;若响应异常,ExcCode寄存器写入外部传入的异常代码ExcCode_in
  • BD寄存器写入外部传入的BD_in信号

代码如下——

always @(posedge clk) begin
       if(reset) begin
       //………………………………………………………… 
       end   
       else if(req) begin
           EXL <= 1'b1;
           EPC <= BD_in ? (pc - 32'd4) : pc; 
           ExcCode <= inter_req ? 5'd0 : ExcCode_in;
           BD <= BD_in;
       end
    	//…………………………………………

此外,无论是否发出中断请求,在每一周期均需要将HWInt[6:2]写入Cause寄存器中的的IP域

always @(posedge clk) begin
       if(reset) begin
      		//………………………………………………………… 
       end   
    	else begin
           IP <= HWInt;
           //…………………………………………………………
       end

系统桥设计

系统桥其实是充当一个“交换机”的角色,将CPU传来的地址写入相应的外设(DM、Timer0、Timer1),只需要组合逻辑便可实现。代码如下——

assign A_out  = A_in;   
assign WD_out = WD_in;
assign DM_WE  = (A_in >= 32'h0    && A_in <= 32'h2fff) ? byteen  : 4'd0;
assign T0_WE  = (A_in >= 32'h7f00 && A_in <= 32'h7f0b) ? &byteen : 1'd0;
assign T1_WE  = (A_in >= 32'h7f10 && A_in <= 32'h7f1b) ? &byteen : 1'd0;
assign RD_out = (A_in >= 32'h0    && A_in <= 32'h2fff) ? DM_RD   :
                (A_in >= 32'h7f00 && A_in <= 32'h7f0b) ? T0_RD   :
                (A_in >= 32'h7f10 && A_in <= 32'h7f1b) ? T1_RD   :
                                                         32'd0;

异常识别

P7中我们考虑的异常情况有以下几种——

  • F级异常:
    • PC地址没有字对齐(AdEL)
    • PC地址超过0x3000 ~ 0x6ffc(AdEL)
  • D级异常:
    • 未知的指令码(RI)
  • E级异常:
    • addi、add、sub计算溢出(Ov)
    • load类指令计算地址时加法溢出(AdEL)
    • store类指令计算地址时加法溢出(AdES)
  • M级异常:
    • lw取数地址未 4 字节对齐(AdEL)
    • lh、lhu取数地址未与 2 字节对齐(AdEL)
    • lh、lhu、lb、lbu取 Timer 寄存器的值(AdEL)
    • load型指令取数地址超出 DM、Timer0、Timer1 的范围(AdEL)
    • sw存数地址未 4 字节对齐(AdES)
    • sh存数地址未 2 字节对齐(AdES)
    • sh、sb存 Timer 寄存器的值(AdES)
    • sw向计时器的 Count 寄存器存值(AdES)
    • store型指令存数地址超出 DM、Timer0、Timer1 的范围(AdES)

针对以上列出的异常情况,我们在每一个流水级对异常进行检测。由于教程提出了以下要求——

  • 发生取指异常后视为 nop 直至提交到 CP0。

  • 发生 RI 异常后视为 nop 直至提交到 CP0。

  • loadstore 类算址溢出按照 AdELAdES 处理。

因此不会出现一个指令在多级出现异常的情况。如果某个流水级出现了新的异常,我们将这个异常流水到下一级即可,而不是流水上一级传来的异常;如果这个流水级没有出现新的异常,则将上一级传来的异常继续流水给下一级即可。代码如下——

//F级异常检测
assign F_ExcCode = (F_pc[1:0] != 2'b00 || F_pc < 32'h3000 || F_pc > 32'h6ffc) ? `ADEL : 5'd0;
//D级异常检测(Invalid_D有效表示当前指令非法)
wire [4:0] D_ExcCode_fixed = (Invalid_D) ? `RI : D_ExcCode;  
//E级异常检测(IsAriOv_E表示当前指令是addi、add、sub中的一种,E_overflow表示ALU出现溢出)
wire [4:0] E_ExcCode_fixed =    (E_overflow && (LSOp_E == `LS_LW || LSOp_E == `LS_LH || LSOp_E == `LS_LHU || LSOp_E == `LS_LB || LSOp_E == `LS_LBU)) ? `ADEL :
                                    (E_overflow && (LSOp_E == `LS_SW || LSOp_E == `LS_SH || LSOp_E == `LS_SB))                                           ? `ADES :
                                    (E_overflow && IsAriOv_E)     
//M级异常检测 
wire lhlb_tag   = (LSOp_M == `LS_LH || LSOp_M == `LS_LHU || LSOp_M == `LS_LB || LSOp_M == `LS_LBU);
wire load_tag   = (LSOp_M == `LS_LH || LSOp_M == `LS_LHU || LSOp_M == `LS_LB || LSOp_M == `LS_LBU || LSOp_M == `LS_LW);
wire store_tag  = (LSOp_M == `LS_SW || LSOp_M == `LS_SH  || LSOp_M == `LS_SB );
wire timer_addr = (M_AO >= 32'h7f00 && M_AO <= 32'h7f0b) || (M_AO >= 32'h7f10 && M_AO <= 32'h7f1b);
wire DM_addr    = ((M_AO >= 32'h0 && M_AO <= 32'h2fff));
//Excption
wire [4:0] M_ExcCode_fixed  =   ((LSOp_M == `LS_LW) && (M_AO[1:0] != 2'b0))                    ? `ADEL :
                                ((LSOp_M == `LS_LH || LSOp_M == `LS_LHU) && (M_AO[0] != 1'b0)) ? `ADEL :
                                (lhlb_tag && timer_addr)                                       ? `ADEL :
                                (load_tag && !timer_addr && !DM_addr)                          ? `ADEL :
                                ((LSOp_M == `LS_SW) && (M_AO[1:0] != 2'b0))                    ? `ADES :
                                ((LSOp_M == `LS_SH) && (M_AO[0] != 1'b0))                      ? `ADES :
                                ((LSOp_M == `LS_SH || LSOp_M == `LS_SB) && timer_addr)         ? `ADES :
                                ((LSOp_M == `LS_SW) && timer_addr && (M_AO[3:0] == 4'h8))      ? `ADES :
                                (store_tag && !timer_addr && !DM_addr)                         ? `ADES :
                                                                                                 M_ExcCode;
           

测试方案

handler设计

.ktext 0x4180

_main_handler:
	mfc0 	$k0, $13
	mfc0 	$k1, $14
	ori 	$k1, $0, 0x007c
	and	$k0, $k1, $k0
	beq 	$0, $k0, _return
	nop
	mfc0	$k0, $14
	addu	$k0, $k0, 4
	mtc0	$k0, $14
	j	_return 
	nop
	
_return:
	eret

测试数据样例

  • MIPS机器码样例

    text
    la	$ra, pos_1
    addi	$ra, $ra, 1
    jr	$ra
    ori	$s0, $0, 100
    ori	$s0, $0, 200 #invalid
    pos_1: 
    addi	$s1, $s0, 20 #invalid
    addi	$s1, $s0, 50 
         	             #F_exc_AdEL
    
    nop                  #you can change it 0xf000000f
    li	$t0, 13
    mfc0	$t0, $t1     #D_exc_RI
    
    lui	$s2, 0x7fff
    lui	$s3, 0x7fff
    add	$s4, $s2, $s3
        #E_exc_Ov_add
    and	$5, $s2, $s3
    
    
    lui	$s2, 0x7fff
    addi	$s3, $s2, 0x7fffffff  
        #E_exc_Ov_addi
    and	$5, $s2, $s3
    
    lui	$s2, 0x7fff
    lui	$s3, 0x8fff
    sub	$s4, $s3, $s2
        #E_exc_Ov_sub
    and	$5, $s2, $s3
    
    li	$s0, 0x12345678
    sw	$s0, 0($0)
    lw	$s1, 3($0)
    lw	$s2, 0($0)
        #D_exc_AdEL
    and	$5, $s2, $s3
    
    li	$s0, 0x8fffffff
    sw	$s0, 0($0)
    lh	$s1, 1($0)
    lh	$s2, 2($0)
        #D_exc_AdEL
    and	$5, $s2, $s3
    
    li	$s0, 0x80000000
    sw	$s0, 0($0)
    lhu	$s1, 1($0)
    lhu	$s2, 2($0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    li	$s0, 0x7f00
    li	$s1, 10
    sw	$s1, 0($s0)
    lh	$s2, 0($s0)
    lw	$s3, 0($s0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    li	$s0, 0x7010
    li	$s1, 10
    sw	$s1, 0($s0)
    li	$s0, 0x2800
    sw	$s1, 0($s0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    li	$s0, 0x12ab34cd
    sw	$s0, 3($0)
    sw	$s0, 4($0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    li	$s0, 0xabcd
    sh	$s0, 1($0)
    sh	$s0, 2($0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    li	$s0, 0x7f10
    li	$s1, 10
    sh	$s1, 0($s0)
    sw	$s1, 0($s0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    li	$s0, 0x7f10
    li	$s1, 10
    sw	$s1, 8($s0)
        #D_exc_AdEL
    and	$5, $s1, $s2
    
    
    li	$s0, 0x8ff0
    li	$s1, 100
    sh	$s1, 0($s0)
    sh	$s1, 0($s0)
       #D_exc_AdEL
    and	$5, $s1, $s2
  • testbench样例

    `timescale 1ns/1ps
    `include "mips.v"
    module mips_txt_int;
    
    	reg clk;
    	reg reset;
    	reg interrupt;
    
    	wire [31:0] macroscopic_pc;
    
    	wire [31:0] i_inst_addr;
    	wire [31:0] i_inst_rdata;
    
    	wire [31:0] m_data_addr;
    	wire [31:0] m_data_rdata;
    	wire [31:0] m_data_wdata;
    	wire [3 :0] m_data_byteen;
    
    	wire [31:0] m_inst_addr;
    
    	wire		w_grf_we;
    	wire [4 :0] w_grf_addr;
    	wire [31:0] w_grf_wdata;
    
    	wire [31:0] w_inst_addr;
    
    	mips uut(
    		.clk(clk),
    		.reset(reset),
    		.interrupt(interrupt),
    		.macroscopic_pc(macroscopic_pc),
    
    		.i_inst_addr(i_inst_addr),
    		.i_inst_rdata(i_inst_rdata),
    
    		.m_data_addr(m_data_addr),
    		.m_data_rdata(m_data_rdata),
    		.m_data_wdata(m_data_wdata),
    		.m_data_byteen(m_data_byteen),
    
    		.m_inst_addr(m_inst_addr),
    
    		.w_grf_we(w_grf_we),
    		.w_grf_addr(w_grf_addr),
    		.w_grf_wdata(w_grf_wdata),
    
    		.w_inst_addr(w_inst_addr)
    	);
    
    	initial begin
    		clk <= 0;
    		reset <= 1;
    		interrupt <= 0;
    		#20 reset <= 0;
    	end
    
    	integer i;
    	reg [31:0] fixed_addr;
    	reg [31:0] fixed_wdata;
    	reg [31:0] data[0:4095];
    	reg [31:0] inst[0:5119];
    
    	// ----------- For Instructions -----------
    
    	assign m_data_rdata = data[(m_data_addr >> 2) % 5120];
    	assign i_inst_rdata = inst[((i_inst_addr - 32'h3000) >> 2) % 5120];
    
    	initial begin
    		$readmemh("code.txt", inst);
    		for (i = 0; i < 5120; i = i + 1) data[i] <= 0;
    	end
    
    	// ----------- For Data Memory -----------
    
    	always @(*) begin
    		fixed_wdata = data[(m_data_addr >> 2) & 4095];
    		fixed_addr = m_data_addr & 32'hfffffffc;
    		if (m_data_byteen[3]) fixed_wdata[31:24] = m_data_wdata[31:24];
    		if (m_data_byteen[2]) fixed_wdata[23:16] = m_data_wdata[23:16];
    		if (m_data_byteen[1]) fixed_wdata[15: 8] = m_data_wdata[15: 8];
    		if (m_data_byteen[0]) fixed_wdata[7 : 0] = m_data_wdata[7 : 0];
    	end
    
    	always @(posedge clk) begin
    		if (reset) for (i = 0; i < 4096; i = i + 1) data[i] <= 0;
    		else if (|m_data_byteen && fixed_addr >> 2 < 4096) begin
    			data[fixed_addr >> 2] <= fixed_wdata;
    			$display("%d@%h: *%h <= %h", $time, m_inst_addr, fixed_addr, fixed_wdata);
    		end
    	end
    
    	// ----------- For Registers -----------
    
    	always @(posedge clk) begin
    		if (~reset) begin
    			if (w_grf_we && (w_grf_addr != 0)) begin
    				$display("%d@%h: $%d <= %h", $time, w_inst_addr, w_grf_addr, w_grf_wdata);
    			end
    		end
    	end
    
    	// ----------- For Interrupt -----------
    
    	wire [31:0] fixed_macroscopic_pc;
    
    	assign fixed_macroscopic_pc = macroscopic_pc & 32'hfffffffc;
    
    	always @(posedge clk) begin
    		if (reset) interrupt <= 0;
    	end
    
    	always @(negedge clk) begin
    		if (~reset && interrupt && |m_data_byteen) begin
    			if (fixed_addr == 32'h7F20) begin
    				interrupt <= 0;
    			end
    		end
    	end
    
    	reg [31:0] need_interrupt = 100;
    
    	always @(negedge clk) begin
    		if (~reset) begin
    			if (need_interrupt == 32'd100 && fixed_macroscopic_pc == 32'h3010) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd99 && fixed_macroscopic_pc == 32'h3014) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd98 && fixed_macroscopic_pc == 32'h3018) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd97 && fixed_macroscopic_pc == 32'h301c) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd96 && fixed_macroscopic_pc == 32'h3020) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd95 && fixed_macroscopic_pc == 32'h3024) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd94 && fixed_macroscopic_pc == 32'h3028) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd93 && fixed_macroscopic_pc == 32'h302c) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd92 && fixed_macroscopic_pc == 32'h3030) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd91 && fixed_macroscopic_pc == 32'h3034) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd90 && fixed_macroscopic_pc == 32'h3038) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd89 && fixed_macroscopic_pc == 32'h303c) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 2;
    			end
    			if (need_interrupt == 32'd87 && fixed_macroscopic_pc == 32'h3044) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd86 && fixed_macroscopic_pc == 32'h3048) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd85 && fixed_macroscopic_pc == 32'h304c) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
    			if (need_interrupt == 32'd84 && fixed_macroscopic_pc == 32'h3050) begin
    					interrupt <= 1;
    					need_interrupt <= need_interrupt - 1;
    			end
                //………………………………………………

思考题

  • Q:我们计组课程一本参考书目标题中有“硬件/软件接口”接口字样,那么到底什么是“硬件/软件接口”?(Tips:什么是接口?和我们到现在为止所学的有什么联系?)

    A: 硬件/软件接口是指软件和硬件之间数据交互的接口。在我看来,这个应该指的是操作系统,操作系统将外部软件程序的机器码传入core,由core执行;而core执行后产生的数据又通过操作系统传给软件。

  • Q:BE 部件对所有的外设都是必要的吗?

    A: 我认为没有必要,BE部件是为了实现DM按字节访存设置的,而其他的部件例如Timer仅仅支持按字访存,因此不需要BE部件。

  • Q:请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?

    A: 鼠标和键盘等外设并不是直接与CPU相连的,中间需要通过软件来连接,这个软件也就是我们熟知的驱动。驱动和硬件之间通过操作系统进行处理。

  • Q:请开发一个主程序以及定时器的 exception handler。整个系统完成如下功能:

    (1)定时器在主程序中被初始化为模式 0;

    (2)定时器倒计数至 0 产生中断;

    (3)handler 设置使能 Enable 为 1 从而再次启动定时器的计数器。(2) 及 (3) 被无限重复。

    (4)主程序在初始化时将定时器初始化为模式 0,设定初值寄存器的初值为某个值,如 100 或 1000。(注意,主程序可能需要涉及对 CP0.SR 的编程,推荐阅读过后文后再进行。)

    A:

    主程序:
    .text
    ori $t0, $0, 0xfc01
    mtc0 $t0, $12
    ori $t0, $0, 0x0
    sw $t0, 0x7f00
    ori $t0, $0, 288
    sw $t0, 0x7f04
    ori $t0, $0, 0x9
    sw $t0, 0x7f00
    addi $t1, $0, 1
    loop:
    add $s0, $0, $0
    add $s1, $0, $0
    add $s2, $0, $0
    add $s3, $0, $0
    j loop
    异常处理程序:
    .ktext 0x00004180
    ori $k0, $0, 0x1
    ori $k1, $0, 0x9
    sw $k0, 0x7f00
    sw $k1, 0x7f00
    eret

文章作者: Hyggge
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hyggge !
  目录