tinyrisc-v学习笔记(五.代码中断)

中断一.

首先对于介绍中断和异常的区别,个人感觉看一下就好。
中断(Interrupt) 机制:即处理器核在顺序执行程序指令流的过程中突然被别的请求打断而中止执行当前的程序,转而去处理别的事情,待其处理完了别的事情,然后重新回到之前程序中断的点继续执行之前的程序指令流
异常(Exception)机制:即处理器核在顺序执行程序指令流的过程中突然遇到了异常的事情而中止执行当前的程序,转而去处理该异常。同步异常:是指令异常,可以定位到具有的指令。异步异常:不能定位到具体的指令。
中断和异常的区别:异常一般是由于处理器内部事件或者程序执行中的事件引起的。
在广义上讲:中断和异常都被称为异常。
其实说白了,就是一个会回到原来的异常,一个不会回到原来的异常。

RISC-V架构异常处理机制:
当前RISCV架构文档主要分为“指令集文档”和“特权架构文档”。RISC-V架构的异常处理机制定义在“特权架构文档”中。RISC-V的架构不仅可以有机器模式(Machine Mode)的工作模式,还可以有用户模式(User Mode)、监督模式(Supervisor Mode)等工作模式。在不同的模式下均可以产生异常,并且有的模式也可以响应中断。RISC-V架构要求机器模式是必须具备的模式,其他的模式均是可选而非必选的模式。
中断,跳转,暂停之间的区别:中断和跳转都是触发暂停的原因。中断操作是为了提高程序的执行效率。中断或者for循环中,都会有跳转操作。
• 退出中断:使用中断返回指令MRET,在机器模式下必备。
中断类型:
外部中断:外设发生的中断,发生在处理核外部的中断。
计时器中断(外部中断中的一种):用mie寄存器中的mtie域进行控制。
软件中断:来自软件(C语言等软件语言)自己触发的中断。
调试中断:Dubug时的中断。
中断屏蔽:通过MIE寄存器,来控制不同类型的中断使能和屏蔽(外部中断、计时器中断、软件中断)。
上面的几个中断我们暂时不用很刻意的去记,看到过一下就好了。
Csr_reg.v(时序逻辑电路)的主要功能:
1.里面有一个64位的计数器,每走一个周期他就加1。
2.写寄存器中地址的后12位,把ex和clint模块中的数据寄存在csr寄存器中
3.如果读寄存器地址来自于ID模块,把寄存器中读到的数据,送到id模块(读寄存器后12位)。
如果读寄存器的地址来自中断clint模块,并且从寄存器读到数据,送给clint(读寄存器后12位)
其实我们要理解,其实这里的jump也好,hold也好。他们也是像其他指令一样,经过一个流程在经过id,ex等模块得到执行的指令才会有的。(浅显的理解,可能不对。这个和普通指令不同的就是他是结合clint,csr_reg进行处理。而普通的是结合regs处理)
首先我们搞清楚,对于ex和clint给的信息中。ex给的信息的优先级高。
所以当同时给了指令的时候,我们先看ex的。csr根据写入的地址(后12位)判断写的是要mtvec(发生异常处理器要跳转的地址),还是mcause(中断原因),mepc(发生异常时的地址),当ex不执行,看clint是否执行,进行的操作的ex是一样的。
之后,我们根据读地址(低12)判断我们要的输出是什么?同样ex优先级优先。可以由图23得出判断
在这里插入图片描述
图23
可以看到其实是一个循环,和regs一样,写入的地址以及内容被读取。之后再被写入读取,以此反复。
下一步clint.v
代码的作用是,首先明白这个点是什么中断,是同步中断(ECALL,EBREAK)还是异步中断(外设)。判断好后,先暂停整条流水线,设置跳转地址为中断入口地址,然后读、写必要的CSR寄存器(mstatus、mepc、mcause等),等读写完这些CSR寄存器后取消流水线暂停,这样处理器就可以从中断入口地址开始取指,进入中断服务程序。
程序:
1.中断仲裁(组合逻辑):同步中断>异步中断>中断返回
同步中断:如果执行阶段的指令为除法指令,则先不处理同步中断,等除法指令执行完再处理。
异步中断:定时器中断(外设中断)和全局中断使能(mstatus[3])打开时,触发异步中断。
中断返回:中断返回指令。
2.CSR寄存器状态机跳转,提取中断返回的地址和引起中断的编码,以及CSR寄存器状态跳转。
3.写CSR寄存器(mstatus、mepc、mcause)
先写中断返回地址mepc
再写mstatus,以关闭全局中断(mstatus[3]=0)
将中断异常编码写入mcause寄存器
中断返回,返回的同时需要将全局中断位恢复(mstatus[3]=mstatus[7])
4.发送中断信号给执行模块(将csr寄存器写完之后,才可以发送中断信号给执行模块)
inst_i:判断是否是同步中断。
inst_addr_i:当前指令的地址。
inst_flag_i:计时器中断的中断flag信号。
int_assert_o:中断有效信号,信号为1时,开始运行中断处理程序。
上述中可能有不太清晰的地方,其实这其中解释也在代码中分步:
即首先在中断仲裁:判断什么中断,对于不同中断采取不同的措施之后确定中断的状态。
随后我们要更换csr寄存器的状态,结合上述csr_reg的代码相互看,我们根据当前的csr_state判断要进行什么操作。
同步中断的时候:写入异常的原因,以及出现jump时,我们的inst地址要倒回上一个,如果没有jump则inst地址不变。
同时我们看到csr_state状态的里各个状态中S_CSR_MEPC是在定时器中断中有的,经过两个周期S_CSR_MSTATUS和S_CSR_MCAUSE后返回初始S_CSR_IDLE状态。
并且我们说到的在发出中断信号前,要写入几个CSR寄存器,其中写入的内容也是根据csr_state来判断的,这里(clint.v代码中的176-240行结合与csr_reg中115-142以及184-218。)也就呼应了csr_reg中的代码内容。这里比如出现S_CSR_MEPC则开始写使能,然后waddr_o写地址[11:0]内容也是MEPC,data_o为inst_addr。
这里的输出也自然回到了csr_reg做输入,之后又写中断的原因,然后又写入了csr_reg,再关闭中断,再到中断返回到中断的时候的地址。
上述步骤执行完了之后才开始,我们将信号发给ex,告诉他在哪里中断,中断返回的地 址在哪,中断需要跳转的地址是在哪。这个算是一个闭路循环。
Ctrl.v
这个就比较的简单,跳转就是改变PC寄存器的值。又因为跳转与否需要在执行阶段才知道,所以当需要跳转时,则需要暂停流水线(正确来说是冲刷流水线。流水线是不可以暂停的,除非时钟不跑了)。
冲刷流水线指的是,在指令中流淌的是NOP的指令。图24参考
在这里插入图片描述
图24
1.其中长方形表示的是时序逻辑电路,云状型表示的是组合逻辑电路。过程如下:
2.在执行阶段,当判断需要发生跳转时,发出跳转信号和跳转地址给ctrl(ctrl.v)模块。ctrl模块判断跳转信号有效后会给pc_reg、if_id和id_ex模块发出流水线暂停信号,并且还会给pc_reg模块发出跳转地址。
3.在时钟上升沿到来时,if_id和id_ex模块如果检测到流水线暂停信号有效则送出NOP指令,从而使得整条流水线(译码阶段、执行阶段)流淌的都是NOP指令,已经取出的指令就会无效,这就是流水线冲刷机制。
ctrl我认为就是当有hold或者jump指令了,从ctrl这里发出给前面pc_reg,if_id,id_ex等

其实整体来说中断就是从if_id开始给出中断的指令和地址,然后经过ex发往csr_regs寄存器,csr_regs出现异常。同时clint也会收到收到inst确定是什么中断,写中断类型。同样写出的状态会与ex交互。我个人感觉clint与csr_regs和ex与csr_regs不同的是,处理的流程不同了。clint直接通过译码识别出来中断,然而ex是经过某些运算才得出中断。(理解如果有错,还请指出)

最后单独介绍一下通用寄存器,我的观点就是他是整个系统的辅助工具,没有它的话系统无法运转。
RISC-V通用寄存器
RISC-V架构支持32位或者64位的架构,32 位架构由RV32表示,其每个通用寄存器的宽度为32比特; 64位架构由RV64表示,其每个通用寄存器的宽度为64比特。
RISC-V架构的整数通用寄存器组,包含32个(I 架构)或者16个(E架构)通用整数寄存器,其中整数寄存器0被预留为常数0,其他的31个(I架构)或者15个(E架构)为普通的通用整数寄存器。
在流水线中能够尽快地读取通用寄存器组,往往是处理器流水线设计的期望之一,这样可以提高处理器性能和优化时序。这个看似简单的道理在很多现存的商用RISC架构中都难以实现,因为经过多年反复修改不断添加新指令后,其指令编码中的寄存器索引位置变得非常凌乱,给译码器造成了负担。
Reg.v代码解析:系统定义的是一个32位宽和32位深度的寄存器。
[32]regs[0:31]。
写寄存器:将ex,jtag中数据寄存在regs中;
读寄存器:读寄存器地址来自jtag,将寄存器读到的数据送给Jtag。
读寄存器的地址来自译码id模块,并将从寄存器中读到的数据,送给译码id模块(regs)。
注意:由于流水线的原因,当前指令处于执行阶段的时候,下一条指令则处于译码阶段。由于执行阶段不会写寄存器,而是在下一个时钟到来时才会进行寄存器写操作,如果译码阶段的指令需要上一条指令的结果,那么此时读到的寄存器的值是错误的。比如下面这两条指令:add x1, x2, x3 、add x4, x1, x5 第二条指令依赖于第一条指令的结果。为了解决这个问题,如果读寄存器等于写寄存器,则直接将要写的值返回给读操作。
这里我的理解是:因为时钟原因,指令与内容的流程是从if_id→id→id_ex→ex,ex和id都是组合逻辑电路也就是获得的值都为即时的,也就是别的模块生成他们就能获得,然而id_ex和if_id都是时序电路,即使当ex计算出来得到的指令,写入regs,也是需要时序的。所以当我们需要上条指令的操作时,我们当前的获取值不一定是对的,比如当ex生成的地址是100准备写入时,写地址的与读地址都是regs[100]时,我们需要等一个时钟,才会写入,然而如果我们没有等到时钟,直接读取时则会出现读取错误,所以当读写地址都一样时,我们加一条指令让其不经过地址写入在读取,直接使读内容等于写内容。如图25代码


图25

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数字IC佛耶戈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值