在总线章节,我们需要将单周期处理器慢慢转变为多周期处理器。讲义提供了异步总线的思路:master发送valid信号,表示自己当前信号合法,slave发送ready信号,表示当前信号已经收到。达成握手的效果。
讲义里首先要求增加一个只读取的sram给IFU使用,用于读取指令。这个很好实现,sram元件里只需要 rdata <= pmem_read(raddr)就行。为了复用代码,再加上之后还需要合并,sram元件之后还需要加上更多功能,而IFU这里只连接读取相关的线。
在增加sram以后就需要考虑信号的问题。我遇到的额外情况是:第一个周期inst还没取到,默认是0,处理器会报错,我的处理方法是时序逻辑里首个周期单独处理,这样就不会出问题。其他情况下:
ifu_valid和idu_ready有效 ---> pc更新,ifu_valid准备无效(因为下个周期要取指,旧指令还没更新)
剩余情况 ---> ifu_valid都可以有效。
讲义一开始就讲异步握手信号的增加其实不太合理,在单周期改多周期的时候,这些信号其实影响不大。在加入sram,需要等待信号读出时,信号才有了必要。
对于握手信号,除了讲义上说的,还有一些内容需要补充:
1. 如果一个元件里有时序相关的内容,比如等待sram读取,那么握手信号也要写到时序逻辑里。如果没有,那就应该写到组合逻辑(时序逻辑也行)
2.对于用到了时序逻辑的信号,可以用状态机来帮助整理思路。如果只是一点点撞,那效率真的很低。
3.一个元件的信号不应该透传,比如exu_ready不应该直接赋值给idu_ready。
--------------------- ------------------------------------ -------------
在增加了IFU的sram以后,现在所有指令的执行就分成了两个周期:第一周期IFU发出请求等待取指,第二周期取到指令开始执行。现在需要的就是给EXU也增加一个sram。
EXU这边需要的是一个可读可写的sram,虽然讲义里说ren可以没有,但个人建议最好加上,这样能防止读出不需要的数据,此外可以考虑加上read_valid信号,这样EXU的状态处理会方便一些。
always @(posedge clk) beginif(ren) beginrdata <= pmem_read(raddr);rvalid <= 1'b1;endelse beginrvalid <= 1'b0;endendalways @(posedge clk) beginif (wen) beginpmem_write(waddr, wdata, wmask);endend
sram连接以后,就需要考虑exu的信号。exu这边只需要接收idu_valid,输出exu_ready。对于其他指令,EXU只需要一个周期执行,写入sram和写入寄存器都是时序逻辑下个周期完成。对于load类指令则需要两个周期(如果之后加入sram延迟那就需要更多,这也是增加read_valid信号的原因)。 我给exu增加了一个状态位,有IDLE和WAIT_LOAD两个状态。
IDLE状态下,如果此时idu_valid有效且是load类指令,下一个状态就转WAIT,并且exu_ready无效,因为当前周期要读数据,下个周期EXU还要执行
其他情况下,下个周期exu_ready有效并且继续IDLE状态。这是非load指令会发生的情况。
WAIT状态下,如果read_valid(sram的读取成功标志)有效,那么下个周期就回到IDLE,exu_ready也恢复有效。read_valid有效说明sram的数据已经取到了,当前周期执行完,下个周期就可以恢复。
其他情况下,说明还没取到数据,要继续保持state和exu_ready。 这个状态转移应该可以应对任意延迟了。
(也许直接利用exu_ready更简洁一些)
再加上这一段后,load指令对应的就是三个周期,其他指令两个周期。
----------------------------------
完成这两个元件以后,idu的信号也要处理。idu一方面要接收ifu_valid和exu_ready,另一方面要发出idu_ready和idu_valid。 我的idu没有时序逻辑,那直接idu_valid赋值1就行。但是idu_ready需要在exu_ready有效并且是load指令时无效( exu_ready有效是因为时序信号慢一拍)。如果不确定自己的设计,可以用波形配合verilog的打印来查看,效果很好。
虽然讲义建议使用difftest等功能来debug,但是有需要的时候也可以考虑直接看波形。在一开始的c++仿真代码里就添加了采集波形的功能,直接用gtkwave就能打开。