实验 4:MIPS 异常与中断

参考资料: MIPS 手册 Ⅲ

异常种类

实现以下几种类型的异常:

  • 读地址错和写地址错。
  • 溢出、break 指令和 syscall 指令(Execution Exceptions)。
  • 保留指令(Instruction Validity Exceptions)。
  • 中断。

中断包括软件中断和硬件中断。软件中断是由软件通过执行某些指令来产生的。硬件中断包括外部的中断(CPU 的接口 ext_int)和时钟中断。MIPS 支持 6 个硬件中断和 2 个软件中断。

CP0 寄存器

为了支持中断,MIPS 添加了协处理器(coprocessor)CP0。

CP0 是一组 32 位的寄存器,用 5 位 index 和 3 位 sel 来进行索引。本实验中只需实现 sel 为 0 的一些寄存器。

需要实现的寄存器:

  • BadVAddr(8 号寄存器,地址错异常时记录该虚拟地址)。
  • Count(9 号寄存器,是一个计时器,每两个时钟周期加一)。
    • 该寄存器是软件可写的,软件写入的优先级高于硬件自增。
  • Compare(11 号寄存器,和 Count 寄存器比较以产生时钟中断)。
    • 当 Compare 寄存器被设置过,且 Compare 和 Count 寄存器值相等时,产生时钟中断信号并保持。
    • 当 Compare 寄存器被软件修改时,清除时钟中断信号。
  • Status(12 号寄存器,记录处理器的运行状态)。
  • Cause(13 号寄存器,记录最近一次异常的原因)。
  • EPC(14 号寄存器,用于异常处理结束后的恢复)。

cp0.Causecp0.Status 有很多个 field,本实验中需要实现的 field 将在下文中提到,没提到的部分可实现为只读恒为 0。

CP0 寄存器不能作为算术指令的源寄存器和目的寄存器,软件需要通过 mfc0mtc0 来读取或写入它们。

CP0 寄存器的写入,包括两种情况:

  • 软件通过 mtc0 指令写入 CP0 寄存器。需要注意 CP0 寄存器和通用寄存器不同,有些寄存器不是每一位都允许软件进行写入,手册上的 R/W 权限描述了各个寄存器每一位的软件读写权限。
  • 硬件写入。

对于它们的优先级,请先用单周期 CPU 模型进行讨论,再思考五级流水线中的做法。

处理异常

异常的处理是由软件和硬件共同完成的。硬件主要处理异常检测与异常恢复。

异常检测

五级流水线的前四个阶段都可能产生异常,但不一定要在当个流水段就响应,可以产生一个异常使能信号,通过流水线寄存器传到 M 阶段再进行统一处理。

中断产生的条件包括:

  • cp0.Status.IE 为 1,全局硬件中断使能为有效。
  • cp0.Status.EXL 为 0,CPU 在执行异常处理程序时,不允许中断。
  • 中断源产生中断(包括 ext_int[5:0], cp0.Cause.IP[7:0] 和时钟中断),且对应的中断使能 cp0.Status.IM[7:0] 为有效。时钟中断对应的 mask 为 IM[7],外部硬件中断对应的 mask 为 IM[7:2]。可用以下代码来表示:interrupt_info = ({exception.ext_int, 2'b00} | cp0_cause.IP | {cp0.timer_interrupt, 7'b0}) & cp0_status.IM;

分析中断信号与各个异常信号,只要有一个为有效,则产生异常。检测到异常后,硬件需要做以下几件事:

  • 清空流水线,并把下一条指令的 PC 设置为 0xbfc00380。本实验中,这是统一的异常处理入口。
  • 根据异常优先级(手册 page 55~56),得到产生异常的原因,按照手册 138 的对应关系生成异常代码 exccode,写入 cp0.Cause.ExcCode。本实验需要实现的 code 包括:IntAdELAdESSysBPRIOv
  • 如果是地址错异常,将出错的虚拟地址写入 cp0.BadVAddr
  • 如果 cp0.Status.EXL 为 0,设置 cp0.EPCcp0.Cause.BD。如果产生异常的指令不在分支延迟槽中,将 cp0.EPC 设为该指令的 PC,并将 cp0.Cause.BD 设为 0;否则,将 cp0.EPC 设置为分支/跳转指令的 PC(即当前 PC 减 4),并将 cp0.Cause.BD 设为 1。
  • cp0.Status.EXL 设置为 1。

异常恢复

软件通过 eret 指令来恢复异常。

此时,硬件需要:

  • 清空流水线,并将下一条指令的 PC 置为 cp0.EPC
  • cp0.Status.EXL ← 0。

有些教科书上说,对于 syscall 类型的异常,当异常返回时,应该返回到下一条指令。在 MIPS 里,这件事情由软件完成(cp0.EPC 是软件可写的)。

添加指令

添加以下指令:

  • addaddisub(可能产生溢出异常)。
  • breaksyscall
  • mfc0mtc0(读取/写入 CP0 寄存器)。
  • eret

实验提交

18307130024/
├── report/   (报告所在目录)
├── source/   (源文件所在目录)
└── verilate/ (仿真代码所在目录)

zip -r 18307130024.zip 18307130024/ 打包。用 unzip 18307130024.zip 检查,应在当前目录下有学号目录。

通过标准

上板通过 test1~test5,并且 make verilate 编译通过。

test4 是异常与中断的测试,仅测试了所要求的异常与软件中断。

test5 包含了十个测试,通过调整开关来执行这十个测试。test5 的默认时钟频率是 50MHz。如果生成 bit 文件时产生时序违例(比如当你的除法器是单周期实现时),请调整时钟频率至 7MHz。助教在测试时会避开时序违例。

调试建议

test5 的十个测试很难用 Vivado 的仿真进行调试,可以用 Verilator 进行调试。不像 test1~test4,测试参数为 TEST=test1 这种,测试 test5 是对每个小测试运行一次 make vsim,参数为 TEST=sha 之类,各个测试的名字在 misc/nscscc 里可以查看到。这些测试没有 trace 文件,无法直接定位到寄存器写入错误的位置,出错了只能看波形图。

你也可以用 Verilator 运行一遍 func_test 测试,它集成了 test1~test4 的全部测试。

截止时间

2021 年 5 月 17 日 12:00