sky 发表于 2020-9-18 09:00:42

从零开始写RISC-V处理器【5】硬件篇(3-完)

本帖最后由 sky 于 2020-9-18 09:19 编辑


从零开始写RISC-V处理器【1】前言
从零开始写RISC-V处理器【2】浅谈Verilog
从零开始写RISC-V处理器【3】硬件篇(1)
从零开始写RISC-V处理器【4】硬件篇(2)
从零开始写RISC-V处理器【5】硬件篇(3-完)

项目地址:https://gitee.com/liangkangnan/tinyriscv
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工具就可以查看波形了。
到这里,硬件篇的内容就结束了。
本系列完!
页: [1]
查看完整版本: 从零开始写RISC-V处理器【5】硬件篇(3-完)