mirror of
https://github.com/handsomezhuzhu/2025-yatcpu.git
synced 2026-02-20 20:10:14 +00:00
Migrated and updated lab1 and lab2 tutorials, etc
- update to two tutorials - added environment and cmd tutorial - updated lab1 CPU arch graph
This commit is contained in:
178
docs/docs/better-tut/labs/lab2/lab2-interrupt.md
Normal file
178
docs/docs/better-tut/labs/lab2/lab2-interrupt.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# 实验二 中断
|
||||
|
||||
[//]: # (完成流水线 CPU 实验后,你就已经对基于流水线 CPU 的原理和设计有初步认识了。但是这个简单的 CPU 只能按照预先的程序指令一直运行,无法中途打断。然而,我们生活的世界充满了不确定性,一个实用的 CPU 需要能够时刻准备好处理来自外部的事件,及时处理中断,并返回到原来的程序中继续执行。)
|
||||
|
||||
完成单周期 CPU 实验后,你获得了一个可以简单的按照预期指令执行的处理器。但是这个简单的 CPU 只能按照预先的程序指令一直运行,无法中途打断。然而,我们生活的世界充满了不确定性,一个实用的 CPU 需要能够时刻准备好处理来自外部的事件,及时处理中断,并返回到原来的程序中继续执行。
|
||||
|
||||
在本实验中,你将学习到:
|
||||
|
||||
- CSR 寄存器以及其操作命令
|
||||
- 中断控制器的原理和设计
|
||||
- 编写一个简单的定时中断发生器
|
||||
|
||||
下面的内容中,不管使用 IDE 还是执行命令,根目录是 `lab2` 文件夹。
|
||||
|
||||
## CSR指令支持
|
||||
|
||||
从预备知识[中断与异常](../../theory/interrupt-and-exception.md)我们已经学习到了 CSR 寄存器的基本概念。本实验里我们在单周期 CPU 的基础上增加对 CSR 寄存器的操作指令的支持。
|
||||
|
||||
CSR相关操作指令集的细节,包括指令的语义,编码等,都可以通过阅读[非特权集手册](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf)的第九章获得。
|
||||
中断相关的具体CSR寄存器的内容与对应含义,请查阅[特权级手册](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf)
|
||||
|
||||
有了实现单周期 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,对于一些特殊情况我们规定:
|
||||
|
||||
1. 中断到来时,我们认为应该让当前指令执行完后,再跳转到中断处理程序。
|
||||
2. `mstatus` 的 `mie`位(machine interrupt enable)记录中断使能,即是否响应中断。如果当前正在执行一条指令以关闭中断使能,但此刻发生外部中断,则规定这种情况不应响应中断。
|
||||
3. 我们不考虑嵌套中断的情况,即中断处理过程中忽略到来的中断。
|
||||
4. 仅考虑在 Machine 特权级下的情况。
|
||||
|
||||
<!-- 还有一些特殊情况。我们知道外部中断使能由 `mstatus` 内容决定,那么要思考如果当前指令如果是修改 `mstatus` 执行结果是关中断的指令执行时,外部中断到来了,那么下个周期是否应该响应中断?
|
||||
为了统一起见,我们认为这种情况不应该响应中断,并且我们认为应该让当前指令执行完后,再跳转到中断处理程序。 -->
|
||||
|
||||
|
||||
接下来,我们讨论 CLINT 要处理的情况,包括 进入中断处理 和 完成中断处理。
|
||||
|
||||
|
||||
### 进入中断处理
|
||||
|
||||
#### 1. 响应硬件中断
|
||||
|
||||
此处将外部设备和定时器的中断归为一类讨论。响应硬件中断时,CPU的工作具体分为两部分:
|
||||
|
||||
保存现场,记录CPU当前正在执行程序的下一条指令地址,以便中断处理后恢复执行;以及记录中断信息和设置相应权限。这对应了下面 CSR 上的操作:
|
||||
|
||||
- `mepc`:保存的是中断或者异常处理完成后,CPU返回并开始执行的地址。所以对于异常和中断,`mepc` 的保存内容需要注意。
|
||||
- `mcause`:保存的是导致中断或者异常的原因,具体代码请查阅特权级手册。
|
||||
- `mstatus`:在响应中断时,需要将 `mstatus` 寄存器中的 `MPIE` 标志位设置为 `0`,禁用中断。
|
||||
|
||||
然后从 `mtvec` 获取中断处理程序的地址,跳转到该地址执行中断处理程序。
|
||||
|
||||
|
||||
|
||||
!!! info "扩展知识:操作系统与硬件配合处理中断"
|
||||
事实上,中断的处理需要操作系统和硬件的紧密配合。在中断发生前,操作系统就要将中断处理程序写入内存并设置中断跳转向量到 `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 能够正确捕捉到该信号即可。
|
||||
|
||||
## 实验任务
|
||||
|
||||
!!! note "实验任务:支持中断处理"
|
||||
|
||||
1. 使 EX 单元支持 CSR 指令的运算。
|
||||
2. 使 CSR 寄存器组支持 CLINT 和来自 CSR 指令的读写操作。
|
||||
3. 使 CLINT 支持响应中断并且在中断结束后回到原来的执行流。
|
||||
4. 使定时中断发生器可以正确产生中断信号,并且实现 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/riscv/peripheral/Timer.scala`
|
||||
|
||||
!!! tip
|
||||
IDEA 可以双击 shift,vscode 可以 shift+P 以打开文件快速搜索面板。
|
||||
|
||||
<!-- 在上面提到的 EX、CSR、CLINT、Timer 四个单元的相应文件里面,请在 `// lab2(CLINTCSR)` 注释处填入相应的代码,使其能够通过 `CPUTest`、`ExecuteTest`、`CLINTCSRTest`、`TimerTest` 测试。 -->
|
||||
|
||||
<!-- 如果能够正确完成本次实验,那么你的 CPU 就可以运行更加复杂的程序了,可以运行一下俄罗斯方块程序试试,如果想要上手玩的话,也许需要一个串口转接板,这样就可以通过电脑的键盘通过 UART 串口给程序输入字符了。 -->
|
||||
|
||||
***
|
||||
|
||||
## 实验报告
|
||||
|
||||
|
||||
1. `CLINTCSRTest.scala` 中添加了 CLINT 处理硬件终端和软件中断的两个测试,请您选择至少一个,并:
|
||||
1. 简述这个测试通过给部件输入什么信号,以测试 CLINT 的哪些功能?
|
||||
2. 在测试波形图上,找到一次从开始处理中断到中断处理完成的波形图,并挑选其中关键的信号说明其过程。例如硬件中断的测试中,有在跳转指令和非跳转指令下的两次中断处理测试;软件测试则分别测试了 `ecall` 和 `ebreak` 两次中断,选择其中一次即可。
|
||||
2. `CPUTest.scala` 中新增了 `SimpleTrapTest`,其执行 `csrc/simpletest.c` 的程序。请您:
|
||||
1. 简述该测试程序如何测试 CPU 的中断处理正确性。
|
||||
2. 在测试波形图上找出说明该程序成功执行的信号。
|
||||
3. 说明您在完成实验的过程中,遇到的实验指导不足或改进建议。
|
||||
|
||||
## CPU架构图
|
||||
|
||||

|
||||
Reference in New Issue
Block a user