计算机组成CO:P7-MIPS微系统设计
产生异常
异常指的是内部异常和外部中断,前者是由于指令执行错误导致的“事件”,后者是由于外部设备信号导致的“事件”。为了响应异常,CPU 会自动跳转到某一特定地点(修改 PC 值),然后进行异常处理(由软件实现,不属于 MIPS 微系统)。
异常码 ExcCode
通过访问 CP0 的相应寄存器,可以获得 ExcCode 码(关于 CP0 之后再说),在本次 Project 中,需要实现的异常码有:
| 异常与中断码 | 助记符与名称 | 指令与指令类型 | 描述 |
|---|---|---|---|
| 0 | Int(外部中断) |
所有指令 | 中断请求,来源于计时器与外部中断。 |
| 4 | AdEL(取指异常) |
所有指令 | PC 地址未字对齐。 |
| 4 | AdEL(取指异常) |
所有指令 | PC 地址超过 0x3000 ~ 0x6ffc 。 |
| 4 | AdEL(取数异常) |
lw |
取数地址未与 4 字节对齐。 |
| 4 | AdEL(取数异常) |
lh |
取数地址未与 2 字节对齐。 |
| 4 | AdEL(取数异常) |
lh, lb |
取 Timer 寄存器的值。 |
| 4 | AdEL(取数异常) |
Load 型指令 | 计算地址时加法溢出。 |
| 4 | AdEL(取数异常) |
Load 型指令 | 取数地址超出 DM、Timer0、Timer1、中断发生器的范围。 |
| 5 | AdES(存数异常) |
sw |
存数地址未 4 字节对齐。 |
| 5 | AdES(存数异常) |
sh |
存数地址未 2 字节对齐。 |
| 5 | AdES(存数异常) |
sh, sb |
存 Timer 寄存器的值。 |
| 5 | AdES(存数异常) |
Store 型指令 | 计算地址加法溢出。 |
| 5 | AdES(存数异常) |
Store 型指令 | 向计时器的 Count 寄存器存值。 |
| 5 | AdES(存数异常) |
Store 型指令 | 存数地址超出 DM、Timer0、Timer1、中断发生器的范围。 |
| 8 | Syscall(系统调用) |
syscall |
系统调用。 |
| 10 | RI(未知指令) |
- | 未知的指令码。 |
| 12 | Ov(溢出异常) |
add, addi, sub |
算术溢出。 |
- 异常与中断码为
Int时,表示此时没有异常发生,仅考虑中断,因此在没有异常产生时,考虑给 CP0 传入Int,再根据中断信号判断中断,无需考虑异常; - 除以 0 是未定义行为,不算异常,即数据中不应该出现除以 0 的情况;
- 优先处理最早可以探查到的异常。
处理异常中断
在一个时间周期内,
- CP0 先检查现在是否处理异常中断,再检查 CP0 所在流水级指令是否异常,以及外部是否有未禁用的中断(如有,考虑中断处理而非异常);
- 如果现在没有处理异常中断,且 CP0 所在流水级存在异常或外部产生了未被禁用的中断,CP0 将向 CPU 发送“跳转到异常中断处理”的信号;
- CPU 接收到这个信号后,对 CP0 所在流水级及之前的流水级进行“清空”,让 PC 跳转到异常中断处理地址
0x4180,CP0 记录下处理完异常后需要返回的地址,以及出现异常的指令是否是延迟槽指令。
完成异常中断处理后,通过 eret 回到需要返回的地址(存在 CP0 中)。
其实CP0就是一个大号的处于M级的分支判断模块,实际上所有工作都是你原先设计的CPU在做,只是CP0这个东西可以帮你保存一些异常中断的状态信息(包含处理完之后回到哪一条指令),以及帮你判断当前时刻是否需要处理一个异常/中断而已。
CP0 (协处理器)的实现
CP0 的任务是对异常进行配置,以及记录异常的信息,因此需要一些寄存器(32 位):
| 寄存器 | 编号 | 功能 |
|---|---|---|
| SR | 12 | 配置异常的功能。 |
| Cause | 13 | 记录异常发生的原因和情况。 |
| EPC | 14 | 记录异常处理结束后需要返回的 PC 。 |
这些寄存器只有特定的位发挥作用:
| 寄存器 | 功能域 | 位域 | 解释 |
|---|---|---|---|
| SR(State Register) | IM(Interrupt Mask) | 15:10 | 分别对应六个外部中断,1 表示允许中断,0 表示禁止中断。只能通过 mtc0 这个指令修改,通过修改这个功能域来屏蔽一些中断。 |
| SR(State Register) | EXL(Exception Level) | 1 | 异常发生时置位,强制进入核心态(进入异常处理程序),禁止中断。 |
| SR(State Register) | IE(Interrupt Enable) | 0 | 全局中断使能,1 表示允许中断,0 表示禁止中断。 |
| Cause | BD(Branch Delay) | 31 | 置 1 表示 EPC 指向当前指令的前一条指令(一定为跳转),否则指向当前指令。 |
| Cause | IP(Interrupt Pending) | 15:10 | 6 位待决的中断位,分别对应 6 个外部中断,1 表示有中断,0 表示无中断,每个周期修改一次,修改的内容来自计时器和外部中断。 |
| Cause | ExcCode | 6:2 | 异常编码,记录当前发生什么异常。 |
| EPC | - | - | 记录异常处理结束后需要返回的 PC。 |
CP0 模块的端口定义如下:
| 端口 | 方向 | 位数 | 解释 |
|---|---|---|---|
| clk | I | 1 | 时钟信号 |
| reset | I | 1 | 复位信号 |
| en | I | 1 | 写使能信号 |
| CP0Add | I | 5 | 寄存器地址 |
| CP0In | I | 32 | CP0 写入数据 |
| CP0Out | O | 32 | CP0 读出数据 |
| VPC | I | 32 | 受害 PC |
| BDIn | I | 1 | 是否是延迟槽指令 |
| ExcCodeIn | I | 5 | 记录异常类型 |
| HWInt | I | 6 | 输入中断信号 |
| EXLClr | I | 1 | 复位 EXL |
| EPCOut | O | 32 | EPC 的值 |
| Req | O | 1 | 进入处理程序请求 |
读写状态寄存器
用来实现 mfc0, mtc0 。
CP0 的读写功能使用 en, CP0Add, CP0In, CP0Out 端口完成,仿照 GRF。
CP0 的三个状态寄存器仅有部分位有用,对于无用位初始化置 0,后续不对其操作。
进入异常中断处理程序
用来实现异常中断处理。VPC, BDIn, ExcCodeIn, HWInt 是用来判断是否进入异常中断处理程序的所有输入端口。
- CP0 先检查
EXL是否为 1 (正在处理异常中断),再检查ExcCodeIn是否不为 0 (当前是否存在异常)和HWInt & IM是否不为 0 且IE不为 0 (存在未被禁用的中断); - 如果现在
EXL为 0 ,并且ExcCodeIn不为 0 或“HWInt & IM不为 0 且IE不为 0”,那么将Req置 1 ,同时记录EPC, BD, EXL, ExcCode的值,否则置 0; - CPU 接收到
Req为 1 后,对 CP0 所在流水级及之前流水级清空(之后的流水级正常进行),让 PC 直接跳转到异常中断处理地址0x4180(这是软件决定的功能,即该地址之后的指令是外部写好的,用于处理异常)。
除此之外,只要没有 reset 信号,每个时钟周期都要记录一次 IP 。
这里的 VPC 接入的是“宏观 PC”,即 CPU 在外部表现为“一个有延迟槽的单周期 CPU”,每条指令只有两个状态:执行完了和没执行。由于 CP0 设置在了 M 级,异常处理由 M 级来做,因此宏观 PC 的值等于 M 级 PC 的值,也即是 VPC 的来源。(宏观 PC 具体在下面讲到)
CP0 记录什么值?
- 记录
ExcCode时,如果当前有中断,不应该直接记录ExcCodeIn,而是记录Int; - 当
BDIn为 1 时,说明发生异常中断的指令是延迟槽内的指令,让EPC记录VPC - 4,即延迟槽前一条指令(某个分支跳转指令),否则记录VPC; EXL置为 1 ;BD置为BDIn。
退出异常中断处理程序
用来实现 eret 。
该指令需要产生一个信号,将 EXLClr 置 1 ,此时在 CP0 中,EXL 修改为 0 .这样便解除了 EXL 记录的异常中断处理状态。
在 D 级判断,需要置位 PC。
封装成单周期 CPU
宏观 PC
以某一个流水级为界限,作为输出端口输出出来,一般为 CP0 所在的流水级(M 级)。
精确异常
精确指出哪一条指令导致了异常,称为异常受害指令。
清空流水线
在异常发生时,需要清空流水线,避免宏观 PC 之后的指令被执行。清空宏观 PC 之后的指令所在的流水线寄存器,即插入 nop 。
信号优先级如下:
| 信号 | 优先级 |
|---|---|
| reset | 最高,复位大于一切 |
| Req | 次高,中断请求比内部阻塞重要 |
| flush / Stall | 最低,流水线信号,外部人员看不到 |
确定 CP0 的位置
E、M 级都可以,这里采用 M 级。
流水异常码
异常信号 ExcCode 应该流水到 CP0 所在流水级,而不能直接提交到 CP0 。
写入 EPC
发生异常的重要行为是将中断指令的 PC 写入 EPC ,如果是延迟槽指令就写入 PC - 4,如果不是就写入 PC。这里的 PC 是宏观 PC ,在上面处理异常部分也提到了。
这样,返回的时候将重新执行异常指令。
异常处理程序
异常处理程序由软件实现,该项目只需要提供接口。
新指令
针对异常处理以及其他需求,需要增加几条指令:mfc0, mtc0, syscall, eret
mfc0:读 CP0 寄存器,mfc0 rt, rd;GPR[rt] <- CP0[rd] ;(COP0+rs)mtc0:写 CP0 寄存器,mtc0 rt, rd;CP0[rd] <- GPR[rt] ;(COP0+rs)syscall:系统调用,产生系统调用异常;(special+func)eret:异常返回,将保存在 CP0 的 EPC 寄存器中的现场(被中断的下一条地址)写入 PC,实现从异常中断处理程序返回。(COP0+func)
| 指令 | op5 | op4 | op3 | op2 | op1 | op0 |
|---|---|---|---|---|---|---|
| “COP0” | 0 | 1 | 0 | 0 | 0 | 0 |
| “special” | 0 | 0 | 0 | 0 | 0 | 0 |
| 指令 | rs4 | rs3 | rs2 | rs1 | rs0 |
|---|---|---|---|---|---|
| mfc0 | 0 | 0 | 0 | 0 | 0 |
| mtc0 | 0 | 0 | 1 | 0 | 0 |
| 指令 | func5 | func4 | func3 | func2 | func1 | func0 |
|---|---|---|---|---|---|---|
| syscall | 0 | 0 | 1 | 1 | 0 | 0 |
| eret | 0 | 1 | 1 | 0 | 0 | 0 |
外设的实现
外设是独立于之前实现的 CPU (P6)的部分,最后的架构应该是这样:

绿色虚线是已经实现的部分,紫色虚线是新增的部分,红色虚线为改变后的 DM 接口。
计时器(Timer)
计时器的主要功能是根据设定的时间来定时产生中断信号,是系统中断的来源之一。
课程组给出了标准实现代码:
1 |
|
中断发生器(InterruptGenerator)
这是本课程抽象简化现实外设后得到的一种外设(键盘、鼠标等的抽象),会在不确定的时刻产生中断信号,并持续置高,直到微系统做出响应,变回低位。
对中断发生器的响应通过系统桥实现,通过 Store 类指令访问地址 0x7F20 。
中断发生器在测试 tb 上实现,无需自己实现,只需要通过以下功能:
- 微系统可以通过外部端口接受外部中断信号(在计时器部分已经实现了)。
- 微系统可以通过访问地址
0x7F20的store类指令,改变对应的微系统输出信号(m_int_addr,m_int_byteen),即系统桥实现正确。
系统桥(Bridge)
系统桥传入对地址的访问请求,通过系统桥内部的转换代码,将请求转变为对应寄存器的读写操作。
思考题
1、请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?
鼠标和键盘产生中断信号,进入中断处理区的对应位置,将输入信号从鼠标和键盘中读入寄存器。
2、请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)
因为中断异常处理程序是独立于其他正常部分的,因此需要单独一块地址空间,避免错误访问。应该是可以的,但要保证接入地址仍然是 0x4180 ,即接口一致,且异常处理方式合理。
3、为何与外设通信需要 Bridge?
系统桥是连接 CPU 和外设的功能设备,它会给 CPU 提供一种接口,使得 CPU 可以像读写普通存储器一样(即按地址读写)来读写复杂多变的外设。系统桥统一且简化了 CPU 的对外接口,CPU 不必为每种外设单独提供接口,符合高内聚,低耦合的设计思想。
4、请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。
略()
5、倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?
6、为什么
jalr指令为什么不能写成jalr $31, $31?
测试数据(神中神!)
1 |
|
机器码:
1 | 34081c01 |
- Title: 计算机组成CO:P7-MIPS微系统设计
- Author: BaconToast
- Created at : 2025-11-18 11:20:49
- Updated at : 2025-11-18 12:54:43
- Link: https://bacontoast-pro.github.io/2025/11/18/co/p7/
- License: This work is licensed under CC BY-NC-SA 4.0.