tinyriscv学习记录之五

二十六、clint.v的学习

阅读代码,熟悉的感觉又出现了:反复咀嚼,难以下咽。和阅读div.v的代码时候一样,看代码找不到突破口的话,那么突破口就是不熟悉试商法的原理,我想在clint.v也是一样。如果清楚中断处理的过程,那么clint.v的代码也就能看明白。所以,还是先搞懂中断处理的流程,弄清需要中断需要做哪些事。

二十七、先知道csr寄存器

在RISC-V 这种架构里,中断/异常处理和 CSR 寄存器分不开的。

原因很简单:中断处理至少要解决 4 件事,而这 4 件事恰好都要靠 CSR 来完成:

  • 记录从哪里回来mepc
  • 记录为什么进中断mcause
  • 记录/修改中断使能状态mstatus
  • 决定中断处理程序入口地址mtvec

也就是说,CPU 一旦进入中断,不能只是“跳走”这么简单,还必须:

  1. 保存现场
  2. 记录原因
  3. 关闭或管理中断使能
  4. 跳到统一的处理入口
  5. 处理完再按保存的信息返回

而这些信息的标准存放位置,就是 CSR。

二十八、重要的CSR寄存器

CSR 名称全称功能
mtvecMachine Trap-Vector Base Address保存中断/异常入口地址,进入 trap 后跳到这里
mepcMachine Exception Program Counter保存异常/中断发生时的返回地址,mret 时跳回这里
mstatusMachine Status Register控制和保存中断使能状态,进入 trap 时关闭中断,返回时恢复
mcauseMachine 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_ASSERT
  • int_flag_i != INT_NONEglobal_int_en_i == True → 异步中断 S_INT_ASYNC_ASSERT
  • mret → 中断返回 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 设成对应异常码,比如

      • ecall11
      • ebreak3
  • 对异步中断:

    • cause <= 32'h80000004
  • 同时算好 inst_addr,也就是将来写进 mepc 的返回地址

这里为什么要算 inst_addr? 因为中断发生时,当前流水线可能正遇到:

  • 普通顺序执行
  • 跳转指令
  • 多周期除法

所以作者要修正“真正该保存哪个 PC”。


第 2 拍:写 mepc

状态转到:S_CSR_MEPC

这时输出:

  • we_o = WriteEnable
  • waddr_o = CSR_MEPC
  • data_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_MCAUSE
  • data_o = cause

作用:

  • 记录这次 trap 的原因

    • ecall
    • ebreak
    • 还是异步中断

第 5 拍:正式通知 EX 跳转到中断入口

还是在 S_CSR_MCAUSE 这个阶段,clint 会同时发:

  • int_assert_o = INT_ASSERT
  • int_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 操作顺序是:

  1. mepc
    保存中断/异常发生时的返回地址
  2. mstatus
    关闭全局中断,防止处理中再被打断
  3. mcause
    记录本次 trap 的原因
  4. 然后才通知跳转到 mtvec

也就是说,实际顺序是:

先保存返回地址 -> 再关中断 -> 再记录原因 -> 最后跳去中断入口

三十一、顺序是:先保存返回地址 -> 再关中断 -> 再记录原因 -> 最后跳去中断入口。操作系统中是先关中断,为什么这里不是呢?不能先关中断再保存返回地址,再记录原因?

先关中断这种思路的重点是:

先把门关上,再整理现场

适合这种情况:

  • 硬件能自动抓住异常现场
  • 或设计上最怕 trap 期间再次被打断
  • 或有更底层机制保证返回地址不会丢

然而,

“先保存返回地址,再关中断”之所以在 clint.v 里更自然,是因为:

  • mepc 是最关键的现场,关系到能不能正确返回
  • mepc 还需要根据当前流水线状态修正计算,不是现成值
  • 这个实现已经用 hold_flag_o 先稳住了流程,所以不必把“立刻关中断”放在第一位
  • 因此作者就按 先保现场、再改状态、再记原因 的顺序来写
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值