一切从头开始
1.目的:单周期单指令cpu 只要加法RV32I
通过最后测试需要五条,后面说原因,add addi bne jal(跳转并存储一个地址) lui把立即数做为高位存储在寄存器中
为了完成add,就要找到最基本的RV32I的指令集结构,有6种指令集类型
- 处理器结构分析:riscv采用哈佛结构即指令存储器与数据存储器分开
- 五条指令:add addi bne(条件跳转,不相等跳转即减法不为0跳转) jal(无条件跳转) lui
因为测试add指令时官方给的指令兼容性测试中需要五条指令才能完成对ADD指令的测试,所以需要实现单周期的五条指令。
- 设计模块:如上图所述。
一. 指令存储器 (一般用rom) 复位不要用循环,占用逻辑资源
一. 取指
完成
二.译码保持细心和耐心,纯组合逻辑电路(将指令对照指令集翻译出来,看rs1 rs2 rd imm 操作类型分别是什么?)
1.根据不同的指令集(相当于字母用中文还是英文翻译)译码出的结果不同
由于riscv指令集非常的规范,所以译码时位置固定,对译码模块设计友好,例如opcode,
看一条指令到底实现什么功能,就看opcode,func3,func7
Lb lh lw lbu lhu属于L型指令,并非I型指令
五种立即数扩展方式
比如ADD指令,先看操作码,判断是什么类型指令,若得知是ADD指令(R为寄存器类型),则要将wr_en,wr_addr,reg1_raddr,reg2_raddr就要进行响应的赋值
若根据操作码识别出跳转指令,则要进行相应的使能去驱动mux_pc模块进行地址的选择
Alu_src_sel 每次alu运算时只能有两个值,该值意思为选取的数值从寄存器来
Alu_op是具体的操作类型
Lui指令并不是在alu里进行移位,因为U型指令本来立即数给的就是高位,不用再进行左移12位,所以进行一个+x0寄存器的值放到rd即可。
指令测试文件好好解读,明白
三. 寄存器堆(先写后读)和立即数的设计(注意拼接操作,20个0 = {20{0}})
猜测:如果寄存器复位清零,那么想要读数据的时候可能读不出来所以不能清零
立即数扩展:把立即数摆到该摆放的位置,然后高位符号位扩展低位放0,例如I指令,把12位立即数按照要求放到立即数该放的位置,然后进行符号位扩展。
四.ALU模块:怎么算,算什么
完成
五.测试单周期五指令的riscv是否成功,需要用测试文件,首先知道测试怎么进行,
看汇编指令文件(tinyriscv里有),学伪指令:nop
li 指令是将立即数+0 放到rd 相当于直接把立即数放到目标寄存器中
看cpu是否通过侧测试,是要关注汇编指令(我能看懂的指令)对应的结果是否和CPU运行指令(给CPU运行的指令),是否结果一致,而是否结果一致就看哪些通用寄存器是否值一致,若一致则通过
注意:在rd寄存器写时,时钟上升沿到来时才会写进去(前面模块设计中设定的)
C语言通过make编译成bin文件,然后转换成mem存到rom中(没有bin文件就要先生成bin文件)
1.目前只扩展R型指令,I型指令,两条J型指令,B型指令
2.将pc_reg,mux_pc合并,并将jump ifb zero 统称为jump_inst(是从执行模块回溯)
3.去掉立即数模块,译码模块直接给出扩展立即数值
4.译码时,只给出寄存器源操作数索引,目的寄存器使能,目的寄存器地址,若PC发生变化也要给出op1,2的jump值方便后续执行计算pc ,输出当前地址和指令目的是给执行阶段,通过索引从寄存器堆取出响应的值
目前只有取值,译码,执行三个阶段
rd = rs1 << rs2 的意思: rs1寄存器的数左移rs2的值(rs2 5bit可以表示为32位,最多移32位)的位数,>> 无论右移还是左移,与之对应的是移动位数!!!
错误一:按位与和逻辑与逻辑搞错,只要是与,只有两个都为1结果才为1,两个0相与为0 (注意)
算数右移:1.正常逻辑右移 2.mask逻辑右移 12相与为3
4.最高位扩展 5.mask逻辑右移取反 45相与6
3 | 6 即可
错误二:向rd所对应的寄存器写1,32位1 否则32位0,有符号在Verilog中表示为$signed
错误三:立即数扩展时将指令和指令地址搞错!!!
Inst_mem模块相当于tiny的rom,组合逻辑
- 给目前27条指令的cpu加触发器,形成多周期流水线的CPU(tiny加的太麻烦了,自己加),而流水线的目的就是为了缩短触发器和触发器之间的逻辑延迟,在不降低吞吐率的情况下,提升了电路的工作频率。
在添加流水线过程中,将译码后的rd wen不要直接给regs通过ex一起给,更加直观方便(21号完成多周期设计(22号把剩余的图补全),22号测试)
22日测试通过!!!修改1:在通用寄存器处读寄存器时,需要进行修改(防止读的数据是下一拍写进去的数据) 修改2:例化ifu时,在一次修改中pc_pc_o给注释掉了,导致里面两个模块没有连接起来所以不出数据 修改3:tb文件因为是多周期cpu,目前是三级流水线,所以tb文件看最后s10 s11的值时,等S10=1之后,需要等待三个周期若S11=1,则结果正确,并非像单周期一样等一个周期
- 添加指令,看乘除法模块
- 加访存指令时,需要在译码阶段就要向总线发出访存请求,和总线设计相关所以提前一个周期,找到相关点在哪,l指令和S指令的具体意思
tiny的设计:整体例化时没有单独的inst_mem,给出pc,应该是传到总线,总线从rom中取出地址,目前暂且把pc_reg和rom放到一起
将不同时期的代码分别放到不同的文件夹中,方便管理,信号命名要一致!!!
用vivado仿真时添加代码时要勾选赋值代码到此工程的选项!!!
在vivado工程文件里修改代码,vivado会自动同步!写代码的时候将不同文件放到不同的文件夹中,一定要做好备份
C语言通过make编译成bin文件,然后转换成mem存到rom中(没有bin文件就要先生成bin文件)
1.目前只扩展R型指令,I型指令,两条J型指令,B型指令
2.将pc_reg,mux_pc合并,并将jump ifb zero 统称为jump_inst(是从执行模块回溯)
3.去掉立即数模块,译码模块直接给出扩展立即数值
4.译码时,只给出寄存器源操作数索引,目的寄存器使能,目的寄存器地址,若PC发生变化也要给出op1,2的jump值方便后续执行计算pc ,输出当前地址和指令目的是给执行阶段,通过索引从寄存器堆取出响应的值
目前只有取值,译码,执行三个阶段
rd = rs1 << rs2 的意思: rs1寄存器的数左移rs2的值(rs2 5bit可以表示为32位,最多移32位)的位数,>> 无论右移还是左移,与之对应的是移动位数!!!
错误一:按位与和逻辑与逻辑搞错,只要是与,只有两个都为1结果才为1,两个0相与为0 (注意)
算数右移:1.正常逻辑右移 2.mask逻辑右移 12相与为3
4.最高位扩展 5.mask逻辑右移取反 45相与6
3 | 6 即可
错误二:向rd所对应的寄存器写1,32位1 否则32位0,有符号在Verilog中表示为$signed
错误三:立即数扩展时将指令和指令地址搞错!!!
Inst_mem模块相当于tiny的rom,组合逻辑
- 给目前27条指令的cpu加触发器,形成多周期流水线的CPU(tiny加的太麻烦了,自己加),而流水线的目的就是为了缩短触发器和触发器之间的逻辑延迟,在不降低吞吐率的情况下,提升了电路的工作频率。
在添加流水线过程中,将译码后的rd wen不要直接给regs通过ex一起给,更加直观方便(21号完成多周期设计(22号把剩余的图补全),22号测试)
22日测试通过!!!修改1:在通用寄存器处读寄存器时,需要进行修改(防止读的数据是下一拍写进去的数据) 修改2:例化ifu时,在一次修改中pc_pc_o给注释掉了,导致里面两个模块没有连接起来所以不出数据 修改3:tb文件因为是多周期cpu,目前是三级流水线,所以tb文件看最后s10 s11的值时,等S10=1之后,需要等待三个周期若S11=1,则结果正确,并非像单周期一样等一个周期
- 添加指令,看乘除法模块
- 加访存指令时,需要在译码阶段就要向总线发出访存请求,和总线设计相关所以提前一个周期,找到相关点在哪,相关点在总线切换主设备时是需要一个时钟周期的,所以需要在译码就给出总线请求,但原代码中,总线切换写成了组合逻辑,所以不用提前一个周期译码就给出总线请求l指令和S指令的具体意思
目标:加l与S指令,其中会设计到总线知识(通过总线写和总线读),完成这两个模块
一, LS指令需要添加读mem_addr_o mem_rdata_i 写mem_wdata_o mem_we mem_wddr_o 以及总线请求 总线有一个作用应该是将信号进行一个转换 由于这俩指令没有跳转地址,所以译码中的op1,2_jump直接赋0;需要的地址是mem里的地址,这里的地址是与rs1 和imm有关,所以给op1,2赋值并将结果加起来给地址即可,把地址通过总线给ram的addr_i,会给出数据data_o,应该是通过总线转换变成mem_rdata_i
总线:4个主设备,6个从设备
1.仲裁顺序:主设备3>主设备0>主设备2>主设备1 将取指放到最后是因为一直赋1,若有优先级比它还低的,就无法执行,所以优先级都要高于1
m3_req是串口下载请求
m0_req是ex模块请求
m2_req是jtag的操作请求
m1_req是pc_reg取指令请求(因为要连续不断的取指令,所以m1_req一直为1(所以就不需要pc_reg发出请求,默认一直发),主设备1是取指模块)只要grant变化,就不继续取值,所以只需要处理中间部分的指令无效即可(具体用流水线冲刷机制)
2.为什么从设备地址前四位要用0补齐,因为设计的是用32位地址总线,而后28位的地址对于从此设计的外设来说已经够用(猜测),所以设计前四位为slave选择比特位
- 问题:tiny用的是若跳转直接给出pc地址,然后冲刷流水线机制(用Nop指令实现)
- 如何将数据通过总线写入外设:不加JTAG和UART时,只能用到两个主设备即pc=m1 ex = m0; grant的选取,选取结束后,从哪能体会出grant给与主设备了呢?答:选取主设备结束后,响应的从设备选择结束后,只有对应的总线位置是可以打通的,其余都是0,相当于只有打通的那部分进行信号传递
目标:1.先通过总线完成取指令00000000(具体参数根据defines完成)
2.在只有ex grant 和 pc grant 情况下 完成 soc模块
在完成1的过程中,即写rib模块时,不要将主从固定,类似于m0主从一起定义,因为主从没有关系,完全根据req和地址进行主从选择,
在完成目标1时,主机为什么一定会选择rom即slave0(因为rom一共4096深度,即用12位即可表示地址,而目前给了31位表示地址,用第32位来判断是否从机为rom,pc开头必为0,所以一定会选择rom,至于主机如果换掉的话,相应的从机地址也会换掉)
目标一完成!可喜可贺,继续加油!
BUG总结:
1.在写总线模块时,由于相似的信号太多且没有看画好的图进行连线导致出错。
2.tb模块时把32个通用寄存器拉出来看的时候,看的是通用寄存器,而今天tb一开始将rom的前32条指令拉出来了。
3.在rom的读模块,复位的时候给data_o赋为0,和tb复位信号冲突,具体冲突过程是tb中由于设置的时间问题,复位时指令为0然后直接跳到第二条指令,解决此问题方法是将tb复位时间提前到第一个周期内即可。
问题:在总线中一开始定义全为0,所以inst_ifu_i才为0,为什么会出现下面这种情况?在Vivado riscv里也出现了,写使能为什么一开始就拉高
找到问题是因为ifu_id流水线模块,复位时给pc_o inst_o赋值导致的,但不知道原因,复位时赋其他值,也不会拉高写使能,但赋0会,但不会影响最终结果。
目标二开启时间:10.25
在完成目标2的过程中发现在S存储指令中发现一个小问题,什么小问题?
我目前的译码模块最后给出结果中只有两个可操作的数即op1_o,op2_o,而S指令至少需要3个可操作的数,所以在执行时,就要将其中rs2寄存器里的数拿出来给mem_wdata,而这个数据是从id模块输出给的,这就能理解为什么tinyriscv中为什么要在译码模块加3个操作数了,所以需要对图2做改动防止S指令操作数不够,输入多余信号,输出没用也可以,目前多余了一个操作数
问题2:在执行到S指令时,会使得pc不取指,怎么解决?只需要在遇到S指令前将m0_req拉低即可,这样m1_req就继续取指令 错,应该用类似流水线冲刷方法
目前L和S指令用到的接口都已经完成,下午soc看L指令,然后增加S指令
L指令添加完毕,个人设计在L指令后面将req请求拉低,如果不拉低,pc_o继续增加,但主机并没有权限从ROM中取指,因为是组合逻辑,这样做会使得req总线请求并未拉高
一、
1.在测试L指令过程中发现,自己设计在L指令之后复位req信号会导致根本不访问从,因为ex模块是纯组合逻辑模块,没有时延
2.但若不拉低有一个问题:虽然测试结果正确,但在执行00000010地址的指令时,主机是m0,而此时也同时在执行00000018地址的取值,主机是m1,由于同一时刻总线只能一主一从,所以00000018地址的数据就无法取出为rib总线复位值0;
在测试过程中发现tinyriscv已经确定好了总体架构以及从机地址空间,目前有6个从机每个从机是256MB,所以必须要用高4位来判断属于哪一个从机模块,具体的从机模块里面可以不用填满256MB,用低地址位来判断具体操作哪个地址的值。大胆猜测,若向存储器里读写数据ram,mem地址高四位一定是1。
二、解决跳转流水线冲刷问题(跳转和加LB指令都用得到)
使用NOP指令,该指令在riscv中是固定指令(即32’h00000001),因为跳转指令只有在执行阶段才知道是否跳转,而多周期处理器这时候已经加载了ifu_id输出pc inst 以及pc_reg的pc 以及指令(因为目前总线读指令是组合逻辑无延迟),若有跳转则要冲刷两个模块的指令pc值保持不变,指令赋为NOP指令即可。作图3
首先先改,没有LB指令的模块(rom_soc),主要是完成跳转指令时的问题,(此时加的模块适用于加LS指令模块),虽然跳转指令可以执行且能正常,但有一个问题,是否跳转在执行才能知道,前面两条已经加载了ifu_id输出pc inst 以及pc_reg的pc
写CTRL模块时,没加宏定义的包含头文件include!!!
Rom_soc加流水线冲刷机制成功,并测试了JAL指令,在最后的指令中并没有执行fall的指令,完成测试(tb中S10与S11的成功间隔仍为1,原来设置为3就是因为没加冲刷机制,执行了pass 之前的两条fall指令)!!!
目前LS工程里,跳转模块是不太对的(主要是没修改冲刷流水线模块),先修改跳转模块即加冲刷流水线,修改完成,再加总线被占用问题,加完了且测试正确。
11月2日,目前需要根据riscv设定好的架构,修改从机地址(4位),本来自己想的是两个从机就用地址最高位来决定选哪个,但是每个从机是256MB,则需要用最高4bit来判断,若用最高1bit判断的话,永远到不了rom以外的从机。(大胆猜测若是写,地址高四位一定是0001,加完S指令验证猜想是否正确)
L指令测试完成,改rib总线,完成。
继续增加S指令
目标二完成!!!恭喜恭喜,仔细细心
1.在总线模块s0_data_o处,错将其定义为1位,导致错误
2.将S类指令起初与L类指令理解混淆,举例:LB指令是将不同字节的数取出来放到低位进行符号位扩展而SB指令是将reg2_rdata_i的低字节,发放到目标地址中,但只更新一字节,存储器里其他值不变。不会指令就看reference,
我自己想法:将reg2_rdata_i取出某一字节后放到目标地址中进行符号位扩展
乘法涉及到的指令参见reference 一共四条,除法涉及到好像也是4条
除法中,大多数是将商写入目的寄存器,所以有div、divu有符号和无符号整数的除法指令,但也有少些时候是将余数写入rd寄存器,所以也有了rem、remu指令(分别用于求有符号余数和无符号余数)
乘法:
- mul 忽略溢出:截取低位
- mulhu 无符号相乘,截取高位
- mulh 两数相乘,都视为2的补码,将结果高位写到rd
- mulhsu rs1有符号×rs2无符号 看rs1 最高位是1,则结果取补码,0不管,将结果高位写到rd。
2.15:1.完成乘法指令并通过测试 2.完成除法指令并通过测试。
在完成1的过程中,因为rs1,rs2都要用负数表示,我没考虑到,只考虑到了乘法结果用负数表示。
完成乘法模块,看下除法模块
以上修改于D:\ic\lxy\project_lxy\CPU2更接近版本\core\LSinst_tinyriscv\core中
乘法指令完成,验证通过。乘法中注意负数的补码还是代表负数,补码乘完后,需要看结果是否为正,若不为正,还要再次取补码。
重点:除法执行时需要暂停流水线,因为试商法至少需要32个时钟周期
试商法:
四个状态:第一个状态是将输入给进来,第二个状态是判断输入是否为0并给出输入值(补码),以及正负,第三个状态正式开始计算。
目前在rom_soc测量正确,在LS_soc中修改除法模块
如果当前指令是除法指令,则需要跳转到下一条指令,跳转地址是pc_i+4
正常指令和除法指令共用一个数据通路。译码阶段主要判断reg写使能,在执行阶段判断div写使能,地址是类似方法赋值的。除法指令来之后,将中断放在ctrl模块控制。
完成情况:除了DIV模块,整体外部构造完成,逻辑目前也讲得通,后面继续完成DIV子模块,再集成起来看是否能跑通。
DIV模块不要参考tiny写,比较不清楚,用三段式状态机比较合适。
https://blog.csdn.net/blueteeth_/article/details/128888764
集成结束后跑不出结果,找问题!!! 大概率是控制信号出错。
试商法看了大概80%
找出控制信号哪里出错。
Debug:
1.都没有给时钟,输出信号怎么变化?
分析到了具体的每一个信号,输入给好了,输出却没有,发现了时钟问题
2.DIV模块触发条件手误,写错了(错写成了posedge rst_n),结果是没触发,所以一直为X状态
3.集成时,定义的连接信号在div_reg_waddr_i接口上出错,导致DIV输出并没有给到Ex的输入模块。
4.Ctrl模块图中给了div_start,但在集成时忘记加上此端口,所以Ctrl模块输入为Z,导致hold_pc接口信号为Z。
证明了div_start处理是正确的(1是流水线要中断,2是pc端需要保持,若不保持,流水线被冲刷的同时,pc以及inst也在同时被冲刷),加油。
除法器采用试商法:
- 采用状态机完成,四个状态 IDLE、START、CALCULATE、END
CALCULATE中使用count移位,若|count为1继续操作,若counter不为1(说明32次操作完成),则跳到END状态,且要判断minuend是否大于等于divisor,若成立则,余数等于相减,若不成立,则余数等于minuend。
对于I指令子集来说,目前还差这十条指令,完成的只剩下最高两条指令了。
RISCV中每一种模式都有其对应的扩展指令集和CSR寄存器,机器模式中断和异常相关的CSR寄存器都是以m开头
一、 中断(中断返回),本质上是一种跳转,但需要增加一些读写CSR寄存器的操作
RISC-V有两种中断,一种是同步中断(异常属于CPU内部),即ECALL、EBREAK等指令产生的中弄断,执行到EBREAK时,能够精确地定义到是哪一条ebreak指令造成的。同步异常在每一次执行时都可以精准定位是哪一条指令,而异步中断(中断)在同样的环境下执行很多遍,每一次发生异常的指令PC可能都不一样。
如何理解:同步中断只可能由内部指令来产生,而异步中断是由外设(UART、GPIO等来产生)
Clint模块:1.中断仲裁(同步中断>异步中断>中断返回),2.CSR状态寄存器变化及写寄存器 3.发送中断信号给执行模块且给出响应的地址。
定义寄存器reg,看是否会来中断(看来的是什么中断:同步中断、异步中断、全局中断)
- 进入异常时的流程:
(1) mtvec是机器模式异常入口基地址
(2) mcause是机器模式异常种类,12中中断,16种异常。
(3) mepc寄存器保存异常返回地址的pc值
但中断和异常的返回地址不同
中断来时,mepc的值被更新为下一条尚未执行指令的pc
异常来时,mepc的值为当前发生异常的pc,如果ecall ebreak产生的异常,需要根据查这两个是几字节的指令从而给mepc响应的+几字节
(4) mtval
(5) mstatus:将MIE域的值更新到MPIE域的值,MIE的值更新为0,MPP域的值一直为11,仅为机器模式
第一部分执行状态跳转,第二部分执行具体内容,合并到一起!
- 退出异常时的流程:必须使用mret指令(该指令也是中断),而sret 和 uret指令仅支持监督模式和用户模式的处理器中使用。
(1) 停止当前程序流,转而从,mepc寄存器定义的pc地址执行
(2) 执行mret指令不仅会让处理器跳转到mepc的pc地址执行,还同时更新mstatus寄存器。
② 更新mstatus寄存器。
(1) 将MPIE域的值更新到MIE域
(2) MPIE域的值更新为1.
先完成CSR寄存器,为了完成CSR指令,因为CSR中涉及到了中断,接口有点多,一点一点增加,完成!!!
测试:可以在已知的反汇编指令里添加CSR指令即可,就可以知道是否读写正确,先留着不测,对于写好的同步中断可以自己加指令进行测试。
1.目前画好了有CSR指令的图,添加了相关指令内容以及CSR写模块,读模块还没有添加,下次来抓紧完成CSR读指令并且测试CSR指令是否正确(并没有专门用于测试CSR的指令兼容性测试文件,猜测是配合中断一起测试)
2.搞清楚mtval的作用,以及ex模块写CSR和clint模块写CSR的优先级
目前的目标是,完成同步中断的测试
对于MCAUSE:有12种中断 16种异常 1为中断,0为异常
像mstatus以及mtvec这种应该都是通过指令从ex模块写进去,此模块没有从中断给mstatus or mtvec,mtvec是一个可读可写的CSR,软件开发人员可以通过软件编程(用指令)更改其中的值,mtvec应该是提前确定好的
上图中32位mcause中最高1位为interrupt域,剩余31位为Exception Code域,所以定时器中断mcause如下所示:
定时器中断mcause=32’h80000004
更新:
核心模块:clint
1.中断仲裁逻辑判断当前属于什么中断,如果正在执行除法指令,同步中断无法打断
异步中断可以打断。
(中断仲裁逻辑判断中断,需要4个状态,同步、异步(计时器中断)、中断返回、外加一个IDLE)
(此中断仲裁逻辑中,同步中断 > 异步中断 >中断返回指令)
2.CSR寄存器状态机跳转(主要看这个),什么时候提取中断返回的地址,和引起中断
的编码(引起中断的原因cause)。 找到之后
3.进行写必要的CSR寄存器(将第二部分得到的值写入对应的csr寄存器)
4.待写完必要的CSR寄存器后,发送中断标志、中断入口地址给ex模块,ex模块根据给的判断
是否跳转以及跳转地址。(这些控制信号给到Ctrl然后Ctrl产生信号控制pc_reg)
流水线的设计中,可以使用同步复位,因为异步复位比较占用逻辑资源
目的主要是打一拍暂存信号,所以不需要复位。
原tiny流水线触发器中,若hold_en,则给输出输规定的默认值(应该是无效指令),
而我自己输出的是全0,因为目前还没有扩展自定义指令集,所以全0指令目前当做
无效指令处理,若后面扩展了全0指令集,当他有具体用处时就不能这样处理。
定时器中断对应中断输入是8'b00000000
一开始关闭全局中断
CSR寄存器状态处于MCAUSE MSTATUS_MERT时发送中断标志以及地址
中断本身也是一种跳转,只不过要写必要的CSR寄存器,
中断来临时需要给不同的CSR寄存器写值,要从里面取出执行的值,因为跳转标志和地址
设置到了执行输出,所以跳转地址要给到ex输入。
下一步计划:按照源代码的顺序先写下来,然后再测试,看看到底跳转地址是个什么情况?
为什么CSR寄存器中,执行的写CSR优先级要高于中断的CSR优先级?
- CSR寄存器本身也是一个寄存器,当做寄存器读写即可,也有可能需要初始化数据(相当于软件可以置位)。
- 中断来临时要CSR里写数据,mcause,mtvec,mepc,mstatus等,然后要从CSR寄存器里读取出这些数据继续操作。
- 中断来临时要用CSR里的数据,如果正常写优先级低,则有可能出现无法取到正确CSR值的效果。
定时器中断是timer定时器模块给的,若中断则8位中的最低位为1,所以在clint模块判断中,若中断输入信号不为8’h0且全局中断使能为1时(全局中断使能是mstatus里的值),才能触发异步中断。
目前先把同步中断的流程梳理一遍,写完同步中断并进行测试。!
1.初步将中断模块/CSR模块写完,明天继续完善 如果能测试就测试!
2.集成还差一个clint模块,执行阶段还没给int_flag和Int_enter_addr(ex模块结束)
3.1集成完成 基本代码没问题
内部逻辑有可能错误,有就及时纠正。(明天看清楚状态机状态),目前在ADD代码中第二条增加了同步中断指令,没加中断返回指令。
3.2发现错误
- 在csr_regs中,发现输入正确时,数据却写不到正确的寄存器里,发现问题所在是在clint_we_addr模块中,错写成了用csr_waddr[11:0]去判断地址,前一个指令的地址csr_waddr为0,所以数据写不进去。
- 在clint模块中又发现一个问题,什么时候clint中断拉高?只要中断状态不处于IDLE 或 CSR寄存器状态不处于IDLE就拉高,原本写的是相与。
目前需要解决的问题:给好中断返回地址后,需要取消流水线暂停以及pc的保持
Int_flag只有在csr_state处于mcause时才会拉高,这里拉高两位,在csr_state又回到IDLE时并没有设计默认值,所以自动保持。发现这个问题后,又发现如果处于IDLE,其余未使用到的信号也需要拉低。
好像发现问题了:
有两种办法,第一种办法就是,错误将else begin中int_state保持了,导致中断一直拉高没有办法拉低从而在Ctrl模块控制流水线。
解决办法:给ctrl模块int_flag_o,当该信号拉高时,暂停流水线中断以及pc取指,优先级大于hold_flag_clint_o导致的流水线中断以及pc保持。
第二种办法,直接将else begin后的int_state = Int_State_IDLE;
红圈导致的hlod_flag_clint_o拉高,是为了中断刚来就要暂停流水线和pc,蓝圈的拉高是为了写csr寄存器所以拉高,当写完后即再一次处于IDLE状态时,这时候就要恢复流水线和pc让其继续执行!
修改后如下图所示:
中断前半部分搞定!!!有空验证一下方法二,然后加上mret指令后,验证同步中断是否完成。
用mtvec指令写CSR指令时发现csr_regs模块中csr_we_i一直拉高,从而找到id的原因
没有初始化,导致错误
初始化后结果正确
发现csr_regs bug,发现写进去的mtvec的值并没有提取到,而被更改成了0,进一步发现csr_regs的问题
在写的时候else情况归零了,所以没取到mtvec,保持即可。
修改结束后测试正确:
01c00113 //给sp_x2寄存器写28
01a00093 //给ra_x1寄存器写26
00208f33 //ra+sp给到t5 //00400113 //给sp_x2寄存器写4,往回跳,得往后跳
305f1173 //把t5的值给mtvec //30511173 //将4读取到csr_mtvec里,作为返回地址
00100073 //同步Ebreak指令
测试成功,再测试返回
测试完成
中断来临时未遇到跳转的情况用ADD测试完成。
中断来临时遇到跳转的情况用bne指令测试
跳转优先级高于hlod_pc,所以在hold_pc拉高时,pc_o还能变化
修改写入csr寄存器的值时:修改inst_addr,发现怎么修改都没用,最终发现问题,clint模块定义了jump,ex模块定义了jump,但并没有集成到一起,所以不变。
修改后结果正确,波形如下所示:
至此,同步中断验证完毕!!!恭喜恭喜!!!
标签:单核,中断,32,跳转,RISC,pc,指令,模块,寄存器 From: https://www.cnblogs.com/lxy0401/p/17197092.html