实验二 中断🚁
完成单周期 CPU 实验后,你获得了一个可以简单的按照预期指令执行的处理器。但是这个简单的 CPU 只能按照预先的程序指令一直运行,无法中途打断。然而,我们生活的世界充满了不确定性,一个实用的 CPU 需要能够时刻准备好处理来自外部的事件,及时处理中断,并返回到原来的程序中继续执行。
在本实验中,你将学习到:
- CSR 寄存器以及其操作命令
- 中断控制器的原理和设计
- 编写一个简单的定时中断发生器
下面的内容中,不管使用 IDE 还是执行命令,根目录是 lab2
文件夹。
CSR指令支持🚁
从预备知识中断与异常我们已经学习到了 CSR 寄存器的基本概念。本实验里我们在单周期 CPU 的基础上增加对 CSR 寄存器的操作指令的支持。
CSR相关操作指令集的细节,包括指令的语义,编码等,都可以通过阅读非特权集手册的第九章获得。 中断相关的具体CSR寄存器的内容与对应含义,请查阅特权级手册
有了实现单周期 CPU 的经验,我们可以把对 CSR 指令的支持分解为:
- CSR 寄存器组:CSR 寄存器是一组类似于 RegisterFile 寄存器组的,地址空间大小为 4096 字节,独立编址的寄存器。 从指令手册可以看到对CSR寄存器的操作都是原子读写的,CSR指令具体的语义请查阅手册。 CSR寄存器组需要根据ID模块译码后给出的控制信号和CSR寄存器地址,来对内部寄存器进行寻址,获取其内容并且修改。
- ID 译码单元:ID 译码单元需要识别 CSR 指令,根据手册里描述的指令语义和编码规范,产生相应的传给其它模块的控制信号与数据。
- EX 执行单元:CSR 指令都是原子读写的,即一条指令的执行结果中,既要把目标 CSR 寄存器原来的内容写入到目标通用寄存器中,还要按指令语义把从目标 CSR 寄存器读出来的内容修改之后再写回给该CSR 寄存器。此时 EX 里面的 ALU 单元是空闲的,要得到写入 CSR 寄存器的值,可以复用 ALU,也可以不复用。
- WB 写回单元:支持 CSR 相关操作指令后,写回到目标通用寄存器的数据来源就多了一个从目标 CSR 寄存器读出来的修改前的值。
中断控制器(CLINT)🚁
CLINT(Core-Local Interrupt)是 RISC-V 架构中提供简单中断和定时器功能的中断处理器,其在中断或异常发生且中断开启时,将暂停CPU当前执行流,设置好相关 CSR 寄存器信息后跳转到中断处理程序中执行中断处理程序。
关键就是该保存哪些信息到对应的CSR寄存器中,答案就是 CPU 执行完当前指令后的下一个状态,比如当前指令是跳转指令,那么 mepc
保存的应该是当前跳转指令的跳转目标地址;以及一些关于中断发生原因的信息。
本实验中,我们希望实现一个支持基本功能的、简单的 CLINT,对于一些特殊情况我们规定:
- 中断到来时,我们认为应该让当前指令执行完后,再跳转到中断处理程序。
mstatus
的mie
位(machine interrupt enable)记录中断使能,即是否响应中断。如果当前正在执行一条指令以关闭中断使能,但此刻发生外部中断,则规定这种情况不应响应中断。- 我们不考虑嵌套中断的情况,即中断处理过程中忽略到来的中断。
- 仅考虑在 Machine 特权级下的情况。
接下来,我们讨论 CLINT 要处理的情况,包括 进入中断处理 和 完成中断处理。
进入中断处理🚁
1. 响应硬件中断🚁
此处将外部设备和定时器的中断归为一类讨论。响应硬件中断时,CPU的工作具体分为两部分:
保存现场,记录CPU当前正在执行程序的下一条指令地址,以便中断处理后恢复执行;以及记录中断信息和设置相应权限。这对应了下面 CSR 上的操作:
mepc
:保存的是中断或者异常处理完成后,CPU返回并开始执行的地址。所以对于异常和中断,mepc
的保存内容需要注意。mcause
:保存的是导致中断或者异常的原因,具体代码请查阅特权级手册。mstatus
:在响应中断时,需要将mstatus
寄存器中的MPIE
标志位设置为0
,禁用中断。
然后从 mtvec
获取中断处理程序的地址,跳转到该地址执行中断处理程序。
扩展知识:操作系统与硬件配合处理中断
事实上,中断的处理需要操作系统和硬件的紧密配合。在中断发生前,操作系统就要将中断处理程序写入内存并设置中断跳转向量到 mtvec
。
中断发生时,CPU保存现场并设置中断信息,操作系统还需保存进程上下文并进行资源调度等工作,完成中断处理也类似。
后续操作系统课程中你将对此有更全面深入的了解。
2. 响应软件中断🚁
ecall
和 ebreak
指令是触发软件中断的两条 environment 指令。ecall
常用于系统调用,例如请求磁盘读写,ebreak
更多用于调试。
详见非特权级手册第2章的 Environment Call and Breakpoints 。
这两条指令都将触发中断,CSR 的设置操作与响应硬件中断的相似。不同的是,mcause
的设置应参照 environment 相应的代码设置。
完成中断处理🚁
不论任何原因进入到中断处理程序后,都需要使用 mret
指令以恢复 CPU 原先程序的执行。
这时候其实干的事与响应中断是大致相同的,只不过需要写入的寄存器只有 mstatus
,跳转的目标地址则是从 mepc
获取。
为简单起见,对于 mret
时 mstatus
的要写入的值,就把 MIE 位置为 MPIE 位(Machine Previous Interrupt Enable)。若 MPIE 为 1 的话 mret
就会恢复中断,如果 MPIE 为 0 的话,mret
则不改变 mstatus
的值。
上述实现没有考虑嵌套中断,且以后实现特权级切换的时侯,mstatus
的改变更为复杂。中断的实现在手册中有明确的标准,想进一步了解中断机制的实现可以看特权级手册的 3.1.6.1 小节(Privilege and Global Interrupt-Enable Stack in mstatus register) 以及 9.6节(Traps)。
而异常的现场保存和恢复与中断处理差不多,只不过保存和恢复的内容上有所不同而已,感兴趣的同学可以自行摸索。
关于 CLINT 的实现🚁
CLINT 具体的实现方法很多,我们采用纯组合逻辑实现这个中断控制器。由于基于单周期 CPU 且 CLINT 是组合逻辑,所以外部中断到来时,CLINT 会马上响应。 目前 YatCPU 的主仓库的多周期CPU中的 CLINT 采用状态机来实现。
CLINT 需要一个周期就把多个寄存器的内容修改的功能,而正常的 CSR 指令只能对一个寄存器读-修改-写(Read-Modify-Write, RMW)。所以 CLINT 和 CSR 之间有独立的优先级更高的通路,用来快速更新 CSR 寄存器的值。
简单的定时中断发生器🚁
我们要实现一个 MMIO 的定时中断发生器——Timer。
MMIO (Memory Mapped I/O)简单来说就是:该外设用来和 CPU 交互的寄存器是与内存一起编址的,所以 CPU 可以通过访存指令(load/store)来修改这些寄存器的值,从而达到 CPU 和外设交互的目的。
目前在没有实现总线的情况下,使用多路选择器实现 MMIO 即可达到目的。原因主要是我们把取指令的操作和 load/store 访存操作分开了,让 Memory 有单独的一个通路进行取指令操作。 因此我们的模型还是一个 CPU 对多个外围设备,不会出现 CPU 的取指操作与访存操作冲突争抢外设的情况。
所以我们 CPU 发出的逻辑地址要发送到哪个设备,就由逻辑地址的高位作为外围设备的位选信号即可,低位则用于设备内部的寻址。
此外还有定时中断发生器的内部逻辑:两个控制寄存器 enable
寄存器和 limit
寄存器。
enable
寄存器用来控制定时中断发生器的使能,为false
则不产生中断, 映射到地址空间的逻辑地址为 0x80000008。limit
寄存器用来控制定时器的中断发生间隔,映射到地址空间的逻辑地址为 0x80000004。中断发生器内部有一个加一计数器,当计数器的值到达limit
为标准的界限时,定时器会发生一次中断信号(enable
使能情况下)。注:产生中断信号的时长没有太大关系,但是至少应该大于一个 CPU 时钟周期,确保 CPU 能够正确捕捉到该信号即可。
实验任务🚁
实验任务:支持中断处理
- 使 EX 单元支持 CSR 指令的运算。
- 使 CSR 寄存器组支持 CLINT 和来自 CSR 指令的读写操作。
- 使 CLINT 支持响应中断并且在中断结束后回到原来的执行流。
- 使定时中断发生器可以正确产生中断信号,并且实现 Timer 寄存器的 MMIO。
请在 // lab2(CLINTCSR)
注释处完成上述支持,并通过 CPUTest
、ExecuteTest
、CLINTCSRTest
、TimerTest
测试。
EX 执行单元的代码文件位于 src/main/scala/riscv/core/Execute.scala
CSR 寄存器组的代码文件位于 src/main/scala/riscv/core/CSR.scala
CLINT 的代码文件位于 src/main/scala/riscv/core/CLINT.scala
Timer 的代码位于 src/main/scala/peripheral/Timer.scala
Tip
IDEA 可以双击 shift,vscode 可以 shift+P 以打开文件快速搜索面板。
CPU架构图🚁
实验报告🚁
CLINTCSRTest.scala
中添加了 CLINT 处理硬件终端和软件中断的两个测试,请您选择至少一个,并:- 简述这个测试通过给部件输入什么信号,以测试 CLINT 的哪些功能?
- 在测试波形图上,找到一次从开始处理中断到中断处理完成的波形图,并挑选其中关键的信号说明其过程。例如硬件中断的测试中,有在跳转指令和非跳转指令下的两次中断处理测试;软件中断则分别测试了
ecall
和ebreak
两次中断,选择其中一次即可。
CPUTest.scala
中新增了SimpleTrapTest
,其执行csrc/simpletest.c
的程序。请您:- 简述该测试程序如何测试 CPU 的中断处理正确性。
- 在测试波形图上找出说明该程序成功执行的信号。
- 说明您在完成实验的过程中,遇到的实验指导不足或改进建议。