二十六、clint.v的学习
阅读代码,熟悉的感觉又出现了:反复咀嚼,难以下咽。和阅读div.v的代码时候一样,看代码找不到突破口的话,那么突破口就是不熟悉试商法的原理,我想在clint.v也是一样。如果清楚中断处理的过程,那么clint.v的代码也就能看明白。所以,还是先搞懂中断处理的流程,弄清需要中断需要做哪些事。
二十七、先知道csr寄存器
在RISC-V 这种架构里,中断/异常处理和 CSR 寄存器分不开的。
原因很简单:中断处理至少要解决 4 件事,而这 4 件事恰好都要靠 CSR 来完成:
- 记录从哪里回来 →
mepc - 记录为什么进中断 →
mcause - 记录/修改中断使能状态 →
mstatus - 决定中断处理程序入口地址 →
mtvec
也就是说,CPU 一旦进入中断,不能只是“跳走”这么简单,还必须:
- 保存现场
- 记录原因
- 关闭或管理中断使能
- 跳到统一的处理入口
- 处理完再按保存的信息返回
而这些信息的标准存放位置,就是 CSR。
二十八、重要的CSR寄存器
| CSR 名称 | 全称 | 功能 |
|---|---|---|
mtvec | Machine Trap-Vector Base Address | 保存中断/异常入口地址,进入 trap 后跳到这里 |
mepc | Machine Exception Program Counter | 保存异常/中断发生时的返回地址,mret 时跳回这里 |
mstatus | Machine Status Register | 控制和保存中断使能状态,进入 trap 时关闭中断,返回时恢复 |
mcause | Machine Cause Register | 记录本次异常/中断的原因 |
二十九、clint.v 的中断处理过程就是:
先识别 trap 类型,暂停流水线,分几个时钟周期依次写 mepc/mstatus/mcause 保存现场,之后再让 ex 跳到 mtvec;执行 mret 时则恢复 mstatus,并让 ex 跳回 mepc。
三十、clint.v代码分成4步
1. 先判断“是不是要处理 trap”
clint 先根据当前输入判断 int_state:
ecall / ebreak→ 同步异常S_INT_SYNC_ASSERTint_flag_i != INT_NONE且global_int_en_i == True→ 异步中断S_INT_ASYNC_ASSERTmret→ 中断返回S_INT_MRET- 否则 →
S_INT_IDLE
也就是说,第一步只是分类:
- 现在是同步异常?
- 异步中断?
- 还是从中断返回?
2. 一旦开始处理,就先暂停流水线
assign hold_flag_o = ((int_state != S_INT_IDLE) | (csr_state != S_CSR_IDLE)) ? `HoldEnable : `HoldDisable;
意思是:
- 只要检测到中断/异常
- 或者 CSR 处理流程还没结束
- 就拉高
hold_flag_o
作用:
- 防止流水线继续往前跑
- 给
clint腾出几个时钟周期,依次写mepc/mstatus/mcause
所以 clint 不是一发现中断就马上跳走,而是先 冻结现场。
3. 进入中断时,分几拍保存现场
这部分由 csr_state 控制。
情况 A:同步异常或异步中断
当 csr_state == S_CSR_IDLE 时,如果发现要进中断:
第 1 拍:准备异常原因和返回地址
-
对同步异常:
-
cause设成对应异常码,比如ecall→11ebreak→3
-
-
对异步中断:
cause <= 32'h80000004
-
同时算好
inst_addr,也就是将来写进mepc的返回地址
这里为什么要算 inst_addr? 因为中断发生时,当前流水线可能正遇到:
- 普通顺序执行
- 跳转指令
- 多周期除法
所以作者要修正“真正该保存哪个 PC”。
第 2 拍:写 mepc
状态转到:S_CSR_MEPC
这时输出:
we_o = WriteEnablewaddr_o = CSR_MEPCdata_o = inst_addr
作用:
- 把将来返回时要继续执行的位置保存到
mepc
第 3 拍:写 mstatus
状态转到:S_CSR_MSTATUS
这时写 mstatus,把全局中断关掉:
data_o <= {csr_mstatus[31:4], 1'b0, csr_mstatus[2:0]};
意思是:
- 清掉中断使能位
- 防止处理当前 trap 时又被新的中断打断
第 4 拍:写 mcause
状态转到:S_CSR_MCAUSE
这时输出:
waddr_o = CSR_MCAUSEdata_o = cause
作用:
-
记录这次 trap 的原因
- 是
ecall - 是
ebreak - 还是异步中断
- 是
第 5 拍:正式通知 EX 跳转到中断入口
还是在 S_CSR_MCAUSE 这个阶段,clint 会同时发:
int_assert_o = INT_ASSERTint_addr_o = csr_mtvec
意思是:
- 现在现场已经保存完了
- 可以让
ex去把 PC 改到mtvec - 开始执行中断/异常处理程序
这点很关键:
不是刚检测到中断就跳,而是先写完关键 CSR,再跳到
mtvec。
这样 trap 语义才完整。
4. 中断返回 mret 的过程
如果当前指令是 mret,int_state = S_INT_MRET。
这时不会走 mepc -> mstatus -> mcause 这一套进入中断流程, 而是进入:S_CSR_MSTATUS_MRET
返回时做两件事:
第 1 件:恢复 mstatus
data_o <= {csr_mstatus[31:4], csr_mstatus[7], csr_mstatus[2:0]};
意思是:
把中断前保存/对应的使能状态恢复回来
第 2 件:通知 EX 跳回 mepc
同一阶段输出:
int_assert_o = INT_ASSERT
int_addr_o = csr_mepc
意思是:
不再跳去 mtvec
而是跳回之前保存的返回地址 mepc
这样中断处理程序执行完后,CPU 就能回到原程序继续运行。
三十、csr_state 的状态推进顺序为什么是这样的?
状态流转是:
S_CSR_IDLE -> S_CSR_MEPC -> S_CSR_MSTATUS -> S_CSR_MCAUSE -> S_CSR_IDLE
对应的 CSR 操作顺序是:
- 写
mepc
保存中断/异常发生时的返回地址 - 写
mstatus
关闭全局中断,防止处理中再被打断 - 写
mcause
记录本次 trap 的原因 - 然后才通知跳转到
mtvec
也就是说,实际顺序是:
先保存返回地址 -> 再关中断 -> 再记录原因 -> 最后跳去中断入口
三十一、顺序是:先保存返回地址 -> 再关中断 -> 再记录原因 -> 最后跳去中断入口。操作系统中是先关中断,为什么这里不是呢?不能先关中断再保存返回地址,再记录原因?
先关中断这种思路的重点是:
先把门关上,再整理现场
适合这种情况:
- 硬件能自动抓住异常现场
- 或设计上最怕 trap 期间再次被打断
- 或有更底层机制保证返回地址不会丢
然而,
“先保存返回地址,再关中断”之所以在 clint.v 里更自然,是因为:
mepc是最关键的现场,关系到能不能正确返回mepc还需要根据当前流水线状态修正计算,不是现成值- 这个实现已经用
hold_flag_o先稳住了流程,所以不必把“立刻关中断”放在第一位 - 因此作者就按 先保现场、再改状态、再记原因 的顺序来写
2267

被折叠的 条评论
为什么被折叠?



