这篇随笔记录的是从普通的多周期处理器到加入握手信号和axi-lite协议sram的处理器。
在之前的多周期处理器里,由于结构比较简单,所以我给ifu和exu的握手信号加入的是时序逻辑,idu由于只做解码,所以握手信号放在了组合逻辑里,差不多就和透传差不多。但是加入sram握手信号以后,很多地方都需要再调整。
一开始连接sram的时候,考虑到ifu这边只需要读取,所以写入相关全都空缺。由于读取需要先地址握手,再数据握手,所以ifu这边也增加一个ar_done,用来标记是否地址已握手。关于ifu_valid信号,这个信号应该在读到inst以后赋值为高,在和idu沟通,也就是ifu_valid && idu_ready后设置低。ifu和idu之间的握手我选择短时握手,握手时顺便传递inst,然后就断开。这里的idu_ready表示的就不是“我已经完成当前指令”,而是“我已准备好接收下一条指令”。和讲义稍微ie有点区别。
在实际运行的时候,发现load指令可以运行,但是write异常,提示地址错误,检查地址发现意外的小。起初我怀疑过是当前store指令处理的有问题,但是检查后发现不应该。问题应该出在别的指令上。打印寄存器,发现值意外的全是0. 和nemu逐条指令做对比,发现了问题:以前的指令严格遵守2/3周期,寄存器的写入会被推移到下条指令的首个周期开头,所以没有出问题。
但是升级握手以后,寄存器的写入也会发生在当前指令周期内,成功写入后指令还在运行,此时wdata已经被清除,但是写使能还开着,导致二次写入,寄存器又被置零。
80000000: 00000413 li s0,0
80000004: 0019a117 auipc sp,0x19a
80000008: ffc10113 addi sp,sp,-4 # 8019a000 <_end>
8000000c: 2ed5c0ef jal ra,8005caf8 <_trm_init>
可以看到,上一个寄存器表里,sp寄存器是有值的(在更上一个周期准备好了值,这个周期被写入),但上面的wdata已经变成了0,在下一周期的寄存器表里就可以看到sp寄存器又变成0了。
此外还注意到如果程序有问题导致模拟器中途报错退出,那波形不会被记录。考虑到这一点,我让ebreak以外的退出点都改调用ebreak,然后在ebreak里增加退出时波形的保存,然后再exit,这样就可以保证cpu仿真内部报错也能记录下波形。
检查波形,发现十分奇怪,最后发现是因为addi指令处理有问题,之前保留的加法器的代码需要删除,但是修改以后,发现store指令仍然存在问题。
调整完store以后发现load指令又出问题,一条条指令排查发现是因为下一条指令更新时旧指令还在,导致向sram发出握手请求,但是又不能提供正确地址导致报错。
做到这里,我的思路就完全改变了:单周期的时候基本只有组合逻辑。而现在,组合逻辑是时序逻辑的附属,时序逻辑负责控制,在某一个状态下才去执行自己对应的组合逻辑。idu这里原本不留周期给它,现在idu也使用时序逻辑,如果ifu_valid && !idu_valid,表示指令已经准备好并且idu不繁忙,那就设置idu_valid和idu_ready,一边ifu握手,一边给exu通知准备执行。
传递信号时的一个准则:记清楚valid表示master信息有效,ready表示slave已经接收到信号。不要多传。
idu_ready表示idu已经收到了ifu的内容,但exu里jal类指令会改变next pc。那么ifu用ifu_valid &&idu_ready为什么不会导致jal指令错误呢?因为idu_ready信号受ifu_valid和idu_valid两个信号控制,idu_valid受exu_ready控制,所以exu这边更新pc_next前,idu_ready不会更新,那ifu自然也不会更新。
调整以后exu的处理一直状态错乱,在控制台没意识到问题,查看wave时我才意识到,我的ifu也会给idu和exu发送inst信号用于一些特殊处理。idu修改后会缓存inst,exu也应该使用IDU缓存的inst,而不是直接连线ifu,错误连线导致处理一直有问题。修改以后该问题解决。
握手也是分“短时握手”和“持续握手”的,在ifu和idu之间,我用的是短时握手,成功握手确认数据传输后就断开,slave做自己的事。ifu idu之间是握手以后idu继续干自己的,握手同时传递信号,然后ifu就更新pc准备下一条指令(现在来看,我也可以把pc_next连到raddr上,也不是非的连接pc)。exu和idu之间则是长时握手,idu_valid有效以后会一直保持,exu收到信息后就取处理,处理完以后才会通知idu完成。目前来看这个逻辑是可用的。
还发现一个很奇怪的事:一开始sram的读地址连的reg是sram_raddr,每个周期开头清零,然后组合逻辑重新赋值,Pmem会报错地址为0, 但是gtkwave查看波形,却发现sram_raddr没有问题,思考了半天,怀疑可能是因为读到了跳变的0导致报错。于是决定直接把stored_addr连到sram的araddr上,结果不报错了。但是stored_addr在exu收到信息过一个周期才会启动,这样会不会还有问题呢?
在修改以后还是存在类似的问题,只不过换了个地方,我也在思考有没有可能是dpi-c机制的问题呢?但是以前没有出现这个问题。但是我和ai沟通后,决定给exu增加一个common_inst状态,exu平时处于空闲,exu_reaady为0.收到idu_valid以后,下一周期才转入common_inst状态并且exu_ready为1,这样处理指令,就不会紧巴巴地出现 “非load/store指令exu只有一个周期,导致exu无法正确处理自己状态,不能给lad/store指令沟通得到更多周期“ 的情况。问题暂时解决,可以跑起来th-thread。
不过,现在所有串口打印都是双倍打印,但现在能打印就说明程序握手方面应该是正常了。
思路:串口打印只会来自putch函数,在源代码里找到putch或者串口地址,然后根据pc或者inst在波形里查看原因。
下图所示就是putch函数对应的store指令,可以看到,写入操作被执行了两次:
检查sram 的写数据部分:
always @(posedge clk) beginif (!wready && wvalid) beginwready <= 1'b1;end else if (wready && wvalid) beginwready <= 1'b0;w_done <= 1'b1;if (aw_done) begin pmem_write(awaddr, wdata, wstrb);endend else if (bvalid && bready) beginw_done <= 1'b0;end end
发现写数据这里只要aw_done就会值执行写入,没有考虑到已经写入但是还在写sram周期的情况。所以增加条件,如果w_done,也就是已经写入过,那也不再写入,问题解决!