首页 > 其他分享 >设计一个采用三级流水线、顺序、单发射、单核的32位RISC-V处理器

设计一个采用三级流水线、顺序、单发射、单核的32位RISC-V处理器

时间:2023-03-09 09:37:06浏览次数:468  
标签:单核 中断 32 跳转 RISC pc 指令 模块 寄存器

 

 一切从头开始

 

1.目的:单周期单指令cpu  只要加法RV32I

 

通过最后测试需要五条,后面说原因,add addi bne jal(跳转并存储一个地址) lui把立即数做为高位存储在寄存器中

 

为了完成add,就要找到最基本的RV32I的指令集结构,有6种指令集类型

 

 

 

  1. 处理器结构分析:riscv采用哈佛结构即指令存储器与数据存储器分开

 

 

 

  1. 五条指令:add addi bne(条件跳转,不相等跳转即减法不为0跳转) jal(无条件跳转) lui

 

因为测试add指令时官方给的指令兼容性测试中需要五条指令才能完成对ADD指令的测试,所以需要实现单周期的五条指令。

 

  1. 设计模块:如上图所述。

 

 

 

 

 

 

 

一. 指令存储器 (一般用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,组合逻辑

  1. 给目前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,则结果正确,并非像单周期一样等一个周期

  1. 添加指令,看乘除法模块            
  2. 加访存指令时,需要在译码阶段就要向总线发出访存请求,和总线设计相关所以提前一个周期,找到相关点在哪,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,组合逻辑

 

  1. 给目前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,则结果正确,并非像单周期一样等一个周期

 

  1. 添加指令,看乘除法模块            
  2. 加访存指令时,需要在译码阶段就要向总线发出访存请求,和总线设计相关所以提前一个周期,找到相关点在哪,相关点在总线切换主设备时是需要一个时钟周期的,所以需要在译码就给出总线请求,但原代码中,总线切换写成了组合逻辑,所以不用提前一个周期译码就给出总线请求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选择比特位

 

 

 

  1. 问题:tiny用的是若跳转直接给出pc地址,然后冲刷流水线机制(用Nop指令实现)
  2. 如何将数据通过总线写入外设:不加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指令(分别用于求有符号余数和无符号余数)

 

乘法:

  1. mul 忽略溢出:截取低位
  2. mulhu 无符号相乘,截取高位
  3. mulh 两数相乘,都视为2的补码,将结果高位写到rd
  4. 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也在同时被冲刷),加油。

 

除法器采用试商法:

  1. 采用状态机完成,四个状态 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. 进入异常时的流程:

 

(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,仅为机器模式

 

第一部分执行状态跳转,第二部分执行具体内容,合并到一起!

 

  1. 退出异常时的流程:必须使用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优先级?

  1. CSR寄存器本身也是一个寄存器,当做寄存器读写即可,也有可能需要初始化数据(相当于软件可以置位)。
  2. 中断来临时要CSR里写数据,mcause,mtvec,mepc,mstatus等,然后要从CSR寄存器里读取出这些数据继续操作。
  3. 中断来临时要用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发现错误

  1. 在csr_regs中,发现输入正确时,数据却写不到正确的寄存器里,发现问题所在是在clint_we_addr模块中,错写成了用csr_waddr[11:0]去判断地址,前一个指令的地址csr_waddr为0,所以数据写不进去。
  2. 在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

相关文章