4.10 中断 中断(中断返回)本质上也是一种跳转,只不过还需要附加一些读写CSR寄存器的操作。 RISC-V中断分为两种类型,一种是同步中断,即ECALL、EBREAK等指令所产生的中断,另一种是异步中断,即GPIO、UART等外设产生的中断。 对于中断模块设计,一种简单的方法就是当检测到中断(中断返回)信号时,先暂停整条流水线,设置跳转地址为中断入口地址,然后读、写必要的CSR寄存器(mstatus、mepc、mcause等),等读写完这些CSR寄存器后取消流水线暂停,这样处理器就可以从中断入口地址开始取指,进入中断服务程序。 下面看tinyriscv的中断是如何设计的。中断模块所在文件:rtl/core/clint.v 输入输出信号列表如下:
先看中断模块是怎样判断有中断信号产生的,如下代码:
第3~4行,复位后的状态,默认没有中断要处理。 第6~7行,判断当前指令是否是ECALL或者EBREAK指令,如果是则设置中断状态为S_INT_SYNC_ASSERT,表示有同步中断要处理。 第8~9行,判断是否有外设中断信号产生,如果是则设置中断状态为S_INT_ASYNC_ASSERT,表示有异步中断要处理。 第10~11行,判断当前指令是否是MRET指令,MRET指令是中断返回指令。如果是,则设置中断状态为S_INT_MRET。 下面就根据当前的中断状态做不同处理(读写不同的CSR寄存器),代码如下:
第1023行,当CSR处于S_CSR_IDLE时,如果中断状态为S_INT_SYNC_ASSERT,则在第11行将CSR状态设置为S_CSR_MEPC,在第12行将当前指令地址保存下来。 在第1323行,根据不同的指令类型,设置不同的中断码(Exception Code),这样在中断服务程序里就可以知道当前中断发生的原因了。 第24~28行,目前tinyriscv只支持定时器这个外设中断。 第30~31行,如果是中断返回指令,则设置CSR状态为S_CSR_MSTATUS_MRET。 第34~48行,一个时钟切换一下CSR状态。 接下来就是写CSR寄存器操作,需要根据上面的CSR状态来写。
第11~15行,写mepc寄存器。 第17~21行,写mcause寄存器。 第23~27行,关闭全局异步中断。 第29~33行,写mstatus寄存器。 最后就是发出中断信号,中断信号会进入到执行阶段。
有两种情况需要发出中断信号,一种是进入中断,另一种是退出中断。 9~12行,写完mstatus寄存器后发出中断进入信号,中断入口地址就是mtvec寄存器的值。 第13~15行,发出中断退出信号,中断退出地址就是mepc寄存器的值。 . 4.11 JTAG JTAG作为一种调试接口,在处理器设计里算是比较大而且复杂、却不起眼的一个模块,绝大部分开源处理器核都没有JTAG(调试)模块。但是为了完整性,tinyriscv还是加入了JTAG模块,还单独为JTAG写了一篇**《深入浅出RISC-V调试》,感兴趣的同学可以去看一下,这里不再单独介绍了。要明白JTAG模块的设计原理,必须先看懂RISC-V的debug spec。 . 4.12 RTL仿真验证 写完处理器代码后,怎么证明所写的处理器是能正确执行指令的呢?这时就需要写testbench来测试了。其实在写代码的时候就应该在头脑里进行仿真。这里并没有使用ModelSim这些软件进行仿真,而是使用了一个轻量级的iverilog和vvp工具。 在写testbench文件时,有两点需要注意的,第一点就是在testbench文件里加上读指令文件的操作:
第2行代码的作用就是将inst.data文件读入到rom模块里,inst.data里面的内容就是一条条指令,这样处理器开始执行时就可以从rom里取到指令。 第二点就是,在仿真期间将仿真波形dump出到某一个文件里:
这样仿真波形就会被dump出到tinyriscv_soc_tb.vcd文件,使用gtkwave工具就可以查看波形了。
|